|  | /* | 
|  | * 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> | 
|  | #ifdef CONFIG_SOC_SERIES_ESP32C3 | 
|  | #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_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 | 
|  | #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; | 
|  | struct gpio_esp32_data *data = dev->data; | 
|  | 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 { | 
|  | gpio_ll_output_disable(&GPIO, io_pin); | 
|  | } | 
|  |  | 
|  | if (flags & GPIO_INPUT) { | 
|  | gpio_ll_input_enable(&GPIO, io_pin); | 
|  | } else { | 
|  | 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(DT_NODELABEL(gpio1), okay) | 
|  | } 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(DT_NODELABEL(gpio1), okay) | 
|  | } 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(DT_NODELABEL(gpio1), okay) | 
|  | } 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(DT_NODELABEL(gpio1), okay) | 
|  | } 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(DT_NODELABEL(gpio1), okay) | 
|  | } 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) | 
|  | { | 
|  | struct gpio_esp32_data *data = dev->data; | 
|  | static bool isr_connected; | 
|  |  | 
|  | if (!isr_connected) { | 
|  | esp_intr_alloc(DT_IRQN(DT_NODELABEL(gpio0)), | 
|  | 0, | 
|  | (ISR_HANDLER)gpio_esp32_isr, | 
|  | (void *)dev, | 
|  | NULL); | 
|  |  | 
|  | 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(DT_NODELABEL(gpio0), okay) | 
|  | gpio_esp32_fire_callbacks(DEVICE_DT_INST_GET(0)); | 
|  | #endif | 
|  |  | 
|  | #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpio1), okay) | 
|  | gpio_esp32_fire_callbacks(DEVICE_DT_INST_GET(1)); | 
|  | #endif | 
|  | } |