Skip to content
Permalink
Browse files

drivers: gpio: sam0: Add interrupt support

This adds interrupt support to the SAM0 GPIO driver.  This is heavily
inspired by @nzmichaelh work in #5715.  The primary difference
from that implementation is that here the External Interrupt
Controller (EIC) is separated out into an interrupt controller driver
that is less tightly coupled to the GPIO API.  Instead it implements
more of a conversion from the EIC's own odd multiplexing to a more
traditional port and pin mask IRQ-like callback.  Unfortunately,
through the EIC on the SAMD2x are relatively well behaved
in terms of pin to EIC line mappings, other chips that share the
peripheral interface are not.  So the EIC driver implements a
per-line lookup to the pin and port pair using definitions extracted
from the ASF headers.

The EIC driver still makes some assumptions about how it will be used:
mostly it assumes exactly one callback per port.  This should be fine
as the only intended user is the GPIO driver itself.

This has been tested with some simple programs and with
tests/drivers/gpio/gpio_basic_api on a SAMD21 breakout and an
adafruit_trinket_m0 board.

Signed-off-by: Derek Hageman <hageman@inthat.cloud>
  • Loading branch information...
Sizurka authored and nashif committed Mar 2, 2019
1 parent 1892c67 commit 4462069d7418555ce874485bd7fc62aacd213335
@@ -8,13 +8,37 @@
#include <device.h>
#include <gpio.h>
#include <soc.h>
#include <interrupt_controller/sam0_eic.h>

#include "gpio_utils.h"

struct gpio_sam0_config {
PortGroup *regs;
#ifdef CONFIG_SAM0_EIC
u8_t id;
#endif
};

struct gpio_sam0_data {
#ifdef CONFIG_SAM0_EIC
sys_slist_t callbacks;
#endif
};

#define DEV_CFG(dev) \
((const struct gpio_sam0_config *const)(dev)->config->config_info)
#define DEV_DATA(dev) \
((struct gpio_sam0_data *const)(dev)->driver_data)

#ifdef CONFIG_SAM0_EIC
static void gpio_sam0_isr(u32_t pins, void *arg)
{
struct device *const dev = (struct device *) arg;
struct gpio_sam0_data *const data = DEV_DATA(dev);

gpio_fire_callbacks(&data->callbacks, dev, pins);
}
#endif

static int gpio_sam0_config(struct device *dev, int access_op, u32_t pin,
int flags)
@@ -61,13 +85,59 @@ static int gpio_sam0_config(struct device *dev, int access_op, u32_t pin,
return -ENOTSUP;
}

/* Write the now-built pin configuration */
regs->PINCFG[pin] = pincfg;

#ifdef CONFIG_SAM0_EIC
if ((flags & GPIO_INT) != 0) {
/*
* The EIC requires setting the pin to function A, so do
* that as part of the interrupt enable, so that the API
* is consistent.
*/
pincfg.bit.PMUXEN = 1;
if (pin & 1) {
regs->PMUX[pin / 2].bit.PMUXO = PORT_PMUX_PMUXE_A_Val;
} else {
regs->PMUX[pin / 2].bit.PMUXE = PORT_PMUX_PMUXE_A_Val;
}

enum sam0_eic_trigger trigger;

if ((flags & GPIO_INT_DOUBLE_EDGE) != 0) {
trigger = SAM0_EIC_BOTH;
} else if (flags & GPIO_INT_EDGE) {
if (flags & GPIO_INT_ACTIVE_HIGH) {
trigger = SAM0_EIC_RISING;
} else {
trigger = SAM0_EIC_FALLING;
}
} else {
if (flags & GPIO_INT_ACTIVE_HIGH) {
trigger = SAM0_EIC_HIGH;
} else {
trigger = SAM0_EIC_LOW;
}
}

int retval = sam0_eic_acquire(config->id, pin, trigger,
(flags & GPIO_INT_DEBOUNCE) != 0,
gpio_sam0_isr, dev);
if (retval != 0) {
return retval;
}
} else {
sam0_eic_release(config->id, pin);
/*
* Pinmux disabled by the config set, so this
* correctly inverts the EIC enable part above.
*/
}
#else
if ((flags & GPIO_INT) != 0) {
/* TODO(mlhx): implement. */
return -ENOTSUP;
}
#endif

/* Write the now-built pin configuration */
regs->PINCFG[pin] = pincfg;

