irq: add virq irq-chip support

This patch support the device to add its interrupt controller as "irq chip"
into generic interrupt framework, the other driver can request its child
interrupt like a real hardware irq.

Example for PMIC:
    GIC-\
        |- ...
        |- GPIO-\
                |- ...
                |- PMIC-\
                        |_ virq_0
                        |_ virq_1
                        |_ virq_2
                        |...
                        |_ virq_n

Change-Id: I17716f3db494a85fc22b23ff18042771a6116da8
Signed-off-by: Joseph Chen <chenjh@rock-chips.com>
This commit is contained in:
Joseph Chen 2019-07-27 10:44:31 +08:00 committed by Jianhong Chen
parent cf34425241
commit 4176611909
5 changed files with 438 additions and 24 deletions

View File

@ -7,4 +7,5 @@
obj-y += irq-gic.o
obj-y += irq-gpio.o
obj-y += irq-generic.o
obj-y += irq-gpio-switch.o
obj-y += irq-gpio-switch.o
obj-y += virq.o

View File

@ -19,6 +19,8 @@ struct irq_desc {
struct irqchip_desc {
struct irq_chip *gic;
struct irq_chip *gpio;
struct irq_chip *virq;
};
static struct irq_desc irq_desc[PLATFORM_MAX_IRQ];
@ -32,13 +34,17 @@ int bad_irq(int irq)
return -EINVAL;
}
if (irq >= PLATFORM_MAX_IRQ) {
IRQ_W("IRQ %d: Out of max supported IRQ(%d)\n",
irq, PLATFORM_MAX_IRQ);
return -EINVAL;
if (irq < PLATFORM_MAX_IRQ) {
if (!irq_desc[irq].handle_irq)
return -EINVAL;
} else {
if (bad_virq(irq)) {
IRQ_E("Unknown virq: %d\n", irq);
return -EINVAL;
}
}
return irq_desc[irq].handle_irq ? 0 : -EINVAL;
return 0;
}
/* general interrupt handler for gpio chip */
@ -81,7 +87,7 @@ int irq_is_busy(int irq)
static int bad_irq_chip(struct irq_chip *chip)
{
return (!chip->name || !chip->irq_init || !chip->irq_enable ||
!chip->irq_disable || !chip->irq_set_type) ? -EINVAL : 0;
!chip->irq_disable) ? -EINVAL : 0;
}
static int __do_arch_irq_init(void)
@ -113,6 +119,12 @@ static int __do_arch_irq_init(void)
goto out;
}
irqchip.virq = arch_virq_get_irqchip();
if (bad_irq_chip(irqchip.virq)) {
IRQ_E("Bad virq irqchip\n");
goto out;
}
ret = irqchip.gic->irq_init();
if (ret) {
IRQ_E("GIC Interrupt setup failed, ret=%d\n", ret);
@ -125,6 +137,12 @@ static int __do_arch_irq_init(void)
goto out;
}
ret = irqchip.virq->irq_init();
if (ret) {
IRQ_E("VIRQ Interrupt setup failed, ret=%d\n", ret);
goto out;
}
return 0;
out:
@ -140,8 +158,10 @@ int irq_handler_enable(int irq)
if (irq < PLATFORM_GIC_MAX_IRQ)
return irqchip.gic->irq_enable(irq);
else
else if (irq < PLATFORM_GPIO_MAX_IRQ)
return irqchip.gpio->irq_enable(irq);
else
return irqchip.virq->irq_enable(irq);
}
int irq_handler_disable(int irq)
@ -151,8 +171,10 @@ int irq_handler_disable(int irq)
if (irq < PLATFORM_GIC_MAX_IRQ)
return irqchip.gic->irq_disable(irq);
else
else if (irq < PLATFORM_GPIO_MAX_IRQ)
return irqchip.gpio->irq_disable(irq);
else
return irqchip.virq->irq_disable(irq);
}
int irq_set_irq_type(int irq, unsigned int type)
@ -162,8 +184,10 @@ int irq_set_irq_type(int irq, unsigned int type)
if (irq < PLATFORM_GIC_MAX_IRQ)
return irqchip.gic->irq_set_type(irq, type);
else
else if (irq < PLATFORM_GPIO_MAX_IRQ)
return irqchip.gpio->irq_set_type(irq, type);
else
return -ENOSYS;
}
int irq_revert_irq_type(int irq)
@ -173,8 +197,10 @@ int irq_revert_irq_type(int irq)
if (irq < PLATFORM_GIC_MAX_IRQ)
return 0;
else
else if (irq < PLATFORM_GPIO_MAX_IRQ)
return irqchip.gpio->irq_revert_type(irq);
else
return -ENOSYS;
}
int irq_get_gpio_level(int irq)
@ -184,8 +210,10 @@ int irq_get_gpio_level(int irq)
if (irq < PLATFORM_GIC_MAX_IRQ)
return 0;
else
else if (irq < PLATFORM_GPIO_MAX_IRQ)
return irqchip.gpio->irq_get_gpio_level(irq);
else
return -ENOSYS;
}
void irq_install_handler(int irq, interrupt_handler_t *handler, void *data)
@ -195,17 +223,14 @@ void irq_install_handler(int irq, interrupt_handler_t *handler, void *data)
return;
}
if (irq >= PLATFORM_MAX_IRQ) {
IRQ_W("IRQ %d: Out of max supported IRQ(%d)\n",
irq, PLATFORM_MAX_IRQ);
return;
if (irq < PLATFORM_MAX_IRQ) {
if (!handler || irq_desc[irq].handle_irq)
return;
irq_desc[irq].handle_irq = handler;
irq_desc[irq].data = data;
} else {
virq_install_handler(irq, handler, data);
}
if (!handler || irq_desc[irq].handle_irq)
return;
irq_desc[irq].handle_irq = handler;
irq_desc[irq].data = data;
}
void irq_free_handler(int irq)
@ -213,8 +238,12 @@ void irq_free_handler(int irq)
if (irq_handler_disable(irq))
return;
irq_desc[irq].handle_irq = NULL;
irq_desc[irq].data = NULL;
if (irq < PLATFORM_MAX_IRQ) {
irq_desc[irq].handle_irq = NULL;
irq_desc[irq].data = NULL;
} else {
virq_free_handler(irq);
}
}
int irqs_suspend(void)

