zephyr/drivers/gpio/gpio_esp32.c
Tomasz Bursztyka 064f5f0cef drivers/gpio: Manage callback addition/removal properly
It needs to verify if the callback was not already installed, and if so:
if is was in controller's list.
It should return an error in case the node is not found though it was
requested to be removed.
If already inserted, it will be silently removed but added again, to
avoid circular list as stated in the bug.

Fixes #11394

Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
2019-01-25 11:24:29 -05:00

364 lines
7.6 KiB
C

/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Include esp-idf headers first to avoid redefining BIT() macro */
#include <soc/dport_reg.h>
#include <soc/gpio_reg.h>
#include <soc/io_mux_reg.h>
#include <soc/soc.h>
#include <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_HIGH) == GPIO_INT_ACTIVE_HIGH) {
return 1;
}
if ((flags & GPIO_INT_DOUBLE_EDGE) == GPIO_INT_DOUBLE_EDGE) {
return 3;
}
return 2; /* Defaults to falling edge. */
}
if ((flags & GPIO_INT_LEVEL) == GPIO_INT_LEVEL) {
if ((flags & GPIO_INT_ACTIVE_HIGH) == GPIO_INT_ACTIVE_HIGH) {
return 5;
}
return 4; /* Defaults to low 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;
unsigned 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 void config_drive_strength(u32_t pin, int flags)
{
volatile u32_t *reg = gpio_pin_reg(pin);
if ((flags & GPIO_DS_DISCONNECT_LOW) == GPIO_DS_DISCONNECT_LOW) {
*reg |= GPIO_PIN_PAD_DRIVER;
} else {
*reg &= ~GPIO_PIN_PAD_DRIVER;
}
}
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, PIN_FUNC_GPIO);
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) {
r = pinmux_pin_input_enable(data->pinmux, pin,
PINMUX_OUTPUT_ENABLED);
assert(r >= 0);
} else {
pinmux_pin_input_enable(data->pinmux, pin,
PINMUX_INPUT_ENABLED);
config_polarity(pin, flags);
}
config_drive_strength(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;
return _gpio_manage_callback(&data->cb, callback, set);
}
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);
esp32_rom_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,
};
#if defined(CONFIG_GPIO_ESP32_0)
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,
}
};
#endif
#if defined(CONFIG_GPIO_ESP32_1)
static struct gpio_esp32_data gpio_data_pins_32_to_39 = {
.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,
}
};
#endif
#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_39);
#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_39));
#endif
ARG_UNUSED(param);
}