| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <rom/ets_sys.h> |
| #include <soc/dport_reg.h> |
| #include <soc/gpio_reg.h> |
| #include <soc/soc.h> |
| #include <errno.h> |
| #include <device.h> |
| #include <gpio.h> |
| #include <kernel.h> |
| #include <misc/util.h> |
| #include <pinmux.h> |
| |
| #include "gpio_utils.h" |
| |
| struct gpio_esp32_data { |
| struct device *pinmux; |
| |
| struct { |
| struct { |
| volatile u32_t *set_reg; |
| volatile u32_t *clear_reg; |
| } write; |
| struct { |
| volatile u32_t *reg; |
| } read; |
| struct { |
| volatile u32_t *status_reg; |
| volatile u32_t *ack_reg; |
| } irq; |
| int pin_offset; |
| } port; |
| |
| u32_t cb_pins; |
| sys_slist_t cb; |
| }; |
| |
| static int convert_int_type(int flags) |
| { |
| /* Reference: "ESP32 Technical Reference Manual", "IO_MUX and |
| * GPIO matrix"; "GPIO_PINn_INT_TYPE". |
| */ |
| |
| if (!(flags & GPIO_INT)) { |
| return 0; /* Disables interrupt for a pin. */ |
| } |
| |
| if ((flags & GPIO_INT_EDGE) == GPIO_INT_EDGE) { |
| if ((flags & GPIO_INT_ACTIVE_LOW) == GPIO_INT_ACTIVE_LOW) { |
| return 2; |
| } |
| |
| if ((flags & GPIO_INT_DOUBLE_EDGE) == GPIO_INT_DOUBLE_EDGE) { |
| return 3; |
| } |
| |
| return 1; /* Defaults to rising edge. */ |
| } |
| |
| if ((flags & GPIO_INT_LEVEL) == GPIO_INT_LEVEL) { |
| if ((flags & GPIO_INT_ACTIVE_LOW) == GPIO_INT_ACTIVE_LOW) { |
| return 4; |
| } |
| |
| return 5; /* Defaults to high level. */ |
| } |
| |
| /* Any other type of interrupt triggering is invalid. */ |
| return -EINVAL; |
| } |
| |
| static inline u32_t *gpio_pin_reg(int pin) |
| { |
| return (u32_t *)(GPIO_PIN0_REG + pin * 4); |
| } |
| |
| static int config_interrupt(u32_t pin, int flags) |
| { |
| volatile u32_t *reg = gpio_pin_reg(pin); |
| int type = convert_int_type(flags); |
| u32_t v; |
| int key; |
| |
| if (type < 0) { |
| return type; |
| } |
| |
| key = irq_lock(); |
| |
| v = *reg; |
| v &= ~(GPIO_PIN_INT_ENA_M | GPIO_PIN_INT_TYPE_M); |
| /* Bit 3 of INT_ENA will enable interrupts on CPU 0 */ |
| v |= (1<<2) << GPIO_PIN_INT_ENA_S; |
| /* Interrupt triggering mode */ |
| v |= type << GPIO_PIN_INT_TYPE_S; |
| *reg = v; |
| |
| irq_unlock(key); |
| |
| return 0; |
| } |
| |
| static void config_polarity(u32_t pin, int flags) |
| { |
| volatile u32_t *reg = (u32_t *)(GPIO_FUNC0_IN_SEL_CFG_REG + 4 * pin); |
| |
| if (flags & GPIO_POL_INV) { |
| *reg |= BIT(GPIO_FUNC0_IN_INV_SEL_S); |
| } else { |
| *reg &= ~BIT(GPIO_FUNC0_IN_INV_SEL_S); |
| } |
| } |
| |
| static int gpio_esp32_config(struct device *dev, int access_op, |
| u32_t pin, int flags) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| u32_t func; |
| int r; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| /* Query pinmux to validate pin number. */ |
| r = pinmux_pin_get(data->pinmux, pin, &func); |
| if (r < 0) { |
| return r; |
| } |
| |
| pinmux_pin_set(data->pinmux, pin, PINMUX_FUNC_A); |
| if (flags & GPIO_PUD_PULL_UP) { |
| pinmux_pin_pullup(data->pinmux, pin, PINMUX_PULLUP_ENABLE); |
| } else if (flags & GPIO_PUD_PULL_DOWN) { |
| pinmux_pin_pullup(data->pinmux, pin, PINMUX_PULLUP_DISABLE); |
| } |
| |
| if (flags & GPIO_DIR_OUT) { |
| pinmux_pin_input_enable(data->pinmux, pin, |
| PINMUX_OUTPUT_ENABLED); |
| } else { |
| pinmux_pin_input_enable(data->pinmux, pin, |
| PINMUX_INPUT_ENABLED); |
| config_polarity(pin, flags); |
| } |
| |
| return config_interrupt(pin, flags); |
| } |
| |
| static int gpio_esp32_write(struct device *dev, int access_op, |
| u32_t pin, u32_t value) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| u32_t v; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| v = BIT(pin - data->port.pin_offset); |
| if (value) { |
| *data->port.write.set_reg = v; |
| } else { |
| *data->port.write.clear_reg = v; |
| } |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_read(struct device *dev, int access_op, |
| u32_t pin, u32_t *value) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| u32_t v; |
| |
| if (access_op != GPIO_ACCESS_BY_PIN) { |
| return -ENOTSUP; |
| } |
| |
| v = *data->port.read.reg; |
| *value = !!(v & BIT(pin - data->port.pin_offset)); |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_manage_callback(struct device *dev, |
| struct gpio_callback *callback, |
| bool set) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| |
| _gpio_manage_callback(&data->cb, callback, set); |
| |
| return 0; |
| } |
| |
| static int gpio_esp32_enable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| |
| if (access_op == GPIO_ACCESS_BY_PIN) { |
| data->cb_pins |= BIT(pin); |
| |
| return 0; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int gpio_esp32_disable_callback(struct device *dev, |
| int access_op, u32_t pin) |
| { |
| struct gpio_esp32_data *data = dev->driver_data; |
| |
| if (access_op == GPIO_ACCESS_BY_PIN) { |
| data->cb_pins &= ~BIT(pin); |
| |
| return 0; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void gpio_esp32_fire_callbacks(struct device *device) |
| { |
| struct gpio_esp32_data *data = device->driver_data; |
| u32_t values = *data->port.irq.status_reg; |
| |
| if (values & data->cb_pins) { |
| _gpio_fire_callbacks(&data->cb, device, values); |
| } |
| |
| *data->port.irq.ack_reg = values; |
| } |
| |
| static void gpio_esp32_isr(void *param); |
| |
| static int gpio_esp32_init(struct device *device) |
| { |
| struct gpio_esp32_data *data = device->driver_data; |
| static bool isr_connected; |
| |
| data->pinmux = device_get_binding(CONFIG_PINMUX_NAME); |
| if (!data->pinmux) { |
| return -ENOTSUP; |
| } |
| |
| if (!isr_connected) { |
| irq_disable(CONFIG_GPIO_ESP32_IRQ); |
| |
| IRQ_CONNECT(CONFIG_GPIO_ESP32_IRQ, 1, gpio_esp32_isr, |
| NULL, 0); |
| |
| intr_matrix_set(0, ETS_GPIO_INTR_SOURCE, |
| CONFIG_GPIO_ESP32_IRQ); |
| |
| irq_enable(CONFIG_GPIO_ESP32_IRQ); |
| |
| isr_connected = true; |
| } |
| |
| return 0; |
| } |
| |
| static const struct gpio_driver_api gpio_esp32_driver = { |
| .config = gpio_esp32_config, |
| .write = gpio_esp32_write, |
| .read = gpio_esp32_read, |
| .manage_callback = gpio_esp32_manage_callback, |
| .enable_callback = gpio_esp32_enable_callback, |
| .disable_callback = gpio_esp32_disable_callback, |
| }; |
| |
| static struct gpio_esp32_data gpio_data_pins_0_to_31 = { |
| .port = { |
| .write = { |
| .set_reg = (u32_t *)GPIO_OUT_W1TS_REG, |
| .clear_reg = (u32_t *)GPIO_OUT_W1TC_REG, |
| }, |
| .read = { |
| .reg = (u32_t *)GPIO_IN_REG, |
| }, |
| .irq = { |
| .status_reg = (u32_t *)GPIO_STATUS_REG, |
| .ack_reg = (u32_t *)GPIO_STATUS_W1TC_REG, |
| }, |
| .pin_offset = 0, |
| } |
| }; |
| static struct gpio_esp32_data gpio_data_pins_32_to_63 = { |
| .port = { |
| .write = { |
| .set_reg = (u32_t *)GPIO_OUT1_W1TS_REG, |
| .clear_reg = (u32_t *)GPIO_OUT1_W1TC_REG, |
| }, |
| .read = { |
| .reg = (u32_t *)GPIO_IN1_REG, |
| }, |
| .irq = { |
| .status_reg = (u32_t *)GPIO_STATUS1_REG, |
| .ack_reg = (u32_t *)GPIO_STATUS1_W1TC_REG, |
| }, |
| .pin_offset = 32, |
| } |
| }; |
| |
| #define GPIO_DEVICE_INIT(__name, __data_struct_name) \ |
| DEVICE_AND_API_INIT(gpio_esp32_ ## __data_struct_name, \ |
| __name, \ |
| gpio_esp32_init, \ |
| &gpio_data_pins_ ## __data_struct_name, \ |
| NULL, \ |
| POST_KERNEL, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &gpio_esp32_driver) |
| |
| /* GPIOs are divided in two groups for ESP32 because the callback |
| * API works with 32-bit bitmasks to manage interrupt callbacks, |
| * and the device has 40 GPIO pins. |
| */ |
| #if defined(CONFIG_GPIO_ESP32_0) |
| GPIO_DEVICE_INIT(CONFIG_GPIO_ESP32_0_NAME, 0_to_31); |
| #endif |
| |
| #if defined(CONFIG_GPIO_ESP32_1) |
| GPIO_DEVICE_INIT(CONFIG_GPIO_ESP32_1_NAME, 32_to_63); |
| #endif |
| |
| static void gpio_esp32_isr(void *param) |
| { |
| #if defined(CONFIG_GPIO_ESP32_0) |
| gpio_esp32_fire_callbacks(DEVICE_GET(gpio_esp32_0_to_31)); |
| #endif |
| #if defined(CONFIG_GPIO_ESP32_1) |
| gpio_esp32_fire_callbacks(DEVICE_GET(gpio_esp32_32_to_63)); |
| #endif |
| |
| ARG_UNUSED(param); |
| } |