2020-04-22 14:27:57

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 0/6] Loongson PCH IRQ Support

This series mainly added IRQ support for Loongson-7A1000 PCH.
DeviceTree will be added later as PCI support is also pending
for reviewing.

Jiaxun Yang (6):
irqchip: Add Loongson HyperTransport Vector support
dt-bindings: interrupt-controller: Add Loongson HTVEC
irqchip: Add Loongson PCH PIC controller
dt-bindings: interrupt-controller: Add Loongson PCH PIC
irqchip: Add Loongson PCH MSI controller
dt-bindings: interrupt-controller: Add Loongson PCH MSI

.../interrupt-controller/loongson,htvec.yaml | 59 ++++
.../loongson,pch-msi.yaml | 56 ++++
.../loongson,pch-pic.yaml | 55 ++++
drivers/irqchip/Kconfig | 26 ++
drivers/irqchip/Makefile | 3 +
drivers/irqchip/irq-loongson-htvec.c | 217 ++++++++++++++
drivers/irqchip/irq-loongson-pch-msi.c | 265 ++++++++++++++++++
drivers/irqchip/irq-loongson-pch-pic.c | 256 +++++++++++++++++
8 files changed, 937 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
create mode 100644 drivers/irqchip/irq-loongson-htvec.c
create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c
create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c

--
2.26.0.rc2


2020-04-22 14:28:09

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 2/6] dt-bindings: interrupt-controller: Add Loongson HTVEC

Add binding for Loongson-3 HyperTransport Interrupt Vector Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../interrupt-controller/loongson,htvec.yaml | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
new file mode 100644
index 000000000000..547a80c89eba
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,htvec.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson-3 HyperTransport Interrupt Vector Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson-3 family of chips for
+ receiving vectorized interrupts from PCH's interrupt controller.
+
+properties:
+ compatible:
+ const: loongson,htvec-1.0
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 1
+ maxItems: 4
+ description: |
+ Four parent interrupts that receive chained interrupts.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ htvec: interrupt-controller@1fb000080 {
+ compatible = "loongson,htvec-1.0";
+ reg = <0xfb000080 0x40>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&liointc>;
+ interrupts = <24 IRQ_TYPE_LEVEL_HIGH>,
+ <25 IRQ_TYPE_LEVEL_HIGH>,
+ <26 IRQ_TYPE_LEVEL_HIGH>,
+ <27 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
--
2.26.0.rc2

2020-04-22 14:28:39

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 4/6] dt-bindings: interrupt-controller: Add Loongson PCH PIC

Add binding for Loongson PCH PIC Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../loongson,pch-pic.yaml | 55 +++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
new file mode 100644
index 000000000000..afc0c924e477
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-pic.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH PIC Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson-7A family of PCH for
+ transforming interrupts from on-chip devices into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-pic-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,pic-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH PIC.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ pic: interrupt-controller@10000000 {
+ compatible = "loongson,pch-pic-1.0";
+ reg = <0x10000000 0x400>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ loongson,pic-base-vec = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-04-22 14:29:36

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 5/6] irqchip: Add Loongson PCH MSI controller

This controller appears on Loongson-7A family of PCH to transform
interrupts from PCI MSI into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
drivers/irqchip/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-msi.c | 265 +++++++++++++++++++++++++
3 files changed, 276 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 4ab7a9b1a5c2..9f2935418f33 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -548,4 +548,14 @@ config LOONGSON_PCH_PIC
help
Support for the Loongson PCH PIC Controller.

+config LOONGSON_PCH_MSI
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ depends on PCI
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select PCI_MSI
+ help
+ Support for the Loongson PCH MSI Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index acc72331cec8..3a4ce283189a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
+obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
new file mode 100644
index 000000000000..4bb00f2ce86a
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-msi.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH MSI support
+ */
+
+#define pr_fmt(fmt) "pch-msi: " fmt
+
+#include <linux/irqchip.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+struct pch_msi_data {
+ spinlock_t msi_map_lock;
+ phys_addr_t addr;
+ u32 irq_first; /* The vectoe number that MSIs start */
+ u32 num_irqs; /* The number of vector for MSIs */
+ unsigned long *msi_map;
+};
+
+static void pch_msi_mask_msi_irq(struct irq_data *d)
+{
+ pci_msi_mask_irq(d);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_msi_unmask_msi_irq(struct irq_data *d)
+{
+ pci_msi_unmask_irq(d);
+ irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip pch_msi_irq_chip = {
+ .name = "PCH MSI",
+ .irq_mask = pch_msi_mask_msi_irq,
+ .irq_unmask = pch_msi_unmask_msi_irq,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+};
+
+static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
+{
+ int first;
+
+ spin_lock(&priv->msi_map_lock);
+
+ first = bitmap_find_next_zero_area(priv->msi_map, priv->num_irqs, 0,
+ num_req, 0);
+ if (first >= priv->num_irqs) {
+ spin_unlock(&priv->msi_map_lock);
+ return -ENOSPC;
+ }
+
+ bitmap_set(priv->msi_map, first, num_req);
+
+ spin_unlock(&priv->msi_map_lock);
+
+ return priv->irq_first + first;
+}
+
+static void pch_msi_free_hwirq(struct pch_msi_data *priv,
+ int hwirq, int num_req)
+{
+ int first = hwirq - priv->irq_first;
+
+ spin_lock(&priv->msi_map_lock);
+
+ bitmap_clear(priv->msi_map, first, num_req);
+
+ spin_unlock(&priv->msi_map_lock);
+}
+
+static void pch_msi_compose_msi_msg(struct irq_data *data,
+ struct msi_msg *msg)
+{
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
+ phys_addr_t msg_addr = priv->addr;
+
+ msg->address_hi = upper_32_bits(msg_addr);
+ msg->address_lo = lower_32_bits(msg_addr);
+ msg->data = data->hwirq;
+}
+
+static struct msi_domain_info pch_msi_domain_info = {
+ .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
+ .chip = &pch_msi_irq_chip,
+};
+
+static struct irq_chip middle_irq_chip = {
+ .name = "PCH MSI Middle",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_compose_msi_msg = pch_msi_compose_msi_msg,
+};
+
+static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, int hwirq)
+{
+ struct irq_fwspec fwspec;
+ int ret;
+
+ if (!is_of_node(domain->parent->fwnode))
+ return -EINVAL;
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (ret)
+ return ret;
+
+ irq_domain_set_info(domain, virq, hwirq,
+ &middle_irq_chip, NULL,
+ handle_simple_irq, NULL, NULL);
+ irq_set_probe(virq);
+
+ return 0;
+}
+
+static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *args)
+{
+ struct pch_msi_data *priv = domain->host_data;
+ int hwirq, err, i;
+
+ hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
+ if (hwirq < 0)
+ return hwirq;
+
+ for (i = 0; i < nr_irqs; i++) {
+ err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
+ if (err)
+ goto err_hwirq;
+
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &middle_irq_chip, priv);
+ }
+
+ return 0;
+err_hwirq:
+ while (--i >= 0)
+ irq_domain_free_irqs_parent(domain, virq, i);
+
+ pch_msi_free_hwirq(priv, hwirq, nr_irqs);
+ return err;
+}
+
+static void pch_msi_middle_domain_free(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+ pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
+}
+
+static const struct irq_domain_ops pch_msi_middle_domain_ops = {
+ .alloc = pch_msi_middle_domain_alloc,
+ .free = pch_msi_middle_domain_free,
+};
+
+static int pch_msi_init_domains(struct pch_msi_data *priv,
+ struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_domain *middle_domain, *msi_domain, *parent_domain;
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ return -ENXIO;
+ }
+
+ middle_domain = irq_domain_add_tree(NULL,
+ &pch_msi_middle_domain_ops,
+ priv);
+ if (!middle_domain) {
+ pr_err("Failed to create the MSI middle domain\n");
+ return -ENOMEM;
+ }
+
+ middle_domain->parent = parent_domain;
+
+ msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
+ &pch_msi_domain_info,
+ middle_domain);
+ if (!msi_domain) {
+ pr_err("Failed to create MSI domain\n");
+ irq_domain_remove(middle_domain);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int pch_msi_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_msi_data *priv;
+ struct resource res;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->msi_map_lock);
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret) {
+ pr_err("Failed to allocate resource\n");
+ goto err_priv;
+ }
+
+ priv->addr = res.start;
+
+ if (of_property_read_u32(node, "loongson,msi-base-vec",
+ &priv->irq_first)) {
+ pr_err("Unable to parse MSI vec base\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ if (of_property_read_u32(node, "loongson,msi-num-vecs",
+ &priv->num_irqs)) {
+ pr_err("Unable to parse MSI vec number\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_irqs),
+ sizeof(*priv->msi_map),
+ GFP_KERNEL);
+ if (!priv->msi_map) {
+ ret = -ENOMEM;
+ goto err_priv;
+ }
+
+ pr_debug("Registering %d MSIs, starting at %d\n",
+ priv->num_irqs, priv->irq_first);
+
+ ret = pch_msi_init_domains(priv, node, parent);
+ if (ret)
+ goto err_map;
+
+ return 0;
+
+err_map:
+ kfree(priv->msi_map);
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
--
2.26.0.rc2

2020-04-22 14:29:56

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Add binding for Loongson PCH MSI controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../loongson,pch-msi.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
new file mode 100644
index 000000000000..dfb9cecacba0
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH MSI Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+description: |
+ This interrupt controller is found in the Loongson-7A family of PCH for
+ transforming interrupts from PCIe MSI into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-msi-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,msi-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH MSI.
+
+ loongson,msi-num-vecs:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the number of parent HyperTransport vectors allocated
+ to PCH MSI.
+
+ msi-controller: true
+
+required:
+ - compatible
+ - reg
+ - msi-controller
+ - loongson,msi-base-vec
+ - loongson,msi-num-vecs
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ msi: msi-controller@2ff00000 {
+ compatible = "loongson,pch-msi-1.0";
+ reg = <0x2ff00000 0x4>;
+ msi-controller;
+ loongson,msi-base-vec = <64>;
+ loongson,msi-num-vecs = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-04-22 14:30:19

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 3/6] irqchip: Add Loongson PCH PIC controller

This controller appears on Loongson-7A family of PCH to transform
interrupts from devices into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
drivers/irqchip/Kconfig | 8 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-pic.c | 256 +++++++++++++++++++++++++
3 files changed, 265 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index de4564e2ea88..4ab7a9b1a5c2 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -540,4 +540,12 @@ config LOONGSON_HTVEC
help
Support for the Loongson3 HyperTransport Interrupt Vector Controller.

+config LOONGSON_PCH_PIC
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Support for the Loongson PCH PIC Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 74561879f5a7..acc72331cec8 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -108,3 +108,4 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c
new file mode 100644
index 000000000000..717ab8335074
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-pic.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH PIC support
+ */
+
+#define pr_fmt(fmt) "pch-pic: " fmt
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.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>
+
+/* Registers */
+#define PCH_PIC_MASK 0x20
+#define PCH_PIC_HTMSI_EN 0x40
+#define PCH_PIC_EDGE 0x60
+#define PCH_PIC_CLR 0x80
+#define PCH_PIC_AUTO0 0xc0
+#define PCH_PIC_AUTO1 0xe0
+#define PCH_INT_ROUTE(irq) (0x100 + irq)
+#define PCH_INT_HTVEC(irq) (0x200 + irq)
+#define PCH_PIC_POL 0x3e0
+
+#define PIC_COUNT_PER_REG 32
+#define PIC_REG_COUNT 2
+#define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
+#define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
+#define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
+
+struct pch_pic {
+ void __iomem *base;
+ struct irq_domain *pic_domain;
+ int ht_vec_base;
+ raw_spinlock_t pic_lock;
+};
+
+static void pch_pic_bitset(void __iomem *addr, int bit)
+{
+ u32 reg;
+
+ addr += PIC_REG_IDX(bit) * 4;
+ reg = readl(addr);
+ reg |= BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+}
+
+static void pch_pic_bitclr(void __iomem *addr, int bit)
+{
+ u32 reg;
+
+ addr += PIC_REG_IDX(bit) * 4;
+ reg = readl(addr);
+ reg &= ~BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+}
+
+static void pch_pic_eoi_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ u32 idx = PIC_REG_IDX(d->hwirq);
+
+ writel(BIT(PIC_REG_BIT(d->hwirq)),
+ priv->base + PCH_PIC_CLR + idx * 4);
+}
+
+static void pch_pic_mask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ pch_pic_bitset(priv->base + PCH_PIC_MASK, d->hwirq);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_pic_unmask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ pch_pic_bitclr(priv->base + PCH_PIC_MASK, d->hwirq);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+ irq_chip_unmask_parent(d);
+}
+
+static int pch_pic_set_type(struct irq_data *d, unsigned int type)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ int ret = 0;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+
+ return ret;
+}
+
+static void pch_pic_enable_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ u8 htvec = d->hwirq + priv->ht_vec_base;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ writeb(htvec, priv->base + PCH_INT_HTVEC(d->hwirq));
+ /* Hardcode to HT0 Lo */
+ writeb(1, priv->base + PCH_INT_ROUTE(d->hwirq));
+ /* Clear auto bounce, we don't need that */
+ pch_pic_bitclr(priv->base + PCH_PIC_AUTO0, d->hwirq);
+ pch_pic_bitclr(priv->base + PCH_PIC_AUTO1, d->hwirq);
+ /* Enable HTMSI transformer */
+ pch_pic_bitset(priv->base + PCH_PIC_HTMSI_EN, d->hwirq);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+ pch_pic_unmask_irq(d);
+}
+
+static struct irq_chip pch_pic_irq_chip = {
+ .name = "PCH PIC",
+ .irq_eoi = pch_pic_eoi_irq,
+ .irq_mask = pch_pic_mask_irq,
+ .irq_unmask = pch_pic_unmask_irq,
+ .irq_enable = pch_pic_enable_irq,
+ .irq_disable = pch_pic_mask_irq,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = pch_pic_set_type,
+};
+
+static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct pch_pic *priv = domain->host_data;
+ struct irq_fwspec fwspec;
+ unsigned long hwirq;
+ unsigned int type;
+ int err;
+
+ irq_domain_translate_twocell(domain, arg, &hwirq, &type);
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq + priv->ht_vec_base;
+
+ err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (err)
+ return err;
+
+ irq_domain_set_info(domain, virq, hwirq,
+ &pch_pic_irq_chip, priv,
+ handle_fasteoi_irq, NULL, NULL);
+ irq_set_probe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops pch_pic_domain_ops = {
+ .translate = irq_domain_translate_twocell,
+ .alloc = pch_pic_alloc,
+ .free = irq_domain_free_irqs_parent,
+};
+
+static void pch_pic_reset(struct pch_pic *priv)
+{
+ u32 idx;
+
+ /* Clear IRQ cause registers, mask all interrupts */
+ for (idx = 0; idx < PIC_REG_COUNT; idx++) {
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * idx);
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * idx);
+ }
+}
+
+static int pch_pic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_pic *priv;
+ struct irq_domain *parent_domain;
+ int err;
+ u32 ht_vec_base;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->pic_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ err = -ENXIO;
+ goto iounmap_base;
+ }
+
+ if (of_property_read_u32(node, "loongson,pic-base-vec",
+ &ht_vec_base))
+ priv->ht_vec_base = 64;
+ else
+ priv->ht_vec_base = ht_vec_base;
+
+ priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ PIC_COUNT,
+ of_node_to_fwnode(node),
+ &pch_pic_domain_ops,
+ priv);
+ if (!priv->pic_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ pch_pic_reset(priv);
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
--
2.26.0.rc2

2020-04-22 15:07:12

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH 1/6] irqchip: Add Loongson HyperTransport Vector support

This controller appears on Loongson-3 chips for receiving interrupt
vectors from PCH's PIC and PCH's PCIe MSI interrupts. It usually act
as the top of irq hierarchy.

Signed-off-by: Jiaxun Yang <[email protected]>
---
drivers/irqchip/Kconfig | 8 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-htvec.c | 217 +++++++++++++++++++++++++++
3 files changed, 226 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-htvec.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index a85aada04a64..de4564e2ea88 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -532,4 +532,12 @@ config LOONGSON_HTPIC
help
Support for the Loongson-3 HyperTransport PIC Controller.

