Skip to content

Commit

Permalink
sim3u: add gpio driver
Browse files Browse the repository at this point in the history
This is just the driver for banks 0,1,2,3. Bank 4 will come via a separate
commit since it needs a different driver.

Signed-off-by: Michael Zimmermann <[email protected]>
  • Loading branch information
M1cha committed Nov 10, 2023
1 parent d39e6b1 commit fa53491
Show file tree
Hide file tree
Showing 6 changed files with 306 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 @@ -87,6 +87,7 @@ 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)
zephyr_library_sources_ifdef(CONFIG_GPIO_RA gpio_ra.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_SI32 gpio_si32.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 @@ -214,4 +214,6 @@ source "drivers/gpio/Kconfig.bcm2711"

source "drivers/gpio/Kconfig.ra"

source "drivers/gpio/Kconfig.si32"

endif # GPIO
10 changes: 10 additions & 0 deletions drivers/gpio/Kconfig.si32
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) 2023 GARDENA GmbH
#
# SPDX-License-Identifier: Apache-2.0

config GPIO_SI32
bool "GPIO Driver for SI32 family of MCUs"
default y
depends on DT_HAS_SILABS_SI32_GPIO_ENABLED
help
Enable GPIO driver for SI32 line of MCUs
242 changes: 242 additions & 0 deletions drivers/gpio/gpio_si32.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* Copyright (c) 2023 GARDENA GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT silabs_si32_gpio

#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/kernel.h>

#include <SI32_PBCFG_A_Type.h>
#include <SI32_PBSTD_A_Type.h>
#include <si32_device.h>

struct gpio_si32_config {
struct gpio_driver_config common;
SI32_PBSTD_A_Type *base;
bool disable_pullups;
};

struct gpio_si32_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
sys_slist_t cb;
gpio_port_pins_t trig_low;
gpio_port_pins_t trig_high;
uint32_t pin_values;
};

static int gpio_si32_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_si32_config *config = dev->config;
uint32_t key = irq_lock();

if (flags & GPIO_OUTPUT) {
if (flags & GPIO_OUTPUT_INIT_HIGH) {
SI32_PBSTD_A_write_pins_high(config->base, BIT(pin));
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
SI32_PBSTD_A_write_pins_low(config->base, BIT(pin));
}

SI32_PBSTD_A_set_pins_push_pull_output(config->base, BIT(pin));
} else if (flags & GPIO_INPUT) {
SI32_PBSTD_A_set_pins_digital_input(config->base, BIT(pin));
} else {
SI32_PBSTD_A_set_pins_analog(config->base, BIT(pin));
}

/* Initially, configure interrupt to trgger on the active value.
* Otherwise we'd get an interrupt immediately after enabling them.
*/
if (flags & GPIO_ACTIVE_HIGH) {
config->base->PM_SET = BIT(pin);
} else {
config->base->PM_CLR = BIT(pin);
}

irq_unlock(key);
return 0;
}

static int gpio_si32_port_get_raw(const struct device *dev, uint32_t *value)
{
const struct gpio_si32_config *config = dev->config;
*value = SI32_PBSTD_A_read_pins(config->base);
return 0;
}

static int gpio_si32_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask,
gpio_port_value_t value)
{
const struct gpio_si32_config *config = dev->config;
SI32_PBSTD_A_write_pins_masked(config->base, value, mask);
return 0;
}

static int gpio_si32_port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins)
{
const struct gpio_si32_config *config = dev->config;
SI32_PBSTD_A_write_pins_high(config->base, pins);
return 0;
}

static int gpio_si32_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins)
{
const struct gpio_si32_config *config = dev->config;
SI32_PBSTD_A_write_pins_low(config->base, pins);
return 0;
}

static int gpio_si32_port_toggle_bits(const struct device *dev, gpio_port_pins_t pins)
{
const struct gpio_si32_config *config = dev->config;
SI32_PBSTD_A_toggle_pins(config->base, pins);
return 0;
}

static int gpio_si32_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,
enum gpio_int_mode mode, enum gpio_int_trig trig)
{
const struct gpio_si32_config *config = dev->config;
struct gpio_si32_data *data = dev->data;
int ret;

uint32_t key = irq_lock();

if (mode == GPIO_INT_MODE_DISABLED) {
config->base->PMEN_CLR = BIT(pin);
WRITE_BIT(data->trig_low, pin, 0);
WRITE_BIT(data->trig_high, pin, 0);

ret = 0;
goto unlock;
} else if (mode == GPIO_INT_MODE_EDGE) {
} else {
/* Not yet implemented */
ret = -ENOTSUP;
goto unlock;
}

WRITE_BIT(data->trig_low, pin, trig & GPIO_INT_TRIG_LOW);
WRITE_BIT(data->trig_high, pin, trig & GPIO_INT_TRIG_HIGH);

config->base->PMEN_SET = BIT(pin);

ret = 0;

unlock:
irq_unlock(key);
return ret;
}

static int gpio_si32_manage_callback(const struct device *dev, struct gpio_callback *callback,
bool set)
{
struct gpio_si32_data *data = dev->data;

return gpio_manage_callback(&data->cb, callback, set);
}

