505 lines
12 KiB
C
505 lines
12 KiB
C
/*
|
|
* Copyright (c) 2017 Jean-Paul Etienne <fractalclone@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT sifive_gpio0
|
|
|
|
/**
|
|
* @file GPIO driver for the SiFive Freedom Processor
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <kernel.h>
|
|
#include <device.h>
|
|
#include <soc.h>
|
|
#include <drivers/gpio.h>
|
|
#include <sys/util.h>
|
|
|
|
#include "gpio_utils.h"
|
|
|
|
typedef void (*sifive_cfg_func_t)(void);
|
|
|
|
/* sifive GPIO register-set structure */
|
|
struct gpio_sifive_t {
|
|
unsigned int in_val;
|
|
unsigned int in_en;
|
|
unsigned int out_en;
|
|
unsigned int out_val;
|
|
unsigned int pue;
|
|
unsigned int ds;
|
|
unsigned int rise_ie;
|
|
unsigned int rise_ip;
|
|
unsigned int fall_ie;
|
|
unsigned int fall_ip;
|
|
unsigned int high_ie;
|
|
unsigned int high_ip;
|
|
unsigned int low_ie;
|
|
unsigned int low_ip;
|
|
unsigned int iof_en;
|
|
unsigned int iof_sel;
|
|
unsigned int invert;
|
|
};
|
|
|
|
struct gpio_sifive_config {
|
|
/* gpio_driver_config needs to be first */
|
|
struct gpio_driver_config common;
|
|
uintptr_t gpio_base_addr;
|
|
/* multi-level encoded interrupt corresponding to pin 0 */
|
|
uint32_t gpio_irq_base;
|
|
sifive_cfg_func_t gpio_cfg_func;
|
|
};
|
|
|
|
struct gpio_sifive_data {
|
|
/* gpio_driver_data needs to be first */
|
|
struct gpio_driver_data common;
|
|
/* list of callbacks */
|
|
sys_slist_t cb;
|
|
};
|
|
|
|
/* Helper Macros for GPIO */
|
|
#define DEV_GPIO_CFG(dev) \
|
|
((const struct gpio_sifive_config * const)(dev)->config_info)
|
|
#define DEV_GPIO(dev) \
|
|
((volatile struct gpio_sifive_t *)(DEV_GPIO_CFG(dev))->gpio_base_addr)
|
|
#define DEV_GPIO_DATA(dev) \
|
|
((struct gpio_sifive_data *)(dev)->driver_data)
|
|
|
|
/* _irq_level and _level2_irq are copied from
|
|
* soc/riscv/riscv-privileged/common/soc_common_irq.c
|
|
* Ideally this kind of thing should be made available in include/irq.h or
|
|
* somewhere similar since the multi-level IRQ format is generic to
|
|
Zephyr, and then both this copy and the one in riscv-privileged
|
|
* be removed for the shared implementation
|
|
*/
|
|
static inline unsigned int _irq_level(unsigned int irq)
|
|
{
|
|
return ((irq >> 8) && 0xff) == 0U ? 1 : 2;
|
|
}
|
|
|
|
static inline unsigned int _level2_irq(unsigned int irq)
|
|
{
|
|
return (irq >> 8) - 1;
|
|
}
|
|
|
|
/* Given gpio_irq_base and the pin number, return the IRQ number for the pin */
|
|
static inline unsigned int gpio_sifive_pin_irq(unsigned int base_irq, int pin)
|
|
{
|
|
unsigned int level = _irq_level(base_irq);
|
|
unsigned int pin_irq = 0;
|
|
|
|
if (level == 1) {
|
|
pin_irq = base_irq + pin;
|
|
} else if (level == 2) {
|
|
pin_irq = base_irq + (pin << 8);
|
|
}
|
|
|
|
return pin_irq;
|
|
}
|
|
|
|
/* Given the PLIC source number, return the number of the GPIO pin associated
|
|
* with the interrupt
|
|
*/
|
|
static inline int gpio_sifive_plic_to_pin(unsigned int base_irq, int plic_irq)
|
|
{
|
|
unsigned int level = _irq_level(base_irq);
|
|
|
|
if (level == 2) {
|
|
base_irq = _level2_irq(base_irq);
|
|
}
|
|
|
|
return (plic_irq - base_irq);
|
|
}
|
|
|
|
static void gpio_sifive_irq_handler(void *arg)
|
|
{
|
|
struct device *dev = (struct device *)arg;
|
|
struct gpio_sifive_data *data = DEV_GPIO_DATA(dev);
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
const struct gpio_sifive_config *cfg = DEV_GPIO_CFG(dev);
|
|
|
|
/* Calculate pin and mask from base level 2 line */
|
|
uint8_t pin = 1 + (riscv_plic_get_irq() - (uint8_t)(cfg->gpio_irq_base >> 8));
|
|
|
|
/* This peripheral tracks each condition separately: a
|
|
* transition from low to high will mark the pending bit for
|
|
* both rise and high, while low will probably be set from the
|
|
* previous state.
|
|
*
|
|
* It is certainly possible, especially on double-edge, that
|
|
* multiple conditions are present. However, there is no way
|
|
* to tell which one occurred first, and no provision to
|
|
* indicate which one occurred in the callback.
|
|
*
|
|
* Clear all the conditions so we only invoke the callback
|
|
* once. Level conditions will remain set after clear.
|
|
*/
|
|
gpio->rise_ip = BIT(pin);
|
|
gpio->fall_ip = BIT(pin);
|
|
gpio->high_ip = BIT(pin);
|
|
gpio->low_ip = BIT(pin);
|
|
|
|
/* Call the corresponding callback registered for the pin */
|
|
gpio_fire_callbacks(&data->cb, dev, BIT(pin));
|
|
}
|
|
|
|
/**
|
|
* @brief Configure pin
|
|
*
|
|
* @param dev Device structure
|
|
* @param pin The pin number
|
|
* @param flags Flags of pin or port
|
|
*
|
|
* @return 0 if successful, failed otherwise
|
|
*/
|
|
static int gpio_sifive_config(struct device *dev,
|
|
gpio_pin_t pin,
|
|
gpio_flags_t flags)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
if (pin >= SIFIVE_PINMUX_PINS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* We cannot support open-source open-drain configuration */
|
|
if ((flags & GPIO_SINGLE_ENDED) != 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* We only support pull-ups, not pull-downs */
|
|
if ((flags & GPIO_PULL_DOWN) != 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Set pull-up if requested */
|
|
WRITE_BIT(gpio->pue, pin, flags & GPIO_PULL_UP);
|
|
|
|
/* Set the initial output value before enabling output to avoid
|
|
* glitches
|
|
*/
|
|
if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0) {
|
|
gpio->out_val |= BIT(pin);
|
|
}
|
|
if ((flags & GPIO_OUTPUT_INIT_LOW) != 0) {
|
|
gpio->out_val &= ~BIT(pin);
|
|
}
|
|
|
|
/* Enable input/output */
|
|
WRITE_BIT(gpio->out_en, pin, flags & GPIO_OUTPUT);
|
|
WRITE_BIT(gpio->in_en, pin, flags & GPIO_INPUT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_port_get_raw(struct device *dev,
|
|
gpio_port_value_t *value)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
*value = gpio->in_val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_port_set_masked_raw(struct device *dev,
|
|
gpio_port_pins_t mask,
|
|
gpio_port_value_t value)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
gpio->out_val = (gpio->out_val & ~mask) | (value & mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_port_set_bits_raw(struct device *dev,
|
|
gpio_port_pins_t mask)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
gpio->out_val |= mask;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_port_clear_bits_raw(struct device *dev,
|
|
gpio_port_pins_t mask)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
gpio->out_val &= ~mask;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_port_toggle_bits(struct device *dev,
|
|
gpio_port_pins_t mask)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
|
|
gpio->out_val ^= mask;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_pin_interrupt_configure(struct device *dev,
|
|
gpio_pin_t pin,
|
|
enum gpio_int_mode mode,
|
|
enum gpio_int_trig trig)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
const struct gpio_sifive_config *cfg = DEV_GPIO_CFG(dev);
|
|
|
|
gpio->rise_ie &= ~BIT(pin);
|
|
gpio->fall_ie &= ~BIT(pin);
|
|
gpio->high_ie &= ~BIT(pin);
|
|
gpio->low_ie &= ~BIT(pin);
|
|
|
|
switch (mode) {
|
|
case GPIO_INT_MODE_DISABLED:
|
|
irq_disable(gpio_sifive_pin_irq(cfg->gpio_irq_base, pin));
|
|
break;
|
|
case GPIO_INT_MODE_LEVEL:
|
|
/* Board supports both levels, but Zephyr does not. */
|
|
if (trig == GPIO_INT_TRIG_HIGH) {
|
|
gpio->high_ip = BIT(pin);
|
|
gpio->high_ie |= BIT(pin);
|
|
} else {
|
|
__ASSERT_NO_MSG(trig == GPIO_INT_TRIG_LOW);
|
|
gpio->low_ip = BIT(pin);
|
|
gpio->low_ie |= BIT(pin);
|
|
}
|
|
irq_enable(gpio_sifive_pin_irq(cfg->gpio_irq_base, pin));
|
|
break;
|
|
case GPIO_INT_MODE_EDGE:
|
|
__ASSERT_NO_MSG(GPIO_INT_TRIG_BOTH ==
|
|
(GPIO_INT_LOW_0 | GPIO_INT_HIGH_1));
|
|
|
|
if ((trig & GPIO_INT_HIGH_1) != 0) {
|
|
gpio->rise_ip = BIT(pin);
|
|
gpio->rise_ie |= BIT(pin);
|
|
}
|
|
if ((trig & GPIO_INT_LOW_0) != 0) {
|
|
gpio->fall_ip = BIT(pin);
|
|
gpio->fall_ie |= BIT(pin);
|
|
}
|
|
irq_enable(gpio_sifive_pin_irq(cfg->gpio_irq_base, pin));
|
|
break;
|
|
default:
|
|
__ASSERT(false, "Invalid MODE %d passed to driver", mode);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_manage_callback(struct device *dev,
|
|
struct gpio_callback *callback,
|
|
bool set)
|
|
{
|
|
struct gpio_sifive_data *data = DEV_GPIO_DATA(dev);
|
|
|
|
return gpio_manage_callback(&data->cb, callback, set);
|
|
}
|
|
|
|
static int gpio_sifive_enable_callback(struct device *dev,
|
|
gpio_pin_t pin)
|
|
{
|
|
const struct gpio_sifive_config *cfg = DEV_GPIO_CFG(dev);
|
|
|
|
if (pin >= SIFIVE_PINMUX_PINS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Enable interrupt for the pin at PLIC (level 2) */
|
|
irq_enable(cfg->gpio_irq_base + (pin << 8));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_sifive_disable_callback(struct device *dev,
|
|
gpio_pin_t pin)
|
|
{
|
|
const struct gpio_sifive_config *cfg = DEV_GPIO_CFG(dev);
|
|
|
|
if (pin >= SIFIVE_PINMUX_PINS) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Disable interrupt for the pin at PLIC (level 2) */
|
|
irq_disable(cfg->gpio_irq_base + (pin << 8));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct gpio_driver_api gpio_sifive_driver = {
|
|
.pin_configure = gpio_sifive_config,
|
|
.port_get_raw = gpio_sifive_port_get_raw,
|
|
.port_set_masked_raw = gpio_sifive_port_set_masked_raw,
|
|
.port_set_bits_raw = gpio_sifive_port_set_bits_raw,
|
|
.port_clear_bits_raw = gpio_sifive_port_clear_bits_raw,
|
|
.port_toggle_bits = gpio_sifive_port_toggle_bits,
|
|
.pin_interrupt_configure = gpio_sifive_pin_interrupt_configure,
|
|
.manage_callback = gpio_sifive_manage_callback,
|
|
.enable_callback = gpio_sifive_enable_callback,
|
|
.disable_callback = gpio_sifive_disable_callback,
|
|
};
|
|
|
|
/**
|
|
* @brief Initialize a GPIO controller
|
|
*
|
|
* Perform basic initialization of a GPIO controller
|
|
*
|
|
* @param dev GPIO device struct
|
|
*
|
|
* @return 0
|
|
*/
|
|
static int gpio_sifive_init(struct device *dev)
|
|
{
|
|
volatile struct gpio_sifive_t *gpio = DEV_GPIO(dev);
|
|
const struct gpio_sifive_config *cfg = DEV_GPIO_CFG(dev);
|
|
|
|
/* Ensure that all gpio registers are reset to 0 initially */
|
|
gpio->in_en = 0U;
|
|
gpio->out_en = 0U;
|
|
gpio->pue = 0U;
|
|
gpio->rise_ie = 0U;
|
|
gpio->fall_ie = 0U;
|
|
gpio->high_ie = 0U;
|
|
gpio->low_ie = 0U;
|
|
gpio->invert = 0U;
|
|
|
|
/* Setup IRQ handler for each gpio pin */
|
|
cfg->gpio_cfg_func();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gpio_sifive_cfg_0(void);
|
|
|
|
static const struct gpio_sifive_config gpio_sifive_config0 = {
|
|
.common = {
|
|
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0),
|
|
},
|
|
.gpio_base_addr = DT_INST_REG_ADDR(0),
|
|
.gpio_irq_base = DT_INST_IRQN(0),
|
|
.gpio_cfg_func = gpio_sifive_cfg_0,
|
|
};
|
|
|
|
static struct gpio_sifive_data gpio_sifive_data0;
|
|
|
|
DEVICE_AND_API_INIT(gpio_sifive_0, DT_INST_LABEL(0),
|
|
gpio_sifive_init,
|
|
&gpio_sifive_data0, &gpio_sifive_config0,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&gpio_sifive_driver);
|
|
|
|
#define IRQ_INIT(n) \
|
|
IRQ_CONNECT(DT_INST_IRQ_BY_IDX(0, n, irq), \
|
|
CONFIG_GPIO_SIFIVE_##n##_PRIORITY, \
|
|
gpio_sifive_irq_handler, \
|
|
DEVICE_GET(gpio_sifive_0), \
|
|
0);
|
|
|
|
static void gpio_sifive_cfg_0(void)
|
|
{
|
|
#if DT_INST_IRQ_HAS_IDX(0, 0)
|
|
IRQ_INIT(0);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 1)
|
|
IRQ_INIT(1);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 2)
|
|
IRQ_INIT(2);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 3)
|
|
IRQ_INIT(3);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 4)
|
|
IRQ_INIT(4);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 5)
|
|
IRQ_INIT(5);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 6)
|
|
IRQ_INIT(6);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 7)
|
|
IRQ_INIT(7);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 8)
|
|
IRQ_INIT(8);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 9)
|
|
IRQ_INIT(9);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 10)
|
|
IRQ_INIT(10);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 11)
|
|
IRQ_INIT(11);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 12)
|
|
IRQ_INIT(12);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 13)
|
|
IRQ_INIT(13);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 14)
|
|
IRQ_INIT(14);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 15)
|
|
IRQ_INIT(15);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 16)
|
|
IRQ_INIT(16);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 17)
|
|
IRQ_INIT(17);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 18)
|
|
IRQ_INIT(18);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 19)
|
|
IRQ_INIT(19);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 20)
|
|
IRQ_INIT(20);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 21)
|
|
IRQ_INIT(21);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 22)
|
|
IRQ_INIT(22);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 23)
|
|
IRQ_INIT(23);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 24)
|
|
IRQ_INIT(24);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 25)
|
|
IRQ_INIT(25);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 26)
|
|
IRQ_INIT(26);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 27)
|
|
IRQ_INIT(27);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 28)
|
|
IRQ_INIT(28);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 29)
|
|
IRQ_INIT(29);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 30)
|
|
IRQ_INIT(30);
|
|
#endif
|
|
#if DT_INST_IRQ_HAS_IDX(0, 31)
|
|
IRQ_INIT(31);
|
|
#endif
|
|
}
|