Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# GNSS receiver configuration
#
menuconfig GNSS
tristate "GNSS receiver support"
help
Say Y here if you have a GNSS receiver (e.g. a GPS receiver).
To compile this driver as a module, choose M here: the module will
be called gnss.
if GNSS
config GNSS_SERIAL
tristate
config GNSS_MTK_SERIAL
tristate "Mediatek GNSS receiver support"
depends on SERIAL_DEV_BUS
select GNSS_SERIAL
help
Say Y here if you have a Mediatek-based GNSS receiver which uses a
serial interface.
To compile this driver as a module, choose M here: the module will
be called gnss-mtk.
If unsure, say N.
config GNSS_SIRF_SERIAL
tristate "SiRFstar GNSS receiver support"
depends on SERIAL_DEV_BUS
help
Say Y here if you have a SiRFstar-based GNSS receiver which uses a
serial interface.
To compile this driver as a module, choose M here: the module will
be called gnss-sirf.
If unsure, say N.
config GNSS_UBX_SERIAL
tristate "u-blox GNSS receiver support"
depends on SERIAL_DEV_BUS
select GNSS_SERIAL
help
Say Y here if you have a u-blox GNSS receiver which uses a serial
interface.
To compile this driver as a module, choose M here: the module will
be called gnss-ubx.
If unsure, say N.
config GNSS_USB
tristate "USB GNSS receiver support"
depends on USB
help
Say Y here if you have a GNSS receiver which uses a USB interface.
To compile this driver as a module, choose M here: the module will
be called gnss-usb.
If unsure, say N.
endif # GNSS
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the GNSS subsystem.
#
obj-$(CONFIG_GNSS) += gnss.o
gnss-y := core.o
obj-$(CONFIG_GNSS_SERIAL) += gnss-serial.o
gnss-serial-y := serial.o
obj-$(CONFIG_GNSS_MTK_SERIAL) += gnss-mtk.o
gnss-mtk-y := mtk.o
obj-$(CONFIG_GNSS_SIRF_SERIAL) += gnss-sirf.o
gnss-sirf-y := sirf.o
obj-$(CONFIG_GNSS_UBX_SERIAL) += gnss-ubx.o
gnss-ubx-y := ubx.o
obj-$(CONFIG_GNSS_USB) += gnss-usb.o
gnss-usb-y := usb.o
+420
View File
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0
/*
* GNSS receiver core
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/gnss.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#define GNSS_FLAG_HAS_WRITE_RAW BIT(0)
#define GNSS_MINORS 16
static DEFINE_IDA(gnss_minors);
static dev_t gnss_first;
/* FIFO size must be a power of two */
#define GNSS_READ_FIFO_SIZE 4096
#define GNSS_WRITE_BUF_SIZE 1024
#define to_gnss_device(d) container_of((d), struct gnss_device, dev)
static int gnss_open(struct inode *inode, struct file *file)
{
struct gnss_device *gdev;
int ret = 0;
gdev = container_of(inode->i_cdev, struct gnss_device, cdev);
get_device(&gdev->dev);
stream_open(inode, file);
file->private_data = gdev;
down_write(&gdev->rwsem);
if (gdev->disconnected) {
ret = -ENODEV;
goto unlock;
}
if (gdev->count++ == 0) {
ret = gdev->ops->open(gdev);
if (ret)
gdev->count--;
}
unlock:
up_write(&gdev->rwsem);
if (ret)
put_device(&gdev->dev);
return ret;
}
static int gnss_release(struct inode *inode, struct file *file)
{
struct gnss_device *gdev = file->private_data;
down_write(&gdev->rwsem);
if (gdev->disconnected)
goto unlock;
if (--gdev->count == 0) {
gdev->ops->close(gdev);
kfifo_reset(&gdev->read_fifo);
}
unlock:
up_write(&gdev->rwsem);
put_device(&gdev->dev);
return 0;
}
static ssize_t gnss_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct gnss_device *gdev = file->private_data;
unsigned int copied;
int ret;
mutex_lock(&gdev->read_mutex);
while (kfifo_is_empty(&gdev->read_fifo)) {
mutex_unlock(&gdev->read_mutex);
if (gdev->disconnected)
return 0;
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(gdev->read_queue,
gdev->disconnected ||
!kfifo_is_empty(&gdev->read_fifo));
if (ret)
return -ERESTARTSYS;
mutex_lock(&gdev->read_mutex);
}
ret = kfifo_to_user(&gdev->read_fifo, buf, count, &copied);
if (ret == 0)
ret = copied;
mutex_unlock(&gdev->read_mutex);
return ret;
}
static ssize_t gnss_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
struct gnss_device *gdev = file->private_data;
size_t written = 0;
int ret;
if (gdev->disconnected)
return -EIO;
if (!count)
return 0;
if (!(gdev->flags & GNSS_FLAG_HAS_WRITE_RAW))
return -EIO;
/* Ignoring O_NONBLOCK, write_raw() is synchronous. */
ret = mutex_lock_interruptible(&gdev->write_mutex);
if (ret)
return -ERESTARTSYS;
for (;;) {
size_t n = count - written;
if (n > GNSS_WRITE_BUF_SIZE)
n = GNSS_WRITE_BUF_SIZE;
if (copy_from_user(gdev->write_buf, buf, n)) {
ret = -EFAULT;
goto out_unlock;
}
/*
* Assumes write_raw can always accept GNSS_WRITE_BUF_SIZE
* bytes.
*
* FIXME: revisit
*/
down_read(&gdev->rwsem);
if (!gdev->disconnected)
ret = gdev->ops->write_raw(gdev, gdev->write_buf, n);
else
ret = -EIO;
up_read(&gdev->rwsem);
if (ret < 0)
break;
written += ret;
buf += ret;
if (written == count)
break;
}
if (written)
ret = written;
out_unlock:
mutex_unlock(&gdev->write_mutex);
return ret;
}
static __poll_t gnss_poll(struct file *file, poll_table *wait)
{
struct gnss_device *gdev = file->private_data;
__poll_t mask = 0;
poll_wait(file, &gdev->read_queue, wait);
if (!kfifo_is_empty(&gdev->read_fifo))
mask |= EPOLLIN | EPOLLRDNORM;
if (gdev->disconnected)
mask |= EPOLLHUP;
return mask;
}
static const struct file_operations gnss_fops = {
.owner = THIS_MODULE,
.open = gnss_open,
.release = gnss_release,
.read = gnss_read,
.write = gnss_write,
.poll = gnss_poll,
};
static struct class *gnss_class;
static void gnss_device_release(struct device *dev)
{
struct gnss_device *gdev = to_gnss_device(dev);
kfree(gdev->write_buf);
kfifo_free(&gdev->read_fifo);
ida_free(&gnss_minors, gdev->id);
kfree(gdev);
}
struct gnss_device *gnss_allocate_device(struct device *parent)
{
struct gnss_device *gdev;
struct device *dev;
int id;
int ret;
gdev = kzalloc_obj(*gdev);
if (!gdev)
return NULL;
id = ida_alloc_max(&gnss_minors, GNSS_MINORS - 1, GFP_KERNEL);
if (id < 0) {
kfree(gdev);
return NULL;
}
gdev->id = id;
dev = &gdev->dev;
device_initialize(dev);
dev->devt = gnss_first + id;
dev->class = gnss_class;
dev->parent = parent;
dev->release = gnss_device_release;
dev_set_drvdata(dev, gdev);
dev_set_name(dev, "gnss%d", id);
init_rwsem(&gdev->rwsem);
mutex_init(&gdev->read_mutex);
mutex_init(&gdev->write_mutex);
init_waitqueue_head(&gdev->read_queue);
ret = kfifo_alloc(&gdev->read_fifo, GNSS_READ_FIFO_SIZE, GFP_KERNEL);
if (ret)
goto err_put_device;
gdev->write_buf = kzalloc(GNSS_WRITE_BUF_SIZE, GFP_KERNEL);
if (!gdev->write_buf)
goto err_put_device;
cdev_init(&gdev->cdev, &gnss_fops);
gdev->cdev.owner = THIS_MODULE;
return gdev;
err_put_device:
put_device(dev);
return NULL;
}
EXPORT_SYMBOL_GPL(gnss_allocate_device);
void gnss_put_device(struct gnss_device *gdev)
{
put_device(&gdev->dev);
}
EXPORT_SYMBOL_GPL(gnss_put_device);
int gnss_register_device(struct gnss_device *gdev)
{
int ret;
/* Set a flag which can be accessed without holding the rwsem. */
if (gdev->ops->write_raw != NULL)
gdev->flags |= GNSS_FLAG_HAS_WRITE_RAW;
ret = cdev_device_add(&gdev->cdev, &gdev->dev);
if (ret) {
dev_err(&gdev->dev, "failed to add device: %d\n", ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(gnss_register_device);
void gnss_deregister_device(struct gnss_device *gdev)
{
down_write(&gdev->rwsem);
gdev->disconnected = true;
if (gdev->count) {
wake_up_interruptible(&gdev->read_queue);
gdev->ops->close(gdev);
}
up_write(&gdev->rwsem);
cdev_device_del(&gdev->cdev, &gdev->dev);
}
EXPORT_SYMBOL_GPL(gnss_deregister_device);
/*
* Caller guarantees serialisation.
*
* Must not be called for a closed device.
*/
int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf,
size_t count)
{
int ret;
ret = kfifo_in(&gdev->read_fifo, buf, count);
wake_up_interruptible(&gdev->read_queue);
return ret;
}
EXPORT_SYMBOL_GPL(gnss_insert_raw);
static const char * const gnss_type_names[GNSS_TYPE_COUNT] = {
[GNSS_TYPE_NMEA] = "NMEA",
[GNSS_TYPE_SIRF] = "SiRF",
[GNSS_TYPE_UBX] = "UBX",
[GNSS_TYPE_MTK] = "MTK",
};
static const char *gnss_type_name(const struct gnss_device *gdev)
{
const char *name = NULL;
if (gdev->type < GNSS_TYPE_COUNT)
name = gnss_type_names[gdev->type];
if (!name)
dev_WARN(&gdev->dev, "type name not defined\n");
return name;
}
static ssize_t type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct gnss_device *gdev = to_gnss_device(dev);
return sprintf(buf, "%s\n", gnss_type_name(gdev));
}
static DEVICE_ATTR_RO(type);
static struct attribute *gnss_attrs[] = {
&dev_attr_type.attr,
NULL,
};
ATTRIBUTE_GROUPS(gnss);
static int gnss_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct gnss_device *gdev = to_gnss_device(dev);
int ret;
ret = add_uevent_var(env, "GNSS_TYPE=%s", gnss_type_name(gdev));
if (ret)
return ret;
return 0;
}
static int __init gnss_module_init(void)
{
int ret;
ret = alloc_chrdev_region(&gnss_first, 0, GNSS_MINORS, "gnss");
if (ret < 0) {
pr_err("failed to allocate device numbers: %d\n", ret);
return ret;
}
gnss_class = class_create("gnss");
if (IS_ERR(gnss_class)) {
ret = PTR_ERR(gnss_class);
pr_err("failed to create class: %d\n", ret);
goto err_unregister_chrdev;
}
gnss_class->dev_groups = gnss_groups;
gnss_class->dev_uevent = gnss_uevent;
pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first));
return 0;
err_unregister_chrdev:
unregister_chrdev_region(gnss_first, GNSS_MINORS);
return ret;
}
module_init(gnss_module_init);
static void __exit gnss_module_exit(void)
{
class_destroy(gnss_class);
unregister_chrdev_region(gnss_first, GNSS_MINORS);
ida_destroy(&gnss_minors);
}
module_exit(gnss_module_exit);
MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
MODULE_DESCRIPTION("GNSS receiver core");
MODULE_LICENSE("GPL v2");
+152
View File
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Mediatek GNSS receiver driver
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#include <linux/errno.h>
#include <linux/gnss.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include "serial.h"
struct mtk_data {
struct regulator *vbackup;
struct regulator *vcc;
};
static int mtk_set_active(struct gnss_serial *gserial)
{
struct mtk_data *data = gnss_serial_get_drvdata(gserial);
int ret;
ret = regulator_enable(data->vcc);
if (ret)
return ret;
return 0;
}
static int mtk_set_standby(struct gnss_serial *gserial)
{
struct mtk_data *data = gnss_serial_get_drvdata(gserial);
int ret;
ret = regulator_disable(data->vcc);
if (ret)
return ret;
return 0;
}
static int mtk_set_power(struct gnss_serial *gserial,
enum gnss_serial_pm_state state)
{
switch (state) {
case GNSS_SERIAL_ACTIVE:
return mtk_set_active(gserial);
case GNSS_SERIAL_OFF:
case GNSS_SERIAL_STANDBY:
return mtk_set_standby(gserial);
}
return -EINVAL;
}
static const struct gnss_serial_ops mtk_gserial_ops = {
.set_power = mtk_set_power,
};
static int mtk_probe(struct serdev_device *serdev)
{
struct gnss_serial *gserial;
struct mtk_data *data;
int ret;
gserial = gnss_serial_allocate(serdev, sizeof(*data));
if (IS_ERR(gserial)) {
ret = PTR_ERR(gserial);
return ret;
}
gserial->ops = &mtk_gserial_ops;
gserial->gdev->type = GNSS_TYPE_MTK;
data = gnss_serial_get_drvdata(gserial);
data->vcc = devm_regulator_get(&serdev->dev, "vcc");
if (IS_ERR(data->vcc)) {
ret = PTR_ERR(data->vcc);
goto err_free_gserial;
}
data->vbackup = devm_regulator_get_optional(&serdev->dev, "vbackup");
if (IS_ERR(data->vbackup)) {
ret = PTR_ERR(data->vbackup);
if (ret == -ENODEV)
data->vbackup = NULL;
else
goto err_free_gserial;
}
if (data->vbackup) {
ret = regulator_enable(data->vbackup);
if (ret)
goto err_free_gserial;
}
ret = gnss_serial_register(gserial);
if (ret)
goto err_disable_vbackup;
return 0;
err_disable_vbackup:
if (data->vbackup)
regulator_disable(data->vbackup);
err_free_gserial:
gnss_serial_free(gserial);
return ret;
}
static void mtk_remove(struct serdev_device *serdev)
{
struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
struct mtk_data *data = gnss_serial_get_drvdata(gserial);
gnss_serial_deregister(gserial);
if (data->vbackup)
regulator_disable(data->vbackup);
gnss_serial_free(gserial);
}
#ifdef CONFIG_OF
static const struct of_device_id mtk_of_match[] = {
{ .compatible = "globaltop,pa6h" },
{},
};
MODULE_DEVICE_TABLE(of, mtk_of_match);
#endif
static struct serdev_device_driver mtk_driver = {
.driver = {
.name = "gnss-mtk",
.of_match_table = of_match_ptr(mtk_of_match),
.pm = &gnss_serial_pm_ops,
},
.probe = mtk_probe,
.remove = mtk_remove,
};
module_serdev_device_driver(mtk_driver);
MODULE_AUTHOR("Loys Ollivier <lollivier@baylibre.com>");
MODULE_DESCRIPTION("Mediatek GNSS receiver driver");
MODULE_LICENSE("GPL v2");
+276
View File
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Generic serial GNSS receiver driver
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#include <linux/errno.h>
#include <linux/gnss.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/serdev.h>
#include <linux/slab.h>
#include "serial.h"
static int gnss_serial_open(struct gnss_device *gdev)
{
struct gnss_serial *gserial = gnss_get_drvdata(gdev);
struct serdev_device *serdev = gserial->serdev;
int ret;
ret = serdev_device_open(serdev);
if (ret)
return ret;
serdev_device_set_baudrate(serdev, gserial->speed);
serdev_device_set_flow_control(serdev, false);
ret = pm_runtime_get_sync(&serdev->dev);
if (ret < 0) {
pm_runtime_put_noidle(&serdev->dev);
goto err_close;
}
return 0;
err_close:
serdev_device_close(serdev);
return ret;
}
static void gnss_serial_close(struct gnss_device *gdev)
{
struct gnss_serial *gserial = gnss_get_drvdata(gdev);
struct serdev_device *serdev = gserial->serdev;
serdev_device_close(serdev);
pm_runtime_put(&serdev->dev);
}
static int gnss_serial_write_raw(struct gnss_device *gdev,
const unsigned char *buf, size_t count)
{
struct gnss_serial *gserial = gnss_get_drvdata(gdev);
struct serdev_device *serdev = gserial->serdev;
int ret;
/* write is only buffered synchronously */
ret = serdev_device_write(serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
if (ret < 0 || ret < count)
return ret;
/* FIXME: determine if interrupted? */
serdev_device_wait_until_sent(serdev, 0);
return count;
}
static const struct gnss_operations gnss_serial_gnss_ops = {
.open = gnss_serial_open,
.close = gnss_serial_close,
.write_raw = gnss_serial_write_raw,
};
static size_t gnss_serial_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t count)
{
struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
struct gnss_device *gdev = gserial->gdev;
return gnss_insert_raw(gdev, buf, count);
}
static const struct serdev_device_ops gnss_serial_serdev_ops = {
.receive_buf = gnss_serial_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static int gnss_serial_set_power(struct gnss_serial *gserial,
enum gnss_serial_pm_state state)
{
if (!gserial->ops || !gserial->ops->set_power)
return 0;
return gserial->ops->set_power(gserial, state);
}
/*
* FIXME: need to provide subdriver defaults or separate dt parsing from
* allocation.
*/
static int gnss_serial_parse_dt(struct serdev_device *serdev)
{
struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
struct device_node *node = serdev->dev.of_node;
u32 speed = 4800;
of_property_read_u32(node, "current-speed", &speed);
gserial->speed = speed;
return 0;
}
struct gnss_serial *gnss_serial_allocate(struct serdev_device *serdev,
size_t data_size)
{
struct gnss_serial *gserial;
struct gnss_device *gdev;
int ret;
gserial = kzalloc(sizeof(*gserial) + data_size, GFP_KERNEL);
if (!gserial)
return ERR_PTR(-ENOMEM);
gdev = gnss_allocate_device(&serdev->dev);
if (!gdev) {
ret = -ENOMEM;
goto err_free_gserial;
}
gdev->ops = &gnss_serial_gnss_ops;
gnss_set_drvdata(gdev, gserial);
gserial->serdev = serdev;
gserial->gdev = gdev;
serdev_device_set_drvdata(serdev, gserial);
serdev_device_set_client_ops(serdev, &gnss_serial_serdev_ops);
ret = gnss_serial_parse_dt(serdev);
if (ret)
goto err_put_device;
return gserial;
err_put_device:
gnss_put_device(gserial->gdev);
err_free_gserial:
kfree(gserial);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(gnss_serial_allocate);
void gnss_serial_free(struct gnss_serial *gserial)
{
gnss_put_device(gserial->gdev);
kfree(gserial);
}
EXPORT_SYMBOL_GPL(gnss_serial_free);
int gnss_serial_register(struct gnss_serial *gserial)
{
struct serdev_device *serdev = gserial->serdev;
int ret;
if (IS_ENABLED(CONFIG_PM)) {
pm_runtime_enable(&serdev->dev);
} else {
ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
if (ret < 0)
return ret;
}
ret = gnss_register_device(gserial->gdev);
if (ret)
goto err_disable_rpm;
return 0;
err_disable_rpm:
if (IS_ENABLED(CONFIG_PM))
pm_runtime_disable(&serdev->dev);
else
gnss_serial_set_power(gserial, GNSS_SERIAL_OFF);
return ret;
}
EXPORT_SYMBOL_GPL(gnss_serial_register);
void gnss_serial_deregister(struct gnss_serial *gserial)
{
struct serdev_device *serdev = gserial->serdev;
gnss_deregister_device(gserial->gdev);
if (IS_ENABLED(CONFIG_PM))
pm_runtime_disable(&serdev->dev);
else
gnss_serial_set_power(gserial, GNSS_SERIAL_OFF);
}
EXPORT_SYMBOL_GPL(gnss_serial_deregister);
#ifdef CONFIG_PM
static int gnss_serial_runtime_suspend(struct device *dev)
{
struct gnss_serial *gserial = dev_get_drvdata(dev);
return gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY);
}
static int gnss_serial_runtime_resume(struct device *dev)
{
struct gnss_serial *gserial = dev_get_drvdata(dev);
return gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
}
#endif /* CONFIG_PM */
static int gnss_serial_prepare(struct device *dev)
{
if (pm_runtime_suspended(dev))
return 1;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int gnss_serial_suspend(struct device *dev)
{
struct gnss_serial *gserial = dev_get_drvdata(dev);
int ret = 0;
/*
* FIXME: serdev currently lacks support for managing the underlying
* device's wakeup settings. A workaround would be to close the serdev
* device here if it is open.
*/
if (!pm_runtime_suspended(dev))
ret = gnss_serial_set_power(gserial, GNSS_SERIAL_STANDBY);
return ret;
}
static int gnss_serial_resume(struct device *dev)
{
struct gnss_serial *gserial = dev_get_drvdata(dev);
int ret = 0;
if (!pm_runtime_suspended(dev))
ret = gnss_serial_set_power(gserial, GNSS_SERIAL_ACTIVE);
return ret;
}
#endif /* CONFIG_PM_SLEEP */
const struct dev_pm_ops gnss_serial_pm_ops = {
.prepare = gnss_serial_prepare,
SET_SYSTEM_SLEEP_PM_OPS(gnss_serial_suspend, gnss_serial_resume)
SET_RUNTIME_PM_OPS(gnss_serial_runtime_suspend, gnss_serial_runtime_resume, NULL)
};
EXPORT_SYMBOL_GPL(gnss_serial_pm_ops);
MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
MODULE_DESCRIPTION("Generic serial GNSS receiver driver");
MODULE_LICENSE("GPL v2");
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Generic serial GNSS receiver driver
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#ifndef _LINUX_GNSS_SERIAL_H
#define _LINUX_GNSS_SERIAL_H
#include <asm/termbits.h>
#include <linux/pm.h>
struct gnss_serial {
struct serdev_device *serdev;
struct gnss_device *gdev;
speed_t speed;
const struct gnss_serial_ops *ops;
unsigned long drvdata[];
};
enum gnss_serial_pm_state {
GNSS_SERIAL_OFF,
GNSS_SERIAL_ACTIVE,
GNSS_SERIAL_STANDBY,
};
struct gnss_serial_ops {
int (*set_power)(struct gnss_serial *gserial,
enum gnss_serial_pm_state state);
};
extern const struct dev_pm_ops gnss_serial_pm_ops;
struct gnss_serial *gnss_serial_allocate(struct serdev_device *gserial,
size_t data_size);
void gnss_serial_free(struct gnss_serial *gserial);
int gnss_serial_register(struct gnss_serial *gserial);
void gnss_serial_deregister(struct gnss_serial *gserial);
static inline void *gnss_serial_get_drvdata(struct gnss_serial *gserial)
{
return gserial->drvdata;
}
#endif /* _LINUX_GNSS_SERIAL_H */
+581
View File
@@ -0,0 +1,581 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SiRFstar GNSS receiver driver
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#include <linux/errno.h>
#include <linux/gnss.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/sched.h>
#include <linux/serdev.h>
#include <linux/slab.h>
#include <linux/wait.h>
#define SIRF_BOOT_DELAY 500
#define SIRF_ON_OFF_PULSE_TIME 100
#define SIRF_ACTIVATE_TIMEOUT 200
#define SIRF_HIBERNATE_TIMEOUT 200
/*
* If no data arrives for this time, we assume that the chip is off.
* REVISIT: The report cycle is configurable and can be several minutes long,
* so this will only work reliably if the report cycle is set to a reasonable
* low value. Also power saving settings (like send data only on movement)
* might things work even worse.
* Workaround might be to parse shutdown or bootup messages.
*/
#define SIRF_REPORT_CYCLE 2000
struct sirf_data {
struct gnss_device *gdev;
struct serdev_device *serdev;
speed_t speed;
struct regulator *vcc;
struct regulator *lna;
struct gpio_desc *on_off;
struct gpio_desc *wakeup;
int irq;
bool active;
struct mutex gdev_mutex;
bool open;
struct mutex serdev_mutex;
int serdev_count;
wait_queue_head_t power_wait;
};
static int sirf_serdev_open(struct sirf_data *data)
{
int ret = 0;
mutex_lock(&data->serdev_mutex);
if (++data->serdev_count == 1) {
ret = serdev_device_open(data->serdev);
if (ret) {
data->serdev_count--;
goto out_unlock;
}
serdev_device_set_baudrate(data->serdev, data->speed);
serdev_device_set_flow_control(data->serdev, false);
}
out_unlock:
mutex_unlock(&data->serdev_mutex);
return ret;
}
static void sirf_serdev_close(struct sirf_data *data)
{
mutex_lock(&data->serdev_mutex);
if (--data->serdev_count == 0)
serdev_device_close(data->serdev);
mutex_unlock(&data->serdev_mutex);
}
static int sirf_open(struct gnss_device *gdev)
{
struct sirf_data *data = gnss_get_drvdata(gdev);
struct serdev_device *serdev = data->serdev;
int ret;
mutex_lock(&data->gdev_mutex);
data->open = true;
mutex_unlock(&data->gdev_mutex);
ret = sirf_serdev_open(data);
if (ret) {
mutex_lock(&data->gdev_mutex);
data->open = false;
mutex_unlock(&data->gdev_mutex);
return ret;
}
ret = pm_runtime_get_sync(&serdev->dev);
if (ret < 0) {
dev_err(&gdev->dev, "failed to runtime resume: %d\n", ret);
pm_runtime_put_noidle(&serdev->dev);
goto err_close;
}
return 0;
err_close:
sirf_serdev_close(data);
mutex_lock(&data->gdev_mutex);
data->open = false;
mutex_unlock(&data->gdev_mutex);
return ret;
}
static void sirf_close(struct gnss_device *gdev)
{
struct sirf_data *data = gnss_get_drvdata(gdev);
struct serdev_device *serdev = data->serdev;
sirf_serdev_close(data);
pm_runtime_put(&serdev->dev);
mutex_lock(&data->gdev_mutex);
data->open = false;
mutex_unlock(&data->gdev_mutex);
}
static int sirf_write_raw(struct gnss_device *gdev, const unsigned char *buf,
size_t count)
{
struct sirf_data *data = gnss_get_drvdata(gdev);
struct serdev_device *serdev = data->serdev;
int ret;
/* write is only buffered synchronously */
ret = serdev_device_write(serdev, buf, count, MAX_SCHEDULE_TIMEOUT);
if (ret < 0 || ret < count)
return ret;
/* FIXME: determine if interrupted? */
serdev_device_wait_until_sent(serdev, 0);
return count;
}
static const struct gnss_operations sirf_gnss_ops = {
.open = sirf_open,
.close = sirf_close,
.write_raw = sirf_write_raw,
};
static size_t sirf_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t count)
{
struct sirf_data *data = serdev_device_get_drvdata(serdev);
struct gnss_device *gdev = data->gdev;
int ret = 0;
if (!data->wakeup && !data->active) {
data->active = true;
wake_up_interruptible(&data->power_wait);
}
mutex_lock(&data->gdev_mutex);
if (data->open)
ret = gnss_insert_raw(gdev, buf, count);
mutex_unlock(&data->gdev_mutex);
return ret;
}
static const struct serdev_device_ops sirf_serdev_ops = {
.receive_buf = sirf_receive_buf,
.write_wakeup = serdev_device_write_wakeup,
};
static irqreturn_t sirf_wakeup_handler(int irq, void *dev_id)
{
struct sirf_data *data = dev_id;
struct device *dev = &data->serdev->dev;
int ret;
ret = gpiod_get_value_cansleep(data->wakeup);
dev_dbg(dev, "%s - wakeup = %d\n", __func__, ret);
if (ret < 0)
goto out;
data->active = ret;
wake_up_interruptible(&data->power_wait);
out:
return IRQ_HANDLED;
}
static int sirf_wait_for_power_state_nowakeup(struct sirf_data *data,
bool active,
unsigned long timeout)
{
int ret;
/* Wait for state change (including any shutdown messages). */
msleep(timeout);
/* Wait for data reception or timeout. */
data->active = false;
ret = wait_event_interruptible_timeout(data->power_wait,
data->active, msecs_to_jiffies(SIRF_REPORT_CYCLE));
if (ret < 0)
return ret;
if (ret > 0 && !active)
return -ETIMEDOUT;
if (ret == 0 && active)
return -ETIMEDOUT;
return 0;
}
static int sirf_wait_for_power_state(struct sirf_data *data, bool active,
unsigned long timeout)
{
int ret;
if (!data->wakeup)
return sirf_wait_for_power_state_nowakeup(data, active, timeout);
ret = wait_event_interruptible_timeout(data->power_wait,
data->active == active, msecs_to_jiffies(timeout));
if (ret < 0)
return ret;
if (ret == 0) {
dev_warn(&data->serdev->dev, "timeout waiting for active state = %d\n",
active);
return -ETIMEDOUT;
}
return 0;
}
static void sirf_pulse_on_off(struct sirf_data *data)
{
gpiod_set_value_cansleep(data->on_off, 1);
msleep(SIRF_ON_OFF_PULSE_TIME);
gpiod_set_value_cansleep(data->on_off, 0);
}
static int sirf_set_active(struct sirf_data *data, bool active)
{
unsigned long timeout;
int retries = 3;
int ret;
if (active)
timeout = SIRF_ACTIVATE_TIMEOUT;
else
timeout = SIRF_HIBERNATE_TIMEOUT;
if (!data->wakeup) {
ret = sirf_serdev_open(data);
if (ret)
return ret;
}
do {
sirf_pulse_on_off(data);
ret = sirf_wait_for_power_state(data, active, timeout);
} while (ret == -ETIMEDOUT && retries--);
if (!data->wakeup)
sirf_serdev_close(data);
if (ret)
return ret;
return 0;
}
static int sirf_runtime_suspend(struct device *dev)
{
struct sirf_data *data = dev_get_drvdata(dev);
int ret2;
int ret;
if (data->on_off)
ret = sirf_set_active(data, false);
else
ret = regulator_disable(data->vcc);
if (ret)
return ret;
ret = regulator_disable(data->lna);
if (ret)
goto err_reenable;
return 0;
err_reenable:
if (data->on_off)
ret2 = sirf_set_active(data, true);
else
ret2 = regulator_enable(data->vcc);
if (ret2)
dev_err(dev,
"failed to reenable power on failed suspend: %d\n",
ret2);
return ret;
}
static int sirf_runtime_resume(struct device *dev)
{
struct sirf_data *data = dev_get_drvdata(dev);
int ret;
ret = regulator_enable(data->lna);
if (ret)
return ret;
if (data->on_off)
ret = sirf_set_active(data, true);
else
ret = regulator_enable(data->vcc);
if (ret)
goto err_disable_lna;
return 0;
err_disable_lna:
regulator_disable(data->lna);
return ret;
}
static int __maybe_unused sirf_suspend(struct device *dev)
{
struct sirf_data *data = dev_get_drvdata(dev);
int ret = 0;
if (!pm_runtime_suspended(dev))
ret = sirf_runtime_suspend(dev);
if (data->wakeup)
disable_irq(data->irq);
return ret;
}
static int __maybe_unused sirf_resume(struct device *dev)
{
struct sirf_data *data = dev_get_drvdata(dev);
int ret = 0;
if (data->wakeup)
enable_irq(data->irq);
if (!pm_runtime_suspended(dev))
ret = sirf_runtime_resume(dev);
return ret;
}
static const struct dev_pm_ops sirf_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(sirf_suspend, sirf_resume)
SET_RUNTIME_PM_OPS(sirf_runtime_suspend, sirf_runtime_resume, NULL)
};
static int sirf_parse_dt(struct serdev_device *serdev)
{
struct sirf_data *data = serdev_device_get_drvdata(serdev);
struct device_node *node = serdev->dev.of_node;
u32 speed = 9600;
of_property_read_u32(node, "current-speed", &speed);
data->speed = speed;
return 0;
}
static int sirf_probe(struct serdev_device *serdev)
{
struct device *dev = &serdev->dev;
struct gnss_device *gdev;
struct sirf_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
gdev = gnss_allocate_device(dev);
if (!gdev)
return -ENOMEM;
gdev->type = GNSS_TYPE_SIRF;
gdev->ops = &sirf_gnss_ops;
gnss_set_drvdata(gdev, data);
data->serdev = serdev;
data->gdev = gdev;
mutex_init(&data->gdev_mutex);
mutex_init(&data->serdev_mutex);
init_waitqueue_head(&data->power_wait);
serdev_device_set_drvdata(serdev, data);
serdev_device_set_client_ops(serdev, &sirf_serdev_ops);
ret = sirf_parse_dt(serdev);
if (ret)
goto err_put_device;
data->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(data->vcc)) {
ret = PTR_ERR(data->vcc);
goto err_put_device;
}
data->lna = devm_regulator_get(dev, "lna");
if (IS_ERR(data->lna)) {
ret = PTR_ERR(data->lna);
goto err_put_device;
}
data->on_off = devm_gpiod_get_optional(dev, "sirf,onoff",
GPIOD_OUT_LOW);
if (IS_ERR(data->on_off)) {
ret = PTR_ERR(data->on_off);
goto err_put_device;
}
if (data->on_off) {
data->wakeup = devm_gpiod_get_optional(dev, "sirf,wakeup",
GPIOD_IN);
if (IS_ERR(data->wakeup)) {
ret = PTR_ERR(data->wakeup);
goto err_put_device;
}
ret = regulator_enable(data->vcc);
if (ret)
goto err_put_device;
/* Wait for chip to boot into hibernate mode. */
msleep(SIRF_BOOT_DELAY);
}
if (data->wakeup) {
ret = gpiod_get_value_cansleep(data->wakeup);
if (ret < 0)
goto err_disable_vcc;
data->active = ret;
ret = gpiod_to_irq(data->wakeup);
if (ret < 0)
goto err_disable_vcc;
data->irq = ret;
ret = request_threaded_irq(data->irq, NULL, sirf_wakeup_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"wakeup", data);
if (ret)
goto err_disable_vcc;
}
if (data->on_off) {
if (!data->wakeup) {
data->active = false;
ret = sirf_serdev_open(data);
if (ret)
goto err_disable_vcc;
msleep(SIRF_REPORT_CYCLE);
sirf_serdev_close(data);
}
/* Force hibernate mode if already active. */
if (data->active) {
ret = sirf_set_active(data, false);
if (ret) {
dev_err(dev, "failed to set hibernate mode: %d\n",
ret);
goto err_free_irq;
}
}
}
if (IS_ENABLED(CONFIG_PM)) {
pm_runtime_set_suspended(dev); /* clear runtime_error flag */
pm_runtime_enable(dev);
} else {
ret = sirf_runtime_resume(dev);
if (ret < 0)
goto err_free_irq;
}
ret = gnss_register_device(gdev);
if (ret)
goto err_disable_rpm;
return 0;
err_disable_rpm:
if (IS_ENABLED(CONFIG_PM))
pm_runtime_disable(dev);
else
sirf_runtime_suspend(dev);
err_free_irq:
if (data->wakeup)
free_irq(data->irq, data);
err_disable_vcc:
if (data->on_off)
regulator_disable(data->vcc);
err_put_device:
gnss_put_device(data->gdev);
return ret;
}
static void sirf_remove(struct serdev_device *serdev)
{
struct sirf_data *data = serdev_device_get_drvdata(serdev);
gnss_deregister_device(data->gdev);
if (IS_ENABLED(CONFIG_PM))
pm_runtime_disable(&serdev->dev);
else
sirf_runtime_suspend(&serdev->dev);
if (data->wakeup)
free_irq(data->irq, data);
if (data->on_off)
regulator_disable(data->vcc);
gnss_put_device(data->gdev);
}
#ifdef CONFIG_OF
static const struct of_device_id sirf_of_match[] = {
{ .compatible = "fastrax,uc430" },
{ .compatible = "linx,r4" },
{ .compatible = "wi2wi,w2sg0004" },
{ .compatible = "wi2wi,w2sg0008i" },
{ .compatible = "wi2wi,w2sg0084i" },
{},
};
MODULE_DEVICE_TABLE(of, sirf_of_match);
#endif
static struct serdev_device_driver sirf_driver = {
.driver = {
.name = "gnss-sirf",
.of_match_table = of_match_ptr(sirf_of_match),
.pm = &sirf_pm_ops,
},
.probe = sirf_probe,
.remove = sirf_remove,
};
module_serdev_device_driver(sirf_driver);
MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
MODULE_DESCRIPTION("SiRFstar GNSS receiver driver");
MODULE_LICENSE("GPL v2");
+153
View File
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0
/*
* u-blox GNSS receiver driver
*
* Copyright (C) 2018 Johan Hovold <johan@kernel.org>
*/
#include <linux/errno.h>
#include <linux/gnss.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include "serial.h"
struct ubx_data {
struct regulator *vcc;
};
static int ubx_set_active(struct gnss_serial *gserial)
{
struct ubx_data *data = gnss_serial_get_drvdata(gserial);
int ret;
ret = regulator_enable(data->vcc);
if (ret)
return ret;
return 0;
}
static int ubx_set_standby(struct gnss_serial *gserial)
{
struct ubx_data *data = gnss_serial_get_drvdata(gserial);
int ret;
ret = regulator_disable(data->vcc);
if (ret)
return ret;
return 0;
}
static int ubx_set_power(struct gnss_serial *gserial,
enum gnss_serial_pm_state state)
{
switch (state) {
case GNSS_SERIAL_ACTIVE:
return ubx_set_active(gserial);
case GNSS_SERIAL_OFF:
case GNSS_SERIAL_STANDBY:
return ubx_set_standby(gserial);
}
return -EINVAL;
}
static const struct gnss_serial_ops ubx_gserial_ops = {
.set_power = ubx_set_power,
};
static int ubx_probe(struct serdev_device *serdev)
{
struct gnss_serial *gserial;
struct gpio_desc *safeboot;
struct gpio_desc *reset;
struct ubx_data *data;
int ret;
gserial = gnss_serial_allocate(serdev, sizeof(*data));
if (IS_ERR(gserial)) {
ret = PTR_ERR(gserial);
return ret;
}
gserial->ops = &ubx_gserial_ops;
gserial->gdev->type = GNSS_TYPE_UBX;
data = gnss_serial_get_drvdata(gserial);
data->vcc = devm_regulator_get(&serdev->dev, "vcc");
if (IS_ERR(data->vcc)) {
ret = PTR_ERR(data->vcc);
goto err_free_gserial;
}
ret = devm_regulator_get_enable_optional(&serdev->dev, "v-bckp");
if (ret < 0 && ret != -ENODEV)
goto err_free_gserial;
/* Deassert safeboot */
safeboot = devm_gpiod_get_optional(&serdev->dev, "safeboot", GPIOD_OUT_LOW);
if (IS_ERR(safeboot)) {
ret = PTR_ERR(safeboot);
goto err_free_gserial;
}
/* Deassert reset */
reset = devm_gpiod_get_optional(&serdev->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(reset)) {
ret = PTR_ERR(reset);
goto err_free_gserial;
}
ret = gnss_serial_register(gserial);
if (ret)
goto err_free_gserial;
return 0;
err_free_gserial:
gnss_serial_free(gserial);
return ret;
}
static void ubx_remove(struct serdev_device *serdev)
{
struct gnss_serial *gserial = serdev_device_get_drvdata(serdev);
gnss_serial_deregister(gserial);
gnss_serial_free(gserial);
}
#ifdef CONFIG_OF
static const struct of_device_id ubx_of_match[] = {
{ .compatible = "u-blox,neo-6m" },
{ .compatible = "u-blox,neo-8" },
{ .compatible = "u-blox,neo-m8" },
{},
};
MODULE_DEVICE_TABLE(of, ubx_of_match);
#endif
static struct serdev_device_driver ubx_driver = {
.driver = {
.name = "gnss-ubx",
.of_match_table = of_match_ptr(ubx_of_match),
.pm = &gnss_serial_pm_ops,
},
.probe = ubx_probe,
.remove = ubx_remove,
};
module_serdev_device_driver(ubx_driver);
MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
MODULE_DESCRIPTION("u-blox GNSS receiver driver");
MODULE_LICENSE("GPL v2");
+214
View File
@@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Generic USB GNSS receiver driver
*
* Copyright (C) 2021 Johan Hovold <johan@kernel.org>
*/
#include <linux/errno.h>
#include <linux/gnss.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#define GNSS_USB_READ_BUF_LEN 512
#define GNSS_USB_WRITE_TIMEOUT 1000
static const struct usb_device_id gnss_usb_id_table[] = {
{ USB_DEVICE(0x1199, 0xb000) }, /* Sierra Wireless XM1210 */
{ }
};
MODULE_DEVICE_TABLE(usb, gnss_usb_id_table);
struct gnss_usb {
struct usb_device *udev;
struct usb_interface *intf;
struct gnss_device *gdev;
struct urb *read_urb;
unsigned int write_pipe;
};
static void gnss_usb_rx_complete(struct urb *urb)
{
struct gnss_usb *gusb = urb->context;
struct gnss_device *gdev = gusb->gdev;
int status = urb->status;
int len;
int ret;
switch (status) {
case 0:
break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
dev_dbg(&gdev->dev, "urb stopped: %d\n", status);
return;
case -EPIPE:
dev_err(&gdev->dev, "urb stopped: %d\n", status);
return;
default:
dev_dbg(&gdev->dev, "nonzero urb status: %d\n", status);
goto resubmit;
}
len = urb->actual_length;
if (len == 0)
goto resubmit;
ret = gnss_insert_raw(gdev, urb->transfer_buffer, len);
if (ret < len)
dev_dbg(&gdev->dev, "dropped %d bytes\n", len - ret);
resubmit:
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret && ret != -EPERM && ret != -ENODEV)
dev_err(&gdev->dev, "failed to resubmit urb: %d\n", ret);
}
static int gnss_usb_open(struct gnss_device *gdev)
{
struct gnss_usb *gusb = gnss_get_drvdata(gdev);
int ret;
ret = usb_submit_urb(gusb->read_urb, GFP_KERNEL);
if (ret) {
if (ret != -EPERM && ret != -ENODEV)
dev_err(&gdev->dev, "failed to submit urb: %d\n", ret);
return ret;
}
return 0;
}
static void gnss_usb_close(struct gnss_device *gdev)
{
struct gnss_usb *gusb = gnss_get_drvdata(gdev);
usb_kill_urb(gusb->read_urb);
}
static int gnss_usb_write_raw(struct gnss_device *gdev,
const unsigned char *buf, size_t count)
{
struct gnss_usb *gusb = gnss_get_drvdata(gdev);
void *tbuf;
int ret;
tbuf = kmemdup(buf, count, GFP_KERNEL);
if (!tbuf)
return -ENOMEM;
ret = usb_bulk_msg(gusb->udev, gusb->write_pipe, tbuf, count, NULL,
GNSS_USB_WRITE_TIMEOUT);
kfree(tbuf);
if (ret)
return ret;
return count;
}
static const struct gnss_operations gnss_usb_gnss_ops = {
.open = gnss_usb_open,
.close = gnss_usb_close,
.write_raw = gnss_usb_write_raw,
};
static int gnss_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *in, *out;
struct gnss_device *gdev;
struct gnss_usb *gusb;
struct urb *urb;
size_t buf_len;
void *buf;
int ret;
ret = usb_find_common_endpoints(intf->cur_altsetting, &in, &out, NULL,
NULL);
if (ret)
return ret;
gusb = kzalloc_obj(*gusb);
if (!gusb)
return -ENOMEM;
gdev = gnss_allocate_device(&intf->dev);
if (!gdev) {
ret = -ENOMEM;
goto err_free_gusb;
}
gdev->ops = &gnss_usb_gnss_ops;
gdev->type = GNSS_TYPE_NMEA;
gnss_set_drvdata(gdev, gusb);
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
ret = -ENOMEM;
goto err_put_gdev;
}
buf_len = max(usb_endpoint_maxp(in), GNSS_USB_READ_BUF_LEN);
buf = kzalloc(buf_len, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto err_free_urb;
}
usb_fill_bulk_urb(urb, udev,
usb_rcvbulkpipe(udev, usb_endpoint_num(in)),
buf, buf_len, gnss_usb_rx_complete, gusb);
gusb->intf = intf;
gusb->udev = udev;
gusb->gdev = gdev;
gusb->read_urb = urb;
gusb->write_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(out));
ret = gnss_register_device(gdev);
if (ret)
goto err_free_buf;
usb_set_intfdata(intf, gusb);
return 0;
err_free_buf:
kfree(buf);
err_free_urb:
usb_free_urb(urb);
err_put_gdev:
gnss_put_device(gdev);
err_free_gusb:
kfree(gusb);
return ret;
}
static void gnss_usb_disconnect(struct usb_interface *intf)
{
struct gnss_usb *gusb = usb_get_intfdata(intf);
gnss_deregister_device(gusb->gdev);
kfree(gusb->read_urb->transfer_buffer);
usb_free_urb(gusb->read_urb);
gnss_put_device(gusb->gdev);
kfree(gusb);
}
static struct usb_driver gnss_usb_driver = {
.name = "gnss-usb",
.probe = gnss_usb_probe,
.disconnect = gnss_usb_disconnect,
.id_table = gnss_usb_id_table,
};
module_usb_driver(gnss_usb_driver);
MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
MODULE_DESCRIPTION("Generic USB GNSS receiver driver");
MODULE_LICENSE("GPL v2");