blob: a3b1f2fd7c636ca18cb8140bcdab3860051c6d54 [file] [log] [blame]
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT silabs_gpio
#include <errno.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_silabs.h>
#include <zephyr/irq.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <sl_hal_gpio.h>
LOG_MODULE_REGISTER(gpio_silabs, CONFIG_GPIO_LOG_LEVEL);
#define SILABS_GPIO_PORT_ADDR_SPACE_SIZE sizeof(GPIO_PORT_TypeDef)
#define GET_SILABS_GPIO_INDEX(node_id) \
(DT_REG_ADDR(node_id) - DT_REG_ADDR(DT_NODELABEL(gpioa))) / SILABS_GPIO_PORT_ADDR_SPACE_SIZE
#define NUMBER_OF_PORTS (SIZEOF_FIELD(GPIO_TypeDef, P) / SIZEOF_FIELD(GPIO_TypeDef, P[0]))
#define NUM_IRQ_LINES 16
#define MAX_EM4_IRQ_PER_PORT 3
#define EM4WU_TO_INT(wu) ((wu) + NUM_IRQ_LINES)
#define INT_TO_EM4WU(int_no) ((int_no) - NUM_IRQ_LINES)
struct gpio_silabs_common_config {
/* IRQ configuration function */
void (*irq_connect)(const struct device *dev);
/* Clock device */
const struct device *clock;
/* Clock control subsystem */
const struct silabs_clock_control_cmu_config clock_cfg;
};
struct gpio_silabs_common_data {
/* a list of all registered GPIO port devices */
const struct device *ports[NUMBER_OF_PORTS];
};
struct gpio_silabs_em4wu_mapping {
uint8_t wu_no;
uint8_t pin;
};
struct gpio_silabs_port_config {
/* gpio_driver_config must be first */
struct gpio_driver_config common;
/* index of the GPIO port */
sl_gpio_port_t gpio_index;
/* pointer to common device */
const struct device *common_dev;
/* Number of valid EM4 wakeup interrupt mappings */
int em4wu_pin_count;
/* EM4 wakeup interrupt mapping for GPIO pins */
struct gpio_silabs_em4wu_mapping em4wu_pins[MAX_EM4_IRQ_PER_PORT];
};
struct gpio_silabs_port_data {
/* gpio_driver_data must be first */
struct gpio_driver_data common;
/* port ISR callback routine list */
sys_slist_t callbacks;
};
static int gpio_silabs_pin_configure(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_gpio_t gpio = {
.port = config->gpio_index,
.pin = pin
};
sl_gpio_mode_t mode;
bool out = false;
if (flags & GPIO_OUTPUT) {
if (flags & GPIO_SINGLE_ENDED) {
if (flags & GPIO_LINE_OPEN_DRAIN) {
mode = SL_GPIO_MODE_WIRED_AND;
} else {
mode = SL_GPIO_MODE_WIRED_OR;
}
} else {
mode = SL_GPIO_MODE_PUSH_PULL;
}
if (flags & GPIO_OUTPUT_INIT_HIGH) {
out = true;
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
out = false;
} else {
out = sl_hal_gpio_get_pin_output(&gpio);
}
} else if (flags & GPIO_INPUT) {
if (flags & GPIO_PULL_UP) {
mode = SL_GPIO_MODE_INPUT_PULL;
out = true; /* Pull-up */
} else if (flags & GPIO_PULL_DOWN) {
mode = SL_GPIO_MODE_INPUT_PULL;
out = false; /* Pull-down */
} else {
mode = SL_GPIO_MODE_INPUT;
}
} else {
mode = SL_GPIO_MODE_DISABLED;
}
sl_hal_gpio_set_pin_mode(&gpio, mode, out);
return 0;
}
#ifdef CONFIG_GPIO_GET_CONFIG
static int gpio_silabs_pin_get_config(const struct device *dev, gpio_pin_t pin,
gpio_flags_t *out_flags)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_gpio_t gpio = {
.port = config->gpio_index,
.pin = pin
};
sl_gpio_mode_t mode;
bool out;
gpio_flags_t flags = 0;
mode = sl_hal_gpio_get_pin_mode(&gpio);
out = sl_hal_gpio_get_pin_output(&gpio);
switch (mode) {
case SL_GPIO_MODE_WIRED_AND:
flags = GPIO_OUTPUT | GPIO_OPEN_DRAIN;
if (out) {
flags |= GPIO_OUTPUT_HIGH;
} else {
flags |= GPIO_OUTPUT_LOW;
}
break;
case SL_GPIO_MODE_WIRED_OR:
flags = GPIO_OUTPUT | GPIO_OPEN_SOURCE;
if (out) {
flags |= GPIO_OUTPUT_HIGH;
} else {
flags |= GPIO_OUTPUT_LOW;
}
break;
case SL_GPIO_MODE_PUSH_PULL:
flags = GPIO_OUTPUT | GPIO_PUSH_PULL;
if (out) {
flags |= GPIO_OUTPUT_HIGH;
} else {
flags |= GPIO_OUTPUT_LOW;
}
break;
case SL_GPIO_MODE_INPUT_PULL:
flags = GPIO_INPUT;
if (out) {
flags |= GPIO_PULL_UP;
} else {
flags |= GPIO_PULL_DOWN;
}
break;
case SL_GPIO_MODE_INPUT:
flags = GPIO_INPUT;
break;
case SL_GPIO_MODE_DISABLED:
flags = GPIO_DISCONNECTED;
break;
default:
break;
}
*out_flags = flags;
return 0;
}
#endif
static int gpio_silabs_port_get_raw(const struct device *dev, gpio_port_value_t *value)
{
const struct gpio_silabs_port_config *config = dev->config;
*value = sl_hal_gpio_get_port_input(config->gpio_index);
return 0;
}
static int gpio_silabs_port_set_masked_raw(const struct device *dev, gpio_port_pins_t mask,
gpio_port_value_t value)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_hal_gpio_set_port_value(config->gpio_index, value, mask);
return 0;
}
static int gpio_silabs_port_set_bits_raw(const struct device *dev, gpio_port_pins_t mask)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_hal_gpio_set_port(config->gpio_index, mask);
return 0;
}
static int gpio_silabs_port_clear_bits_raw(const struct device *dev, gpio_port_pins_t mask)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_hal_gpio_clear_port(config->gpio_index, mask);
return 0;
}
static int gpio_silabs_port_toggle_bits(const struct device *dev, gpio_port_pins_t mask)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_hal_gpio_toggle_port(config->gpio_index, mask);
return 0;
}
static int interrupt_to_port(int int_no)
{
uint32_t reg = 0;
if (int_no < 8) {
reg = GPIO->EXTIPSELL;
#if defined(_GPIO_EXTIPSELH_MASK)
} else {
int_no -= 8;
reg = GPIO->EXTIPSELH;
#endif
}
return FIELD_GET(0xF << (int_no * 4), reg);
}
static int interrupt_to_pin(int int_no)
{
int offset = int_no;
uint32_t reg = 0;
if (int_no < 8) {
reg = GPIO->EXTIPINSELL;
#if defined(_GPIO_EXTIPINSELH_MASK)
} else {
offset -= 8;
reg = GPIO->EXTIPINSELH;
#endif
}
return ROUND_DOWN(int_no, 4) + FIELD_GET(0xF << (offset * 4), reg);
}
static int gpio_silabs_pin_interrupt_configure_em4wu(sl_gpio_t *gpio, enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
int32_t em4wu_no = sl_hal_gpio_get_em4_interrupt_number(gpio);
int32_t int_no = EM4WU_TO_INT(em4wu_no);
if (em4wu_no == SL_GPIO_INTERRUPT_UNAVAILABLE) {
LOG_ERR("Pin %u is not EM4 wakeup capable", gpio->pin);
return -EINVAL;
}
if (mode != GPIO_INT_MODE_DISABLED) {
if (trig == GPIO_INT_TRIG_BOTH) {
LOG_ERR("EM4 wakeup interrupt on pin %u can only trigger on one edge",
gpio->pin);
return -ENOTSUP;
}
sl_hal_gpio_configure_wakeup_em4_external_interrupt(gpio, em4wu_no,
trig == GPIO_INT_TRIG_HIGH);
}
if (mode == GPIO_INT_MODE_DISABLED) {
sl_hal_gpio_disable_interrupts(BIT(int_no));
sl_hal_gpio_disable_pin_em4_wakeup(BIT(int_no));
} else {
sl_hal_gpio_enable_interrupts(BIT(int_no));
}
return 0;
}
static int gpio_silabs_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,
enum gpio_int_mode mode, enum gpio_int_trig trig)
{
const struct gpio_silabs_port_config *config = dev->config;
sl_gpio_t gpio = {
.port = config->gpio_index,
.pin = pin
};
sl_gpio_interrupt_flag_t flag = SL_GPIO_INTERRUPT_RISING_FALLING_EDGE;
uint32_t enabled_interrupts;
int32_t int_no = SL_GPIO_INTERRUPT_UNAVAILABLE;
bool em4_wakeup;
em4_wakeup = ((trig & GPIO_INT_WAKEUP) == GPIO_INT_WAKEUP);
trig &= ~GPIO_INT_WAKEUP;
if (mode == GPIO_INT_MODE_LEVEL) {
LOG_ERR("Level interrupt not supported on pin %u", pin);
return -ENOTSUP;
}
if (em4_wakeup) {
return gpio_silabs_pin_interrupt_configure_em4wu(&gpio, mode, trig);
}
enabled_interrupts = sl_hal_gpio_get_enabled_interrupts();
for (int i = 0; i < NUM_IRQ_LINES; ++i) {
if ((enabled_interrupts & BIT(i)) && interrupt_to_port(i) == config->gpio_index &&
interrupt_to_pin(i) == pin) {
if (mode == GPIO_INT_MODE_DISABLED) {
sl_hal_gpio_disable_interrupts(BIT(i));
} else if (int_no == SL_GPIO_INTERRUPT_UNAVAILABLE) {
int_no = i;
}
}
}
if (mode == GPIO_INT_MODE_DISABLED) {
return 0;
}
if (trig == GPIO_INT_TRIG_LOW) {
flag = SL_GPIO_INTERRUPT_FALLING_EDGE;
} else if (trig == GPIO_INT_TRIG_HIGH) {
flag = SL_GPIO_INTERRUPT_RISING_EDGE;
} else {
flag = SL_GPIO_INTERRUPT_RISING_FALLING_EDGE;
}
int_no = sl_hal_gpio_configure_external_interrupt(&gpio, int_no, flag);
if (int_no == SL_GPIO_INTERRUPT_UNAVAILABLE) {
LOG_ERR("No available interrupt for pin %u", pin);
return -EINVAL;
}
sl_hal_gpio_enable_interrupts(BIT(int_no));
return 0;
}
static int gpio_silabs_port_manage_callback(const struct device *dev, struct gpio_callback *cb,
bool set)
{
struct gpio_silabs_port_data *data = dev->data;
return gpio_manage_callback(&data->callbacks, cb, set);
}
static void gpio_silabs_em4wu_interrupt_to_port_pin(struct gpio_silabs_common_data *data,
int int_no, int *port, int *pin)
{
ARRAY_FOR_EACH(data->ports, p) {
const struct device *dev = data->ports[p];
const struct gpio_silabs_port_config *config = dev->config;
for (int i = 0; i < config->em4wu_pin_count; i++) {
const struct gpio_silabs_em4wu_mapping *em4wu = &config->em4wu_pins[i];
if (em4wu->wu_no == INT_TO_EM4WU(int_no)) {
*port = config->gpio_index;
*pin = em4wu->pin;
return;
}
}
}
}
static void gpio_silabs_common_isr(const struct device *dev)
{
struct gpio_silabs_common_data *data = dev->data;
uint32_t pending = sl_hal_gpio_get_enabled_pending_interrupts();
uint32_t port_pin_masks[NUMBER_OF_PORTS] = { };
while (pending) {
int int_no = find_lsb_set(pending) - 1;
int port = -1;
int pin = -1;
if (int_no >= NUM_IRQ_LINES) {
gpio_silabs_em4wu_interrupt_to_port_pin(data, int_no, &port, &pin);
} else {
port = interrupt_to_port(int_no);
pin = interrupt_to_pin(int_no);
}
port_pin_masks[port] |= BIT(pin);
sl_hal_gpio_clear_interrupts(BIT(int_no));
pending &= ~BIT(int_no);
}
ARRAY_FOR_EACH(data->ports, port) {
if (data->ports[port] && port_pin_masks[port]) {
const struct device *port_dev = data->ports[port];
struct gpio_silabs_port_data *port_data = port_dev->data;
gpio_fire_callbacks(&port_data->callbacks, port_dev, port_pin_masks[port]);
}
}
}
static int gpio_silabs_port_init(const struct device *dev)
{
const struct gpio_silabs_port_config *config = dev->config;
struct gpio_silabs_common_data *common_data = config->common_dev->data;
common_data->ports[config->gpio_index] = dev;
LOG_DBG("Added GPIO port %s", dev->name);
return 0;
}
static DEVICE_API(gpio, gpio_driver_api) = {
.pin_configure = gpio_silabs_pin_configure,
#ifdef CONFIG_GPIO_GET_CONFIG
.pin_get_config = gpio_silabs_pin_get_config,
#endif
.port_get_raw = gpio_silabs_port_get_raw,
.port_set_masked_raw = gpio_silabs_port_set_masked_raw,
.port_set_bits_raw = gpio_silabs_port_set_bits_raw,
.port_clear_bits_raw = gpio_silabs_port_clear_bits_raw,
.port_toggle_bits = gpio_silabs_port_toggle_bits,
.pin_interrupt_configure = gpio_silabs_pin_interrupt_configure,
.manage_callback = gpio_silabs_port_manage_callback,
};
static int gpio_silabs_common_init(const struct device *dev)
{
const struct gpio_silabs_common_config *cfg = dev->config;
int ret;
/* Enable clock */
ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->clock_cfg);
if (ret < 0 && ret != -EALREADY) {
return ret;
}
if (cfg->irq_connect) {
cfg->irq_connect(dev);
}
LOG_DBG("Silabs GPIO common init complete");
return 0;
}
#define EM4_WAKEUP_PIN(node, prop, idx) \
{ \
.wu_no = DT_PROP_BY_IDX(node, prop, idx), \
.pin = DT_PROP_BY_IDX(node, silabs_wakeup_pins, idx), \
},
#define GPIO_PORT_INIT(n) \
static const struct gpio_silabs_port_config gpio_silabs_port_config_##n = { \
.common.port_pin_mask = (gpio_port_pins_t)(-1), \
.gpio_index = GET_SILABS_GPIO_INDEX(n), \
.common_dev = DEVICE_DT_GET(DT_PARENT(n)), \
.em4wu_pin_count = DT_PROP_LEN(n, silabs_wakeup_ints), \
.em4wu_pins = {DT_FOREACH_PROP_ELEM(n, silabs_wakeup_ints, EM4_WAKEUP_PIN)}, \
}; \
static struct gpio_silabs_port_data gpio_silabs_port_data_##n; \
DEVICE_DT_DEFINE(n, gpio_silabs_port_init, NULL, &gpio_silabs_port_data_##n, \
&gpio_silabs_port_config_##n, PRE_KERNEL_1, CONFIG_GPIO_INIT_PRIORITY, \
&gpio_driver_api);
#define GPIO_CONTROLLER_INIT(idx) \
static struct gpio_silabs_common_data gpio_silabs_common_data_##idx = {}; \
static void gpio_silabs_irq_connect_##idx(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(idx, gpio_even, irq), \
DT_INST_IRQ_BY_NAME(idx, gpio_even, priority), gpio_silabs_common_isr, \
DEVICE_DT_INST_GET(idx), 0); \
IRQ_CONNECT(DT_INST_IRQ_BY_NAME(idx, gpio_odd, irq), \
DT_INST_IRQ_BY_NAME(idx, gpio_odd, priority), gpio_silabs_common_isr, \
DEVICE_DT_INST_GET(idx), 0); \
irq_enable(DT_INST_IRQ_BY_NAME(idx, gpio_even, irq)); \
irq_enable(DT_INST_IRQ_BY_NAME(idx, gpio_odd, irq)); \
} \
static const struct gpio_silabs_common_config gpio_silabs_common_config_##idx = { \
.irq_connect = gpio_silabs_irq_connect_##idx, \
.clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \
.clock_cfg = SILABS_DT_INST_CLOCK_CFG(idx), \
}; \
DEVICE_DT_INST_DEFINE(idx, gpio_silabs_common_init, NULL, &gpio_silabs_common_data_##idx, \
&gpio_silabs_common_config_##idx, PRE_KERNEL_1, \
CONFIG_GPIO_SILABS_COMMON_INIT_PRIORITY, NULL); \
DT_INST_FOREACH_CHILD_STATUS_OKAY(idx, GPIO_PORT_INIT)
DT_INST_FOREACH_STATUS_OKAY(GPIO_CONTROLLER_INIT)