528 lines
13 KiB
C
528 lines
13 KiB
C
/*
|
|
* 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_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_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;
|
|
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
|
|
}
|