summaryrefslogtreecommitdiff
path: root/drivers/irqchip/irq-riscv-intc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/irqchip/irq-riscv-intc.c')
-rw-r--r--drivers/irqchip/irq-riscv-intc.c284
1 files changed, 261 insertions, 23 deletions
diff --git a/drivers/irqchip/irq-riscv-intc.c b/drivers/irqchip/irq-riscv-intc.c
index f229e3e66387..70290b35b317 100644
--- a/drivers/irqchip/irq-riscv-intc.c
+++ b/drivers/irqchip/irq-riscv-intc.c
@@ -6,6 +6,7 @@
*/
#define pr_fmt(fmt) "riscv-intc: " fmt
+#include <linux/acpi.h>
#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/cpu.h>
@@ -16,17 +17,29 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/smp.h>
+#include <linux/soc/andes/irq.h>
+
+#include <asm/hwcap.h>
static struct irq_domain *intc_domain;
+static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;
+static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;
+static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;
-static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
+static void riscv_intc_irq(struct pt_regs *regs)
{
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
- if (unlikely(cause >= BITS_PER_LONG))
- panic("unexpected interrupt cause");
+ if (generic_handle_domain_irq(intc_domain, cause))
+ pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);
+}
+
+static void riscv_intc_aia_irq(struct pt_regs *regs)
+{
+ unsigned long topi;
- generic_handle_domain_irq(intc_domain, cause);
+ while ((topi = csr_read(CSR_TOPI)))
+ generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);
}
/*
@@ -38,12 +51,43 @@ static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
static void riscv_intc_irq_mask(struct irq_data *d)
{
- csr_clear(CSR_IE, BIT(d->hwirq));
+ if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
+ csr_clear(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
+ else
+ csr_clear(CSR_IE, BIT(d->hwirq));
}
static void riscv_intc_irq_unmask(struct irq_data *d)
{
- csr_set(CSR_IE, BIT(d->hwirq));
+ if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
+ csr_set(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
+ else
+ csr_set(CSR_IE, BIT(d->hwirq));
+}
+
+static void andes_intc_irq_mask(struct irq_data *d)
+{
+ /*
+ * Andes specific S-mode local interrupt causes (hwirq)
+ * are defined as (256 + n) and controlled by n-th bit
+ * of SLIE.
+ */
+ unsigned int mask = BIT(d->hwirq % BITS_PER_LONG);
+
+ if (d->hwirq < ANDES_SLI_CAUSE_BASE)
+ csr_clear(CSR_IE, mask);
+ else
+ csr_clear(ANDES_CSR_SLIE, mask);
+}
+
+static void andes_intc_irq_unmask(struct irq_data *d)
+{
+ unsigned int mask = BIT(d->hwirq % BITS_PER_LONG);
+
+ if (d->hwirq < ANDES_SLI_CAUSE_BASE)
+ csr_set(CSR_IE, mask);
+ else
+ csr_set(ANDES_CSR_SLIE, mask);
}
static void riscv_intc_irq_eoi(struct irq_data *d)
@@ -69,12 +113,21 @@ static struct irq_chip riscv_intc_chip = {
.irq_eoi = riscv_intc_irq_eoi,
};
+static struct irq_chip andes_intc_chip = {
+ .name = "RISC-V INTC",
+ .irq_mask = andes_intc_irq_mask,
+ .irq_unmask = andes_intc_irq_unmask,
+ .irq_eoi = riscv_intc_irq_eoi,
+};
+
static int riscv_intc_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
+ struct irq_chip *chip = d->host_data;
+
irq_set_percpu_devid(irq);
- irq_domain_set_info(d, irq, hwirq, &riscv_intc_chip, d->host_data,
- handle_percpu_devid_irq, NULL, NULL);
+ irq_domain_set_info(d, irq, hwirq, chip, NULL, handle_percpu_devid_irq,
+ NULL, NULL);
return 0;
}
@@ -92,6 +145,15 @@ static int riscv_intc_domain_alloc(struct irq_domain *domain,
if (ret)
return ret;
+ /*
+ * Only allow hwirq for which we have corresponding standard or
+ * custom interrupt enable register.
+ */
+ if (hwirq >= riscv_intc_nr_irqs &&
+ (hwirq < riscv_intc_custom_base ||
+ hwirq >= riscv_intc_custom_base + riscv_intc_custom_nr_irqs))
+ return -EINVAL;
+
for (i = 0; i < nr_irqs; i++) {
ret = riscv_intc_domain_map(domain, virq + i, hwirq + i);
if (ret)
@@ -104,7 +166,8 @@ static int riscv_intc_domain_alloc(struct irq_domain *domain,
static const struct irq_domain_ops riscv_intc_domain_ops = {
.map = riscv_intc_domain_map,
.xlate = irq_domain_xlate_onecell,
- .alloc = riscv_intc_domain_alloc
+ .alloc = riscv_intc_domain_alloc,
+ .free = irq_domain_free_irqs_top,
};
static struct fwnode_handle *riscv_intc_hwnode(void)
@@ -112,11 +175,44 @@ static struct fwnode_handle *riscv_intc_hwnode(void)
return intc_domain->fwnode;
}
+static int __init riscv_intc_init_common(struct fwnode_handle *fn, struct irq_chip *chip)
+{
+ int rc;
+
+ intc_domain = irq_domain_create_tree(fn, &riscv_intc_domain_ops, chip);
+ if (!intc_domain) {
+ pr_err("unable to add IRQ domain\n");
+ return -ENXIO;
+ }
+
+ if (riscv_isa_extension_available(NULL, SxAIA)) {
+ riscv_intc_nr_irqs = 64;
+ rc = set_handle_irq(&riscv_intc_aia_irq);
+ } else {
+ rc = set_handle_irq(&riscv_intc_irq);
+ }
+ if (rc) {
+ pr_err("failed to set irq handler\n");
+ return rc;
+ }
+
+ riscv_set_intc_hwnode_fn(riscv_intc_hwnode);
+
+ pr_info("%d local interrupts mapped%s\n",
+ riscv_intc_nr_irqs,
+ riscv_isa_extension_available(NULL, SxAIA) ? " using AIA" : "");
+ if (riscv_intc_custom_nr_irqs)
+ pr_info("%d custom local interrupts mapped\n", riscv_intc_custom_nr_irqs);
+
+ return 0;
+}
+
static int __init riscv_intc_init(struct device_node *node,
struct device_node *parent)
{
- int rc;
+ struct irq_chip *chip = &riscv_intc_chip;
unsigned long hartid;
+ int rc;
rc = riscv_of_parent_hartid(node, &hartid);
if (rc < 0) {
@@ -130,27 +226,169 @@ static int __init riscv_intc_init(struct device_node *node,
* for each INTC DT node. We only need to do INTC initialization
* for the INTC DT node belonging to boot CPU (or boot HART).
*/
- if (riscv_hartid_to_cpuid(hartid) != smp_processor_id())
+ if (riscv_hartid_to_cpuid(hartid) != smp_processor_id()) {
+ /*
+ * The INTC nodes of each CPU are suppliers for downstream
+ * interrupt controllers (such as PLIC, IMSIC and APLIC
+ * direct-mode) so we should mark an INTC node as initialized
+ * if we are not creating IRQ domain for it.
+ */
+ fwnode_dev_initialized(of_fwnode_handle(node), true);
return 0;
+ }
- intc_domain = irq_domain_add_linear(node, BITS_PER_LONG,
- &riscv_intc_domain_ops, NULL);
- if (!intc_domain) {
- pr_err("unable to add IRQ domain\n");
- return -ENXIO;
+ if (of_device_is_compatible(node, "andestech,cpu-intc")) {
+ riscv_intc_custom_base = ANDES_SLI_CAUSE_BASE;
+ riscv_intc_custom_nr_irqs = ANDES_RV_IRQ_LAST;
+ chip = &andes_intc_chip;
}
- rc = set_handle_irq(&riscv_intc_irq);
- if (rc) {
- pr_err("failed to set irq handler\n");
- return rc;
+ return riscv_intc_init_common(of_fwnode_handle(node), chip);
+}
+
+IRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init);
+IRQCHIP_DECLARE(andes, "andestech,cpu-intc", riscv_intc_init);
+
+#ifdef CONFIG_ACPI
+
+struct rintc_data {
+ union {
+ u32 ext_intc_id;
+ struct {
+ u32 context_id : 16,
+ reserved : 8,
+ aplic_plic_id : 8;
+ };
+ };
+ unsigned long hart_id;
+ u64 imsic_addr;
+ u32 imsic_size;
+};
+
+static u32 nr_rintc;
+static struct rintc_data **rintc_acpi_data;
+
+#define for_each_matching_plic(_plic_id) \
+ unsigned int _plic; \
+ \
+ for (_plic = 0; _plic < nr_rintc; _plic++) \
+ if (rintc_acpi_data[_plic]->aplic_plic_id != _plic_id) \
+ continue; \
+ else
+
+unsigned int acpi_rintc_get_plic_nr_contexts(unsigned int plic_id)
+{
+ unsigned int nctx = 0;
+
+ for_each_matching_plic(plic_id)
+ nctx++;
+
+ return nctx;
+}
+
+static struct rintc_data *get_plic_context(unsigned int plic_id, unsigned int ctxt_idx)
+{
+ unsigned int ctxt = 0;
+
+ for_each_matching_plic(plic_id) {
+ if (ctxt == ctxt_idx)
+ return rintc_acpi_data[_plic];
+
+ ctxt++;
}
- riscv_set_intc_hwnode_fn(riscv_intc_hwnode);
+ return NULL;
+}
- pr_info("%d local interrupts mapped\n", BITS_PER_LONG);
+unsigned long acpi_rintc_ext_parent_to_hartid(unsigned int plic_id, unsigned int ctxt_idx)
+{
+ struct rintc_data *data = get_plic_context(plic_id, ctxt_idx);
+
+ return data ? data->hart_id : INVALID_HARTID;
+}
+unsigned int acpi_rintc_get_plic_context(unsigned int plic_id, unsigned int ctxt_idx)
+{
+ struct rintc_data *data = get_plic_context(plic_id, ctxt_idx);
+
+ return data ? data->context_id : INVALID_CONTEXT;
+}
+
+unsigned long acpi_rintc_index_to_hartid(u32 index)
+{
+ return index >= nr_rintc ? INVALID_HARTID : rintc_acpi_data[index]->hart_id;
+}
+
+int acpi_rintc_get_imsic_mmio_info(u32 index, struct resource *res)
+{
+ if (index >= nr_rintc)
+ return -1;
+
+ res->start = rintc_acpi_data[index]->imsic_addr;
+ res->end = res->start + rintc_acpi_data[index]->imsic_size - 1;
+ res->flags = IORESOURCE_MEM;
return 0;
}
-IRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init);
+static int __init riscv_intc_acpi_match(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ return 0;
+}
+
+static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_rintc *rintc;
+ struct fwnode_handle *fn;
+ int count;
+ int rc;
+
+ if (!rintc_acpi_data) {
+ count = acpi_table_parse_madt(ACPI_MADT_TYPE_RINTC, riscv_intc_acpi_match, 0);
+ if (count <= 0)
+ return -EINVAL;
+
+ rintc_acpi_data = kcalloc(count, sizeof(*rintc_acpi_data), GFP_KERNEL);
+ if (!rintc_acpi_data)
+ return -ENOMEM;
+ }
+
+ rintc = (struct acpi_madt_rintc *)header;
+ rintc_acpi_data[nr_rintc] = kzalloc(sizeof(*rintc_acpi_data[0]), GFP_KERNEL);
+ if (!rintc_acpi_data[nr_rintc])
+ return -ENOMEM;
+
+ rintc_acpi_data[nr_rintc]->ext_intc_id = rintc->ext_intc_id;
+ rintc_acpi_data[nr_rintc]->hart_id = rintc->hart_id;
+ rintc_acpi_data[nr_rintc]->imsic_addr = rintc->imsic_addr;
+ rintc_acpi_data[nr_rintc]->imsic_size = rintc->imsic_size;
+ nr_rintc++;
+
+ /*
+ * The ACPI MADT will have one INTC for each CPU (or HART)
+ * so riscv_intc_acpi_init() function will be called once
+ * for each INTC. We only do INTC initialization
+ * for the INTC belonging to the boot CPU (or boot HART).
+ */
+ if (riscv_hartid_to_cpuid(rintc->hart_id) != smp_processor_id())
+ return 0;
+
+ fn = irq_domain_alloc_named_fwnode("RISCV-INTC");
+ if (!fn) {
+ pr_err("unable to allocate INTC FW node\n");
+ return -ENOMEM;
+ }
+
+ rc = riscv_intc_init_common(fn, &riscv_intc_chip);
+ if (rc)
+ irq_domain_free_fwnode(fn);
+ else
+ acpi_set_irq_model(ACPI_IRQ_MODEL_RINTC, riscv_acpi_get_gsi_domain_id);
+
+ return rc;
+}
+
+IRQCHIP_ACPI_DECLARE(riscv_intc, ACPI_MADT_TYPE_RINTC, NULL,
+ ACPI_MADT_RINTC_VERSION_V1, riscv_intc_acpi_init);
+#endif