zephyr/drivers/gpio/gpio_nxp_s32.c
Manuel Argüelles a034cce23c gpio: nxp_s32: support passing external interrupts to WKPU
Extend the NXP S32 GPIO driver to be able to route external interrupts
to either SIUL2 EIRQ interrupt controller or, when available on the
SoC, WKPU interrupt controller.

Since WKPU can support up to 64 external interrupt sources and SIUL2
EIRQ up to 32, gpio_get_pending_int() is removed and the interrupt
controller specific API must be used instead.

Signed-off-by: Manuel Argüelles <manuel.arguelles@nxp.com>
2023-10-11 16:38:34 +01:00

610 lines
16 KiB
C

/*
* Copyright 2022-2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_s32_gpio
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/dt-bindings/gpio/nxp-s32-gpio.h>
#include <zephyr/logging/log.h>
#include <Siul2_Port_Ip.h>
#include <Siul2_Dio_Ip.h>
LOG_MODULE_REGISTER(nxp_s32_gpio, CONFIG_GPIO_LOG_LEVEL);
#ifdef CONFIG_NXP_S32_EIRQ
#include <zephyr/drivers/interrupt_controller/intc_eirq_nxp_s32.h>
#endif
#ifdef CONFIG_NXP_S32_WKPU
#include <zephyr/drivers/interrupt_controller/intc_wkpu_nxp_s32.h>
#endif
#if defined(CONFIG_NXP_S32_EIRQ) || defined(CONFIG_NXP_S32_WKPU)
#define NXP_S32_GPIO_LINE_NOT_FOUND 0xff
struct gpio_nxp_s32_irq_map {
uint8_t pin;
uint8_t line;
} __packed;
struct gpio_nxp_s32_irq_config {
const struct device *ctrl;
uint8_t map_cnt;
struct gpio_nxp_s32_irq_map *map;
};
#endif
struct gpio_nxp_s32_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
Siul2_Dio_Ip_GpioType *gpio_base;
Siul2_Port_Ip_PortType *port_base;
#ifdef CONFIG_NXP_S32_EIRQ
struct gpio_nxp_s32_irq_config *eirq_info;
#endif
#ifdef CONFIG_NXP_S32_WKPU
struct gpio_nxp_s32_irq_config *wkpu_info;
#endif
};
struct gpio_nxp_s32_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
#if defined(CONFIG_NXP_S32_EIRQ) || defined(CONFIG_NXP_S32_WKPU)
sys_slist_t callbacks;
#if defined(CONFIG_NXP_S32_WKPU)
uint32_t pin_wkpu_mask;
#endif /* defined(CONFIG_NXP_S32_WKPU) */
#endif
};
static int nxp_s32_gpio_configure(const struct device *dev, gpio_pin_t pin,
gpio_flags_t flags)
{
const struct gpio_nxp_s32_config *port_config = dev->config;
Siul2_Dio_Ip_GpioType *gpio_base = port_config->gpio_base;
Siul2_Port_Ip_PortType *port_base = port_config->port_base;
Siul2_Port_Ip_PortPullConfig pull_config;
if ((flags & GPIO_SINGLE_ENDED) != 0) {
return -ENOTSUP;
}
#if defined(CONFIG_NXP_S32_WKPU)
struct gpio_nxp_s32_data *data = dev->data;
WRITE_BIT(data->pin_wkpu_mask, pin, (flags & NXP_S32_GPIO_INT_WKPU));
#else
if (flags & NXP_S32_GPIO_INT_WKPU) {
return -ENOTSUP;
}
#endif
switch (flags & GPIO_DIR_MASK) {
case GPIO_INPUT:
Siul2_Port_Ip_SetPinDirection(port_base, pin, SIUL2_PORT_IN);
break;
case GPIO_OUTPUT:
Siul2_Port_Ip_SetPinDirection(port_base, pin, SIUL2_PORT_OUT);
break;
case GPIO_INPUT | GPIO_OUTPUT:
Siul2_Port_Ip_SetPinDirection(port_base, pin, SIUL2_PORT_IN_OUT);
break;
default:
Siul2_Port_Ip_SetPinDirection(port_base, pin, SIUL2_PORT_HI_Z);
break;
}
Siul2_Port_Ip_SetOutputBuffer(port_base, pin,
(flags & GPIO_OUTPUT), PORT_MUX_AS_GPIO);
switch (flags & (GPIO_OUTPUT | GPIO_OUTPUT_INIT_HIGH | GPIO_OUTPUT_INIT_LOW)) {
case GPIO_OUTPUT_HIGH:
Siul2_Dio_Ip_WritePin(gpio_base, pin, 1);
break;
case GPIO_OUTPUT_LOW:
Siul2_Dio_Ip_WritePin(gpio_base, pin, 0);
break;
default:
break;
}
if ((flags & GPIO_PULL_UP) != 0) {
pull_config = PORT_INTERNAL_PULL_UP_ENABLED;
} else if ((flags & GPIO_PULL_DOWN) != 0) {
pull_config = PORT_INTERNAL_PULL_DOWN_ENABLED;
} else {
pull_config = PORT_INTERNAL_PULL_NOT_ENABLED;
}
Siul2_Port_Ip_SetPullSel(port_base, pin, pull_config);
return 0;
}
static int nxp_s32_gpio_port_get_raw(const struct device *port, uint32_t *value)
{
const struct gpio_nxp_s32_config *config = port->config;
*value = Siul2_Dio_Ip_ReadPins(config->gpio_base);
return 0;
}
static int nxp_s32_gpio_port_set_masked_raw(const struct device *port,
gpio_port_pins_t mask,
gpio_port_value_t value)
{
const struct gpio_nxp_s32_config *config = port->config;
Siul2_Dio_Ip_GpioType *gpio_base = config->gpio_base;
gpio_port_pins_t pins_value = Siul2_Dio_Ip_GetPinsOutput(gpio_base);
pins_value = (pins_value & ~mask) | (mask & value);
Siul2_Dio_Ip_WritePins(gpio_base, pins_value);
return 0;
}
static int nxp_s32_gpio_port_set_bits_raw(const struct device *port,
gpio_port_pins_t pins)
{
const struct gpio_nxp_s32_config *config = port->config;
Siul2_Dio_Ip_SetPins(config->gpio_base, pins);
return 0;
}
static int nxp_s32_gpio_port_clear_bits_raw(const struct device *port,
gpio_port_pins_t pins)
{
const struct gpio_nxp_s32_config *config = port->config;
Siul2_Dio_Ip_ClearPins(config->gpio_base, pins);
return 0;
}
static int nxp_s32_gpio_port_toggle_bits(const struct device *port,
gpio_port_pins_t pins)
{
const struct gpio_nxp_s32_config *config = port->config;
Siul2_Dio_Ip_TogglePins(config->gpio_base, pins);
return 0;
}
#if defined(CONFIG_NXP_S32_EIRQ) || defined(CONFIG_NXP_S32_WKPU)
static uint8_t nxp_s32_gpio_pin_to_line(const struct gpio_nxp_s32_irq_config *irq_cfg,
uint8_t pin)
{
uint8_t i;
for (i = 0; i < irq_cfg->map_cnt; i++) {
if (irq_cfg->map[i].pin == pin) {
return irq_cfg->map[i].line;
}
}
return NXP_S32_GPIO_LINE_NOT_FOUND;
}
static void nxp_s32_gpio_isr(uint8_t pin, void *arg)
{
const struct device *dev = (struct device *)arg;
struct gpio_nxp_s32_data *data = dev->data;
gpio_fire_callbacks(&data->callbacks, dev, BIT(pin));
}
#if defined(CONFIG_NXP_S32_EIRQ)
static int nxp_s32_gpio_eirq_get_trigger(Siul2_Icu_Ip_EdgeType *edge_type,
enum gpio_int_mode mode,
enum gpio_int_trig trigger)
{
if (mode == GPIO_INT_MODE_DISABLED) {
*edge_type = SIUL2_ICU_DISABLE;
return 0;
}
if (mode == GPIO_INT_MODE_LEVEL) {
return -ENOTSUP;
}
switch (trigger) {
case GPIO_INT_TRIG_LOW:
*edge_type = SIUL2_ICU_FALLING_EDGE;
break;
case GPIO_INT_TRIG_HIGH:
*edge_type = SIUL2_ICU_RISING_EDGE;
break;
case GPIO_INT_TRIG_BOTH:
*edge_type = SIUL2_ICU_BOTH_EDGES;
break;
default:
return -ENOTSUP;
}
return 0;
}
static int nxp_s32_gpio_config_eirq(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
const struct gpio_nxp_s32_config *config = dev->config;
const struct gpio_nxp_s32_irq_config *irq_cfg = config->eirq_info;
uint8_t irq_line;
Siul2_Icu_Ip_EdgeType edge_type;
if (irq_cfg == NULL) {
LOG_ERR("external interrupt controller not available or enabled");
return -ENOTSUP;
}
if (nxp_s32_gpio_eirq_get_trigger(&edge_type, mode, trig)) {
LOG_ERR("trigger or mode not supported");
return -ENOTSUP;
}
irq_line = nxp_s32_gpio_pin_to_line(irq_cfg, pin);
if (irq_line == NXP_S32_GPIO_LINE_NOT_FOUND) {
if (edge_type == SIUL2_ICU_DISABLE) {
return 0;
}
LOG_ERR("pin %d cannot be used for external interrupt", pin);
return -ENOTSUP;
}
if (edge_type == SIUL2_ICU_DISABLE) {
eirq_nxp_s32_disable_interrupt(irq_cfg->ctrl, irq_line);
eirq_nxp_s32_unset_callback(irq_cfg->ctrl, irq_line);
} else {
if (eirq_nxp_s32_set_callback(irq_cfg->ctrl, irq_line,
nxp_s32_gpio_isr, pin, (void *)dev)) {
LOG_ERR("pin %d is already in use", pin);
return -EBUSY;
}
eirq_nxp_s32_enable_interrupt(irq_cfg->ctrl, irq_line, edge_type);
}
return 0;
}
#endif /* CONFIG_NXP_S32_EIRQ */
#if defined(CONFIG_NXP_S32_WKPU)
static int nxp_s32_gpio_wkpu_get_trigger(Wkpu_Ip_EdgeType *edge_type,
enum gpio_int_mode mode,
enum gpio_int_trig trigger)
{
if (mode == GPIO_INT_MODE_DISABLED) {
*edge_type = WKPU_IP_NONE_EDGE;
return 0;
}
if (mode == GPIO_INT_MODE_LEVEL) {
return -ENOTSUP;
}
switch (trigger) {
case GPIO_INT_TRIG_LOW:
*edge_type = WKPU_IP_FALLING_EDGE;
break;
case GPIO_INT_TRIG_HIGH:
*edge_type = WKPU_IP_RISING_EDGE;
break;
case GPIO_INT_TRIG_BOTH:
*edge_type = WKPU_IP_BOTH_EDGES;
break;
default:
return -ENOTSUP;
}
return 0;
}
static int nxp_s32_gpio_config_wkpu(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
const struct gpio_nxp_s32_config *config = dev->config;
const struct gpio_nxp_s32_irq_config *irq_cfg = config->wkpu_info;
uint8_t irq_line;
Wkpu_Ip_EdgeType edge_type;
if (irq_cfg == NULL) {
LOG_ERR("WKPU controller not available or enabled");
return -ENOTSUP;
}
if (nxp_s32_gpio_wkpu_get_trigger(&edge_type, mode, trig)) {
LOG_ERR("trigger or mode not supported");
return -ENOTSUP;
}
irq_line = nxp_s32_gpio_pin_to_line(irq_cfg, pin);
if (irq_line == NXP_S32_GPIO_LINE_NOT_FOUND) {
if (edge_type == WKPU_IP_NONE_EDGE) {
return 0;
}
LOG_ERR("pin %d cannot be used for external interrupt", pin);
return -ENOTSUP;
}
if (edge_type == WKPU_IP_NONE_EDGE) {
wkpu_nxp_s32_disable_interrupt(irq_cfg->ctrl, irq_line);
wkpu_nxp_s32_unset_callback(irq_cfg->ctrl, irq_line);
} else {
if (wkpu_nxp_s32_set_callback(irq_cfg->ctrl, irq_line,
nxp_s32_gpio_isr, pin, (void *)dev)) {
LOG_ERR("pin %d is already in use", pin);
return -EBUSY;
}
wkpu_nxp_s32_enable_interrupt(irq_cfg->ctrl, irq_line, edge_type);
}
return 0;
}
#endif /* CONFIG_NXP_S32_WKPU */
static int nxp_s32_gpio_pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
#if defined(CONFIG_NXP_S32_WKPU)
struct gpio_nxp_s32_data *data = dev->data;
if (data->pin_wkpu_mask & BIT(pin)) {
return nxp_s32_gpio_config_wkpu(dev, pin, mode, trig);
}
#endif
#if defined(CONFIG_NXP_S32_EIRQ)
return nxp_s32_gpio_config_eirq(dev, pin, mode, trig);
#endif
}
static int nxp_s32_gpio_manage_callback(const struct device *dev,
struct gpio_callback *cb, bool set)
{
struct gpio_nxp_s32_data *data = dev->data;
return gpio_manage_callback(&data->callbacks, cb, set);
}
#endif /* defined(CONFIG_NXP_S32_EIRQ) || defined(CONFIG_NXP_S32_WKPU) */
#ifdef CONFIG_GPIO_GET_CONFIG
static int nxp_s32_gpio_pin_get_config(const struct device *dev,
gpio_pin_t pin,
gpio_flags_t *out_flags)
{
const struct gpio_nxp_s32_config *config = dev->config;
Siul2_Dio_Ip_GpioType *gpio_base = config->gpio_base;
Siul2_Port_Ip_PortType *port_base = config->port_base;
Siul2_Dio_Ip_PinsChannelType pins_output;
gpio_flags_t flags = 0;
if ((port_base->MSCR[pin] & SIUL2_MSCR_IBE_MASK) != 0) {
flags |= GPIO_INPUT;
}
if ((port_base->MSCR[pin] & SIUL2_MSCR_OBE_MASK) != 0) {
flags |= GPIO_OUTPUT;
pins_output = Siul2_Dio_Ip_GetPinsOutput(gpio_base);
if ((pins_output & BIT(pin)) != 0) {
flags |= GPIO_OUTPUT_HIGH;
} else {
flags |= GPIO_OUTPUT_LOW;
}
#ifdef FEATURE_SIUL2_PORT_IP_HAS_OPEN_DRAIN
if ((port_base->MSCR[pin] & SIUL2_MSCR_ODE_MASK) != 0) {
flags |= GPIO_OPEN_DRAIN;
}
#endif /* FEATURE_SIUL2_PORT_IP_HAS_OPEN_DRAIN */
}
if ((port_base->MSCR[pin] & SIUL2_MSCR_PUE_MASK) != 0) {
if ((port_base->MSCR[pin] & SIUL2_MSCR_PUS_MASK) != 0) {
flags |= GPIO_PULL_UP;
} else {
flags |= GPIO_PULL_DOWN;
}
}
*out_flags = flags;
return 0;
}
#endif /* CONFIG_GPIO_GET_CONFIG */
#ifdef CONFIG_GPIO_GET_DIRECTION
static int nxp_s32_gpio_port_get_direction(const struct device *dev,
gpio_port_pins_t map,
gpio_port_pins_t *inputs,
gpio_port_pins_t *outputs)
{
const struct gpio_nxp_s32_config *config = dev->config;
Siul2_Port_Ip_PortType *port_base = config->port_base;
gpio_port_pins_t ip = 0;
gpio_port_pins_t op = 0;
uint32_t pin;
map &= config->common.port_pin_mask;
if (inputs != NULL) {
while (map) {
pin = find_lsb_set(map) - 1;
ip |= (!!(port_base->MSCR[pin] & SIUL2_MSCR_IBE_MASK)) * BIT(pin);
map &= ~BIT(pin);
}
*inputs = ip;
}
if (outputs != NULL) {
while (map) {
pin = find_lsb_set(map) - 1;
op |= (!!(port_base->MSCR[pin] & SIUL2_MSCR_OBE_MASK)) * BIT(pin);
map &= ~BIT(pin);
}
*outputs = op;
}
return 0;
}
#endif /* CONFIG_GPIO_GET_DIRECTION */
static const struct gpio_driver_api gpio_nxp_s32_driver_api = {
.pin_configure = nxp_s32_gpio_configure,
.port_get_raw = nxp_s32_gpio_port_get_raw,
.port_set_masked_raw = nxp_s32_gpio_port_set_masked_raw,
.port_set_bits_raw = nxp_s32_gpio_port_set_bits_raw,
.port_clear_bits_raw = nxp_s32_gpio_port_clear_bits_raw,
.port_toggle_bits = nxp_s32_gpio_port_toggle_bits,
#if defined(CONFIG_NXP_S32_EIRQ) || defined(CONFIG_NXP_S32_WKPU)
.pin_interrupt_configure = nxp_s32_gpio_pin_interrupt_configure,
.manage_callback = nxp_s32_gpio_manage_callback,
#endif
#ifdef CONFIG_GPIO_GET_CONFIG
.pin_get_config = nxp_s32_gpio_pin_get_config,
#endif
#ifdef CONFIG_GPIO_GET_DIRECTION
.port_get_direction = nxp_s32_gpio_port_get_direction,
#endif
};
/* Calculate the port pin mask based on ngpios and gpio-reserved-ranges node
* properties. Multiple reserved ranges are not supported.
*
* For example, for the following gpio node definition:
*
* gpioo: gpio@40521716 {
* ...
* ngpios = <14>;
* gpio-reserved-ranges = <0 10>;
* };
*
* the generated mask will be will be 0x3C00.
*/
#define GPIO_NXP_S32_RESERVED_PIN_MASK(n) \
(GENMASK(DT_INST_PROP_BY_IDX(n, gpio_reserved_ranges, 0) + \
DT_INST_PROP_BY_IDX(n, gpio_reserved_ranges, 1) - 1, \
DT_INST_PROP_BY_IDX(n, gpio_reserved_ranges, 0) \
))
#define GPIO_NXP_S32_PORT_PIN_MASK(n) \
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, gpio_reserved_ranges), \
(GPIO_PORT_PIN_MASK_FROM_DT_INST(n) \
& ~(GPIO_NXP_S32_RESERVED_PIN_MASK(n))), \
(GPIO_PORT_PIN_MASK_FROM_DT_INST(n)))
#define GPIO_NXP_S32_REG_ADDR(n) \
((Siul2_Dio_Ip_GpioType *)DT_INST_REG_ADDR_BY_NAME(n, pgpdo))
#define GPIO_NXP_S32_PORT_REG_ADDR(n) \
((Siul2_Port_Ip_PortType *)DT_INST_REG_ADDR_BY_NAME(n, mscr))
#ifdef CONFIG_NXP_S32_EIRQ
#define GPIO_NXP_S32_EIRQ_NODE(n) \
DT_INST_PHANDLE(n, interrupt_parent)
#define GPIO_NXP_S32_EIRQ_PIN_LINE(idx, n) \
DT_INST_IRQ_BY_IDX(n, idx, gpio_pin), \
DT_INST_IRQ_BY_IDX(n, idx, eirq_line) \
#define GPIO_NXP_S32_SET_EIRQ_INFO(n) \
BUILD_ASSERT((DT_NODE_HAS_PROP(DT_DRV_INST(n), interrupt_parent) == \
DT_NODE_HAS_PROP(DT_DRV_INST(n), interrupts)), \
"interrupts and interrupt-parent must be set when " \
"using external interrupts"); \
IF_ENABLED(DT_NODE_HAS_STATUS(GPIO_NXP_S32_EIRQ_NODE(n), okay), ( \
static uint8_t gpio_nxp_s32_eirq_data_##n[] = { \
LISTIFY(DT_NUM_IRQS(DT_DRV_INST(n)), \
GPIO_NXP_S32_EIRQ_PIN_LINE, (,), n) \
}; \
static struct gpio_nxp_s32_irq_config gpio_nxp_s32_eirq_##n = { \
.ctrl = DEVICE_DT_GET(GPIO_NXP_S32_EIRQ_NODE(n)), \
.map_cnt = DT_NUM_IRQS(DT_DRV_INST(n)), \
.map = (struct gpio_nxp_s32_irq_map *) \
gpio_nxp_s32_eirq_data_##n, \
}; \
))
#define GPIO_NXP_S32_GET_EIRQ_INFO(n) \
.eirq_info = UTIL_AND(DT_NODE_HAS_STATUS(GPIO_NXP_S32_EIRQ_NODE(n), okay),\
&gpio_nxp_s32_eirq_##n),
#else
#define GPIO_NXP_S32_SET_EIRQ_INFO(n)
#define GPIO_NXP_S32_GET_EIRQ_INFO(n)
#endif /* CONFIG_NXP_S32_EIRQ */
#ifdef CONFIG_NXP_S32_WKPU
#define GPIO_NXP_S32_WKPU_NODE(n) DT_INST_PHANDLE(n, nxp_wkpu)
#define GPIO_NXP_S32_SET_WKPU_INFO(n) \
BUILD_ASSERT((DT_INST_NODE_HAS_PROP(n, nxp_wkpu) == \
DT_INST_NODE_HAS_PROP(n, nxp_wkpu_interrupts)), \
"nxp,wkpu and nxp,wkpu-interrupts must be provided"); \
IF_ENABLED(DT_NODE_HAS_STATUS(GPIO_NXP_S32_WKPU_NODE(n), okay), ( \
static uint8_t gpio_nxp_s32_wkpu_data_##n[] = \
DT_INST_PROP(n, nxp_wkpu_interrupts); \
static struct gpio_nxp_s32_irq_config gpio_nxp_s32_wkpu_##n = { \
.ctrl = DEVICE_DT_GET(GPIO_NXP_S32_WKPU_NODE(n)), \
.map_cnt = sizeof(gpio_nxp_s32_wkpu_data_##n) / \
sizeof(struct gpio_nxp_s32_irq_map), \
.map = (struct gpio_nxp_s32_irq_map *) \
gpio_nxp_s32_wkpu_data_##n, \
}; \
))
#define GPIO_NXP_S32_GET_WKPU_INFO(n) \
.wkpu_info = UTIL_AND(DT_NODE_HAS_STATUS(GPIO_NXP_S32_WKPU_NODE(n), okay),\
&gpio_nxp_s32_wkpu_##n)
#else
#define GPIO_NXP_S32_SET_WKPU_INFO(n)
#define GPIO_NXP_S32_GET_WKPU_INFO(n)
#endif /* CONFIG_NXP_S32_WKPU */
#define GPIO_NXP_S32_DEVICE_INIT(n) \
GPIO_NXP_S32_SET_EIRQ_INFO(n) \
GPIO_NXP_S32_SET_WKPU_INFO(n) \
static const struct gpio_nxp_s32_config gpio_nxp_s32_config_##n = { \
.common = { \
.port_pin_mask = GPIO_NXP_S32_PORT_PIN_MASK(n), \
}, \
.gpio_base = GPIO_NXP_S32_REG_ADDR(n), \
.port_base = GPIO_NXP_S32_PORT_REG_ADDR(n), \
GPIO_NXP_S32_GET_EIRQ_INFO(n) \
GPIO_NXP_S32_GET_WKPU_INFO(n) \
}; \
static struct gpio_nxp_s32_data gpio_nxp_s32_data_##n; \
static int gpio_nxp_s32_init_##n(const struct device *dev) \
{ \
return 0; \
} \
DEVICE_DT_INST_DEFINE(n, \
gpio_nxp_s32_init_##n, \
NULL, \
&gpio_nxp_s32_data_##n, \
&gpio_nxp_s32_config_##n, \
POST_KERNEL, \
CONFIG_GPIO_INIT_PRIORITY, \
&gpio_nxp_s32_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_NXP_S32_DEVICE_INIT)