+config LOONGSON_HTVEC
+ bool "Loongson3 HyperTransport Interrupt Vector Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Support for the Loongson3 HyperTransport Interrupt Vector Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 37bbe39bf909..74561879f5a7 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -107,3 +107,4 @@ obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
+obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
new file mode 100644
index 000000000000..e155ebb99efb
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-htvec.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson HyperTransport Interrupt Vector support
+ */
+
+#define pr_fmt(fmt) "htvec: " 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>
+
+/* Registers */
+#define HTVEC_EN_OFF 0x20
+#define HTVEC_MAX_PARENT_IRQ 4
+
+#define VEC_COUNT_PER_REG 32
+#define VEC_REG_COUNT 4
+#define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT)
+#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
+#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
+
+struct htvec {
+ void __iomem *base;
+ struct irq_domain *htvec_domain;
+ raw_spinlock_t htvec_lock;
+};
+
+static void htvec_irq_dispatch(struct irq_desc *desc)
+{
+ struct htvec *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ uint32_t pending;
+ bool handled = false;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < VEC_REG_COUNT; i++) {
+ pending = readl(priv->base + 4 * i);
+ /* Ack all IRQs at once, otherwise IRQ flood might happen */
+ writel(pending, priv->base + 4 * i);
+ while (pending) {
+ int bit = __ffs(pending);
+
+ generic_handle_irq(irq_linear_revmap(priv->htvec_domain,
+ bit + 32 * i));
+ pending &= ~BIT(bit);
+ handled = true;
+ }
+ }
+
+ if (!handled)
+ spurious_interrupt();
+
+ chained_irq_exit(chip, desc);
+}
+
+static void htvec_bitset(void __iomem *addr, int bit)
+{
+ u32 reg;
+
+ addr += VEC_REG_IDX(bit) * 4;
+ reg = readl(addr);
+ reg |= BIT(VEC_REG_BIT(bit));
+ writel(reg, addr);
+}
+
+static void htvec_bitclr(void __iomem *addr, int bit)
+{
+ u32 reg;
+
+ addr += VEC_REG_IDX(bit) * 4;
+ reg = readl(addr);
+ reg &= ~BIT(VEC_REG_BIT(bit));
+ writel(reg, addr);
+}
+
+static void htvec_mask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ htvec_bitclr(priv->base + HTVEC_EN_OFF, d->hwirq);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static void htvec_unmask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ htvec_bitset(priv->base + HTVEC_EN_OFF, d->hwirq);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static struct irq_chip htvec_irq_chip = {
+ .name = "LOONGSON_HTVEC",
+ .irq_mask = htvec_mask_irq,
+ .irq_unmask = htvec_unmask_irq,
+};
+
+static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct htvec *priv = domain->host_data;
+ unsigned long hwirq;
+ unsigned int type;
+
+ irq_domain_translate_onecell(domain, arg, &hwirq, &type);
+
+ /* Not much to do, just setup the irqdata */
+ irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &htvec_irq_chip, priv);
+
+ return 0;
+}
+
+static void htvec_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 htvec_domain_ops = {
+ .translate = irq_domain_translate_onecell,
+ .alloc = htvec_domain_alloc,
+ .free = htvec_domain_free,
+};
+
+static void htvec_reset(struct htvec *priv)
+{
+ u32 idx;
+
+ /* Clear IRQ cause registers, mask all interrupts */
+ for (idx = 0; idx < VEC_REG_COUNT; idx++) {
+ writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
+ writel_relaxed(0xFFFFFFFF, priv->base);
+ }
+}
+
+static int htvec_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct htvec *priv;
+ int err, parent_irq[4], num_parents = 0, i;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->htvec_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ /* Interrupt may come from any of the 4 interrupt line */
+ for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
+ parent_irq[i] = irq_of_parse_and_map(node, i);
+ if (parent_irq[i] <= 0)
+ break;
+
+ num_parents++;
+ }
+
+ if (!num_parents) {
+ pr_err("Failed to get parent irqs\n");
+ err = -ENODEV;
+ goto iounmap_base;
+ }
+
+ priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node),
+ VEC_COUNT,
+ &htvec_domain_ops,
+ priv);
+ if (!priv->htvec_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ htvec_reset(priv);
+
+ for (i = 0; i < num_parents; i++) {
+ irq_set_chained_handler_and_data(parent_irq[i],
+ htvec_irq_dispatch, priv);
+ }
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);
--
2.26.0.rc2

2020-04-23 05:45:51

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 0/6] Loongson PCH IRQ Support

Hi, Jiaxun,

We have established a rule before, so please don't call the PCH bridge
as Loongson-7A or Loongson-7A1000. You can call it LS7A, or Loongson's
LS7A if needed.

Huacai

On Wed, Apr 22, 2020 at 10:26 PM Jiaxun Yang <[email protected]> wrote:
>
> This series mainly added IRQ support for Loongson-7A1000 PCH.
> DeviceTree will be added later as PCI support is also pending
> for reviewing.
>
> Jiaxun Yang (6):
> irqchip: Add Loongson HyperTransport Vector support
> dt-bindings: interrupt-controller: Add Loongson HTVEC
> irqchip: Add Loongson PCH PIC controller
> dt-bindings: interrupt-controller: Add Loongson PCH PIC
> irqchip: Add Loongson PCH MSI controller
> dt-bindings: interrupt-controller: Add Loongson PCH MSI
>
> .../interrupt-controller/loongson,htvec.yaml | 59 ++++
> .../loongson,pch-msi.yaml | 56 ++++
> .../loongson,pch-pic.yaml | 55 ++++
> drivers/irqchip/Kconfig | 26 ++
> drivers/irqchip/Makefile | 3 +
> drivers/irqchip/irq-loongson-htvec.c | 217 ++++++++++++++
> drivers/irqchip/irq-loongson-pch-msi.c | 265 ++++++++++++++++++
> drivers/irqchip/irq-loongson-pch-pic.c | 256 +++++++++++++++++
> 8 files changed, 937 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
> create mode 100644 drivers/irqchip/irq-loongson-htvec.c
> create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c
> create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c
>
> --
> 2.26.0.rc2
>

2020-04-23 05:49:00

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 4/6] dt-bindings: interrupt-controller: Add Loongson PCH PIC

Hi, Jiaxun,

On Wed, Apr 22, 2020 at 10:27 PM Jiaxun Yang <[email protected]> wrote:
>
> Add binding for Loongson PCH PIC Controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> .../loongson,pch-pic.yaml | 55 +++++++++++++++++++
> 1 file changed, 55 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
> new file mode 100644
> index 000000000000..afc0c924e477
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
> @@ -0,0 +1,55 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-pic.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Loongson PCH PIC Controller
> +
> +maintainers:
> + - Jiaxun Yang <[email protected]>
> +
> +allOf:
> + - $ref: /schemas/interrupt-controller.yaml#
> +
> +description: |
> + This interrupt controller is found in the Loongson-7A family of PCH for
Please use "Loongson's LS7A family" here.

> + transforming interrupts from on-chip devices into HyperTransport vectorized
> + interrupts.
> +
> +properties:
> + compatible:
> + const: loongson,pch-pic-1.0
> +
> + reg:
> + maxItems: 1
> +
> + loongson,pic-base-vec:
> + $ref: '/schemas/types.yaml#/definitions/uint32'
> + description: |
> + u32 value of the base of parent HyperTransport vector allocated
> + to PCH PIC.
> +
> + interrupt-controller: true
> +
> + '#interrupt-cells':
> + const: 2
> +
> +required:
> + - compatible
> + - reg
> + - interrupt-controller
> + - '#interrupt-cells'
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + pic: interrupt-controller@10000000 {
> + compatible = "loongson,pch-pic-1.0";
> + reg = <0x10000000 0x400>;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + loongson,pic-base-vec = <64>;
> + interrupt-parent = <&htvec>;
> + };
> +...
> --
> 2.26.0.rc2
>

2020-04-23 05:50:03

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Hi, Jiaxun,

On Wed, Apr 22, 2020 at 10:28 PM Jiaxun Yang <[email protected]> wrote:
>
> Add binding for Loongson PCH MSI controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> .../loongson,pch-msi.yaml | 56 +++++++++++++++++++
> 1 file changed, 56 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> new file mode 100644
> index 000000000000..dfb9cecacba0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> @@ -0,0 +1,56 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Loongson PCH MSI Controller
> +
> +maintainers:
> + - Jiaxun Yang <[email protected]>
> +
> +description: |
> + This interrupt controller is found in the Loongson-7A family of PCH for
Please use "Loongson's LS7A family" here.

> + transforming interrupts from PCIe MSI into HyperTransport vectorized
> + interrupts.
> +
> +properties:
> + compatible:
> + const: loongson,pch-msi-1.0
> +
> + reg:
> + maxItems: 1
> +
> + loongson,msi-base-vec:
> + $ref: '/schemas/types.yaml#/definitions/uint32'
> + description: |
> + u32 value of the base of parent HyperTransport vector allocated
> + to PCH MSI.
> +
> + loongson,msi-num-vecs:
> + $ref: '/schemas/types.yaml#/definitions/uint32'
> + description: |
> + u32 value of the number of parent HyperTransport vectors allocated
> + to PCH MSI.
> +
> + msi-controller: true
> +
> +required:
> + - compatible
> + - reg
> + - msi-controller
> + - loongson,msi-base-vec
> + - loongson,msi-num-vecs
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + msi: msi-controller@2ff00000 {
> + compatible = "loongson,pch-msi-1.0";
> + reg = <0x2ff00000 0x4>;
> + msi-controller;
> + loongson,msi-base-vec = <64>;
> + loongson,msi-num-vecs = <64>;
> + interrupt-parent = <&htvec>;
> + };
> +...
> --
> 2.26.0.rc2
>

2020-04-23 05:51:12

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 3/6] irqchip: Add Loongson PCH PIC controller

Hi, Jiaxun,

On Wed, Apr 22, 2020 at 10:26 PM Jiaxun Yang <[email protected]> wrote:
>
> This controller appears on Loongson-7A family of PCH to transform
Please use "Loongson's LS7A family" here.

> interrupts from devices into HyperTransport vectorized interrrupts
> and send them to procrssor's HT vector controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> drivers/irqchip/Kconfig | 8 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-pch-pic.c | 256 +++++++++++++++++++++++++
> 3 files changed, 265 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index de4564e2ea88..4ab7a9b1a5c2 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -540,4 +540,12 @@ config LOONGSON_HTVEC
> help
> Support for the Loongson3 HyperTransport Interrupt Vector Controller.
>
> +config LOONGSON_PCH_PIC
> + bool "Loongson PCH PIC Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + help
> + Support for the Loongson PCH PIC Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 74561879f5a7..acc72331cec8 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -108,3 +108,4 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
> obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c
> new file mode 100644
> index 000000000000..717ab8335074
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-pch-pic.c
> @@ -0,0 +1,256 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson PCH PIC support
> + */
> +
> +#define pr_fmt(fmt) "pch-pic: " fmt
> +
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqdomain.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>
> +
> +/* Registers */
> +#define PCH_PIC_MASK 0x20
> +#define PCH_PIC_HTMSI_EN 0x40
> +#define PCH_PIC_EDGE 0x60
> +#define PCH_PIC_CLR 0x80
> +#define PCH_PIC_AUTO0 0xc0
> +#define PCH_PIC_AUTO1 0xe0
> +#define PCH_INT_ROUTE(irq) (0x100 + irq)
> +#define PCH_INT_HTVEC(irq) (0x200 + irq)
> +#define PCH_PIC_POL 0x3e0
> +
> +#define PIC_COUNT_PER_REG 32
> +#define PIC_REG_COUNT 2
> +#define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
> +#define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
> +#define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
> +
> +struct pch_pic {
> + void __iomem *base;
> + struct irq_domain *pic_domain;
> + int ht_vec_base;
> + raw_spinlock_t pic_lock;
> +};
> +
> +static void pch_pic_bitset(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += PIC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg |= BIT(PIC_REG_BIT(bit));
> + writel(reg, addr);
> +}
> +
> +static void pch_pic_bitclr(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += PIC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg &= ~BIT(PIC_REG_BIT(bit));
> + writel(reg, addr);
> +}
> +
> +static void pch_pic_eoi_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + u32 idx = PIC_REG_IDX(d->hwirq);
> +
> + writel(BIT(PIC_REG_BIT(d->hwirq)),
> + priv->base + PCH_PIC_CLR + idx * 4);
> +}
> +
> +static void pch_pic_mask_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + pch_pic_bitset(priv->base + PCH_PIC_MASK, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_pic_unmask_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + pch_pic_bitclr(priv->base + PCH_PIC_MASK, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + irq_chip_unmask_parent(d);
> +}
> +
> +static int pch_pic_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + int ret = 0;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_LEVEL_LOW:
> + pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> +
> + return ret;
> +}
> +
> +static void pch_pic_enable_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + u8 htvec = d->hwirq + priv->ht_vec_base;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + writeb(htvec, priv->base + PCH_INT_HTVEC(d->hwirq));
> + /* Hardcode to HT0 Lo */
> + writeb(1, priv->base + PCH_INT_ROUTE(d->hwirq));
> + /* Clear auto bounce, we don't need that */
> + pch_pic_bitclr(priv->base + PCH_PIC_AUTO0, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_AUTO1, d->hwirq);
> + /* Enable HTMSI transformer */
> + pch_pic_bitset(priv->base + PCH_PIC_HTMSI_EN, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + pch_pic_unmask_irq(d);
> +}
> +
> +static struct irq_chip pch_pic_irq_chip = {
> + .name = "PCH PIC",
> + .irq_eoi = pch_pic_eoi_irq,
> + .irq_mask = pch_pic_mask_irq,
> + .irq_unmask = pch_pic_unmask_irq,
> + .irq_enable = pch_pic_enable_irq,
> + .irq_disable = pch_pic_mask_irq,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_set_type = pch_pic_set_type,
> +};
> +
> +static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + struct pch_pic *priv = domain->host_data;
> + struct irq_fwspec fwspec;
> + unsigned long hwirq;
> + unsigned int type;
> + int err;
> +
> + irq_domain_translate_twocell(domain, arg, &hwirq, &type);
> +
> + fwspec.fwnode = domain->parent->fwnode;
> + fwspec.param_count = 1;
> + fwspec.param[0] = hwirq + priv->ht_vec_base;
> +
> + err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
> + if (err)
> + return err;
> +
> + irq_domain_set_info(domain, virq, hwirq,
> + &pch_pic_irq_chip, priv,
> + handle_fasteoi_irq, NULL, NULL);
> + irq_set_probe(virq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops pch_pic_domain_ops = {
> + .translate = irq_domain_translate_twocell,
> + .alloc = pch_pic_alloc,
> + .free = irq_domain_free_irqs_parent,
> +};
> +
> +static void pch_pic_reset(struct pch_pic *priv)
> +{
> + u32 idx;
> +
> + /* Clear IRQ cause registers, mask all interrupts */
> + for (idx = 0; idx < PIC_REG_COUNT; idx++) {
> + writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * idx);
> + writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * idx);
> + }
> +}
> +
> +static int pch_pic_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_pic *priv;
> + struct irq_domain *parent_domain;
> + int err;
> + u32 ht_vec_base;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + raw_spin_lock_init(&priv->pic_lock);
> + priv->base = of_iomap(node, 0);
> + if (!priv->base) {
> + err = -ENOMEM;
> + goto free_priv;
> + }
> +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + pr_err("Failed to find the parent domain\n");
> + err = -ENXIO;
> + goto iounmap_base;
> + }
> +
> + if (of_property_read_u32(node, "loongson,pic-base-vec",
> + &ht_vec_base))
> + priv->ht_vec_base = 64;
> + else
> + priv->ht_vec_base = ht_vec_base;
> +
> + priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
> + PIC_COUNT,
> + of_node_to_fwnode(node),
> + &pch_pic_domain_ops,
> + priv);
> + if (!priv->pic_domain) {
> + pr_err("Failed to create IRQ domain\n");
> + err = -ENOMEM;
> + goto iounmap_base;
> + }
> +
> + pch_pic_reset(priv);
> +
> + return 0;
> +
> +iounmap_base:
> + iounmap(priv->base);
> +free_priv:
> + kfree(priv);
> +
> + return err;
> +}
> +
> +IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
> --
> 2.26.0.rc2
>

2020-04-23 05:52:49

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 5/6] irqchip: Add Loongson PCH MSI controller

Hi, Jiaxun

On Wed, Apr 22, 2020 at 10:27 PM Jiaxun Yang <[email protected]> wrote:
>
> This controller appears on Loongson-7A family of PCH to transform
Please use "Loongson's LS7A family" here.

