You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
570 lines
14 KiB
570 lines
14 KiB
/* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
* |
|
* Copyright (c) 2024 Realtek Semiconductor Corporation, SIBG-SD7 |
|
* Author: Lin Yu-Cheng <lin_yu_cheng@realtek.com> |
|
*/ |
|
|
|
#define DT_DRV_COMPAT realtek_rts5912_gpio |
|
|
|
#include <errno.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/irq.h> |
|
#include "zephyr/drivers/gpio/gpio_utils.h" |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/dt-bindings/gpio/realtek-gpio.h> |
|
|
|
#include <reg/reg_gpio.h> |
|
|
|
LOG_MODULE_REGISTER(gpio_rts5912, CONFIG_GPIO_LOG_LEVEL); |
|
|
|
#define RTS5912_GPIOA_REG_BASE ((GPIO_Type *)(DT_REG_ADDR(DT_NODELABEL(gpioa)))) |
|
|
|
struct gpio_rts5912_config { |
|
struct gpio_driver_config common; |
|
volatile uint32_t *reg_base; |
|
uint8_t num_pins; |
|
}; |
|
|
|
struct gpio_rts5912_data { |
|
struct gpio_driver_data common; |
|
sys_slist_t callbacks; |
|
}; |
|
|
|
static int pin_is_valid(const struct gpio_rts5912_config *config, gpio_pin_t pin) |
|
{ |
|
if (pin >= config->num_pins) { |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int pin_output_high(const struct device *port, gpio_pin_t pin) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = &config->reg_base[pin]; |
|
|
|
int err = pin_is_valid(config, pin); |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_OUTMD_Msk) { |
|
/* Switch I/O mode to input mode when configuration is open-drain with output high |
|
*/ |
|
*gcr = (*gcr & ~GPIO_GCR_DIR_Msk) | GPIO_GCR_OUTCTRL_Msk; |
|
} else { |
|
*gcr |= GPIO_GCR_OUTCTRL_Msk | GPIO_GCR_DIR_Msk; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int pin_output_low(const struct device *port, gpio_pin_t pin) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = &config->reg_base[pin]; |
|
|
|
int err = pin_is_valid(config, pin); |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
*gcr = (*gcr & ~GPIO_GCR_OUTCTRL_Msk) | GPIO_GCR_DIR_Msk; |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_configuration(const struct device *port, gpio_pin_t pin, gpio_flags_t flags) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = &config->reg_base[pin]; |
|
uint32_t cfg_val = *gcr; |
|
|
|
int err = pin_is_valid(config, pin); |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (flags & GPIO_INPUT) { |
|
cfg_val &= ~GPIO_GCR_DIR_Msk; |
|
cfg_val &= ~GPIO_GCR_OUTCTRL_Msk; |
|
/* enable input detect */ |
|
cfg_val |= GPIO_GCR_INDETEN_Msk; |
|
} |
|
|
|
if (flags & GPIO_DISCONNECTED) { |
|
cfg_val &= ~GPIO_GCR_DIR_Msk; |
|
cfg_val &= ~GPIO_GCR_OUTCTRL_Msk; |
|
/* disable input detect */ |
|
cfg_val &= ~GPIO_GCR_INDETEN_Msk; |
|
} |
|
|
|
if (flags & GPIO_OPEN_DRAIN) { |
|
cfg_val |= GPIO_GCR_OUTMD_Msk; |
|
} else { |
|
cfg_val &= ~GPIO_GCR_OUTMD_Msk; |
|
} |
|
|
|
switch (flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) { |
|
case GPIO_PULL_UP: |
|
cfg_val &= ~GPIO_GCR_PULLDWEN_Msk; |
|
cfg_val |= GPIO_GCR_PULLUPEN_Msk; |
|
break; |
|
case GPIO_PULL_DOWN: |
|
cfg_val &= ~GPIO_GCR_PULLUPEN_Msk; |
|
cfg_val |= GPIO_GCR_PULLDWEN_Msk; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
switch (flags & RTS5912_GPIO_VOLTAGE_MASK) { |
|
case RTS5912_GPIO_VOLTAGE_1V8: |
|
cfg_val |= GPIO_GCR_INVOLMD_Msk; |
|
break; |
|
case RTS5912_GPIO_VOLTAGE_DEFAULT: |
|
case RTS5912_GPIO_VOLTAGE_3V3: |
|
cfg_val &= ~GPIO_GCR_INVOLMD_Msk; |
|
break; |
|
case RTS5912_GPIO_VOLTAGE_5V0: |
|
return -ENOTSUP; |
|
default: |
|
break; |
|
} |
|
|
|
if (flags & RTS5912_GPIO_OUTDRV) { |
|
cfg_val |= GPIO_GCR_OUTDRV_Msk; |
|
} else { |
|
cfg_val &= ~GPIO_GCR_OUTDRV_Msk; |
|
} |
|
|
|
if (flags & RTS5912_GPIO_SLEWRATE) { |
|
cfg_val |= GPIO_GCR_SLEWRATE_Msk; |
|
} else { |
|
cfg_val &= ~GPIO_GCR_SLEWRATE_Msk; |
|
} |
|
|
|
if (flags & RTS5912_GPIO_SCHEN) { |
|
cfg_val |= GPIO_GCR_SCHEN_Msk; |
|
} else { |
|
cfg_val &= ~GPIO_GCR_SCHEN_Msk; |
|
} |
|
|
|
cfg_val &= ~GPIO_GCR_MFCTRL_Msk; |
|
switch (flags & RTS5912_GPIO_MFCTRL_MASK) { |
|
case RTS5912_GPIO_MFCTRL_0: |
|
cfg_val |= (0U << GPIO_GCR_MFCTRL_Pos); |
|
break; |
|
case RTS5912_GPIO_MFCTRL_1: |
|
cfg_val |= (1U << GPIO_GCR_MFCTRL_Pos); |
|
break; |
|
case RTS5912_GPIO_MFCTRL_2: |
|
cfg_val |= (2U << GPIO_GCR_MFCTRL_Pos); |
|
break; |
|
case RTS5912_GPIO_MFCTRL_3: |
|
cfg_val |= (3U << GPIO_GCR_MFCTRL_Pos); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
*gcr = cfg_val; |
|
|
|
if (flags & GPIO_OUTPUT) { |
|
if (flags & GPIO_OUTPUT_INIT_HIGH) { |
|
pin_output_high(port, pin); |
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) { |
|
pin_output_low(port, pin); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
static int gpio_rts5912_get_configuration(const struct device *port, gpio_pin_t pin, |
|
gpio_flags_t *flags) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = &config->reg_base[pin]; |
|
gpio_flags_t cfg_flag = 0x0UL; |
|
|
|
int err = pin_is_valid(config, pin); |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_OUTCTRL_Msk) { |
|
cfg_flag |= GPIO_OUTPUT | GPIO_OUTPUT_INIT_HIGH; |
|
} else { |
|
if (*gcr & GPIO_GCR_DIR_Msk) { |
|
cfg_flag |= GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW; |
|
} else { |
|
cfg_flag |= GPIO_INPUT; |
|
if (*gcr & GPIO_GCR_INVOLMD_Msk) { |
|
cfg_flag |= RTS5912_GPIO_VOLTAGE_1V8; |
|
} else { |
|
cfg_flag |= RTS5912_GPIO_VOLTAGE_3V3; |
|
} |
|
} |
|
} |
|
|
|
if (*gcr & GPIO_GCR_OUTMD_Msk) { |
|
cfg_flag |= GPIO_OPEN_DRAIN; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_PULLUPEN_Msk) { |
|
cfg_flag |= GPIO_PULL_UP; |
|
} else if (*gcr & GPIO_GCR_PULLDWEN_Msk) { |
|
cfg_flag |= GPIO_PULL_DOWN; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_INDETEN_Msk) { |
|
cfg_flag |= RTS5912_GPIO_INDETEN; |
|
} else { |
|
cfg_flag &= ~RTS5912_GPIO_INDETEN; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_OUTDRV_Msk) { |
|
cfg_flag |= RTS5912_GPIO_OUTDRV; |
|
} else { |
|
cfg_flag &= ~RTS5912_GPIO_OUTDRV; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_SLEWRATE_Msk) { |
|
cfg_flag |= RTS5912_GPIO_SLEWRATE; |
|
} else { |
|
cfg_flag &= ~RTS5912_GPIO_SLEWRATE; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_SCHEN_Msk) { |
|
cfg_flag |= RTS5912_GPIO_SCHEN; |
|
} else { |
|
cfg_flag &= ~RTS5912_GPIO_SCHEN; |
|
} |
|
|
|
switch ((*gcr & GPIO_GCR_MFCTRL_Msk) >> GPIO_GCR_MFCTRL_Pos) { |
|
case 0: |
|
cfg_flag |= RTS5912_GPIO_MFCTRL_0; |
|
break; |
|
case 1: |
|
cfg_flag |= RTS5912_GPIO_MFCTRL_1; |
|
break; |
|
case 2: |
|
cfg_flag |= RTS5912_GPIO_MFCTRL_2; |
|
break; |
|
case 3: |
|
cfg_flag |= RTS5912_GPIO_MFCTRL_3; |
|
break; |
|
default: |
|
cfg_flag |= RTS5912_GPIO_MFCTRL_0; |
|
break; |
|
} |
|
|
|
if (*gcr & GPIO_GCR_INTEN_Msk) { |
|
switch (*gcr & GPIO_GCR_INTCTRL_Msk) { |
|
case GPIO_GCR_INTCTRL_TRIG_EDGE_HIGH: |
|
cfg_flag |= GPIO_INT_EDGE_RISING; |
|
break; |
|
case GPIO_GCR_INTCTRL_TRIG_EDGE_LOW: |
|
cfg_flag |= GPIO_INT_EDGE_FALLING; |
|
break; |
|
case GPIO_GCR_INTCTRL_TRIG_EDGE_BOTH: |
|
cfg_flag |= GPIO_INT_EDGE_BOTH; |
|
break; |
|
case GPIO_GCR_INTCTRL_TRIG_LEVEL_LOW: |
|
cfg_flag |= GPIO_INT_LEVEL_LOW; |
|
break; |
|
case GPIO_GCR_INTCTRL_TRIG_LEVEL_HIGH: |
|
cfg_flag |= GPIO_INT_LEVEL_HIGH; |
|
break; |
|
default: |
|
cfg_flag |= GPIO_INT_LEVEL_LOW; |
|
break; |
|
} |
|
} else { |
|
cfg_flag |= GPIO_INT_DISABLE; |
|
} |
|
|
|
*flags = cfg_flag; |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
static int gpio_rts5912_port_get_raw(const struct device *port, gpio_port_value_t *value) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
gpio_port_value_t ret_val = 0; |
|
uint16_t mask = 0x1U; |
|
|
|
for (gpio_pin_t i = 0; i < config->num_pins; i++) { |
|
if (config->reg_base[i] & GPIO_GCR_PINSTS_Msk) { |
|
ret_val |= (gpio_port_value_t)mask; |
|
} |
|
mask <<= 1; |
|
} |
|
|
|
*value = ret_val; |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_port_set_masked_raw(const struct device *port, gpio_port_pins_t mask, |
|
gpio_port_value_t value) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
uint32_t pin; |
|
|
|
mask &= 0x0000FFFF; |
|
for (; mask; mask &= ~BIT(pin)) { |
|
pin = find_lsb_set(mask) - 1; |
|
if (pin >= config->num_pins) { |
|
break; |
|
} |
|
|
|
if (value & BIT(pin)) { |
|
pin_output_high(port, pin); |
|
} else { |
|
pin_output_low(port, pin); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_port_set_bits_raw(const struct device *port, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = config->reg_base; |
|
uint32_t pin = 0; |
|
|
|
pins &= 0x0000FFFF; |
|
gpio_port_pins_t sel_pin = 1; |
|
|
|
for (; pins;) { |
|
if (pins & sel_pin) { |
|
pin_output_high(port, pin); |
|
} |
|
pins &= ~sel_pin; |
|
sel_pin <<= 1; |
|
gcr++; |
|
pin++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_port_clear_bits_raw(const struct device *port, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = config->reg_base; |
|
uint32_t pin = 0; |
|
|
|
pins &= 0x0000FFFF; |
|
gpio_port_pins_t sel_pin = 1; |
|
|
|
for (; pins;) { |
|
if (pins & sel_pin) { |
|
pin_output_low(port, pin); |
|
} |
|
pins &= ~sel_pin; |
|
sel_pin <<= 1; |
|
gcr++; |
|
pin++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_port_toggle_bits(const struct device *port, gpio_port_pins_t pins) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = config->reg_base; |
|
uint32_t pin = 0; |
|
|
|
pins &= 0x0000FFFF; |
|
gpio_port_pins_t sel_pin = 0x1UL; |
|
|
|
for (; pins;) { |
|
if (pins & sel_pin) { |
|
if (*gcr & GPIO_GCR_OUTCTRL_Msk) { |
|
pin_output_low(port, pin); |
|
} else { |
|
pin_output_high(port, pin); |
|
} |
|
} |
|
|
|
pins &= ~sel_pin; |
|
sel_pin <<= 1; |
|
gcr++; |
|
pin++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static gpio_pin_t gpio_rts5912_get_intr_pin(volatile uint32_t *reg_base) |
|
{ |
|
gpio_pin_t pin = 0U; |
|
|
|
for (; pin < 16; pin++) { |
|
if (reg_base[pin] & GPIO_GCR_INTSTS_Msk) { |
|
break; |
|
} |
|
} |
|
|
|
return pin; |
|
} |
|
|
|
static void gpio_rts5912_isr(const void *arg) |
|
{ |
|
const struct device *port = arg; |
|
const struct gpio_rts5912_config *config = port->config; |
|
struct gpio_rts5912_data *data = port->data; |
|
volatile uint32_t *gcr = config->reg_base; |
|
unsigned int key = irq_lock(); |
|
gpio_pin_t pin = gpio_rts5912_get_intr_pin(gcr); |
|
|
|
if (gcr[pin] & GPIO_GCR_INTSTS_Msk) { |
|
gcr[pin] |= GPIO_GCR_INTSTS_Msk; |
|
|
|
gpio_fire_callbacks(&data->callbacks, port, BIT(pin)); |
|
} |
|
irq_unlock(key); |
|
} |
|
|
|
static int gpio_rts5912_intr_config(const struct device *port, gpio_pin_t pin, |
|
enum gpio_int_mode mode, enum gpio_int_trig trig) |
|
{ |
|
const struct gpio_rts5912_config *config = port->config; |
|
volatile uint32_t *gcr = &config->reg_base[pin]; |
|
uint32_t cfg_val = *gcr; |
|
uint32_t pin_index = |
|
DT_IRQ_BY_IDX(DT_NODELABEL(gpioa), 0, irq) + |
|
((uint32_t)(&config->reg_base[pin]) - (uint32_t)(RTS5912_GPIOA_REG_BASE)) / 4; |
|
|
|
int err = pin_is_valid(config, pin); |
|
|
|
if (err) { |
|
return err; |
|
} |
|
|
|
switch (mode) { |
|
case GPIO_INT_MODE_DISABLED: |
|
cfg_val &= ~GPIO_GCR_INTEN_Msk; |
|
irq_disable(pin_index); |
|
*gcr = cfg_val; |
|
return 0; |
|
case GPIO_INT_MODE_LEVEL: |
|
switch (trig) { |
|
case GPIO_INT_TRIG_LOW: |
|
cfg_val &= ~GPIO_GCR_INTCTRL_Msk; |
|
cfg_val |= GPIO_GCR_INTCTRL_TRIG_LEVEL_LOW; |
|
break; |
|
case GPIO_INT_TRIG_HIGH: |
|
cfg_val &= ~GPIO_GCR_INTCTRL_Msk; |
|
cfg_val |= GPIO_GCR_INTCTRL_TRIG_LEVEL_HIGH; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
break; |
|
case GPIO_INT_MODE_EDGE: |
|
switch (trig) { |
|
case GPIO_INT_TRIG_LOW: |
|
cfg_val &= ~GPIO_GCR_INTCTRL_Msk; |
|
cfg_val |= GPIO_GCR_INTCTRL_TRIG_EDGE_LOW; |
|
break; |
|
case GPIO_INT_TRIG_HIGH: |
|
cfg_val &= ~GPIO_GCR_INTCTRL_Msk; |
|
cfg_val |= GPIO_GCR_INTCTRL_TRIG_EDGE_HIGH; |
|
break; |
|
case GPIO_INT_TRIG_BOTH: |
|
cfg_val &= ~GPIO_GCR_INTCTRL_Msk; |
|
cfg_val |= GPIO_GCR_INTCTRL_TRIG_EDGE_BOTH; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
/* enable interrupt */ |
|
cfg_val |= GPIO_GCR_INTEN_Msk; |
|
/* set value to GPIO register */ |
|
*gcr = cfg_val; |
|
|
|
irq_enable(pin_index); |
|
|
|
return 0; |
|
} |
|
|
|
static int gpio_rts5912_manage_cb(const struct device *port, struct gpio_callback *cb, bool set) |
|
{ |
|
struct gpio_rts5912_data *data = port->data; |
|
|
|
return gpio_manage_callback(&data->callbacks, cb, set); |
|
} |
|
|
|
static DEVICE_API(gpio, gpio_rts5912_driver_api) = { |
|
.pin_configure = gpio_rts5912_configuration, |
|
#ifdef CONFIG_GPIO_GET_CONFIG |
|
.pin_get_config = gpio_rts5912_get_configuration, |
|
#endif |
|
.port_get_raw = gpio_rts5912_port_get_raw, |
|
.port_set_masked_raw = gpio_rts5912_port_set_masked_raw, |
|
.port_set_bits_raw = gpio_rts5912_port_set_bits_raw, |
|
.port_clear_bits_raw = gpio_rts5912_port_clear_bits_raw, |
|
.port_toggle_bits = gpio_rts5912_port_toggle_bits, |
|
.pin_interrupt_configure = gpio_rts5912_intr_config, |
|
.manage_callback = gpio_rts5912_manage_cb, |
|
}; |
|
|
|
#ifdef CONFIG_GEN_ISR_TABLES |
|
#define RTS5912_GPIO_DTNAMIC_IRQ(id) \ |
|
for (int i = 0; i < 16 && (DT_INST_IRQ_BY_IDX(id, 0, irq) + i) < 132; i++) { \ |
|
irq_connect_dynamic((DT_INST_IRQ_BY_IDX(id, 0, irq) + i), \ |
|
DT_INST_IRQ(id, priority), gpio_rts5912_isr, \ |
|
DEVICE_DT_INST_GET(id), 0U); \ |
|
} |
|
#else |
|
#define RTS5912_GPIO_DTNAMIC_IRQ(id) \ |
|
IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), gpio_rts5912_isr, \ |
|
DEVICE_DT_INST_GET(id), 0U); |
|
#endif |
|
|
|
#define GPIO_RTS5912_INIT(id) \ |
|
static int gpio_rts5912_init_##id(const struct device *dev) \ |
|
{ \ |
|
if (!(DT_INST_IRQ_HAS_CELL(id, irq))) { \ |
|
return 0; \ |
|
} \ |
|
\ |
|
RTS5912_GPIO_DTNAMIC_IRQ(id) \ |
|
\ |
|
return 0; \ |
|
} \ |
|
\ |
|
static struct gpio_rts5912_data gpio_rts5912_data_##id; \ |
|
\ |
|
static const struct gpio_rts5912_config gpio_rts5912_config_##id = { \ |
|
.common = {.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(id)}, \ |
|
.reg_base = (volatile uint32_t *)DT_INST_REG_ADDR(id), \ |
|
.num_pins = DT_INST_PROP(id, ngpios), \ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(id, gpio_rts5912_init_##id, NULL, &gpio_rts5912_data_##id, \ |
|
&gpio_rts5912_config_##id, POST_KERNEL, CONFIG_GPIO_INIT_PRIORITY, \ |
|
&gpio_rts5912_driver_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(GPIO_RTS5912_INIT)
|
|
|