zephyr/drivers/gpio/gpio_atmel_sam3.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

387 lines
8.5 KiB
C

/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file Driver for the Atmel SAM3 PIO Controller.
*/
#include <errno.h>
#include <kernel.h>
#include <device.h>
#include <init.h>
#include <soc.h>
#include <gpio.h>
#include "gpio_utils.h"
typedef void (*config_func_t)(struct device *dev);
/* Configuration data */
struct gpio_sam3_config {
Pio *port;
config_func_t config_func;
};
struct gpio_sam3_runtime {
/* callbacks */
sys_slist_t cb;
};
static void _config(struct device *dev, u32_t mask, int flags)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
/* Setup the pin direction */
if ((flags & GPIO_DIR_MASK) == GPIO_DIR_OUT) {
cfg->port->PIO_OER = mask;
} else {
cfg->port->PIO_ODR = mask;
}
/* Setup interrupt config */
if (flags & GPIO_INT) {
if (flags & GPIO_INT_DOUBLE_EDGE) {
cfg->port->PIO_AIMDR = mask;
} else {
cfg->port->PIO_AIMER = mask;
if (flags & GPIO_INT_EDGE) {
cfg->port->PIO_ESR = mask;
} else {
cfg->port->PIO_LSR = mask;
}
if (flags & GPIO_INT_ACTIVE_HIGH) {
/* Trigger in high level or rising edge */
cfg->port->PIO_REHLSR = mask;
} else {
/* Trigger in low level or falling edge */
cfg->port->PIO_FELLSR = mask;
}
}
}
/* Pull-up? */
if ((flags & GPIO_PUD_MASK) == GPIO_PUD_PULL_UP) {
/* Enable pull-up */
cfg->port->PIO_PUER = mask;
} else {
/* Disable pull-up */
cfg->port->PIO_PUDR = mask;
}
/* Debounce */
if (flags & GPIO_INT_DEBOUNCE) {
cfg->port->PIO_DIFSR = mask;
} else {
cfg->port->PIO_SCIFSR = mask;
}
cfg->port->PIO_PER = mask;
}
/**
* @brief Configure pin or port
*
* @param dev Device struct
* @param access_op Access operation (pin or port)
* @param pin The pin number
* @param flags Flags of pin or port
*
* @return 0 if successful, failed otherwise
*/
static int gpio_sam3_config(struct device *dev, int access_op,
u32_t pin, int flags)
{
switch (access_op) {
case GPIO_ACCESS_BY_PIN:
_config(dev, BIT(pin), flags);
break;
case GPIO_ACCESS_BY_PORT:
_config(dev, (0xFFFFFFFF), flags);
break;
default:
return -ENOTSUP;
}
return 0;
}
/**
* @brief Set the pin or port output
*
* @param dev Device struct
* @param access_op Access operation (pin or port)
* @param pin The pin number
* @param value Value to set (0 or 1)
*
* @return 0 if successful, failed otherwise
*/
static int gpio_sam3_write(struct device *dev, int access_op,
u32_t pin, u32_t value)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
switch (access_op) {
case GPIO_ACCESS_BY_PIN:
if (value) {
/* set the pin */
cfg->port->PIO_SODR = BIT(pin);
} else {
/* clear the pin */
cfg->port->PIO_CODR = BIT(pin);
}
break;
case GPIO_ACCESS_BY_PORT:
if (value) {
/* set all pins */
cfg->port->PIO_SODR = 0xFFFFFFFF;
} else {
/* clear all pins */
cfg->port->PIO_CODR = 0xFFFFFFFF;
}
break;
default:
return -ENOTSUP;
}
return 0;
}
/**
* @brief Read the pin or port status
*
* @param dev Device struct
* @param access_op Access operation (pin or port)
* @param pin The pin number
* @param value Value of input pin(s)
*
* @return 0 if successful, failed otherwise
*/
static int gpio_sam3_read(struct device *dev, int access_op,
u32_t pin, u32_t *value)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
*value = cfg->port->PIO_PDSR;
switch (access_op) {
case GPIO_ACCESS_BY_PIN:
*value = (*value >> pin) & 0x01;
break;
case GPIO_ACCESS_BY_PORT:
break;
default:
return -ENOTSUP;
}
return 0;
}
static void gpio_sam3_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct gpio_sam3_config *cfg = dev->config->config_info;
struct gpio_sam3_runtime *context = dev->driver_data;
u32_t int_stat;
int_stat = cfg->port->PIO_ISR;
_gpio_fire_callbacks(&context->cb, dev, int_stat);
}
static int gpio_sam3_manage_callback(struct device *dev,
struct gpio_callback *callback,
bool set)
{
struct gpio_sam3_runtime *context = dev->driver_data;
return _gpio_manage_callback(&context->cb, callback, set);
}
static int gpio_sam3_enable_callback(struct device *dev,
int access_op, u32_t pin)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
u32_t mask;
switch (access_op) {
case GPIO_ACCESS_BY_PIN:
mask = BIT(pin);
break;
case GPIO_ACCESS_BY_PORT:
mask = 0xFFFFFFFF;
break;
default:
return -ENOTSUP;
}
cfg->port->PIO_IER |= mask;
return 0;
}
static int gpio_sam3_disable_callback(struct device *dev,
int access_op, u32_t pin)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
u32_t mask;
switch (access_op) {
case GPIO_ACCESS_BY_PIN:
mask = BIT(pin);
break;
case GPIO_ACCESS_BY_PORT:
mask = 0xFFFFFFFF;
break;
default:
return -ENOTSUP;
}
cfg->port->PIO_IDR |= mask;
return 0;
}
static const struct gpio_driver_api gpio_sam3_drv_api_funcs = {
.config = gpio_sam3_config,
.write = gpio_sam3_write,
.read = gpio_sam3_read,
.manage_callback = gpio_sam3_manage_callback,
.enable_callback = gpio_sam3_enable_callback,
.disable_callback = gpio_sam3_disable_callback,
};
/**
* @brief Initialization function of MMIO
*
* @param dev Device struct
* @return 0 if successful, failed otherwise.
*/
static int gpio_sam3_init(struct device *dev)
{
const struct gpio_sam3_config *cfg = dev->config->config_info;
cfg->config_func(dev);
return 0;
}
/* Port A */
#ifdef CONFIG_GPIO_ATMEL_SAM3_PORTA
static void gpio_sam3_config_a(struct device *dev);
static const struct gpio_sam3_config gpio_sam3_a_cfg = {
.port = PIOA,
.config_func = gpio_sam3_config_a,
};
static struct gpio_sam3_runtime gpio_sam3_a_runtime;
DEVICE_AND_API_INIT(gpio_sam3_a, CONFIG_GPIO_ATMEL_SAM3_PORTA_DEV_NAME,
gpio_sam3_init, &gpio_sam3_a_runtime, &gpio_sam3_a_cfg,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam3_drv_api_funcs);
static void gpio_sam3_config_a(struct device *dev)
{
/* Enable clock for PIO controller */
PMC->PMC_PCER0 = BIT(ID_PIOA);
IRQ_CONNECT(PIOA_IRQn, CONFIG_GPIO_ATMEL_SAM3_PORTA_IRQ_PRI,
gpio_sam3_isr, DEVICE_GET(gpio_sam3_a), 0);
irq_enable(PIOA_IRQn);
}
#endif /* CONFIG_GPIO_ATMEL_SAM3_PORTA */
/* Port B */
#ifdef CONFIG_GPIO_ATMEL_SAM3_PORTB
static void gpio_sam3_config_b(struct device *dev);
static const struct gpio_sam3_config gpio_sam3_b_cfg = {
.port = PIOB,
.config_func = gpio_sam3_config_b,
};
static struct gpio_sam3_runtime gpio_sam3_b_runtime;
DEVICE_AND_API_INIT(gpio_sam3_b, CONFIG_GPIO_ATMEL_SAM3_PORTB_DEV_NAME,
gpio_sam3_init, &gpio_sam3_b_runtime, &gpio_sam3_b_cfg,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam3_drv_api_funcs);
static void gpio_sam3_config_b(struct device *dev)
{
/* Enable clock for PIO controller */
PMC->PMC_PCER0 = BIT(ID_PIOB);
IRQ_CONNECT(PIOB_IRQn, CONFIG_GPIO_ATMEL_SAM3_PORTB_IRQ_PRI,
gpio_sam3_isr, DEVICE_GET(gpio_sam3_b), 0);
irq_enable(PIOB_IRQn);
}
#endif /* CONFIG_GPIO_ATMEL_SAM3_PORTB */
/* Port C */
#ifdef CONFIG_GPIO_ATMEL_SAM3_PORTC
static void gpio_sam3_config_c(struct device *dev);
static const struct gpio_sam3_config gpio_sam3_c_cfg = {
.port = PIOC,
.config_func = gpio_sam3_config_c,
};
static struct gpio_sam3_runtime gpio_sam3_c_runtime;
DEVICE_AND_API_INIT(gpio_sam3_c, CONFIG_GPIO_ATMEL_SAM3_PORTC_DEV_NAME,
gpio_sam3_init, &gpio_sam3_c_runtime, &gpio_sam3_c_cfg,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam3_drv_api_funcs);
static void gpio_sam3_config_c(struct device *dev)
{
/* Enable clock for PIO controller */
PMC->PMC_PCER0 = BIT(ID_PIOC);
IRQ_CONNECT(PIOC_IRQn, CONFIG_GPIO_ATMEL_SAM3_PORTC_IRQ_PRI,
gpio_sam3_isr, DEVICE_GET(gpio_sam3_c), 0);
irq_enable(PIOC_IRQn);
}
#endif /* CONFIG_GPIO_ATMEL_SAM3_PORTA */
/* Port D */
#ifdef CONFIG_GPIO_ATMEL_SAM3_PORTD
static void gpio_sam3_config_d(struct device *dev);
static const struct gpio_sam3_config gpio_sam3_d_cfg = {
.port = PIOD,
.config_func = gpio_sam3_config_d,
};
static struct gpio_sam3_runtime gpio_sam3_d_runtime;
DEVICE_AND_API_INIT(gpio_sam3_d, CONFIG_GPIO_ATMEL_SAM3_PORTD_DEV_NAME,
gpio_sam3_init, &gpio_sam3_d_runtime, &gpio_sam3_d_cfg,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&gpio_sam3_drv_api_funcs);
static void gpio_sam3_config_d(struct device *dev)
{
/* Enable clock for PIO controller */
PMC->PMC_PCER0 = BIT(ID_PIOD);
IRQ_CONNECT(PIOD_IRQn, CONFIG_GPIO_ATMEL_SAM3_PORTD_IRQ_PRI,
gpio_sam3_isr, DEVICE_GET(gpio_sam3_d), 0);
irq_enable(PIOD_IRQn);
}
#endif /* CONFIG_GPIO_ATMEL_SAM3_PORTD */