zephyr/drivers/interrupt_controller/sam0_eic.c
Derek Hageman 4462069d74 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>
2019-05-07 08:43:56 -04:00

395 lines
8.3 KiB
C

/*
* Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <soc.h>
#include "sam0_eic.h"
#include "sam0_eic_priv.h"
struct sam0_eic_line_assignment {
u8_t pin : 5;
u8_t port : 2;
u8_t enabled : 1;
};
struct sam0_eic_port_data {
sam0_eic_callback_t cb;
void *data;
};
struct sam0_eic_data {
struct sam0_eic_port_data ports[PORT_GROUPS];
struct sam0_eic_line_assignment lines[EIC_EXTINT_NUM];
};
#define DEV_DATA(dev) \
((struct sam0_eic_data *const)(dev)->driver_data)
DEVICE_DECLARE(sam0_eic);
static void wait_synchronization(void)
{
while (EIC->STATUS.bit.SYNCBUSY) {
}
}
static void sam0_eic_isr(void *arg)
{
struct device *dev = (struct device *)arg;
struct sam0_eic_data *const dev_data = DEV_DATA(dev);
u16_t bits = EIC->INTFLAG.reg;
u32_t line_index;
/* Acknowledge all interrupts */
EIC->INTFLAG.reg = bits;
/* No clz on M0, so just do a quick test */
#if __CORTEX_M >= 3
line_index = __CLZ(__RBIT(bits));
bits >>= line_index;
#else
if (bits & 0xFF) {
line_index = 0;
} else {
line_index = 8;
bits >>= 8;
}
#endif
/*
* Map the EIC lines to the port pin masks based on which port is
* selected in the line data.
*/
for (; bits; bits >>= 1, line_index++) {
if (!(bits & 1)) {
continue;
}
/*
* These could be aggregated together into one call, but
* usually on a single one will be set, so just call them
* one by one.
*/
struct sam0_eic_line_assignment *line_assignment =
&dev_data->lines[line_index];
struct sam0_eic_port_data *port_data =
&dev_data->ports[line_assignment->port];
port_data->cb(BIT(line_assignment->pin), port_data->data);
}
}
int sam0_eic_acquire(int port, int pin, enum sam0_eic_trigger trigger,
bool filter, sam0_eic_callback_t cb, void *data)
{
struct device *dev = DEVICE_GET(sam0_eic);
struct sam0_eic_data *dev_data = dev->driver_data;
struct sam0_eic_port_data *port_data;
struct sam0_eic_line_assignment *line_assignment;
u32_t mask;
int line_index;
int config_index;
int config_shift;
int key;
u32_t config;
line_index = sam0_eic_map_to_line(port, pin);
if (line_index < 0) {
return line_index;
}
mask = BIT(line_index);
config_index = line_index / 8;
config_shift = (line_index % 8) * 4;
/* Lock everything so it's safe to reconfigure */
key = irq_lock();
/* Disable the EIC for reconfiguration */
EIC->CTRL.bit.ENABLE = 0;
line_assignment = &dev_data->lines[line_index];
/* Check that the required line is available */
if (line_assignment->enabled) {
if (line_assignment->port != port ||
line_assignment->pin != pin) {
goto err_in_use;
}
}
/* Set the EIC configuration data */
port_data = &dev_data->ports[port];
port_data->cb = cb;
port_data->data = data;
line_assignment->pin = pin;
line_assignment->port = port;
line_assignment->enabled = 1;
config = EIC->CONFIG[config_index].reg;
config &= ~(0xF << config_shift);
switch (trigger) {
case SAM0_EIC_RISING:
config |= EIC_CONFIG_SENSE0_RISE << config_shift;
break;
case SAM0_EIC_FALLING:
config |= EIC_CONFIG_SENSE0_FALL << config_shift;
break;
case SAM0_EIC_BOTH:
config |= EIC_CONFIG_SENSE0_BOTH << config_shift;
break;
case SAM0_EIC_HIGH:
config |= EIC_CONFIG_SENSE0_HIGH << config_shift;
break;
case SAM0_EIC_LOW:
config |= EIC_CONFIG_SENSE0_LOW << config_shift;
break;
}
if (filter) {
config |= EIC_CONFIG_FILTEN0 << config_shift;
}
/* Apply the config to the EIC itself */
EIC->CONFIG[config_index].reg = config;
EIC->CTRL.bit.ENABLE = 1;
wait_synchronization();
/*
* Errata: The EIC generates a spurious interrupt for the newly
* enabled pin after being enabled, so clear it before re-enabling
* the IRQ.
*/
EIC->INTFLAG.reg = mask;
irq_unlock(key);
return 0;
err_in_use:
EIC->CTRL.bit.ENABLE = 1;
wait_synchronization();
irq_unlock(key);
return -EBUSY;
}
static bool sam0_eic_check_ownership(int port, int pin, int line_index)
{
struct device *dev = DEVICE_GET(sam0_eic);
struct sam0_eic_data *dev_data = dev->driver_data;
struct sam0_eic_line_assignment *line_assignment =
&dev_data->lines[line_index];
if (!line_assignment->enabled) {
return false;
}
if (line_assignment->port != port ||
line_assignment->pin != pin) {
return false;
}
return true;
}
int sam0_eic_release(int port, int pin)
{
struct device *dev = DEVICE_GET(sam0_eic);
struct sam0_eic_data *dev_data = dev->driver_data;
u32_t mask;
int line_index;
int config_index;
int config_shift;
int key;
line_index = sam0_eic_map_to_line(port, pin);
if (line_index < 0) {
return line_index;
}
mask = BIT(line_index);
config_index = line_index / 8;
config_shift = (line_index % 8) * 4;
/* Lock everything so it's safe to reconfigure */
key = irq_lock();
/* Disable the EIC */
EIC->CTRL.bit.ENABLE = 0;
wait_synchronization();
/*
* Check to make sure the requesting actually owns the line and do
* nothing if it does not.
*/
if (!sam0_eic_check_ownership(port, pin, line_index)) {
goto done;
}
dev_data->lines[line_index].enabled = 0;
/* Clear the EIC config, including the trigger condition */
EIC->CONFIG[config_index].reg &= ~(0xF << config_shift);
/* Clear any pending interrupt for it */
EIC->INTENCLR.reg = mask;
EIC->INTFLAG.reg = mask;
done:
EIC->CTRL.bit.ENABLE = 1;
wait_synchronization();
irq_unlock(key);
return 0;
}
int sam0_eic_enable_interrupt(int port, int pin)
{
u32_t mask;
int line_index;
line_index = sam0_eic_map_to_line(port, pin);
if (line_index < 0) {
return line_index;
}
if (!sam0_eic_check_ownership(port, pin, line_index)) {
return -EBUSY;
}
mask = BIT(line_index);
EIC->INTFLAG.reg = mask;
EIC->INTENSET.reg = mask;
return 0;
}
int sam0_eic_disable_interrupt(int port, int pin)
{
u32_t mask;
int line_index;
line_index = sam0_eic_map_to_line(port, pin);
if (line_index < 0) {
return line_index;
}
if (!sam0_eic_check_ownership(port, pin, line_index)) {
return -EBUSY;
}
mask = BIT(line_index);
EIC->INTENCLR.reg = mask;
EIC->INTFLAG.reg = mask;
return 0;
}
u32_t sam0_eic_interrupt_pending(int port)
{
struct device *dev = DEVICE_GET(sam0_eic);
struct sam0_eic_data *dev_data = dev->driver_data;
struct sam0_eic_line_assignment *line_assignment;
u32_t set = EIC->INTFLAG.reg;
u32_t mask = 0;
for (int line_index = 0; line_index < EIC_EXTINT_NUM; line_index++) {
line_assignment = &dev_data->lines[line_index];
if (!line_assignment->enabled) {
continue;
}
if (line_assignment->port != port) {
continue;
}
if (!(set & BIT(line_index))) {
continue;
}
mask |= BIT(line_assignment->pin);
}
return mask;
}
#define SAM0_EIC_IRQ_CONNECT(n) \
do { \
IRQ_CONNECT(DT_ATMEL_SAM0_EIC_0_IRQ_ ## n, \
DT_ATMEL_SAM0_EIC_0_IRQ_ ## n ## _PRIORITY, \
sam0_eic_isr, DEVICE_GET(sam0_eic), 0); \
irq_enable(DT_ATMEL_SAM0_EIC_0_IRQ_ ## n); \
} while (0)
static int sam0_eic_init(struct device *dev)
{
ARG_UNUSED(dev);
/* Enable the EIC clock in PM */
PM->APBAMASK.bit.EIC_ = 1;
/* Enable the GCLK */
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_EIC | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_CLKEN;
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_0
SAM0_EIC_IRQ_CONNECT(0);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_1
SAM0_EIC_IRQ_CONNECT(1);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_2
SAM0_EIC_IRQ_CONNECT(2);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_3
SAM0_EIC_IRQ_CONNECT(3);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_4
SAM0_EIC_IRQ_CONNECT(4);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_5
SAM0_EIC_IRQ_CONNECT(5);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_6
SAM0_EIC_IRQ_CONNECT(6);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_7
SAM0_EIC_IRQ_CONNECT(7);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_8
SAM0_EIC_IRQ_CONNECT(8);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_9
SAM0_EIC_IRQ_CONNECT(9);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_10
SAM0_EIC_IRQ_CONNECT(10);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_11
SAM0_EIC_IRQ_CONNECT(11);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_12
SAM0_EIC_IRQ_CONNECT(12);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_13
SAM0_EIC_IRQ_CONNECT(13);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_14
SAM0_EIC_IRQ_CONNECT(14);
#endif
#ifdef DT_ATMEL_SAM0_EIC_0_IRQ_15
SAM0_EIC_IRQ_CONNECT(15);
#endif
EIC->CTRL.bit.ENABLE = 1;
wait_synchronization();
return 0;
}
static struct sam0_eic_data eic_data;
DEVICE_INIT(sam0_eic, DT_ATMEL_SAM0_EIC_0_LABEL, sam0_eic_init,
&eic_data, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);