View File

@ -24,6 +24,14 @@
*/
struct irq_chip *arch_gic_get_irqchip(void);
struct irq_chip *arch_gpio_get_irqchip(void);
struct irq_chip *arch_virq_get_irqchip(void);
/*
* IRQ-VIRTUAL
*/
int bad_virq(int irq);
void virq_free_handler(int irq);
int virq_install_handler(int irq, interrupt_handler_t *handler, void *data);
/*
* Other

347
drivers/irq/virq.c Normal file
View File

@ -0,0 +1,347 @@
/*
* (C) Copyright 2019 Rockchip Electronics Co., Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <dm.h>
#include <fdtdec.h>
#include <malloc.h>
#include <asm/io.h>
#include <asm/u-boot-arm.h>
#include <irq-generic.h>
#include "irq-internal.h"
DECLARE_GLOBAL_DATA_PTR;
static LIST_HEAD(virq_desc_head);
static u32 virq_id = PLATFORM_MAX_IRQ;
static u32 virq_id_alloc(void)
{
return ++virq_id;
}
struct virq_data {
int irq;
void *data;
interrupt_handler_t *handle_irq;
};
/* The structure to maintail the irqchip and child virqs */
struct virq_desc {
struct virq_chip *chip; /* irq chip */
struct virq_data *virqs; /* child irq data list */
struct udevice *parent; /* parent device */
int pirq; /* parent irq */
int irq_base; /* child irq base */
int irq_end; /* child irq end */
uint reg_stride;
uint unalign_reg_idx;
uint unalign_reg_stride;
uint *status_buf;
struct list_head node;
};
static struct virq_desc *find_virq_desc(int irq)
{
struct virq_desc *desc;
struct list_head *node;
list_for_each(node, &virq_desc_head) {
desc = list_entry(node, struct virq_desc, node);
if (irq >= desc->irq_base && irq <= desc->irq_end)
return desc;
}
return NULL;
}
static struct virq_desc *find_virq_desc_by_pirq(int parent_irq)
{
struct virq_desc *desc;
struct list_head *node;
list_for_each(node, &virq_desc_head) {
desc = list_entry(node, struct virq_desc, node);
if (parent_irq == desc->pirq)
return desc;
}
return NULL;
}
int virq_to_irq(struct virq_chip *chip, int virq)
{
struct virq_desc *desc;
struct list_head *node;
int irq;
if (!chip)
return -EINVAL;
list_for_each(node, &virq_desc_head) {
desc = list_entry(node, struct virq_desc, node);
if (desc->chip == chip) {
irq = desc->irq_base + virq;
if (irq >= desc->irq_base && irq <= desc->irq_end)
return irq;
}
}
return -ENONET;
}
int bad_virq(int irq)
{
return !find_virq_desc(irq);
}
int virq_install_handler(int irq, interrupt_handler_t *handler, void *data)
{
struct virq_desc *desc;
int virq;
if (!handler)
return -EINVAL;
desc = find_virq_desc(irq);
if (!desc)
return -ENOENT;
virq = irq - desc->irq_base;
if (desc->virqs[virq].handle_irq)
return -EBUSY;
desc->virqs[virq].handle_irq = handler;
desc->virqs[virq].data = data;
return 0;
}
void virq_free_handler(int irq)
{
struct virq_desc *desc;
int virq;
desc = find_virq_desc(irq);
if (!desc)
return;
virq = irq - desc->irq_base;
desc->virqs[virq].handle_irq = NULL;
desc->virqs[virq].data = NULL;
}
static uint reg_base_get(struct virq_desc *desc, uint reg_base, int idx)
{
int reg_addr;
if (idx <= desc->unalign_reg_idx) {
reg_addr = reg_base + (idx * desc->unalign_reg_stride);
} else {
reg_addr = reg_base +
(desc->unalign_reg_idx * desc->unalign_reg_stride);
reg_addr += (idx - desc->unalign_reg_idx) * desc->reg_stride;
}
return reg_addr;
}
void virq_chip_generic_handler(int pirq, void *pdata)
{
struct virq_chip *chip;
struct virq_desc *desc;
struct virq_data *vdata;
struct udevice *parent;
uint status_reg;
void *data;
int irq;
int ret;
int i;
desc = find_virq_desc_by_pirq(pirq);
if (!desc)
return;
chip = desc->chip;
vdata = desc->virqs;
parent = (struct udevice *)pdata;
if (!chip || !vdata || !parent)
return;
/* Read all status register */
for (i = 0; i < chip->num_regs; i++) {
status_reg = reg_base_get(desc, chip->status_base, i);
desc->status_buf[i] = chip->i2c_read(parent, status_reg);
if (desc->status_buf[i] < 0) {
printf("%s: Read status register 0x%x failed, ret=%d\n",
__func__, status_reg, desc->status_buf[i]);
}
}
/* Handle all virq handler */
for (i = 0; i < chip->num_irqs; i++) {
if (desc->status_buf[chip->irqs[i].reg_offset] &
chip->irqs[i].mask) {
irq = vdata[i].irq;
data = vdata[i].data;
if (vdata[i].handle_irq)
vdata[i].handle_irq(irq, data);
}
}
/* Clear all status register */
for (i = 0; i < chip->num_regs; i++) {
status_reg = reg_base_get(desc, chip->status_base, i);
ret = chip->i2c_write(parent, status_reg, ~0U);
if (ret)
printf("%s: Clear status register 0x%x failed, ret=%d\n",
__func__, status_reg, ret);
}
}
int virq_add_chip(struct udevice *dev, struct virq_chip *chip,
int irq, int enable)
{
struct virq_data *vdata;
struct virq_desc *desc;
uint *status_buf;
uint mask_reg;
int ret;
int i;
if (irq < 0)
return -EINVAL;
desc = (struct virq_desc *)malloc(sizeof(*desc));
if (!desc)
return -ENOMEM;
vdata = (struct virq_data *)calloc(sizeof(*vdata), chip->num_irqs);
if (!vdata) {
ret = -ENOMEM;
goto free1;
}
status_buf = (uint *)calloc(sizeof(*status_buf), chip->num_irqs);
if (!status_buf) {
ret = -ENOMEM;
goto free2;
}
for (i = 0; i < chip->num_irqs; i++)
vdata[i].irq = virq_id_alloc();
desc->parent = dev;
desc->pirq = irq;
desc->chip = chip;
desc->virqs = vdata;
desc->irq_base = vdata[0].irq;
desc->irq_end = vdata[chip->num_irqs - 1].irq;
desc->status_buf = status_buf;
desc->reg_stride = chip->irq_reg_stride ? : 1;
desc->unalign_reg_stride = chip->irq_unalign_reg_stride ? : 1;
desc->unalign_reg_idx = chip->irq_unalign_reg_stride ?
chip->irq_unalign_reg_idx : 0;
list_add_tail(&desc->node, &virq_desc_head);
/* Mask all register */
for (i = 0; i < chip->num_regs; i++) {
mask_reg = reg_base_get(desc, chip->mask_base, i);
ret = chip->i2c_write(dev, mask_reg, ~0U);
if (ret)
printf("%s: Set mask register 0x%x failed, ret=%d\n",
__func__, mask_reg, ret);
}
/* Add parent irq into interrupt framework with generic virq handler */
irq_install_handler(irq, virq_chip_generic_handler, dev);
return enable ? irq_handler_enable(irq) : irq_handler_disable(irq);
free1:
free(desc);
free2:
free(status_buf);
return ret;
}
static int virq_init(void)
{
INIT_LIST_HEAD(&virq_desc_head);
return 0;
}
static int __virq_enable(int irq, int enable)
{
struct virq_chip *chip;
struct virq_desc *desc;
uint mask_reg, mask_val;
uint reg_val;
int virq;
int ret;
desc = find_virq_desc(irq);
if (!desc) {
printf("%s: %s Invalid irq %d\n",
__func__, enable ? "Enable" : "Disable", irq);
return -ENOENT;
}
chip = desc->chip;
if (!chip)
return -ENOENT;
virq = irq - desc->irq_base;
mask_val = chip->irqs[virq].mask;
mask_reg = reg_base_get(desc, chip->mask_base,
chip->irqs[virq].reg_offset);
reg_val = chip->i2c_read(desc->parent, mask_reg);
if (enable)
reg_val &= ~mask_val;
else
reg_val |= mask_val;
ret = chip->i2c_write(desc->parent, mask_reg, reg_val);
if (ret) {
printf("%s: Clear status register 0x%x failed, ret=%d\n",
__func__, mask_reg, ret);
return ret;
}
return 0;
}
static int virq_enable(int irq)
{
if (bad_virq(irq))
return -EINVAL;
return __virq_enable(irq, 1);
}
static int virq_disable(int irq)
{
if (bad_virq(irq))
return -EINVAL;
return __virq_enable(irq, 0);
}
struct irq_chip virq_generic_chip = {
.name = "virq-irq-chip",
.irq_init = virq_init,
.irq_enable = virq_enable,
.irq_disable = virq_disable,
};
struct irq_chip *arch_virq_get_irqchip(void)
{
return &virq_generic_chip;
}

