Renesas SH7751 Internal interrupt controller driver.

Signed-off-by: Yoshinori Sato <ys...@users.sourceforge.jp>
---
 drivers/irqchip/Kconfig              |   8 +
 drivers/irqchip/Makefile             |   1 +
 drivers/irqchip/irq-renesas-sh7751.c | 282 +++++++++++++++++++++++++++
 3 files changed, 291 insertions(+)
 create mode 100644 drivers/irqchip/irq-renesas-sh7751.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 14464716bacb..f45a229963d4 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -715,4 +715,12 @@ config SUNPLUS_SP7021_INTC
          chained controller, routing all interrupt source in P-Chip to
          the primary controller on C-Chip.
 
+config RENESAS_SH7751_INTC
+       bool "Renesas SH7751 Interrupt Controller"
+       depends on SH_DEVICE_TREE || COMPILE_TEST
+       select IRQ_DOMAIN_HIERARCHY
+       help
+         Support for the Renesas SH7751 On-chip interrupt controller.
+         And external interrupt encoder for some targets.
+
 endmenu
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index d9dc3d99aaa8..7bde45f05a1e 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -124,3 +124,4 @@ obj-$(CONFIG_IRQ_IDT3243X)          += irq-idt3243x.o
 obj-$(CONFIG_APPLE_AIC)                        += irq-apple-aic.o
 obj-$(CONFIG_MCHP_EIC)                 += irq-mchp-eic.o
 obj-$(CONFIG_SUNPLUS_SP7021_INTC)      += irq-sp7021-intc.o
