Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

439 lines
15 KiB

/*
* Copyright (c) 2024 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT silabs_siwx91x_gpio
#include "sl_si91x_driver_gpio.h"
#include "sl_status.h"
#include <errno.h>
#include <zephyr/sys/util.h>
#include <zephyr/irq.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
/* Zephyr GPIO header must be included after driver, due to symbol conflicts
* for GPIO_INPUT and GPIO_OUTPUT between preprocessor macros in the Zephyr
* API and struct member register definitions for the SiWx91x device.
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#if CONFIG_GPIO_SILABS_SIWX91X_COMMON_INIT_PRIORITY >= CONFIG_GPIO_INIT_PRIORITY
#error CONFIG_GPIO_SILABS_SIWX91X_COMMON_INIT_PRIORITY must be less than \
CONFIG_GPIO_INIT_PRIORITY.
#endif
#define MAX_PORT_COUNT 4
#define MAX_PIN_COUNT 16
#define INVALID_PORT 0xFF
#define INTERRUPT_COUNT 8
/* Types */
struct gpio_siwx91x_common_config {
EGPIO_Type *reg;
};
struct gpio_siwx91x_port_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
const struct device *parent;
uint8_t pads[MAX_PIN_COUNT];
int port;
int hal_port;
bool ulp;
};
struct gpio_siwx91x_common_data {
/* a list of all ports */
const struct device *ports[MAX_PORT_COUNT];
sl_gpio_t interrupts[INTERRUPT_COUNT];
};
struct gpio_siwx91x_port_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
/* port ISR callback routine address */
sys_slist_t callbacks;
#if defined(CONFIG_PM)
/* stores the direction of each pin */
uint16_t pin_direction[MAX_PIN_COUNT];
#endif
};
/* Functions */
static int gpio_siwx91x_port_pm_action(const struct device *port, enum pm_device_action action)
{
__maybe_unused const struct gpio_siwx91x_port_config *config = port->config;
__maybe_unused struct gpio_siwx91x_port_data *data = port->data;
#if defined(CONFIG_PM)
switch (action) {
case PM_DEVICE_ACTION_RESUME:
for (int pin = 0; pin < MAX_PIN_COUNT; ++pin) {
if (config->common.port_pin_mask & BIT(pin)) {
sl_si91x_gpio_set_pin_direction(config->hal_port, pin,
data->pin_direction[pin]);
}
}
break;
case PM_DEVICE_ACTION_SUSPEND:
for (int pin = 0; pin < MAX_PIN_COUNT; ++pin) {
if (config->common.port_pin_mask & BIT(pin)) {
data->pin_direction[pin] =
sl_si91x_gpio_get_pin_direction(config->hal_port, pin);
}
}
break;
default:
return -ENOTSUP;
}
#endif
return 0;
}
static int gpio_siwx91x_pin_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_siwx91x_port_config *cfg = dev->config;
const struct device *parent = cfg->parent;
const struct gpio_siwx91x_common_config *pcfg = parent->config;
sl_status_t status;
sl_si91x_gpio_driver_disable_state_t disable_state = GPIO_HZ;
if (flags & GPIO_SINGLE_ENDED) {
return -ENOTSUP;
}
uint8_t pad = cfg->pads[pin];
if (pad == 0) {
/* Enable MCU pad */
status = sl_si91x_gpio_driver_enable_host_pad_selection((cfg->hal_port << 4) | pin);
if (status != SL_STATUS_OK) {
return -ENODEV;
}
} else if (pad != 0xFF && pad != 9) {
/* Assign pad to MCU subsystem */
status = sl_si91x_gpio_driver_enable_pad_selection(pad);
if (status != SL_STATUS_OK) {
return -ENODEV;
}
}
if (flags & GPIO_PULL_UP) {
disable_state = GPIO_PULLUP;
} else if (flags & GPIO_PULL_DOWN) {
disable_state = GPIO_PULLDOWN;
}
if (cfg->ulp) {
sl_si91x_gpio_select_ulp_pad_driver_disable_state(pin, disable_state);
} else {
sl_si91x_gpio_select_pad_driver_disable_state((cfg->port << 4) | pin,
disable_state);
}
if (flags & GPIO_INPUT) {
if (cfg->ulp) {
sl_si91x_gpio_driver_enable_ulp_pad_receiver(pin);
} else {
sl_si91x_gpio_driver_enable_pad_receiver((cfg->port << 4) | pin);
}
} else {
if (cfg->ulp) {
sl_si91x_gpio_driver_disable_ulp_pad_receiver(pin);
} else {
sl_si91x_gpio_driver_disable_pad_receiver((cfg->port << 4) | pin);
}
}
pcfg->reg->PIN_CONFIG[(cfg->port << 4) + pin].GPIO_CONFIG_REG_b.MODE = 0;
if (flags & GPIO_OUTPUT_INIT_HIGH) {
sl_gpio_set_pin_output(cfg->hal_port, pin);
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
sl_gpio_clear_pin_output(cfg->hal_port, pin);
}
sl_si91x_gpio_set_pin_direction(cfg->hal_port, pin, (flags & GPIO_OUTPUT) ? 0 : 1);
return 0;
}
static int gpio_siwx91x_port_get(const struct device *port, gpio_port_value_t *value)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
*value = sl_gpio_get_port_input(cfg->hal_port);
return 0;
}
static int gpio_siwx91x_port_set_masked(const struct device *port, gpio_port_pins_t mask,
gpio_port_value_t value)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
const struct device *parent = cfg->parent;
const struct gpio_siwx91x_common_config *pcfg = parent->config;
/* Cannot use HAL function sl_gpio_set_port_output_value(), as it doesn't clear bits. */
pcfg->reg->PORT_CONFIG[cfg->port].PORT_LOAD_REG =
(pcfg->reg->PORT_CONFIG[cfg->port].PORT_LOAD_REG & ~mask) | (value & mask);
return 0;
}
static int gpio_siwx91x_port_set_bits(const struct device *port, gpio_port_pins_t pins)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
sl_gpio_set_port_output(cfg->hal_port, pins);
return 0;
}
static int gpio_siwx91x_port_clear_bits(const struct device *port, gpio_port_pins_t pins)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
sl_gpio_clear_port_output(cfg->hal_port, pins);
return 0;
}
static int gpio_siwx91x_port_toggle_bits(const struct device *port, gpio_port_pins_t pins)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
sl_gpio_toggle_port_output(cfg->hal_port, pins);
return 0;
}
static bool receiver_enabled(bool ulp, sl_gpio_port_t port, int pin)
{
if (ulp) {
return ULP_PAD_CONFIG_REG & BIT(pin);
} else {
return PAD_REG(((port << 4) | pin))->GPIO_PAD_CONFIG_REG_b.PADCONFIG_REN;
}
}
int gpio_siwx91x_port_get_direction(const struct device *port, gpio_port_pins_t map,
gpio_port_pins_t *inputs, gpio_port_pins_t *outputs)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
if (inputs != NULL) {
*inputs = 0;
}
if (outputs != NULL) {
*outputs = 0;
}
for (int i = 0; i < MAX_PIN_COUNT; i++) {
if ((map & BIT(i))) {
if (sl_si91x_gpio_get_pin_direction(cfg->hal_port, i) == 0) {
if (outputs != NULL) {
*outputs |= BIT(i);
}
}
if (receiver_enabled(cfg->ulp, cfg->port, i)) {
if (inputs != NULL) {
*inputs |= BIT(i);
}
}
}
}
return 0;
}
static int gpio_siwx91x_manage_callback(const struct device *port, struct gpio_callback *callback,
bool set)
{
struct gpio_siwx91x_port_data *data = port->data;
return gpio_manage_callback(&data->callbacks, callback, set);
}
static int gpio_siwx91x_interrupt_configure(const struct device *port, gpio_pin_t pin,
enum gpio_int_mode mode, enum gpio_int_trig trig)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
const struct device *parent = cfg->parent;
const struct gpio_siwx91x_common_config *pcfg = parent->config;
struct gpio_siwx91x_common_data *data = parent->data;
sl_si91x_gpio_interrupt_config_flag_t flags = 0;
if (mode & GPIO_INT_DISABLE) {
ARRAY_FOR_EACH(data->interrupts, i) {
if (data->interrupts[i].port == cfg->port &&
data->interrupts[i].pin == pin) {
data->interrupts[i].port = INVALID_PORT;
if (cfg->ulp) {
sl_si91x_gpio_configure_ulp_pin_interrupt(i, flags, pin);
} else {
sl_gpio_configure_interrupt(cfg->port, pin, i, flags);
}
/* Configure function doesn't mask interrupts when disabling */
pcfg->reg->INTR[i].GPIO_INTR_CTRL_b.MASK = 1;
if (cfg->ulp) {
sl_si91x_gpio_clear_ulp_interrupt(i);
} else {
sl_gpio_clear_interrupts(i);
}
return 0;
}
}
/* No interrupt to disable, but this is not an error */
return 0;
}
if (trig == GPIO_INT_TRIG_LOW) {
flags = (mode == GPIO_INT_MODE_EDGE) ? SL_GPIO_INTERRUPT_FALL_EDGE
: SL_GPIO_INTERRUPT_LEVEL_LOW;
} else if (trig == GPIO_INT_TRIG_HIGH) {
flags = (mode == GPIO_INT_MODE_EDGE) ? SL_GPIO_INTERRUPT_RISE_EDGE
: SL_GPIO_INTERRUPT_LEVEL_HIGH;
} else if (trig == GPIO_INT_TRIG_BOTH) {
/* SL_GPIO_INTERRUPT_RISE_FALL_EDGE would make more sense, but HAL
* implementation is buggy.
*/
flags = SL_GPIO_INTERRUPT_RISE_EDGE | SL_GPIO_INTERRUPT_FALL_EDGE;
}
ARRAY_FOR_EACH(data->interrupts, i) {
if (data->interrupts[i].port == INVALID_PORT ||
(data->interrupts[i].port == cfg->port && data->interrupts[i].pin == pin)) {
data->interrupts[i].port = cfg->port;
data->interrupts[i].pin = pin;
if (cfg->ulp) {
sl_si91x_gpio_configure_ulp_pin_interrupt(i, flags, pin);
} else {
sl_gpio_configure_interrupt(cfg->port, pin, i, flags);
}
return 0;
}
}
/* No more available interrupts */
return -EBUSY;
}
static inline int gpio_siwx91x_init_port(const struct device *port)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
const struct device *parent = cfg->parent;
struct gpio_siwx91x_common_data *data = parent->data;
/* Register port as active */
__ASSERT(cfg->port < MAX_PORT_COUNT, "Too many ports");
data->ports[cfg->port] = port;
return pm_device_driver_init(port, gpio_siwx91x_port_pm_action);
}
static void gpio_siwx91x_isr(const struct device *parent)
{
const struct gpio_siwx91x_common_config *pcfg = parent->config;
struct gpio_siwx91x_common_data *common = parent->data;
const struct device *port;
struct gpio_siwx91x_port_data *data;
ARRAY_FOR_EACH(common->interrupts, i) {
sl_gpio_port_t port_no = common->interrupts[i].port;
uint32_t pending = pcfg->reg->INTR[i].GPIO_INTR_STATUS_b.INTERRUPT_STATUS;
if (pending && port_no != INVALID_PORT) {
/* Clear interrupt */
pcfg->reg->INTR[i].GPIO_INTR_STATUS_b.INTERRUPT_STATUS = 1;
port = common->ports[port_no];
data = port->data;
gpio_fire_callbacks(&data->callbacks, port, BIT(common->interrupts[i].pin));
}
}
}
static uint32_t gpio_siwx91x_get_pending_int(const struct device *port)
{
const struct gpio_siwx91x_port_config *cfg = port->config;
const struct device *parent = cfg->parent;
const struct gpio_siwx91x_common_config *pcfg = parent->config;
uint32_t status = 0;
ARRAY_FOR_EACH(pcfg->reg->INTR, i) {
if (pcfg->reg->INTR[i].GPIO_INTR_STATUS_b.INTERRUPT_STATUS) {
status |= BIT(i);
}
}
return status;
}
static DEVICE_API(gpio, gpio_siwx91x_api) = {
.pin_configure = gpio_siwx91x_pin_configure,
.port_get_raw = gpio_siwx91x_port_get,
.port_set_masked_raw = gpio_siwx91x_port_set_masked,
.port_set_bits_raw = gpio_siwx91x_port_set_bits,
.port_clear_bits_raw = gpio_siwx91x_port_clear_bits,
.port_toggle_bits = gpio_siwx91x_port_toggle_bits,
.pin_interrupt_configure = gpio_siwx91x_interrupt_configure,
.manage_callback = gpio_siwx91x_manage_callback,
.get_pending_int = gpio_siwx91x_get_pending_int,
#ifdef CONFIG_GPIO_GET_DIRECTION
.port_get_direction = gpio_siwx91x_port_get_direction,
#endif
};
#define GPIO_PORT_INIT(n) \
static const struct gpio_siwx91x_port_config gpio_siwx91x_port_config##n = { \
.common.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_NODE(n), \
.parent = DEVICE_DT_GET(DT_PARENT(n)), \
.pads = DT_PROP(n, silabs_pads), \
.port = DT_REG_ADDR(n), \
.hal_port = (DT_PROP(DT_PARENT(n), silabs_ulp) ? SL_GPIO_ULP_PORT : 0) + \
DT_REG_ADDR(n), \
.ulp = DT_PROP(DT_PARENT(n), silabs_ulp), \
}; \
static struct gpio_siwx91x_port_data gpio_siwx91x_port_data##n; \
\
PM_DEVICE_DT_INST_DEFINE(n, gpio_siwx91x_port_pm_action); \
DEVICE_DT_DEFINE(n, gpio_siwx91x_init_port, PM_DEVICE_DT_INST_GET(n), \
&gpio_siwx91x_port_data##n, &gpio_siwx91x_port_config##n, PRE_KERNEL_1, \
CONFIG_GPIO_INIT_PRIORITY, &gpio_siwx91x_api);
#define CONFIGURE_SHARED_INTERRUPT(node_id, prop, idx) \
IRQ_CONNECT(DT_IRQ_BY_IDX(node_id, idx, irq), DT_IRQ_BY_IDX(node_id, idx, priority), \
gpio_siwx91x_isr, DEVICE_DT_GET(node_id), 0); \
irq_enable(DT_IRQ_BY_IDX(node_id, idx, irq));
static DEVICE_API(gpio, gpio_siwx91x_common_api) = { };
#define GPIO_CONTROLLER_INIT(idx) \
static const struct gpio_siwx91x_common_config gpio_siwx91x_config##idx = { \
.reg = (EGPIO_Type *)DT_INST_REG_ADDR(idx), \
}; \
static struct gpio_siwx91x_common_data gpio_siwx91x_data##idx; \
\
static int gpio_siwx91x_init_controller_##idx(const struct device *dev) \
{ \
struct gpio_siwx91x_common_data *data = dev->data; \
int status; \
\
status = sl_si91x_gpio_driver_enable_clock( \
COND_CODE_1(DT_INST_PROP(idx, silabs_ulp), (ULPCLK_GPIO), (M4CLK_GPIO))); \
if (status) { \
return -ENODEV; \
} \
ARRAY_FOR_EACH(data->interrupts, i) { \
data->interrupts[i].port = INVALID_PORT; \
} \
DT_INST_FOREACH_PROP_ELEM(idx, interrupt_names, CONFIGURE_SHARED_INTERRUPT); \
return 0; \
} \
DEVICE_DT_INST_DEFINE(idx, gpio_siwx91x_init_controller_##idx, NULL, \
&gpio_siwx91x_data##idx, &gpio_siwx91x_config##idx, \
PRE_KERNEL_1, CONFIG_GPIO_SILABS_SIWX91X_COMMON_INIT_PRIORITY, \
&gpio_siwx91x_common_api); \
DT_INST_FOREACH_CHILD_STATUS_OKAY(idx, GPIO_PORT_INIT);
DT_INST_FOREACH_STATUS_OKAY(GPIO_CONTROLLER_INIT)