View File

@ -64,6 +64,30 @@ struct irq_chip {
int (*irq_get_gpio_level)(int irq);
};
/*
* Virtual irq chip structure
*/
typedef int(virq_i2c_write_t)(struct udevice *dev, uint reg, uint value);
typedef int(virq_i2c_read_t)(struct udevice *dev, uint reg);
struct virq_reg {
uint reg_offset;
uint mask;
};
struct virq_chip {
uint status_base;
uint mask_base;
uint irq_reg_stride;
uint irq_unalign_reg_idx;
uint irq_unalign_reg_stride;
int num_regs;
const struct virq_reg *irqs;
int num_irqs;
virq_i2c_read_t *i2c_read;
virq_i2c_write_t *i2c_write;
};
/* APIs for irqs */
void irq_install_handler(int irq, interrupt_handler_t *handler, void *data);
void irq_free_handler(int irq);
@ -93,4 +117,9 @@ int gpio_to_irq(struct gpio_desc *gpio);
int hard_gpio_to_irq(unsigned gpio);
int phandle_gpio_to_irq(u32 gpio_phandle, u32 pin);
/* Virtual irq */
int virq_to_irq(struct virq_chip *chip, int virq);
int virq_add_chip(struct udevice *dev, struct virq_chip *chip,
int irq, int enable);
#endif /* _IRQ_GENERIC_H */