> interrupts from PCI MSI into HyperTransport vectorized interrrupts
> and send them to procrssor's HT vector controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> drivers/irqchip/Kconfig | 10 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-pch-msi.c | 265 +++++++++++++++++++++++++
> 3 files changed, 276 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 4ab7a9b1a5c2..9f2935418f33 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -548,4 +548,14 @@ config LOONGSON_PCH_PIC
> help
> Support for the Loongson PCH PIC Controller.
>
> +config LOONGSON_PCH_MSI
> + bool "Loongson PCH PIC Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + depends on PCI
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + select PCI_MSI
> + help
> + Support for the Loongson PCH MSI Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index acc72331cec8..3a4ce283189a 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
> +obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
> diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
> new file mode 100644
> index 000000000000..4bb00f2ce86a
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-pch-msi.c
> @@ -0,0 +1,265 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson PCH MSI support
> + */
> +
> +#define pr_fmt(fmt) "pch-msi: " fmt
> +
> +#include <linux/irqchip.h>
> +#include <linux/msi.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_pci.h>
> +#include <linux/pci.h>
> +#include <linux/slab.h>
> +
> +struct pch_msi_data {
> + spinlock_t msi_map_lock;
> + phys_addr_t addr;
> + u32 irq_first; /* The vectoe number that MSIs start */
> + u32 num_irqs; /* The number of vector for MSIs */
> + unsigned long *msi_map;
> +};
> +
> +static void pch_msi_mask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_mask_irq(d);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_unmask_irq(d);
> + irq_chip_unmask_parent(d);
> +}
> +
> +static struct irq_chip pch_msi_irq_chip = {
> + .name = "PCH MSI",
> + .irq_mask = pch_msi_mask_msi_irq,
> + .irq_unmask = pch_msi_unmask_msi_irq,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> +};
> +
> +static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
> +{
> + int first;
> +
> + spin_lock(&priv->msi_map_lock);
> +
> + first = bitmap_find_next_zero_area(priv->msi_map, priv->num_irqs, 0,
> + num_req, 0);
> + if (first >= priv->num_irqs) {
> + spin_unlock(&priv->msi_map_lock);
> + return -ENOSPC;
> + }
> +
> + bitmap_set(priv->msi_map, first, num_req);
> +
> + spin_unlock(&priv->msi_map_lock);
> +
> + return priv->irq_first + first;
> +}
> +
> +static void pch_msi_free_hwirq(struct pch_msi_data *priv,
> + int hwirq, int num_req)
> +{
> + int first = hwirq - priv->irq_first;
> +
> + spin_lock(&priv->msi_map_lock);
> +
> + bitmap_clear(priv->msi_map, first, num_req);
> +
> + spin_unlock(&priv->msi_map_lock);
> +}
> +
> +static void pch_msi_compose_msi_msg(struct irq_data *data,
> + struct msi_msg *msg)
> +{
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
> + phys_addr_t msg_addr = priv->addr;
> +
> + msg->address_hi = upper_32_bits(msg_addr);
> + msg->address_lo = lower_32_bits(msg_addr);
> + msg->data = data->hwirq;
> +}
> +
> +static struct msi_domain_info pch_msi_domain_info = {
> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
> + .chip = &pch_msi_irq_chip,
> +};
> +
> +static struct irq_chip middle_irq_chip = {
> + .name = "PCH MSI Middle",
> + .irq_mask = irq_chip_mask_parent,
> + .irq_unmask = irq_chip_unmask_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_compose_msi_msg = pch_msi_compose_msi_msg,
> +};
> +
> +static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, int hwirq)
> +{
> + struct irq_fwspec fwspec;
> + int ret;
> +
> + if (!is_of_node(domain->parent->fwnode))
> + return -EINVAL;
> +
> + fwspec.fwnode = domain->parent->fwnode;
> + fwspec.param_count = 1;
> + fwspec.param[0] = hwirq;
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
> + if (ret)
> + return ret;
> +
> + irq_domain_set_info(domain, virq, hwirq,
> + &middle_irq_chip, NULL,
> + handle_simple_irq, NULL, NULL);
> + irq_set_probe(virq);
> +
> + return 0;
> +}
> +
> +static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs, void *args)
> +{
> + struct pch_msi_data *priv = domain->host_data;
> + int hwirq, err, i;
> +
> + hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
> + if (hwirq < 0)
> + return hwirq;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
> + if (err)
> + goto err_hwirq;
> +
> + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
> + &middle_irq_chip, priv);
> + }
> +
> + return 0;
> +err_hwirq:
> + while (--i >= 0)
> + irq_domain_free_irqs_parent(domain, virq, i);
> +
> + pch_msi_free_hwirq(priv, hwirq, nr_irqs);
> + return err;
> +}
> +
> +static void pch_msi_middle_domain_free(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
> +
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> + pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
> +}
> +
> +static const struct irq_domain_ops pch_msi_middle_domain_ops = {
> + .alloc = pch_msi_middle_domain_alloc,
> + .free = pch_msi_middle_domain_free,
> +};
> +
> +static int pch_msi_init_domains(struct pch_msi_data *priv,
> + struct device_node *node,
> + struct device_node *parent)
> +{
> + struct irq_domain *middle_domain, *msi_domain, *parent_domain;
> +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + pr_err("Failed to find the parent domain\n");
> + return -ENXIO;
> + }
> +
> + middle_domain = irq_domain_add_tree(NULL,
> + &pch_msi_middle_domain_ops,
> + priv);
> + if (!middle_domain) {
> + pr_err("Failed to create the MSI middle domain\n");
> + return -ENOMEM;
> + }
> +
> + middle_domain->parent = parent_domain;
> +
> + msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
> + &pch_msi_domain_info,
> + middle_domain);
> + if (!msi_domain) {
> + pr_err("Failed to create MSI domain\n");
> + irq_domain_remove(middle_domain);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static int pch_msi_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_msi_data *priv;
> + struct resource res;
> + int ret;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + spin_lock_init(&priv->msi_map_lock);
> +
> + ret = of_address_to_resource(node, 0, &res);
> + if (ret) {
> + pr_err("Failed to allocate resource\n");
> + goto err_priv;
> + }
> +
> + priv->addr = res.start;
> +
> + if (of_property_read_u32(node, "loongson,msi-base-vec",
> + &priv->irq_first)) {
> + pr_err("Unable to parse MSI vec base\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + if (of_property_read_u32(node, "loongson,msi-num-vecs",
> + &priv->num_irqs)) {
> + pr_err("Unable to parse MSI vec number\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_irqs),
> + sizeof(*priv->msi_map),
> + GFP_KERNEL);
> + if (!priv->msi_map) {
> + ret = -ENOMEM;
> + goto err_priv;
> + }
> +
> + pr_debug("Registering %d MSIs, starting at %d\n",
> + priv->num_irqs, priv->irq_first);
> +
> + ret = pch_msi_init_domains(priv, node, parent);
> + if (ret)
> + goto err_map;
> +
> + return 0;
> +
> +err_map:
> + kfree(priv->msi_map);
> +err_priv:
> + kfree(priv);
> + return ret;
> +}
> +
> +IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
> --
> 2.26.0.rc2
>

2020-04-23 12:47:12

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Huacai,

On 2020-04-23 06:55, Huacai Chen wrote:
> Hi, Jiaxun,
>
> On Wed, Apr 22, 2020 at 10:28 PM Jiaxun Yang <[email protected]>
> wrote:
>>
>> Add binding for Loongson PCH MSI controller.
>>
>> Signed-off-by: Jiaxun Yang <[email protected]>
>> ---
>> .../loongson,pch-msi.yaml | 56
>> +++++++++++++++++++
>> 1 file changed, 56 insertions(+)
>> create mode 100644
>> Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>>
>> diff --git
>> a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>> b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>> new file mode 100644
>> index 000000000000..dfb9cecacba0
>> --- /dev/null
>> +++
>> b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>> @@ -0,0 +1,56 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id:
>> "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
>> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
>> +
>> +title: Loongson PCH MSI Controller
>> +
>> +maintainers:
>> + - Jiaxun Yang <[email protected]>
>> +
>> +description: |
>> + This interrupt controller is found in the Loongson-7A family of PCH
>> for
> Please use "Loongson's LS7A family" here.

It's the fourth email you send on the same subject. I think the author
has got the message already. Frankly, it is only a name, and if they
want to call it Bob, so be it.

Thanks,

M.
--
Jazz is not dead. It just smells funny...

2020-04-23 13:33:45

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 1/6] irqchip: Add Loongson HyperTransport Vector support

On Wed, 22 Apr 2020 15:24:21 +0100,
Jiaxun Yang <[email protected]> wrote:
>
> This controller appears on Loongson-3 chips for receiving interrupt
> vectors from PCH's PIC and PCH's PCIe MSI interrupts. It usually act
> as the top of irq hierarchy.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> drivers/irqchip/Kconfig | 8 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-htvec.c | 217 +++++++++++++++++++++++++++
> 3 files changed, 226 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-htvec.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index a85aada04a64..de4564e2ea88 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -532,4 +532,12 @@ config LOONGSON_HTPIC
> help
> Support for the Loongson-3 HyperTransport PIC Controller.
>
> +config LOONGSON_HTVEC
> + bool "Loongson3 HyperTransport Interrupt Vector Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + help
> + Support for the Loongson3 HyperTransport Interrupt Vector Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 37bbe39bf909..74561879f5a7 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -107,3 +107,4 @@ obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
> obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
> obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
> obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
> +obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
> diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
> new file mode 100644
> index 000000000000..e155ebb99efb
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-htvec.c
> @@ -0,0 +1,217 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson HyperTransport Interrupt Vector support
> + */
> +
> +#define pr_fmt(fmt) "htvec: " 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>
> +
> +/* Registers */
> +#define HTVEC_EN_OFF 0x20
> +#define HTVEC_MAX_PARENT_IRQ 4
> +
> +#define VEC_COUNT_PER_REG 32
> +#define VEC_REG_COUNT 4
> +#define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT)
> +#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
> +#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
> +
> +struct htvec {
> + void __iomem *base;
> + struct irq_domain *htvec_domain;
> + raw_spinlock_t htvec_lock;

nit: please align member of the structure vertically:

struct htvec {
void __iomem *base;
struct irq_domain *htvec_d
raw_spinlock_t htvec_lock;
};

> +};
> +
> +static void htvec_irq_dispatch(struct irq_desc *desc)
> +{
> + struct htvec *priv = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + uint32_t pending;

In the kernel, please use u32. uint32_t is reserved for uapi code.

> + bool handled = false;
> + int i;
> +
> + chained_irq_enter(chip, desc);

If this is a chained interrupt controller, it isn't the top-level
interrupt controller, as it obviously feed into another one.

> +
> + for (i = 0; i < VEC_REG_COUNT; i++) {
> + pending = readl(priv->base + 4 * i);
> + /* Ack all IRQs at once, otherwise IRQ flood might happen */
> + writel(pending, priv->base + 4 * i);
> + while (pending) {
> + int bit = __ffs(pending);
> +
> + generic_handle_irq(irq_linear_revmap(priv->htvec_domain,
> + bit + 32 * i));

Isn't this 32 actually VEC_COUNT_PER_REG?

> + pending &= ~BIT(bit);
> + handled = true;
> + }
> + }
> +
> + if (!handled)
> + spurious_interrupt();
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static void htvec_bitset(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += VEC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg |= BIT(VEC_REG_BIT(bit));
> + writel(reg, addr);
> +}
> +
> +static void htvec_bitclr(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += VEC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg &= ~BIT(VEC_REG_BIT(bit));
> + writel(reg, addr);
> +}

Given that these two functions have only a single call site, please
move them into their respective caller. At least we won't have to
worry about the locking.

> +
> +static void htvec_mask_irq(struct irq_data *d)
> +{
> + struct htvec *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->htvec_lock, flags);
> + htvec_bitclr(priv->base + HTVEC_EN_OFF, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
> +}
> +
> +static void htvec_unmask_irq(struct irq_data *d)
> +{
> + struct htvec *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->htvec_lock, flags);
> + htvec_bitset(priv->base + HTVEC_EN_OFF, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
> +}
> +
> +static struct irq_chip htvec_irq_chip = {
> + .name = "LOONGSON_HTVEC",
> + .irq_mask = htvec_mask_irq,
> + .irq_unmask = htvec_unmask_irq,
> +};
> +
> +static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + struct htvec *priv = domain->host_data;
> + unsigned long hwirq;
> + unsigned int type;
> +
> + irq_domain_translate_onecell(domain, arg, &hwirq, &type);
> +
> + /* Not much to do, just setup the irqdata */
> + irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
> + &htvec_irq_chip, priv);

What sets the flow handler?

Another thing is that you ignore the "nr_irqs" parameter, while you
are handling it in the free callback. You have to be consistent.

> +
> + return 0;
> +}
> +
> +static void htvec_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 htvec_domain_ops = {
> + .translate = irq_domain_translate_onecell,
> + .alloc = htvec_domain_alloc,
> + .free = htvec_domain_free,
> +};
> +
> +static void htvec_reset(struct htvec *priv)
> +{
> + u32 idx;
> +
> + /* Clear IRQ cause registers, mask all interrupts */
> + for (idx = 0; idx < VEC_REG_COUNT; idx++) {
> + writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
> + writel_relaxed(0xFFFFFFFF, priv->base);
> + }
> +}
> +
> +static int htvec_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct htvec *priv;
> + int err, parent_irq[4], num_parents = 0, i;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + raw_spin_lock_init(&priv->htvec_lock);
> + priv->base = of_iomap(node, 0);
> + if (!priv->base) {
> + err = -ENOMEM;
> + goto free_priv;
> + }
> +
> + /* Interrupt may come from any of the 4 interrupt line */
> + for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
> + parent_irq[i] = irq_of_parse_and_map(node, i);
> + if (parent_irq[i] <= 0)
> + break;
> +
> + num_parents++;
> + }
> +
> + if (!num_parents) {
> + pr_err("Failed to get parent irqs\n");
> + err = -ENODEV;
> + goto iounmap_base;
> + }
> +
> + priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node),
> + VEC_COUNT,
> + &htvec_domain_ops,
> + priv);
> + if (!priv->htvec_domain) {
> + pr_err("Failed to create IRQ domain\n");
> + err = -ENOMEM;
> + goto iounmap_base;
> + }
> +
> + htvec_reset(priv);
> +
> + for (i = 0; i < num_parents; i++) {
> + irq_set_chained_handler_and_data(parent_irq[i],
> + htvec_irq_dispatch, priv);
> + }

Useless braces.

> +
> + return 0;
> +
> +iounmap_base:
> + iounmap(priv->base);
> +free_priv:
> + kfree(priv);
> +
> + return err;
> +}
> +
> +IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);

Thanks,

M.

--
Jazz is not dead, it just smells funny.

2020-04-23 14:28:20

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 3/6] irqchip: Add Loongson PCH PIC controller

On Wed, 22 Apr 2020 22:24:23 +0800
Jiaxun Yang <[email protected]> wrote:

> This controller appears on Loongson-7A family of PCH to transform
> interrupts from devices into HyperTransport vectorized interrrupts
> and send them to procrssor's HT vector controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> drivers/irqchip/Kconfig | 8 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-pch-pic.c | 256 +++++++++++++++++++++++++
> 3 files changed, 265 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index de4564e2ea88..4ab7a9b1a5c2 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -540,4 +540,12 @@ config LOONGSON_HTVEC
> help
> Support for the Loongson3 HyperTransport Interrupt Vector Controller.
>
> +config LOONGSON_PCH_PIC
> + bool "Loongson PCH PIC Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + help
> + Support for the Loongson PCH PIC Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 74561879f5a7..acc72331cec8 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -108,3 +108,4 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
> obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c
> new file mode 100644
> index 000000000000..717ab8335074
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-pch-pic.c
> @@ -0,0 +1,256 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson PCH PIC support
> + */
> +
> +#define pr_fmt(fmt) "pch-pic: " fmt
> +
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqchip.h>
> +#include <linux/irqdomain.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>
> +
> +/* Registers */
> +#define PCH_PIC_MASK 0x20
> +#define PCH_PIC_HTMSI_EN 0x40
> +#define PCH_PIC_EDGE 0x60
> +#define PCH_PIC_CLR 0x80
> +#define PCH_PIC_AUTO0 0xc0
> +#define PCH_PIC_AUTO1 0xe0
> +#define PCH_INT_ROUTE(irq) (0x100 + irq)
> +#define PCH_INT_HTVEC(irq) (0x200 + irq)
> +#define PCH_PIC_POL 0x3e0
> +
> +#define PIC_COUNT_PER_REG 32
> +#define PIC_REG_COUNT 2
> +#define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
> +#define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
> +#define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
> +
> +struct pch_pic {
> + void __iomem *base;
> + struct irq_domain *pic_domain;
> + int ht_vec_base;
> + raw_spinlock_t pic_lock;
> +};

Same comment about the layout of the data structure.

