Browse Source
This adds support for the GRLIB GRGPIO2 controller used in LEON and NOEL-V systems. Signed-off-by: Martin Åberg <martin.aberg@gaisler.com>pull/76436/head
6 changed files with 384 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||||
|
# Copyright (c) 2023 Frontgrade Gaisler AB |
||||||
|
# SPDX-License-Identifier: Apache-2.0 |
||||||
|
|
||||||
|
config GPIO_GRGPIO2 |
||||||
|
bool "GRLIB GRGPIO revision 2" |
||||||
|
default y |
||||||
|
depends on DT_HAS_GAISLER_GRGPIO_ENABLED |
||||||
|
help |
||||||
|
Enable driver for GRLIB GRGPIO revision 2. |
@ -0,0 +1,53 @@ |
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Frontgrade Gaisler AB |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
#ifndef ZEPHYR_DRIVERS_GPIO_GPIO_GRGPIO_H_ |
||||||
|
#define ZEPHYR_DRIVERS_GPIO_GPIO_GRGPIO_H_ |
||||||
|
|
||||||
|
struct grgpio_regs { |
||||||
|
uint32_t data; /* 0x00 I/O port data register */ |
||||||
|
uint32_t output; /* 0x04 I/O port output register */ |
||||||
|
uint32_t dir; /* 0x08 I/O port direction register */ |
||||||
|
uint32_t imask; /* 0x0C Interrupt mask register */ |
||||||
|
uint32_t ipol; /* 0x10 Interrupt polarity register */ |
||||||
|
uint32_t iedge; /* 0x14 Interrupt edge register */ |
||||||
|
uint32_t bypass; /* 0x18 Bypass register */ |
||||||
|
uint32_t cap; /* 0x1C Capability register */ |
||||||
|
uint32_t irqmap[4]; /* 0x20 - 0x2C Interrupt map registers */ |
||||||
|
uint32_t res_30; /* 0x30 Reserved */ |
||||||
|
uint32_t res_34; /* 0x34 Reserved */ |
||||||
|
uint32_t res_38; /* 0x38 Reserved */ |
||||||
|
uint32_t res_3C; /* 0x3C Reserved */ |
||||||
|
uint32_t iavail; /* 0x40 Interrupt available register */ |
||||||
|
uint32_t iflag; /* 0x44 Interrupt flag register */ |
||||||
|
uint32_t res_48; /* 0x48 Reserved */ |
||||||
|
uint32_t pulse; /* 0x4C Pulse register */ |
||||||
|
uint32_t res_50; /* 0x50 Reserved */ |
||||||
|
uint32_t output_or; /* 0x54 I/O port output register, logical-OR */ |
||||||
|
uint32_t dir_or; /* 0x58 I/O port dir. register, logical-OR */ |
||||||
|
uint32_t imask_or; /* 0x5C Interrupt mask register, logical-OR */ |
||||||
|
uint32_t res_60; /* 0x60 Reserved */ |
||||||
|
uint32_t output_and; /* 0x64 I/O port output register, logical-AND */ |
||||||
|
uint32_t dir_and; /* 0x68 I/O port dir. register, logical-AND */ |
||||||
|
uint32_t imask_and; /* 0x6C Interrupt mask register, logical-AND */ |
||||||
|
uint32_t res_70; /* 0x70 Reserved */ |
||||||
|
uint32_t output_xor; /* 0x74 I/O port output register, logical-XOR */ |
||||||
|
uint32_t dir_xor; /* 0x78 I/O port dir. register, logical-XOR */ |
||||||
|
uint32_t imask_xor; /* 0x7C Interrupt mask register, logical-XOR */ |
||||||
|
}; |
||||||
|
|
||||||
|
#define GRGPIO_CAP_PU_BIT 18 |
||||||
|
#define GRGPIO_CAP_IER_BIT 17 |
||||||
|
#define GRGPIO_CAP_IFL_BIT 16 |
||||||
|
#define GRGPIO_CAP_IRQGEN_BIT 8 |
||||||
|
#define GRGPIO_CAP_NLINES_BIT 0 |
||||||
|
|
||||||
|
#define GRGPIO_CAP_PU (0x1 << GRGPIO_CAP_PU_BIT) |
||||||
|
#define GRGPIO_CAP_IER (0x1 << GRGPIO_CAP_IER_BIT) |
||||||
|
#define GRGPIO_CAP_IFL (0x1 << GRGPIO_CAP_IFL_BIT) |
||||||
|
#define GRGPIO_CAP_IRQGEN (0x1f << GRGPIO_CAP_IRQGEN_BIT) |
||||||
|
#define GRGPIO_CAP_NLINES (0x1f << GRGPIO_CAP_NLINES_BIT) |
||||||
|
|
||||||
|
#endif /* ZEPHYR_DRIVERS_GPIO_GPIO_GRGPIO_H_ */ |
@ -0,0 +1,304 @@ |
|||||||
|
/*
|
||||||
|
* 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) |
Loading…
Reference in new issue