* [PATCH RFC 07/10] irqchip/loongson-htvec: Add suspend/resume support
@ 2022-05-15 3:41 Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 08/10] irqchip/loongson-liointc: Add ACPI init support Jianmin Lv
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Jianmin Lv @ 2022-05-15 3:41 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier
Cc: linux-kernel, Xuefeng Li, Huacai Chen, Jiaxun Yang, Huacai Chen,
Jianmin Lv
Add suspend/resume support for HTVEC irqchip, which is needed for
suspend/hibernation.
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
---
drivers/irqchip/irq-loongson-htvec.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
index 354630b..a3c60e3 100644
--- a/drivers/irqchip/irq-loongson-htvec.c
+++ b/drivers/irqchip/irq-loongson-htvec.c
@@ -16,6 +16,7 @@
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
+#include <linux/syscore_ops.h>
#include "irq-loongarch-pic-common.h"
/* Registers */
@@ -31,6 +32,7 @@ struct htvec {
struct irq_domain *htvec_domain;
raw_spinlock_t htvec_lock;
struct fwnode_handle *domain_handle;
+ u32 saved_vec_en[HTVEC_MAX_PARENT_IRQ];
};
static struct htvec *htvec_priv;
@@ -158,6 +160,29 @@ static void htvec_reset(struct htvec *priv)
}
}
+static int htvec_suspend(void)
+{
+ int i;
+
+ for (i = 0; i < htvec_priv->num_parents; i++)
+ htvec_priv->saved_vec_en[i] = readl(htvec_priv->base + HTVEC_EN_OFF + 4 * i);
+
+ return 0;
+}
+
+static void htvec_resume(void)
+{
+ int i;
+
+ for (i = 0; i < htvec_priv->num_parents; i++)
+ writel(htvec_priv->saved_vec_en[i], htvec_priv->base + HTVEC_EN_OFF + 4 * i);
+}
+
+static struct syscore_ops htvec_syscore_ops = {
+ .suspend = htvec_suspend,
+ .resume = htvec_resume,
+};
+
static int htvec_init(phys_addr_t addr, unsigned long size,
int num_parents, int parent_irq[], struct fwnode_handle *domain_handle)
{
@@ -193,6 +218,8 @@ static int htvec_init(phys_addr_t addr, unsigned long size,
register_syscore_ops(&htvec_syscore_ops);
+ register_syscore_ops(&htvec_syscore_ops);
+
return 0;
iounmap_base:
--
1.8.3.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH RFC 08/10] irqchip/loongson-liointc: Add ACPI init support
2022-05-15 3:41 [PATCH RFC 07/10] irqchip/loongson-htvec: Add suspend/resume support Jianmin Lv
@ 2022-05-15 3:42 ` Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 09/10] irqchip: Add Loongson Extended I/O interrupt controller support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 10/10] irqchip: Add Loongson PCH LPC " Jianmin Lv
2 siblings, 0 replies; 4+ messages in thread
From: Jianmin Lv @ 2022-05-15 3:42 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier
Cc: linux-kernel, Xuefeng Li, Huacai Chen, Jiaxun Yang, Huacai Chen,
Jianmin Lv
We are preparing to add new Loongson (based on LoongArch, not compatible
with old MIPS-based Loongson) support. LoongArch use ACPI other than DT
as its boot protocol, so add ACPI init support.
LIOINTC stands for "Legacy I/O Interrupts" that described in Section
11.1 of "Loongson 3A5000 Processor Reference Manual". For more
information please refer Documentation/loongarch/irq-chip-model.rst.
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
---
drivers/irqchip/irq-loongson-liointc.c | 215 +++++++++++++++++++++------------
1 file changed, 136 insertions(+), 79 deletions(-)
diff --git a/drivers/irqchip/irq-loongson-liointc.c b/drivers/irqchip/irq-loongson-liointc.c
index 649c583..8b6487f 100644
--- a/drivers/irqchip/irq-loongson-liointc.c
+++ b/drivers/irqchip/irq-loongson-liointc.c
@@ -16,10 +16,14 @@
#include <linux/smp.h>
#include <linux/irqchip/chained_irq.h>
+#ifdef CONFIG_MIPS
#include <loongson.h>
+#else
+#include <asm/loongson.h>
+#endif
#define LIOINTC_CHIP_IRQ 32
-#define LIOINTC_NUM_PARENT 4
+#define LIOINTC_NUM_PARENT 4
#define LIOINTC_NUM_CORES 4
#define LIOINTC_INTC_CHIP_START 0x20
@@ -41,6 +45,7 @@ struct liointc_handler_data {
};
struct liointc_priv {
+ struct fwnode_handle *domain_handle;
struct irq_chip_generic *gc;
struct liointc_handler_data handler[LIOINTC_NUM_PARENT];
void __iomem *core_isr[LIOINTC_NUM_CORES];
@@ -53,7 +58,7 @@ static void liointc_chained_handle_irq(struct irq_desc *desc)
struct liointc_handler_data *handler = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
struct irq_chip_generic *gc = handler->priv->gc;
- int core = get_ebase_cpunum() % LIOINTC_NUM_CORES;
+ int core = cpu_logical_map(smp_processor_id()) % LIOINTC_NUM_CORES;
u32 pending;
chained_irq_enter(chip, desc);
@@ -143,97 +148,62 @@ static void liointc_resume(struct irq_chip_generic *gc)
irq_gc_unlock_irqrestore(gc, flags);
}
-static const char * const parent_names[] = {"int0", "int1", "int2", "int3"};
-static const char * const core_reg_names[] = {"isr0", "isr1", "isr2", "isr3"};
+static int parent_irq[LIOINTC_NUM_PARENT];
+static u32 parent_int_map[LIOINTC_NUM_PARENT];
+static const char *const parent_names[] = {"int0", "int1", "int2", "int3"};
+static const char *const core_reg_names[] = {"isr0", "isr1", "isr2", "isr3"};
-static void __iomem *liointc_get_reg_byname(struct device_node *node,
- const char *name)
-{
- int index = of_property_match_string(node, "reg-names", name);
-
- if (index < 0)
- return NULL;
-
- return of_iomap(node, index);
-}
-
-static int __init liointc_of_init(struct device_node *node,
- struct device_node *parent)
+static int liointc_init(phys_addr_t addr, unsigned long size, int revision,
+ struct fwnode_handle *domain_handle, struct device_node *node)
{
+ int i, err;
+ void __iomem *base;
+ struct irq_chip_type *ct;
struct irq_chip_generic *gc;
struct irq_domain *domain;
- struct irq_chip_type *ct;
struct liointc_priv *priv;
- void __iomem *base;
- u32 of_parent_int_map[LIOINTC_NUM_PARENT];
- int parent_irq[LIOINTC_NUM_PARENT];
- bool have_parent = FALSE;
- int sz, i, err = 0;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
- if (of_device_is_compatible(node, "loongson,liointc-2.0")) {
- base = liointc_get_reg_byname(node, "main");
- if (!base) {
- err = -ENODEV;
- goto out_free_priv;
- }
+ base = ioremap(addr, size);
+ if (!base)
+ goto out_free_priv;
- for (i = 0; i < LIOINTC_NUM_CORES; i++)
- priv->core_isr[i] = liointc_get_reg_byname(node, core_reg_names[i]);
- if (!priv->core_isr[0]) {
- err = -ENODEV;
- goto out_iounmap_base;
- }
- } else {
- base = of_iomap(node, 0);
- if (!base) {
- err = -ENODEV;
- goto out_free_priv;
- }
+ priv->domain_handle = domain_handle;
- for (i = 0; i < LIOINTC_NUM_CORES; i++)
- priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS;
- }
+ for (i = 0; i < LIOINTC_NUM_CORES; i++)
+ priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS;
- for (i = 0; i < LIOINTC_NUM_PARENT; i++) {
- parent_irq[i] = of_irq_get_byname(node, parent_names[i]);
- if (parent_irq[i] > 0)
- have_parent = TRUE;
- }
- if (!have_parent) {
- err = -ENODEV;
- goto out_iounmap_isr;
- }
+ for (i = 0; i < LIOINTC_NUM_PARENT; i++)
+ priv->handler[i].parent_int_map = parent_int_map[i];
- sz = of_property_read_variable_u32_array(node,
- "loongson,parent_int_map",
- &of_parent_int_map[0],
- LIOINTC_NUM_PARENT,
- LIOINTC_NUM_PARENT);
- if (sz < 4) {
- pr_err("loongson-liointc: No parent_int_map\n");
- err = -ENODEV;
- goto out_iounmap_isr;
- }
+#ifdef CONFIG_OF
+ if (revision > 1) {
+ for (i = 0; i < LIOINTC_NUM_CORES; i++) {
+ int index = of_property_match_string(node,
+ "reg-names", core_reg_names[i]);
- for (i = 0; i < LIOINTC_NUM_PARENT; i++)
- priv->handler[i].parent_int_map = of_parent_int_map[i];
+ if (index < 0)
+ return -EINVAL;
+
+ priv->core_isr[i] = of_iomap(node, index);
+ }
+ }
+#endif
/* Setup IRQ domain */
- domain = irq_domain_add_linear(node, 32,
+ domain = irq_domain_create_linear(domain_handle, LIOINTC_CHIP_IRQ,
&irq_generic_chip_ops, priv);
if (!domain) {
pr_err("loongson-liointc: cannot add IRQ domain\n");
- err = -EINVAL;
- goto out_iounmap_isr;
+ goto out_iounmap;
}
- err = irq_alloc_domain_generic_chips(domain, 32, 1,
- node->full_name, handle_level_irq,
- IRQ_NOPROBE, 0, 0);
+ err = irq_alloc_domain_generic_chips(domain, LIOINTC_CHIP_IRQ, 1,
+ (node ? node->full_name : "LIOINTC"),
+ handle_level_irq, 0, IRQ_NOPROBE, 0);
if (err) {
pr_err("loongson-liointc: unable to register IRQ domain\n");
goto out_free_domain;
@@ -289,24 +259,111 @@ static int __init liointc_of_init(struct device_node *node,
liointc_chained_handle_irq, &priv->handler[i]);
}
+ liointc_domain = domain;
return 0;
out_free_domain:
irq_domain_remove(domain);
-out_iounmap_isr:
- for (i = 0; i < LIOINTC_NUM_CORES; i++) {
- if (!priv->core_isr[i])
- continue;
- iounmap(priv->core_isr[i]);
- }
-out_iounmap_base:
+out_iounmap:
iounmap(base);
out_free_priv:
kfree(priv);
- return err;
+ return -EINVAL;
+}
+
+#ifdef CONFIG_OF
+
+static int __init liointc_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ bool have_parent = FALSE;
+ int sz, i, index, revision, err = 0;
+ struct resource res;
+
+ if (!of_device_is_compatible(node, "loongson,liointc-2.0")) {
+ index = 0;
+ revision = 1;
+ } else {
+ index = of_property_match_string(node, "reg-names", "main");
+ revision = 2;
+ }
+
+ if (of_address_to_resource(node, index, &res))
+ return -EINVAL;
+
+ for (i = 0; i < LIOINTC_NUM_PARENT; i++) {
+ parent_irq[i] = of_irq_get_byname(node, parent_names[i]);
+ if (parent_irq[i] > 0)
+ have_parent = TRUE;
+ }
+ if (!have_parent)
+ return -ENODEV;
+
+ sz = of_property_read_variable_u32_array(node,
+ "loongson,parent_int_map",
+ &parent_int_map[0],
+ LIOINTC_NUM_PARENT,
+ LIOINTC_NUM_PARENT);
+ if (sz < 4) {
+ pr_err("loongson-liointc: No parent_int_map\n");
+ return -ENODEV;
+ }
+
+ err = liointc_init(res.start, resource_size(&res),
+ revision, of_node_to_fwnode(node), node);
+ if (err < 0)
+ return err;
+
+ return 0;
}
IRQCHIP_DECLARE(loongson_liointc_1_0, "loongson,liointc-1.0", liointc_of_init);
IRQCHIP_DECLARE(loongson_liointc_1_0a, "loongson,liointc-1.0a", liointc_of_init);
IRQCHIP_DECLARE(loongson_liointc_2_0, "loongson,liointc-2.0", liointc_of_init);
+
+#endif
+
+#ifdef CONFIG_ACPI
+static int __init
+htintc_parse_madt(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_ht_pic *htintc_entry = (struct acpi_madt_ht_pic *)header;
+
+ return htvec_acpi_init(liointc_domain, htintc_entry);
+}
+
+static int __init acpi_cascade_irqdomain_init(void)
+{
+ acpi_table_parse_madt(ACPI_MADT_TYPE_HT_PIC,
+ htintc_parse_madt, 0);
+ return 0;
+}
+int __init liointc_acpi_init(struct irq_domain *parent, struct acpi_madt_lio_pic *acpi_liointc)
+{
+ int ret;
+ struct fwnode_handle *domain_handle;
+
+ if (!acpi_liointc)
+ return -EINVAL;
+
+ parent_int_map[0] = acpi_liointc->cascade_map[0];
+ parent_int_map[1] = acpi_liointc->cascade_map[1];
+
+ parent_irq[0] = irq_create_mapping(parent, acpi_liointc->cascade[0]);
+ parent_irq[1] = irq_create_mapping(parent, acpi_liointc->cascade[1]);
+
+ domain_handle = irq_domain_alloc_fwnode((phys_addr_t *)acpi_liointc);
+ if (!domain_handle) {
+ pr_err("Unable to allocate domain handle\n");
+ return -ENOMEM;
+ }
+ ret = liointc_init(acpi_liointc->address, acpi_liointc->size,
+ 1, domain_handle, NULL);
+ if (ret == 0)
+ acpi_cascade_irqdomain_init();
+
+ return ret;
+}
+#endif
--
1.8.3.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH RFC 09/10] irqchip: Add Loongson Extended I/O interrupt controller support
2022-05-15 3:41 [PATCH RFC 07/10] irqchip/loongson-htvec: Add suspend/resume support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 08/10] irqchip/loongson-liointc: Add ACPI init support Jianmin Lv
@ 2022-05-15 3:42 ` Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 10/10] irqchip: Add Loongson PCH LPC " Jianmin Lv
2 siblings, 0 replies; 4+ messages in thread
From: Jianmin Lv @ 2022-05-15 3:42 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier
Cc: linux-kernel, Xuefeng Li, Huacai Chen, Jiaxun Yang, Huacai Chen,
Jianmin Lv
We are preparing to add new Loongson (based on LoongArch, not compatible
with old MIPS-based Loongson) support. This patch add Loongson Extended
I/O CPU interrupt controller support.
EIOINTC stands for "Extended I/O Interrupts" that described in Section
11.2 of "Loongson 3A5000 Processor Reference Manual". For more
information please refer Documentation/loongarch/irq-chip-model.rst.
Loongson-3A5000 has 4 cores per NUMA node, and each NUMA node has an
EIOINTC; while Loongson-3C5000 has 16 cores per NUMA node, and each NUMA
node has 4 EIOINTCs. In other words, 16 cores of one NUMA node in
Loongson-3C5000 are organized in 4 groups, each group connects to an
EIOINTC. We call the "group" here as an EIOINTC node, so each EIOINTC
node always includes 4 cores (both in Loongson-3A5000 and Loongson-
3C5000).
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
---
drivers/irqchip/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-eiointc.c | 381 +++++++++++++++++++++++++++++++++
include/linux/cpuhotplug.h | 1 +
4 files changed, 393 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-eiointc.c
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index a596ee7..eea0c15 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -564,6 +564,16 @@ config LOONGSON_LIOINTC
help
Support for the Loongson Local I/O Interrupt Controller.
+config LOONGSON_EIOINTC
+ bool "Loongson Extend I/O Interrupt Controller"
+ depends on LOONGARCH
+ depends on MACH_LOONGSON64
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select GENERIC_IRQ_CHIP
+ help
+ Support for the Loongson3 Extend I/O Interrupt Vector Controller.
+
config LOONGSON_HTPIC
bool "Loongson3 HyperTransport PIC Controller"
depends on (MACH_LOONGSON64 && MIPS)
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 736f030..0d82759 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -107,6 +107,7 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o
obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-pic-common.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
+obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-loongson-pch-pic.o
diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c
new file mode 100644
index 0000000..e72caf5
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-eiointc.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loongson Extend I/O Interrupt Controller support
+ *
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#define pr_fmt(fmt) "eiointc: " fmt
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/syscore_ops.h>
+#include "irq-loongarch-pic-common.h"
+
+#define EIOINTC_REG_NODEMAP 0x14a0
+#define EIOINTC_REG_IPMAP 0x14c0
+#define EIOINTC_REG_ENABLE 0x1600
+#define EIOINTC_REG_BOUNCE 0x1680
+#define EIOINTC_REG_ISR 0x1800
+#define EIOINTC_REG_ROUTE 0x1c00
+
+#define VEC_REG_COUNT 4
+#define VEC_COUNT_PER_REG 64
+#define VEC_COUNT (VEC_REG_COUNT * VEC_COUNT_PER_REG)
+#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
+#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
+#define EIOINTC_ALL_ENABLE 0xffffffff
+
+#define MAX_EIO_NODES (NR_CPUS / CORES_PER_EIO_NODE)
+
+static int nr_pics;
+
+struct eiointc_priv {
+ u32 node;
+ nodemask_t node_map;
+ cpumask_t cpuspan_map;
+ struct fwnode_handle *domain_handle;
+ struct irq_domain *eiointc_domain;
+};
+
+static struct eiointc_priv *eiointc_priv[2];
+
+int eiointc_get_node(int id)
+{
+ return eiointc_priv[id]->node;
+}
+
+static int cpu_to_eio_node(int cpu)
+{
+ return cpu_logical_map(cpu) / CORES_PER_EIO_NODE;
+}
+
+static void eiointc_set_irq_route(int pos, unsigned int cpu, unsigned int mnode, nodemask_t *node_map)
+{
+ int i, node, cpu_node, route_node;
+ unsigned char coremap[MAX_EIO_NODES];
+ uint32_t pos_off, data, data_byte, data_mask;
+
+ pos_off = pos & ~3;
+ data_byte = pos & 3;
+ data_mask = ~BIT_MASK(data_byte) & 0xf;
+
+ memset(coremap, 0, sizeof(unsigned char) * MAX_EIO_NODES);
+
+ /* Calculate node and coremap of target irq */
+ cpu_node = cpu_logical_map(cpu) / CORES_PER_EIO_NODE;
+ coremap[cpu_node] |= BIT(cpu_logical_map(cpu) % CORES_PER_EIO_NODE);
+
+ for_each_online_cpu(i) {
+ node = cpu_to_eio_node(i);
+ if (!node_isset(node, *node_map))
+ continue;
+
+ /* EIO node 0 is in charge of inter-node interrupt dispatch */
+ route_node = (node == mnode) ? cpu_node : node;
+ data = ((coremap[node] | (route_node << 4)) << (data_byte * 8));
+ csr_any_send(EIOINTC_REG_ROUTE + pos_off, data, data_mask, node * CORES_PER_EIO_NODE);
+ }
+}
+
+static DEFINE_RAW_SPINLOCK(affinity_lock);
+
+static int eiointc_set_irq_affinity(struct irq_data *d, const struct cpumask *affinity, bool force)
+{
+ unsigned int cpu;
+ unsigned long flags;
+ uint32_t vector, regaddr;
+ struct cpumask intersect_affinity;
+ struct eiointc_priv *priv = d->domain->host_data;
+
+ if (!IS_ENABLED(CONFIG_SMP))
+ return -EPERM;
+
+ raw_spin_lock_irqsave(&affinity_lock, flags);
+
+ cpumask_and(&intersect_affinity, affinity, cpu_online_mask);
+ cpumask_and(&intersect_affinity, &intersect_affinity, &priv->cpuspan_map);
+
+ if (cpumask_empty(&intersect_affinity)) {
+ raw_spin_unlock_irqrestore(&affinity_lock, flags);
+ return -EINVAL;
+ }
+ cpu = cpumask_first(&intersect_affinity);
+
+ if (!d->parent_data)
+ vector = d->hwirq;
+ else
+ vector = d->parent_data->hwirq;
+
+ regaddr = EIOINTC_REG_ENABLE + ((vector >> 5) << 2);
+
+ /* Mask target vector */
+ csr_any_send(regaddr, EIOINTC_ALL_ENABLE & (~BIT(vector & 0x1F)),
+ 0x0, priv->node * CORES_PER_EIO_NODE);
+
+ /* Set route for target vector */
+ eiointc_set_irq_route(vector, cpu, priv->node, &priv->node_map);
+
+ /* Unmask target vector */
+ csr_any_send(regaddr, EIOINTC_ALL_ENABLE,
+ 0x0, priv->node * CORES_PER_EIO_NODE);
+
+ irq_data_update_effective_affinity(d, cpumask_of(cpu));
+
+ raw_spin_unlock_irqrestore(&affinity_lock, flags);
+
+ return IRQ_SET_MASK_OK;
+}
+
+static int eiointc_index(int node)
+{
+ int i;
+
+ for (i = 0; i < nr_pics; i++) {
+ if (node_isset(node, eiointc_priv[i]->node_map))
+ return i;
+ }
+
+ return -1;
+}
+
+static int eiointc_router_init(unsigned int cpu)
+{
+ int i, bit;
+ int node = cpu_to_eio_node(cpu);
+ int index = eiointc_index(node);
+ uint32_t data;
+
+ if (index < 0) {
+ pr_err("Error: invalid nodemap!\n");
+ return -1;
+ }
+
+ if ((cpu_logical_map(cpu) % CORES_PER_EIO_NODE) == 0) {
+ eiointc_enable();
+
+ for (i = 0; i < VEC_COUNT / 32; i++) {
+ data = (((1 << (i * 2 + 1)) << 16) | (1 << (i * 2)));
+ iocsr_writel(data, EIOINTC_REG_NODEMAP + i * 4);
+ }
+
+ for (i = 0; i < VEC_COUNT / 32 / 4; i++) {
+ bit = BIT(1 + index); /* Route to IP[1 + index] */
+ data = bit | (bit << 8) | (bit << 16) | (bit << 24);
+ iocsr_writel(data, EIOINTC_REG_IPMAP + i * 4);
+ }
+
+ for (i = 0; i < VEC_COUNT / 4; i++) {
+ /* Route to Node-0 Core-0 */
+ if (index == 0)
+ bit = BIT(cpu_logical_map(0));
+ else
+ bit = (eiointc_priv[index]->node << 4) | 1;
+
+ data = bit | (bit << 8) | (bit << 16) | (bit << 24);
+ iocsr_writel(data, EIOINTC_REG_ROUTE + i * 4);
+ }
+
+ for (i = 0; i < VEC_COUNT / 32; i++) {
+ data = 0xffffffff;
+ iocsr_writel(data, EIOINTC_REG_ENABLE + i * 4);
+ iocsr_writel(data, EIOINTC_REG_BOUNCE + i * 4);
+ }
+ }
+
+ return 0;
+}
+
+static void eiointc_irq_dispatch(struct irq_desc *desc)
+{
+ int i;
+ u64 pending;
+ bool handled = false;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ struct eiointc_priv *priv = irq_desc_get_handler_data(desc);
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < VEC_REG_COUNT; i++) {
+ pending = iocsr_readq(EIOINTC_REG_ISR + (i << 3));
+ iocsr_writeq(pending, EIOINTC_REG_ISR + (i << 3));
+ while (pending) {
+ int bit = __ffs(pending);
+ int irq = bit + VEC_COUNT_PER_REG * i;
+
+ generic_handle_domain_irq(priv->eiointc_domain, irq);
+ pending &= ~BIT(bit);
+ handled = true;
+ }
+ }
+
+ if (!handled)
+ spurious_interrupt();
+
+ chained_irq_exit(chip, desc);
+}
+
+static void eiointc_ack_irq(struct irq_data *d)
+{
+ if (d->parent_data)
+ irq_chip_ack_parent(d);
+}
+
+static void eiointc_mask_irq(struct irq_data *d)
+{
+ if (d->parent_data)
+ irq_chip_mask_parent(d);
+}
+
+static void eiointc_unmask_irq(struct irq_data *d)
+{
+ if (d->parent_data)
+ irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip eiointc_irq_chip = {
+ .name = "EIOINTC",
+ .irq_ack = eiointc_ack_irq,
+ .irq_mask = eiointc_mask_irq,
+ .irq_unmask = eiointc_unmask_irq,
+ .irq_set_affinity = eiointc_set_irq_affinity,
+};
+
+static int eiointc_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ int ret;
+ unsigned int i, type;
+ unsigned long hwirq = 0;
+ struct eiointc *priv = domain->host_data;
+
+ ret = irq_domain_translate_onecell(domain, arg, &hwirq, &type);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (hwirq >= IOCSR_EXTIOI_VECTOR_NUM)
+ return -EINVAL;
+
+ for (i = 0; i < nr_irqs; i++) {
+ irq_domain_set_info(domain, virq + i, hwirq + i, &eiointc_irq_chip,
+ priv, handle_edge_irq, NULL, NULL);
+ }
+
+ return 0;
+}
+
+static void eiointc_domain_free(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs)
+{
+ int i;
+
+ for (i = 0; i < nr_irqs; i++) {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ }
+}
+
+static const struct irq_domain_ops eiointc_domain_ops = {
+ .translate = irq_domain_translate_onecell,
+ .alloc = eiointc_domain_alloc,
+ .free = eiointc_domain_free,
+};
+
+static int eiointc_suspend(void)
+{
+ return 0;
+}
+
+static void eiointc_resume(void)
+{
+ int i, j;
+ struct irq_desc *desc;
+ struct irq_data *irq_data;
+
+ eiointc_router_init(0);
+
+ for (i = 0; i < nr_pics; i++) {
+ for (j = 0; j < VEC_COUNT; j++) {
+ desc = irq_resolve_mapping(eiointc_priv[i]->eiointc_domain, j);
+ if (desc && desc->handle_irq && desc->handle_irq != handle_bad_irq) {
+ irq_data = &desc->irq_data;
+ eiointc_set_irq_affinity(irq_data, irq_data->common->affinity, 0);
+ }
+ }
+ }
+}
+
+static struct syscore_ops eiointc_syscore_ops = {
+ .suspend = eiointc_suspend,
+ .resume = eiointc_resume,
+};
+
+int __init eiointc_acpi_init(struct irq_domain *parent,
+ struct acpi_madt_eio_pic *acpi_eiointc)
+{
+ int i, parent_irq;
+ unsigned long node_map;
+ struct eiointc_priv *priv;
+
+ if (!acpi_eiointc)
+ return -EINVAL;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->domain_handle = irq_domain_alloc_fwnode((phys_addr_t *)acpi_eiointc);
+ if (!priv->domain_handle) {
+ pr_err("Unable to allocate domain handle\n");
+ goto out_free_priv;
+ }
+
+ priv->node = acpi_eiointc->node;
+ node_map = acpi_eiointc->node_map ? : -1ULL;
+
+ for_each_possible_cpu(i) {
+ if (node_map & (1ULL << cpu_to_eio_node(i))) {
+ node_set(cpu_to_eio_node(i), priv->node_map);
+ cpumask_or(&priv->cpuspan_map, &priv->cpuspan_map, cpumask_of(i));
+ }
+ }
+
+ /* Setup IRQ domain */
+ priv->eiointc_domain = irq_domain_create_linear(priv->domain_handle, VEC_COUNT,
+ &eiointc_domain_ops, priv);
+ if (!priv->eiointc_domain) {
+ pr_err("loongson-eiointc: cannot add IRQ domain\n");
+ goto out_free_priv;
+ }
+
+ eiointc_priv[nr_pics++] = priv;
+
+ eiointc_router_init(0);
+
+ parent_irq = irq_create_mapping(parent, acpi_eiointc->cascade);
+ irq_set_chained_handler_and_data(parent_irq, eiointc_irq_dispatch, priv);
+
+ register_syscore_ops(&eiointc_syscore_ops);
+ cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_LOONGARCH_STARTING,
+ "irqchip/loongarch/intc:starting",
+ eiointc_router_init, NULL);
+
+ acpi_set_vector_parent(acpi_eiointc->node, priv->eiointc_domain);
+ return 0;
+
+out_free_priv:
+ priv->domain_handle = NULL;
+ kfree(priv);
+
+ return -ENOMEM;
+}
diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h
index 82e3313..2af7c658 100644
--- a/include/linux/cpuhotplug.h
+++ b/include/linux/cpuhotplug.h
@@ -150,6 +150,7 @@ enum cpuhp_state {
CPUHP_AP_IRQ_BCM2836_STARTING,
CPUHP_AP_IRQ_MIPS_GIC_STARTING,
CPUHP_AP_IRQ_RISCV_STARTING,
+ CPUHP_AP_IRQ_LOONGARCH_STARTING,
CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING,
CPUHP_AP_ARM_MVEBU_COHERENCY,
CPUHP_AP_MICROCODE_LOADER,
--
1.8.3.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH RFC 10/10] irqchip: Add Loongson PCH LPC controller support
2022-05-15 3:41 [PATCH RFC 07/10] irqchip/loongson-htvec: Add suspend/resume support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 08/10] irqchip/loongson-liointc: Add ACPI init support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 09/10] irqchip: Add Loongson Extended I/O interrupt controller support Jianmin Lv
@ 2022-05-15 3:42 ` Jianmin Lv
2 siblings, 0 replies; 4+ messages in thread
From: Jianmin Lv @ 2022-05-15 3:42 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier
Cc: linux-kernel, Xuefeng Li, Huacai Chen, Jiaxun Yang, Huacai Chen,
Jianmin Lv
We are preparing to add new Loongson (based on LoongArch, not compatible
with old MIPS-based Loongson) support. This patch add Loongson PCH LPC
interrupt controller support.
PCH-LPC stands for "LPC Interrupts" that described in Section 24.3 of
"Loongson 7A1000 Bridge User Manual". For more information please refer
Documentation/loongarch/irq-chip-model.rst.
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
---
drivers/irqchip/Kconfig | 8 ++
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-lpc.c | 225 +++++++++++++++++++++++++++++++++
3 files changed, 234 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-lpc.c
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index eea0c15..c56a8db 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -610,6 +610,14 @@ config LOONGSON_PCH_MSI
help
Support for the Loongson PCH MSI Controller.
+config LOONGSON_PCH_LPC
+ bool "Loongson PCH LPC Controller"
+ depends on MACH_LOONGSON64
+ default (MACH_LOONGSON64 && LOONGARCH)
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Support for the Loongson PCH LPC Controller.
+
config MST_IRQ
bool "MStar Interrupt Controller"
depends on ARCH_MEDIATEK || ARCH_MSTARV7 || COMPILE_TEST
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 0d82759..a57566c 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -112,6 +112,7 @@ obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-loongson-pch-pic.o
obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
+obj-$(CONFIG_LOONGSON_PCH_LPC) += irq-loongson-pch-lpc.o
obj-$(CONFIG_MST_IRQ) += irq-mst-intc.o
obj-$(CONFIG_SL28CPLD_INTC) += irq-sl28cpld.o
obj-$(CONFIG_MACH_REALTEK_RTL) += irq-realtek-rtl.o
diff --git a/drivers/irqchip/irq-loongson-pch-lpc.c b/drivers/irqchip/irq-loongson-pch-lpc.c
new file mode 100644
index 0000000..18e400d
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-lpc.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loongson LPC Interrupt Controller support
+ *
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+
+#define pr_fmt(fmt) "lpc: " fmt
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/syscore_ops.h>
+
+/* Registers */
+#define LPC_INT_CTL 0x00
+#define LPC_INT_ENA 0x04
+#define LPC_INT_STS 0x08
+#define LPC_INT_CLR 0x0c
+#define LPC_INT_POL 0x10
+#define LPC_COUNT 16
+
+struct pch_lpc {
+ void __iomem *base;
+ struct irq_domain *lpc_domain;
+ struct fwnode_handle *domain_handle;
+ raw_spinlock_t lpc_lock;
+ u32 saved_reg_ctl;
+ u32 saved_reg_ena;
+ u32 saved_reg_pol;
+};
+
+static struct pch_lpc *pch_lpc_priv;
+
+static void ack_lpc_irq(struct irq_data *d)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&pch_lpc_priv->lpc_lock, flags);
+ writel(0x1 << d->irq, pch_lpc_priv->base + LPC_INT_CLR);
+ raw_spin_unlock_irqrestore(&pch_lpc_priv->lpc_lock, flags);
+}
+static void mask_lpc_irq(struct irq_data *d)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&pch_lpc_priv->lpc_lock, flags);
+ writel(readl(pch_lpc_priv->base + LPC_INT_ENA) & (~(0x1 << (d->irq))),
+ pch_lpc_priv->base + LPC_INT_ENA);
+ raw_spin_unlock_irqrestore(&pch_lpc_priv->lpc_lock, flags);
+}
+
+static void mask_ack_lpc_irq(struct irq_data *d)
+{
+}
+
+static void unmask_lpc_irq(struct irq_data *d)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&pch_lpc_priv->lpc_lock, flags);
+ writel(readl(pch_lpc_priv->base + LPC_INT_ENA) | (0x1 << (d->irq)),
+ pch_lpc_priv->base + LPC_INT_ENA);
+ raw_spin_unlock_irqrestore(&pch_lpc_priv->lpc_lock, flags);
+}
+
+static int lpc_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 val;
+ u32 mask = 0x1 << (d->hwirq);
+
+ if (!(type & IRQ_TYPE_LEVEL_MASK))
+ return 0;
+
+ val = readl(pch_lpc_priv->base + LPC_INT_POL);
+
+ if (type == IRQ_TYPE_LEVEL_HIGH)
+ val |= mask;
+ else
+ val &= ~mask;
+
+ writel(val, pch_lpc_priv->base + LPC_INT_POL);
+
+ return 0;
+}
+
+static struct irq_chip pch_lpc_irq_chip = {
+ .name = "PCH LPC",
+ .irq_mask = mask_lpc_irq,
+ .irq_unmask = unmask_lpc_irq,
+ .irq_ack = ack_lpc_irq,
+ .irq_mask_ack = mask_ack_lpc_irq,
+ .irq_eoi = unmask_lpc_irq,
+ .irq_set_type = lpc_set_type,
+ .flags = IRQCHIP_SKIP_SET_WAKE,
+};
+
+static void lpc_irq_dispatch(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 pending;
+
+ chained_irq_enter(chip, desc);
+
+ pending = readl(pch_lpc_priv->base + LPC_INT_ENA);
+ pending &= readl(pch_lpc_priv->base + LPC_INT_STS);
+ if (!pending)
+ spurious_interrupt();
+
+ while (pending) {
+ int bit = __ffs(pending);
+
+ generic_handle_irq(bit);
+ pending &= ~BIT(bit);
+ }
+ chained_irq_exit(chip, desc);
+}
+
+static int pch_lpc_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ irq_set_chip_and_handler(irq, &pch_lpc_irq_chip, handle_level_irq);
+ return 0;
+}
+
+static const struct irq_domain_ops pch_lpc_domain_ops = {
+ .map = pch_lpc_map,
+ .translate = irq_domain_translate_twocell,
+};
+
+static void pch_lpc_reset(struct pch_lpc *priv)
+{
+ /* Enable the LPC interrupt, bit31: en bit30: edge */
+ writel(0x80000000, priv->base + LPC_INT_CTL);
+ writel(0, priv->base + LPC_INT_ENA);
+ /* Clear all 18-bit interrpt bit */
+ writel(0x3ffff, priv->base + LPC_INT_CLR);
+}
+
+static int pch_lpc_disabled(struct pch_lpc *priv)
+{
+ return (readl(priv->base + LPC_INT_ENA) == 0xffffffff) &&
+ (readl(priv->base + LPC_INT_STS) == 0xffffffff);
+}
+
+static int pch_lpc_suspend(void)
+{
+ pch_lpc_priv->saved_reg_ctl = readl(pch_lpc_priv->base + LPC_INT_CTL);
+ pch_lpc_priv->saved_reg_ena = readl(pch_lpc_priv->base + LPC_INT_ENA);
+ pch_lpc_priv->saved_reg_pol = readl(pch_lpc_priv->base + LPC_INT_POL);
+ return 0;
+}
+
+static void pch_lpc_resume(void)
+{
+ writel(pch_lpc_priv->saved_reg_ctl, pch_lpc_priv->base + LPC_INT_CTL);
+ writel(pch_lpc_priv->saved_reg_ena, pch_lpc_priv->base + LPC_INT_ENA);
+ writel(pch_lpc_priv->saved_reg_pol, pch_lpc_priv->base + LPC_INT_POL);
+}
+
+static struct syscore_ops pch_lpc_syscore_ops = {
+ .suspend = pch_lpc_suspend,
+ .resume = pch_lpc_resume,
+};
+
+int __init pch_lpc_acpi_init(struct irq_domain *parent,
+ struct acpi_madt_lpc_pic *acpi_pchlpc)
+{
+ int parent_irq;
+ struct pch_lpc *priv;
+ struct irq_fwspec fwspec;
+
+ if (!acpi_pchlpc)
+ return -EINVAL;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->lpc_lock);
+
+ priv->base = ioremap(acpi_pchlpc->address, acpi_pchlpc->size);
+ if (!priv->base)
+ goto free_priv;
+
+ if (pch_lpc_disabled(priv)) {
+ pr_err("Failed to get LPC status\n");
+ goto iounmap_base;
+ }
+
+ priv->domain_handle = irq_domain_alloc_fwnode((phys_addr_t *)acpi_pchlpc);
+ if (!priv->domain_handle) {
+ pr_err("Unable to allocate domain handle\n");
+ goto iounmap_base;
+ }
+ priv->lpc_domain = irq_domain_add_legacy(NULL, LPC_COUNT, 0, 0,
+ &pch_lpc_domain_ops, priv);
+ if (!priv->lpc_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ goto iounmap_base;
+ }
+ pch_lpc_reset(priv);
+
+ fwspec.fwnode = parent->fwnode;
+ fwspec.param[0] = acpi_pchlpc->cascade;
+ fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
+ fwspec.param_count = 2;
+ parent_irq = irq_create_fwspec_mapping(&fwspec);
+ irq_set_chained_handler_and_data(parent_irq, lpc_irq_dispatch, priv);
+ pch_lpc_priv = priv;
+
+ register_syscore_ops(&pch_lpc_syscore_ops);
+ pch_lpc_domain = priv->lpc_domain;
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return -ENOMEM;
+}
--
1.8.3.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2022-05-15 3:42 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-15 3:41 [PATCH RFC 07/10] irqchip/loongson-htvec: Add suspend/resume support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 08/10] irqchip/loongson-liointc: Add ACPI init support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 09/10] irqchip: Add Loongson Extended I/O interrupt controller support Jianmin Lv
2022-05-15 3:42 ` [PATCH RFC 10/10] irqchip: Add Loongson PCH LPC " Jianmin Lv
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).