if ((flags & GPIO_POL_MASK) != GPIO_POL_NORMAL) {
return -ENOTSUP;
@@ -113,10 +183,57 @@ static int gpio_sam0_read(struct device *dev, int access_op, u32_t pin,
return 0;
}

#ifdef CONFIG_SAM0_EIC

int gpio_sam0_manage_callback(struct device *dev,
struct gpio_callback *callback, bool set)
{
struct gpio_sam0_data *const data = DEV_DATA(dev);

return gpio_manage_callback(&data->callbacks, callback, set);
}

int gpio_sam0_enable_callback(struct device *dev, int access_op, u32_t pin)
{
const struct gpio_sam0_config *config = DEV_CFG(dev);

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

return sam0_eic_enable_interrupt(config->id, pin);
}

int gpio_sam0_disable_callback(struct device *dev, int access_op, u32_t pin)
{
const struct gpio_sam0_config *config = DEV_CFG(dev);

if (access_op != GPIO_ACCESS_BY_PIN) {
return -ENOTSUP;
}

return sam0_eic_disable_interrupt(config->id, pin);
}

u32_t gpio_sam0_get_pending_int(struct device *dev)
{
const struct gpio_sam0_config *config = DEV_CFG(dev);

return sam0_eic_interrupt_pending(config->id);
}

#endif

static const struct gpio_driver_api gpio_sam0_api = {
.config = gpio_sam0_config,
.write = gpio_sam0_write,
.read = gpio_sam0_read,
#ifdef CONFIG_SAM0_EIC
.manage_callback = gpio_sam0_manage_callback,
.enable_callback = gpio_sam0_enable_callback,
.disable_callback = gpio_sam0_disable_callback,
.get_pending_int = gpio_sam0_get_pending_int,
#endif
};

static int gpio_sam0_init(struct device *dev) { return 0; }
@@ -126,33 +243,51 @@ static int gpio_sam0_init(struct device *dev) { return 0; }

static const struct gpio_sam0_config gpio_sam0_config_0 = {
.regs = (PortGroup *)DT_ATMEL_SAM0_GPIO_PORT_A_BASE_ADDRESS,
#ifdef CONFIG_SAM0_EIC
.id = 0,
#endif
};

static struct gpio_sam0_data gpio_sam0_data_0;

DEVICE_AND_API_INIT(gpio_sam0_0, DT_ATMEL_SAM0_GPIO_PORT_A_LABEL,
gpio_sam0_init, NULL, &gpio_sam0_config_0, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api);
gpio_sam0_init, &gpio_sam0_data_0, &gpio_sam0_config_0,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam0_api);
#endif

/* Port B */
#if DT_ATMEL_SAM0_GPIO_PORT_B_BASE_ADDRESS

static const struct gpio_sam0_config gpio_sam0_config_1 = {
.regs = (PortGroup *)DT_ATMEL_SAM0_GPIO_PORT_B_BASE_ADDRESS,
#ifdef CONFIG_SAM0_EIC
.id = 1,
#endif
};

static struct gpio_sam0_data gpio_sam0_data_1;

DEVICE_AND_API_INIT(gpio_sam0_1, DT_ATMEL_SAM0_GPIO_PORT_B_LABEL,
gpio_sam0_init, NULL, &gpio_sam0_config_1, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api);
gpio_sam0_init, &gpio_sam0_data_1, &gpio_sam0_config_1,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam0_api);
#endif

/* Port C */
#if DT_ATMEL_SAM0_GPIO_PORT_C_BASE_ADDRESS

static const struct gpio_sam0_config gpio_sam0_config_2 = {
.regs = (PortGroup *)DT_ATMEL_SAM0_GPIO_PORT_C_BASE_ADDRESS,
#ifdef CONFIG_SAM0_EIC
.id = 2,
#endif
};

static struct gpio_sam0_data gpio_sam0_data_2;

DEVICE_AND_API_INIT(gpio_sam0_2, DT_ATMEL_SAM0_GPIO_PORT_C_LABEL,
gpio_sam0_init, NULL, &gpio_sam0_config_2, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &gpio_sam0_api);
gpio_sam0_init, &gpio_sam0_data_2, &gpio_sam0_config_2,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam0_api);
#endif
@@ -12,3 +12,4 @@ zephyr_sources_ifdef(CONFIG_SOC_FAMILY_STM32 exti_stm32.c)
zephyr_sources_ifdef(CONFIG_CAVS_ICTL cavs_ictl.c)
zephyr_sources_ifdef(CONFIG_DW_ICTL dw_ictl.c)
zephyr_sources_ifdef(CONFIG_RV32M1_INTMUX rv32m1_intmux.c)
zephyr_sources_ifdef(CONFIG_SAM0_EIC sam0_eic.c)
@@ -164,4 +164,6 @@ source "drivers/interrupt_controller/Kconfig.s1000"

source "drivers/interrupt_controller/Kconfig.rv32m1"

source "drivers/interrupt_controller/Kconfig.sam0"

endmenu
@@ -0,0 +1,17 @@
# Kconfig - SAM0 EIC configuration
#
# Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
#
# SPDX-License-Identifier: Apache-2.0
#

if SOC_FAMILY_SAM0

config SAM0_EIC
bool "External Interrupt Controller (EIC) Driver for SAM0 series devices"
default y
help
Enable EIC driver for SAM0 series of devices. This is required for
GPIO interrupt support.

endif # SOC_FAMILY_SAM0

0 comments on commit 4462069

Please sign in to comment.
You can’t perform that action at this time.