> +
> +static void pch_pic_bitset(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += PIC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg |= BIT(PIC_REG_BIT(bit));
> + writel(reg, addr);
> +}
> +
> +static void pch_pic_bitclr(void __iomem *addr, int bit)
> +{
> + u32 reg;
> +
> + addr += PIC_REG_IDX(bit) * 4;
> + reg = readl(addr);
> + reg &= ~BIT(PIC_REG_BIT(bit));
> + writel(reg, addr);
> +}
> +
> +static void pch_pic_eoi_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + u32 idx = PIC_REG_IDX(d->hwirq);
> +
> + writel(BIT(PIC_REG_BIT(d->hwirq)),
> + priv->base + PCH_PIC_CLR + idx * 4);
> +}
> +
> +static void pch_pic_mask_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + pch_pic_bitset(priv->base + PCH_PIC_MASK, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_pic_unmask_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + pch_pic_bitclr(priv->base + PCH_PIC_MASK, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + irq_chip_unmask_parent(d);
> +}
> +
> +static int pch_pic_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + int ret = 0;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + pch_pic_bitset(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_LEVEL_HIGH:
> + pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + case IRQ_TYPE_LEVEL_LOW:
> + pch_pic_bitclr(priv->base + PCH_PIC_EDGE, d->hwirq);
> + pch_pic_bitset(priv->base + PCH_PIC_POL, d->hwirq);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);

It would make a lot more sense if the locking was done in the
bitclr/bitset functions, unless there is a reason for wanting larger
critical sections (but I can't see it at the moment).

> +
> + return ret;
> +}
> +
> +static void pch_pic_enable_irq(struct irq_data *d)
> +{
> + struct pch_pic *priv = irq_data_get_irq_chip_data(d);
> + u8 htvec = d->hwirq + priv->ht_vec_base;
> + unsigned long flags;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);
> + writeb(htvec, priv->base + PCH_INT_HTVEC(d->hwirq));
> + /* Hardcode to HT0 Lo */
> + writeb(1, priv->base + PCH_INT_ROUTE(d->hwirq));
> + /* Clear auto bounce, we don't need that */
> + pch_pic_bitclr(priv->base + PCH_PIC_AUTO0, d->hwirq);
> + pch_pic_bitclr(priv->base + PCH_PIC_AUTO1, d->hwirq);
> + /* Enable HTMSI transformer */
> + pch_pic_bitset(priv->base + PCH_PIC_HTMSI_EN, d->hwirq);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> + pch_pic_unmask_irq(d);

This looks wrong. enable/disable should not call unmask/mask. It is
also odd that you don't have a proper disable callback that does the
opposite of enable (you call the mask callback, which is also wrong).

I suggest you kill enable/disable altogether, and move this code to the
the probe (or reset) function.

> +}
> +
> +static struct irq_chip pch_pic_irq_chip = {
> + .name = "PCH PIC",
> + .irq_eoi = pch_pic_eoi_irq,
> + .irq_mask = pch_pic_mask_irq,
> + .irq_unmask = pch_pic_unmask_irq,
> + .irq_enable = pch_pic_enable_irq,
> + .irq_disable = pch_pic_mask_irq,
> + .irq_set_affinity = irq_chip_set_affinity_parent,

Given that this plugs into the HTVEC irqchip, which doesn't implement
an affinity method (nor that it could, given that it is itself a
chained irqchip), I don't see the point...

> + .irq_set_type = pch_pic_set_type,
> +};
> +
> +static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + struct pch_pic *priv = domain->host_data;
> + struct irq_fwspec fwspec;
> + unsigned long hwirq;
> + unsigned int type;
> + int err;
> +
> + irq_domain_translate_twocell(domain, arg, &hwirq, &type);
> +
> + fwspec.fwnode = domain->parent->fwnode;
> + fwspec.param_count = 1;
> + fwspec.param[0] = hwirq + priv->ht_vec_base;
> +
> + err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
> + if (err)
> + return err;
> +
> + irq_domain_set_info(domain, virq, hwirq,
> + &pch_pic_irq_chip, priv,
> + handle_fasteoi_irq, NULL, NULL);
> + irq_set_probe(virq);
> +
> + return 0;
> +}
> +
> +static const struct irq_domain_ops pch_pic_domain_ops = {
> + .translate = irq_domain_translate_twocell,
> + .alloc = pch_pic_alloc,
> + .free = irq_domain_free_irqs_parent,
> +};
> +
> +static void pch_pic_reset(struct pch_pic *priv)
> +{
> + u32 idx;
> +
> + /* Clear IRQ cause registers, mask all interrupts */
> + for (idx = 0; idx < PIC_REG_COUNT; idx++) {
> + writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * idx);
> + writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * idx);
> + }
> +}
> +
> +static int pch_pic_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_pic *priv;
> + struct irq_domain *parent_domain;
> + int err;
> + u32 ht_vec_base;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + raw_spin_lock_init(&priv->pic_lock);
> + priv->base = of_iomap(node, 0);
> + if (!priv->base) {
> + err = -ENOMEM;
> + goto free_priv;
> + }
> +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + pr_err("Failed to find the parent domain\n");
> + err = -ENXIO;
> + goto iounmap_base;
> + }
> +
> + if (of_property_read_u32(node, "loongson,pic-base-vec",
> + &ht_vec_base))
> + priv->ht_vec_base = 64;

The binding doesn't describe the property as being optional.

> + else
> + priv->ht_vec_base = ht_vec_base;
> +
> + priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
> + PIC_COUNT,
> + of_node_to_fwnode(node),
> + &pch_pic_domain_ops,
> + priv);
> + if (!priv->pic_domain) {
> + pr_err("Failed to create IRQ domain\n");
> + err = -ENOMEM;
> + goto iounmap_base;
> + }
> +
> + pch_pic_reset(priv);
> +
> + return 0;
> +
> +iounmap_base:
> + iounmap(priv->base);
> +free_priv:
> + kfree(priv);
> +
> + return err;
> +}
> +
> +IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);

Thanks,

M.
--
Jazz is not dead. It just smells funny...

2020-04-23 19:17:31

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 5/6] irqchip: Add Loongson PCH MSI controller

On Wed, 22 Apr 2020 22:24:25 +0800
Jiaxun Yang <[email protected]> wrote:

> This controller appears on Loongson-7A family of PCH to transform
> interrupts from PCI MSI into HyperTransport vectorized interrrupts
> and send them to procrssor's HT vector controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> drivers/irqchip/Kconfig | 10 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-pch-msi.c | 265 +++++++++++++++++++++++++
> 3 files changed, 276 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 4ab7a9b1a5c2..9f2935418f33 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -548,4 +548,14 @@ config LOONGSON_PCH_PIC
> help
> Support for the Loongson PCH PIC Controller.
>
> +config LOONGSON_PCH_MSI
> + bool "Loongson PCH PIC Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + depends on PCI
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + select PCI_MSI
> + help
> + Support for the Loongson PCH MSI Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index acc72331cec8..3a4ce283189a 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
> +obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
> diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
> new file mode 100644
> index 000000000000..4bb00f2ce86a
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-pch-msi.c
> @@ -0,0 +1,265 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson PCH MSI support
> + */
> +
> +#define pr_fmt(fmt) "pch-msi: " fmt
> +
> +#include <linux/irqchip.h>
> +#include <linux/msi.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_pci.h>
> +#include <linux/pci.h>
> +#include <linux/slab.h>
> +
> +struct pch_msi_data {
> + spinlock_t msi_map_lock;
> + phys_addr_t addr;

addr isn't very descriptive. use something like "doorbell", or anything
that shows that this is where the endpoint write to to generate a MSI.

> + u32 irq_first; /* The vectoe number that MSIs start */
> + u32 num_irqs; /* The number of vector for MSIs */
> + unsigned long *msi_map;
> +};
> +
> +static void pch_msi_mask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_mask_irq(d);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_unmask_irq(d);
> + irq_chip_unmask_parent(d);
> +}
> +
> +static struct irq_chip pch_msi_irq_chip = {
> + .name = "PCH MSI",
> + .irq_mask = pch_msi_mask_msi_irq,
> + .irq_unmask = pch_msi_unmask_msi_irq,
> + .irq_set_affinity = irq_chip_set_affinity_parent,

Same remark as the previous driver.

> +};
> +
> +static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
> +{
> + int first;
> +
> + spin_lock(&priv->msi_map_lock);
> +
> + first = bitmap_find_next_zero_area(priv->msi_map, priv->num_irqs, 0,
> + num_req, 0);

This doesn't work for Multi-MSI, where the base of the MSI vectors must
be naturally aligned to the number of vectors. See
bitmap_find_free_region() and co.

> + if (first >= priv->num_irqs) {
> + spin_unlock(&priv->msi_map_lock);
> + return -ENOSPC;
> + }
> +
> + bitmap_set(priv->msi_map, first, num_req);
> +
> + spin_unlock(&priv->msi_map_lock);
> +
> + return priv->irq_first + first;
> +}
> +
> +static void pch_msi_free_hwirq(struct pch_msi_data *priv,
> + int hwirq, int num_req)
> +{
> + int first = hwirq - priv->irq_first;
> +
> + spin_lock(&priv->msi_map_lock);
> +
> + bitmap_clear(priv->msi_map, first, num_req);

See bitmap_release_region() in relation to the above.

> +
> + spin_unlock(&priv->msi_map_lock);
> +}
> +
> +static void pch_msi_compose_msi_msg(struct irq_data *data,
> + struct msi_msg *msg)
> +{
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
> + phys_addr_t msg_addr = priv->addr;
> +
> + msg->address_hi = upper_32_bits(msg_addr);
> + msg->address_lo = lower_32_bits(msg_addr);
> + msg->data = data->hwirq;
> +}
> +
> +static struct msi_domain_info pch_msi_domain_info = {
> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
> + .chip = &pch_msi_irq_chip,
> +};
> +
> +static struct irq_chip middle_irq_chip = {
> + .name = "PCH MSI Middle",
> + .irq_mask = irq_chip_mask_parent,
> + .irq_unmask = irq_chip_unmask_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,

again.

> + .irq_compose_msi_msg = pch_msi_compose_msi_msg,
> +};
> +
> +static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, int hwirq)
> +{
> + struct irq_fwspec fwspec;
> + int ret;
> +
> + if (!is_of_node(domain->parent->fwnode))
> + return -EINVAL;

How can that happen?

> +
> + fwspec.fwnode = domain->parent->fwnode;
> + fwspec.param_count = 1;
> + fwspec.param[0] = hwirq;
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
> + if (ret)
> + return ret;
> +
> + irq_domain_set_info(domain, virq, hwirq,
> + &middle_irq_chip, NULL,
> + handle_simple_irq, NULL, NULL);

No, this should at least be handle_edge_irq. More importantly, you
should use the flow set by the underlying irqchip, and not provide your
own.

> + irq_set_probe(virq);

Probe? what does it mean for MSIs? I also don't see how you tell the
underlying irqchip that the MSI is edge triggered.

> +
> + return 0;
> +}
> +
> +static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs, void *args)
> +{
> + struct pch_msi_data *priv = domain->host_data;
> + int hwirq, err, i;
> +
> + hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
> + if (hwirq < 0)
> + return hwirq;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
> + if (err)
> + goto err_hwirq;
> +
> + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
> + &middle_irq_chip, priv);
> + }
> +
> + return 0;
> +err_hwirq:
> + while (--i >= 0)
> + irq_domain_free_irqs_parent(domain, virq, i);
> +
> + pch_msi_free_hwirq(priv, hwirq, nr_irqs);
> + return err;
> +}
> +
> +static void pch_msi_middle_domain_free(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
> +
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> + pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
> +}
> +
> +static const struct irq_domain_ops pch_msi_middle_domain_ops = {
> + .alloc = pch_msi_middle_domain_alloc,
> + .free = pch_msi_middle_domain_free,
> +};
> +
> +static int pch_msi_init_domains(struct pch_msi_data *priv,
> + struct device_node *node,
> + struct device_node *parent)
> +{
> + struct irq_domain *middle_domain, *msi_domain, *parent_domain;
> +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + pr_err("Failed to find the parent domain\n");
> + return -ENXIO;
> + }
> +
> + middle_domain = irq_domain_add_tree(NULL,
> + &pch_msi_middle_domain_ops,
> + priv);

You don't really need a tree, unless your interrupt space is huge and
very sparse. Given that the DT example says 64, I would go with a
linear domain if that number is realistic.

> + if (!middle_domain) {
> + pr_err("Failed to create the MSI middle domain\n");
> + return -ENOMEM;
> + }
> +
> + middle_domain->parent = parent_domain;
> +
> + msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
> + &pch_msi_domain_info,
> + middle_domain);
> + if (!msi_domain) {
> + pr_err("Failed to create MSI domain\n");
> + irq_domain_remove(middle_domain);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static int pch_msi_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_msi_data *priv;
> + struct resource res;
> + int ret;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + spin_lock_init(&priv->msi_map_lock);
> +
> + ret = of_address_to_resource(node, 0, &res);
> + if (ret) {
> + pr_err("Failed to allocate resource\n");
> + goto err_priv;
> + }
> +
> + priv->addr = res.start;
> +
> + if (of_property_read_u32(node, "loongson,msi-base-vec",
> + &priv->irq_first)) {
> + pr_err("Unable to parse MSI vec base\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + if (of_property_read_u32(node, "loongson,msi-num-vecs",
> + &priv->num_irqs)) {
> + pr_err("Unable to parse MSI vec number\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_irqs),
> + sizeof(*priv->msi_map),
> + GFP_KERNEL);
> + if (!priv->msi_map) {
> + ret = -ENOMEM;
> + goto err_priv;
> + }
> +
> + pr_debug("Registering %d MSIs, starting at %d\n",
> + priv->num_irqs, priv->irq_first);
> +
> + ret = pch_msi_init_domains(priv, node, parent);
> + if (ret)
> + goto err_map;
> +
> + return 0;
> +
> +err_map:
> + kfree(priv->msi_map);
> +err_priv:
> + kfree(priv);
> + return ret;
> +}
> +
> +IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);

Thanks,

M.
--
Jazz is not dead. It just smells funny...

2020-04-24 01:22:20

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Hi, Marc,

On Thu, Apr 23, 2020 at 8:46 PM Marc Zyngier <[email protected]> wrote:
>
> Huacai,
>
> On 2020-04-23 06:55, Huacai Chen wrote:
> > Hi, Jiaxun,
> >
> > On Wed, Apr 22, 2020 at 10:28 PM Jiaxun Yang <[email protected]>
> > wrote:
> >>
> >> Add binding for Loongson PCH MSI controller.
> >>
> >> Signed-off-by: Jiaxun Yang <[email protected]>
> >> ---
> >> .../loongson,pch-msi.yaml | 56
> >> +++++++++++++++++++
> >> 1 file changed, 56 insertions(+)
> >> create mode 100644
> >> Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> >>
> >> diff --git
> >> a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> >> b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> >> new file mode 100644
> >> index 000000000000..dfb9cecacba0
> >> --- /dev/null
> >> +++
> >> b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
> >> @@ -0,0 +1,56 @@
> >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >> +%YAML 1.2
> >> +---
> >> +$id:
> >> "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
> >> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> >> +
> >> +title: Loongson PCH MSI Controller
> >> +
> >> +maintainers:
> >> + - Jiaxun Yang <[email protected]>
> >> +
> >> +description: |
> >> + This interrupt controller is found in the Loongson-7A family of PCH
> >> for
> > Please use "Loongson's LS7A family" here.
>
> It's the fourth email you send on the same subject. I think the author
> has got the message already. Frankly, it is only a name, and if they
> want to call it Bob, so be it.
I'm sorry that make so much noise, and I will not do this again. Yes,
a name is just a name, but we can do something to avoid confusing as
much as possible.

>
> Thanks,
>
> M.
> --
> Jazz is not dead. It just smells funny...

Thanks,
Huacai

2020-04-24 01:36:07

by Jiaxun Yang

[permalink] [raw]
Subject: Re: [PATCH 5/6] irqchip: Add Loongson PCH MSI controller

On Thu, 23 Apr 2020 15:41:35 +0100
Marc Zyngier <[email protected]> wrote:

> On Wed, 22 Apr 2020 22:24:25 +0800
> Jiaxun Yang <[email protected]> wrote:
>
> > This controller appears on Loongson-7A family of PCH to transform
> > interrupts from PCI MSI into HyperTransport vectorized interrrupts
> > and send them to procrssor's HT vector controller.
> >
> > Signed-off-by: Jiaxun Yang <[email protected]>
> > ---
[...]
> > + ret = irq_domain_alloc_irqs_parent(domain, virq, 1,
> > &fwspec);
> > + if (ret)
> > + return ret;
> > +
> > + irq_domain_set_info(domain, virq, hwirq,
> > + &middle_irq_chip, NULL,
> > + handle_simple_irq, NULL, NULL);
>
> No, this should at least be handle_edge_irq. More importantly, you
> should use the flow set by the underlying irqchip, and not provide
> your own.

Hi Marc,
Thanks for your review.

The underlying irqchip (HTVEC) follows a simple_irq flow as it only
has mask/unmask callback, and it doesn't have tyoe configuration. so I
followed simple_irq flow.

How can I use the flow set by the underlying irqchip? Just use
irq_domain_set_hwirq_and_chip here and set_handler in HTVEC driver?


>
> > + irq_set_probe(virq);
>
> Probe? what does it mean for MSIs? I also don't see how you tell the
> underlying irqchip that the MSI is edge triggered.
>
> > +
> > + return 0;
> > +}
> > +
> > +static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
> > + unsigned int virq,
> > + unsigned int nr_irqs,
> > void *args) +{
> > + struct pch_msi_data *priv = domain->host_data;
> > + int hwirq, err, i;
> > +
> > + hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
> > + if (hwirq < 0)
> > + return hwirq;
> > +
> > + for (i = 0; i < nr_irqs; i++) {
> > + err = pch_msi_parent_domain_alloc(domain, virq +
> > i, hwirq + i);
> > + if (err)
> > + goto err_hwirq;
> > +
> > + irq_domain_set_hwirq_and_chip(domain, virq + i,
> > hwirq + i,
> > + &middle_irq_chip,
> > priv);
> > + }
> > +
> > + return 0;
> > +err_hwirq:
> > + while (--i >= 0)
> > + irq_domain_free_irqs_parent(domain, virq, i);
> > +
> > + pch_msi_free_hwirq(priv, hwirq, nr_irqs);
> > + return err;
> > +}
> > +
> > +static void pch_msi_middle_domain_free(struct irq_domain *domain,
> > + unsigned int virq,
> > + unsigned int nr_irqs)
> > +{
> > + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
> > + struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
> > +
> > + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> > + pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
> > +}
> > +
> > +static const struct irq_domain_ops pch_msi_middle_domain_ops = {
> > + .alloc = pch_msi_middle_domain_alloc,
> > + .free = pch_msi_middle_domain_free,
> > +};
> > +
> > +static int pch_msi_init_domains(struct pch_msi_data *priv,
> > + struct device_node *node,
> > + struct device_node *parent)
> > +{
> > + struct irq_domain *middle_domain, *msi_domain,
> > *parent_domain; +
> > + parent_domain = irq_find_host(parent);
> > + if (!parent_domain) {
> > + pr_err("Failed to find the parent domain\n");
> > + return -ENXIO;
> > + }
> > +
> > + middle_domain = irq_domain_add_tree(NULL,
> > +
> > &pch_msi_middle_domain_ops,
> > + priv);
>
> You don't really need a tree, unless your interrupt space is huge and
> very sparse. Given that the DT example says 64, I would go with a
> linear domain if that number is realistic.
>
It can up to 192 in latest generation of chip, is it still suitable?

In the latest generation, we have a enhanced version of HTVEC which has
another delivery system that will be able to configure affinity. That's
why I placed set_affinity call back here and in PCH PIC driver.

Thanks.

[...]
> > +}
> > +
> > +IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
>
> Thanks,
>
> M.

