zephyr/drivers/gpio/gpio_b91.c

561 lines
15 KiB
C

/*
* Copyright (c) 2021 Telink Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "analog.h"
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include "gpio_utils.h"
/* Driver dts compatibility: telink,b91_gpio */
#define DT_DRV_COMPAT telink_b91_gpio
/* Get GPIO instance */
#define GET_GPIO(dev) ((volatile struct gpio_b91_t *) \
((const struct gpio_b91_config *)dev->config)->gpio_base)
/* Get GPIO IRQ number defined in dts */
#define GET_IRQ_NUM(dev) (((const struct gpio_b91_config *)dev->config)->irq_num)
/* Get GPIO IRQ priority defined in dts */
#define GET_IRQ_PRIORITY(dev) (((const struct gpio_b91_config *)dev->config)->irq_priority)
/* Get GPIO port number: port A - 0, port B - 1, ..., port F - 5 */
#define GET_PORT_NUM(gpio) ((uint8_t)(((uint32_t)gpio - DT_REG_ADDR(DT_NODELABEL(gpioa))) / \
DT_REG_SIZE(DT_NODELABEL(gpioa))))
/* Check that gpio is port C */
#define IS_PORT_C(gpio) ((uint32_t)gpio == DT_REG_ADDR(DT_NODELABEL(gpioc)))
/* Check that gpio is port D */
#define IS_PORT_D(gpio) ((uint32_t)gpio == DT_REG_ADDR(DT_NODELABEL(gpiod)))
/* Check that 'inst' has only 1 interrupt selected in dts */
#define IS_INST_IRQ_EN(inst) (DT_NUM_IRQS(DT_DRV_INST(inst)) == 1)
/* Max pin number per port (pin 0..7) */
#define PIN_NUM_MAX ((uint8_t)7u)
/* IRQ Enable registers */
#define reg_irq_risc0_en(i) REG_ADDR8(0x140338 + i)
#define reg_irq_risc1_en(i) REG_ADDR8(0x140340 + i)
/* Pull-up/down resistors */
#define GPIO_PIN_UP_DOWN_FLOAT ((uint8_t)0u)
#define GPIO_PIN_PULLDOWN_100K ((uint8_t)2u)
#define GPIO_PIN_PULLUP_10K ((uint8_t)3u)
/* GPIO interrupt types */
#define INTR_RISING_EDGE ((uint8_t)0u)
#define INTR_FALLING_EDGE ((uint8_t)1u)
#define INTR_HIGH_LEVEL ((uint8_t)2u)
#define INTR_LOW_LEVEL ((uint8_t)3u)
/* Supported IRQ numbers */
#define IRQ_GPIO ((uint8_t)25u)
#define IRQ_GPIO2_RISC0 ((uint8_t)26u)
#define IRQ_GPIO2_RISC1 ((uint8_t)27u)
/* B91 GPIO registers structure */
struct gpio_b91_t {
uint8_t input; /* Input: read GPI input */
uint8_t ie; /* IE: input enable, high active. 1: enable, 0: disable */
uint8_t oen; /* OEN: output enable, low active. 0: enable, 1: disable */
uint8_t output; /* Output: configure GPIO output */
uint8_t polarity; /* Polarity: interrupt polarity: rising, falling */
uint8_t ds; /* DS: drive strength. 1: maximum (default), 0: minimal */
uint8_t actas_gpio; /* Act as GPIO: enable (1) or disable (0) GPIO function */
uint8_t irq_en; /* Act as GPIO: enable (1) or disable (0) GPIO function */
};
/* GPIO driver configuration structure */
struct gpio_b91_config {
struct gpio_driver_config common;
uint32_t gpio_base;
uint8_t irq_num;
uint8_t irq_priority;
void (*pirq_connect)(void);
};
/* GPIO driver data structure */
struct gpio_b91_data {
struct gpio_driver_data common; /* driver data */
sys_slist_t callbacks; /* list of callbacks */
};
/* Set IRQ Enable bit based on IRQ number */
static inline void gpiob_b91_irq_en_set(const struct device *dev, gpio_pin_t pin)
{
uint8_t irq = GET_IRQ_NUM(dev);
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
if (irq == IRQ_GPIO) {
BM_SET(gpio->irq_en, BIT(pin));
} else if (irq == IRQ_GPIO2_RISC0) {
BM_SET(reg_irq_risc0_en(GET_PORT_NUM(gpio)), BIT(pin));
} else if (irq == IRQ_GPIO2_RISC1) {
BM_SET(reg_irq_risc1_en(GET_PORT_NUM(gpio)), BIT(pin));
} else {
__ASSERT(false, "Not supported GPIO IRQ number.");
}
}
/* Clear IRQ Enable bit based on IRQ number */
static inline void gpiob_b91_irq_en_clr(const struct device *dev, gpio_pin_t pin)
{
uint8_t irq = GET_IRQ_NUM(dev);
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
if (irq == IRQ_GPIO) {
BM_CLR(gpio->irq_en, BIT(pin));
} else if (irq == IRQ_GPIO2_RISC0) {
BM_CLR(reg_irq_risc0_en(GET_PORT_NUM(gpio)), BIT(pin));
} else if (irq == IRQ_GPIO2_RISC1) {
BM_CLR(reg_irq_risc1_en(GET_PORT_NUM(gpio)), BIT(pin));
}
}
/* Get IRQ Enable register value */
static inline uint8_t gpio_b91_irq_en_get(const struct device *dev)
{
uint8_t status = 0;
uint8_t irq = GET_IRQ_NUM(dev);
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
if (irq == IRQ_GPIO) {
status = gpio->irq_en;
} else if (irq == IRQ_GPIO2_RISC0) {
status = reg_irq_risc0_en(GET_PORT_NUM(gpio));
} else if (irq == IRQ_GPIO2_RISC1) {
status = reg_irq_risc1_en(GET_PORT_NUM(gpio));
}
return status;
}
/* Clear IRQ Status bit */
static inline void gpio_b91_irq_status_clr(uint8_t irq)
{
gpio_irq_status_e status = 0;
if (irq == IRQ_GPIO) {
status = FLD_GPIO_IRQ_CLR;
} else if (irq == IRQ_GPIO2_RISC0) {
status = FLD_GPIO_IRQ_GPIO2RISC0_CLR;
} else if (irq == IRQ_GPIO2_RISC1) {
status = FLD_GPIO_IRQ_GPIO2RISC1_CLR;
}
reg_gpio_irq_clr = status;
}
/* Set pin's irq type */
void gpio_b91_irq_set(const struct device *dev, gpio_pin_t pin,
uint8_t trigger_type)
{
uint8_t irq_lvl = 0;
uint8_t irq_mask = 0;
uint8_t irq_num = GET_IRQ_NUM(dev);
uint8_t irq_prioriy = GET_IRQ_PRIORITY(dev);
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
/* Get level and mask based on IRQ number */
if (irq_num == IRQ_GPIO) {
irq_lvl = FLD_GPIO_IRQ_LVL_GPIO;
irq_mask = FLD_GPIO_IRQ_MASK_GPIO;
} else if (irq_num == IRQ_GPIO2_RISC0) {
irq_lvl = FLD_GPIO_IRQ_LVL_GPIO2RISC0;
irq_mask = FLD_GPIO_IRQ_MASK_GPIO2RISC0;
} else if (irq_num == IRQ_GPIO2_RISC1) {
irq_lvl = FLD_GPIO_IRQ_LVL_GPIO2RISC1;
irq_mask = FLD_GPIO_IRQ_MASK_GPIO2RISC1;
}
/* Set polarity and level */
switch (trigger_type) {
case INTR_RISING_EDGE:
BM_CLR(gpio->polarity, BIT(pin));
BM_CLR(reg_gpio_irq_risc_mask, irq_lvl);
break;
case INTR_FALLING_EDGE:
BM_SET(gpio->polarity, BIT(pin));
BM_CLR(reg_gpio_irq_risc_mask, irq_lvl);
break;
case INTR_HIGH_LEVEL:
BM_CLR(gpio->polarity, BIT(pin));
BM_SET(reg_gpio_irq_risc_mask, irq_lvl);
break;
case INTR_LOW_LEVEL:
BM_SET(gpio->polarity, BIT(pin));
BM_SET(reg_gpio_irq_risc_mask, irq_lvl);
break;
}
if (irq_num == IRQ_GPIO) {
reg_gpio_irq_ctrl |= FLD_GPIO_CORE_INTERRUPT_EN;
}
gpio_b91_irq_status_clr(irq_num);
BM_SET(reg_gpio_irq_risc_mask, irq_mask);
/* Enable peripheral interrupt */
gpiob_b91_irq_en_set(dev, pin);
/* Enable PLIC interrupt */
riscv_plic_irq_enable(irq_num);
riscv_plic_set_priority(irq_num, irq_prioriy);
}
/* Set pin's pull-up/down resistor */
static void gpio_b91_up_down_res_set(volatile struct gpio_b91_t *gpio,
gpio_pin_t pin,
uint8_t up_down_res)
{
uint8_t val;
uint8_t mask;
uint8_t analog_reg;
pin = BIT(pin);
val = up_down_res & 0x03;
analog_reg = 0x0e + (GET_PORT_NUM(gpio) << 1) + ((pin & 0xf0) ? 1 : 0);
if (pin & 0x11) {
val = val << 0;
mask = 0xfc;
} else if (pin & 0x22) {
val = val << 2;
mask = 0xf3;
} else if (pin & 0x44) {
val = val << 4;
mask = 0xcf;
} else if (pin & 0x88) {
val = val << 6;
mask = 0x3f;
} else {
return;
}
analog_write_reg8(analog_reg, (analog_read_reg8(analog_reg) & mask) | val);
}
/* Config Pin pull-up / pull-down resistors */
static void gpio_b91_config_up_down_res(volatile struct gpio_b91_t *gpio,
gpio_pin_t pin,
gpio_flags_t flags)
{
if ((flags & GPIO_PULL_UP) != 0) {
gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_PULLUP_10K);
} else if ((flags & GPIO_PULL_DOWN) != 0) {
gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_PULLDOWN_100K);
} else {
gpio_b91_up_down_res_set(gpio, pin, GPIO_PIN_UP_DOWN_FLOAT);
}
}
/* Config Pin In/Out direction */
static void gpio_b91_config_in_out(volatile struct gpio_b91_t *gpio,
gpio_pin_t pin,
gpio_flags_t flags)
{
uint8_t ie_addr = 0;
/* Port C and D Input Enable registers are located in another place: analog */
if (IS_PORT_C(gpio)) {
ie_addr = areg_gpio_pc_ie;
} else if (IS_PORT_D(gpio)) {
ie_addr = areg_gpio_pd_ie;
}
/* Enable/disable output */
WRITE_BIT(gpio->oen, pin, ~flags & GPIO_OUTPUT);
/* Enable/disable input */
if (ie_addr != 0) {
/* Port C and D are located in analog space */
if (flags & GPIO_INPUT) {
analog_write_reg8(ie_addr, analog_read_reg8(ie_addr) | BIT(pin));
} else {
analog_write_reg8(ie_addr, analog_read_reg8(ie_addr) & (~BIT(pin)));
}
} else {
/* Input Enable registers of all other ports are located in common GPIO space */
WRITE_BIT(gpio->ie, pin, flags & GPIO_INPUT);
}
}
/* GPIO driver initialization */
static int gpio_b91_init(const struct device *dev)
{
const struct gpio_b91_config *cfg = dev->config;
cfg->pirq_connect();
return 0;
}
/* API implementation: pin_configure */
static int gpio_b91_pin_configure(const struct device *dev,
gpio_pin_t pin,
gpio_flags_t flags)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
/* Check input parameters: pin number */
if (pin > PIN_NUM_MAX) {
return -ENOTSUP;
}
/* Check input parameters: open-source and open-drain */
if ((flags & GPIO_SINGLE_ENDED) != 0) {
return -ENOTSUP;
}
/* Check input parameters: simultaneous in/out mode */
if ((flags & GPIO_OUTPUT) && (flags & GPIO_INPUT)) {
return -ENOTSUP;
}
/* Set GPIO init state if defined to avoid glitches */
if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
gpio->output |= BIT(pin);
} else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
gpio->output &= ~BIT(pin);
}
/* GPIO function enable */
WRITE_BIT(gpio->actas_gpio, BIT(pin), 1);
/* Set GPIO pull-up / pull-down resistors */
gpio_b91_config_up_down_res(gpio, pin, flags);
/* Enable/disable input/output */
gpio_b91_config_in_out(gpio, pin, flags);
return 0;
}
/* API implementation: port_get_raw */
static int gpio_b91_port_get_raw(const struct device *dev,
gpio_port_value_t *value)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
*value = gpio->input;
return 0;
}
/* API implementation: port_set_masked_raw */
static int gpio_b91_port_set_masked_raw(const struct device *dev,
gpio_port_pins_t mask,
gpio_port_value_t value)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
gpio->output = (gpio->output & ~mask) | (value & mask);
return 0;
}
/* API implementation: port_set_bits_raw */
static int gpio_b91_port_set_bits_raw(const struct device *dev,
gpio_port_pins_t mask)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
gpio->output |= mask;
return 0;
}
/* API implementation: port_clear_bits_raw */
static int gpio_b91_port_clear_bits_raw(const struct device *dev,
gpio_port_pins_t mask)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
gpio->output &= ~mask;
return 0;
}
/* API implementation: port_toggle_bits */
static int gpio_b91_port_toggle_bits(const struct device *dev,
gpio_port_pins_t mask)
{
volatile struct gpio_b91_t *gpio = GET_GPIO(dev);
gpio->output ^= mask;
return 0;
}
/* API implementation: interrupts handler */
#if IS_INST_IRQ_EN(0) || IS_INST_IRQ_EN(1) || IS_INST_IRQ_EN(2) || \
IS_INST_IRQ_EN(3) || IS_INST_IRQ_EN(4)
static void gpio_b91_irq_handler(const struct device *dev)
{
struct gpio_b91_data *data = dev->data;
uint8_t irq = GET_IRQ_NUM(dev);
uint8_t status = gpio_b91_irq_en_get(dev);
gpio_b91_irq_status_clr(irq);
gpio_fire_callbacks(&data->callbacks, dev, status);
}
#endif
/* API implementation: pin_interrupt_configure */
static int gpio_b91_pin_interrupt_configure(const struct device *dev,
gpio_pin_t pin,
enum gpio_int_mode mode,
enum gpio_int_trig trig)
{
int ret_status = 0;
switch (mode) {
case GPIO_INT_MODE_DISABLED: /* GPIO interrupt disable */
gpiob_b91_irq_en_clr(dev, pin);
break;
case GPIO_INT_MODE_LEVEL:
if (trig == GPIO_INT_TRIG_HIGH) { /* GPIO interrupt High level */
gpio_b91_irq_set(dev, pin, INTR_HIGH_LEVEL);
} else if (trig == GPIO_INT_TRIG_LOW) { /* GPIO interrupt Low level */
gpio_b91_irq_set(dev, pin, INTR_LOW_LEVEL);
} else {
ret_status = -ENOTSUP;
}
break;
case GPIO_INT_MODE_EDGE:
if (trig == GPIO_INT_TRIG_HIGH) { /* GPIO interrupt Rising edge */
gpio_b91_irq_set(dev, pin, INTR_RISING_EDGE);
} else if (trig == GPIO_INT_TRIG_LOW) { /* GPIO interrupt Falling edge */
gpio_b91_irq_set(dev, pin, INTR_FALLING_EDGE);
} else {
ret_status = -ENOTSUP;
}
break;
default:
ret_status = -ENOTSUP;
break;
}
return ret_status;
}
/* API implementation: manage_callback */
static int gpio_b91_manage_callback(const struct device *dev,
struct gpio_callback *callback,
bool set)
{
struct gpio_b91_data *data = dev->data;
return gpio_manage_callback(&data->callbacks, callback, set);
}
/* GPIO driver APIs structure */
static const struct gpio_driver_api gpio_b91_driver_api = {
.pin_configure = gpio_b91_pin_configure,
.port_get_raw = gpio_b91_port_get_raw,
.port_set_masked_raw = gpio_b91_port_set_masked_raw,
.port_set_bits_raw = gpio_b91_port_set_bits_raw,
.port_clear_bits_raw = gpio_b91_port_clear_bits_raw,
.port_toggle_bits = gpio_b91_port_toggle_bits,
.pin_interrupt_configure = gpio_b91_pin_interrupt_configure,
.manage_callback = gpio_b91_manage_callback
};
/* If instance 0 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0
static void gpio_b91_irq_connect_0(void)
{
#if IS_INST_IRQ_EN(0)
IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
gpio_b91_irq_handler,
DEVICE_DT_INST_GET(0), 0);
#endif
}
#endif
/* If instance 1 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1
static void gpio_b91_irq_connect_1(void)
{
#if IS_INST_IRQ_EN(1)
IRQ_CONNECT(DT_INST_IRQN(1), DT_INST_IRQ(1, priority),
gpio_b91_irq_handler,
DEVICE_DT_INST_GET(1), 0);
#endif
}
#endif
/* If instance 2 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 2
static void gpio_b91_irq_connect_2(void)
{
#if IS_INST_IRQ_EN(2)
IRQ_CONNECT(DT_INST_IRQN(2), DT_INST_IRQ(2, priority),
gpio_b91_irq_handler,
DEVICE_DT_INST_GET(2), 0);
#endif
}
#endif
/* If instance 3 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 3
static void gpio_b91_irq_connect_3(void)
{
#if IS_INST_IRQ_EN(3)
IRQ_CONNECT(DT_INST_IRQN(3), DT_INST_IRQ(3, priority),
gpio_b91_irq_handler,
DEVICE_DT_INST_GET(3), 0);
#endif
}
#endif
/* If instance 4 is present and has interrupt enabled, connect IRQ */
#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 4
static void gpio_b91_irq_connect_4(void)
{
#if IS_INST_IRQ_EN(4)
IRQ_CONNECT(DT_INST_IRQN(4), DT_INST_IRQ(4, priority),
gpio_b91_irq_handler,
DEVICE_DT_INST_GET(4), 0);
#endif
}
#endif
/* GPIO driver registration */
#define GPIO_B91_INIT(n) \
static const struct gpio_b91_config gpio_b91_config_##n = { \
.common = { \
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n) \
}, \
.gpio_base = DT_INST_REG_ADDR(n), \
.irq_num = DT_INST_IRQN(n), \
.irq_priority = DT_INST_IRQ(n, priority), \
.pirq_connect = gpio_b91_irq_connect_##n \
}; \
static struct gpio_b91_data gpio_b91_data_##n; \
\
DEVICE_DT_INST_DEFINE(n, gpio_b91_init, \
NULL, \
&gpio_b91_data_##n, \
&gpio_b91_config_##n, \
PRE_KERNEL_1, \
CONFIG_GPIO_INIT_PRIORITY, \
&gpio_b91_driver_api);
DT_INST_FOREACH_STATUS_OKAY(GPIO_B91_INIT)