summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hovold <johan@kernel.org>2018-06-01 04:22:52 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-06-28 07:29:47 -0400
commit2b6a440351436d792b1960822da4b7d6e673f568 (patch)
tree120e01f16584b31ccde83b44d2d13b2a7ed32360
parent7daf201d7fe8334e2d2364d4e8ed3394ec9af819 (diff)
gnss: add GNSS receiver subsystem
Add a new subsystem for GNSS (e.g. GPS) receivers. While GNSS receivers are typically accessed using a UART interface they often also support other I/O interfaces such as I2C, SPI and USB, while yet other devices use iomem or even some form of remote-processor messaging (rpmsg). The new GNSS subsystem abstracts the underlying interface and provides a new "gnss" class type, which exposes a character-device interface (e.g. /dev/gnss0) to user space. This allows GNSS receivers to have a representation in the Linux device model, something which is important not least for power management purposes. Note that the character-device interface provides raw access to whatever protocol the receiver is (currently) using, such as NMEA 0183, UBX or SiRF Binary. These protocols are expected to be continued to be handled by user space for the time being, even if some hybrid solutions are also conceivable (e.g. to have kernel drivers issue management commands). This will still allow for better platform integration by allowing GNSS devices and their resources (e.g. regulators and enable-gpios) to be described by firmware and managed by kernel drivers rather than platform-specific scripts and services. While the current interface is kept minimal, it could be extended using IOCTLs, sysfs or uevents as needs and proper abstraction levels are identified and determined (e.g. for device and feature identification). Signed-off-by: Johan Hovold <johan@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/Kconfig2
-rw-r--r--drivers/Makefile1
-rw-r--r--drivers/gnss/Kconfig11
-rw-r--r--drivers/gnss/Makefile7
-rw-r--r--drivers/gnss/core.c371
-rw-r--r--include/linux/gnss.h66
7 files changed, 464 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 6cfd16790add..436e6dcabf34 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6038,6 +6038,12 @@ F: Documentation/isdn/README.gigaset
6038F: drivers/isdn/gigaset/ 6038F: drivers/isdn/gigaset/
6039F: include/uapi/linux/gigaset_dev.h 6039F: include/uapi/linux/gigaset_dev.h
6040 6040
6041GNSS SUBSYSTEM
6042M: Johan Hovold <johan@kernel.org>
6043S: Maintained
6044F: drivers/gnss/
6045F: include/linux/gnss.h
6046
6041GO7007 MPEG CODEC 6047GO7007 MPEG CODEC
6042M: Hans Verkuil <hans.verkuil@cisco.com> 6048M: Hans Verkuil <hans.verkuil@cisco.com>
6043L: linux-media@vger.kernel.org 6049L: linux-media@vger.kernel.org
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 95b9ccc08165..ab4d43923c4d 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -9,6 +9,8 @@ source "drivers/bus/Kconfig"
9 9
10source "drivers/connector/Kconfig" 10source "drivers/connector/Kconfig"
11 11
12source "drivers/gnss/Kconfig"
13
12source "drivers/mtd/Kconfig" 14source "drivers/mtd/Kconfig"
13 15
14source "drivers/of/Kconfig" 16source "drivers/of/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 24cd47014657..cc9a7c5f7d2c 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -185,3 +185,4 @@ obj-$(CONFIG_TEE) += tee/
185obj-$(CONFIG_MULTIPLEXER) += mux/ 185obj-$(CONFIG_MULTIPLEXER) += mux/
186obj-$(CONFIG_UNISYS_VISORBUS) += visorbus/ 186obj-$(CONFIG_UNISYS_VISORBUS) += visorbus/
187obj-$(CONFIG_SIOX) += siox/ 187obj-$(CONFIG_SIOX) += siox/
188obj-$(CONFIG_GNSS) += gnss/
diff --git a/drivers/gnss/Kconfig b/drivers/gnss/Kconfig
new file mode 100644
index 000000000000..103fcc70992e
--- /dev/null
+++ b/drivers/gnss/Kconfig
@@ -0,0 +1,11 @@
1#
2# GNSS receiver configuration
3#
4
5menuconfig GNSS
6 tristate "GNSS receiver support"
7 ---help---
8 Say Y here if you have a GNSS receiver (e.g. a GPS receiver).
9
10 To compile this driver as a module, choose M here: the module will
11 be called gnss.
diff --git a/drivers/gnss/Makefile b/drivers/gnss/Makefile
new file mode 100644
index 000000000000..1f7a7baab1d9
--- /dev/null
+++ b/drivers/gnss/Makefile
@@ -0,0 +1,7 @@
1# SPDX-License-Identifier: GPL-2.0
2#
3# Makefile for the GNSS subsystem.
4#
5
6obj-$(CONFIG_GNSS) += gnss.o
7gnss-y := core.o
diff --git a/drivers/gnss/core.c b/drivers/gnss/core.c
new file mode 100644
index 000000000000..307894ca2725
--- /dev/null
+++ b/drivers/gnss/core.c
@@ -0,0 +1,371 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * GNSS receiver core
4 *
5 * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
6 */
7
8#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
9
10#include <linux/cdev.h>
11#include <linux/errno.h>
12#include <linux/fs.h>
13#include <linux/gnss.h>
14#include <linux/idr.h>
15#include <linux/init.h>
16#include <linux/kernel.h>
17#include <linux/module.h>
18#include <linux/poll.h>
19#include <linux/slab.h>
20#include <linux/uaccess.h>
21#include <linux/wait.h>
22
23#define GNSS_FLAG_HAS_WRITE_RAW BIT(0)
24
25#define GNSS_MINORS 16
26
27static DEFINE_IDA(gnss_minors);
28static dev_t gnss_first;
29
30/* FIFO size must be a power of two */
31#define GNSS_READ_FIFO_SIZE 4096
32#define GNSS_WRITE_BUF_SIZE 1024
33
34#define to_gnss_device(d) container_of((d), struct gnss_device, dev)
35
36static int gnss_open(struct inode *inode, struct file *file)
37{
38 struct gnss_device *gdev;
39 int ret = 0;
40
41 gdev = container_of(inode->i_cdev, struct gnss_device, cdev);
42
43 get_device(&gdev->dev);
44
45 nonseekable_open(inode, file);
46 file->private_data = gdev;
47
48 down_write(&gdev->rwsem);
49 if (gdev->disconnected) {
50 ret = -ENODEV;
51 goto unlock;
52 }
53
54 if (gdev->count++ == 0) {
55 ret = gdev->ops->open(gdev);
56 if (ret)
57 gdev->count--;
58 }
59unlock:
60 up_write(&gdev->rwsem);
61
62 if (ret)
63 put_device(&gdev->dev);
64
65 return ret;
66}
67
68static int gnss_release(struct inode *inode, struct file *file)
69{
70 struct gnss_device *gdev = file->private_data;
71
72 down_write(&gdev->rwsem);
73 if (gdev->disconnected)
74 goto unlock;
75
76 if (--gdev->count == 0) {
77 gdev->ops->close(gdev);
78 kfifo_reset(&gdev->read_fifo);
79 }
80unlock:
81 up_write(&gdev->rwsem);
82
83 put_device(&gdev->dev);
84
85 return 0;
86}
87
88static ssize_t gnss_read(struct file *file, char __user *buf,
89 size_t count, loff_t *pos)
90{
91 struct gnss_device *gdev = file->private_data;
92 unsigned int copied;
93 int ret;
94
95 mutex_lock(&gdev->read_mutex);
96 while (kfifo_is_empty(&gdev->read_fifo)) {
97 mutex_unlock(&gdev->read_mutex);
98
99 if (gdev->disconnected)
100 return 0;
101
102 if (file->f_flags & O_NONBLOCK)
103 return -EAGAIN;
104
105 ret = wait_event_interruptible(gdev->read_queue,
106 gdev->disconnected ||
107 !kfifo_is_empty(&gdev->read_fifo));
108 if (ret)
109 return -ERESTARTSYS;
110
111 mutex_lock(&gdev->read_mutex);
112 }
113
114 ret = kfifo_to_user(&gdev->read_fifo, buf, count, &copied);
115 if (ret == 0)
116 ret = copied;
117
118 mutex_unlock(&gdev->read_mutex);
119
120 return ret;
121}
122
123static ssize_t gnss_write(struct file *file, const char __user *buf,
124 size_t count, loff_t *pos)
125{
126 struct gnss_device *gdev = file->private_data;
127 size_t written = 0;
128 int ret;
129
130 if (gdev->disconnected)
131 return -EIO;
132
133 if (!count)
134 return 0;
135
136 if (!(gdev->flags & GNSS_FLAG_HAS_WRITE_RAW))
137 return -EIO;
138
139 /* Ignoring O_NONBLOCK, write_raw() is synchronous. */
140
141 ret = mutex_lock_interruptible(&gdev->write_mutex);
142 if (ret)
143 return -ERESTARTSYS;
144
145 for (;;) {
146 size_t n = count - written;
147
148 if (n > GNSS_WRITE_BUF_SIZE)
149 n = GNSS_WRITE_BUF_SIZE;
150
151 if (copy_from_user(gdev->write_buf, buf, n)) {
152 ret = -EFAULT;
153 goto out_unlock;
154 }
155
156 /*
157 * Assumes write_raw can always accept GNSS_WRITE_BUF_SIZE
158 * bytes.
159 *
160 * FIXME: revisit
161 */
162 down_read(&gdev->rwsem);
163 if (!gdev->disconnected)
164 ret = gdev->ops->write_raw(gdev, gdev->write_buf, n);
165 else
166 ret = -EIO;
167 up_read(&gdev->rwsem);
168
169 if (ret < 0)
170 break;
171
172 written += ret;
173 buf += ret;
174
175 if (written == count)
176 break;
177 }
178
179 if (written)
180 ret = written;
181out_unlock:
182 mutex_unlock(&gdev->write_mutex);
183
184 return ret;
185}
186
187static __poll_t gnss_poll(struct file *file, poll_table *wait)
188{
189 struct gnss_device *gdev = file->private_data;
190 __poll_t mask = 0;
191
192 poll_wait(file, &gdev->read_queue, wait);
193
194 if (!kfifo_is_empty(&gdev->read_fifo))
195 mask |= EPOLLIN | EPOLLRDNORM;
196 if (gdev->disconnected)
197 mask |= EPOLLHUP;
198
199 return mask;
200}
201
202static const struct file_operations gnss_fops = {
203 .owner = THIS_MODULE,
204 .open = gnss_open,
205 .release = gnss_release,
206 .read = gnss_read,
207 .write = gnss_write,
208 .poll = gnss_poll,
209 .llseek = no_llseek,
210};
211
212static struct class *gnss_class;
213
214static void gnss_device_release(struct device *dev)
215{
216 struct gnss_device *gdev = to_gnss_device(dev);
217
218 kfree(gdev->write_buf);
219 kfifo_free(&gdev->read_fifo);
220 ida_simple_remove(&gnss_minors, gdev->id);
221 kfree(gdev);
222}
223
224struct gnss_device *gnss_allocate_device(struct device *parent)
225{
226 struct gnss_device *gdev;
227 struct device *dev;
228 int id;
229 int ret;
230
231 gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
232 if (!gdev)
233 return NULL;
234
235 id = ida_simple_get(&gnss_minors, 0, GNSS_MINORS, GFP_KERNEL);
236 if (id < 0) {
237 kfree(gdev);
238 return ERR_PTR(id);
239 }
240
241 gdev->id = id;
242
243 dev = &gdev->dev;
244 device_initialize(dev);
245 dev->devt = gnss_first + id;
246 dev->class = gnss_class;
247 dev->parent = parent;
248 dev->release = gnss_device_release;
249 dev_set_drvdata(dev, gdev);
250 dev_set_name(dev, "gnss%d", id);
251
252 init_rwsem(&gdev->rwsem);
253 mutex_init(&gdev->read_mutex);
254 mutex_init(&gdev->write_mutex);
255 init_waitqueue_head(&gdev->read_queue);
256
257 ret = kfifo_alloc(&gdev->read_fifo, GNSS_READ_FIFO_SIZE, GFP_KERNEL);
258 if (ret)
259 goto err_put_device;
260
261 gdev->write_buf = kzalloc(GNSS_WRITE_BUF_SIZE, GFP_KERNEL);
262 if (!gdev->write_buf)
263 goto err_put_device;
264
265 cdev_init(&gdev->cdev, &gnss_fops);
266 gdev->cdev.owner = THIS_MODULE;
267
268 return gdev;
269
270err_put_device:
271 put_device(dev);
272
273 return ERR_PTR(-ENOMEM);
274}
275EXPORT_SYMBOL_GPL(gnss_allocate_device);
276
277void gnss_put_device(struct gnss_device *gdev)
278{
279 put_device(&gdev->dev);
280}
281EXPORT_SYMBOL_GPL(gnss_put_device);
282
283int gnss_register_device(struct gnss_device *gdev)
284{
285 int ret;
286
287 /* Set a flag which can be accessed without holding the rwsem. */
288 if (gdev->ops->write_raw != NULL)
289 gdev->flags |= GNSS_FLAG_HAS_WRITE_RAW;
290
291 ret = cdev_device_add(&gdev->cdev, &gdev->dev);
292 if (ret) {
293 dev_err(&gdev->dev, "failed to add device: %d\n", ret);
294 return ret;
295 }
296
297 return 0;
298}
299EXPORT_SYMBOL_GPL(gnss_register_device);
300
301void gnss_deregister_device(struct gnss_device *gdev)
302{
303 down_write(&gdev->rwsem);
304 gdev->disconnected = true;
305 if (gdev->count) {
306 wake_up_interruptible(&gdev->read_queue);
307 gdev->ops->close(gdev);
308 }
309 up_write(&gdev->rwsem);
310
311 cdev_device_del(&gdev->cdev, &gdev->dev);
312}
313EXPORT_SYMBOL_GPL(gnss_deregister_device);
314
315/*
316 * Caller guarantees serialisation.
317 *
318 * Must not be called for a closed device.
319 */
320int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf,
321 size_t count)
322{
323 int ret;
324
325 ret = kfifo_in(&gdev->read_fifo, buf, count);
326
327 wake_up_interruptible(&gdev->read_queue);
328
329 return ret;
330}
331EXPORT_SYMBOL_GPL(gnss_insert_raw);
332
333static int __init gnss_module_init(void)
334{
335 int ret;
336
337 ret = alloc_chrdev_region(&gnss_first, 0, GNSS_MINORS, "gnss");
338 if (ret < 0) {
339 pr_err("failed to allocate device numbers: %d\n", ret);
340 return ret;
341 }
342
343 gnss_class = class_create(THIS_MODULE, "gnss");
344 if (IS_ERR(gnss_class)) {
345 ret = PTR_ERR(gnss_class);
346 pr_err("failed to create class: %d\n", ret);
347 goto err_unregister_chrdev;
348 }
349
350 pr_info("GNSS driver registered with major %d\n", MAJOR(gnss_first));
351
352 return 0;
353
354err_unregister_chrdev:
355 unregister_chrdev_region(gnss_first, GNSS_MINORS);
356
357 return ret;
358}
359module_init(gnss_module_init);
360
361static void __exit gnss_module_exit(void)
362{
363 class_destroy(gnss_class);
364 unregister_chrdev_region(gnss_first, GNSS_MINORS);
365 ida_destroy(&gnss_minors);
366}
367module_exit(gnss_module_exit);
368
369MODULE_AUTHOR("Johan Hovold <johan@kernel.org>");
370MODULE_DESCRIPTION("GNSS receiver core");
371MODULE_LICENSE("GPL v2");
diff --git a/include/linux/gnss.h b/include/linux/gnss.h
new file mode 100644
index 000000000000..e26aeac1e0e2
--- /dev/null
+++ b/include/linux/gnss.h
@@ -0,0 +1,66 @@
1/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * GNSS receiver support
4 *
5 * Copyright (C) 2018 Johan Hovold <johan@kernel.org>
6 */
7
8#ifndef _LINUX_GNSS_H
9#define _LINUX_GNSS_H
10
11#include <linux/cdev.h>
12#include <linux/device.h>
13#include <linux/kfifo.h>
14#include <linux/mutex.h>
15#include <linux/rwsem.h>
16#include <linux/types.h>
17#include <linux/wait.h>
18
19struct gnss_device;
20
21struct gnss_operations {
22 int (*open)(struct gnss_device *gdev);
23 void (*close)(struct gnss_device *gdev);
24 int (*write_raw)(struct gnss_device *gdev, const unsigned char *buf,
25 size_t count);
26};
27
28struct gnss_device {
29 struct device dev;
30 struct cdev cdev;
31 int id;
32
33 unsigned long flags;
34
35 struct rw_semaphore rwsem;
36 const struct gnss_operations *ops;
37 unsigned int count;
38 unsigned int disconnected:1;
39
40 struct mutex read_mutex;
41 struct kfifo read_fifo;
42 wait_queue_head_t read_queue;
43
44 struct mutex write_mutex;
45 char *write_buf;
46};
47
48struct gnss_device *gnss_allocate_device(struct device *parent);
49void gnss_put_device(struct gnss_device *gdev);
50int gnss_register_device(struct gnss_device *gdev);
51void gnss_deregister_device(struct gnss_device *gdev);
52
53int gnss_insert_raw(struct gnss_device *gdev, const unsigned char *buf,
54 size_t count);
55
56static inline void gnss_set_drvdata(struct gnss_device *gdev, void *data)
57{
58 dev_set_drvdata(&gdev->dev, data);
59}
60
61static inline void *gnss_get_drvdata(struct gnss_device *gdev)
62{
63 return dev_get_drvdata(&gdev->dev);
64}
65
66#endif /* _LINUX_GNSS_H */