--
Jiaxun Yang

2020-04-24 08:30:28

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH 5/6] irqchip: Add Loongson PCH MSI controller

On 2020-04-24 02:33, Jiaxun Yang wrote:
> On Thu, 23 Apr 2020 15:41:35 +0100
> Marc Zyngier <[email protected]> wrote:
>
>> On Wed, 22 Apr 2020 22:24:25 +0800
>> Jiaxun Yang <[email protected]> wrote:
>>
>> > This controller appears on Loongson-7A family of PCH to transform
>> > interrupts from PCI MSI into HyperTransport vectorized interrrupts
>> > and send them to procrssor's HT vector controller.
>> >
>> > Signed-off-by: Jiaxun Yang <[email protected]>
>> > ---
> [...]
>> > + ret = irq_domain_alloc_irqs_parent(domain, virq, 1,
>> > &fwspec);
>> > + if (ret)
>> > + return ret;
>> > +
>> > + irq_domain_set_info(domain, virq, hwirq,
>> > + &middle_irq_chip, NULL,
>> > + handle_simple_irq, NULL, NULL);
>>
>> No, this should at least be handle_edge_irq. More importantly, you
>> should use the flow set by the underlying irqchip, and not provide
>> your own.
>
> Hi Marc,
> Thanks for your review.
>
> The underlying irqchip (HTVEC) follows a simple_irq flow as it only
> has mask/unmask callback, and it doesn't have tyoe configuration. so I
> followed simple_irq flow.

Not having a type doesn't mean that it can stick to simple_irq, which is
the wrong things to use in 99% of the HW cases.

> How can I use the flow set by the underlying irqchip? Just use
> irq_domain_set_hwirq_and_chip here and set_handler in HTVEC driver?

You need to find out how the HTVEC behaves. My gut feeling is that it
is itself edge triggered, but you would need to look in the
documentation
to find out.

>
>
>>
>> > + irq_set_probe(virq);
>>
>> Probe? what does it mean for MSIs? I also don't see how you tell the
>> underlying irqchip that the MSI is edge triggered.
>>
>> > +
>> > + return 0;
>> > +}
>> > +
>> > +static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
>> > + unsigned int virq,
>> > + unsigned int nr_irqs,
>> > void *args) +{
>> > + struct pch_msi_data *priv = domain->host_data;
>> > + int hwirq, err, i;
>> > +
>> > + hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
>> > + if (hwirq < 0)
>> > + return hwirq;
>> > +
>> > + for (i = 0; i < nr_irqs; i++) {
>> > + err = pch_msi_parent_domain_alloc(domain, virq +
>> > i, hwirq + i);
>> > + if (err)
>> > + goto err_hwirq;
>> > +
>> > + irq_domain_set_hwirq_and_chip(domain, virq + i,
>> > hwirq + i,
>> > + &middle_irq_chip,
>> > priv);
>> > + }
>> > +
>> > + return 0;
>> > +err_hwirq:
>> > + while (--i >= 0)
>> > + irq_domain_free_irqs_parent(domain, virq, i);
>> > +
>> > + pch_msi_free_hwirq(priv, hwirq, nr_irqs);
>> > + return err;
>> > +}
>> > +
>> > +static void pch_msi_middle_domain_free(struct irq_domain *domain,
>> > + unsigned int virq,
>> > + unsigned int nr_irqs)
>> > +{
>> > + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
>> > + struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
>> > +
>> > + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
>> > + pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
>> > +}
>> > +
>> > +static const struct irq_domain_ops pch_msi_middle_domain_ops = {
>> > + .alloc = pch_msi_middle_domain_alloc,
>> > + .free = pch_msi_middle_domain_free,
>> > +};
>> > +
>> > +static int pch_msi_init_domains(struct pch_msi_data *priv,
>> > + struct device_node *node,
>> > + struct device_node *parent)
>> > +{
>> > + struct irq_domain *middle_domain, *msi_domain,
>> > *parent_domain; +
>> > + parent_domain = irq_find_host(parent);
>> > + if (!parent_domain) {
>> > + pr_err("Failed to find the parent domain\n");
>> > + return -ENXIO;
>> > + }
>> > +
>> > + middle_domain = irq_domain_add_tree(NULL,
>> > +
>> > &pch_msi_middle_domain_ops,
>> > + priv);
>>
>> You don't really need a tree, unless your interrupt space is huge and
>> very sparse. Given that the DT example says 64, I would go with a
>> linear domain if that number is realistic.
>>
> It can up to 192 in latest generation of chip, is it still suitable?

That's a pretty small number, really. Just stick to a linear domain.

> In the latest generation, we have a enhanced version of HTVEC which has
> another delivery system that will be able to configure affinity. That's
> why I placed set_affinity call back here and in PCH PIC driver.

Once you add support for this new version, this will make sense. at the
moment, this is pretty pointless.

Thanks,

M.
--
Jazz is not dead. It just smells funny...

2020-04-28 06:35:33

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 1/6] irqchip: Add Loongson HyperTransport Vector support

This controller appears on Loongson-3 chips for receiving interrupt
vectors from PCH's PIC and PCH's PCIe MSI interrupts.

Signed-off-by: Jiaxun Yang <[email protected]>
---
v2:
- Style cleanup
- Set ack callback and set correct edge_irq handler
---
drivers/irqchip/Kconfig | 8 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-htvec.c | 214 +++++++++++++++++++++++++++
3 files changed, 223 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-htvec.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index a85aada04a64..de4564e2ea88 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -532,4 +532,12 @@ config LOONGSON_HTPIC
help
Support for the Loongson-3 HyperTransport PIC Controller.

+config LOONGSON_HTVEC
+ bool "Loongson3 HyperTransport Interrupt Vector Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Support for the Loongson3 HyperTransport Interrupt Vector Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 37bbe39bf909..74561879f5a7 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -107,3 +107,4 @@ obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
+obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
new file mode 100644
index 000000000000..3b6032e3bb13
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-htvec.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson HyperTransport Interrupt Vector support
+ */
+
+#define pr_fmt(fmt) "htvec: " 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>
+
+/* Registers */
+#define HTVEC_EN_OFF 0x20
+#define HTVEC_MAX_PARENT_IRQ 4
+
+#define VEC_COUNT_PER_REG 32
+#define VEC_REG_COUNT 4
+#define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT)
+#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
+#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
+
+struct htvec {
+ void __iomem *base;
+ struct irq_domain *htvec_domain;
+ raw_spinlock_t htvec_lock;
+};
+
+static void htvec_irq_dispatch(struct irq_desc *desc)
+{
+ struct htvec *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 pending;
+ bool handled = false;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < VEC_REG_COUNT; i++) {
+ pending = readl(priv->base + 4 * i);
+ writel(pending, priv->base + 4 * i);
+ while (pending) {
+ int bit = __ffs(pending);
+
+ generic_handle_irq(irq_linear_revmap(priv->htvec_domain,
+ bit + VEC_COUNT_PER_REG * i));
+ pending &= ~BIT(bit);
+ handled = true;
+ }
+ }
+
+ if (!handled)
+ spurious_interrupt();
+
+ chained_irq_exit(chip, desc);
+}
+
+static void htvec_ack_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ void __iomem *addr = priv->base;
+
+ writel(VEC_REG_BIT(d->hwirq), priv->base + VEC_REG_IDX(d->hwirq) * 4);
+}
+
+static void htvec_mask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ void __iomem *addr = priv->base + HTVEC_EN_OFF;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ addr += VEC_REG_IDX(d->hwirq) * 4;
+ reg = readl(addr);
+ reg &= ~BIT(VEC_REG_BIT(d->hwirq));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static void htvec_unmask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ void __iomem *addr = priv->base + HTVEC_EN_OFF;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ addr += VEC_REG_IDX(d->hwirq) * 4;
+ reg = readl(addr);
+ reg |= BIT(VEC_REG_BIT(d->hwirq));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static struct irq_chip htvec_irq_chip = {
+ .name = "LOONGSON_HTVEC",
+ .irq_mask = htvec_mask_irq,
+ .irq_unmask = htvec_unmask_irq,
+ .irq_ack = htvec_ack_irq,
+};
+
+static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct htvec *priv = domain->host_data;
+ unsigned long hwirq;
+ unsigned int type, i;
+
+ irq_domain_translate_onecell(domain, arg, &hwirq, &type);
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
+ priv, handle_edge_irq, NULL, NULL);
+
+ return 0;
+}
+
+static void htvec_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 htvec_domain_ops = {
+ .translate = irq_domain_translate_onecell,
+ .alloc = htvec_domain_alloc,
+ .free = htvec_domain_free,
+};
+
+static void htvec_reset(struct htvec *priv)
+{
+ u32 idx;
+
+ /* Clear IRQ cause registers, mask all interrupts */
+ for (idx = 0; idx < VEC_REG_COUNT; idx++) {
+ writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
+ writel_relaxed(0xFFFFFFFF, priv->base);
+ }
+}
+
+static int htvec_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct htvec *priv;
+ int err, parent_irq[4], num_parents = 0, i;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->htvec_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ /* Interrupt may come from any of the 4 interrupt line */
+ for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
+ parent_irq[i] = irq_of_parse_and_map(node, i);
+ if (parent_irq[i] <= 0)
+ break;
+
+ num_parents++;
+ }
+
+ if (!num_parents) {
+ pr_err("Failed to get parent irqs\n");
+ err = -ENODEV;
+ goto iounmap_base;
+ }
+
+ priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node),
+ VEC_COUNT,
+ &htvec_domain_ops,
+ priv);
+ if (!priv->htvec_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ htvec_reset(priv);
+
+ for (i = 0; i < num_parents; i++)
+ irq_set_chained_handler_and_data(parent_irq[i],
+ htvec_irq_dispatch, priv);
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);
--
2.26.0.rc2

2020-04-28 06:36:00

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 4/6] dt-bindings: interrupt-controller: Add Loongson PCH PIC

Add binding for Loongson PCH PIC Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
--
v2:
- Fix naming
- Mark loongson,pic-base-vec as required
---
.../loongson,pch-pic.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
new file mode 100644
index 000000000000..92a8cd0ce23b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-pic.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH PIC Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson LS7A family of PCH for
+ transforming interrupts from on-chip devices into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-pic-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,pic-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH PIC.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - loongson,pic-base-vec
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ pic: interrupt-controller@10000000 {
+ compatible = "loongson,pch-pic-1.0";
+ reg = <0x10000000 0x400>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ loongson,pic-base-vec = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-04-28 06:36:25

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 3/6] irqchip: Add Loongson PCH PIC controller

This controller appears on Loongson LS7A family of PCH to transform
interrupts from devices into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
v2:
- Style clean-ups
- Use IRQ_FASTEOI_HIERARCHY_HANDLERS
- Move lock into bitclr & bitset
- Make loongson,pic-base-vec as required property
---
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-pic.c | 245 +++++++++++++++++++++++++
3 files changed, 255 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index de4564e2ea88..5524a621638c 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -540,4 +540,13 @@ config LOONGSON_HTVEC
help
Support for the Loongson3 HyperTransport Interrupt Vector Controller.

+config LOONGSON_PCH_PIC
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
+ help
+ Support for the Loongson PCH PIC Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 74561879f5a7..acc72331cec8 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -108,3 +108,4 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c
new file mode 100644
index 000000000000..9b4605873b2a
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-pic.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH PIC support
+ */
+
+#define pr_fmt(fmt) "pch-pic: " fmt
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.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>
+
+/* Registers */
+#define PCH_PIC_MASK 0x20
+#define PCH_PIC_HTMSI_EN 0x40
+#define PCH_PIC_EDGE 0x60
+#define PCH_PIC_CLR 0x80
+#define PCH_PIC_AUTO0 0xc0
+#define PCH_PIC_AUTO1 0xe0
+#define PCH_INT_ROUTE(irq) (0x100 + irq)
+#define PCH_INT_HTVEC(irq) (0x200 + irq)
+#define PCH_PIC_POL 0x3e0
+
+#define PIC_COUNT_PER_REG 32
+#define PIC_REG_COUNT 2
+#define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
+#define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
+#define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
+
+struct pch_pic {
+ void __iomem *base;
+ struct irq_domain *pic_domain;
+ u32 ht_vec_base;
+ raw_spinlock_t pic_lock;
+};
+
+static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit)
+{
+ void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ reg = readl(addr);
+ reg |= BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+}
+
+static void pch_pic_bitclr(struct pch_pic *priv, int offset, int bit)
+{
+ void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ reg = readl(addr);
+ reg &= ~BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+}
+
+static void pch_pic_eoi_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ u32 idx = PIC_REG_IDX(d->hwirq);
+
+ writel(BIT(PIC_REG_BIT(d->hwirq)),
+ priv->base + PCH_PIC_CLR + idx * 4);
+}
+
+static void pch_pic_mask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+
+ pch_pic_bitset(priv, PCH_PIC_MASK, d->hwirq);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_pic_unmask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+
+ pch_pic_bitclr(priv, PCH_PIC_MASK, d->hwirq);
+ irq_chip_unmask_parent(d);
+}
+
+static int pch_pic_set_type(struct irq_data *d, unsigned int type)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ int ret = 0;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct irq_chip pch_pic_irq_chip = {
+ .name = "PCH PIC",
+ .irq_mask = pch_pic_mask_irq,
+ .irq_unmask = pch_pic_unmask_irq,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_eoi = pch_pic_eoi_irq,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = pch_pic_set_type,
+};
+
+static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct pch_pic *priv = domain->host_data;
+ struct irq_fwspec fwspec;
+ unsigned long hwirq;
+ unsigned int type;
+ int err;
+
+ irq_domain_translate_twocell(domain, arg, &hwirq, &type);
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq + priv->ht_vec_base;
+
+ err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (err)
+ return err;
+
+ irq_domain_set_info(domain, virq, hwirq,
+ &pch_pic_irq_chip, priv,
+ handle_fasteoi_ack_irq, NULL, NULL);
+ irq_set_probe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops pch_pic_domain_ops = {
+ .translate = irq_domain_translate_twocell,
+ .alloc = pch_pic_alloc,
+ .free = irq_domain_free_irqs_parent,
+};
+
+static void pch_pic_reset(struct pch_pic *priv)
+{
+ int i;
+
+ for (i = 0; i < PIC_COUNT; i++) {
+ /* Write vectore ID */
+ writeb(priv->ht_vec_base + i, priv->base + PCH_INT_HTVEC(i));
+ /* Hardcode route to HT0 Lo */
+ writeb(1, priv->base + PCH_INT_ROUTE(i));
+ }
+
+ for (i = 0; i < PIC_REG_COUNT; i++) {
+ /* Clear IRQ cause registers, mask all interrupts */
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * i);
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * i);
+ /* Clear auto bounce, we don't need that */
+ writel_relaxed(0, priv->base + PCH_PIC_AUTO0 + 4 * i);
+ writel_relaxed(0, priv->base + PCH_PIC_AUTO1 + 4 * i);
+ /* Enable HTMSI transformer */
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_HTMSI_EN + 4 * i);
+ }
+}
+
+static int pch_pic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_pic *priv;
+ struct irq_domain *parent_domain;
+ int err;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->pic_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ err = -ENXIO;
+ goto iounmap_base;
+ }
+
+ if (of_property_read_u32(node, "loongson,pic-base-vec",
+ &priv->ht_vec_base)) {
+ pr_err("Failed to determine pic-base-vec\n");
+ err = -EINVAL;
+ goto iounmap_base;
+ }
+
+ priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ PIC_COUNT,
+ of_node_to_fwnode(node),
+ &pch_pic_domain_ops,
+ priv);
+ if (!priv->pic_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ pch_pic_reset(priv);
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
--
2.26.0.rc2

2020-04-28 06:37:37

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 2/6] dt-bindings: interrupt-controller: Add Loongson HTVEC

Add binding for Loongson-3 HyperTransport Interrupt Vector Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../interrupt-controller/loongson,htvec.yaml | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
new file mode 100644
index 000000000000..547a80c89eba
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,htvec.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson-3 HyperTransport Interrupt Vector Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson-3 family of chips for
+ receiving vectorized interrupts from PCH's interrupt controller.
+
+properties:
+ compatible:
+ const: loongson,htvec-1.0
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 1
+ maxItems: 4
+ description: |
+ Four parent interrupts that receive chained interrupts.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ htvec: interrupt-controller@1fb000080 {
+ compatible = "loongson,htvec-1.0";
+ reg = <0xfb000080 0x40>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&liointc>;
+ interrupts = <24 IRQ_TYPE_LEVEL_HIGH>,
+ <25 IRQ_TYPE_LEVEL_HIGH>,
+ <26 IRQ_TYPE_LEVEL_HIGH>,
+ <27 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
--
2.26.0.rc2

2020-04-28 06:38:13

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 5/6] irqchip: Add Loongson PCH MSI controller

This controller appears on Loongson LS7A family of PCH to transform
interrupts from PCI MSI into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
--
v2:
- Style clean-ups
- Add ack callback
- Use bitmap_find_free_region
---
drivers/irqchip/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-msi.c | 259 +++++++++++++++++++++++++
3 files changed, 270 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 5524a621638c..0b6b826dd843 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -549,4 +549,14 @@ config LOONGSON_PCH_PIC
help
Support for the Loongson PCH PIC Controller.

