162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * GNSS receiver core 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2018 Johan Hovold <johan@kernel.org> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/cdev.h> 1162306a36Sopenharmony_ci#include <linux/errno.h> 1262306a36Sopenharmony_ci#include <linux/fs.h> 1362306a36Sopenharmony_ci#include <linux/gnss.h> 1462306a36Sopenharmony_ci#include <linux/idr.h> 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/kernel.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci#include <linux/poll.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/uaccess.h> 2162306a36Sopenharmony_ci#include <linux/wait.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define GNSS_FLAG_HAS_WRITE_RAW BIT(0) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define GNSS_MINORS 16 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic DEFINE_IDA(gnss_minors); 2862306a36Sopenharmony_cistatic dev_t gnss_first; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* FIFO size must be a power of two */ 3162306a36Sopenharmony_ci#define GNSS_READ_FIFO_SIZE 4096 3262306a36Sopenharmony_ci#define GNSS_WRITE_BUF_SIZE 1024 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define to_gnss_device(d) container_of((d), struct gnss_device, dev) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic int gnss_open(struct inode *inode, struct file *file) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci struct gnss_device *gdev; 3962306a36Sopenharmony_ci int ret = 0; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci gdev = container_of(inode->i_cdev, struct gnss_device, cdev); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci get_device(&gdev->dev); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci stream_open(inode, file); 4662306a36Sopenharmony_ci file->private_data = gdev; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci down_write(&gdev->rwsem); 4962306a36Sopenharmony_ci if (gdev->disconnected) { 5062306a36Sopenharmony_ci ret = -ENODEV; 5162306a36Sopenharmony_ci goto unlock; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (gdev->count++ == 0) { 5562306a36Sopenharmony_ci ret = gdev->ops->open(gdev); 5662306a36Sopenharmony_ci if (ret) 5762306a36Sopenharmony_ci gdev->count--; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ciunlock: 6062306a36Sopenharmony_ci up_write(&gdev->rwsem); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (ret) 6362306a36Sopenharmony_ci put_device(&gdev->dev); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return ret; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic int gnss_release(struct inode *inode, struct file *file) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci struct gnss_device *gdev = file->private_data; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci down_write(&gdev->rwsem); 7362306a36Sopenharmony_ci if (gdev->disconnected) 7462306a36Sopenharmony_ci goto unlock; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (--gdev->count == 0) { 7762306a36Sopenharmony_ci gdev->ops->close(gdev); 7862306a36Sopenharmony_ci kfifo_reset(&gdev->read_fifo); 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ciunlock: 8162306a36Sopenharmony_ci up_write(&gdev->rwsem); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci put_device(&gdev->dev); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic ssize_t gnss_read(struct file *file, char __user *buf, 8962306a36Sopenharmony_ci size_t count, loff_t *pos) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct gnss_device *gdev = file->private_data; 9262306a36Sopenharmony_ci unsigned int copied; 9362306a36Sopenharmony_ci int ret; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci mutex_lock(&gdev->read_mutex); 9662306a36Sopenharmony_ci while (kfifo_is_empty(&gdev->read_fifo)) { 9762306a36Sopenharmony_ci mutex_unlock(&gdev->read_mutex); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (gdev->disconnected) 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (file->f_flags & O_NONBLOCK) 10362306a36Sopenharmony_ci return -EAGAIN; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci ret = wait_event_interruptible(gdev->read_queue, 10662306a36Sopenharmony_ci gdev->disconnected || 10762306a36Sopenharmony_ci !kfifo_is_empty(&gdev->read_fifo)); 10862306a36Sopenharmony_ci if (ret) 10962306a36Sopenharmony_ci return -ERESTARTSYS; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci mutex_lock(&gdev->read_mutex); 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci ret = kfifo_to_user(&gdev->read_fifo, buf, count, &copied); 11562306a36Sopenharmony_ci if (ret == 0) 11662306a36Sopenharmony_ci ret = copied; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci mutex_unlock(&gdev->read_mutex); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci return ret; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic ssize_t gnss_write(struct file *file, const char __user *buf, 12462306a36Sopenharmony_ci size_t count, loff_t *pos) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct gnss_device *gdev = file->private_data; 12762306a36Sopenharmony_ci size_t written = 0; 12862306a36Sopenharmony_ci int ret; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if (gdev->disconnected) 13162306a36Sopenharmony_ci return -EIO; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!count) 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (!(gdev->flags & GNSS_FLAG_HAS_WRITE_RAW)) 13762306a36Sopenharmony_ci return -EIO; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Ignoring O_NONBLOCK, write_raw() is synchronous. */ 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci ret = mutex_lock_interruptible(&gdev->write_mutex); 14262306a36Sopenharmony_ci if (ret) 14362306a36Sopenharmony_ci return -ERESTARTSYS; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci for (;;) { 14662306a36Sopenharmony_ci size_t n = count - written; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (n > GNSS_WRITE_BUF_SIZE) 14962306a36Sopenharmony_ci n = GNSS_WRITE_BUF_SIZE; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (copy_from_user(gdev->write_buf, buf, n)) { 15262306a36Sopenharmony_ci ret = -EFAULT; 15362306a36Sopenharmony_ci goto out_unlock; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci /* 15762306a36Sopenharmony_ci * Assumes write_raw can always accept GNSS_WRITE_BUF_SIZE 15862306a36Sopenharmony_ci * bytes. 15962306a36Sopenharmony_ci * 16062306a36Sopenharmony_ci * FIXME: revisit 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_ci down_read(&gdev->rwsem); 16362306a36Sopenharmony_ci if (!gdev->disconnected) 16462306a36Sopenharmony_ci ret = gdev->ops->write_raw(gdev, gdev->write_buf, n); 16562306a36Sopenharmony_ci else 16662306a36Sopenharmony_ci ret = -EIO; 16762306a36Sopenharmony_ci up_read(&gdev->rwsem); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (ret < 0) 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci written += ret; 17362306a36Sopenharmony_ci buf += ret; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (written == count) 17662306a36Sopenharmony_ci break; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (written) 18062306a36Sopenharmony_ci ret = written; 18162306a36Sopenharmony_ciout_unlock: 18262306a36Sopenharmony_ci mutex_unlock(&gdev->write_mutex); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci return ret; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic __poll_t gnss_poll(struct file *file, poll_table *wait) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci struct gnss_device *gdev = file->private_data; 19062306a36Sopenharmony_ci __poll_t mask = 0; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci poll_wait(file, &gdev->read_queue, wait); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (!kfifo_is_empty(&gdev->read_fifo)) 19562306a36Sopenharmony_ci mask |= EPOLLIN | EPOLLRDNORM; 19662306a36Sopenharmony_ci if (gdev->disconnected) 19762306a36Sopenharmony_ci mask |= EPOLLHUP; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci return mask; 20062306a36Sopenharmony_ci} 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic const struct file_operations gnss_fops = { 20362306a36Sopenharmony_ci .owner = THIS_MODULE, 20462306a36Sopenharmony_ci .open = gnss_open, 20562306a36Sopenharmony_ci .release = gnss_release, 20662306a36Sopenharmony_ci .read = gnss_read, 20762306a36Sopenharmony_ci .write = gnss_write, 20862306a36Sopenharmony_ci .poll = gnss_poll, 20962306a36Sopenharmony_ci .llseek = no_llseek, 21062306a36Sopenharmony_ci}; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic struct class *gnss_class; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_cistatic void gnss_device_release(struct device *dev) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct gnss_device *gdev = to_gnss_device(dev); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci kfree(gdev->write_buf); 21962306a36Sopenharmony_ci kfifo_free(&gdev->read_fifo); 22062306a36Sopenharmony_ci ida_free(&gnss_minors, gdev->id); 22162306a36Sopenharmony_ci kfree(gdev); 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistruct gnss_device *gnss_allocate_device(struct device *parent) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct gnss_device *gdev; 22762306a36Sopenharmony_ci struct device *dev; 22862306a36Sopenharmony_ci int id; 22962306a36Sopenharmony_ci int ret; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); 23262306a36Sopenharmony_ci if (!gdev) 23362306a36Sopenharmony_ci return NULL; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci id = ida_alloc_max(&gnss_minors, GNSS_MINORS - 1, GFP_KERNEL); 23662306a36Sopenharmony_ci if (id < 0) { 23762306a36Sopenharmony_ci kfree(gdev); 23862306a36Sopenharmony_ci return NULL; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci gdev->id = id; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci dev = &gdev->dev; 24462306a36Sopenharmony_ci device_initialize(dev); 24562306a36Sopenharmony_ci dev->devt = gnss_first + id; 24662306a36Sopenharmony_ci dev->class = gnss_class; 24762306a36Sopenharmony_ci dev->parent = parent; 24862306a36Sopenharmony_ci dev->release = gnss_device_release; 24962306a36Sopenharmony_ci dev_set_drvdata(dev, gdev); 25062306a36Sopenharmony_ci dev_set_name(dev, "gnss%d", id); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci init_rwsem(&gdev->rwsem); 25362306a36Sopenharmony_ci mutex_init(&gdev->read_mutex); 25462306a36Sopenharmony_ci mutex_init(&gdev->write_mutex); 25562306a36Sopenharmony_ci init_waitqueue_head(&gdev->read_queue); 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci ret = kfifo_alloc(&gdev->read_fifo, GNSS_READ_FIFO_SIZE, GFP_KERNEL); 25862306a36Sopenharmony_ci if (ret) 25962306a36Sopenharmony_ci goto err_put_device; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci gdev->write_buf = kzalloc(GNSS_WRITE_BUF_SIZE, GFP_KERNEL); 26262306a36Sopenharmony_ci if (!gdev->write_buf) 26362306a36Sopenharmony_ci goto err_put_device; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci cdev_init(&gdev->cdev, &gnss_fops); 26662306a36Sopenharmony_ci gdev->cdev.owner = THIS_MODULE; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return gdev; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cierr_put_device: 27162306a36Sopenharmony_ci put_device(dev); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci return NULL; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gnss_allocate_device); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_civoid gnss_put_device(struct gnss_device *gdev) 27862306a36Sopenharmony_ci{ 27962306a36Sopenharmony_ci put_device(&gdev->dev); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gnss_put_device); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ciint gnss_register_device(struct gnss_device *gdev) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci int ret; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* Set a flag which can be accessed without holding the rwsem. */ 28862306a36Sopenharmony_ci if (gdev->ops->write_raw != NULL) 28962306a36Sopenharmony_ci gdev->flags |= GNSS_FLAG_HAS_WRITE_RAW; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci ret = cdev_device_add(&gdev->cdev, &gdev->dev); 29262306a36Sopenharmony_ci if (ret) { 29362306a36Sopenharmony_ci dev_err(&gdev->dev, "failed to add device: %d\n", ret); 29462306a36Sopenharmony_ci return ret; 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci return 0; 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gnss_register_device); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_civoid gnss_deregister_device(struct gnss_device *gdev) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci down_write(&gdev->rwsem); 30462306a36Sopenharmony_ci gdev->disconnected = true; 30562306a36Sopenharmony_ci if (gdev->count) { 30662306a36Sopenharmony_ci wake_up_interruptible(&gdev->read_queue); 30762306a36Sopenharmony_ci gdev->ops->close(gdev); 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci up_write(&gdev->rwsem); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci cdev_device_del(&gdev->cdev, &gdev->dev); 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gnss_deregister_device); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci/* 31662306a36Sopenharmony_ci * Caller guarantees serialisation. 31762306a36Sopenharmony_ci * 31862306a36Sopenharmony_ci * Must not be called for a closed device. 31962306a36Sopenharmony_ci */ 32062306a36Sopenharmony_ciint gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf, 32162306a36Sopenharmony_ci size_t count) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci int ret; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci ret = kfifo_in(&gdev->read_fifo, buf, count); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci wake_up_interruptible(&gdev->read_queue); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return ret; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gnss_insert_raw); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic const char * const gnss_type_names[GNSS_TYPE_COUNT] = { 33462306a36Sopenharmony_ci [GNSS_TYPE_NMEA] = "NMEA", 33562306a36Sopenharmony_ci [GNSS_TYPE_SIRF] = "SiRF", 33662306a36Sopenharmony_ci [GNSS_TYPE_UBX] = "UBX", 33762306a36Sopenharmony_ci [GNSS_TYPE_MTK] = "MTK", 33862306a36Sopenharmony_ci}; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_cistatic const char *gnss_type_name(const struct gnss_device *gdev) 34162306a36Sopenharmony_ci{ 34262306a36Sopenharmony_ci const char *name = NULL; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci if (gdev->type < GNSS_TYPE_COUNT) 34562306a36Sopenharmony_ci name = gnss_type_names[gdev->type]; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci if (!name) 34862306a36Sopenharmony_ci dev_WARN(&gdev->dev, "type name not defined\n"); 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci return name; 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic ssize_t type_show(struct device *dev, struct device_attribute *attr, 35462306a36Sopenharmony_ci char *buf) 35562306a36Sopenharmony_ci{ 35662306a36Sopenharmony_ci struct gnss_device *gdev = to_gnss_device(dev); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci return sprintf(buf, "%s\n", gnss_type_name(gdev)); 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(type); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic struct attribute *gnss_attrs[] = { 36362306a36Sopenharmony_ci &dev_attr_type.attr, 36462306a36Sopenharmony_ci NULL, 36562306a36Sopenharmony_ci}; 36662306a36Sopenharmony_ciATTRIBUTE_GROUPS(gnss); 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_cistatic int gnss_uevent(const struct device *dev, struct kobj_uevent_env *env) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci const struct gnss_device *gdev = to_gnss_device(dev); 37162306a36Sopenharmony_ci int ret; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci ret = add_uevent_var(env, "GNSS_TYPE=%s", gnss_type_name(gdev)); 37462306a36Sopenharmony_ci if (ret) 37562306a36Sopenharmony_ci return ret; 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic int __init gnss_module_init(void) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci int ret; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci ret = alloc_chrdev_region(&gnss_first, 0, GNSS_MINORS, "gnss"); 38562306a36Sopenharmony_ci if (ret < 0) { 38662306a36Sopenharmony_ci pr_err("failed to allocate device numbers: %d\n", ret); 38762306a36Sopenharmony_ci return ret; 38862306a36Sopenharmony_ci } 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci gnss_class = class_create("gnss"); 39162306a36Sopenharmony_ci if (IS_ERR(gnss_class)) { 39262306a36Sopenharmony_ci ret = PTR_ERR(gnss_class); 39362306a36Sopenharmony_ci pr_err("failed to create class: %d\n", ret); 39462306a36Sopenharmony_ci goto err_unregister_chrdev; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci gnss_class->dev_groups = gnss_groups; 39862306a36Sopenharmony_ci gnss_class->dev_uevent = gnss_uevent; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first)); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci return 0; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_cierr_unregister_chrdev: 40562306a36Sopenharmony_ci unregister_chrdev_region(gnss_first, GNSS_MINORS); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci return ret; 40862306a36Sopenharmony_ci} 40962306a36Sopenharmony_cimodule_init(gnss_module_init); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic void __exit gnss_module_exit(void) 41262306a36Sopenharmony_ci{ 41362306a36Sopenharmony_ci class_destroy(gnss_class); 41462306a36Sopenharmony_ci unregister_chrdev_region(gnss_first, GNSS_MINORS); 41562306a36Sopenharmony_ci ida_destroy(&gnss_minors); 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_cimodule_exit(gnss_module_exit); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ciMODULE_AUTHOR("Johan Hovold <johan@kernel.org>"); 42062306a36Sopenharmony_ciMODULE_DESCRIPTION("GNSS receiver core"); 42162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 422