rk3568_ubuntu_r60_v1.3.2/u-boot/drivers/irq/virq.c
2023-11-03 06:12:44 +00:00

420 lines
8.4 KiB
C

/*
* (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;
u32 flag;
u32 count;
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 use_count; /* enable count */
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);
}
void virqs_show(int pirq)
{
struct virq_data *vdata;
struct virq_desc *desc;
struct udevice *dev;
int num;
int i;
desc = find_virq_desc_by_pirq(pirq);
if (!desc)
return;
vdata = desc->virqs;
num = desc->irq_end - desc->irq_base;
for (i = 0; i < num; i++) {
if (!vdata[i].handle_irq)
continue;
dev = (struct udevice *)vdata[i].data;
printf(" %3d %d 0x%08lx %-12s |-- %-12s %d\n",
vdata[i].irq,
vdata[i].flag & IRQ_FLG_ENABLE ? 1 : 0,
(ulong)vdata[i].handle_irq, dev->driver->name, dev->name,
vdata[i].count);
}
}
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->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].count++;
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->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)
{
struct virq_data *vdata;
struct virq_desc *desc;
uint *status_buf;
uint status_reg;
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->use_count = 0;
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->write(dev, mask_reg, ~0U);
if (ret)
printf("%s: Set mask register 0x%x failed, ret=%d\n",
__func__, mask_reg, ret);
}
/* Clear all status */
for (i = 0; i < chip->num_regs; i++) {
status_reg = reg_base_get(desc, chip->status_base, i);
ret = chip->write(dev, status_reg, ~0U);
if (ret)
printf("%s: Clear status register 0x%x failed, ret=%d\n",
__func__, status_reg, ret);
}
/* Add parent irq into interrupt framework with generic virq handler */
irq_install_handler(irq, virq_chip_generic_handler, dev);
return 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->read(desc->parent, mask_reg);
if (enable)
reg_val &= ~mask_val;
else
reg_val |= mask_val;
ret = chip->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;
}
if (enable)
desc->virqs[virq].flag |= IRQ_FLG_ENABLE;
else
desc->virqs[virq].flag &= ~IRQ_FLG_ENABLE;
return 0;
}
static int virq_enable(int irq)
{
struct virq_desc *desc = find_virq_desc(irq);
int ret;
if (bad_virq(irq))
return -EINVAL;
ret = __virq_enable(irq, 1);
if (!ret) {
if (desc->use_count == 0)
irq_handler_enable(desc->pirq);
desc->use_count++;
}
return ret;
}
static int virq_disable(int irq)
{
struct virq_desc *desc = find_virq_desc(irq);
int ret;
if (bad_virq(irq))
return -EINVAL;
ret = __virq_enable(irq, 0);
if (!ret) {
if (desc->use_count <= 0)
return ret;
if (desc->use_count == 1)
irq_handler_disable(desc->pirq);
desc->use_count--;
}
return ret;
}
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;
}