From 97826b70d4cf3a95f01c0bfe549418234e9d02a8 Mon Sep 17 00:00:00 2001 From: Chen Xingyu Date: Fri, 29 Sep 2023 21:35:32 +0800 Subject: [PATCH] drivers: gpio: Add GPIO driver for BCM2711 The BCM2711 SoC exposes 58 GPIOs. The first 28 (bank 0) are accessible to users via the 40-pin header, while the others (bank 1) are used for controlling on-board peripherals. Signed-off-by: Chen Xingyu --- drivers/gpio/CMakeLists.txt | 1 + drivers/gpio/Kconfig | 2 + drivers/gpio/Kconfig.bcm2711 | 9 + drivers/gpio/gpio_bcm2711.c | 346 +++++++++++++++++++++++ dts/arm64/broadcom/bcm2711.dtsi | 31 ++ dts/bindings/gpio/brcm,bcm2711-gpio.yaml | 22 ++ 6 files changed, 411 insertions(+) create mode 100644 drivers/gpio/Kconfig.bcm2711 create mode 100644 drivers/gpio/gpio_bcm2711.c create mode 100644 dts/bindings/gpio/brcm,bcm2711-gpio.yaml diff --git a/drivers/gpio/CMakeLists.txt b/drivers/gpio/CMakeLists.txt index b168aa38a5e3c23..a5974e209024122 100644 --- a/drivers/gpio/CMakeLists.txt +++ b/drivers/gpio/CMakeLists.txt @@ -84,6 +84,7 @@ zephyr_library_sources_ifdef(CONFIG_GPIO_EFINIX_SAPPHIRE gpio_efinix_sapphire zephyr_library_sources_ifdef(CONFIG_GPIO_DAVINCI gpio_davinci.c) zephyr_library_sources_ifdef(CONFIG_GPIO_SEDI gpio_sedi.c) zephyr_library_sources_ifdef(CONFIG_GPIO_ALTERA_PIO gpio_altera_pio.c) +zephyr_library_sources_ifdef(CONFIG_GPIO_BCM2711 gpio_bcm2711.c) if (CONFIG_GPIO_EMUL_SDL) zephyr_library_sources(gpio_emul_sdl.c) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index b008da0bd321dee..cce660403b4a3ec 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -208,4 +208,6 @@ source "drivers/gpio/Kconfig.sedi" source "drivers/gpio/Kconfig.altera" +source "drivers/gpio/Kconfig.bcm2711" + endif # GPIO diff --git a/drivers/gpio/Kconfig.bcm2711 b/drivers/gpio/Kconfig.bcm2711 new file mode 100644 index 000000000000000..ad7c3c11b13f5b4 --- /dev/null +++ b/drivers/gpio/Kconfig.bcm2711 @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Chen Xingyu +# SPDX-License-Identifier: Apache-2.0 + +config GPIO_BCM2711 + bool "BCM2711 GPIO driver" + default y + depends on DT_HAS_BRCM_BCM2711_GPIO_ENABLED + help + Enable BCM2711 GPIO driver. diff --git a/drivers/gpio/gpio_bcm2711.c b/drivers/gpio/gpio_bcm2711.c new file mode 100644 index 000000000000000..3f9f66995116b8d --- /dev/null +++ b/drivers/gpio/gpio_bcm2711.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2023 Chen Xingyu + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT brcm_bcm2711_gpio + +#include +#include +#include +#include +#include + +#define GPIO_REG_GROUP(n, cnt) (n / cnt) +#define GPIO_REG_SHIFT(n, cnt) (n % cnt) + +#define GPFSEL(base, n) (base + 0x00 + 0x04 * n) +#define GPSET(base, n) (base + 0x1C + 0x04 * n) +#define GPCLR(base, n) (base + 0x28 + 0x04 * n) +#define GPLEV(base, n) (base + 0x34 + 0x04 * n) +#define GPEDS(base, n) (base + 0x40 + 0x04 * n) +#define GPREN(base, n) (base + 0x4C + 0x04 * n) +#define GPFEN(base, n) (base + 0x58 + 0x04 * n) +#define GPHEN(base, n) (base + 0x64 + 0x04 * n) +#define GPLEN(base, n) (base + 0x70 + 0x04 * n) +#define GPAREN(base, n) (base + 0x7C + 0x04 * n) +#define GPAFEN(base, n) (base + 0x88 + 0x04 * n) +#define GPIO_PUP_PDN_CNTRL_REG(base, n) (base + 0xE4 + 0x04 * n) + +#define FSEL_GROUPS (10) +#define FSEL_BITS (3) +#define FSEL_OUTPUT (0x1) + +#define IO_GROUPS (32) + +#define PULL_GROUPS (16) +#define PULL_BITS (2) +#define PULL_UP (0x1) +#define PULL_DOWN (0x2) + +#define DEV_CFG(dev) ((const struct gpio_bcm2711_config *const)(dev)->config) +#define DEV_DATA(dev) ((struct gpio_bcm2711_data *const)(dev)->data) + +#define RPI_PIN_NUM(dev, n) (DEV_CFG(dev)->offset + n) + +struct gpio_bcm2711_config { + struct gpio_driver_config common; + + DEVICE_MMIO_NAMED_ROM(reg_base); + + void (*irq_config_func)(void); + + uint8_t offset; + uint8_t ngpios; +}; + +struct gpio_bcm2711_data { + struct gpio_driver_data common; + + DEVICE_MMIO_NAMED_RAM(reg_base); + mem_addr_t base; + + sys_slist_t cb; +}; + +static int gpio_bcm2711_pin_configure(const struct device *port, gpio_pin_t pin, gpio_flags_t flags) +{ + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t group; + uint32_t shift; + uint32_t regval; + + /* Set direction */ + if (flags & (GPIO_INPUT | GPIO_OUTPUT)) { + group = GPIO_REG_GROUP(RPI_PIN_NUM(port, pin), FSEL_GROUPS); + shift = GPIO_REG_SHIFT(RPI_PIN_NUM(port, pin), FSEL_GROUPS); + + regval = sys_read32(GPFSEL(data->base, group)); + regval &= ~(BIT_MASK(FSEL_BITS) << (shift * FSEL_BITS)); + if (flags & GPIO_OUTPUT) { + regval |= (FSEL_OUTPUT << (shift * FSEL_BITS)); + } + sys_write32(regval, GPFSEL(data->base, group)); + } + + /* Set output value */ + if (flags & GPIO_OUTPUT) { + if (flags & GPIO_OPEN_DRAIN) { + return -ENOTSUP; + } + + group = GPIO_REG_GROUP(RPI_PIN_NUM(port, pin), IO_GROUPS); + shift = GPIO_REG_SHIFT(RPI_PIN_NUM(port, pin), IO_GROUPS); + + if (flags & GPIO_OUTPUT_INIT_HIGH) { + regval = sys_read32(GPSET(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPSET(data->base, group)); + } else if (flags & GPIO_OUTPUT_INIT_LOW) { + regval = sys_read32(GPCLR(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPCLR(data->base, group)); + } + } + + /* Set pull */ + if (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) { + group = GPIO_REG_GROUP(RPI_PIN_NUM(port, pin), PULL_GROUPS); + shift = GPIO_REG_SHIFT(RPI_PIN_NUM(port, pin), PULL_GROUPS); + + regval = sys_read32(GPIO_PUP_PDN_CNTRL_REG(data->base, group)); + regval &= ~(BIT_MASK(PULL_BITS) << (shift * PULL_BITS)); + if (flags & GPIO_PULL_UP) { + regval |= (PULL_UP << (shift * PULL_BITS)); + } else { + regval |= (PULL_DOWN << (shift * PULL_BITS)); + } + sys_write32(regval, GPIO_PUP_PDN_CNTRL_REG(data->base, group)); + } + + return 0; +} + +static int gpio_bcm2711_port_get_raw(const struct device *port, gpio_port_value_t *value) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + + regval0 = sys_read32(GPLEV(data->base, 0)); + regval1 = sys_read32(GPLEV(data->base, 1)); + + *value = ((regval0 >> cfg->offset) | (regval1 << (IO_GROUPS - cfg->offset))) & + BIT_MASK(cfg->ngpios); + + return 0; +} + +static int gpio_bcm2711_port_set_masked_raw(const struct device *port, gpio_port_pins_t mask, + gpio_port_value_t value) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + uint32_t mask0, mask1; + + value &= BIT_MASK(cfg->ngpios); + mask &= BIT_MASK(cfg->ngpios); + + regval0 = value << cfg->offset; + mask0 = mask << cfg->offset; + + regval1 = value >> (IO_GROUPS - cfg->offset); + mask1 = mask >> (IO_GROUPS - cfg->offset); + + sys_write32(regval0 & mask0, GPSET(data->base, 0)); + sys_write32(regval0 ^ mask0, GPCLR(data->base, 0)); + sys_write32(regval1 & mask1, GPSET(data->base, 1)); + sys_write32(regval1 ^ mask1, GPCLR(data->base, 1)); + + return 0; +} + +static int gpio_bcm2711_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + + regval0 = pins << cfg->offset; + regval1 = pins >> (IO_GROUPS - cfg->offset); + + sys_write32(regval0, GPSET(data->base, 0)); + sys_write32(regval1, GPSET(data->base, 1)); + + return 0; +} + +static int gpio_bcm2711_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + + regval0 = pins << cfg->offset; + regval1 = pins >> (IO_GROUPS - cfg->offset); + + sys_write32(regval0, GPCLR(data->base, 0)); + sys_write32(regval1, GPCLR(data->base, 1)); + + return 0; +} + +static int gpio_bcm2711_port_toggle_bits(const struct device *port, gpio_port_pins_t pins) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + uint32_t mask0, mask1; + + regval0 = sys_read32(GPLEV(data->base, 0)); + regval1 = sys_read32(GPLEV(data->base, 1)); + + mask0 = pins << cfg->offset; + mask1 = pins >> (IO_GROUPS - cfg->offset); + + sys_write32(regval0 ^ mask0, GPSET(data->base, 0)); + sys_write32(regval0 & mask0, GPCLR(data->base, 0)); + sys_write32(regval1 ^ mask1, GPSET(data->base, 1)); + sys_write32(regval1 & mask1, GPCLR(data->base, 1)); + + return 0; +} + +static int gpio_bcm2711_pin_interrupt_configure(const struct device *port, gpio_pin_t pin, + enum gpio_int_mode mode, enum gpio_int_trig trig) +{ + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t group; + uint32_t shift; + uint32_t regval; + + group = GPIO_REG_GROUP(RPI_PIN_NUM(port, pin), IO_GROUPS); + shift = GPIO_REG_SHIFT(RPI_PIN_NUM(port, pin), IO_GROUPS); + + /* Clear all detection first */ + + regval = sys_read32(GPREN(data->base, group)); + regval &= ~BIT(shift); + sys_write32(regval, GPREN(data->base, group)); + + regval = sys_read32(GPFEN(data->base, group)); + regval &= ~BIT(shift); + sys_write32(regval, GPFEN(data->base, group)); + + regval = sys_read32(GPHEN(data->base, group)); + regval &= ~BIT(shift); + sys_write32(regval, GPHEN(data->base, group)); + + regval = sys_read32(GPLEN(data->base, group)); + regval &= ~BIT(shift); + sys_write32(regval, GPLEN(data->base, group)); + + if (mode == GPIO_INT_MODE_LEVEL) { + if (trig & GPIO_INT_LOW_0) { + regval = sys_read32(GPLEN(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPLEN(data->base, group)); + } + if (trig & GPIO_INT_HIGH_1) { + regval = sys_read32(GPHEN(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPHEN(data->base, group)); + } + } else if (mode == GPIO_INT_MODE_EDGE) { + if (trig & GPIO_INT_LOW_0) { + regval = sys_read32(GPFEN(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPFEN(data->base, group)); + } + if (trig & GPIO_INT_HIGH_1) { + regval = sys_read32(GPREN(data->base, group)); + regval |= BIT(shift); + sys_write32(regval, GPREN(data->base, group)); + } + } + + return 0; +} + +static int gpio_bcm2711_manage_callback(const struct device *port, struct gpio_callback *cb, + bool set) +{ + struct gpio_bcm2711_data *data = DEV_DATA(port); + + return gpio_manage_callback(&data->cb, cb, set); +} + +static void gpio_bcm2711_isr(const struct device *port) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + uint32_t regval0, regval1; + uint32_t status; + + regval0 = sys_read32(GPEDS(data->base, 0)); + regval1 = sys_read32(GPEDS(data->base, 1)); + + regval0 &= BIT_MASK(cfg->ngpios) << cfg->offset; + regval1 &= BIT_MASK(cfg->ngpios) >> (IO_GROUPS - cfg->offset); + + status = (regval0 >> cfg->offset) | (regval1 << (IO_GROUPS - cfg->offset)); + gpio_fire_callbacks(&data->cb, port, status); + + /* Write to clear */ + sys_write32(regval0, GPEDS(data->base, 0)); + sys_write32(regval1, GPEDS(data->base, 1)); +} + +int gpio_bcm2711_init(const struct device *port) +{ + const struct gpio_bcm2711_config *cfg = DEV_CFG(port); + struct gpio_bcm2711_data *data = DEV_DATA(port); + + DEVICE_MMIO_NAMED_MAP(port, reg_base, K_MEM_CACHE_NONE); + data->base = DEVICE_MMIO_NAMED_GET(port, reg_base); + + cfg->irq_config_func(); + + return 0; +} + +static const struct gpio_driver_api gpio_bcm2711_api = { + .pin_configure = gpio_bcm2711_pin_configure, + .port_get_raw = gpio_bcm2711_port_get_raw, + .port_set_masked_raw = gpio_bcm2711_port_set_masked_raw, + .port_set_bits_raw = gpio_bcm2711_port_set_bits_raw, + .port_clear_bits_raw = gpio_bcm2711_port_clear_bits_raw, + .port_toggle_bits = gpio_bcm2711_port_toggle_bits, + .pin_interrupt_configure = gpio_bcm2711_pin_interrupt_configure, + .manage_callback = gpio_bcm2711_manage_callback, +}; + +#define GPIO_BCM2711_INST(n) \ + static struct gpio_bcm2711_data gpio_bcm2711_data_##n; \ + \ + static void gpio_bcm2711_irq_config_func_##n(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), gpio_bcm2711_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQN(n)); \ + } \ + \ + static const struct gpio_bcm2711_config gpio_bcm2711_cfg_##n = { \ + .common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0)}, \ + DEVICE_MMIO_NAMED_ROM_INIT(reg_base, DT_INST_PARENT(n)), \ + .irq_config_func = gpio_bcm2711_irq_config_func_##n, \ + .offset = DT_INST_REG_ADDR(n), \ + .ngpios = DT_INST_PROP(n, ngpios), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, gpio_bcm2711_init, NULL, &gpio_bcm2711_data_##n, \ + &gpio_bcm2711_cfg_##n, PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, \ + &gpio_bcm2711_api); + +DT_INST_FOREACH_STATUS_OKAY(GPIO_BCM2711_INST) diff --git a/dts/arm64/broadcom/bcm2711.dtsi b/dts/arm64/broadcom/bcm2711.dtsi index dd577e289f0df83..038d8dc4d6c7884 100644 --- a/dts/arm64/broadcom/bcm2711.dtsi +++ b/dts/arm64/broadcom/bcm2711.dtsi @@ -52,6 +52,37 @@ status = "okay"; }; + gpio: gpio@fe200000 { + compatible = "simple-bus"; + reg = <0xfe200000 0xf4>; + #address-cells = <1>; + #size-cells = <0>; + + /* GPIO 0 ~ 27 */ + gpio0: gpio@0 { + compatible = "brcm,bcm2711-gpio"; + reg = <0>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + ngpios = <28>; + status = "disabled"; + }; + + /* GPIO 28 ~ 45 */ + gpio1: gpio@1c { + compatible = "brcm,bcm2711-gpio"; + reg = <28>; + interrupts = ; + gpio-controller; + #gpio-cells = <2>; + ngpios = <18>; + status = "disabled"; + }; + }; + uart1: uart@fe215040 { compatible = "brcm,bcm2711-aux-uart"; reg = <0xfe215040 0x40>; diff --git a/dts/bindings/gpio/brcm,bcm2711-gpio.yaml b/dts/bindings/gpio/brcm,bcm2711-gpio.yaml new file mode 100644 index 000000000000000..9237ee6dfc5cf8e --- /dev/null +++ b/dts/bindings/gpio/brcm,bcm2711-gpio.yaml @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Chen Xingyu +# SPDX-License-Identifier: Apache-2.0 + +description: BCM2711 GPIO + +compatible: "brcm,bcm2711-gpio" + +include: [gpio-controller.yaml, base.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + "#gpio-cells": + const: 2 + +gpio-cells: + - pin + - flags