Skip to content
Snippets Groups Projects
Commit 208e8c6d authored by Tobias Kahlki's avatar Tobias Kahlki Committed by Tobias Kahlki
Browse files

driver:pi4io: Added IRQ support

BCS 746-001420
parent f3b87af9
No related branches found
No related tags found
No related merge requests found
......@@ -8,6 +8,7 @@
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>
......@@ -16,7 +17,9 @@
#define PI4IO_IO_DIRECTION 0x3
#define PI4IO_OUTPUT 0x5
#define PI4IO_OUTPUT_HI_IMPEDANCE 0x7
#define PI4IO_INPUT_DEFAULT_STATE 0x9
#define PI4IO_INPUT_STATUS 0xF
#define PI4IO_INTERRUPT_MASK 0x11
#define PI4IO_INTERRUPT_STATUS 0x13
#define PI4IO_CHIP_ID_VAL 0xA0
......@@ -67,6 +70,11 @@ struct pi4io_priv {
struct regmap *regmap;
struct gpio_chip gpio;
struct gpio_desc *reset_gpio;
struct mutex lock;
struct irq_chip irqchip;
unsigned irq_type[8];
unsigned irq_enabled;
unsigned irq_status;
};
static int pi4io_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
......@@ -212,12 +220,210 @@ static int pi4io_reset(struct pi4io_priv *pi4io)
return 0;
};
static irqreturn_t pi4io_irq(int irq, void *data)
{
struct pi4io_priv *pi4io = data;
struct device *dev = &pi4io->i2c->dev;
unsigned long change, offset;
int ret, status;
ret = regmap_read(pi4io->regmap, PI4IO_INTERRUPT_STATUS, &status);
if (ret < 0) {
dev_err(dev, "Failed to read interrupts: %d", ret);
return IRQ_NONE;
}
/*
* Only if the interrupt for a specific port is enabled,
* the irq_mapping further down is working. To prevent
* errors from popping up, we only process interrupts
* for ports that are enabled.
*/
mutex_lock(&pi4io->lock);
change = status & pi4io->irq_enabled;
mutex_unlock(&pi4io->lock);
dev_dbg(dev, "irq_enabled: %d status: %d change: %lu",
pi4io->irq_enabled, status, change);
for_each_set_bit(offset, &change, pi4io->gpio.ngpio) {
ret = irq_find_mapping(pi4io->gpio.irq.domain, offset);
if (ret != 0) {
handle_nested_irq(ret);
dev_dbg(dev, "IRQ handled");
} else {
dev_err(dev, "Invalid IRQ mapping");
return IRQ_NONE;
}
}
return IRQ_HANDLED;
}
static void pi4io_irq_ack(struct irq_data *data)
{
/* NOOP */
}
static void pi4io_irq_mask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
dev_dbg(&pi4io->i2c->dev, "Masking hwirq %lu", hwirq);
gpiochip_disable_irq(&pi4io->gpio, hwirq);
}
static void pi4io_irq_unmask(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
irq_hw_number_t hwirq = irqd_to_hwirq(data);
dev_dbg(&pi4io->i2c->dev, "Unmasking hwirq %lu", hwirq);
gpiochip_enable_irq(&pi4io->gpio, hwirq);
}
static int pi4io_irq_set_wake(struct irq_data *data, unsigned int on)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
dev_dbg(&pi4io->i2c->dev, "Set wake");
return irq_set_irq_wake(pi4io->i2c->irq, on);
}
static void pi4io_irq_enable(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
pi4io->irq_enabled |= (1 << data->hwirq);
dev_dbg(&pi4io->i2c->dev, "Enabled IRQs: %u",
pi4io->irq_enabled);
}
static void pi4io_irq_disable(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
pi4io->irq_enabled &= ~(1 << data->hwirq);
dev_dbg(&pi4io->i2c->dev, "Enabled IRQs: %u",
pi4io->irq_enabled);
}
static void pi4io_irq_bus_lock(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
dev_dbg(&pi4io->i2c->dev, "Lock bus");
mutex_lock(&pi4io->lock);
}
static void pi4io_irq_bus_sync_unlock(struct irq_data *data)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
int ret;
/*
* The regmap update should be done in the irq_enable function.
* However, accessing the regmap in the enable/disable function
* leads to a segmentation fault.
*/
ret = regmap_update_bits(pi4io->regmap, PI4IO_INPUT_DEFAULT_STATE, 1 << data->hwirq,
pi4io->irq_type[data->hwirq] << data->hwirq);
if (ret)
dev_err(&pi4io->i2c->dev, "Unable to set interrupt for %lu",
data->hwirq);
ret = regmap_update_bits(pi4io->regmap, PI4IO_INTERRUPT_MASK, 1 << data->hwirq,
~pi4io->irq_enabled & (1 << data->hwirq));
if (ret)
dev_err(&pi4io->i2c->dev, "Masking of %lu failed",
data->hwirq);
mutex_unlock(&pi4io->lock);
dev_dbg(&pi4io->i2c->dev, "Unlock bus");
}
static int pi4io_irq_set_type(struct irq_data *data, unsigned int type)
{
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
unsigned int irq_type;
/*
* The pi4io only supports high/low level interrupts. The right
* way would be to only support those two modes. However, gpiomon
* (and probably other tools too) do crash, if we do not support
* all five modes.
*
* Note: The edge both mode doesn't work and will do the same as
* the rising edge interrupt.
*/
switch (type) {
case IRQ_TYPE_EDGE_RISING:
case IRQ_TYPE_EDGE_BOTH:
case IRQ_TYPE_LEVEL_HIGH:
dev_dbg(&pi4io->i2c->dev, "Set rising edge IRQ for %lu",
data->hwirq);
irq_type = 0;
break;
case IRQ_TYPE_EDGE_FALLING:
case IRQ_TYPE_LEVEL_LOW:
dev_dbg(&pi4io->i2c->dev, "Set falling edge IRQ for %lu",
data->hwirq);
irq_type = 1;
break;
default:
dev_err(&pi4io->i2c->dev, "Invalid IRQ type %u for %lu",
type, data->hwirq);
return -EINVAL;
}
pi4io->irq_type[data->hwirq] = irq_type;
return 0;
}
static int pi4io_irq_init_hw(struct gpio_chip *gc)
{
struct pi4io_priv *pi4io = gpiochip_get_data(gc);
int ret, status;
/*
* Disable (mask) all interrupts and read the status
* register. Reading the status register resets all
* active interrupts.
*/
pi4io->irq_enabled = 0;
ret = regmap_update_bits(pi4io->regmap, PI4IO_INTERRUPT_MASK, 0xFF, 0xFF);
if (ret)
dev_err(&pi4io->i2c->dev, "Masking of all interrupts failed");
ret = regmap_read(pi4io->regmap, PI4IO_INTERRUPT_STATUS, &status);
if (ret < 0)
dev_err(&pi4io->i2c->dev, "Failed to read interrupts: %d", ret);
dev_dbg(&pi4io->i2c->dev, "Current interrupt status: %d", status);
return 0;
}
/*
* @TODO Rework the return/error handling
*/
static int pi4io_probe(
struct i2c_client *client, const struct i2c_device_id *id)
{
int ret, chip_id;
struct device *dev = &client->dev;
struct pi4io_priv *pi4io;
struct gpio_irq_chip *gpio_irqchip;
pi4io = devm_kzalloc(dev, sizeof(struct pi4io_priv), GFP_KERNEL);
if (!pi4io) {
......@@ -245,6 +451,40 @@ static int pi4io_probe(
return -EINVAL;
}
if (pi4io->i2c->irq) {
dev_dbg(dev, "Setup IRQ");
pi4io->irqchip.name = "pi4io";
pi4io->irqchip.irq_enable = pi4io_irq_enable;
pi4io->irqchip.irq_disable = pi4io_irq_disable;
pi4io->irqchip.irq_ack = pi4io_irq_ack;
pi4io->irqchip.irq_mask = pi4io_irq_mask;
pi4io->irqchip.irq_unmask = pi4io_irq_unmask;
pi4io->irqchip.irq_set_wake = pi4io_irq_set_wake;
pi4io->irqchip.irq_bus_lock = pi4io_irq_bus_lock;
pi4io->irqchip.irq_bus_sync_unlock = pi4io_irq_bus_sync_unlock;
pi4io->irqchip.irq_set_type = pi4io_irq_set_type;
ret = devm_request_threaded_irq(dev, pi4io->i2c->irq,
NULL, pi4io_irq, IRQF_ONESHOT |
IRQF_TRIGGER_FALLING | IRQF_SHARED,
dev_name(dev), pi4io);
if (ret)
goto fail;
gpio_irqchip = &pi4io->gpio.irq;
gpio_irqchip->chip = &pi4io->irqchip;
/* This will let us handle the parent IRQ in the driver */
gpio_irqchip->parent_handler = NULL;
gpio_irqchip->num_parents = 0;
gpio_irqchip->parents = NULL;
gpio_irqchip->default_type = IRQ_TYPE_NONE;
gpio_irqchip->handler = handle_level_irq;
gpio_irqchip->init_hw = pi4io_irq_init_hw;
gpio_irqchip->threaded = true;
}
ret = pi4io_gpio_setup(pi4io);
if (ret < 0) {
dev_err(dev, "Failed to setup GPIOs: %d", ret);
......@@ -253,6 +493,12 @@ static int pi4io_probe(
dev_dbg(dev, "PI4IO probe finished");
return 0;
fail:
dev_err(dev, "PI4IO probe error %d for '%s'\n", ret,
pi4io->i2c->name);
return ret;
}
static const struct i2c_device_id pi4io_id_table[] = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment