1ee4736e5SJohan Hovold // SPDX-License-Identifier: GPL-2.0
2ee4736e5SJohan Hovold /*
3ee4736e5SJohan Hovold * Generic USB GNSS receiver driver
4ee4736e5SJohan Hovold *
5ee4736e5SJohan Hovold * Copyright (C) 2021 Johan Hovold <johan@kernel.org>
6ee4736e5SJohan Hovold */
7ee4736e5SJohan Hovold
8ee4736e5SJohan Hovold #include <linux/errno.h>
9ee4736e5SJohan Hovold #include <linux/gnss.h>
10ee4736e5SJohan Hovold #include <linux/init.h>
11ee4736e5SJohan Hovold #include <linux/kernel.h>
12ee4736e5SJohan Hovold #include <linux/module.h>
13ee4736e5SJohan Hovold #include <linux/slab.h>
14ee4736e5SJohan Hovold #include <linux/usb.h>
15ee4736e5SJohan Hovold
16ee4736e5SJohan Hovold #define GNSS_USB_READ_BUF_LEN 512
17ee4736e5SJohan Hovold #define GNSS_USB_WRITE_TIMEOUT 1000
18ee4736e5SJohan Hovold
19ee4736e5SJohan Hovold static const struct usb_device_id gnss_usb_id_table[] = {
20*547d2167SJohan Hovold { USB_DEVICE(0x1199, 0xb000) }, /* Sierra Wireless XM1210 */
21ee4736e5SJohan Hovold { }
22ee4736e5SJohan Hovold };
23ee4736e5SJohan Hovold MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
24ee4736e5SJohan Hovold
25ee4736e5SJohan Hovold struct gnss_usb {
26ee4736e5SJohan Hovold struct usb_device *udev;
27ee4736e5SJohan Hovold struct usb_interface *intf;
28ee4736e5SJohan Hovold struct gnss_device *gdev;
29ee4736e5SJohan Hovold struct urb *read_urb;
30ee4736e5SJohan Hovold unsigned int write_pipe;
31ee4736e5SJohan Hovold };
32ee4736e5SJohan Hovold
gnss_usb_rx_complete(struct urb * urb)33ee4736e5SJohan Hovold static void gnss_usb_rx_complete(struct urb *urb)
34ee4736e5SJohan Hovold {
35ee4736e5SJohan Hovold struct gnss_usb *gusb = urb->context;
36ee4736e5SJohan Hovold struct gnss_device *gdev = gusb->gdev;
37ee4736e5SJohan Hovold int status = urb->status;
38ee4736e5SJohan Hovold int len;
39ee4736e5SJohan Hovold int ret;
40ee4736e5SJohan Hovold
41ee4736e5SJohan Hovold switch (status) {
42ee4736e5SJohan Hovold case 0:
43ee4736e5SJohan Hovold break;
44ee4736e5SJohan Hovold case -ENOENT:
45ee4736e5SJohan Hovold case -ECONNRESET:
46ee4736e5SJohan Hovold case -ESHUTDOWN:
47ee4736e5SJohan Hovold dev_dbg(&gdev->dev, "urb stopped: %d\n", status);
48ee4736e5SJohan Hovold return;
49ee4736e5SJohan Hovold case -EPIPE:
50ee4736e5SJohan Hovold dev_err(&gdev->dev, "urb stopped: %d\n", status);
51ee4736e5SJohan Hovold return;
52ee4736e5SJohan Hovold default:
53ee4736e5SJohan Hovold dev_dbg(&gdev->dev, "nonzero urb status: %d\n", status);
54ee4736e5SJohan Hovold goto resubmit;
55ee4736e5SJohan Hovold }
56ee4736e5SJohan Hovold
57ee4736e5SJohan Hovold len = urb->actual_length;
58ee4736e5SJohan Hovold if (len == 0)
59ee4736e5SJohan Hovold goto resubmit;
60ee4736e5SJohan Hovold
61ee4736e5SJohan Hovold ret = gnss_insert_raw(gdev, urb->transfer_buffer, len);
62ee4736e5SJohan Hovold if (ret < len)
63ee4736e5SJohan Hovold dev_dbg(&gdev->dev, "dropped %d bytes\n", len - ret);
64ee4736e5SJohan Hovold resubmit:
65ee4736e5SJohan Hovold ret = usb_submit_urb(urb, GFP_ATOMIC);
66ee4736e5SJohan Hovold if (ret && ret != -EPERM && ret != -ENODEV)
67ee4736e5SJohan Hovold dev_err(&gdev->dev, "failed to resubmit urb: %d\n", ret);
68ee4736e5SJohan Hovold }
69ee4736e5SJohan Hovold
gnss_usb_open(struct gnss_device * gdev)70ee4736e5SJohan Hovold static int gnss_usb_open(struct gnss_device *gdev)
71ee4736e5SJohan Hovold {
72ee4736e5SJohan Hovold struct gnss_usb *gusb = gnss_get_drvdata(gdev);
73ee4736e5SJohan Hovold int ret;
74ee4736e5SJohan Hovold
75ee4736e5SJohan Hovold ret = usb_submit_urb(gusb->read_urb, GFP_KERNEL);
76ee4736e5SJohan Hovold if (ret) {
77ee4736e5SJohan Hovold if (ret != -EPERM && ret != -ENODEV)
78ee4736e5SJohan Hovold dev_err(&gdev->dev, "failed to submit urb: %d\n", ret);
79ee4736e5SJohan Hovold return ret;
80ee4736e5SJohan Hovold }
81ee4736e5SJohan Hovold
82ee4736e5SJohan Hovold return 0;
83ee4736e5SJohan Hovold }
84ee4736e5SJohan Hovold
gnss_usb_close(struct gnss_device * gdev)85ee4736e5SJohan Hovold static void gnss_usb_close(struct gnss_device *gdev)
86ee4736e5SJohan Hovold {
87ee4736e5SJohan Hovold struct gnss_usb *gusb = gnss_get_drvdata(gdev);
88ee4736e5SJohan Hovold
89ee4736e5SJohan Hovold usb_kill_urb(gusb->read_urb);
90ee4736e5SJohan Hovold }
91ee4736e5SJohan Hovold
gnss_usb_write_raw(struct gnss_device * gdev,const unsigned char * buf,size_t count)92ee4736e5SJohan Hovold static int gnss_usb_write_raw(struct gnss_device *gdev,
93ee4736e5SJohan Hovold const unsigned char *buf, size_t count)
94ee4736e5SJohan Hovold {
95ee4736e5SJohan Hovold struct gnss_usb *gusb = gnss_get_drvdata(gdev);
96ee4736e5SJohan Hovold void *tbuf;
97ee4736e5SJohan Hovold int ret;
98ee4736e5SJohan Hovold
99ee4736e5SJohan Hovold tbuf = kmemdup(buf, count, GFP_KERNEL);
100ee4736e5SJohan Hovold if (!tbuf)
101ee4736e5SJohan Hovold return -ENOMEM;
102ee4736e5SJohan Hovold
103ee4736e5SJohan Hovold ret = usb_bulk_msg(gusb->udev, gusb->write_pipe, tbuf, count, NULL,
104ee4736e5SJohan Hovold GNSS_USB_WRITE_TIMEOUT);
105ee4736e5SJohan Hovold kfree(tbuf);
106ee4736e5SJohan Hovold if (ret)
107ee4736e5SJohan Hovold return ret;
108ee4736e5SJohan Hovold
109ee4736e5SJohan Hovold return count;
110ee4736e5SJohan Hovold }
111ee4736e5SJohan Hovold
112ee4736e5SJohan Hovold static const struct gnss_operations gnss_usb_gnss_ops = {
113ee4736e5SJohan Hovold .open = gnss_usb_open,
114ee4736e5SJohan Hovold .close = gnss_usb_close,
115ee4736e5SJohan Hovold .write_raw = gnss_usb_write_raw,
116ee4736e5SJohan Hovold };
117ee4736e5SJohan Hovold
gnss_usb_probe(struct usb_interface * intf,const struct usb_device_id * id)118ee4736e5SJohan Hovold static int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
119ee4736e5SJohan Hovold {
120ee4736e5SJohan Hovold struct usb_device *udev = interface_to_usbdev(intf);
121ee4736e5SJohan Hovold struct usb_endpoint_descriptor *in, *out;
122ee4736e5SJohan Hovold struct gnss_device *gdev;
123ee4736e5SJohan Hovold struct gnss_usb *gusb;
124ee4736e5SJohan Hovold struct urb *urb;
125ee4736e5SJohan Hovold size_t buf_len;
126ee4736e5SJohan Hovold void *buf;
127ee4736e5SJohan Hovold int ret;
128ee4736e5SJohan Hovold
129ee4736e5SJohan Hovold ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
130ee4736e5SJohan Hovold NULL);
131ee4736e5SJohan Hovold if (ret)
132ee4736e5SJohan Hovold return ret;
133ee4736e5SJohan Hovold
134ee4736e5SJohan Hovold gusb = kzalloc(sizeof(*gusb), GFP_KERNEL);
135ee4736e5SJohan Hovold if (!gusb)
136ee4736e5SJohan Hovold return -ENOMEM;
137ee4736e5SJohan Hovold
138ee4736e5SJohan Hovold gdev = gnss_allocate_device(&intf->dev);
139ee4736e5SJohan Hovold if (!gdev) {
140ee4736e5SJohan Hovold ret = -ENOMEM;
141ee4736e5SJohan Hovold goto err_free_gusb;
142ee4736e5SJohan Hovold }
143ee4736e5SJohan Hovold
144ee4736e5SJohan Hovold gdev->ops = &gnss_usb_gnss_ops;
145ee4736e5SJohan Hovold gdev->type = GNSS_TYPE_NMEA;
146ee4736e5SJohan Hovold gnss_set_drvdata(gdev, gusb);
147ee4736e5SJohan Hovold
148ee4736e5SJohan Hovold urb = usb_alloc_urb(0, GFP_KERNEL);
149ee4736e5SJohan Hovold if (!urb) {
150ee4736e5SJohan Hovold ret = -ENOMEM;
151ee4736e5SJohan Hovold goto err_put_gdev;
152ee4736e5SJohan Hovold }
153ee4736e5SJohan Hovold
154ee4736e5SJohan Hovold buf_len = max(usb_endpoint_maxp(in), GNSS_USB_READ_BUF_LEN);
155ee4736e5SJohan Hovold
156ee4736e5SJohan Hovold buf = kzalloc(buf_len, GFP_KERNEL);
157ee4736e5SJohan Hovold if (!buf) {
158ee4736e5SJohan Hovold ret = -ENOMEM;
159ee4736e5SJohan Hovold goto err_free_urb;
160ee4736e5SJohan Hovold }
161ee4736e5SJohan Hovold
162ee4736e5SJohan Hovold usb_fill_bulk_urb(urb, udev,
163ee4736e5SJohan Hovold usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
164ee4736e5SJohan Hovold buf, buf_len, gnss_usb_rx_complete, gusb);
165ee4736e5SJohan Hovold
166ee4736e5SJohan Hovold gusb->intf = intf;
167ee4736e5SJohan Hovold gusb->udev = udev;
168ee4736e5SJohan Hovold gusb->gdev = gdev;
169ee4736e5SJohan Hovold gusb->read_urb = urb;
170ee4736e5SJohan Hovold gusb->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
171ee4736e5SJohan Hovold
172ee4736e5SJohan Hovold ret = gnss_register_device(gdev);
173ee4736e5SJohan Hovold if (ret)
174ee4736e5SJohan Hovold goto err_free_buf;
175ee4736e5SJohan Hovold
176ee4736e5SJohan Hovold usb_set_intfdata(intf, gusb);
177ee4736e5SJohan Hovold
178ee4736e5SJohan Hovold return 0;
179ee4736e5SJohan Hovold
180ee4736e5SJohan Hovold err_free_buf:
181ee4736e5SJohan Hovold kfree(buf);
182ee4736e5SJohan Hovold err_free_urb:
183ee4736e5SJohan Hovold usb_free_urb(urb);
184ee4736e5SJohan Hovold err_put_gdev:
185ee4736e5SJohan Hovold gnss_put_device(gdev);
186ee4736e5SJohan Hovold err_free_gusb:
187ee4736e5SJohan Hovold kfree(gusb);
188ee4736e5SJohan Hovold
189ee4736e5SJohan Hovold return ret;
190ee4736e5SJohan Hovold }
191ee4736e5SJohan Hovold
gnss_usb_disconnect(struct usb_interface * intf)192ee4736e5SJohan Hovold static void gnss_usb_disconnect(struct usb_interface *intf)
193ee4736e5SJohan Hovold {
194ee4736e5SJohan Hovold struct gnss_usb *gusb = usb_get_intfdata(intf);
195ee4736e5SJohan Hovold
196ee4736e5SJohan Hovold gnss_deregister_device(gusb->gdev);
197ee4736e5SJohan Hovold
198ee4736e5SJohan Hovold kfree(gusb->read_urb->transfer_buffer);
199ee4736e5SJohan Hovold usb_free_urb(gusb->read_urb);
200ee4736e5SJohan Hovold gnss_put_device(gusb->gdev);
201ee4736e5SJohan Hovold kfree(gusb);
202ee4736e5SJohan Hovold }
203ee4736e5SJohan Hovold
204ee4736e5SJohan Hovold static struct usb_driver gnss_usb_driver = {
205ee4736e5SJohan Hovold .name = "gnss-usb",
206ee4736e5SJohan Hovold .probe = gnss_usb_probe,
207ee4736e5SJohan Hovold .disconnect = gnss_usb_disconnect,
208ee4736e5SJohan Hovold .id_table = gnss_usb_id_table,
209ee4736e5SJohan Hovold };
210ee4736e5SJohan Hovold module_usb_driver(gnss_usb_driver);
211ee4736e5SJohan Hovold
212ee4736e5SJohan Hovold MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
213ee4736e5SJohan Hovold MODULE_DESCRIPTION("Generic USB GNSS receiver driver");
214ee4736e5SJohan Hovold MODULE_LICENSE("GPL v2");
215