215 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Generic USB GNSS receiver driver4 *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 51217#define GNSS_USB_WRITE_TIMEOUT 100018 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, urb->transfer_buffer, 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(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(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(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 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(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 180err_free_buf:181 kfree(buf);182err_free_urb:183 usb_free_urb(urb);184err_put_gdev:185 gnss_put_device(gdev);186err_free_gusb:187 kfree(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(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 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