| /* |
| * 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) |