summaryrefslogtreecommitdiffstats
path: root/target/linux/bcm27xx/patches-6.1/950-0907-drivers-char-add-generic-gpiomem-driver.patch
blob: 14e6b0a9e48f4771a1ea24a94fc459d4cd7f9839 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
From fdf9cab5eaa849e90b12e17718bc47130a91433c Mon Sep 17 00:00:00 2001
From: Jonathan Bell <jonathan@raspberrypi.com>
Date: Tue, 25 Apr 2023 15:52:13 +0100
Subject: [PATCH] drivers: char: add generic gpiomem driver

Based on bcm2835-gpiomem.

We allow export of the "GPIO registers" to userspace via a chardev as
this allows for finer access control (e.g. users must be group gpio, root
not required).

This driver allows access to either rp1-gpiomem or gpiomem, depending on
which nodes are populated in devicetree.

RP1 has a different look-and-feel to BCM283x SoCs as it has split ranges
for IO controls and the parallel registered OE/IN/OUT access. To handle
this, the driver concatenates the ranges for an IO bank and the
corresponding RIO instance into a contiguous buffer.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
---
 drivers/char/Kconfig               |   8 +
 drivers/char/Makefile              |   1 +
 drivers/char/raspberrypi-gpiomem.c | 276 +++++++++++++++++++++++++++++
 3 files changed, 285 insertions(+)
 create mode 100644 drivers/char/raspberrypi-gpiomem.c

--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -461,4 +461,12 @@ config RANDOM_TRUST_BOOTLOADER
 	  believe its RNG facilities may be faulty. This may also be configured
 	  at boot time with "random.trust_bootloader=on/off".
 
+config RASPBERRYPI_GPIOMEM
+        tristate "Rootless GPIO access via mmap() on Raspberry Pi boards"
+        default n
+        help
+                Provides users with root-free access to the GPIO registers
+                on the board. Calling mmap(/dev/gpiomem) will map the GPIO
+                register page to the user's pointer.
+
 endmenu
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -46,3 +46,4 @@ obj-$(CONFIG_XILLYBUS_CLASS)	+= xillybus
 obj-$(CONFIG_POWERNV_OP_PANEL)	+= powernv-op-panel.o
 obj-$(CONFIG_ADI)		+= adi.o
 obj-$(CONFIG_BRCM_CHAR_DRIVERS) += broadcom/
