Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/arm/broadcom/bcm2835/raspberrypi_virtgpio.c
178586 views
1
/*
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 2025 Tetsuya Uemura <[email protected]>
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
8
* are met:
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer.
11
* 2. Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25
* SUCH DAMAGE.
26
*/
27
28
/*
29
* This is a driver for bcm2835-virtgpio GPIO controller device found on some
30
* Raspberry Pi models (listed below but not limited to). On which, the green
31
* LED (ACT) is connected to this controller. With the help of this driver, a
32
* node corresponding to the green LED will be created under /dev/led, allowing
33
* us to control it.
34
*
35
* Applicable models (according to the FDTs of those models):
36
* Compute Module 2 (CM2)
37
* 3 Model B (not 3B+)
38
* Compute Module 3 (CM3) and possibly 3+ (CM3+)
39
* Compute Module 4 SODIMM (CM4S)
40
*/
41
42
#include "opt_platform.h"
43
44
#include <sys/param.h>
45
#include <sys/systm.h>
46
#include <sys/gpio.h>
47
#include <sys/kernel.h>
48
#include <sys/malloc.h>
49
#include <sys/module.h>
50
#include <sys/mutex.h>
51
52
#include <vm/vm.h>
53
#include <vm/pmap.h>
54
55
#include <dev/gpio/gpiobusvar.h>
56
#include <dev/ofw/ofw_bus.h>
57
58
#include <arm/broadcom/bcm2835/bcm2835_firmware.h>
59
#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>
60
61
#include "gpio_if.h"
62
63
#define RPI_VIRT_GPIO_PINS 2
64
65
struct rpi_virt_gpio_softc {
66
device_t busdev;
67
device_t firmware;
68
struct mtx sc_mtx;
69
70
void *vaddr; /* Virtual address. */
71
vm_paddr_t paddr; /* Physical address. */
72
73
struct gpio_pin gpio_pins[RPI_VIRT_GPIO_PINS];
74
uint32_t state[RPI_VIRT_GPIO_PINS];
75
};
76
77
#define RPI_VIRT_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)
78
#define RPI_VIRT_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)
79
80
static struct ofw_compat_data compat_data[] = {
81
{"brcm,bcm2835-virtgpio", 1},
82
{NULL, 0}
83
};
84
85
static device_t
86
rpi_virt_gpio_get_bus(device_t dev)
87
{
88
struct rpi_virt_gpio_softc *sc;
89
90
sc = device_get_softc(dev);
91
92
return (sc->busdev);
93
}
94
95
static int
96
rpi_virt_gpio_pin_max(device_t dev, int *maxpin)
97
{
98
*maxpin = RPI_VIRT_GPIO_PINS - 1;
99
100
return (0);
101
}
102
103
static int
104
rpi_virt_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
105
{
106
if (pin >= RPI_VIRT_GPIO_PINS)
107
return (EINVAL);
108
109
*caps = GPIO_PIN_OUTPUT;
110
111
return (0);
112
}
113
114
static int
115
rpi_virt_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
116
{
117
if (pin >= RPI_VIRT_GPIO_PINS)
118
return (EINVAL);
119
120
*flags = GPIO_PIN_OUTPUT;
121
122
return (0);
123
}
124
125
static int
126
rpi_virt_gpio_pin_set(device_t dev, uint32_t pin, uint32_t value)
127
{
128
struct rpi_virt_gpio_softc *sc;
129
uint32_t *ptr;
130
uint16_t on, off;
131
132
if (pin >= RPI_VIRT_GPIO_PINS)
133
return (EINVAL);
134
135
sc = device_get_softc(dev);
136
137
RPI_VIRT_GPIO_LOCK(sc);
138
on = (uint16_t)(sc->state[pin] >> 16);
139
off = (uint16_t)sc->state[pin];
140
141
if (bootverbose)
142
device_printf(dev, "on: %hu, off: %hu, now: %d -> %u\n",
143
on, off, on - off, value);
144
145
if ((value > 0 && on - off != 0) || (value == 0 && on - off == 0)) {
146
RPI_VIRT_GPIO_UNLOCK(sc);
147
return (0);
148
}
149
150
if (value > 0)
151
++on;
152
else
153
++off;
154
155
sc->state[pin] = (on << 16 | off);
156
ptr = (uint32_t *)sc->vaddr;
157
ptr[pin] = sc->state[pin];
158
RPI_VIRT_GPIO_UNLOCK(sc);
159
160
return (0);
161
}
162
163
static int
164
rpi_virt_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val)
165
{
166
struct rpi_virt_gpio_softc *sc;
167
uint32_t *ptr, v;
168
169
if (pin >= RPI_VIRT_GPIO_PINS)
170
return (EINVAL);
171
172
sc = device_get_softc(dev);
173
174
ptr = (uint32_t *)sc->vaddr;
175
RPI_VIRT_GPIO_LOCK(sc);
176
v = ptr[pin];
177
RPI_VIRT_GPIO_UNLOCK(sc);
178
*val = ((uint16_t)(v >> 16) - (uint16_t)v) == 0 ? 0 : 1;
179
180
return (0);
181
}
182
183
static int
184
rpi_virt_gpio_pin_toggle(device_t dev, uint32_t pin)
185
{
186
int rv;
187
unsigned int val;
188
189
if (pin >= RPI_VIRT_GPIO_PINS)
190
return (EINVAL);
191
192
rv = rpi_virt_gpio_pin_get(dev, pin, &val);
193
if (rv != 0)
194
return (rv);
195
196
rv = rpi_virt_gpio_pin_set(dev, pin, val == 0 ? 1 : 0);
197
198
return (rv);
199
}
200
201
static int
202
rpi_virt_gpio_probe(device_t dev)
203
{
204
device_t firmware;
205
phandle_t gpio;
206
union msg_gpiovirtbuf cfg;
207
int rv;
208
209
if (ofw_bus_status_okay(dev) == 0)
210
return (ENXIO);
211
212
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
213
return (ENXIO);
214
215
gpio = ofw_bus_get_node(dev);
216
if (OF_hasprop(gpio, "gpio-controller") == 0)
217
return (ENXIO);
218
219
/* Check whether the firmware is ready. */
220
firmware = device_get_parent(dev);
221
rv = bcm2835_firmware_property(firmware,
222
BCM2835_FIRMWARE_TAG_GET_GPIOVIRTBUF, &cfg, sizeof(cfg));
223
if (rv != 0)
224
return (ENXIO);
225
226
device_set_desc(dev, "Raspberry Pi Virtual GPIO controller");
227
228
return (BUS_PROBE_DEFAULT);
229
}
230
231
static int
232
rpi_virt_gpio_attach(device_t dev)
233
{
234
struct rpi_virt_gpio_softc *sc;
235
union msg_gpiovirtbuf cfg;
236
int i, rv;
237
238
sc = device_get_softc(dev);
239
sc->firmware = device_get_parent(dev);
240
mtx_init(&sc->sc_mtx, "Raspberry Pi virtgpio", NULL, MTX_SPIN);
241
242
/*
243
* According to the Linux source at:
244
* https://github.com/raspberrypi/linux/blob/rpi-6.12.y/drivers/gpio/gpio-bcm-virt.c
245
* it first attempts to set the pre-allocated physical memory address
246
* in the firmware. If it is successfully acquired, access virtgpio via
247
* the virtual memory address mapped to that physical address.
248
*
249
* If the above fails, then as a fallback, attempts to obtain a
250
* physical memory address for accessing virtgpio from the firmware.
251
* And if obtained, link it to a virtual memory address and access
252
* virtgpio via it.
253
*
254
* An OpenWRT virtgpio driver I happened to see at first only
255
* implemented the fallback method. Then I implemented this method on
256
* FreeBSD and tested it with the 20240429 firmware, but it didn't
257
* work.
258
*
259
* At this point, I realised the first method in the source above. So I
260
* implemented this method on FreeBSD and tested it, and it worked. In
261
* my opinion, the second method was used until some time prior to
262
* 20240429, and then the firmware was modified and the first method
263
* was introduced. In my driver, only the first method exists.
264
*/
265
266
/* Allocate a physical memory range for accessing virtgpio. */
267
sc->vaddr = contigmalloc(
268
PAGE_SIZE, /* size */
269
M_DEVBUF, M_ZERO, /* type, flags */
270
0, BCM2838_PERIPH_MAXADDR, /* low, high */
271
PAGE_SIZE, 0); /* alignment, boundary */
272
if (sc->vaddr == NULL) {
273
device_printf(dev, "Failed to allocate memory.\n");
274
return ENOMEM;
275
}
276
sc->paddr = vtophys(sc->vaddr);
277
/* Mark it uncacheable. */
278
pmap_change_attr((vm_offset_t)sc->vaddr, PAGE_SIZE,
279
VM_MEMATTR_UNCACHEABLE);
280
281
if (bootverbose)
282
device_printf(dev,
283
"KVA alloc'd: virtual: %p, phys: %#jx\n",
284
sc->vaddr, (uintmax_t)sc->paddr);
285
286
/* Set this address in firmware. */
287
cfg.req.addr = (uint32_t)sc->paddr;
288
rv = bcm2835_firmware_property(sc->firmware,
289
BCM2835_FIRMWARE_TAG_SET_GPIOVIRTBUF, &cfg, sizeof(cfg));
290
if (bootverbose)
291
device_printf(dev, "rv: %d, addr: 0x%x\n", rv, cfg.resp.addr);
292
if (rv != 0 || cfg.resp.addr != 0)
293
goto fail;
294
295
/* Pins only support output. */
296
for (i = 0; i < RPI_VIRT_GPIO_PINS; i++) {
297
sc->gpio_pins[i].gp_pin = i;
298
sc->gpio_pins[i].gp_caps = sc->gpio_pins[i].gp_flags
299
= GPIO_PIN_OUTPUT;
300
}
301
sc->busdev = gpiobus_add_bus(dev);
302
if (sc->busdev == NULL)
303
goto fail;
304
305
bus_attach_children(dev);
306
return (0);
307
308
fail:
309
/* Release resource if necessary. */
310
free(sc->vaddr, M_DEVBUF);
311
mtx_destroy(&sc->sc_mtx);
312
313
return (ENXIO);
314
}
315
316
static int
317
rpi_virt_gpio_detach(device_t dev)
318
{
319
return (EBUSY);
320
}
321
322
static device_method_t rpi_virt_gpio_methods[] = {
323
/* Device interface */
324
DEVMETHOD(device_probe, rpi_virt_gpio_probe),
325
DEVMETHOD(device_attach, rpi_virt_gpio_attach),
326
DEVMETHOD(device_detach, rpi_virt_gpio_detach),
327
328
/* GPIO protocol */
329
DEVMETHOD(gpio_get_bus, rpi_virt_gpio_get_bus),
330
DEVMETHOD(gpio_pin_max, rpi_virt_gpio_pin_max),
331
DEVMETHOD(gpio_pin_getcaps, rpi_virt_gpio_pin_getcaps),
332
DEVMETHOD(gpio_pin_getflags, rpi_virt_gpio_pin_getflags),
333
DEVMETHOD(gpio_pin_set, rpi_virt_gpio_pin_set),
334
DEVMETHOD(gpio_pin_get, rpi_virt_gpio_pin_get),
335
DEVMETHOD(gpio_pin_toggle, rpi_virt_gpio_pin_toggle),
336
337
DEVMETHOD_END
338
};
339
340
static driver_t rpi_virt_gpio_driver = {
341
"gpio",
342
rpi_virt_gpio_methods,
343
sizeof(struct rpi_virt_gpio_softc),
344
};
345
346
EARLY_DRIVER_MODULE(rpi_virt_gpio, bcm2835_firmware, rpi_virt_gpio_driver,
347
0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);
348
349