Path: blob/main/sys/arm/broadcom/bcm2835/raspberrypi_virtgpio.c
178586 views
/*1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2025 Tetsuya Uemura <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627/*28* This is a driver for bcm2835-virtgpio GPIO controller device found on some29* Raspberry Pi models (listed below but not limited to). On which, the green30* LED (ACT) is connected to this controller. With the help of this driver, a31* node corresponding to the green LED will be created under /dev/led, allowing32* us to control it.33*34* Applicable models (according to the FDTs of those models):35* Compute Module 2 (CM2)36* 3 Model B (not 3B+)37* Compute Module 3 (CM3) and possibly 3+ (CM3+)38* Compute Module 4 SODIMM (CM4S)39*/4041#include "opt_platform.h"4243#include <sys/param.h>44#include <sys/systm.h>45#include <sys/gpio.h>46#include <sys/kernel.h>47#include <sys/malloc.h>48#include <sys/module.h>49#include <sys/mutex.h>5051#include <vm/vm.h>52#include <vm/pmap.h>5354#include <dev/gpio/gpiobusvar.h>55#include <dev/ofw/ofw_bus.h>5657#include <arm/broadcom/bcm2835/bcm2835_firmware.h>58#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>5960#include "gpio_if.h"6162#define RPI_VIRT_GPIO_PINS 26364struct rpi_virt_gpio_softc {65device_t busdev;66device_t firmware;67struct mtx sc_mtx;6869void *vaddr; /* Virtual address. */70vm_paddr_t paddr; /* Physical address. */7172struct gpio_pin gpio_pins[RPI_VIRT_GPIO_PINS];73uint32_t state[RPI_VIRT_GPIO_PINS];74};7576#define RPI_VIRT_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)77#define RPI_VIRT_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)7879static struct ofw_compat_data compat_data[] = {80{"brcm,bcm2835-virtgpio", 1},81{NULL, 0}82};8384static device_t85rpi_virt_gpio_get_bus(device_t dev)86{87struct rpi_virt_gpio_softc *sc;8889sc = device_get_softc(dev);9091return (sc->busdev);92}9394static int95rpi_virt_gpio_pin_max(device_t dev, int *maxpin)96{97*maxpin = RPI_VIRT_GPIO_PINS - 1;9899return (0);100}101102static int103rpi_virt_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)104{105if (pin >= RPI_VIRT_GPIO_PINS)106return (EINVAL);107108*caps = GPIO_PIN_OUTPUT;109110return (0);111}112113static int114rpi_virt_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)115{116if (pin >= RPI_VIRT_GPIO_PINS)117return (EINVAL);118119*flags = GPIO_PIN_OUTPUT;120121return (0);122}123124static int125rpi_virt_gpio_pin_set(device_t dev, uint32_t pin, uint32_t value)126{127struct rpi_virt_gpio_softc *sc;128uint32_t *ptr;129uint16_t on, off;130131if (pin >= RPI_VIRT_GPIO_PINS)132return (EINVAL);133134sc = device_get_softc(dev);135136RPI_VIRT_GPIO_LOCK(sc);137on = (uint16_t)(sc->state[pin] >> 16);138off = (uint16_t)sc->state[pin];139140if (bootverbose)141device_printf(dev, "on: %hu, off: %hu, now: %d -> %u\n",142on, off, on - off, value);143144if ((value > 0 && on - off != 0) || (value == 0 && on - off == 0)) {145RPI_VIRT_GPIO_UNLOCK(sc);146return (0);147}148149if (value > 0)150++on;151else152++off;153154sc->state[pin] = (on << 16 | off);155ptr = (uint32_t *)sc->vaddr;156ptr[pin] = sc->state[pin];157RPI_VIRT_GPIO_UNLOCK(sc);158159return (0);160}161162static int163rpi_virt_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val)164{165struct rpi_virt_gpio_softc *sc;166uint32_t *ptr, v;167168if (pin >= RPI_VIRT_GPIO_PINS)169return (EINVAL);170171sc = device_get_softc(dev);172173ptr = (uint32_t *)sc->vaddr;174RPI_VIRT_GPIO_LOCK(sc);175v = ptr[pin];176RPI_VIRT_GPIO_UNLOCK(sc);177*val = ((uint16_t)(v >> 16) - (uint16_t)v) == 0 ? 0 : 1;178179return (0);180}181182static int183rpi_virt_gpio_pin_toggle(device_t dev, uint32_t pin)184{185int rv;186unsigned int val;187188if (pin >= RPI_VIRT_GPIO_PINS)189return (EINVAL);190191rv = rpi_virt_gpio_pin_get(dev, pin, &val);192if (rv != 0)193return (rv);194195rv = rpi_virt_gpio_pin_set(dev, pin, val == 0 ? 1 : 0);196197return (rv);198}199200static int201rpi_virt_gpio_probe(device_t dev)202{203device_t firmware;204phandle_t gpio;205union msg_gpiovirtbuf cfg;206int rv;207208if (ofw_bus_status_okay(dev) == 0)209return (ENXIO);210211if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)212return (ENXIO);213214gpio = ofw_bus_get_node(dev);215if (OF_hasprop(gpio, "gpio-controller") == 0)216return (ENXIO);217218/* Check whether the firmware is ready. */219firmware = device_get_parent(dev);220rv = bcm2835_firmware_property(firmware,221BCM2835_FIRMWARE_TAG_GET_GPIOVIRTBUF, &cfg, sizeof(cfg));222if (rv != 0)223return (ENXIO);224225device_set_desc(dev, "Raspberry Pi Virtual GPIO controller");226227return (BUS_PROBE_DEFAULT);228}229230static int231rpi_virt_gpio_attach(device_t dev)232{233struct rpi_virt_gpio_softc *sc;234union msg_gpiovirtbuf cfg;235int i, rv;236237sc = device_get_softc(dev);238sc->firmware = device_get_parent(dev);239mtx_init(&sc->sc_mtx, "Raspberry Pi virtgpio", NULL, MTX_SPIN);240241/*242* According to the Linux source at:243* https://github.com/raspberrypi/linux/blob/rpi-6.12.y/drivers/gpio/gpio-bcm-virt.c244* it first attempts to set the pre-allocated physical memory address245* in the firmware. If it is successfully acquired, access virtgpio via246* the virtual memory address mapped to that physical address.247*248* If the above fails, then as a fallback, attempts to obtain a249* physical memory address for accessing virtgpio from the firmware.250* And if obtained, link it to a virtual memory address and access251* virtgpio via it.252*253* An OpenWRT virtgpio driver I happened to see at first only254* implemented the fallback method. Then I implemented this method on255* FreeBSD and tested it with the 20240429 firmware, but it didn't256* work.257*258* At this point, I realised the first method in the source above. So I259* implemented this method on FreeBSD and tested it, and it worked. In260* my opinion, the second method was used until some time prior to261* 20240429, and then the firmware was modified and the first method262* was introduced. In my driver, only the first method exists.263*/264265/* Allocate a physical memory range for accessing virtgpio. */266sc->vaddr = contigmalloc(267PAGE_SIZE, /* size */268M_DEVBUF, M_ZERO, /* type, flags */2690, BCM2838_PERIPH_MAXADDR, /* low, high */270PAGE_SIZE, 0); /* alignment, boundary */271if (sc->vaddr == NULL) {272device_printf(dev, "Failed to allocate memory.\n");273return ENOMEM;274}275sc->paddr = vtophys(sc->vaddr);276/* Mark it uncacheable. */277pmap_change_attr((vm_offset_t)sc->vaddr, PAGE_SIZE,278VM_MEMATTR_UNCACHEABLE);279280if (bootverbose)281device_printf(dev,282"KVA alloc'd: virtual: %p, phys: %#jx\n",283sc->vaddr, (uintmax_t)sc->paddr);284285/* Set this address in firmware. */286cfg.req.addr = (uint32_t)sc->paddr;287rv = bcm2835_firmware_property(sc->firmware,288BCM2835_FIRMWARE_TAG_SET_GPIOVIRTBUF, &cfg, sizeof(cfg));289if (bootverbose)290device_printf(dev, "rv: %d, addr: 0x%x\n", rv, cfg.resp.addr);291if (rv != 0 || cfg.resp.addr != 0)292goto fail;293294/* Pins only support output. */295for (i = 0; i < RPI_VIRT_GPIO_PINS; i++) {296sc->gpio_pins[i].gp_pin = i;297sc->gpio_pins[i].gp_caps = sc->gpio_pins[i].gp_flags298= GPIO_PIN_OUTPUT;299}300sc->busdev = gpiobus_add_bus(dev);301if (sc->busdev == NULL)302goto fail;303304bus_attach_children(dev);305return (0);306307fail:308/* Release resource if necessary. */309free(sc->vaddr, M_DEVBUF);310mtx_destroy(&sc->sc_mtx);311312return (ENXIO);313}314315static int316rpi_virt_gpio_detach(device_t dev)317{318return (EBUSY);319}320321static device_method_t rpi_virt_gpio_methods[] = {322/* Device interface */323DEVMETHOD(device_probe, rpi_virt_gpio_probe),324DEVMETHOD(device_attach, rpi_virt_gpio_attach),325DEVMETHOD(device_detach, rpi_virt_gpio_detach),326327/* GPIO protocol */328DEVMETHOD(gpio_get_bus, rpi_virt_gpio_get_bus),329DEVMETHOD(gpio_pin_max, rpi_virt_gpio_pin_max),330DEVMETHOD(gpio_pin_getcaps, rpi_virt_gpio_pin_getcaps),331DEVMETHOD(gpio_pin_getflags, rpi_virt_gpio_pin_getflags),332DEVMETHOD(gpio_pin_set, rpi_virt_gpio_pin_set),333DEVMETHOD(gpio_pin_get, rpi_virt_gpio_pin_get),334DEVMETHOD(gpio_pin_toggle, rpi_virt_gpio_pin_toggle),335336DEVMETHOD_END337};338339static driver_t rpi_virt_gpio_driver = {340"gpio",341rpi_virt_gpio_methods,342sizeof(struct rpi_virt_gpio_softc),343};344345EARLY_DRIVER_MODULE(rpi_virt_gpio, bcm2835_firmware, rpi_virt_gpio_driver,3460, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);347348349