xref: /openbmc/linux/drivers/gnss/core.c (revision 1aaba11d)
12b6a4403SJohan Hovold // SPDX-License-Identifier: GPL-2.0
22b6a4403SJohan Hovold /*
32b6a4403SJohan Hovold  * GNSS receiver core
42b6a4403SJohan Hovold  *
52b6a4403SJohan Hovold  * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
62b6a4403SJohan Hovold  */
72b6a4403SJohan Hovold 
82b6a4403SJohan Hovold #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
92b6a4403SJohan Hovold 
102b6a4403SJohan Hovold #include <linux/cdev.h>
112b6a4403SJohan Hovold #include <linux/errno.h>
122b6a4403SJohan Hovold #include <linux/fs.h>
132b6a4403SJohan Hovold #include <linux/gnss.h>
142b6a4403SJohan Hovold #include <linux/idr.h>
152b6a4403SJohan Hovold #include <linux/init.h>
162b6a4403SJohan Hovold #include <linux/kernel.h>
172b6a4403SJohan Hovold #include <linux/module.h>
182b6a4403SJohan Hovold #include <linux/poll.h>
192b6a4403SJohan Hovold #include <linux/slab.h>
202b6a4403SJohan Hovold #include <linux/uaccess.h>
212b6a4403SJohan Hovold #include <linux/wait.h>
222b6a4403SJohan Hovold 
232b6a4403SJohan Hovold #define GNSS_FLAG_HAS_WRITE_RAW		BIT(0)
242b6a4403SJohan Hovold 
252b6a4403SJohan Hovold #define GNSS_MINORS	16
262b6a4403SJohan Hovold 
272b6a4403SJohan Hovold static DEFINE_IDA(gnss_minors);
282b6a4403SJohan Hovold static dev_t gnss_first;
292b6a4403SJohan Hovold 
302b6a4403SJohan Hovold /* FIFO size must be a power of two */
312b6a4403SJohan Hovold #define GNSS_READ_FIFO_SIZE	4096
322b6a4403SJohan Hovold #define GNSS_WRITE_BUF_SIZE	1024
332b6a4403SJohan Hovold 
342b6a4403SJohan Hovold #define to_gnss_device(d) container_of((d), struct gnss_device, dev)
352b6a4403SJohan Hovold 
gnss_open(struct inode * inode,struct file * file)362b6a4403SJohan Hovold static int gnss_open(struct inode *inode, struct file *file)
372b6a4403SJohan Hovold {
382b6a4403SJohan Hovold 	struct gnss_device *gdev;
392b6a4403SJohan Hovold 	int ret = 0;
402b6a4403SJohan Hovold 
412b6a4403SJohan Hovold 	gdev = container_of(inode->i_cdev, struct gnss_device, cdev);
422b6a4403SJohan Hovold 
432b6a4403SJohan Hovold 	get_device(&gdev->dev);
442b6a4403SJohan Hovold 
45c5bf68feSKirill Smelkov 	stream_open(inode, file);
462b6a4403SJohan Hovold 	file->private_data = gdev;
472b6a4403SJohan Hovold 
482b6a4403SJohan Hovold 	down_write(&gdev->rwsem);
492b6a4403SJohan Hovold 	if (gdev->disconnected) {
502b6a4403SJohan Hovold 		ret = -ENODEV;
512b6a4403SJohan Hovold 		goto unlock;
522b6a4403SJohan Hovold 	}
532b6a4403SJohan Hovold 
542b6a4403SJohan Hovold 	if (gdev->count++ == 0) {
552b6a4403SJohan Hovold 		ret = gdev->ops->open(gdev);
562b6a4403SJohan Hovold 		if (ret)
572b6a4403SJohan Hovold 			gdev->count--;
582b6a4403SJohan Hovold 	}
592b6a4403SJohan Hovold unlock:
602b6a4403SJohan Hovold 	up_write(&gdev->rwsem);
612b6a4403SJohan Hovold 
622b6a4403SJohan Hovold 	if (ret)
632b6a4403SJohan Hovold 		put_device(&gdev->dev);
642b6a4403SJohan Hovold 
652b6a4403SJohan Hovold 	return ret;
662b6a4403SJohan Hovold }
672b6a4403SJohan Hovold 
gnss_release(struct inode * inode,struct file * file)682b6a4403SJohan Hovold static int gnss_release(struct inode *inode, struct file *file)
692b6a4403SJohan Hovold {
702b6a4403SJohan Hovold 	struct gnss_device *gdev = file->private_data;
712b6a4403SJohan Hovold 
722b6a4403SJohan Hovold 	down_write(&gdev->rwsem);
732b6a4403SJohan Hovold 	if (gdev->disconnected)
742b6a4403SJohan Hovold 		goto unlock;
752b6a4403SJohan Hovold 
762b6a4403SJohan Hovold 	if (--gdev->count == 0) {
772b6a4403SJohan Hovold 		gdev->ops->close(gdev);
782b6a4403SJohan Hovold 		kfifo_reset(&gdev->read_fifo);
792b6a4403SJohan Hovold 	}
802b6a4403SJohan Hovold unlock:
812b6a4403SJohan Hovold 	up_write(&gdev->rwsem);
822b6a4403SJohan Hovold 
832b6a4403SJohan Hovold 	put_device(&gdev->dev);
842b6a4403SJohan Hovold 
852b6a4403SJohan Hovold 	return 0;
862b6a4403SJohan Hovold }
872b6a4403SJohan Hovold 
gnss_read(struct file * file,char __user * buf,size_t count,loff_t * pos)882b6a4403SJohan Hovold static ssize_t gnss_read(struct file *file, char __user *buf,
892b6a4403SJohan Hovold 				size_t count, loff_t *pos)
902b6a4403SJohan Hovold {
912b6a4403SJohan Hovold 	struct gnss_device *gdev = file->private_data;
922b6a4403SJohan Hovold 	unsigned int copied;
932b6a4403SJohan Hovold 	int ret;
942b6a4403SJohan Hovold 
952b6a4403SJohan Hovold 	mutex_lock(&gdev->read_mutex);
962b6a4403SJohan Hovold 	while (kfifo_is_empty(&gdev->read_fifo)) {
972b6a4403SJohan Hovold 		mutex_unlock(&gdev->read_mutex);
982b6a4403SJohan Hovold 
992b6a4403SJohan Hovold 		if (gdev->disconnected)
1002b6a4403SJohan Hovold 			return 0;
1012b6a4403SJohan Hovold 
1022b6a4403SJohan Hovold 		if (file->f_flags & O_NONBLOCK)
1032b6a4403SJohan Hovold 			return -EAGAIN;
1042b6a4403SJohan Hovold 
1052b6a4403SJohan Hovold 		ret = wait_event_interruptible(gdev->read_queue,
1062b6a4403SJohan Hovold 				gdev->disconnected ||
1072b6a4403SJohan Hovold 				!kfifo_is_empty(&gdev->read_fifo));
1082b6a4403SJohan Hovold 		if (ret)
1092b6a4403SJohan Hovold 			return -ERESTARTSYS;
1102b6a4403SJohan Hovold 
1112b6a4403SJohan Hovold 		mutex_lock(&gdev->read_mutex);
1122b6a4403SJohan Hovold 	}
1132b6a4403SJohan Hovold 
1142b6a4403SJohan Hovold 	ret = kfifo_to_user(&gdev->read_fifo, buf, count, &copied);
1152b6a4403SJohan Hovold 	if (ret == 0)
1162b6a4403SJohan Hovold 		ret = copied;
1172b6a4403SJohan Hovold 
1182b6a4403SJohan Hovold 	mutex_unlock(&gdev->read_mutex);
1192b6a4403SJohan Hovold 
1202b6a4403SJohan Hovold 	return ret;
1212b6a4403SJohan Hovold }
1222b6a4403SJohan Hovold 
gnss_write(struct file * file,const char __user * buf,size_t count,loff_t * pos)1232b6a4403SJohan Hovold static ssize_t gnss_write(struct file *file, const char __user *buf,
1242b6a4403SJohan Hovold 				size_t count, loff_t *pos)
1252b6a4403SJohan Hovold {
1262b6a4403SJohan Hovold 	struct gnss_device *gdev = file->private_data;
1272b6a4403SJohan Hovold 	size_t written = 0;
1282b6a4403SJohan Hovold 	int ret;
1292b6a4403SJohan Hovold 
1302b6a4403SJohan Hovold 	if (gdev->disconnected)
1312b6a4403SJohan Hovold 		return -EIO;
1322b6a4403SJohan Hovold 
1332b6a4403SJohan Hovold 	if (!count)
1342b6a4403SJohan Hovold 		return 0;
1352b6a4403SJohan Hovold 
1362b6a4403SJohan Hovold 	if (!(gdev->flags & GNSS_FLAG_HAS_WRITE_RAW))
1372b6a4403SJohan Hovold 		return -EIO;
1382b6a4403SJohan Hovold 
1392b6a4403SJohan Hovold 	/* Ignoring O_NONBLOCK, write_raw() is synchronous. */
1402b6a4403SJohan Hovold 
1412b6a4403SJohan Hovold 	ret = mutex_lock_interruptible(&gdev->write_mutex);
1422b6a4403SJohan Hovold 	if (ret)
1432b6a4403SJohan Hovold 		return -ERESTARTSYS;
1442b6a4403SJohan Hovold 
1452b6a4403SJohan Hovold 	for (;;) {
1462b6a4403SJohan Hovold 		size_t n = count - written;
1472b6a4403SJohan Hovold 
1482b6a4403SJohan Hovold 		if (n > GNSS_WRITE_BUF_SIZE)
1492b6a4403SJohan Hovold 			n = GNSS_WRITE_BUF_SIZE;
1502b6a4403SJohan Hovold 
1512b6a4403SJohan Hovold 		if (copy_from_user(gdev->write_buf, buf, n)) {
1522b6a4403SJohan Hovold 			ret = -EFAULT;
1532b6a4403SJohan Hovold 			goto out_unlock;
1542b6a4403SJohan Hovold 		}
1552b6a4403SJohan Hovold 
1562b6a4403SJohan Hovold 		/*
1572b6a4403SJohan Hovold 		 * Assumes write_raw can always accept GNSS_WRITE_BUF_SIZE
1582b6a4403SJohan Hovold 		 * bytes.
1592b6a4403SJohan Hovold 		 *
1602b6a4403SJohan Hovold 		 * FIXME: revisit
1612b6a4403SJohan Hovold 		 */
1622b6a4403SJohan Hovold 		down_read(&gdev->rwsem);
1632b6a4403SJohan Hovold 		if (!gdev->disconnected)
1642b6a4403SJohan Hovold 			ret = gdev->ops->write_raw(gdev, gdev->write_buf, n);
1652b6a4403SJohan Hovold 		else
1662b6a4403SJohan Hovold 			ret = -EIO;
1672b6a4403SJohan Hovold 		up_read(&gdev->rwsem);
1682b6a4403SJohan Hovold 
1692b6a4403SJohan Hovold 		if (ret < 0)
1702b6a4403SJohan Hovold 			break;
1712b6a4403SJohan Hovold 
1722b6a4403SJohan Hovold 		written += ret;
1732b6a4403SJohan Hovold 		buf += ret;
1742b6a4403SJohan Hovold 
1752b6a4403SJohan Hovold 		if (written == count)
1762b6a4403SJohan Hovold 			break;
1772b6a4403SJohan Hovold 	}
1782b6a4403SJohan Hovold 
1792b6a4403SJohan Hovold 	if (written)
1802b6a4403SJohan Hovold 		ret = written;
1812b6a4403SJohan Hovold out_unlock:
1822b6a4403SJohan Hovold 	mutex_unlock(&gdev->write_mutex);
1832b6a4403SJohan Hovold 
1842b6a4403SJohan Hovold 	return ret;
1852b6a4403SJohan Hovold }
1862b6a4403SJohan Hovold 
gnss_poll(struct file * file,poll_table * wait)1872b6a4403SJohan Hovold static __poll_t gnss_poll(struct file *file, poll_table *wait)
1882b6a4403SJohan Hovold {
1892b6a4403SJohan Hovold 	struct gnss_device *gdev = file->private_data;
1902b6a4403SJohan Hovold 	__poll_t mask = 0;
1912b6a4403SJohan Hovold 
1922b6a4403SJohan Hovold 	poll_wait(file, &gdev->read_queue, wait);
1932b6a4403SJohan Hovold 
1942b6a4403SJohan Hovold 	if (!kfifo_is_empty(&gdev->read_fifo))
1952b6a4403SJohan Hovold 		mask |= EPOLLIN | EPOLLRDNORM;
1962b6a4403SJohan Hovold 	if (gdev->disconnected)
1972b6a4403SJohan Hovold 		mask |= EPOLLHUP;
1982b6a4403SJohan Hovold 
1992b6a4403SJohan Hovold 	return mask;
2002b6a4403SJohan Hovold }
2012b6a4403SJohan Hovold 
2022b6a4403SJohan Hovold static const struct file_operations gnss_fops = {
2032b6a4403SJohan Hovold 	.owner		= THIS_MODULE,
2042b6a4403SJohan Hovold 	.open		= gnss_open,
2052b6a4403SJohan Hovold 	.release	= gnss_release,
2062b6a4403SJohan Hovold 	.read		= gnss_read,
2072b6a4403SJohan Hovold 	.write		= gnss_write,
2082b6a4403SJohan Hovold 	.poll		= gnss_poll,
2092b6a4403SJohan Hovold 	.llseek		= no_llseek,
2102b6a4403SJohan Hovold };
2112b6a4403SJohan Hovold 
2122b6a4403SJohan Hovold static struct class *gnss_class;
2132b6a4403SJohan Hovold 
gnss_device_release(struct device * dev)2142b6a4403SJohan Hovold static void gnss_device_release(struct device *dev)
2152b6a4403SJohan Hovold {
2162b6a4403SJohan Hovold 	struct gnss_device *gdev = to_gnss_device(dev);
2172b6a4403SJohan Hovold 
2182b6a4403SJohan Hovold 	kfree(gdev->write_buf);
2192b6a4403SJohan Hovold 	kfifo_free(&gdev->read_fifo);
220c0c725d7SBo Liu 	ida_free(&gnss_minors, gdev->id);
2212b6a4403SJohan Hovold 	kfree(gdev);
2222b6a4403SJohan Hovold }
2232b6a4403SJohan Hovold 
gnss_allocate_device(struct device * parent)2242b6a4403SJohan Hovold struct gnss_device *gnss_allocate_device(struct device *parent)
2252b6a4403SJohan Hovold {
2262b6a4403SJohan Hovold 	struct gnss_device *gdev;
2272b6a4403SJohan Hovold 	struct device *dev;
2282b6a4403SJohan Hovold 	int id;
2292b6a4403SJohan Hovold 	int ret;
2302b6a4403SJohan Hovold 
2312b6a4403SJohan Hovold 	gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
2322b6a4403SJohan Hovold 	if (!gdev)
2332b6a4403SJohan Hovold 		return NULL;
2342b6a4403SJohan Hovold 
235c0c725d7SBo Liu 	id = ida_alloc_max(&gnss_minors, GNSS_MINORS - 1, GFP_KERNEL);
2362b6a4403SJohan Hovold 	if (id < 0) {
2372b6a4403SJohan Hovold 		kfree(gdev);
238d9995a0fSDan Carpenter 		return NULL;
2392b6a4403SJohan Hovold 	}
2402b6a4403SJohan Hovold 
2412b6a4403SJohan Hovold 	gdev->id = id;
2422b6a4403SJohan Hovold 
2432b6a4403SJohan Hovold 	dev = &gdev->dev;
2442b6a4403SJohan Hovold 	device_initialize(dev);
2452b6a4403SJohan Hovold 	dev->devt = gnss_first + id;
2462b6a4403SJohan Hovold 	dev->class = gnss_class;
2472b6a4403SJohan Hovold 	dev->parent = parent;
2482b6a4403SJohan Hovold 	dev->release = gnss_device_release;
2492b6a4403SJohan Hovold 	dev_set_drvdata(dev, gdev);
2502b6a4403SJohan Hovold 	dev_set_name(dev, "gnss%d", id);
2512b6a4403SJohan Hovold 
2522b6a4403SJohan Hovold 	init_rwsem(&gdev->rwsem);
2532b6a4403SJohan Hovold 	mutex_init(&gdev->read_mutex);
2542b6a4403SJohan Hovold 	mutex_init(&gdev->write_mutex);
2552b6a4403SJohan Hovold 	init_waitqueue_head(&gdev->read_queue);
2562b6a4403SJohan Hovold 
2572b6a4403SJohan Hovold 	ret = kfifo_alloc(&gdev->read_fifo, GNSS_READ_FIFO_SIZE, GFP_KERNEL);
2582b6a4403SJohan Hovold 	if (ret)
2592b6a4403SJohan Hovold 		goto err_put_device;
2602b6a4403SJohan Hovold 
2612b6a4403SJohan Hovold 	gdev->write_buf = kzalloc(GNSS_WRITE_BUF_SIZE, GFP_KERNEL);
2622b6a4403SJohan Hovold 	if (!gdev->write_buf)
2632b6a4403SJohan Hovold 		goto err_put_device;
2642b6a4403SJohan Hovold 
2652b6a4403SJohan Hovold 	cdev_init(&gdev->cdev, &gnss_fops);
2662b6a4403SJohan Hovold 	gdev->cdev.owner = THIS_MODULE;
2672b6a4403SJohan Hovold 
2682b6a4403SJohan Hovold 	return gdev;
2692b6a4403SJohan Hovold 
2702b6a4403SJohan Hovold err_put_device:
2712b6a4403SJohan Hovold 	put_device(dev);
2722b6a4403SJohan Hovold 
273d9995a0fSDan Carpenter 	return NULL;
2742b6a4403SJohan Hovold }
2752b6a4403SJohan Hovold EXPORT_SYMBOL_GPL(gnss_allocate_device);
2762b6a4403SJohan Hovold 
gnss_put_device(struct gnss_device * gdev)2772b6a4403SJohan Hovold void gnss_put_device(struct gnss_device *gdev)
2782b6a4403SJohan Hovold {
2792b6a4403SJohan Hovold 	put_device(&gdev->dev);
2802b6a4403SJohan Hovold }
2812b6a4403SJohan Hovold EXPORT_SYMBOL_GPL(gnss_put_device);
2822b6a4403SJohan Hovold 
gnss_register_device(struct gnss_device * gdev)2832b6a4403SJohan Hovold int gnss_register_device(struct gnss_device *gdev)
2842b6a4403SJohan Hovold {
2852b6a4403SJohan Hovold 	int ret;
2862b6a4403SJohan Hovold 
2872b6a4403SJohan Hovold 	/* Set a flag which can be accessed without holding the rwsem. */
2882b6a4403SJohan Hovold 	if (gdev->ops->write_raw != NULL)
2892b6a4403SJohan Hovold 		gdev->flags |= GNSS_FLAG_HAS_WRITE_RAW;
2902b6a4403SJohan Hovold 
2912b6a4403SJohan Hovold 	ret = cdev_device_add(&gdev->cdev, &gdev->dev);
2922b6a4403SJohan Hovold 	if (ret) {
2932b6a4403SJohan Hovold 		dev_err(&gdev->dev, "failed to add device: %d\n", ret);
2942b6a4403SJohan Hovold 		return ret;
2952b6a4403SJohan Hovold 	}
2962b6a4403SJohan Hovold 
2972b6a4403SJohan Hovold 	return 0;
2982b6a4403SJohan Hovold }
2992b6a4403SJohan Hovold EXPORT_SYMBOL_GPL(gnss_register_device);
3002b6a4403SJohan Hovold 
gnss_deregister_device(struct gnss_device * gdev)3012b6a4403SJohan Hovold void gnss_deregister_device(struct gnss_device *gdev)
3022b6a4403SJohan Hovold {
3032b6a4403SJohan Hovold 	down_write(&gdev->rwsem);
3042b6a4403SJohan Hovold 	gdev->disconnected = true;
3052b6a4403SJohan Hovold 	if (gdev->count) {
3062b6a4403SJohan Hovold 		wake_up_interruptible(&gdev->read_queue);
3072b6a4403SJohan Hovold 		gdev->ops->close(gdev);
3082b6a4403SJohan Hovold 	}
3092b6a4403SJohan Hovold 	up_write(&gdev->rwsem);
3102b6a4403SJohan Hovold 
3112b6a4403SJohan Hovold 	cdev_device_del(&gdev->cdev, &gdev->dev);
3122b6a4403SJohan Hovold }
3132b6a4403SJohan Hovold EXPORT_SYMBOL_GPL(gnss_deregister_device);
3142b6a4403SJohan Hovold 
3152b6a4403SJohan Hovold /*
3162b6a4403SJohan Hovold  * Caller guarantees serialisation.
3172b6a4403SJohan Hovold  *
3182b6a4403SJohan Hovold  * Must not be called for a closed device.
3192b6a4403SJohan Hovold  */
gnss_insert_raw(struct gnss_device * gdev,const unsigned char * buf,size_t count)3202b6a4403SJohan Hovold int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf,
3212b6a4403SJohan Hovold 				size_t count)
3222b6a4403SJohan Hovold {
3232b6a4403SJohan Hovold 	int ret;
3242b6a4403SJohan Hovold 
3252b6a4403SJohan Hovold 	ret = kfifo_in(&gdev->read_fifo, buf, count);
3262b6a4403SJohan Hovold 
3272b6a4403SJohan Hovold 	wake_up_interruptible(&gdev->read_queue);
3282b6a4403SJohan Hovold 
3292b6a4403SJohan Hovold 	return ret;
3302b6a4403SJohan Hovold }
3312b6a4403SJohan Hovold EXPORT_SYMBOL_GPL(gnss_insert_raw);
3322b6a4403SJohan Hovold 
33310f14663SJohan Hovold static const char * const gnss_type_names[GNSS_TYPE_COUNT] = {
33410f14663SJohan Hovold 	[GNSS_TYPE_NMEA]	= "NMEA",
33510f14663SJohan Hovold 	[GNSS_TYPE_SIRF]	= "SiRF",
33610f14663SJohan Hovold 	[GNSS_TYPE_UBX]		= "UBX",
337625239d4SLoys Ollivier 	[GNSS_TYPE_MTK]		= "MTK",
33810f14663SJohan Hovold };
33910f14663SJohan Hovold 
gnss_type_name(const struct gnss_device * gdev)34023680f0bSGreg Kroah-Hartman static const char *gnss_type_name(const struct gnss_device *gdev)
34110f14663SJohan Hovold {
34210f14663SJohan Hovold 	const char *name = NULL;
34310f14663SJohan Hovold 
34410f14663SJohan Hovold 	if (gdev->type < GNSS_TYPE_COUNT)
34510f14663SJohan Hovold 		name = gnss_type_names[gdev->type];
34610f14663SJohan Hovold 
34710f14663SJohan Hovold 	if (!name)
34810f14663SJohan Hovold 		dev_WARN(&gdev->dev, "type name not defined\n");
34910f14663SJohan Hovold 
35010f14663SJohan Hovold 	return name;
35110f14663SJohan Hovold }
35210f14663SJohan Hovold 
type_show(struct device * dev,struct device_attribute * attr,char * buf)35310f14663SJohan Hovold static ssize_t type_show(struct device *dev, struct device_attribute *attr,
35410f14663SJohan Hovold 				char *buf)
35510f14663SJohan Hovold {
35610f14663SJohan Hovold 	struct gnss_device *gdev = to_gnss_device(dev);
35710f14663SJohan Hovold 
35810f14663SJohan Hovold 	return sprintf(buf, "%s\n", gnss_type_name(gdev));
35910f14663SJohan Hovold }
36010f14663SJohan Hovold static DEVICE_ATTR_RO(type);
36110f14663SJohan Hovold 
36210f14663SJohan Hovold static struct attribute *gnss_attrs[] = {
36310f14663SJohan Hovold 	&dev_attr_type.attr,
36410f14663SJohan Hovold 	NULL,
36510f14663SJohan Hovold };
36610f14663SJohan Hovold ATTRIBUTE_GROUPS(gnss);
36710f14663SJohan Hovold 
gnss_uevent(const struct device * dev,struct kobj_uevent_env * env)36823680f0bSGreg Kroah-Hartman static int gnss_uevent(const struct device *dev, struct kobj_uevent_env *env)
36910f14663SJohan Hovold {
37023680f0bSGreg Kroah-Hartman 	const struct gnss_device *gdev = to_gnss_device(dev);
37110f14663SJohan Hovold 	int ret;
37210f14663SJohan Hovold 
37310f14663SJohan Hovold 	ret = add_uevent_var(env, "GNSS_TYPE=%s", gnss_type_name(gdev));
37410f14663SJohan Hovold 	if (ret)
37510f14663SJohan Hovold 		return ret;
37610f14663SJohan Hovold 
37710f14663SJohan Hovold 	return 0;
37810f14663SJohan Hovold }
37910f14663SJohan Hovold 
gnss_module_init(void)3802b6a4403SJohan Hovold static int __init gnss_module_init(void)
3812b6a4403SJohan Hovold {
3822b6a4403SJohan Hovold 	int ret;
3832b6a4403SJohan Hovold 
3842b6a4403SJohan Hovold 	ret = alloc_chrdev_region(&gnss_first, 0, GNSS_MINORS, "gnss");
3852b6a4403SJohan Hovold 	if (ret < 0) {
3862b6a4403SJohan Hovold 		pr_err("failed to allocate device numbers: %d\n", ret);
3872b6a4403SJohan Hovold 		return ret;
3882b6a4403SJohan Hovold 	}
3892b6a4403SJohan Hovold 
390*1aaba11dSGreg Kroah-Hartman 	gnss_class = class_create("gnss");
3912b6a4403SJohan Hovold 	if (IS_ERR(gnss_class)) {
3922b6a4403SJohan Hovold 		ret = PTR_ERR(gnss_class);
3932b6a4403SJohan Hovold 		pr_err("failed to create class: %d\n", ret);
3942b6a4403SJohan Hovold 		goto err_unregister_chrdev;
3952b6a4403SJohan Hovold 	}
3962b6a4403SJohan Hovold 
39710f14663SJohan Hovold 	gnss_class->dev_groups = gnss_groups;
39810f14663SJohan Hovold 	gnss_class->dev_uevent = gnss_uevent;
39910f14663SJohan Hovold 
4002b6a4403SJohan Hovold 	pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first));
4012b6a4403SJohan Hovold 
4022b6a4403SJohan Hovold 	return 0;
4032b6a4403SJohan Hovold 
4042b6a4403SJohan Hovold err_unregister_chrdev:
4052b6a4403SJohan Hovold 	unregister_chrdev_region(gnss_first, GNSS_MINORS);
4062b6a4403SJohan Hovold 
4072b6a4403SJohan Hovold 	return ret;
4082b6a4403SJohan Hovold }
4092b6a4403SJohan Hovold module_init(gnss_module_init);
4102b6a4403SJohan Hovold 
gnss_module_exit(void)4112b6a4403SJohan Hovold static void __exit gnss_module_exit(void)
4122b6a4403SJohan Hovold {
4132b6a4403SJohan Hovold 	class_destroy(gnss_class);
4142b6a4403SJohan Hovold 	unregister_chrdev_region(gnss_first, GNSS_MINORS);
4152b6a4403SJohan Hovold 	ida_destroy(&gnss_minors);
4162b6a4403SJohan Hovold }
4172b6a4403SJohan Hovold module_exit(gnss_module_exit);
4182b6a4403SJohan Hovold 
4192b6a4403SJohan Hovold MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
4202b6a4403SJohan Hovold MODULE_DESCRIPTION("GNSS receiver core");
4212b6a4403SJohan Hovold MODULE_LICENSE("GPL v2");
422