| /* |
| * Copyright (c) 2017 Intel Corporation |
| * Copyright (c) 2021 Espressif Systems (Shanghai) Co., Ltd. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_esp32_gpio |
| |
| /* Include esp-idf headers first to avoid redefining BIT() macro */ |
| #include <soc/gpio_reg.h> |
| #include <soc/io_mux_reg.h> |
| #include <soc/soc.h> |
| #include <hal/gpio_ll.h> |
| #include <esp_attr.h> |
| #include <hal/rtc_io_hal.h> |
| |
| #include <soc.h> |
| #include <errno.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/dt-bindings/gpio/espressif-esp32-gpio.h> |
| #if defined(CONFIG_SOC_SERIES_ESP32C2) || \ |
| defined(CONFIG_SOC_SERIES_ESP32C3) || \ |
| defined(CONFIG_SOC_SERIES_ESP32C6) |
| #include <zephyr/drivers/interrupt_controller/intc_esp32c3.h> |
| #else |
| #include <zephyr/drivers/interrupt_controller/intc_esp32.h> |
| #endif |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/util.h> |
| |
| #include <zephyr/drivers/gpio/gpio_utils.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(gpio_esp32, CONFIG_LOG_DEFAULT_LEVEL); |
| |
| #ifdef CONFIG_SOC_SERIES_ESP32C2 |
| #define out out.val |
| #define in in.val |
| #define out_w1ts out_w1ts.val |
| #define out_w1tc out_w1tc.val |
| /* arch_curr_cpu() is not available for riscv based chips */ |
| #define CPU_ID() 0 |
| #define ISR_HANDLER isr_handler_t |
| #elif CONFIG_SOC_SERIES_ESP32C3 |
| /* gpio structs in esp32c3 series are different from xtensa ones */ |
| #define out out.data |
| #define in in.data |
| #define out_w1ts out_w1ts.val |
| #define out_w1tc out_w1tc.val |
| /* arch_curr_cpu() is not available for riscv based chips */ |
| #define CPU_ID() 0 |
| #define ISR_HANDLER isr_handler_t |
| #elif defined(CONFIG_SOC_SERIES_ESP32C6) |
| /* gpio structs in esp32c6 are also different */ |
| #define out out.out_data_orig |
| #define in in.in_data_next |
| #define out_w1ts out_w1ts.val |
| #define out_w1tc out_w1tc.val |
| /* arch_curr_cpu() is not available for riscv based chips */ |
| #define CPU_ID() 0 |
| #define ISR_HANDLER isr_handler_t |
| #else |
| #define CPU_ID() arch_curr_cpu()->id |
| #define ISR_HANDLER intr_handler_t |
| #endif |
| |
| #ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT |
| #define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0 |
| #endif |
| |
| struct gpio_esp32_config { |
| /* gpio_driver_config needs to be first */ |
| struct gpio_driver_config drv_cfg; |
| gpio_dev_t *const gpio_base; |
| gpio_dev_t *const gpio_dev; |
| const int gpio_port; |
| }; |
| |
| struct gpio_esp32_data { |
| /* gpio_driver_data needs to be first */ |
| struct gpio_driver_data common; |
| sys_slist_t cb; |
| }; |
| |
| static inline bool rtc_gpio_is_valid_gpio(uint32_t gpio_num) |
| { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| return (gpio_num < SOC_GPIO_PIN_COUNT && rtc_io_num_map[gpio_num] >= 0); |
| #else |
| return false; |
| #endif |
| } |
| |
| static inline bool gpio_pin_is_valid(uint32_t pin) |
| { |
| return ((BIT(pin) & SOC_GPIO_VALID_GPIO_MASK) != 0); |
| } |
| |
| static inline bool gpio_pin_is_output_capable(uint32_t pin) |
| { |
| return ((BIT(pin) & SOC_GPIO_VALID_OUTPUT_GPIO_MASK) != 0); |
| } |
| |
| static int gpio_esp32_config(const struct device *dev, |
| gpio_pin_t pin, |
| gpio_flags_t flags) |
| { |
| const struct gpio_esp32_config *const cfg = dev->config; |
| uint32_t io_pin = (uint32_t) pin + ((cfg->gpio_port == 1 && pin < 32) ? 32 : 0); |
| uint32_t key; |
| int ret = 0; |
| |
| if (!gpio_pin_is_valid(io_pin)) { |
| LOG_ERR("Selected IO pin is not valid."); |
| return -EINVAL; |
| } |
| |
| key = irq_lock(); |
| |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| if (rtc_gpio_is_valid_gpio(io_pin)) { |
| rtcio_hal_function_select(rtc_io_num_map[io_pin], RTCIO_FUNC_DIGITAL); |
| } |
| #endif |
| |
| if (io_pin >= GPIO_NUM_MAX) { |
| LOG_ERR("Invalid pin."); |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| /* Set pin function as GPIO */ |
| gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[io_pin], PIN_FUNC_GPIO); |
| |
| if (flags & GPIO_PULL_UP) { |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_pullup_en(&GPIO, io_pin); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| int rtcio_num = rtc_io_num_map[io_pin]; |
| |
| if (rtc_io_desc[rtcio_num].pullup) { |
| rtcio_hal_pullup_enable(rtcio_num); |
| } else { |
| ret = -ENOTSUP; |
| goto end; |
| } |
| #endif |
| } |
| } else { |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_pullup_dis(&GPIO, io_pin); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| int rtcio_num = rtc_io_num_map[io_pin]; |
| |
| if (rtc_io_desc[rtcio_num].pullup) { |
| rtcio_hal_pullup_disable(rtcio_num); |
| } |
| #else |
| ret = -ENOTSUP; |
| goto end; |
| #endif |
| } |
| } |
| |
| if (flags & GPIO_SINGLE_ENDED) { |
| if (flags & GPIO_LINE_OPEN_DRAIN) { |
| gpio_ll_od_enable(cfg->gpio_base, io_pin); |
| } else { |
| LOG_ERR("GPIO configuration not supported"); |
| ret = -ENOTSUP; |
| goto end; |
| } |
| } else { |
| gpio_ll_od_disable(cfg->gpio_base, io_pin); |
| } |
| |
| if (flags & GPIO_PULL_DOWN) { |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_pulldown_en(&GPIO, io_pin); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| int rtcio_num = rtc_io_num_map[io_pin]; |
| |
| if (rtc_io_desc[rtcio_num].pulldown) { |
| rtcio_hal_pulldown_enable(rtcio_num); |
| } else { |
| ret = -ENOTSUP; |
| goto end; |
| } |
| #endif |
| } |
| } else { |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_pulldown_dis(&GPIO, io_pin); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| int rtcio_num = rtc_io_num_map[io_pin]; |
| |
| if (rtc_io_desc[rtcio_num].pulldown) { |
| rtcio_hal_pulldown_disable(rtcio_num); |
| } |
| #else |
| ret = -ENOTSUP; |
| goto end; |
| #endif |
| } |
| } |
| |
| if (flags & GPIO_OUTPUT) { |
| |
| if (!gpio_pin_is_output_capable(pin)) { |
| LOG_ERR("GPIO can only be used as input"); |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| /* |
| * By default, drive strength is set to its maximum value when the pin is set |
| * to either low or high states. Alternative drive strength is weak-only, |
| * while any other intermediary combination is considered invalid. |
| */ |
| switch (flags & ESP32_GPIO_DS_MASK) { |
| case ESP32_GPIO_DS_DFLT: |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_set_drive_capability(cfg->gpio_base, |
| io_pin, |
| GPIO_DRIVE_CAP_3); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| rtcio_hal_set_drive_capability(rtc_io_num_map[io_pin], |
| GPIO_DRIVE_CAP_3); |
| #endif |
| } |
| break; |
| case ESP32_GPIO_DS_ALT: |
| if (!rtc_gpio_is_valid_gpio(io_pin) || SOC_GPIO_SUPPORT_RTC_INDEPENDENT) { |
| gpio_ll_set_drive_capability(cfg->gpio_base, |
| io_pin, |
| GPIO_DRIVE_CAP_0); |
| } else { |
| #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED |
| rtcio_hal_set_drive_capability(rtc_io_num_map[io_pin], |
| GPIO_DRIVE_CAP_0); |
| #endif |
| } |
| break; |
| default: |
| ret = -EINVAL; |
| goto end; |
| } |
| |
| gpio_ll_output_enable(&GPIO, io_pin); |
| esp_rom_gpio_matrix_out(io_pin, SIG_GPIO_OUT_IDX, false, false); |
| |
| /* Set output pin initial value */ |
| if (flags & GPIO_OUTPUT_INIT_HIGH) { |
| gpio_ll_set_level(cfg->gpio_base, io_pin, 1); |
| } else if (flags & GPIO_OUTPUT_INIT_LOW) { |
| gpio_ll_set_level(cfg->gpio_base, io_pin, 0); |
| } |
| } else { |
| if (!(flags & ESP32_GPIO_PIN_OUT_EN)) { |
| gpio_ll_output_disable(&GPIO, io_pin); |
| } |
| } |
| |
| if (flags & GPIO_INPUT) { |
| gpio_ll_input_enable(&GPIO, io_pin); |
| } else { |
| if (!(flags & ESP32_GPIO_PIN_IN_EN)) { |
| gpio_ll_input_disable(&GPIO, io_pin); |
| } |
| } |
| |
| end: |
| irq_unlock(key); |
| |
| return ret; |
| } |
| |
| static int gpio_esp32_port_get_raw(const struct device *port, uint32_t *value) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| |
| if (cfg->gpio_port == 0) { |
| *value = cfg->gpio_dev->in; |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| } else { |
| *value = cfg->gpio_dev->in1.data; |
| #endif |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_port_set_masked_raw(const struct device *port, |
| uint32_t mask, uint32_t value) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| |
| uint32_t key = irq_lock(); |
| |
| if (cfg->gpio_port == 0) { |
| cfg->gpio_dev->out = (cfg->gpio_dev->out & ~mask) | (mask & value); |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| } else { |
| cfg->gpio_dev->out1.data = (cfg->gpio_dev->out1.data & ~mask) | (mask & value); |
| #endif |
| } |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_port_set_bits_raw(const struct device *port, |
| uint32_t pins) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| |
| if (cfg->gpio_port == 0) { |
| cfg->gpio_dev->out_w1ts = pins; |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| } else { |
| cfg->gpio_dev->out1_w1ts.data = pins; |
| #endif |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_port_clear_bits_raw(const struct device *port, |
| uint32_t pins) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| |
| if (cfg->gpio_port == 0) { |
| cfg->gpio_dev->out_w1tc = pins; |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| } else { |
| cfg->gpio_dev->out1_w1tc.data = pins; |
| #endif |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_port_toggle_bits(const struct device *port, |
| uint32_t pins) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| uint32_t key = irq_lock(); |
| |
| if (cfg->gpio_port == 0) { |
| cfg->gpio_dev->out ^= pins; |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| } else { |
| cfg->gpio_dev->out1.data ^= pins; |
| #endif |
| } |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int convert_int_type(enum gpio_int_mode mode, |
| enum gpio_int_trig trig) |
| { |
| if (mode == GPIO_INT_MODE_DISABLED) { |
| return GPIO_INTR_DISABLE; |
| } |
| |
| if (mode == GPIO_INT_MODE_LEVEL) { |
| switch (trig) { |
| case GPIO_INT_TRIG_LOW: |
| return GPIO_INTR_LOW_LEVEL; |
| case GPIO_INT_TRIG_HIGH: |
| return GPIO_INTR_HIGH_LEVEL; |
| default: |
| return -EINVAL; |
| } |
| } else { /* edge interrupts */ |
| switch (trig) { |
| case GPIO_INT_TRIG_HIGH: |
| return GPIO_INTR_POSEDGE; |
| case GPIO_INT_TRIG_LOW: |
| return GPIO_INTR_NEGEDGE; |
| case GPIO_INT_TRIG_BOTH: |
| return GPIO_INTR_ANYEDGE; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| /* Any other type of interrupt triggering is invalid. */ |
| return -EINVAL; |
| } |
| |
| static int gpio_esp32_pin_interrupt_configure(const struct device *port, |
| gpio_pin_t pin, |
| enum gpio_int_mode mode, |
| enum gpio_int_trig trig) |
| { |
| const struct gpio_esp32_config *const cfg = port->config; |
| uint32_t io_pin = (uint32_t) pin + ((cfg->gpio_port == 1 && pin < 32) ? 32 : 0); |
| int intr_trig_mode = convert_int_type(mode, trig); |
| uint32_t key; |
| |
| if (intr_trig_mode < 0) { |
| return intr_trig_mode; |
| } |
| |
| key = irq_lock(); |
| if (cfg->gpio_port == 0) { |
| gpio_ll_clear_intr_status(cfg->gpio_base, BIT(pin)); |
| } else { |
| gpio_ll_clear_intr_status_high(cfg->gpio_base, BIT(pin)); |
| } |
| |
| gpio_ll_set_intr_type(cfg->gpio_base, io_pin, intr_trig_mode); |
| gpio_ll_intr_enable_on_core(cfg->gpio_base, CPU_ID(), io_pin); |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_manage_callback(const struct device *dev, |
| struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_esp32_data *data = dev->data; |
| |
| return gpio_manage_callback(&data->cb, callback, set); |
| } |
| |
| static uint32_t gpio_esp32_get_pending_int(const struct device *dev) |
| { |
| const struct gpio_esp32_config *const cfg = dev->config; |
| uint32_t irq_status; |
| uint32_t const core_id = CPU_ID(); |
| |
| if (cfg->gpio_port == 0) { |
| gpio_ll_get_intr_status(cfg->gpio_base, core_id, &irq_status); |
| } else { |
| gpio_ll_get_intr_status_high(cfg->gpio_base, core_id, &irq_status); |
| } |
| |
| return irq_status; |
| } |
| |
| static void IRAM_ATTR gpio_esp32_fire_callbacks(const struct device *dev) |
| { |
| const struct gpio_esp32_config *const cfg = dev->config; |
| struct gpio_esp32_data *data = dev->data; |
| uint32_t irq_status; |
| uint32_t const core_id = CPU_ID(); |
| |
| if (cfg->gpio_port == 0) { |
| gpio_ll_get_intr_status(cfg->gpio_base, core_id, &irq_status); |
| gpio_ll_clear_intr_status(cfg->gpio_base, irq_status); |
| } else { |
| gpio_ll_get_intr_status_high(cfg->gpio_base, core_id, &irq_status); |
| gpio_ll_clear_intr_status_high(cfg->gpio_base, irq_status); |
| } |
| |
| if (irq_status != 0) { |
| gpio_fire_callbacks(&data->cb, dev, irq_status); |
| } |
| } |
| |
| static void gpio_esp32_isr(void *param); |
| |
| static int gpio_esp32_init(const struct device *dev) |
| { |
| static bool isr_connected; |
| |
| if (!isr_connected) { |
| int ret = esp_intr_alloc(DT_IRQ_BY_IDX(DT_NODELABEL(gpio0), 0, irq), |
| ESP_PRIO_TO_FLAGS(DT_IRQ_BY_IDX(DT_NODELABEL(gpio0), 0, priority)) | |
| ESP_INT_FLAGS_CHECK(DT_IRQ_BY_IDX(DT_NODELABEL(gpio0), 0, flags)), |
| (ISR_HANDLER)gpio_esp32_isr, |
| (void *)dev, |
| NULL); |
| |
| if (ret != 0) { |
| LOG_ERR("could not allocate interrupt (err %d)", ret); |
| return ret; |
| } |
| |
| isr_connected = true; |
| } |
| |
| return 0; |
| } |
| |
| static const struct gpio_driver_api gpio_esp32_driver_api = { |
| .pin_configure = gpio_esp32_config, |
| .port_get_raw = gpio_esp32_port_get_raw, |
| .port_set_masked_raw = gpio_esp32_port_set_masked_raw, |
| .port_set_bits_raw = gpio_esp32_port_set_bits_raw, |
| .port_clear_bits_raw = gpio_esp32_port_clear_bits_raw, |
| .port_toggle_bits = gpio_esp32_port_toggle_bits, |
| .pin_interrupt_configure = gpio_esp32_pin_interrupt_configure, |
| .manage_callback = gpio_esp32_manage_callback, |
| .get_pending_int = gpio_esp32_get_pending_int |
| }; |
| |
| #define ESP_SOC_GPIO_INIT(_id) \ |
| static struct gpio_esp32_data gpio_data_##_id; \ |
| static struct gpio_esp32_config gpio_config_##_id = { \ |
| .drv_cfg = { \ |
| .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(_id), \ |
| }, \ |
| .gpio_base = (gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio0)), \ |
| .gpio_dev = (gpio_dev_t *)DT_REG_ADDR(DT_NODELABEL(gpio##_id)), \ |
| .gpio_port = _id \ |
| }; \ |
| DEVICE_DT_DEFINE(DT_NODELABEL(gpio##_id), \ |
| &gpio_esp32_init, \ |
| NULL, \ |
| &gpio_data_##_id, \ |
| &gpio_config_##_id, \ |
| PRE_KERNEL_1, \ |
| CONFIG_GPIO_INIT_PRIORITY, \ |
| &gpio_esp32_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(ESP_SOC_GPIO_INIT); |
| |
| static void IRAM_ATTR gpio_esp32_isr(void *param) |
| { |
| ARG_UNUSED(param); |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio0)) |
| gpio_esp32_fire_callbacks(DEVICE_DT_INST_GET(0)); |
| #endif |
| |
| #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gpio1)) |
| gpio_esp32_fire_callbacks(DEVICE_DT_INST_GET(1)); |
| #endif |
| } |