blob: 32578f1e16d6fffcbc2955a70c315b6a1b534fab [file] [log] [blame]
/*
* SPDX-License-Identifier: Apache-2.0
*
* GPIO driver for the CC2650 SOC from Texas Instruments.
*/
#include <toolchain/gcc.h>
#include <device.h>
#include <gpio.h>
#include <init.h>
#include <soc.h>
#include <sys_io.h>
#include "gpio_utils.h"
struct gpio_cc2650_data {
u32_t pin_callback_enables;
sys_slist_t callbacks;
};
/* Pre-declarations */
static int gpio_cc2650_init(struct device *dev);
static int gpio_cc2650_config(struct device *port, int access_op,
u32_t pin, int flags);
static int gpio_cc2650_write(struct device *port, int access_op,
u32_t pin, u32_t value);
static int gpio_cc2650_read(struct device *port, int access_op,
u32_t pin, u32_t *value);
static int gpio_cc2650_manage_callback(struct device *port,
struct gpio_callback *callback,
bool set);
static int gpio_cc2650_enable_callback(struct device *port,
int access_op,
u32_t pin);
static int gpio_cc2650_disable_callback(struct device *port,
int access_op,
u32_t pin);
static u32_t gpio_cc2650_get_pending_int(struct device *dev);
/* GPIO registers */
static const u32_t doutset31_0 =
REG_ADDR(TI_CC2650_GPIO_40022000_BASE_ADDRESS,
CC2650_GPIO_DOUTSET31_0);
static const u32_t doutclr31_0 =
REG_ADDR(TI_CC2650_GPIO_40022000_BASE_ADDRESS,
CC2650_GPIO_DOUTCLR31_0);
static const u32_t din31_0 =
REG_ADDR(TI_CC2650_GPIO_40022000_BASE_ADDRESS,
CC2650_GPIO_DIN31_0);
static const u32_t doe31_0 =
REG_ADDR(TI_CC2650_GPIO_40022000_BASE_ADDRESS,
CC2650_GPIO_DOE31_0);
static const u32_t evflags31_0 =
REG_ADDR(TI_CC2650_GPIO_40022000_BASE_ADDRESS,
CC2650_GPIO_EVFLAGS31_0);
static struct gpio_cc2650_data gpio_cc2650_data = {
.pin_callback_enables = 0
};
static const struct gpio_driver_api gpio_cc2650_funcs = {
.config = gpio_cc2650_config,
.write = gpio_cc2650_write,
.read = gpio_cc2650_read,
.manage_callback = gpio_cc2650_manage_callback,
.enable_callback = gpio_cc2650_enable_callback,
.disable_callback = gpio_cc2650_disable_callback,
.get_pending_int = gpio_cc2650_get_pending_int
};
DEVICE_AND_API_INIT(gpio_cc2650_0, CONFIG_GPIO_CC2650_NAME,
gpio_cc2650_init, &gpio_cc2650_data, NULL,
PRE_KERNEL_1, CONFIG_GPIO_CC2650_INIT_PRIO,
&gpio_cc2650_funcs);
static void disconnect(const int pin, u32_t *gpiodoe31_0,
u32_t *iocfg)
{
*gpiodoe31_0 &= ~BIT(pin);
*iocfg &= ~(CC2650_IOC_IOCFGX_PULL_CTL_MASK |
CC2650_IOC_IOCFGX_IE_MASK);
*iocfg |= CC2650_IOC_INPUT_DISABLED |
CC2650_IOC_NO_PULL;
}
/* Configure a single pin.
* If any asked option is not implementable, rollback entirely to
* previous configuration.
*
* Note: For pin drive strength, the CC2650 devices only support
* symmetric sink/source capabilities.
* Thus, you may ONLY determine the common drive strength with
* GPIO *low output state* flags. Flags for *high output state*
* will be ignored.
*/
static int gpio_cc2650_config_pin(int pin, int flags)
{
const u32_t iocfg = REG_ADDR(TI_CC2650_PINMUX_40081000_BASE_ADDRESS,
CC2650_IOC_IOCFG0 + 0x4 * pin);
u32_t iocfg_config = sys_read32(iocfg);
u32_t gpio_doe31_0_config = sys_read32(doe31_0);
/* Reset all configurable fields to 0 */
iocfg_config &= ~(CC2650_IOC_IOCFGX_IOSTR_MASK |
CC2650_IOC_IOCFGX_PULL_CTL_MASK |
CC2650_IOC_IOCFGX_EDGE_DET_MASK |
CC2650_IOC_IOCFGX_EDGE_IRQ_EN_MASK |
CC2650_IOC_IOCFGX_IOMODE_MASK |
CC2650_IOC_IOCFGX_IE_MASK |
CC2650_IOC_IOCFGX_HYST_EN_MASK);
if (flags & GPIO_PIN_DISABLE) {
disconnect(pin, &gpio_doe31_0_config, &iocfg_config);
goto commit_config;
}
if (flags & GPIO_DIR_OUT) {
gpio_doe31_0_config |= BIT(pin);
iocfg_config |= CC2650_IOC_INPUT_DISABLED;
} else {
gpio_doe31_0_config &= ~BIT(pin);
iocfg_config |= CC2650_IOC_INPUT_ENABLED;
}
if (flags & GPIO_INT) {
if (!(flags & GPIO_INT_EDGE) &&
!(flags & GPIO_INT_DOUBLE_EDGE)) {
/* Can't do level-based interrupt */
/* Don't commit changes */
return -ENOTSUP;
}
iocfg_config |= BIT(CC2650_IOC_IOCFGX_EDGE_IRQ_EN_POS);
if (flags & GPIO_INT_EDGE) {
if (flags & GPIO_INT_ACTIVE_HIGH) {
iocfg_config |= CC2650_IOC_POS_EDGE_DET;
} else {
iocfg_config |= CC2650_IOC_NEG_EDGE_DET;
}
} else if (flags & GPIO_INT_DOUBLE_EDGE) {
iocfg_config |= CC2650_IOC_NEG_AND_POS_EDGE_DET;
}
if (flags & GPIO_INT_CLOCK_SYNC) {
/* Don't commit changes */
return -ENOTSUP;
}
if (flags & GPIO_INT_DEBOUNCE) {
iocfg_config |= CC2650_IOC_HYSTERESIS_ENABLED;
} else {
iocfg_config |= CC2650_IOC_HYSTERESIS_DISABLED;
}
}
if (flags & GPIO_POL_INV) {
iocfg_config |= CC2650_IOC_INVERTED_IO;
} else {
iocfg_config |= CC2650_IOC_NORMAL_IO;
}
if (flags & GPIO_PUD_PULL_UP) {
iocfg_config |= CC2650_IOC_PULL_UP;
} else if (flags & GPIO_PUD_PULL_DOWN) {
iocfg_config |= CC2650_IOC_PULL_DOWN;
} else {
iocfg_config |= CC2650_IOC_NO_PULL;
}
/* Remember, we only look at GPIO_DS_*_LOW ! */
if (flags & GPIO_DS_DISCONNECT_LOW) {
disconnect(pin, &gpio_doe31_0_config, &iocfg_config);
}
if (flags & GPIO_DS_ALT_LOW) {
iocfg_config |= CC2650_IOC_MAX_DRIVE_STRENGTH;
} else {
iocfg_config |= CC2650_IOC_MIN_DRIVE_STRENGTH;
}
/* Commit changes */
commit_config:
sys_write32(iocfg_config, iocfg);
sys_write32(gpio_doe31_0_config, doe31_0);
return 0;
}
static inline void gpio_cc2650_write_pin(int pin, u32_t value)
{
value ? sys_write32(BIT(pin), doutset31_0) :
sys_write32(BIT(pin), doutclr31_0);
}
static inline void gpio_cc2650_read_pin(int pin, u32_t *value)
{
*value = sys_read32(din31_0) & BIT(pin);
}
static void gpio_cc2650_isr(void *arg)
{
struct device *dev = (struct device *)arg;
struct gpio_cc2650_data *data = dev->driver_data;
const u32_t events = sys_read32(evflags31_0);
const u32_t call_mask = events & data->pin_callback_enables;
/* Clear GPIO trigger events */
u32_t evflags = sys_read32(evflags31_0);
sys_write32(evflags | call_mask, evflags31_0);
_gpio_fire_callbacks(&data->callbacks, dev, call_mask);
}
static int gpio_cc2650_init(struct device *dev)
{
ARG_UNUSED(dev);
/* ISR setup */
IRQ_CONNECT(TI_CC2650_GPIO_40022000_IRQ_0,
TI_CC2650_GPIO_40022000_IRQ_0_PRIORITY,
gpio_cc2650_isr, DEVICE_GET(gpio_cc2650_0),
0);
irq_enable(TI_CC2650_GPIO_40022000_IRQ_0);
return 0;
}
static int gpio_cc2650_config(struct device *port, int access_op,
u32_t pin, int flags)
{
ARG_UNUSED(port);
if (access_op == GPIO_ACCESS_BY_PIN) {
return gpio_cc2650_config_pin(pin, flags);
}
const u32_t nb_pins = 32;
for (u8_t i = 0; i < nb_pins; ++i) {
if (pin & 0x1 &&
gpio_cc2650_config_pin(i, flags) == -ENOTSUP) {
/* The flags being treated the same for
* every pin, if we get here then it's
* necessarily the first pin on which we act.
*
* We expect gpio_cc2650_config_pin() to
* NOT commit its changes if any problem
* arises, thus we do nothing special here
* to implement rollback to previous
* configuration.
*/
return -ENOTSUP;
}
pin >>= 1;
}
return 0;
}
static int gpio_cc2650_write(struct device *port, int access_op,
u32_t pin, u32_t value)
{
ARG_UNUSED(port);
if (access_op == GPIO_ACCESS_BY_PIN) {
gpio_cc2650_write_pin(pin, value);
} else {
const u32_t nb_pins = 32;
for (u32_t i = 0; i < nb_pins; ++i) {
if (pin & 0x1) {
gpio_cc2650_write_pin(i, value);
}
pin >>= 1;
}
}
return 0;
}
static int gpio_cc2650_read(struct device *port, int access_op,
u32_t pin, u32_t *value)
{
ARG_UNUSED(port);
if (access_op == GPIO_ACCESS_BY_PIN) {
gpio_cc2650_read_pin(pin, value);
*value >>= pin;
} else {
const u32_t nb_pins = 32;
for (u32_t i = 0; i < nb_pins; ++i) {
if (pin & 0x1) {
gpio_cc2650_read_pin(i, value);
}
pin >>= 1;
}
}
return 0;
}
static int gpio_cc2650_manage_callback(struct device *port,
struct gpio_callback *callback,
bool set)
{
struct gpio_cc2650_data *data = port->driver_data;
_gpio_manage_callback(&data->callbacks, callback, set);
return 0;
}
static int gpio_cc2650_enable_callback(struct device *port,
int access_op,
u32_t pin)
{
struct gpio_cc2650_data *data = port->driver_data;
if (access_op == GPIO_ACCESS_BY_PIN) {
data->pin_callback_enables |= BIT(pin);
} else {
data->pin_callback_enables |= pin;
}
return 0;
}
static int gpio_cc2650_disable_callback(struct device *port,
int access_op,
u32_t pin)
{
struct gpio_cc2650_data *data = port->driver_data;
if (access_op == GPIO_ACCESS_BY_PIN) {
data->pin_callback_enables &= ~BIT(pin);
} else {
data->pin_callback_enables &= ~pin;
}
return 0;
}
static u32_t gpio_cc2650_get_pending_int(struct device *dev)
{
ARG_UNUSED(dev);
return sys_read32(evflags31_0);
}