Changes signature so it takes uint32_t instead of pointer to a register. Later `sys_read*` and `sys_write*` functions are used, which cast given address to volatile pointer anyway. This required changing types of some fields in LiteX GPIO driver and removal of two casts in clock control driver. There was a weird assert from LiteX GPIO driver, which checked whether size of first register in dts was a multiple of 4. It didn't make much sense, so I removed it. Previous dts was describing size of a register in terms of subregisters used. New one uses size of register, so right now it is almost always 4 bytes. Most drivers don't read register size from dts anyway, so only changes had to be made in GPIO and clock control drivers. Both use `litex_read` and `litex_write` to operate on `n`bytes. Now GPIO driver calculates this `n` value in compile time from given number of pins and stores it in `reg_size` field of config struct like before. Registe sizes in clock control driver are hardcoded, because they are tied to LiteX wrapper anyway. This makes it possible to have code, independent of CSR data width. Signed-off-by: Michal Sieron <msieron@internships.antmicro.com>
334 lines
9 KiB
C
334 lines
9 KiB
C
/*
|
|
* Copyright (c) 2019-2021 Antmicro <www.antmicro.com>
|
|
* Copyright (c) 2021 Raptor Engineering, LLC <sales@raptorengineering.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT litex_gpio
|
|
|
|
#include <errno.h>
|
|
#include <device.h>
|
|
#include <drivers/gpio.h>
|
|
#include <zephyr/types.h>
|
|
#include <sys/util.h>
|
|
#include <string.h>
|
|
#include <logging/log.h>
|
|
|
|
#include "gpio_utils.h"
|
|
|
|
#define SUPPORTED_FLAGS (GPIO_INPUT | GPIO_OUTPUT | \
|
|
GPIO_OUTPUT_INIT_LOW | GPIO_OUTPUT_INIT_HIGH | \
|
|
GPIO_ACTIVE_LOW | GPIO_ACTIVE_HIGH)
|
|
|
|
#define GPIO_LOW 0
|
|
#define GPIO_HIGH 1
|
|
|
|
#define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
|
|
LOG_MODULE_REGISTER(gpio_litex);
|
|
|
|
static const char *LITEX_LOG_REG_SIZE_NGPIOS_MISMATCH =
|
|
"Cannot handle all of the gpios with the register of given size\n";
|
|
static const char *LITEX_LOG_CANNOT_CHANGE_DIR =
|
|
"Cannot change port direction selected in device tree\n";
|
|
|
|
struct gpio_litex_cfg {
|
|
uint32_t reg_addr;
|
|
int reg_size;
|
|
uint32_t ev_pending_addr;
|
|
uint32_t ev_enable_addr;
|
|
uint32_t ev_mode_addr;
|
|
uint32_t ev_edge_addr;
|
|
int nr_gpios;
|
|
bool port_is_output;
|
|
};
|
|
|
|
struct gpio_litex_data {
|
|
struct gpio_driver_data common;
|
|
const struct device *dev;
|
|
sys_slist_t cb;
|
|
};
|
|
|
|
/* Helper macros for GPIO */
|
|
|
|
#define DEV_GPIO_CFG(dev) \
|
|
((const struct gpio_litex_cfg *)(dev)->config)
|
|
|
|
/* Helper functions for bit / port access */
|
|
|
|
static inline void set_bit(const struct gpio_litex_cfg *config,
|
|
uint32_t bit, bool val)
|
|
{
|
|
int regv, new_regv;
|
|
|
|
regv = litex_read(config->reg_addr, config->reg_size);
|
|
new_regv = (regv & ~BIT(bit)) | (val << bit);
|
|
litex_write(config->reg_addr, config->reg_size, new_regv);
|
|
}
|
|
|
|
static inline uint32_t get_bit(const struct gpio_litex_cfg *config, uint32_t bit)
|
|
{
|
|
int regv = litex_read(config->reg_addr, config->reg_size);
|
|
|
|
return !!(regv & BIT(bit));
|
|
}
|
|
|
|
static inline void set_port(const struct gpio_litex_cfg *config, uint32_t value)
|
|
{
|
|
litex_write(config->reg_addr, config->reg_size, value);
|
|
}
|
|
|
|
static inline uint32_t get_port(const struct gpio_litex_cfg *config)
|
|
{
|
|
int regv = litex_read(config->reg_addr, config->reg_size);
|
|
|
|
return (regv & BIT_MASK(config->nr_gpios));
|
|
}
|
|
|
|
/* Driver functions */
|
|
|
|
static int gpio_litex_configure(const struct device *dev,
|
|
gpio_pin_t pin, gpio_flags_t flags)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
|
|
if (flags & ~SUPPORTED_FLAGS) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if ((flags & GPIO_OUTPUT) && (flags & GPIO_INPUT)) {
|
|
/* Pin cannot be configured as input and output */
|
|
return -ENOTSUP;
|
|
} else if (!(flags & (GPIO_INPUT | GPIO_OUTPUT))) {
|
|
/* Pin has to be configured as input or output */
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (flags & GPIO_OUTPUT) {
|
|
if (!gpio_config->port_is_output) {
|
|
LOG_ERR("%s", LITEX_LOG_CANNOT_CHANGE_DIR);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (flags & GPIO_OUTPUT_INIT_HIGH) {
|
|
set_bit(gpio_config, pin, GPIO_HIGH);
|
|
} else if (flags & GPIO_OUTPUT_INIT_LOW) {
|
|
set_bit(gpio_config, pin, GPIO_LOW);
|
|
}
|
|
} else {
|
|
if (gpio_config->port_is_output) {
|
|
LOG_ERR("%s", LITEX_LOG_CANNOT_CHANGE_DIR);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_litex_port_get_raw(const struct device *dev,
|
|
gpio_port_value_t *value)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
|
|
*value = get_port(gpio_config);
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_litex_port_set_masked_raw(const struct device *dev,
|
|
gpio_port_pins_t mask,
|
|
gpio_port_value_t value)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
uint32_t port_val;
|
|
|
|
port_val = get_port(gpio_config);
|
|
port_val = (port_val & ~mask) | (value & mask);
|
|
set_port(gpio_config, port_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_litex_port_set_bits_raw(const struct device *dev,
|
|
gpio_port_pins_t pins)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
uint32_t port_val;
|
|
|
|
port_val = get_port(gpio_config);
|
|
port_val |= pins;
|
|
set_port(gpio_config, port_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_litex_port_clear_bits_raw(const struct device *dev,
|
|
gpio_port_pins_t pins)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
uint32_t port_val;
|
|
|
|
port_val = get_port(gpio_config);
|
|
port_val &= ~pins;
|
|
set_port(gpio_config, port_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_litex_port_toggle_bits(const struct device *dev,
|
|
gpio_port_pins_t pins)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
uint32_t port_val;
|
|
|
|
port_val = get_port(gpio_config);
|
|
port_val ^= pins;
|
|
set_port(gpio_config, port_val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void gpio_litex_irq_handler(const struct device *dev)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
struct gpio_litex_data *data = dev->data;
|
|
|
|
uint8_t int_status =
|
|
litex_read(gpio_config->ev_pending_addr, gpio_config->reg_size);
|
|
uint8_t ev_enabled =
|
|
litex_read(gpio_config->ev_enable_addr, gpio_config->reg_size);
|
|
|
|
/* clear events */
|
|
litex_write(gpio_config->ev_pending_addr, gpio_config->reg_size,
|
|
int_status);
|
|
|
|
gpio_fire_callbacks(&data->cb, dev, int_status & ev_enabled);
|
|
}
|
|
|
|
static int gpio_litex_manage_callback(const struct device *dev,
|
|
struct gpio_callback *callback, bool set)
|
|
{
|
|
struct gpio_litex_data *data = dev->data;
|
|
|
|
return gpio_manage_callback(&data->cb, callback, set);
|
|
}
|
|
|
|
static int gpio_litex_pin_interrupt_configure(const struct device *dev,
|
|
gpio_pin_t pin,
|
|
enum gpio_int_mode mode,
|
|
enum gpio_int_trig trig)
|
|
{
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev);
|
|
|
|
if (gpio_config->port_is_output == true) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (mode == GPIO_INT_MODE_EDGE) {
|
|
uint8_t ev_enabled = litex_read(gpio_config->ev_enable_addr,
|
|
gpio_config->reg_size);
|
|
uint8_t ev_mode = litex_read(gpio_config->ev_mode_addr,
|
|
gpio_config->reg_size);
|
|
uint8_t ev_edge = litex_read(gpio_config->ev_edge_addr,
|
|
gpio_config->reg_size);
|
|
|
|
litex_write(gpio_config->ev_enable_addr, gpio_config->reg_size,
|
|
ev_enabled | BIT(pin));
|
|
|
|
if (trig == GPIO_INT_TRIG_HIGH) {
|
|
/* Change mode to 'edge' and edge to 'rising' */
|
|
litex_write(gpio_config->ev_mode_addr, gpio_config->reg_size,
|
|
ev_mode & ~BIT(pin));
|
|
litex_write(gpio_config->ev_edge_addr, gpio_config->reg_size,
|
|
ev_edge & ~BIT(pin));
|
|
} else if (trig == GPIO_INT_TRIG_LOW) {
|
|
/* Change mode to 'edge' and edge to 'falling' */
|
|
litex_write(gpio_config->ev_mode_addr, gpio_config->reg_size,
|
|
ev_mode & ~BIT(pin));
|
|
litex_write(gpio_config->ev_edge_addr, gpio_config->reg_size,
|
|
ev_edge | BIT(pin));
|
|
} else if (trig == GPIO_INT_TRIG_BOTH) {
|
|
/* Change mode to 'change' */
|
|
litex_write(gpio_config->ev_mode_addr, gpio_config->reg_size,
|
|
ev_mode | BIT(pin));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (mode == GPIO_INT_DISABLE) {
|
|
uint8_t ev_enabled = litex_read(gpio_config->ev_enable_addr,
|
|
gpio_config->reg_size);
|
|
litex_write(gpio_config->ev_enable_addr, gpio_config->reg_size,
|
|
ev_enabled & ~BIT(pin));
|
|
return 0;
|
|
}
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static const struct gpio_driver_api gpio_litex_driver_api = {
|
|
.pin_configure = gpio_litex_configure,
|
|
.port_get_raw = gpio_litex_port_get_raw,
|
|
.port_set_masked_raw = gpio_litex_port_set_masked_raw,
|
|
.port_set_bits_raw = gpio_litex_port_set_bits_raw,
|
|
.port_clear_bits_raw = gpio_litex_port_clear_bits_raw,
|
|
.port_toggle_bits = gpio_litex_port_toggle_bits,
|
|
.pin_interrupt_configure = gpio_litex_pin_interrupt_configure,
|
|
.manage_callback = gpio_litex_manage_callback,
|
|
};
|
|
|
|
/* Device Instantiation */
|
|
#define GPIO_LITEX_IRQ_INIT(n) \
|
|
do { \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
|
|
gpio_litex_irq_handler, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
\
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
} while (0)
|
|
|
|
#define GPIO_LITEX_INIT(n) \
|
|
static int gpio_litex_port_init_##n(const struct device *dev); \
|
|
\
|
|
static const struct gpio_litex_cfg gpio_litex_cfg_##n = { \
|
|
.reg_addr = DT_INST_REG_ADDR(n), \
|
|
.reg_size = DT_INST_REG_SIZE(n), \
|
|
.nr_gpios = DT_INST_PROP(n, ngpios), \
|
|
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), ( \
|
|
.ev_mode_addr = DT_INST_REG_ADDR_BY_NAME(n, irq_mode), \
|
|
.ev_edge_addr = DT_INST_REG_ADDR_BY_NAME(n, irq_edge), \
|
|
.ev_pending_addr = DT_INST_REG_ADDR_BY_NAME(n, irq_pend), \
|
|
.ev_enable_addr = DT_INST_REG_ADDR_BY_NAME(n, irq_en), \
|
|
)) \
|
|
.port_is_output = DT_INST_PROP(n, port_is_output), \
|
|
}; \
|
|
static struct gpio_litex_data gpio_litex_data_##n; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, \
|
|
gpio_litex_port_init_##n, \
|
|
NULL, \
|
|
&gpio_litex_data_##n, \
|
|
&gpio_litex_cfg_##n, \
|
|
POST_KERNEL, \
|
|
CONFIG_GPIO_INIT_PRIORITY, \
|
|
&gpio_litex_driver_api \
|
|
); \
|
|
\
|
|
static int gpio_litex_port_init_##n(const struct device *dev) \
|
|
{ \
|
|
const struct gpio_litex_cfg *gpio_config = DEV_GPIO_CFG(dev); \
|
|
\
|
|
/* Check if gpios fit in declared register space */ \
|
|
/* Number of subregisters times size in bits */ \
|
|
const int max_gpios_can_fit = DT_INST_REG_SIZE(n) / 4 \
|
|
* CONFIG_LITEX_CSR_DATA_WIDTH; \
|
|
if (gpio_config->nr_gpios > max_gpios_can_fit) { \
|
|
LOG_ERR("%s", LITEX_LOG_REG_SIZE_NGPIOS_MISMATCH); \
|
|
return -EINVAL; \
|
|
} \
|
|
\
|
|
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), \
|
|
(GPIO_LITEX_IRQ_INIT(n);)) \
|
|
return 0; \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(GPIO_LITEX_INIT)
|