GNU Linux-libre 5.19-rc6-gnu
[releases.git] / drivers / gnss / usb.c
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
19 static const struct usb_device_id gnss_usb_id_table[] = {
20         { USB_DEVICE(0x1199, 0xb000) },         /* Sierra Wireless XM1210 */
21         { }
22 };
23 MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
24
25 struct 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
33 static 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, urb->transfer_buffer, len);
62         if (ret < len)
63                 dev_dbg(&gdev->dev, "dropped %d bytes\n", len - ret);
64 resubmit:
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
70 static 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(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
85 static void gnss_usb_close(struct gnss_device *gdev)
86 {
87         struct gnss_usb *gusb = gnss_get_drvdata(gdev);
88
89         usb_kill_urb(gusb->read_urb);
90 }
91
92 static 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(buf, count, GFP_KERNEL);
100         if (!tbuf)
101                 return -ENOMEM;
102
103         ret = usb_bulk_msg(gusb->udev, gusb->write_pipe, tbuf, count, NULL,
104                         GNSS_USB_WRITE_TIMEOUT);
105         kfree(tbuf);
106         if (ret)
107                 return ret;
108
109         return count;
110 }
111
112 static 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
118 static 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(intf->cur_altsetting, &in, &out, NULL,
130                         NULL);
131         if (ret)
132                 return ret;
133
134         gusb = kzalloc(sizeof(*gusb), GFP_KERNEL);
135         if (!gusb)
136                 return -ENOMEM;
137
138         gdev = gnss_allocate_device(&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, gusb);
147
148         urb = usb_alloc_urb(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(buf_len, GFP_KERNEL);
157         if (!buf) {
158                 ret = -ENOMEM;
159                 goto err_free_urb;
160         }
161
162         usb_fill_bulk_urb(urb, udev,
163                         usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
164                         buf, buf_len, gnss_usb_rx_complete, 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, gusb);
177
178         return 0;
179
180 err_free_buf:
181         kfree(buf);
182 err_free_urb:
183         usb_free_urb(urb);
184 err_put_gdev:
185         gnss_put_device(gdev);
186 err_free_gusb:
187         kfree(gusb);
188
189         return ret;
190 }
191
192 static void gnss_usb_disconnect(struct usb_interface *intf)
193 {
194         struct gnss_usb *gusb = usb_get_intfdata(intf);
195
196         gnss_deregister_device(gusb->gdev);
197
198         kfree(gusb->read_urb->transfer_buffer);
199         usb_free_urb(gusb->read_urb);
200         gnss_put_device(gusb->gdev);
201         kfree(gusb);
202 }
203
204 static 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 };
210 module_usb_driver(gnss_usb_driver);
211
212 MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
213 MODULE_DESCRIPTION("Generic USB GNSS receiver driver");
214 MODULE_LICENSE("GPL v2");