blob: e97eefd6b8ddb43e4b0295f3ca39806e5579a80e [file] [log] [blame]
/*
* Copyright (c) 2022 SEAL AG
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_numicro_gpio
#include <errno.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_utils.h>
#include <zephyr/dt-bindings/gpio/numicro-gpio.h>
#include <NuMicro.h>
#define MODE_PIN_SHIFT(pin) ((pin) * 2)
#define MODE_MASK(pin) (3 << MODE_PIN_SHIFT(pin))
#define DINOFF_PIN_SHIFT(pin) ((pin) + 16)
#define DINOFF_MASK(pin) (1 << DINOFF_PIN_SHIFT(pin))
#define PUSEL_PIN_SHIFT(pin) ((pin) * 2)
#define PUSEL_MASK(pin) (3 << PUSEL_PIN_SHIFT(pin))
#define PORT_PIN_MASK 0xFFFF
struct gpio_numicro_config {
/* gpio_driver_config needs to be first */
struct gpio_driver_config common;
GPIO_T *regs;
};
struct gpio_numicro_data {
/* gpio_driver_data needs to be first */
struct gpio_driver_data common;
/* port ISR callback routine address */
sys_slist_t callbacks;
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT
/*
* backup of the INTEN register.
* The higher half is RHIEN for whether rising trigger is enabled, and
* the lower half is FLIEN for whether falling trigger is enabled.
*/
uint32_t interrupt_en_reg_bak;
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */
};
static int gpio_numicro_configure(const struct device *dev,
gpio_pin_t pin, gpio_flags_t flags)
{
const struct gpio_numicro_config *cfg = dev->config;
GPIO_T * const regs = cfg->regs;
uint32_t mode;
uint32_t debounce_enable = 0;
uint32_t schmitt_enable = 0;
uint32_t disable_input_path = 0;
uint32_t bias = GPIO_PUSEL_DISABLE;
/* Pin mode */
if ((flags & GPIO_OUTPUT) != 0) {
/* Output */
if ((flags & GPIO_SINGLE_ENDED) != 0) {
if ((flags & GPIO_LINE_OPEN_DRAIN) != 0) {
mode = GPIO_MODE_OPEN_DRAIN;
} else {
/* Output can't be open source */
return -ENOTSUP;
}
} else {
mode = GPIO_MODE_OUTPUT;
}
} else if ((flags & GPIO_INPUT) != 0) {
/* Input */
mode = GPIO_MODE_INPUT;
if ((flags & NUMICRO_GPIO_INPUT_DEBOUNCE) != 0) {
debounce_enable = 1;
}
if ((flags & NUMICRO_GPIO_INPUT_SCHMITT) != 0) {
schmitt_enable = 1;
}
} else {
/* Deactivated: Analog */
mode = GPIO_MODE_INPUT;
disable_input_path = 1;
}
/* Bias */
if ((flags & GPIO_OUTPUT) != 0 || (flags & GPIO_INPUT) != 0) {
if ((flags & GPIO_PULL_UP) != 0) {
bias = GPIO_PUSEL_PULL_UP;
} else if ((flags & GPIO_PULL_DOWN) != 0) {
bias = GPIO_PUSEL_PULL_DOWN;
}
}
regs->MODE = (regs->MODE & ~MODE_MASK(pin)) |
(mode << MODE_PIN_SHIFT(pin));
regs->DBEN = (regs->DBEN & ~BIT(pin)) | (debounce_enable << pin);
regs->SMTEN = (regs->SMTEN & ~BIT(pin)) | (schmitt_enable << pin);
regs->DINOFF = (regs->DINOFF & ~DINOFF_MASK(pin)) |
(disable_input_path << DINOFF_PIN_SHIFT(pin));
regs->PUSEL = (regs->PUSEL & ~PUSEL_MASK(pin)) |
(bias << PUSEL_PIN_SHIFT(pin));
return 0;
}
static int gpio_numicro_port_get_raw(const struct device *dev, uint32_t *value)
{
const struct gpio_numicro_config *cfg = dev->config;
*value = cfg->regs->PIN & PORT_PIN_MASK;
return 0;
}
static int gpio_numicro_port_set_masked_raw(const struct device *dev,
uint32_t mask,
uint32_t value)
{
const struct gpio_numicro_config *cfg = dev->config;
cfg->regs->DATMSK = ~mask;
cfg->regs->DOUT = value;
return 0;
}
static int gpio_numicro_port_set_bits_raw(const struct device *dev,
uint32_t mask)
{
const struct gpio_numicro_config *cfg = dev->config;
cfg->regs->DATMSK = ~mask;
cfg->regs->DOUT = PORT_PIN_MASK;
return 0;
}
static int gpio_numicro_port_clear_bits_raw(const struct device *dev,
uint32_t mask)
{
const struct gpio_numicro_config *cfg = dev->config;
cfg->regs->DATMSK = ~mask;
cfg->regs->DOUT = 0;
return 0;
}
static int gpio_numicro_port_toggle_bits(const struct device *dev, uint32_t mask)
{
const struct gpio_numicro_config *cfg = dev->config;
cfg->regs->DATMSK = 0;
cfg->regs->DOUT ^= mask;
return 0;
}
static int gpio_numicro_pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin, enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
const struct gpio_numicro_config *cfg = dev->config;
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT
struct gpio_numicro_data *data = dev->data;
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */
uint32_t int_type = 0;
uint32_t int_level = 0;
uint32_t int_level_mask = BIT(pin) | BIT(pin + 16);
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT
if (mode == GPIO_INT_MODE_DISABLE_ONLY) {
cfg->regs->INTEN &= ~(BIT(pin) | BIT(pin + 16));
return 0;
} else if (mode == GPIO_INT_MODE_ENABLE_ONLY) {
cfg->regs->INTEN |= data->interrupt_en_reg_bak & (BIT(pin) | BIT(pin + 16));
return 0;
}
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */
if (mode != GPIO_INT_MODE_DISABLED) {
int_type = (mode == GPIO_INT_MODE_LEVEL) ? 1 : 0;
switch (trig) {
case GPIO_INT_TRIG_LOW:
int_level = BIT(pin);
break;
case GPIO_INT_TRIG_HIGH:
int_level = BIT(pin + 16);
break;
case GPIO_INT_TRIG_BOTH:
int_level = BIT(pin) | BIT(pin + 16);
break;
default:
return -EINVAL;
}
}
cfg->regs->INTTYPE = (cfg->regs->INTTYPE & ~BIT(pin)) | (int_type << pin);
cfg->regs->INTEN = (cfg->regs->INTEN & ~int_level_mask) | int_level;
#ifdef CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT
data->interrupt_en_reg_bak = cfg->regs->INTEN;
#endif /* CONFIG_GPIO_ENABLE_DISABLE_INTERRUPT */
return 0;
}
static int gpio_numicro_manage_callback(const struct device *dev,
struct gpio_callback *callback,
bool set)
{
struct gpio_numicro_data *data = dev->data;
return gpio_manage_callback(&data->callbacks, callback, set);
}
static void gpio_numicro_isr(const struct device *dev)
{
const struct gpio_numicro_config *cfg = dev->config;
struct gpio_numicro_data *data = dev->data;
uint32_t int_status;
int_status = cfg->regs->INTSRC;
/* Clear the port interrupts */
cfg->regs->INTSRC = int_status;
gpio_fire_callbacks(&data->callbacks, dev, int_status);
}
static const struct gpio_driver_api gpio_numicro_driver_api = {
.pin_configure = gpio_numicro_configure,
.port_get_raw = gpio_numicro_port_get_raw,
.port_set_masked_raw = gpio_numicro_port_set_masked_raw,
.port_set_bits_raw = gpio_numicro_port_set_bits_raw,
.port_clear_bits_raw = gpio_numicro_port_clear_bits_raw,
.port_toggle_bits = gpio_numicro_port_toggle_bits,
.pin_interrupt_configure = gpio_numicro_pin_interrupt_configure,
.manage_callback = gpio_numicro_manage_callback,
};
#define GPIO_NUMICRO_INIT(n) \
static int gpio_numicro_port##n##_init(const struct device *dev)\
{ \
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
gpio_numicro_isr, \
DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
return 0; \
} \
\
static struct gpio_numicro_data gpio_numicro_port##n##_data; \
\
static const struct gpio_numicro_config gpio_numicro_port##n##_config = {\
.common = { \
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),\
}, \
.regs = (GPIO_T *)DT_INST_REG_ADDR(n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, \
gpio_numicro_port##n##_init, \
NULL, \
&gpio_numicro_port##n##_data, \
&gpio_numicro_port##n##_config, \
PRE_KERNEL_1, \
CONFIG_GPIO_INIT_PRIORITY, \
&gpio_numicro_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_NUMICRO_INIT)