+config LOONGSON_PCH_MSI
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ depends on PCI
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select PCI_MSI
+ help
+ Support for the Loongson PCH MSI Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index acc72331cec8..3a4ce283189a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
+obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
new file mode 100644
index 000000000000..5b4d607a899e
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-msi.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH MSI support
+ */
+
+#define pr_fmt(fmt) "pch-msi: " fmt
+
+#include <linux/irqchip.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+struct pch_msi_data {
+ spinlock_t msi_map_lock;
+ phys_addr_t doorbell;
+ u32 irq_first; /* The vector number that MSIs starts */
+ u32 num_irqs; /* The number of vectors for MSIs */
+ unsigned long *msi_map;
+};
+
+static void pch_msi_mask_msi_irq(struct irq_data *d)
+{
+ pci_msi_mask_irq(d);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_msi_unmask_msi_irq(struct irq_data *d)
+{
+ pci_msi_unmask_irq(d);
+ irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip pch_msi_irq_chip = {
+ .name = "PCH MSI",
+ .irq_mask = pch_msi_mask_msi_irq,
+ .irq_unmask = pch_msi_unmask_msi_irq,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+};
+
+static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
+{
+ int first;
+
+ spin_lock(&priv->msi_map_lock);
+
+ first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
+ get_count_order(num_req));
+ if (first < 0) {
+ spin_unlock(&priv->msi_map_lock);
+ return -ENOSPC;
+ }
+
+ bitmap_set(priv->msi_map, first, num_req);
+ spin_unlock(&priv->msi_map_lock);
+
+ return priv->irq_first + first;
+}
+
+static void pch_msi_free_hwirq(struct pch_msi_data *priv,
+ int hwirq, int num_req)
+{
+ int first = hwirq - priv->irq_first;
+
+ spin_lock(&priv->msi_map_lock);
+ bitmap_clear(priv->msi_map, first, num_req);
+ spin_unlock(&priv->msi_map_lock);
+}
+
+static void pch_msi_compose_msi_msg(struct irq_data *data,
+ struct msi_msg *msg)
+{
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
+
+ msg->address_hi = upper_32_bits(priv->doorbell);
+ msg->address_lo = lower_32_bits(priv->doorbell);
+ msg->data = data->hwirq;
+}
+
+static struct msi_domain_info pch_msi_domain_info = {
+ .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
+ .chip = &pch_msi_irq_chip,
+};
+
+static struct irq_chip middle_irq_chip = {
+ .name = "PCH MSI Middle",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_compose_msi_msg = pch_msi_compose_msi_msg,
+};
+
+static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, int hwirq)
+{
+ struct irq_fwspec fwspec;
+ int ret;
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (ret)
+ return ret;
+
+ irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &middle_irq_chip, NULL);
+
+ return 0;
+}
+
+static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *args)
+{
+ struct pch_msi_data *priv = domain->host_data;
+ int hwirq, err, i;
+
+ hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
+ if (hwirq < 0)
+ return hwirq;
+
+ for (i = 0; i < nr_irqs; i++) {
+ err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
+ if (err)
+ goto err_hwirq;
+
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &middle_irq_chip, priv);
+ }
+
+ return 0;
+
+err_hwirq:
+ while (--i >= 0)
+ irq_domain_free_irqs_parent(domain, virq, i);
+
+ pch_msi_free_hwirq(priv, hwirq, nr_irqs);
+ return err;
+}
+
+static void pch_msi_middle_domain_free(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+ pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
+}
+
+static const struct irq_domain_ops pch_msi_middle_domain_ops = {
+ .alloc = pch_msi_middle_domain_alloc,
+ .free = pch_msi_middle_domain_free,
+};
+
+static int pch_msi_init_domains(struct pch_msi_data *priv,
+ struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_domain *middle_domain, *msi_domain, *parent_domain;
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ return -ENXIO;
+ }
+
+ middle_domain = irq_domain_add_linear(NULL, priv->num_irqs,
+ &pch_msi_middle_domain_ops,
+ priv);
+ if (!middle_domain) {
+ pr_err("Failed to create the MSI middle domain\n");
+ return -ENOMEM;
+ }
+
+ middle_domain->parent = parent_domain;
+
+ msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
+ &pch_msi_domain_info,
+ middle_domain);
+ if (!msi_domain) {
+ pr_err("Failed to create MSI domain\n");
+ irq_domain_remove(middle_domain);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int pch_msi_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_msi_data *priv;
+ struct resource res;
+ int ret;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->msi_map_lock);
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret) {
+ pr_err("Failed to allocate resource\n");
+ goto err_priv;
+ }
+
+ priv->doorbell = res.start;
+
+ if (of_property_read_u32(node, "loongson,msi-base-vec",
+ &priv->irq_first)) {
+ pr_err("Unable to parse MSI vec base\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ if (of_property_read_u32(node, "loongson,msi-num-vecs",
+ &priv->num_irqs)) {
+ pr_err("Unable to parse MSI vec number\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_irqs),
+ sizeof(*priv->msi_map),
+ GFP_KERNEL);
+ if (!priv->msi_map) {
+ ret = -ENOMEM;
+ goto err_priv;
+ }
+
+ pr_debug("Registering %d MSIs, starting at %d\n",
+ priv->num_irqs, priv->irq_first);
+
+ ret = pch_msi_init_domains(priv, node, parent);
+ if (ret)
+ goto err_map;
+
+ return 0;
+
+err_map:
+ kfree(priv->msi_map);
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
--
2.26.0.rc2

2020-04-28 06:38:29

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v2 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Add binding for Loongson PCH MSI controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../loongson,pch-msi.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
new file mode 100644
index 000000000000..513ed1933035
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH MSI Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+description: |
+ This interrupt controller is found in the Loongson LS7A family of PCH for
+ transforming interrupts from PCIe MSI into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-msi-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,msi-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH MSI.
+
+ loongson,msi-num-vecs:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the number of parent HyperTransport vectors allocated
+ to PCH MSI.
+
+ msi-controller: true
+
+required:
+ - compatible
+ - reg
+ - msi-controller
+ - loongson,msi-base-vec
+ - loongson,msi-num-vecs
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ msi: msi-controller@2ff00000 {
+ compatible = "loongson,pch-msi-1.0";
+ reg = <0x2ff00000 0x4>;
+ msi-controller;
+ loongson,msi-base-vec = <64>;
+ loongson,msi-num-vecs = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-04-28 13:10:02

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH v2 5/6] irqchip: Add Loongson PCH MSI controller

On Tue, 28 Apr 2020 14:32:44 +0800
Jiaxun Yang <[email protected]> wrote:

> This controller appears on Loongson LS7A family of PCH to transform
> interrupts from PCI MSI into HyperTransport vectorized interrrupts
> and send them to procrssor's HT vector controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> --
> v2:
> - Style clean-ups
> - Add ack callback
> - Use bitmap_find_free_region
> ---
> drivers/irqchip/Kconfig | 10 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-pch-msi.c | 259 +++++++++++++++++++++++++
> 3 files changed, 270 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 5524a621638c..0b6b826dd843 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -549,4 +549,14 @@ config LOONGSON_PCH_PIC
> help
> Support for the Loongson PCH PIC Controller.
>
> +config LOONGSON_PCH_MSI
> + bool "Loongson PCH PIC Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + depends on PCI
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + select PCI_MSI
> + help
> + Support for the Loongson PCH MSI Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index acc72331cec8..3a4ce283189a 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
> +obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
> diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
> new file mode 100644
> index 000000000000..5b4d607a899e
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-pch-msi.c
> @@ -0,0 +1,259 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson PCH MSI support
> + */
> +
> +#define pr_fmt(fmt) "pch-msi: " fmt
> +
> +#include <linux/irqchip.h>
> +#include <linux/msi.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_pci.h>
> +#include <linux/pci.h>
> +#include <linux/slab.h>
> +
> +struct pch_msi_data {
> + spinlock_t msi_map_lock;
> + phys_addr_t doorbell;
> + u32 irq_first; /* The vector number that MSIs starts */
> + u32 num_irqs; /* The number of vectors for MSIs */
> + unsigned long *msi_map;
> +};
> +
> +static void pch_msi_mask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_mask_irq(d);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_unmask_irq(d);
> + irq_chip_unmask_parent(d);
> +}
> +
> +static struct irq_chip pch_msi_irq_chip = {
> + .name = "PCH MSI",
> + .irq_mask = pch_msi_mask_msi_irq,
> + .irq_unmask = pch_msi_unmask_msi_irq,
> + .irq_ack = irq_chip_ack_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> +};
> +
> +static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
> +{
> + int first;
> +
> + spin_lock(&priv->msi_map_lock);

Why does it have to be a spinlock? As far as I can tell, we never
allocate MSIs from non-preemptible contexts.

> +
> + first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
> + get_count_order(num_req));
> + if (first < 0) {
> + spin_unlock(&priv->msi_map_lock);
> + return -ENOSPC;
> + }
> +
> + bitmap_set(priv->msi_map, first, num_req);

What is that for? bitmap_find_free_region has already done the work for
you.

> + spin_unlock(&priv->msi_map_lock);
> +
> + return priv->irq_first + first;
> +}
> +
> +static void pch_msi_free_hwirq(struct pch_msi_data *priv,
> + int hwirq, int num_req)
> +{
> + int first = hwirq - priv->irq_first;
> +
> + spin_lock(&priv->msi_map_lock);
> + bitmap_clear(priv->msi_map, first, num_req);

bitmap_clear doesn't reverse the effects of bitmap_find_free_region.

> + spin_unlock(&priv->msi_map_lock);
> +}
> +
> +static void pch_msi_compose_msi_msg(struct irq_data *data,
> + struct msi_msg *msg)
> +{
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
> +
> + msg->address_hi = upper_32_bits(priv->doorbell);
> + msg->address_lo = lower_32_bits(priv->doorbell);
> + msg->data = data->hwirq;
> +}
> +
> +static struct msi_domain_info pch_msi_domain_info = {
> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
> + .chip = &pch_msi_irq_chip,
> +};
> +
> +static struct irq_chip middle_irq_chip = {
> + .name = "PCH MSI Middle",

This "middle" thing doesn't mean anything. What it really implements is
a generic MSI irqchip. What you call PCH MSI seems to be PCI-MSI. Pretty
confusing.

> + .irq_mask = irq_chip_mask_parent,
> + .irq_unmask = irq_chip_unmask_parent,
> + .irq_ack = irq_chip_ack_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .irq_compose_msi_msg = pch_msi_compose_msi_msg,
> +};
> +
> +static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, int hwirq)
> +{
> + struct irq_fwspec fwspec;
> + int ret;
> +
> + fwspec.fwnode = domain->parent->fwnode;
> + fwspec.param_count = 1;
> + fwspec.param[0] = hwirq;
> +
> + ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
> + if (ret)
> + return ret;
> +
> + irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
> + &middle_irq_chip, NULL);
> +
> + return 0;
> +}
> +
> +static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs, void *args)
> +{
> + struct pch_msi_data *priv = domain->host_data;
> + int hwirq, err, i;
> +
> + hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
> + if (hwirq < 0)
> + return hwirq;
> +
> + for (i = 0; i < nr_irqs; i++) {
> + err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
> + if (err)
> + goto err_hwirq;
> +
> + irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
> + &middle_irq_chip, priv);

So you're doing that twice per MSI. I think once is enough.

> + }
> +
> + return 0;
> +
> +err_hwirq:
> + while (--i >= 0)
> + irq_domain_free_irqs_parent(domain, virq, i);

This looks very wrong. Why isn't it just:

irq_domain_free_irqs_parent(domain, virq, i - 1);

?

> +
> + pch_msi_free_hwirq(priv, hwirq, nr_irqs);

And when you look at the whole error handling (once fixed), it is
exactly pch_msi_middle_domain_free(). So why not calling this directly?

> + return err;
> +}
> +
> +static void pch_msi_middle_domain_free(struct irq_domain *domain,
> + unsigned int virq,
> + unsigned int nr_irqs)
> +{
> + struct irq_data *d = irq_domain_get_irq_data(domain, virq);
> + struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
> +
> + irq_domain_free_irqs_parent(domain, virq, nr_irqs);
> + pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
> +}
> +
> +static const struct irq_domain_ops pch_msi_middle_domain_ops = {
> + .alloc = pch_msi_middle_domain_alloc,
> + .free = pch_msi_middle_domain_free,
> +};
> +
> +static int pch_msi_init_domains(struct pch_msi_data *priv,
> + struct device_node *node,
> + struct device_node *parent)
> +{
> + struct irq_domain *middle_domain, *msi_domain,
> *parent_domain; +
> + parent_domain = irq_find_host(parent);
> + if (!parent_domain) {
> + pr_err("Failed to find the parent domain\n");
> + return -ENXIO;
> + }

Can't you check this early in the probe routine and bail out?

> +
> + middle_domain = irq_domain_add_linear(NULL, priv->num_irqs,
> + &pch_msi_middle_domain_ops,
> + priv);

NULL isn't an acceptable parameter. This irqdomain really belongs to
the node. See irq_domain_update_bus_token() to specialize the domain so
that its allocation doesn't clash with the PCI domain that you allocate
below (DOMAIN_BUS_NEXUS is used by quite a few drivers).

Also, please use irq_domain_create_linear instead, in order
to be consistent with the rest of the API usage in this file.

> + if (!middle_domain) {
> + pr_err("Failed to create the MSI middle domain\n");
> + return -ENOMEM;
> + }
> +
> + middle_domain->parent = parent_domain;
> +
> + msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
> + &pch_msi_domain_info,
> + middle_domain);
> + if (!msi_domain) {
> + pr_err("Failed to create MSI domain\n");
> + irq_domain_remove(middle_domain);
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static int pch_msi_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_msi_data *priv;
> + struct resource res;
> + int ret;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + spin_lock_init(&priv->msi_map_lock);
> +
> + ret = of_address_to_resource(node, 0, &res);
> + if (ret) {
> + pr_err("Failed to allocate resource\n");
> + goto err_priv;
> + }
> +
> + priv->doorbell = res.start;
> +
> + if (of_property_read_u32(node, "loongson,msi-base-vec",
> + &priv->irq_first)) {
> + pr_err("Unable to parse MSI vec base\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + if (of_property_read_u32(node, "loongson,msi-num-vecs",
> + &priv->num_irqs)) {
> + pr_err("Unable to parse MSI vec number\n");
> + ret = -EINVAL;
> + goto err_priv;
> + }
> +
> + priv->msi_map = kcalloc(BITS_TO_LONGS(priv->num_irqs),
> + sizeof(*priv->msi_map),
> + GFP_KERNEL);

We have bitmap_alloc() that should already do the right thing.

> + if (!priv->msi_map) {
> + ret = -ENOMEM;
> + goto err_priv;
> + }
> +
> + pr_debug("Registering %d MSIs, starting at %d\n",
> + priv->num_irqs, priv->irq_first);
> +
> + ret = pch_msi_init_domains(priv, node, parent);
> + if (ret)
> + goto err_map;
> +
> + return 0;
> +
> +err_map:
> + kfree(priv->msi_map);
> +err_priv:
> + kfree(priv);
> + return ret;
> +}
> +
> +IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);


Thanks,

M.
--
Jazz is not dead. It just smells funny...

2020-04-28 17:01:57

by Marc Zyngier

[permalink] [raw]
Subject: Re: [PATCH v2 1/6] irqchip: Add Loongson HyperTransport Vector support

On Tue, 28 Apr 2020 14:32:40 +0800
Jiaxun Yang <[email protected]> wrote:

