513 lines
13 KiB
C
513 lines
13 KiB
C
/*
|
|
* Copyright (c) 2018, Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <drivers/gpio.h>
|
|
#include <hal/nrf_gpio.h>
|
|
#include <hal/nrf_gpiote.h>
|
|
|
|
#include "gpio_utils.h"
|
|
|
|
/* Mask holding information about which channels are allocated. */
|
|
static atomic_t gpiote_alloc_mask;
|
|
|
|
struct gpio_nrfx_data {
|
|
/* gpio_driver_data needs to be first */
|
|
struct gpio_driver_data common;
|
|
sys_slist_t callbacks;
|
|
|
|
/* Mask holding information about which pins have been configured to
|
|
* trigger interrupts using gpio_nrfx_config function.
|
|
*/
|
|
uint32_t pin_int_en;
|
|
|
|
uint32_t int_active_level;
|
|
uint32_t trig_edge;
|
|
uint32_t double_edge;
|
|
};
|
|
|
|
struct gpio_nrfx_cfg {
|
|
/* gpio_driver_config needs to be first */
|
|
struct gpio_driver_config common;
|
|
NRF_GPIO_Type *port;
|
|
uint8_t port_num;
|
|
};
|
|
|
|
static inline struct gpio_nrfx_data *get_port_data(struct device *port)
|
|
{
|
|
return port->driver_data;
|
|
}
|
|
|
|
static inline const struct gpio_nrfx_cfg *get_port_cfg(struct device *port)
|
|
{
|
|
return port->config_info;
|
|
}
|
|
|
|
static int gpiote_channel_alloc(atomic_t *mask, uint32_t abs_pin,
|
|
nrf_gpiote_polarity_t polarity)
|
|
{
|
|
for (uint8_t channel = 0; channel < GPIOTE_CH_NUM; ++channel) {
|
|
atomic_val_t prev = atomic_or(mask, BIT(channel));
|
|
|
|
if ((prev & BIT(channel)) == 0) {
|
|
nrf_gpiote_event_t evt =
|
|
offsetof(NRF_GPIOTE_Type, EVENTS_IN[channel]);
|
|
|
|
nrf_gpiote_event_configure(NRF_GPIOTE, channel, abs_pin,
|
|
polarity);
|
|
nrf_gpiote_event_clear(NRF_GPIOTE, evt);
|
|
nrf_gpiote_event_enable(NRF_GPIOTE, channel);
|
|
nrf_gpiote_int_enable(NRF_GPIOTE, BIT(channel));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Function checks if given pin does not have already enabled GPIOTE event and
|
|
* disables it.
|
|
*/
|
|
static void gpiote_pin_cleanup(atomic_t *mask, uint32_t abs_pin)
|
|
{
|
|
uint32_t intenset = nrf_gpiote_int_enable_check(NRF_GPIOTE,
|
|
NRF_GPIOTE_INT_IN_MASK);
|
|
|
|
for (size_t i = 0; i < GPIOTE_CH_NUM; i++) {
|
|
if ((nrf_gpiote_event_pin_get(NRF_GPIOTE, i) == abs_pin)
|
|
&& (intenset & BIT(i))) {
|
|
(void)atomic_and(mask, ~BIT(i));
|
|
nrf_gpiote_event_disable(NRF_GPIOTE, i);
|
|
nrf_gpiote_int_disable(NRF_GPIOTE, BIT(i));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline uint32_t sense_for_pin(const struct gpio_nrfx_data *data,
|
|
uint32_t pin)
|
|
{
|
|
if ((BIT(pin) & data->int_active_level) != 0U) {
|
|
return NRF_GPIO_PIN_SENSE_HIGH;
|
|
}
|
|
return NRF_GPIO_PIN_SENSE_LOW;
|
|
}
|
|
|
|
static int gpiote_pin_int_cfg(struct device *port, uint32_t pin)
|
|
{
|
|
struct gpio_nrfx_data *data = get_port_data(port);
|
|
const struct gpio_nrfx_cfg *cfg = get_port_cfg(port);
|
|
uint32_t abs_pin = NRF_GPIO_PIN_MAP(cfg->port_num, pin);
|
|
int res = 0;
|
|
|
|
gpiote_pin_cleanup(&gpiote_alloc_mask, abs_pin);
|
|
nrf_gpio_cfg_sense_set(abs_pin, NRF_GPIO_PIN_NOSENSE);
|
|
|
|
/* Pins trigger interrupts only if pin has been configured to do so */
|
|
if (data->pin_int_en & BIT(pin)) {
|
|
if (data->trig_edge & BIT(pin)) {
|
|
/* For edge triggering we use GPIOTE channels. */
|
|
nrf_gpiote_polarity_t pol;
|
|
|
|
if (data->double_edge & BIT(pin)) {
|
|
pol = NRF_GPIOTE_POLARITY_TOGGLE;
|
|
} else if ((data->int_active_level & BIT(pin)) != 0U) {
|
|
pol = NRF_GPIOTE_POLARITY_LOTOHI;
|
|
} else {
|
|
pol = NRF_GPIOTE_POLARITY_HITOLO;
|
|
}
|
|
|
|
res = gpiote_channel_alloc(&gpiote_alloc_mask,
|
|
abs_pin, pol);
|
|
} else {
|
|
/* For level triggering we use sense mechanism. */
|
|
uint32_t sense = sense_for_pin(data, pin);
|
|
|
|
nrf_gpio_cfg_sense_set(abs_pin, sense);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int gpio_nrfx_config(struct device *port,
|
|
gpio_pin_t pin, gpio_flags_t flags)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
nrf_gpio_pin_pull_t pull;
|
|
nrf_gpio_pin_drive_t drive;
|
|
nrf_gpio_pin_dir_t dir;
|
|
nrf_gpio_pin_input_t input;
|
|
|
|
switch (flags & (GPIO_DS_LOW_MASK | GPIO_DS_HIGH_MASK |
|
|
GPIO_OPEN_DRAIN)) {
|
|
case GPIO_DS_DFLT_LOW | GPIO_DS_DFLT_HIGH:
|
|
drive = NRF_GPIO_PIN_S0S1;
|
|
break;
|
|
case GPIO_DS_DFLT_LOW | GPIO_DS_ALT_HIGH:
|
|
drive = NRF_GPIO_PIN_S0H1;
|
|
break;
|
|
case GPIO_DS_DFLT_LOW | GPIO_OPEN_DRAIN:
|
|
drive = NRF_GPIO_PIN_S0D1;
|
|
break;
|
|
|
|
case GPIO_DS_ALT_LOW | GPIO_DS_DFLT_HIGH:
|
|
drive = NRF_GPIO_PIN_H0S1;
|
|
break;
|
|
case GPIO_DS_ALT_LOW | GPIO_DS_ALT_HIGH:
|
|
drive = NRF_GPIO_PIN_H0H1;
|
|
break;
|
|
case GPIO_DS_ALT_LOW | GPIO_OPEN_DRAIN:
|
|
drive = NRF_GPIO_PIN_H0D1;
|
|
break;
|
|
|
|
case GPIO_DS_DFLT_HIGH | GPIO_OPEN_SOURCE:
|
|
drive = NRF_GPIO_PIN_D0S1;
|
|
break;
|
|
case GPIO_DS_ALT_HIGH | GPIO_OPEN_SOURCE:
|
|
drive = NRF_GPIO_PIN_D0H1;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((flags & GPIO_PULL_UP) != 0) {
|
|
pull = NRF_GPIO_PIN_PULLUP;
|
|
} else if ((flags & GPIO_PULL_DOWN) != 0) {
|
|
pull = NRF_GPIO_PIN_PULLDOWN;
|
|
} else {
|
|
pull = NRF_GPIO_PIN_NOPULL;
|
|
}
|
|
|
|
dir = ((flags & GPIO_OUTPUT) != 0)
|
|
? NRF_GPIO_PIN_DIR_OUTPUT
|
|
: NRF_GPIO_PIN_DIR_INPUT;
|
|
|
|
input = ((flags & GPIO_INPUT) != 0)
|
|
? NRF_GPIO_PIN_INPUT_CONNECT
|
|
: NRF_GPIO_PIN_INPUT_DISCONNECT;
|
|
|
|
if ((flags & GPIO_OUTPUT) != 0) {
|
|
if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
|
|
nrf_gpio_port_out_set(reg, BIT(pin));
|
|
} else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
|
|
nrf_gpio_port_out_clear(reg, BIT(pin));
|
|
}
|
|
}
|
|
|
|
nrf_gpio_cfg(NRF_GPIO_PIN_MAP(get_port_cfg(port)->port_num, pin),
|
|
dir, input, pull, drive, NRF_GPIO_PIN_NOSENSE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_port_get_raw(struct device *port, uint32_t *value)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
|
|
*value = nrf_gpio_port_in_read(reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_port_set_masked_raw(struct device *port, uint32_t mask,
|
|
uint32_t value)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
uint32_t value_tmp;
|
|
|
|
value_tmp = nrf_gpio_port_out_read(reg) & ~mask;
|
|
nrf_gpio_port_out_write(reg, value_tmp | (mask & value));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_port_set_bits_raw(struct device *port, uint32_t mask)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
|
|
nrf_gpio_port_out_set(reg, mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_port_clear_bits_raw(struct device *port, uint32_t mask)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
|
|
nrf_gpio_port_out_clear(reg, mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_port_toggle_bits(struct device *port, uint32_t mask)
|
|
{
|
|
NRF_GPIO_Type *reg = get_port_cfg(port)->port;
|
|
uint32_t value;
|
|
|
|
value = nrf_gpio_port_out_read(reg);
|
|
nrf_gpio_port_out_write(reg, value ^ mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_nrfx_pin_interrupt_configure(struct device *port,
|
|
gpio_pin_t pin, enum gpio_int_mode mode,
|
|
enum gpio_int_trig trig)
|
|
{
|
|
struct gpio_nrfx_data *data = get_port_data(port);
|
|
uint32_t abs_pin = NRF_GPIO_PIN_MAP(get_port_cfg(port)->port_num, pin);
|
|
|
|
if ((mode == GPIO_INT_MODE_EDGE) &&
|
|
(nrf_gpio_pin_dir_get(abs_pin) == NRF_GPIO_PIN_DIR_OUTPUT)) {
|
|
/*
|
|
* The pin's output value as specified in the GPIO will be
|
|
* ignored as long as the pin is controlled by GPIOTE.
|
|
* Pin with output enabled cannot be used as an edge interrupt
|
|
* source.
|
|
*/
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
WRITE_BIT(data->pin_int_en, pin, mode != GPIO_INT_MODE_DISABLED);
|
|
WRITE_BIT(data->trig_edge, pin, mode == GPIO_INT_MODE_EDGE);
|
|
WRITE_BIT(data->double_edge, pin, trig == GPIO_INT_TRIG_BOTH);
|
|
WRITE_BIT(data->int_active_level, pin, trig == GPIO_INT_TRIG_HIGH);
|
|
|
|
return gpiote_pin_int_cfg(port, pin);
|
|
}
|
|
|
|
static int gpio_nrfx_manage_callback(struct device *port,
|
|
struct gpio_callback *callback,
|
|
bool set)
|
|
{
|
|
return gpio_manage_callback(&get_port_data(port)->callbacks,
|
|
callback, set);
|
|
}
|
|
|
|
static const struct gpio_driver_api gpio_nrfx_drv_api_funcs = {
|
|
.pin_configure = gpio_nrfx_config,
|
|
.port_get_raw = gpio_nrfx_port_get_raw,
|
|
.port_set_masked_raw = gpio_nrfx_port_set_masked_raw,
|
|
.port_set_bits_raw = gpio_nrfx_port_set_bits_raw,
|
|
.port_clear_bits_raw = gpio_nrfx_port_clear_bits_raw,
|
|
.port_toggle_bits = gpio_nrfx_port_toggle_bits,
|
|
.pin_interrupt_configure = gpio_nrfx_pin_interrupt_configure,
|
|
.manage_callback = gpio_nrfx_manage_callback,
|
|
};
|
|
|
|
static inline uint32_t get_level_pins(struct device *port)
|
|
{
|
|
struct gpio_nrfx_data *data = get_port_data(port);
|
|
|
|
/* Take into consideration only pins that were configured to
|
|
* trigger interrupts.
|
|
*/
|
|
uint32_t out = data->pin_int_en;
|
|
|
|
/* Exclude pins that trigger interrupts by edge. */
|
|
out &= ~data->trig_edge & ~data->double_edge;
|
|
|
|
/* The sequence above assumes that the sense field will be
|
|
* configured only for these pins. If anybody's modifying
|
|
* PIN_CNF directly it won't work.
|
|
*/
|
|
return out;
|
|
}
|
|
|
|
static void cfg_level_pins(struct device *port)
|
|
{
|
|
const struct gpio_nrfx_data *data = get_port_data(port);
|
|
const struct gpio_nrfx_cfg *cfg = get_port_cfg(port);
|
|
uint32_t pin = 0U;
|
|
uint32_t bit = 1U << pin;
|
|
uint32_t level_pins = get_level_pins(port);
|
|
|
|
/* Configure sense detection on all pins that use it. */
|
|
while (level_pins) {
|
|
if (level_pins & bit) {
|
|
uint32_t abs_pin = NRF_GPIO_PIN_MAP(cfg->port_num, pin);
|
|
uint32_t sense = sense_for_pin(data, pin);
|
|
|
|
nrf_gpio_cfg_sense_set(abs_pin, sense);
|
|
level_pins &= ~bit;
|
|
}
|
|
++pin;
|
|
bit <<= 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Function for getting pins that triggered level interrupt.
|
|
*
|
|
* @param port Pointer to GPIO port device.
|
|
*
|
|
* @return Bitmask where 1 marks pin as trigger source.
|
|
*/
|
|
static uint32_t check_level_trigger_pins(struct device *port)
|
|
{
|
|
struct gpio_nrfx_data *data = get_port_data(port);
|
|
const struct gpio_nrfx_cfg *cfg = get_port_cfg(port);
|
|
uint32_t level_pins = get_level_pins(port);
|
|
uint32_t port_in = nrf_gpio_port_in_read(cfg->port);
|
|
|
|
/* Extract which pins have logic level same as interrupt trigger level.
|
|
*/
|
|
uint32_t pin_states = ~(port_in ^ data->int_active_level);
|
|
|
|
/* Discard pins that aren't configured for level. */
|
|
uint32_t out = pin_states & level_pins;
|
|
|
|
/* Disable sense detection on all pins that use it, whether
|
|
* they appear to have triggered or not. This ensures
|
|
* nobody's requesting DETECT.
|
|
*/
|
|
uint32_t pin = 0U;
|
|
uint32_t bit = 1U << pin;
|
|
|
|
while (level_pins) {
|
|
if (level_pins & bit) {
|
|
uint32_t abs_pin = NRF_GPIO_PIN_MAP(cfg->port_num, pin);
|
|
|
|
nrf_gpio_cfg_sense_set(abs_pin, NRF_GPIO_PIN_NOSENSE);
|
|
level_pins &= ~bit;
|
|
}
|
|
++pin;
|
|
bit <<= 1;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static inline void fire_callbacks(struct device *port, uint32_t pins)
|
|
{
|
|
struct gpio_nrfx_data *data = get_port_data(port);
|
|
sys_slist_t *list = &data->callbacks;
|
|
|
|
gpio_fire_callbacks(list, port, pins);
|
|
}
|
|
|
|
#ifdef CONFIG_GPIO_NRF_P0
|
|
DEVICE_DECLARE(gpio_nrfx_p0);
|
|
#endif
|
|
#ifdef CONFIG_GPIO_NRF_P1
|
|
DEVICE_DECLARE(gpio_nrfx_p1);
|
|
#endif
|
|
|
|
static void gpiote_event_handler(void)
|
|
{
|
|
uint32_t fired_triggers[GPIO_COUNT] = {0};
|
|
bool port_event = nrf_gpiote_event_check(NRF_GPIOTE,
|
|
NRF_GPIOTE_EVENT_PORT);
|
|
|
|
if (port_event) {
|
|
#ifdef CONFIG_GPIO_NRF_P0
|
|
fired_triggers[0] =
|
|
check_level_trigger_pins(DEVICE_GET(gpio_nrfx_p0));
|
|
#endif
|
|
#ifdef CONFIG_GPIO_NRF_P1
|
|
fired_triggers[1] =
|
|
check_level_trigger_pins(DEVICE_GET(gpio_nrfx_p1));
|
|
#endif
|
|
|
|
/* Sense detect was disabled while checking pins so
|
|
* DETECT should be deasserted.
|
|
*/
|
|
nrf_gpiote_event_clear(NRF_GPIOTE, NRF_GPIOTE_EVENT_PORT);
|
|
}
|
|
|
|
/* Handle interrupt from GPIOTE channels. */
|
|
for (size_t i = 0; i < GPIOTE_CH_NUM; i++) {
|
|
nrf_gpiote_event_t evt =
|
|
offsetof(NRF_GPIOTE_Type, EVENTS_IN[i]);
|
|
|
|
if (nrf_gpiote_int_enable_check(NRF_GPIOTE, BIT(i)) &&
|
|
nrf_gpiote_event_check(NRF_GPIOTE, evt)) {
|
|
uint32_t abs_pin = nrf_gpiote_event_pin_get(NRF_GPIOTE, i);
|
|
/* Divide absolute pin number to port and pin parts. */
|
|
fired_triggers[abs_pin / 32U] |= BIT(abs_pin % 32);
|
|
nrf_gpiote_event_clear(NRF_GPIOTE, evt);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_GPIO_NRF_P0
|
|
if (fired_triggers[0]) {
|
|
fire_callbacks(DEVICE_GET(gpio_nrfx_p0), fired_triggers[0]);
|
|
}
|
|
#endif
|
|
#ifdef CONFIG_GPIO_NRF_P1
|
|
if (fired_triggers[1]) {
|
|
fire_callbacks(DEVICE_GET(gpio_nrfx_p1), fired_triggers[1]);
|
|
}
|
|
#endif
|
|
|
|
if (port_event) {
|
|
/* Reprogram sense to match current configuration.
|
|
* This may cause DETECT to be re-asserted.
|
|
*/
|
|
#ifdef CONFIG_GPIO_NRF_P0
|
|
cfg_level_pins(DEVICE_GET(gpio_nrfx_p0));
|
|
#endif
|
|
#ifdef CONFIG_GPIO_NRF_P1
|
|
cfg_level_pins(DEVICE_GET(gpio_nrfx_p1));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#define GPIOTE_NODE DT_INST(0, nordic_nrf_gpiote)
|
|
|
|
static int gpio_nrfx_init(struct device *port)
|
|
{
|
|
static bool gpio_initialized;
|
|
|
|
if (!gpio_initialized) {
|
|
gpio_initialized = true;
|
|
IRQ_CONNECT(DT_IRQN(GPIOTE_NODE), DT_IRQ(GPIOTE_NODE, priority),
|
|
gpiote_event_handler, NULL, 0);
|
|
|
|
irq_enable(DT_IRQN(GPIOTE_NODE));
|
|
nrf_gpiote_int_enable(NRF_GPIOTE, NRF_GPIOTE_INT_PORT_MASK);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Device instantiation is done with node labels because 'port_num' is
|
|
* the peripheral number by SoC numbering. We therefore cannot use
|
|
* DT_INST APIs here without wider changes.
|
|
*/
|
|
|
|
#define GPIO(id) DT_NODELABEL(gpio##id)
|
|
|
|
#define GPIO_NRF_DEVICE(id) \
|
|
static const struct gpio_nrfx_cfg gpio_nrfx_p##id##_cfg = { \
|
|
.common = { \
|
|
.port_pin_mask = \
|
|
GPIO_PORT_PIN_MASK_FROM_DT_NODE(GPIO(id)), \
|
|
}, \
|
|
.port = NRF_P##id, \
|
|
.port_num = id \
|
|
}; \
|
|
\
|
|
static struct gpio_nrfx_data gpio_nrfx_p##id##_data; \
|
|
\
|
|
DEVICE_AND_API_INIT(gpio_nrfx_p##id, \
|
|
DT_LABEL(GPIO(id)), \
|
|
gpio_nrfx_init, \
|
|
&gpio_nrfx_p##id##_data, \
|
|
&gpio_nrfx_p##id##_cfg, \
|
|
POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
|
|
&gpio_nrfx_drv_api_funcs)
|
|
|
|
#ifdef CONFIG_GPIO_NRF_P0
|
|
GPIO_NRF_DEVICE(0);
|
|
#endif
|
|
|
|
#ifdef CONFIG_GPIO_NRF_P1
|
|
GPIO_NRF_DEVICE(1);
|
|
#endif
|