xref: /openbmc/linux/drivers/gnss/usb.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
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