/* * (C) Copyright 2019 Rockchip Electronics Co., Ltd * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include #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; }