162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic USB GNSS receiver driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2021 Johan Hovold <johan@kernel.org>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/errno.h>
962306a36Sopenharmony_ci#include <linux/gnss.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/usb.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define GNSS_USB_READ_BUF_LEN	512
1762306a36Sopenharmony_ci#define GNSS_USB_WRITE_TIMEOUT	1000
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic const struct usb_device_id gnss_usb_id_table[] = {
2062306a36Sopenharmony_ci	{ USB_DEVICE(0x1199, 0xb000) },		/* Sierra Wireless XM1210 */
2162306a36Sopenharmony_ci	{ }
2262306a36Sopenharmony_ci};
2362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistruct gnss_usb {
2662306a36Sopenharmony_ci	struct usb_device *udev;
2762306a36Sopenharmony_ci	struct usb_interface *intf;
2862306a36Sopenharmony_ci	struct gnss_device *gdev;
2962306a36Sopenharmony_ci	struct urb *read_urb;
3062306a36Sopenharmony_ci	unsigned int write_pipe;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void gnss_usb_rx_complete(struct urb *urb)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct gnss_usb *gusb = urb->context;
3662306a36Sopenharmony_ci	struct gnss_device *gdev = gusb->gdev;
3762306a36Sopenharmony_ci	int status = urb->status;
3862306a36Sopenharmony_ci	int len;
3962306a36Sopenharmony_ci	int ret;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	switch (status) {
4262306a36Sopenharmony_ci	case 0:
4362306a36Sopenharmony_ci		break;
4462306a36Sopenharmony_ci	case -ENOENT:
4562306a36Sopenharmony_ci	case -ECONNRESET:
4662306a36Sopenharmony_ci	case -ESHUTDOWN:
4762306a36Sopenharmony_ci		dev_dbg(&gdev->dev, "urb stopped: %d\n", status);
4862306a36Sopenharmony_ci		return;
4962306a36Sopenharmony_ci	case -EPIPE:
5062306a36Sopenharmony_ci		dev_err(&gdev->dev, "urb stopped: %d\n", status);
5162306a36Sopenharmony_ci		return;
5262306a36Sopenharmony_ci	default:
5362306a36Sopenharmony_ci		dev_dbg(&gdev->dev, "nonzero urb status: %d\n", status);
5462306a36Sopenharmony_ci		goto resubmit;
5562306a36Sopenharmony_ci	}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	len = urb->actual_length;
5862306a36Sopenharmony_ci	if (len == 0)
5962306a36Sopenharmony_ci		goto resubmit;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	ret = gnss_insert_raw(gdev, urb->transfer_buffer, len);
6262306a36Sopenharmony_ci	if (ret < len)
6362306a36Sopenharmony_ci		dev_dbg(&gdev->dev, "dropped %d bytes\n", len - ret);
6462306a36Sopenharmony_ciresubmit:
6562306a36Sopenharmony_ci	ret = usb_submit_urb(urb, GFP_ATOMIC);
6662306a36Sopenharmony_ci	if (ret && ret != -EPERM && ret != -ENODEV)
6762306a36Sopenharmony_ci		dev_err(&gdev->dev, "failed to resubmit urb: %d\n", ret);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int gnss_usb_open(struct gnss_device *gdev)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct gnss_usb *gusb = gnss_get_drvdata(gdev);
7362306a36Sopenharmony_ci	int ret;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	ret = usb_submit_urb(gusb->read_urb, GFP_KERNEL);
7662306a36Sopenharmony_ci	if (ret) {
7762306a36Sopenharmony_ci		if (ret != -EPERM && ret != -ENODEV)
7862306a36Sopenharmony_ci			dev_err(&gdev->dev, "failed to submit urb: %d\n", ret);
7962306a36Sopenharmony_ci		return ret;
8062306a36Sopenharmony_ci	}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	return 0;
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic void gnss_usb_close(struct gnss_device *gdev)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct gnss_usb *gusb = gnss_get_drvdata(gdev);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	usb_kill_urb(gusb->read_urb);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int gnss_usb_write_raw(struct gnss_device *gdev,
9362306a36Sopenharmony_ci		const unsigned char *buf, size_t count)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct gnss_usb *gusb = gnss_get_drvdata(gdev);
9662306a36Sopenharmony_ci	void *tbuf;
9762306a36Sopenharmony_ci	int ret;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	tbuf = kmemdup(buf, count, GFP_KERNEL);
10062306a36Sopenharmony_ci	if (!tbuf)
10162306a36Sopenharmony_ci		return -ENOMEM;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	ret = usb_bulk_msg(gusb->udev, gusb->write_pipe, tbuf, count, NULL,
10462306a36Sopenharmony_ci			GNSS_USB_WRITE_TIMEOUT);
10562306a36Sopenharmony_ci	kfree(tbuf);
10662306a36Sopenharmony_ci	if (ret)
10762306a36Sopenharmony_ci		return ret;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return count;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic const struct gnss_operations gnss_usb_gnss_ops = {
11362306a36Sopenharmony_ci	.open		= gnss_usb_open,
11462306a36Sopenharmony_ci	.close		= gnss_usb_close,
11562306a36Sopenharmony_ci	.write_raw	= gnss_usb_write_raw,
11662306a36Sopenharmony_ci};
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	struct usb_device *udev = interface_to_usbdev(intf);
12162306a36Sopenharmony_ci	struct usb_endpoint_descriptor *in, *out;
12262306a36Sopenharmony_ci	struct gnss_device *gdev;
12362306a36Sopenharmony_ci	struct gnss_usb *gusb;
12462306a36Sopenharmony_ci	struct urb *urb;
12562306a36Sopenharmony_ci	size_t buf_len;
12662306a36Sopenharmony_ci	void *buf;
12762306a36Sopenharmony_ci	int ret;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
13062306a36Sopenharmony_ci			NULL);
13162306a36Sopenharmony_ci	if (ret)
13262306a36Sopenharmony_ci		return ret;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	gusb = kzalloc(sizeof(*gusb), GFP_KERNEL);
13562306a36Sopenharmony_ci	if (!gusb)
13662306a36Sopenharmony_ci		return -ENOMEM;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	gdev = gnss_allocate_device(&intf->dev);
13962306a36Sopenharmony_ci	if (!gdev) {
14062306a36Sopenharmony_ci		ret = -ENOMEM;
14162306a36Sopenharmony_ci		goto err_free_gusb;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	gdev->ops = &gnss_usb_gnss_ops;
14562306a36Sopenharmony_ci	gdev->type = GNSS_TYPE_NMEA;
14662306a36Sopenharmony_ci	gnss_set_drvdata(gdev, gusb);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	urb = usb_alloc_urb(0, GFP_KERNEL);
14962306a36Sopenharmony_ci	if (!urb) {
15062306a36Sopenharmony_ci		ret = -ENOMEM;
15162306a36Sopenharmony_ci		goto err_put_gdev;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	buf_len = max(usb_endpoint_maxp(in), GNSS_USB_READ_BUF_LEN);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	buf = kzalloc(buf_len, GFP_KERNEL);
15762306a36Sopenharmony_ci	if (!buf) {
15862306a36Sopenharmony_ci		ret = -ENOMEM;
15962306a36Sopenharmony_ci		goto err_free_urb;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	usb_fill_bulk_urb(urb, udev,
16362306a36Sopenharmony_ci			usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
16462306a36Sopenharmony_ci			buf, buf_len, gnss_usb_rx_complete, gusb);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	gusb->intf = intf;
16762306a36Sopenharmony_ci	gusb->udev = udev;
16862306a36Sopenharmony_ci	gusb->gdev = gdev;
16962306a36Sopenharmony_ci	gusb->read_urb = urb;
17062306a36Sopenharmony_ci	gusb->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	ret = gnss_register_device(gdev);
17362306a36Sopenharmony_ci	if (ret)
17462306a36Sopenharmony_ci		goto err_free_buf;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	usb_set_intfdata(intf, gusb);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	return 0;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cierr_free_buf:
18162306a36Sopenharmony_ci	kfree(buf);
18262306a36Sopenharmony_cierr_free_urb:
18362306a36Sopenharmony_ci	usb_free_urb(urb);
18462306a36Sopenharmony_cierr_put_gdev:
18562306a36Sopenharmony_ci	gnss_put_device(gdev);
18662306a36Sopenharmony_cierr_free_gusb:
18762306a36Sopenharmony_ci	kfree(gusb);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	return ret;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void gnss_usb_disconnect(struct usb_interface *intf)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct gnss_usb *gusb = usb_get_intfdata(intf);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	gnss_deregister_device(gusb->gdev);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	kfree(gusb->read_urb->transfer_buffer);
19962306a36Sopenharmony_ci	usb_free_urb(gusb->read_urb);
20062306a36Sopenharmony_ci	gnss_put_device(gusb->gdev);
20162306a36Sopenharmony_ci	kfree(gusb);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic struct usb_driver gnss_usb_driver = {
20562306a36Sopenharmony_ci	.name		= "gnss-usb",
20662306a36Sopenharmony_ci	.probe		= gnss_usb_probe,
20762306a36Sopenharmony_ci	.disconnect	= gnss_usb_disconnect,
20862306a36Sopenharmony_ci	.id_table	= gnss_usb_id_table,
20962306a36Sopenharmony_ci};
21062306a36Sopenharmony_cimodule_usb_driver(gnss_usb_driver);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ciMODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
21362306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic USB GNSS receiver driver");
21462306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
215