> This controller appears on Loongson-3 chips for receiving interrupt
> vectors from PCH's PIC and PCH's PCIe MSI interrupts.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> v2:
> - Style cleanup
> - Set ack callback and set correct edge_irq handler
> ---
> drivers/irqchip/Kconfig | 8 +
> drivers/irqchip/Makefile | 1 +
> drivers/irqchip/irq-loongson-htvec.c | 214 +++++++++++++++++++++++++++
> 3 files changed, 223 insertions(+)
> create mode 100644 drivers/irqchip/irq-loongson-htvec.c
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index a85aada04a64..de4564e2ea88 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -532,4 +532,12 @@ config LOONGSON_HTPIC
> help
> Support for the Loongson-3 HyperTransport PIC Controller.
>
> +config LOONGSON_HTVEC
> + bool "Loongson3 HyperTransport Interrupt Vector Controller"
> + depends on MACH_LOONGSON64 || COMPILE_TEST
> + default MACH_LOONGSON64
> + select IRQ_DOMAIN_HIERARCHY
> + help
> + Support for the Loongson3 HyperTransport Interrupt Vector Controller.
> +
> endmenu
> diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
> index 37bbe39bf909..74561879f5a7 100644
> --- a/drivers/irqchip/Makefile
> +++ b/drivers/irqchip/Makefile
> @@ -107,3 +107,4 @@ obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
> obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
> obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
> obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
> +obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
> diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
> new file mode 100644
> index 000000000000..3b6032e3bb13
> --- /dev/null
> +++ b/drivers/irqchip/irq-loongson-htvec.c
> @@ -0,0 +1,214 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020, Jiaxun Yang <[email protected]>
> + * Loongson HyperTransport Interrupt Vector support
> + */
> +
> +#define pr_fmt(fmt) "htvec: " 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>
> +
> +/* Registers */
> +#define HTVEC_EN_OFF 0x20
> +#define HTVEC_MAX_PARENT_IRQ 4
> +
> +#define VEC_COUNT_PER_REG 32
> +#define VEC_REG_COUNT 4
> +#define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT)
> +#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
> +#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
> +
> +struct htvec {
> + void __iomem *base;
> + struct irq_domain *htvec_domain;
> + raw_spinlock_t htvec_lock;
> +};
> +
> +static void htvec_irq_dispatch(struct irq_desc *desc)
> +{
> + struct htvec *priv = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_desc_get_chip(desc);
> + u32 pending;
> + bool handled = false;
> + int i;
> +
> + chained_irq_enter(chip, desc);
> +
> + for (i = 0; i < VEC_REG_COUNT; i++) {
> + pending = readl(priv->base + 4 * i);
> + writel(pending, priv->base + 4 * i);
> + while (pending) {
> + int bit = __ffs(pending);
> +
> + generic_handle_irq(irq_linear_revmap(priv->htvec_domain,
> + bit + VEC_COUNT_PER_REG * i));
> + pending &= ~BIT(bit);
> + handled = true;
> + }
> + }
> +
> + if (!handled)
> + spurious_interrupt();
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static void htvec_ack_irq(struct irq_data *d)
> +{
> + struct htvec *priv = irq_data_get_irq_chip_data(d);
> + void __iomem *addr = priv->base;
> +
> + writel(VEC_REG_BIT(d->hwirq), priv->base + VEC_REG_IDX(d->hwirq) * 4);

Are you really sure about this? All the other operations seems to be
based on a hot-bit pattern, while these particular registers seems to
be taking hwirq mod 32. I'm willing to bet this is wrong.

M.
--
Jazz is not dead. It just smells funny...

2020-05-01 09:24:36

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 1/6] irqchip: Add Loongson HyperTransport Vector support

This controller appears on Loongson-3 chips for receiving interrupt
vectors from PCH's PIC and PCH's PCIe MSI interrupts.

Signed-off-by: Jiaxun Yang <[email protected]>
---
v2:
- Style cleanup
- Set ack callback and set correct edge_irq handler

v3:
- Correct bitops in ACK callback
---
drivers/irqchip/Kconfig | 8 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-htvec.c | 213 +++++++++++++++++++++++++++
3 files changed, 222 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-htvec.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index a85aada04a64..de4564e2ea88 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -532,4 +532,12 @@ config LOONGSON_HTPIC
help
Support for the Loongson-3 HyperTransport PIC Controller.

+config LOONGSON_HTVEC
+ bool "Loongson3 HyperTransport Interrupt Vector Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Support for the Loongson3 HyperTransport Interrupt Vector Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 37bbe39bf909..74561879f5a7 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -107,3 +107,4 @@ obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o
obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o
obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o
+obj-$(CONFIG_LOONGSON_HTVEC) += irq-loongson-htvec.o
diff --git a/drivers/irqchip/irq-loongson-htvec.c b/drivers/irqchip/irq-loongson-htvec.c
new file mode 100644
index 000000000000..b3ad9931d08b
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-htvec.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson HyperTransport Interrupt Vector support
+ */
+
+#define pr_fmt(fmt) "htvec: " 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>
+
+/* Registers */
+#define HTVEC_EN_OFF 0x20
+#define HTVEC_MAX_PARENT_IRQ 4
+
+#define VEC_COUNT_PER_REG 32
+#define VEC_REG_COUNT 4
+#define VEC_COUNT (VEC_COUNT_PER_REG * VEC_REG_COUNT)
+#define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
+#define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
+
+struct htvec {
+ void __iomem *base;
+ struct irq_domain *htvec_domain;
+ raw_spinlock_t htvec_lock;
+};
+
+static void htvec_irq_dispatch(struct irq_desc *desc)
+{
+ struct htvec *priv = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ u32 pending;
+ bool handled = false;
+ int i;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < VEC_REG_COUNT; i++) {
+ pending = readl(priv->base + 4 * i);
+ while (pending) {
+ int bit = __ffs(pending);
+
+ generic_handle_irq(irq_linear_revmap(priv->htvec_domain,
+ bit + VEC_COUNT_PER_REG * i));
+ pending &= ~BIT(bit);
+ handled = true;
+ }
+ }
+
+ if (!handled)
+ spurious_interrupt();
+
+ chained_irq_exit(chip, desc);
+}
+
+static void htvec_ack_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+
+ writel(BIT(VEC_REG_BIT(d->hwirq)),
+ priv->base + VEC_REG_IDX(d->hwirq) * 4);
+}
+
+static void htvec_mask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ void __iomem *addr = priv->base + HTVEC_EN_OFF;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ addr += VEC_REG_IDX(d->hwirq) * 4;
+ reg = readl(addr);
+ reg &= ~BIT(VEC_REG_BIT(d->hwirq));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static void htvec_unmask_irq(struct irq_data *d)
+{
+ struct htvec *priv = irq_data_get_irq_chip_data(d);
+ void __iomem *addr = priv->base + HTVEC_EN_OFF;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->htvec_lock, flags);
+ addr += VEC_REG_IDX(d->hwirq) * 4;
+ reg = readl(addr);
+ reg |= BIT(VEC_REG_BIT(d->hwirq));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
+}
+
+static struct irq_chip htvec_irq_chip = {
+ .name = "LOONGSON_HTVEC",
+ .irq_mask = htvec_mask_irq,
+ .irq_unmask = htvec_unmask_irq,
+ .irq_ack = htvec_ack_irq,
+};
+
+static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct htvec *priv = domain->host_data;
+ unsigned long hwirq;
+ unsigned int type, i;
+
+ irq_domain_translate_onecell(domain, arg, &hwirq, &type);
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
+ priv, handle_edge_irq, NULL, NULL);
+
+ return 0;
+}
+
+static void htvec_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 htvec_domain_ops = {
+ .translate = irq_domain_translate_onecell,
+ .alloc = htvec_domain_alloc,
+ .free = htvec_domain_free,
+};
+
+static void htvec_reset(struct htvec *priv)
+{
+ u32 idx;
+
+ /* Clear IRQ cause registers, mask all interrupts */
+ for (idx = 0; idx < VEC_REG_COUNT; idx++) {
+ writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
+ writel_relaxed(0xFFFFFFFF, priv->base);
+ }
+}
+
+static int htvec_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct htvec *priv;
+ int err, parent_irq[4], num_parents = 0, i;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->htvec_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ /* Interrupt may come from any of the 4 interrupt line */
+ for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
+ parent_irq[i] = irq_of_parse_and_map(node, i);
+ if (parent_irq[i] <= 0)
+ break;
+
+ num_parents++;
+ }
+
+ if (!num_parents) {
+ pr_err("Failed to get parent irqs\n");
+ err = -ENODEV;
+ goto iounmap_base;
+ }
+
+ priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node),
+ VEC_COUNT,
+ &htvec_domain_ops,
+ priv);
+ if (!priv->htvec_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ htvec_reset(priv);
+
+ for (i = 0; i < num_parents; i++)
+ irq_set_chained_handler_and_data(parent_irq[i],
+ htvec_irq_dispatch, priv);
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);
--
2.26.0.rc2

2020-05-01 09:25:01

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 3/6] irqchip: Add Loongson PCH PIC controller

This controller appears on Loongson LS7A family of PCH to transform
interrupts from devices into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
v2:
- Style clean-ups
- Use IRQ_FASTEOI_HIERARCHY_HANDLERS
- Move lock into bitclr & bitset
- Make loongson,pic-base-vec as required property
---
drivers/irqchip/Kconfig | 9 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-pic.c | 245 +++++++++++++++++++++++++
3 files changed, 255 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-pic.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index de4564e2ea88..5524a621638c 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -540,4 +540,13 @@ config LOONGSON_HTVEC
help
Support for the Loongson3 HyperTransport Interrupt Vector Controller.

+config LOONGSON_PCH_PIC
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select IRQ_FASTEOI_HIERARCHY_HANDLERS
+ help
+ Support for the Loongson PCH PIC Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 74561879f5a7..acc72331cec8 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -108,3 +108,4 @@ obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o
obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c
new file mode 100644
index 000000000000..9b4605873b2a
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-pic.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH PIC support
+ */
+
+#define pr_fmt(fmt) "pch-pic: " fmt
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.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>
+
+/* Registers */
+#define PCH_PIC_MASK 0x20
+#define PCH_PIC_HTMSI_EN 0x40
+#define PCH_PIC_EDGE 0x60
+#define PCH_PIC_CLR 0x80
+#define PCH_PIC_AUTO0 0xc0
+#define PCH_PIC_AUTO1 0xe0
+#define PCH_INT_ROUTE(irq) (0x100 + irq)
+#define PCH_INT_HTVEC(irq) (0x200 + irq)
+#define PCH_PIC_POL 0x3e0
+
+#define PIC_COUNT_PER_REG 32
+#define PIC_REG_COUNT 2
+#define PIC_COUNT (PIC_COUNT_PER_REG * PIC_REG_COUNT)
+#define PIC_REG_IDX(irq_id) ((irq_id) / PIC_COUNT_PER_REG)
+#define PIC_REG_BIT(irq_id) ((irq_id) % PIC_COUNT_PER_REG)
+
+struct pch_pic {
+ void __iomem *base;
+ struct irq_domain *pic_domain;
+ u32 ht_vec_base;
+ raw_spinlock_t pic_lock;
+};
+
+static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit)
+{
+ void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ reg = readl(addr);
+ reg |= BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+}
+
+static void pch_pic_bitclr(struct pch_pic *priv, int offset, int bit)
+{
+ void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
+ unsigned long flags;
+ u32 reg;
+
+ raw_spin_lock_irqsave(&priv->pic_lock, flags);
+ reg = readl(addr);
+ reg &= ~BIT(PIC_REG_BIT(bit));
+ writel(reg, addr);
+ raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
+}
+
+static void pch_pic_eoi_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ u32 idx = PIC_REG_IDX(d->hwirq);
+
+ writel(BIT(PIC_REG_BIT(d->hwirq)),
+ priv->base + PCH_PIC_CLR + idx * 4);
+}
+
+static void pch_pic_mask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+
+ pch_pic_bitset(priv, PCH_PIC_MASK, d->hwirq);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_pic_unmask_irq(struct irq_data *d)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+
+ pch_pic_bitclr(priv, PCH_PIC_MASK, d->hwirq);
+ irq_chip_unmask_parent(d);
+}
+
+static int pch_pic_set_type(struct irq_data *d, unsigned int type)
+{
+ struct pch_pic *priv = irq_data_get_irq_chip_data(d);
+ int ret = 0;
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
+ pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static struct irq_chip pch_pic_irq_chip = {
+ .name = "PCH PIC",
+ .irq_mask = pch_pic_mask_irq,
+ .irq_unmask = pch_pic_unmask_irq,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_eoi = pch_pic_eoi_irq,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_set_type = pch_pic_set_type,
+};
+
+static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ struct pch_pic *priv = domain->host_data;
+ struct irq_fwspec fwspec;
+ unsigned long hwirq;
+ unsigned int type;
+ int err;
+
+ irq_domain_translate_twocell(domain, arg, &hwirq, &type);
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq + priv->ht_vec_base;
+
+ err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (err)
+ return err;
+
+ irq_domain_set_info(domain, virq, hwirq,
+ &pch_pic_irq_chip, priv,
+ handle_fasteoi_ack_irq, NULL, NULL);
+ irq_set_probe(virq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops pch_pic_domain_ops = {
+ .translate = irq_domain_translate_twocell,
+ .alloc = pch_pic_alloc,
+ .free = irq_domain_free_irqs_parent,
+};
+
+static void pch_pic_reset(struct pch_pic *priv)
+{
+ int i;
+
+ for (i = 0; i < PIC_COUNT; i++) {
+ /* Write vectore ID */
+ writeb(priv->ht_vec_base + i, priv->base + PCH_INT_HTVEC(i));
+ /* Hardcode route to HT0 Lo */
+ writeb(1, priv->base + PCH_INT_ROUTE(i));
+ }
+
+ for (i = 0; i < PIC_REG_COUNT; i++) {
+ /* Clear IRQ cause registers, mask all interrupts */
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * i);
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * i);
+ /* Clear auto bounce, we don't need that */
+ writel_relaxed(0, priv->base + PCH_PIC_AUTO0 + 4 * i);
+ writel_relaxed(0, priv->base + PCH_PIC_AUTO1 + 4 * i);
+ /* Enable HTMSI transformer */
+ writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_HTMSI_EN + 4 * i);
+ }
+}
+
+static int pch_pic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_pic *priv;
+ struct irq_domain *parent_domain;
+ int err;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&priv->pic_lock);
+ priv->base = of_iomap(node, 0);
+ if (!priv->base) {
+ err = -ENOMEM;
+ goto free_priv;
+ }
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ err = -ENXIO;
+ goto iounmap_base;
+ }
+
+ if (of_property_read_u32(node, "loongson,pic-base-vec",
+ &priv->ht_vec_base)) {
+ pr_err("Failed to determine pic-base-vec\n");
+ err = -EINVAL;
+ goto iounmap_base;
+ }
+
+ priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
+ PIC_COUNT,
+ of_node_to_fwnode(node),
+ &pch_pic_domain_ops,
+ priv);
+ if (!priv->pic_domain) {
+ pr_err("Failed to create IRQ domain\n");
+ err = -ENOMEM;
+ goto iounmap_base;
+ }
+
+ pch_pic_reset(priv);
+
+ return 0;
+
+iounmap_base:
+ iounmap(priv->base);
+free_priv:
+ kfree(priv);
+
+ return err;
+}
+
+IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
--
2.26.0.rc2

2020-05-01 09:25:35

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 5/6] irqchip: Add Loongson PCH MSI controller

This controller appears on Loongson LS7A family of PCH to transform
interrupts from PCI MSI into HyperTransport vectorized interrrupts
and send them to procrssor's HT vector controller.

Signed-off-by: Jiaxun Yang <[email protected]>
--
v2:
- Style clean-ups
- Add ack callback
- Use bitmap_find_free_region
v3:
- Style clean-ups
- mutex lock instead of spin lock
- correct bitmap usage
---
drivers/irqchip/Kconfig | 10 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-loongson-pch-msi.c | 255 +++++++++++++++++++++++++
3 files changed, 266 insertions(+)
create mode 100644 drivers/irqchip/irq-loongson-pch-msi.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 5524a621638c..0b6b826dd843 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -549,4 +549,14 @@ config LOONGSON_PCH_PIC
help
Support for the Loongson PCH PIC Controller.

