305 lines
7.0 KiB
C
305 lines
7.0 KiB
C
|
/*
|
||
|
* Copyright (c) 2023 Frontgrade Gaisler AB
|
||
|
*
|
||
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Driver for GRLIB GRGPIO revision 2.
|
||
|
* - iflag determine pending interrupt.
|
||
|
* - interrupt map decides interrupt number if implemented.
|
||
|
* - logic or/and/xor registers used when possible
|
||
|
*/
|
||
|
|
||
|
#define DT_DRV_COMPAT gaisler_grgpio
|
||
|
|
||
|
#include <zephyr/kernel.h>
|
||
|
#include <zephyr/drivers/gpio.h>
|
||
|
#include <zephyr/drivers/gpio/gpio_utils.h>
|
||
|
#include "gpio_grgpio.h"
|
||
|
|
||
|
#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
|
||
|
#include <zephyr/logging/log.h>
|
||
|
LOG_MODULE_REGISTER(gpio_grgpio2);
|
||
|
|
||
|
struct cfg {
|
||
|
struct gpio_driver_config common;
|
||
|
volatile struct grgpio_regs *regs;
|
||
|
int interrupt;
|
||
|
};
|
||
|
|
||
|
struct data {
|
||
|
struct gpio_driver_data common;
|
||
|
struct k_spinlock lock;
|
||
|
sys_slist_t cb;
|
||
|
uint32_t imask;
|
||
|
uint32_t connected;
|
||
|
int irqgen;
|
||
|
};
|
||
|
|
||
|
static void grgpio_isr(const struct device *dev);
|
||
|
|
||
|
static int pin_configure(const struct device *dev,
|
||
|
gpio_pin_t pin, gpio_flags_t flags)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
struct data *data = dev->data;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
uint32_t mask = 1 << pin;
|
||
|
|
||
|
if (flags & GPIO_SINGLE_ENDED) {
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
|
||
|
if (flags == GPIO_DISCONNECTED) {
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
|
||
|
if ((flags & GPIO_DIR_MASK) == (GPIO_INPUT | GPIO_OUTPUT)) {
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
|
||
|
if ((flags & (GPIO_PULL_UP | GPIO_PULL_DOWN)) != 0) {
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
|
||
|
if (flags & GPIO_OUTPUT) {
|
||
|
k_spinlock_key_t key;
|
||
|
|
||
|
/*
|
||
|
* Register operations are atomic, but do the sequence under
|
||
|
* lock so it serializes.
|
||
|
*/
|
||
|
key = k_spin_lock(&data->lock);
|
||
|
if (flags & GPIO_OUTPUT_INIT_HIGH) {
|
||
|
regs->output_or = mask;
|
||
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
|
||
|
regs->output_and = ~mask;
|
||
|
}
|
||
|
regs->dir_or = mask;
|
||
|
k_spin_unlock(&data->lock, key);
|
||
|
} else {
|
||
|
regs->dir_and = ~mask;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_get_raw(const struct device *dev, gpio_port_value_t *value)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
|
||
|
*value = cfg->regs->data;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_set_masked_raw(const struct device *dev,
|
||
|
gpio_port_pins_t mask,
|
||
|
gpio_port_value_t value)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
struct data *data = dev->data;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
uint32_t port_val;
|
||
|
k_spinlock_key_t key;
|
||
|
|
||
|
value &= mask;
|
||
|
key = k_spin_lock(&data->lock);
|
||
|
port_val = (regs->output & ~mask) | value;
|
||
|
regs->output = port_val;
|
||
|
k_spin_unlock(&data->lock, key);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_set_bits_raw(const struct device *dev, gpio_port_pins_t pins)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
|
||
|
regs->output_or = pins;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_clear_bits_raw(const struct device *dev, gpio_port_pins_t pins)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
|
||
|
regs->output_and = ~pins;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int port_toggle_bits(const struct device *dev, gpio_port_pins_t pins)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
|
||
|
regs->output_xor = pins;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static uint32_t get_pending_int(const struct device *dev)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
|
||
|
return regs->iflag;
|
||
|
}
|
||
|
|
||
|
static int pin_interrupt_configure(const struct device *dev,
|
||
|
gpio_pin_t pin,
|
||
|
enum gpio_int_mode mode,
|
||
|
enum gpio_int_trig trig)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
struct data *data = dev->data;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
int ret = 0;
|
||
|
const uint32_t mask = 1 << pin;
|
||
|
uint32_t polmask;
|
||
|
k_spinlock_key_t key;
|
||
|
|
||
|
if ((mask & data->imask) == 0) {
|
||
|
/* This pin can not generate interrupt */
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
if (mode != GPIO_INT_MODE_DISABLED) {
|
||
|
if (trig == GPIO_INT_TRIG_LOW) {
|
||
|
polmask = 0;
|
||
|
} else if (trig == GPIO_INT_TRIG_HIGH) {
|
||
|
polmask = mask;
|
||
|
} else {
|
||
|
return -ENOTSUP;
|
||
|
}
|
||
|
}
|
||
|
key = k_spin_lock(&data->lock);
|
||
|
if (mode == GPIO_INT_MODE_DISABLED) {
|
||
|
regs->imask_and = ~mask;
|
||
|
} else if (mode == GPIO_INT_MODE_LEVEL) {
|
||
|
regs->imask_and = ~mask;
|
||
|
regs->iedge &= ~mask;
|
||
|
regs->ipol = (regs->ipol & ~mask) | polmask;
|
||
|
regs->imask_or = mask;
|
||
|
} else if (mode == GPIO_INT_MODE_EDGE) {
|
||
|
regs->imask_and = ~mask;
|
||
|
regs->iedge |= mask;
|
||
|
regs->ipol = (regs->ipol & ~mask) | polmask;
|
||
|
regs->imask_or = mask;
|
||
|
} else {
|
||
|
ret = -ENOTSUP;
|
||
|
}
|
||
|
k_spin_unlock(&data->lock, key);
|
||
|
|
||
|
/* Remove old interrupt history for this pin. */
|
||
|
regs->iflag = mask;
|
||
|
|
||
|
int interrupt = cfg->interrupt;
|
||
|
const int irqgen = data->irqgen;
|
||
|
|
||
|
if (irqgen == 0) {
|
||
|
interrupt += pin;
|
||
|
} else if (irqgen == 1) {
|
||
|
;
|
||
|
} else if (irqgen < 32) {
|
||
|
/* look up interrupt number in GRGPIO interrupt map */
|
||
|
uint32_t val = regs->irqmap[pin/4];
|
||
|
|
||
|
val >>= (3 - pin % 4) * 8;
|
||
|
interrupt += (val & 0x1f);
|
||
|
}
|
||
|
|
||
|
if (interrupt && ((1 << interrupt) & data->connected) == 0) {
|
||
|
irq_connect_dynamic(
|
||
|
interrupt,
|
||
|
0,
|
||
|
(void (*)(const void *)) grgpio_isr,
|
||
|
dev,
|
||
|
0
|
||
|
);
|
||
|
irq_enable(interrupt);
|
||
|
data->connected |= 1 << interrupt;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int manage_callback(const struct device *dev,
|
||
|
struct gpio_callback *callback,
|
||
|
bool set)
|
||
|
{
|
||
|
struct data *data = dev->data;
|
||
|
|
||
|
return gpio_manage_callback(&data->cb, callback, set);
|
||
|
}
|
||
|
|
||
|
static void grgpio_isr(const struct device *dev)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
struct data *data = dev->data;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
uint32_t pins;
|
||
|
|
||
|
/* no locking needed when iflag is implemented */
|
||
|
pins = regs->iflag;
|
||
|
if (pins == 0) {
|
||
|
return;
|
||
|
}
|
||
|
regs->iflag = pins;
|
||
|
gpio_fire_callbacks(&data->cb, dev, pins);
|
||
|
}
|
||
|
|
||
|
static int grgpio_init(const struct device *dev)
|
||
|
{
|
||
|
const struct cfg *cfg = dev->config;
|
||
|
struct data *data = dev->data;
|
||
|
volatile struct grgpio_regs *regs = cfg->regs;
|
||
|
|
||
|
data->irqgen = (regs->cap & GRGPIO_CAP_IRQGEN) >> GRGPIO_CAP_IRQGEN_BIT;
|
||
|
regs->dir = 0;
|
||
|
/* Mask all Interrupts */
|
||
|
regs->imask = 0;
|
||
|
/* Make IRQ Rising edge triggered default */
|
||
|
regs->ipol = 0xffffffff;
|
||
|
regs->iedge = 0xffffffff;
|
||
|
regs->iflag = 0xffffffff;
|
||
|
/* Read what I/O lines have IRQ support */
|
||
|
data->imask = regs->ipol;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct gpio_driver_api driver_api = {
|
||
|
.pin_configure = pin_configure,
|
||
|
.port_get_raw = port_get_raw,
|
||
|
.port_set_masked_raw = port_set_masked_raw,
|
||
|
.port_set_bits_raw = port_set_bits_raw,
|
||
|
.port_clear_bits_raw = port_clear_bits_raw,
|
||
|
.port_toggle_bits = port_toggle_bits,
|
||
|
.pin_interrupt_configure = pin_interrupt_configure,
|
||
|
.manage_callback = manage_callback,
|
||
|
.get_pending_int = get_pending_int,
|
||
|
};
|
||
|
|
||
|
#define GRGPIO_INIT(n) \
|
||
|
static const struct cfg cfg_##n = { \
|
||
|
.common = { \
|
||
|
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(n),\
|
||
|
}, \
|
||
|
.regs = (void *) DT_INST_REG_ADDR(n), \
|
||
|
.interrupt = DT_INST_IRQN(n), \
|
||
|
}; \
|
||
|
static struct data data_##n; \
|
||
|
\
|
||
|
DEVICE_DT_INST_DEFINE(n, \
|
||
|
grgpio_init, \
|
||
|
NULL, \
|
||
|
&data_##n, \
|
||
|
&cfg_##n, \
|
||
|
POST_KERNEL, \
|
||
|
CONFIG_GPIO_INIT_PRIORITY, \
|
||
|
&driver_api \
|
||
|
);
|
||
|
|
||
|
DT_INST_FOREACH_STATUS_OKAY(GRGPIO_INIT)
|