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

source code of linux/drivers/gnss/usb.c