-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
Showing
6 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.