+obj-$(CONFIG_RENESAS_SH7751_INTC)      += irq-renesas-sh7751.o
diff --git a/drivers/irqchip/irq-renesas-sh7751.c 
b/drivers/irqchip/irq-renesas-sh7751.c
new file mode 100644
index 000000000000..91d6dc3ed04c
--- /dev/null
+++ b/drivers/irqchip/irq-renesas-sh7751.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas SH7751 interrupt controller driver
+ *
+ * Copyright 2023 Yoshinori Sato <ys...@users.sourceforge.jp>
+ */
+
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <dt-bindings/interrupt-controller/renesas,sh7751-intc.h>
+
+struct ipr {
+       u16 off;
+       u16 idx;
+};
+
+struct sh7751_intc_priv {
+       const struct ipr *iprmap;
+       void __iomem *base;
+       void __iomem *intpri00;
+       bool         irlm;
+};
+
+enum {
+       R_ICR         = 0x00,
+       R_IPR         = 0x04,
+       R_INTPRI00    = 0x00,
+       R_INTREQ00    = 0x20,
+       R_INTMSK00    = 0x40,
+       R_INTMSKCLR00 = 0x60,
+};
+
+#define ICR_IRLM BIT(7)
+
+/*
+ * SH7751 IRQ mapping
+ *  IRQ16 - 63: Group0 - IPRA to IPRD
+ *   IRQ16 - 31: external IRL input (ICR.IRLM is 0)
+ *  IRQ80 - 92: Group1 - INTPRI00
+ */
+#define IRQ_START      16
+#define MAX_IRL                (IRQ_START + NR_IRL)
+#define GRP0_IRQ_END   63
+#define GRP1_IRQ_START 80
+#define IRQ_END                92
+
+#define NR_IPRMAP0     (GRP0_IRQ_END - IRQ_START + 1)
+#define NR_IPRMAP1     (IRQ_END - GRP1_IRQ_START)
+#define IPR_PRI_MASK   0x000f
+
+#define IPRA                   0
+#define IPRB                   4
+#define IPRC                   8
+#define IPRD                   12
+#define INTPRI00               256
+#define IPR_B12                        12
+#define IPR_B8                 8
+#define IPR_B4                 4
+#define IPR_B0                 0
+
+/* SH7751 EVT to IPR mapping table */
+static const struct ipr sh7751_iprmap[] = {
+       [evt2irq(0x240)] = {IPRD, IPR_B12},     /* IRL0 (ICR.IRLM=1) */
+       [evt2irq(0x2a0)] = {IPRD, IPR_B8},      /* IRL1 (ICR.IRLM=1) */
+       [evt2irq(0x300)] = {IPRD, IPR_B4},      /* IRL2 (ICR.IRLM=1) */
+       [evt2irq(0x360)] = {IPRD, IPR_B0},      /* IRL3 (ICR.IRLM=1) */
+       [evt2irq(0x400)] = {IPRA, IPR_B12},     /* TMU0 */
+       [evt2irq(0x420)] = {IPRA, IPR_B8},      /* TMU1 */
+       [evt2irq(0x440)] = {IPRA, IPR_B4},      /* TMU2 TNUI */
+       [evt2irq(0x460)] = {IPRA, IPR_B4},      /* TMU2 TICPI */
+       [evt2irq(0x480)] = {IPRA, IPR_B0},      /* RTC ATI */
+       [evt2irq(0x4a0)] = {IPRA, IPR_B0},      /* RTC PRI */
+       [evt2irq(0x4c0)] = {IPRA, IPR_B0},      /* RTC CUI */
+       [evt2irq(0x4e0)] = {IPRB, IPR_B4},      /* SCI ERI */
+       [evt2irq(0x500)] = {IPRB, IPR_B4},      /* SCI RXI */
+       [evt2irq(0x520)] = {IPRB, IPR_B4},      /* SCI TXI */
+       [evt2irq(0x540)] = {IPRB, IPR_B4},      /* SCI TEI */
+       [evt2irq(0x560)] = {IPRB, IPR_B12},     /* WDT */
+       [evt2irq(0x580)] = {IPRB, IPR_B8},      /* REF RCMI */
+       [evt2irq(0x5a0)] = {IPRB, IPR_B4},      /* REF ROVI */
+       [evt2irq(0x600)] = {IPRC, IPR_B0},      /* H-UDI */
+       [evt2irq(0x620)] = {IPRC, IPR_B12},     /* GPIO */
+       [evt2irq(0x640)] = {IPRC, IPR_B8},      /* DMAC DMTE0 */
+       [evt2irq(0x660)] = {IPRC, IPR_B8},      /* DMAC DMTE1 */
+       [evt2irq(0x680)] = {IPRC, IPR_B8},      /* DMAC DMTE2 */
+       [evt2irq(0x6a0)] = {IPRC, IPR_B8},      /* DMAC DMTE3 */
+       [evt2irq(0x6c0)] = {IPRC, IPR_B8},      /* DMAC DMAE */
+       [evt2irq(0x700)] = {IPRC, IPR_B4},      /* SCIF ERI */
+       [evt2irq(0x720)] = {IPRC, IPR_B4},      /* SCIF RXI */
+       [evt2irq(0x740)] = {IPRC, IPR_B4},      /* SCIF BRI */
+       [evt2irq(0x760)] = {IPRC, IPR_B4},      /* SCIF TXI */
+       [evt2irq(0x780)] = {IPRC, IPR_B8},      /* DMAC DMTE4 */
+       [evt2irq(0x7a0)] = {IPRC, IPR_B8},      /* DMAC DMTE5 */
+       [evt2irq(0x7c0)] = {IPRC, IPR_B8},      /* DMAC DMTE6 */
+       [evt2irq(0x7e0)] = {IPRC, IPR_B8},      /* DMAC DMTE7 */
+       [evt2irq(0xa00)] = {INTPRI00, IPR_B0},  /* PCIC PCISERR */
+       [evt2irq(0xa20)] = {INTPRI00, IPR_B4},  /* PCIC PCIDMA3 */
+       [evt2irq(0xa40)] = {INTPRI00, IPR_B4},  /* PCIC PCIDMA2 */
+       [evt2irq(0xa60)] = {INTPRI00, IPR_B4},  /* PCIC PCIDMA1 */
+       [evt2irq(0xa80)] = {INTPRI00, IPR_B4},  /* PCIC PCIDMA0 */
+       [evt2irq(0xaa0)] = {INTPRI00, IPR_B4},  /* PCIC PCIPWON */
+       [evt2irq(0xac0)] = {INTPRI00, IPR_B4},  /* PCIC PCIPWDWN */
+       [evt2irq(0xae0)] = {INTPRI00, IPR_B4},  /* PCIC PCIERR */
+       [evt2irq(0xb00)] = {INTPRI00, IPR_B8},  /* TMU3 */
+       [evt2irq(0xb80)] = {INTPRI00, IPR_B12}, /* TMU4 */
+};
+
+/*
+ * IPR registers have 4bit priority x 4 entry (16bits)
+ */
+static void update_ipr(struct sh7751_intc_priv *priv, unsigned int irq, u16 
pri)
+{
+       const struct ipr *ipr = NULL;
+       void __iomem *ipr_base;
+       unsigned int offset;
+       u16 mask;
+
+       ipr = priv->iprmap + irq;
+       if (irq < GRP1_IRQ_START) {
+               /* Group0 */
+               ipr_base = priv->base + R_IPR;
+               offset = ipr->off;
+       } else {
+               /* Group1 */
+               ipr_base = priv->intpri00;
+               offset = ipr->off - INTPRI00;
+       }
+       mask = ~(IPR_PRI_MASK << ipr->idx);
+       pri = (pri & IPR_PRI_MASK) << ipr->idx;
+       mask &= __raw_readw(ipr_base + offset);
+       __raw_writew(mask | pri, ipr_base + offset);
+}
+
+static inline bool is_valid_irq(unsigned int irq)
+{
+       /* IRQ16 - 63 */
+       if (irq >= IRQ_START && irq < IRQ_START + NR_IPRMAP0)
+               return true;
+       /* IRQ80 - 92 */
+       if (irq >= GRP1_IRQ_START && irq <= IRQ_END)
+               return true;
+       return false;
+}
+
+static inline struct sh7751_intc_priv *irq_data_to_priv(struct irq_data *data)
+{
+       return data->domain->host_data;
+}
+
+/* Interrupt unmask priority is 1, mask priority is 0 */
+#define PRI_ENABLE  1
+#define PRI_DISABLE 0
+static void endisable_irq(struct irq_data *data, bool enable)
+{
+       struct sh7751_intc_priv *priv;
+       unsigned int irq;
+
+       priv = irq_data_to_priv(data);
+
+       irq = irqd_to_hwirq(data);
+       if (!is_valid_irq(irq)) {
+               /* IRQ out of range */
+               pr_warn_once("%s: IRQ %u is out of range\n", __FILE__, irq);
+               return;
+       }
+
+       if (irq <= MAX_IRL && !priv->irlm) {
+               /* IRL encoded external interrupt */
+               /* enable and disable from SR.IMASK */
+               update_sr_imask(irq - IRQ_START, enable);
+       } else {
+               /* Internal peripheral interrupt */
+               /* enable and disable from interrupt priority */
+               update_ipr(priv, irq, enable ? PRI_ENABLE : PRI_DISABLE);
+       }
+}
+
+static void sh7751_mask_irq(struct irq_data *data)
+{
+       endisable_irq(data, false);
+}
+
+static void sh7751_unmask_irq(struct irq_data *data)
+{
+       endisable_irq(data, true);
+}
+
+static const struct irq_chip sh7751_irq_chip = {
+       .name           = "SH7751-INTC",
+       .irq_unmask     = sh7751_unmask_irq,
+       .irq_mask       = sh7751_mask_irq,
+};
+
+static int irq_sh7751_map(struct irq_domain *h, unsigned int virq,
+                         irq_hw_number_t hw_irq_num)
+{
+       irq_set_chip_and_handler(virq, &sh7751_irq_chip, handle_level_irq);
+       irq_get_irq_data(virq)->chip_data = h->host_data;
+       irq_modify_status(virq, IRQ_NOREQUEST, IRQ_NOPROBE);
+       return 0;
+}
+
+static int irq_sh7751_xlate(struct irq_domain *d, struct device_node *ctrlr,
+                            const u32 *intspec, unsigned int intsize,
+                            unsigned long *out_hwirq, unsigned int *out_type)
+{
+       if (WARN_ON(intsize < 1))
+               return -EINVAL;
+       *out_hwirq = evt2irq(intspec[0]);
+       *out_type = IRQ_TYPE_NONE;
+       return 0;
+}
+
+static const struct irq_domain_ops irq_ops = {
+       .map    = irq_sh7751_map,
+       .xlate  = irq_sh7751_xlate,
+};
+
+static int __init shintc_of_init(struct device_node *intc, struct device_node 
*parent,
+                                const struct ipr *iprmap)
+{
+       struct sh7751_intc_priv *priv;
+       void __iomem *base, *base2;
+       struct irq_domain *domain;
+       u16 icr;
+       int ret;
+
+       priv = kzalloc(sizeof(struct sh7751_intc_priv), GFP_KERNEL);
+       if (priv == NULL)
+               return -ENOMEM;
+
+       base = of_iomap(intc, 0);
+       base2 = of_iomap(intc, 1);
+       if (!base || !base2) {
+               pr_err("%pOFP: Invalid register definition\n", intc);
+               ret = -EINVAL;
+               goto error;
+       }
+
+       priv->base = base;
+       priv->intpri00 = base2;
+       priv->iprmap = iprmap;
+
+       if (of_property_read_bool(intc, "renesas,irlm")) {
+               priv->irlm = true;
+               icr = __raw_readw(priv->base + R_ICR);
+               icr |= ICR_IRLM;
+               __raw_writew(icr, priv->base + R_ICR);
+       }
+
+       domain = irq_domain_add_linear(intc, NR_IRQS, &irq_ops, priv);
+       if (domain == NULL) {
+               pr_err("%pOFP: cannot initialize irq domain\n", intc);
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       irq_set_default_host(domain);
+       pr_info("%pOFP: SH7751 Interrupt controller (%s external IRQ)",
+               intc, priv->irlm ? "4 lines" : "15 level");
+       return 0;
+
+error:
+       if (base)
+               iounmap(base);
+       if (base2)
+               iounmap(base);
+       kfree(priv);
+       return ret;
+}
+
+static int __init sh7751_intc_of_init(struct device_node *intc,
+                                     struct device_node *parent)
+{
+       return shintc_of_init(intc, parent, sh7751_iprmap);
+}
+
+IRQCHIP_DECLARE(sh_7751_intc, "renesas,sh7751-intc", sh7751_intc_of_init);
-- 
2.39.2

Reply via email to