Skip to content

Commit ee4736e

Browse files
committed
gnss: add USB support
Add a generic driver for GNSS receivers with a USB interface with two bulk endpoints. The driver currently assumes that the device protocol is NMEA (only) but this can be generalised later as needed. Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Greg Kroah-Hartman <[email protected]> Tested-by: Marc Ferland <[email protected]> Signed-off-by: Johan Hovold <[email protected]>
1 parent b15c901 commit ee4736e

File tree

3 files changed

+227
-0
lines changed

3 files changed

+227
-0
lines changed

drivers/gnss/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,15 @@ config GNSS_UBX_SERIAL
5454

5555
If unsure, say N.
5656

57+
config GNSS_USB
58+
tristate "USB GNSS receiver support"
59+
depends on USB
60+
help
61+
Say Y here if you have a GNSS receiver which uses a USB interface.
62+
63+
To compile this driver as a module, choose M here: the module will
64+
be called gnss-usb.
65+
66+
If unsure, say N.
67+
5768
endif # GNSS

drivers/gnss/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ gnss-sirf-y := sirf.o
1717

1818
obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o
1919
gnss-ubx-y := ubx.o
20+
21+
obj-$(CONFIG_GNSS_USB) += gnss-usb.o
22+
gnss-usb-y := usb.o

drivers/gnss/usb.c

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

0 commit comments

Comments
 (0)