blob: 2d272cdea09da38b4f180b88251ceb311b47895c [file] [log] [blame]
/*
* 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)