Skip to content

Commit

Permalink
drivers: gpio: Add GPIO driver for BCM2711
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
xingrz committed Oct 9, 2023
1 parent f528b40 commit 97826b7
Show file tree
Hide file tree
Showing 6 changed files with 411 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/gpio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpio/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,6 @@ source "drivers/gpio/Kconfig.sedi"

source "drivers/gpio/Kconfig.altera"

source "drivers/gpio/Kconfig.bcm2711"

endif # GPIO
9 changes: 9 additions & 0 deletions drivers/gpio/Kconfig.bcm2711
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023 Chen Xingyu <[email protected]>
# 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.
346 changes: 346 additions & 0 deletions drivers/gpio/gpio_bcm2711.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
/*
* Copyright (c) 2023 Chen Xingyu <[email protected]>
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT brcm_bcm2711_gpio

#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/irq.h>

#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)
Loading

0 comments on commit 97826b7

Please sign in to comment.