+config LOONGSON_PCH_MSI
+ bool "Loongson PCH PIC Controller"
+ depends on MACH_LOONGSON64 || COMPILE_TEST
+ depends on PCI
+ default MACH_LOONGSON64
+ select IRQ_DOMAIN_HIERARCHY
+ select PCI_MSI
+ help
+ Support for the Loongson PCH MSI Controller.
+
endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index acc72331cec8..3a4ce283189a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -109,3 +109,4 @@ obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.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
+obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c
new file mode 100644
index 000000000000..73e6124a95ac
--- /dev/null
+++ b/drivers/irqchip/irq-loongson-pch-msi.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, Jiaxun Yang <[email protected]>
+ * Loongson PCH MSI support
+ */
+
+#define pr_fmt(fmt) "pch-msi: " fmt
+
+#include <linux/irqchip.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+struct pch_msi_data {
+ struct mutex msi_map_lock;
+ phys_addr_t doorbell;
+ u32 irq_first; /* The vector number that MSIs starts */
+ u32 num_irqs; /* The number of vectors for MSIs */
+ unsigned long *msi_map;
+};
+
+static void pch_msi_mask_msi_irq(struct irq_data *d)
+{
+ pci_msi_mask_irq(d);
+ irq_chip_mask_parent(d);
+}
+
+static void pch_msi_unmask_msi_irq(struct irq_data *d)
+{
+ pci_msi_unmask_irq(d);
+ irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip pch_msi_irq_chip = {
+ .name = "PCH PCI MSI",
+ .irq_mask = pch_msi_mask_msi_irq,
+ .irq_unmask = pch_msi_unmask_msi_irq,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+};
+
+static int pch_msi_allocate_hwirq(struct pch_msi_data *priv, int num_req)
+{
+ int first;
+
+ mutex_lock(&priv->msi_map_lock);
+
+ first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
+ get_count_order(num_req));
+ if (first < 0) {
+ mutex_unlock(&priv->msi_map_lock);
+ return -ENOSPC;
+ }
+
+ mutex_unlock(&priv->msi_map_lock);
+
+ return priv->irq_first + first;
+}
+
+static void pch_msi_free_hwirq(struct pch_msi_data *priv,
+ int hwirq, int num_req)
+{
+ int first = hwirq - priv->irq_first;
+
+ mutex_lock(&priv->msi_map_lock);
+ bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
+ mutex_unlock(&priv->msi_map_lock);
+}
+
+static void pch_msi_compose_msi_msg(struct irq_data *data,
+ struct msi_msg *msg)
+{
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(data);
+
+ msg->address_hi = upper_32_bits(priv->doorbell);
+ msg->address_lo = lower_32_bits(priv->doorbell);
+ msg->data = data->hwirq;
+}
+
+static struct msi_domain_info pch_msi_domain_info = {
+ .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+ MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
+ .chip = &pch_msi_irq_chip,
+};
+
+static struct irq_chip middle_irq_chip = {
+ .name = "PCH MSI",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_ack = irq_chip_ack_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_compose_msi_msg = pch_msi_compose_msi_msg,
+};
+
+static int pch_msi_parent_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, int hwirq)
+{
+ struct irq_fwspec fwspec;
+ int ret;
+
+ fwspec.fwnode = domain->parent->fwnode;
+ fwspec.param_count = 1;
+ fwspec.param[0] = hwirq;
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int pch_msi_middle_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *args)
+{
+ struct pch_msi_data *priv = domain->host_data;
+ int hwirq, err, i;
+
+ hwirq = pch_msi_allocate_hwirq(priv, nr_irqs);
+ if (hwirq < 0)
+ return hwirq;
+
+ for (i = 0; i < nr_irqs; i++) {
+ err = pch_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
+ if (err)
+ goto err_hwirq;
+
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &middle_irq_chip, priv);
+ }
+
+ return 0;
+
+err_hwirq:
+ pch_msi_free_hwirq(priv, hwirq, nr_irqs);
+ irq_domain_free_irqs_parent(domain, virq, i - 1);
+
+ return err;
+}
+
+static void pch_msi_middle_domain_free(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs)
+{
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+ struct pch_msi_data *priv = irq_data_get_irq_chip_data(d);
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+ pch_msi_free_hwirq(priv, d->hwirq, nr_irqs);
+}
+
+static const struct irq_domain_ops pch_msi_middle_domain_ops = {
+ .alloc = pch_msi_middle_domain_alloc,
+ .free = pch_msi_middle_domain_free,
+};
+
+static int pch_msi_init_domains(struct pch_msi_data *priv,
+ struct device_node *node,
+ struct irq_domain *parent)
+{
+ struct irq_domain *middle_domain, *msi_domain;
+
+ middle_domain = irq_domain_create_linear(of_node_to_fwnode(node),
+ priv->num_irqs,
+ &pch_msi_middle_domain_ops,
+ priv);
+ if (!middle_domain) {
+ pr_err("Failed to create the MSI middle domain\n");
+ return -ENOMEM;
+ }
+
+ middle_domain->parent = parent;
+ irq_domain_update_bus_token(middle_domain, DOMAIN_BUS_NEXUS);
+
+ msi_domain = pci_msi_create_irq_domain(of_node_to_fwnode(node),
+ &pch_msi_domain_info,
+ middle_domain);
+ if (!msi_domain) {
+ pr_err("Failed to create PCI MSI domain\n");
+ irq_domain_remove(middle_domain);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int pch_msi_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct pch_msi_data *priv;
+ struct irq_domain *parent_domain;
+ struct resource res;
+ int ret;
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("Failed to find the parent domain\n");
+ return -ENXIO;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->msi_map_lock);
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret) {
+ pr_err("Failed to allocate resource\n");
+ goto err_priv;
+ }
+
+ priv->doorbell = res.start;
+
+ if (of_property_read_u32(node, "loongson,msi-base-vec",
+ &priv->irq_first)) {
+ pr_err("Unable to parse MSI vec base\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ if (of_property_read_u32(node, "loongson,msi-num-vecs",
+ &priv->num_irqs)) {
+ pr_err("Unable to parse MSI vec number\n");
+ ret = -EINVAL;
+ goto err_priv;
+ }
+
+ priv->msi_map = bitmap_alloc(priv->num_irqs, GFP_KERNEL);
+ if (!priv->msi_map) {
+ ret = -ENOMEM;
+ goto err_priv;
+ }
+
+ pr_debug("Registering %d MSIs, starting at %d\n",
+ priv->num_irqs, priv->irq_first);
+
+ ret = pch_msi_init_domains(priv, node, parent_domain);
+ if (ret)
+ goto err_map;
+
+ return 0;
+
+err_map:
+ kfree(priv->msi_map);
+err_priv:
+ kfree(priv);
+ return ret;
+}
+
+IRQCHIP_DECLARE(pch_msi, "loongson,pch-msi-1.0", pch_msi_init);
--
2.26.0.rc2

2020-05-01 09:26:05

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 4/6] dt-bindings: interrupt-controller: Add Loongson PCH PIC

Add binding for Loongson PCH PIC Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
--
v2:
- Fix naming
- Mark loongson,pic-base-vec as required
---
.../loongson,pch-pic.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
new file mode 100644
index 000000000000..92a8cd0ce23b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-pic.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-pic.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH PIC Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson LS7A family of PCH for
+ transforming interrupts from on-chip devices into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-pic-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,pic-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH PIC.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - loongson,pic-base-vec
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ pic: interrupt-controller@10000000 {
+ compatible = "loongson,pch-pic-1.0";
+ reg = <0x10000000 0x400>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ loongson,pic-base-vec = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-05-01 09:26:15

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

Add binding for Loongson PCH MSI controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../loongson,pch-msi.yaml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
new file mode 100644
index 000000000000..513ed1933035
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,pch-msi.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson PCH MSI Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+description: |
+ This interrupt controller is found in the Loongson LS7A family of PCH for
+ transforming interrupts from PCIe MSI into HyperTransport vectorized
+ interrupts.
+
+properties:
+ compatible:
+ const: loongson,pch-msi-1.0
+
+ reg:
+ maxItems: 1
+
+ loongson,msi-base-vec:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the base of parent HyperTransport vector allocated
+ to PCH MSI.
+
+ loongson,msi-num-vecs:
+ $ref: '/schemas/types.yaml#/definitions/uint32'
+ description: |
+ u32 value of the number of parent HyperTransport vectors allocated
+ to PCH MSI.
+
+ msi-controller: true
+
+required:
+ - compatible
+ - reg
+ - msi-controller
+ - loongson,msi-base-vec
+ - loongson,msi-num-vecs
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ msi: msi-controller@2ff00000 {
+ compatible = "loongson,pch-msi-1.0";
+ reg = <0x2ff00000 0x4>;
+ msi-controller;
+ loongson,msi-base-vec = <64>;
+ loongson,msi-num-vecs = <64>;
+ interrupt-parent = <&htvec>;
+ };
+...
--
2.26.0.rc2

2020-05-01 09:26:43

by Jiaxun Yang

[permalink] [raw]
Subject: [PATCH v3 2/6] dt-bindings: interrupt-controller: Add Loongson HTVEC

Add binding for Loongson-3 HyperTransport Interrupt Vector Controller.

Signed-off-by: Jiaxun Yang <[email protected]>
---
.../interrupt-controller/loongson,htvec.yaml | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
new file mode 100644
index 000000000000..547a80c89eba
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/interrupt-controller/loongson,htvec.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Loongson-3 HyperTransport Interrupt Vector Controller
+
+maintainers:
+ - Jiaxun Yang <[email protected]>
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+description: |
+ This interrupt controller is found in the Loongson-3 family of chips for
+ receiving vectorized interrupts from PCH's interrupt controller.
+
+properties:
+ compatible:
+ const: loongson,htvec-1.0
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 1
+ maxItems: 4
+ description: |
+ Four parent interrupts that receive chained interrupts.
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ htvec: interrupt-controller@1fb000080 {
+ compatible = "loongson,htvec-1.0";
+ reg = <0xfb000080 0x40>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ interrupt-parent = <&liointc>;
+ interrupts = <24 IRQ_TYPE_LEVEL_HIGH>,
+ <25 IRQ_TYPE_LEVEL_HIGH>,
+ <26 IRQ_TYPE_LEVEL_HIGH>,
+ <27 IRQ_TYPE_LEVEL_HIGH>;
+ };
+...
--
2.26.0.rc2

2020-05-12 07:47:51

by Jiaxun Yang

[permalink] [raw]
Subject: Re: [PATCH v3 1/6] irqchip: Add Loongson HyperTransport Vector support

On Fri, 1 May 2020 17:21:32 +0800
Jiaxun Yang <[email protected]> wrote:

> This controller appears on Loongson-3 chips for receiving interrupt
> vectors from PCH's PIC and PCH's PCIe MSI interrupts.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> v2:
> - Style cleanup
> - Set ack callback and set correct edge_irq handler
>
> v3:
> - Correct bitops in ACK callback

Any update about v3?

Thanks.

[...]
--
Jiaxun Yang

2020-05-12 16:45:24

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v3 2/6] dt-bindings: interrupt-controller: Add Loongson HTVEC

On Fri, May 01, 2020 at 05:21:33PM +0800, Jiaxun Yang wrote:
> Add binding for Loongson-3 HyperTransport Interrupt Vector Controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> .../interrupt-controller/loongson,htvec.yaml | 59 +++++++++++++++++++
> 1 file changed, 59 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
>
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
> new file mode 100644
> index 000000000000..547a80c89eba
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/loongson,htvec.yaml
> @@ -0,0 +1,59 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: "http://devicetree.org/schemas/interrupt-controller/loongson,htvec.yaml#"
> +$schema: "http://devicetree.org/meta-schemas/core.yaml#"
> +
> +title: Loongson-3 HyperTransport Interrupt Vector Controller
> +
> +maintainers:
> + - Jiaxun Yang <[email protected]>
> +
> +allOf:
> + - $ref: /schemas/interrupt-controller.yaml#

Don't need this. It's already applied to any node named
'interrupt-controller'.

> +
> +description: |

Can drop '|' if you don't need formatting.

> + This interrupt controller is found in the Loongson-3 family of chips for
> + receiving vectorized interrupts from PCH's interrupt controller.
> +
> +properties:
> + compatible:
> + const: loongson,htvec-1.0
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + minItems: 1
> + maxItems: 4
> + description: |
> + Four parent interrupts that receive chained interrupts.
> +
> + interrupt-controller: true
> +
> + '#interrupt-cells':
> + const: 1
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - interrupt-controller
> + - '#interrupt-cells'

Add:

additionalProperties: false

> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + htvec: interrupt-controller@1fb000080 {

Unit-address doesn't match reg.

> + compatible = "loongson,htvec-1.0";
> + reg = <0xfb000080 0x40>;
> + interrupt-controller;
> + #interrupt-cells = <1>;
> +
> + interrupt-parent = <&liointc>;
> + interrupts = <24 IRQ_TYPE_LEVEL_HIGH>,
> + <25 IRQ_TYPE_LEVEL_HIGH>,
> + <26 IRQ_TYPE_LEVEL_HIGH>,
> + <27 IRQ_TYPE_LEVEL_HIGH>;
> + };
> +...
> --
> 2.26.0.rc2
>

2020-05-12 20:59:34

by Rob Herring

[permalink] [raw]
Subject: Re: [PATCH v3 6/6] dt-bindings: interrupt-controller: Add Loongson PCH MSI

On Fri, 1 May 2020 17:21:37 +0800, Jiaxun Yang wrote:
> Add binding for Loongson PCH MSI controller.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> .../loongson,pch-msi.yaml | 56 +++++++++++++++++++
> 1 file changed, 56 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/interrupt-controller/loongson,pch-msi.yaml
>

Reviewed-by: Rob Herring <[email protected]>

2020-05-13 12:11:58

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH v3 1/6] irqchip: Add Loongson HyperTransport Vector support

Jiaxun Yang <[email protected]> writes:
> +static void htvec_mask_irq(struct irq_data *d)
> +{
> + struct htvec *priv = irq_data_get_irq_chip_data(d);
> + void __iomem *addr = priv->base + HTVEC_EN_OFF;
> + unsigned long flags;
> + u32 reg;
> +
> + raw_spin_lock_irqsave(&priv->htvec_lock, flags);

No need for irqsave() these functions are called with interrupts disabled.

> + addr += VEC_REG_IDX(d->hwirq) * 4;
> + reg = readl(addr);
> + reg &= ~BIT(VEC_REG_BIT(d->hwirq));
> + writel(reg, addr);
> + raw_spin_unlock_irqrestore(&priv->htvec_lock, flags);
> +}

> +static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
> + unsigned int nr_irqs, void *arg)
> +{
> + struct htvec *priv = domain->host_data;
> + unsigned long hwirq;
> + unsigned int type, i;
> +
> + irq_domain_translate_onecell(domain, arg, &hwirq, &type);
> +
> + for (i = 0; i < nr_irqs; i++)
> + irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
> + priv, handle_edge_irq, NULL, NULL);

This wants curly brackets and the second line of arguments wants to be
aligned with the first argument:

for (i = 0; i < nr_irqs; i++) {
irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
priv, handle_edge_irq, NULL, NULL);
}

See https://lore.kernel.org/lkml/alpine.DEB.2.20.1701171956290.3645@nanos/

The alignment of arguments wants to be fixed all over the place.

> +static int htvec_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct htvec *priv;
> + int err, parent_irq[4], num_parents = 0, i;

Please order the variable declaration in reverse fir tree length order:

int err, parent_irq[4], num_parents = 0, i;
struct htvec *priv;

That's way better readable than the above. All over the place please.

> + priv->htvec_domain = irq_domain_create_linear(of_node_to_fwnode(node),
> + VEC_COUNT,
> + &htvec_domain_ops,
> + priv);
> + if (!priv->htvec_domain) {
> + pr_err("Failed to create IRQ domain\n");
> + err = -ENOMEM;
> + goto iounmap_base;
> + }
> +
> + htvec_reset(priv);
> +
> + for (i = 0; i < num_parents; i++)
> + irq_set_chained_handler_and_data(parent_irq[i],
> + htvec_irq_dispatch, priv);

See above.

Thanks,

tglx

2020-05-13 12:13:28

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH v2 3/6] irqchip: Add Loongson PCH PIC controller

Jiaxun Yang <[email protected]> writes:
> +static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit)
> +{
> + void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
> + unsigned long flags;
> + u32 reg;
> +
> + raw_spin_lock_irqsave(&priv->pic_lock, flags);

See other reply.

> + reg = readl(addr);
> + reg |= BIT(PIC_REG_BIT(bit));
> + writel(reg, addr);
> + raw_spin_unlock_irqrestore(&priv->pic_lock, flags);
> +}
> +static int pch_pic_of_init(struct device_node *node,
> + struct device_node *parent)
> +{
> + struct pch_pic *priv;
> + struct irq_domain *parent_domain;
> + int err;

ordering

Thanks,

tglx

2020-05-13 12:15:50

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH v2 5/6] irqchip: Add Loongson PCH MSI controller

Jiaxun Yang <[email protected]> writes:
> +
> +struct pch_msi_data {
> + spinlock_t msi_map_lock;
> + phys_addr_t doorbell;
> + u32 irq_first; /* The vector number that MSIs starts */
> + u32 num_irqs; /* The number of vectors for MSIs */
> + unsigned long *msi_map;
> +};
> +
> +static void pch_msi_mask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_mask_irq(d);
> + irq_chip_mask_parent(d);
> +}
> +
> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
> +{
> + pci_msi_unmask_irq(d);
> + irq_chip_unmask_parent(d);

The ordering of mask and unmask is assymetric. That does not make sense.

> +static struct msi_domain_info pch_msi_domain_info = {
> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
> + .chip = &pch_msi_irq_chip,

Please maintain tabular layout.

Thanks,

tglx

2020-05-13 12:19:46

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH v2 5/6] irqchip: Add Loongson PCH MSI controller

Thomas Gleixner <[email protected]> writes:
> Jiaxun Yang <[email protected]> writes:
>> +
>> +struct pch_msi_data {
>> + spinlock_t msi_map_lock;
>> + phys_addr_t doorbell;
>> + u32 irq_first; /* The vector number that MSIs starts */
>> + u32 num_irqs; /* The number of vectors for MSIs */
>> + unsigned long *msi_map;
>> +};
>> +
>> +static void pch_msi_mask_msi_irq(struct irq_data *d)
>> +{
>> + pci_msi_mask_irq(d);
>> + irq_chip_mask_parent(d);
>> +}
>> +
>> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
>> +{
>> + pci_msi_unmask_irq(d);
>> + irq_chip_unmask_parent(d);
>
> The ordering of mask and unmask is assymetric. That does not make sense.
>
>> +static struct msi_domain_info pch_msi_domain_info = {
>> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
>> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
>> + .chip = &pch_msi_irq_chip,
>
> Please maintain tabular layout.

Ooops. Wanted to reply to V3, but the comments are valid for V3 as well.

Thanks,

tglx

2020-05-20 11:55:10

by Jiaxun Yang

[permalink] [raw]
Subject: Re: [PATCH v2 5/6] irqchip: Add Loongson PCH MSI controller



于 2020年5月13日 GMT+08:00 下午8:15:40, Thomas Gleixner <[email protected]> 写到:
>Thomas Gleixner <[email protected]> writes:
>> Jiaxun Yang <[email protected]> writes:
>>> +
>>> +struct pch_msi_data {
>>> + spinlock_t msi_map_lock;
>>> + phys_addr_t doorbell;
>>> + u32 irq_first; /* The vector number that MSIs starts */
>>> + u32 num_irqs; /* The number of vectors for MSIs */
>>> + unsigned long *msi_map;
>>> +};
>>> +
>>> +static void pch_msi_mask_msi_irq(struct irq_data *d)
>>> +{
>>> + pci_msi_mask_irq(d);
>>> + irq_chip_mask_parent(d);
>>> +}
>>> +
>>> +static void pch_msi_unmask_msi_irq(struct irq_data *d)
>>> +{
>>> + pci_msi_unmask_irq(d);
>>> + irq_chip_unmask_parent(d);
>>
>> The ordering of mask and unmask is assymetric. That does not make sense.
>>
>>> +static struct msi_domain_info pch_msi_domain_info = {
>>> + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
>>> + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
>>> + .chip = &pch_msi_irq_chip,
>>
>> Please maintain tabular layout.
>
>Ooops. Wanted to reply to V3, but the comments are valid for V3 as well.

All fixed in v4.

Please review.

Thanks!


>
>Thanks,
>
> tglx

--
Jiaxun Yang