+obj-$(CONFIG_RASPBERRYPI_GPIOMEM) += raspberrypi-gpiomem.o
--- /dev/null
+++ b/drivers/char/raspberrypi-gpiomem.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * raspberrypi-gpiomem.c
+ *
+ * Provides MMIO access to discontiguous section of Device memory as a linear
+ * user mapping. Successor to bcm2835-gpiomem.c.
+ *
+ * Copyright (c) 2023, Raspberry Pi Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/pagemap.h>
+#include <linux/io.h>
+
+#define DRIVER_NAME "rpi-gpiomem"
+#define DEVICE_MINOR 0
+
+/*
+ * Sensible max for a hypothetical "gpio" controller that splits pads,
+ * IO controls, GPIO in/out/enable, and function selection into different
+ * ranges. Most use only one or two.
+ */
+#define MAX_RANGES 4
+
+struct io_windows {
+	unsigned long phys_base;
+	unsigned long len;
+};
+
+struct rpi_gpiomem_priv {
+	dev_t devid;
+	struct class *class;
+	struct cdev rpi_gpiomem_cdev;
+	struct device *dev;
+	const char *name;
+	unsigned int nr_wins;
+	struct io_windows iowins[4];
+};
+
+static int rpi_gpiomem_open(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+	int ret = 0;
+	struct rpi_gpiomem_priv *priv;
+
+	if (dev != DEVICE_MINOR)
+		ret = -ENXIO;
+
+	priv = container_of(inode->i_cdev, struct rpi_gpiomem_priv,
+				rpi_gpiomem_cdev);
+	if (!priv)
+		return -EINVAL;
+	file->private_data = priv;
+	return ret;
+}
+
+static int rpi_gpiomem_release(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+	int ret = 0;
+
+	if (dev != DEVICE_MINOR)
+		ret = -ENXIO;
+
+	return ret;
+}
+
+static const struct vm_operations_struct rpi_gpiomem_vm_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+	.access = generic_access_phys
+#endif
+};
+
+static int rpi_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	int i;
+	struct rpi_gpiomem_priv *priv;
+	unsigned long base;
+	unsigned long len = 0;
+	unsigned long offset;
+
+	priv = file->private_data;
+	/*
+	 * Userspace must provide a virtual address space at least
+	 * the size of the concatenated ranges.
+	 */
+	for (i = 0; i < priv->nr_wins; i++)
+		len += priv->iowins[i].len;
+	if (len > vma->vm_end - vma->vm_start + 1)
+		return -EINVAL;
+
+	vma->vm_ops = &rpi_gpiomem_vm_ops;
+	offset = vma->vm_start;
+	for (i = 0; i < priv->nr_wins; i++) {
+		base = priv->iowins[i].phys_base >> PAGE_SHIFT;
+		len = priv->iowins[i].len;
+		vma->vm_page_prot = phys_mem_access_prot(file, base, len,
+							 vma->vm_page_prot);
+		if (remap_pfn_range(vma, offset,
+			    base, len,
+			    vma->vm_page_prot))
+			break;
+		offset += len;
+	}
+
+	if (i < priv->nr_wins)
+		return -EAGAIN;
+
+	return 0;
+}
+
+static const struct file_operations rpi_gpiomem_fops = {
+	.owner = THIS_MODULE,
+	.open = rpi_gpiomem_open,
+	.release = rpi_gpiomem_release,
+	.mmap = rpi_gpiomem_mmap,
+};
+
+static const struct of_device_id rpi_gpiomem_of_match[];
+
+static int rpi_gpiomem_probe(struct platform_device *pdev)
+{
+	int err, i;
+	const struct of_device_id *id;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource *ioresource;
+	struct rpi_gpiomem_priv *priv;
+
+	/* Allocate buffers and instance data */
+
+	priv = kzalloc(sizeof(struct rpi_gpiomem_priv), GFP_KERNEL);
+
+	if (!priv) {
+		err = -ENOMEM;
+		goto failed_inst_alloc;
+	}
+	platform_set_drvdata(pdev, priv);
+
+	priv->dev = dev;
+	id = of_match_device(rpi_gpiomem_of_match, dev);
+	if (!id)
+		return -EINVAL;
+
+	/*
+	 * Device node naming - for legacy (bcm2835) DT bindings, the driver
+	 * created the node based on a hardcoded name - for new bindings,
+	 * take the node name from DT.
+	 */
+	if (id == &rpi_gpiomem_of_match[0]) {
+		priv->name = "gpiomem";
+	} else {
+		err = of_property_read_string(node, "chardev-name", &priv->name);
+		if (err)
+			return -EINVAL;
+	}
+
+	/*
+	 * Go find the register ranges associated with this instance
+	 */
+	for (i = 0; i < MAX_RANGES; i++) {
+		ioresource = platform_get_resource(pdev, IORESOURCE_MEM, i);
+		if (!ioresource && i == 0) {
+			dev_err(priv->dev, "failed to get IO resource - no ranges available\n");
+			err = -ENOENT;
+			goto failed_get_resource;
+		}
+		if (!ioresource)
+			break;
+
+		priv->iowins[i].phys_base = ioresource->start;
+		priv->iowins[i].len = (ioresource->end + 1) - ioresource->start;
+		dev_info(&pdev->dev, "window base 0x%08lx size 0x%08lx\n",
+			 priv->iowins[i].phys_base, priv->iowins[i].len);
+		priv->nr_wins++;
+	}
+
+	/* Create character device entries */
+
+	err = alloc_chrdev_region(&priv->devid,
+				  DEVICE_MINOR, 1, priv->name);
+	if (err != 0) {
+		dev_err(priv->dev, "unable to allocate device number");
+		goto failed_alloc_chrdev;
+	}
+	cdev_init(&priv->rpi_gpiomem_cdev, &rpi_gpiomem_fops);
+	priv->rpi_gpiomem_cdev.owner = THIS_MODULE;
+	err = cdev_add(&priv->rpi_gpiomem_cdev, priv->devid, 1);
+	if (err != 0) {
+		dev_err(priv->dev, "unable to register device");
+		goto failed_cdev_add;
+	}
+
+	/* Create sysfs entries */
+
+	priv->class = class_create(THIS_MODULE, priv->name);
+	if (IS_ERR(priv->class)) {
+		err = PTR_ERR(priv->class);
+		goto failed_class_create;
+	}
+
+	dev = device_create(priv->class, NULL, priv->devid, NULL, priv->name);
+	if (IS_ERR(dev)) {
+		err = PTR_ERR(dev);
+		goto failed_device_create;
+	}
+
+	dev_info(priv->dev, "initialised %u regions as /dev/%s\n",
+		 priv->nr_wins, priv->name);
+
+	return 0;
+
+failed_device_create:
+	class_destroy(priv->class);
+failed_class_create:
+	cdev_del(&priv->rpi_gpiomem_cdev);
+failed_cdev_add:
+	unregister_chrdev_region(priv->devid, 1);
+failed_alloc_chrdev:
+failed_get_resource:
+	kfree(priv);
+failed_inst_alloc:
+	dev_err(&pdev->dev, "could not load rpi_gpiomem");
+	return err;
+}
+
+static int rpi_gpiomem_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rpi_gpiomem_priv *priv = platform_get_drvdata(pdev);
+
+	device_destroy(priv->class, priv->devid);
+	class_destroy(priv->class);
+	cdev_del(&priv->rpi_gpiomem_cdev);
+	unregister_chrdev_region(priv->devid, 1);
+	kfree(priv);
+
+	dev_info(dev, "%s driver removed - OK", priv->name);
+	return 0;
+}
+
+static const struct of_device_id rpi_gpiomem_of_match[] = {
+	{
+		.compatible = "brcm,bcm2835-gpiomem",
+	},
+	{
+		.compatible = "raspberrypi,gpiomem",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rpi_gpiomem_of_match);
+
+static struct platform_driver rpi_gpiomem_driver = {
+	.probe = rpi_gpiomem_probe,
+	.remove = rpi_gpiomem_remove,
+	.driver = {
+		   .name = DRIVER_NAME,
+		   .owner = THIS_MODULE,
+		   .of_match_table = rpi_gpiomem_of_match,
+		   },
+};
+
+module_platform_driver(rpi_gpiomem_driver);
+
+MODULE_ALIAS("platform:rpi-gpiomem");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Driver for accessing GPIOs from userspace");
+MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>");