static const struct gpio_driver_api gpio_si32_driver = {
.pin_configure = gpio_si32_configure,
.port_get_raw = gpio_si32_port_get_raw,
.port_set_masked_raw = gpio_si32_port_set_masked_raw,
.port_set_bits_raw = gpio_si32_port_set_bits_raw,
.port_clear_bits_raw = gpio_si32_port_clear_bits_raw,
.port_toggle_bits = gpio_si32_port_toggle_bits,
.pin_interrupt_configure = gpio_si32_pin_interrupt_configure,
.manage_callback = gpio_si32_manage_callback,
};

static int gpio_si32_init(const struct device *dev)
{
const struct gpio_si32_config *config = dev->config;

if (config->disable_pullups) {
SI32_PBSTD_A_disable_pullup_resistors(config->base);
}

return 0;
}

#define GPIO_DEVICE_INIT(inst) \
static const struct gpio_si32_config gpio_si32_cfg_##inst = { \
.common = \
{ \
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_NGPIOS(16U), \
}, \
.base = (SI32_PBSTD_A_Type *)DT_INST_REG_ADDR(inst), \
.disable_pullups = DT_INST_PROP(inst, disable_pullups), \
}; \
static struct gpio_si32_data gpio_si32_data_##inst; \
DEVICE_DT_INST_DEFINE(inst, gpio_si32_init, NULL, &gpio_si32_data_##inst, \
&gpio_si32_cfg_##inst, PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, \
&gpio_si32_driver);

DT_INST_FOREACH_STATUS_OKAY(GPIO_DEVICE_INIT)

#define GPIO_DEVICE_LIST_ENTRY(inst) DEVICE_DT_GET(DT_DRV_INST(inst)),
static const struct device *gpio_devices[] = {DT_INST_FOREACH_STATUS_OKAY(GPIO_DEVICE_LIST_ENTRY)};

/* The hardware only supports level interrupts, so we have to emulate edge
* interrupts in this handler.
*/
static void gpio_si32_irq_handler(const struct device *arg)
{
ARG_UNUSED(arg);

irq_disable(PMATCH_IRQn);
NVIC_ClearPendingIRQ(PMATCH_IRQn);

for (size_t i = 0; i < ARRAY_SIZE(gpio_devices); i++) {
const struct device *dev = gpio_devices[i];
const struct gpio_si32_config *config = dev->config;
struct gpio_si32_data *data = dev->data;

uint32_t pmen = SI32_PBSTD_A_read_pmen(config->base);
uint32_t pm = SI32_PBSTD_A_read_pm(config->base);
uint32_t values = SI32_PBSTD_A_read_pins(config->base);

/* Invert triggers for all pins which are at their trigger
* values. This disables interrupts until they change again
* since the hardware only supports level interrupts.
*/
uint32_t pins_not_at_trigger_value = (pm ^ values) & pmen;
uint32_t pins_at_trigger_value = (~pins_not_at_trigger_value) & pmen;
SI32_PBSTD_A_write_pm(config->base, pm ^ pins_at_trigger_value);

/* To check which pins actually changed we have to store and
* compare the previous value.
*/
uint32_t changed_pins = (values ^ data->pin_values) & pmen;
data->pin_values = values;

if (changed_pins) {
/* The user might be interested in both levels or just one,
* so filter those events here.
*/
uint32_t changed_pins_high = (values & changed_pins) & data->trig_high;
uint32_t changed_pins_low = ((~values) & changed_pins) & data->trig_low;

gpio_fire_callbacks(&data->cb, dev, changed_pins_high | changed_pins_low);
}
}

irq_enable(PMATCH_IRQn);
}

static int gpio_si32_common_init(void)
{
/* This is the only mode we support right now */
SI32_PBCFG_A_select_port_match_mode_pin_match(SI32_PBCFG_0);

IRQ_CONNECT(PMATCH_IRQn, 0, gpio_si32_irq_handler, NULL, 0);
irq_enable(PMATCH_IRQn);

return 0;
}
SYS_INIT(gpio_si32_common_init, PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY);
28 changes: 28 additions & 0 deletions dts/arm/silabs/sim3u.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,34 @@
write-block-size = <2>;
};
};

gpio0: gpio@4002a0a0 {
compatible = "silabs,si32-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x4002a0a0 0xa0>;
};

gpio1: gpio@4002a140 {
compatible = "silabs,si32-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x4002a140 0xa0>;
};

gpio2: gpio@4002a1e0 {
compatible = "silabs,si32-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x4002a1e0 0x140>;
};

gpio3: gpio@4002a320 {
compatible = "silabs,si32-gpio";
gpio-controller;
#gpio-cells = <2>;
reg = <0x4002a320 0xa0>;
};
};
};

Expand Down
23 changes: 23 additions & 0 deletions dts/bindings/gpio/silabs,si32-gpio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0

description: SI32 GPIO node

compatible: "silabs,si32-gpio"

include: [gpio-controller.yaml, base.yaml]

properties:
reg:
required: true

"#gpio-cells":
const: 2

disable-pullups:
required: false
type: boolean

gpio-cells:
- pin
- flags

0 comments on commit fa53491

Please sign in to comment.