[RFC PATCH 05/12] drivers/irqchip: Add SH7751 and boards specific irqchip.
Yoshinori Sato <[email protected]>
| Newsgroups | gmane.linux.ports.sh.devel |
|---|---|
| Message-ID | <52f2118d33bea5e74d4ffda4b3d935772e743ec2.1693384846.git.ysato@users.sourceforge.jp> |
- irq-renesas-sh7751: Renesas SH7751 internal INTC. - irq-renesas-r2d: Renesas RTS7751R2D external interrupt encoder. - irq-iodata-julian: IO DATA Device LANDISK external interrupt encoder. Signed-off-by: Yoshinori Sato <[email protected]> --- drivers/irqchip/Kconfig | 4 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-iodata-julian.c | 163 +++++++++++++++++++++++ drivers/irqchip/irq-renesas-r2d.c | 175 +++++++++++++++++++++++++ drivers/irqchip/irq-renesas-sh7751.c | 186 +++++++++++++++++++++++++++ 5 files changed, 529 insertions(+) create mode 100644 drivers/irqchip/irq-iodata-julian.c create mode 100644 drivers/irqchip/irq-renesas-r2d.c create mode 100644 drivers/irqchip/irq-renesas-sh7751.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 09e422da482f..372b22d5183f 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -660,6 +660,10 @@ config APPLE_AIC help Support for the Apple Interrupt Controller found on Apple Silicon SoCs, such as the M1. +config RENESAS_SH_INTC + def_bool y if SH_DEVICE_TREE + select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY config MCHP_EIC bool "Microchip External Interrupt Controller" diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index ffd945fe71aa..a3859bdd0442 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -120,3 +120,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_SH_INTC) += irq-renesas-sh7751.o irq-iodata-julian.o irq-renesas-r2d.o diff --git a/drivers/irqchip/irq-iodata-julian.c b/drivers/irqchip/irq-iodata-julian.c new file mode 100644 index 000000000000..95e502904a68 --- /dev/null +++ b/drivers/irqchip/irq-iodata-julian.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IO-DATA DEVICE LANDISK / USL-5P (a.k.a Julian) interrupt encoder + * + * Copyright (C) 2023 Yoshinori Sato + */ + +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define NUM_IRQ 16 + +struct julian_intc_priv { + struct device *dev; + void __iomem *base; + struct irq_domain *irq_domain; +}; + +static struct julian_intc_priv *irq_data_to_priv(struct irq_data *data) +{ + return data->domain->host_data; +} + +static void julian_intc_mask_irq(struct irq_data *data) +{ + struct julian_intc_priv *priv = irq_data_to_priv(data); + int irq = data->irq; + u8 mask; + + mask = __raw_readb(priv->base + 5); + mask &= ~(1 << (irq - 5)); + __raw_writeb(mask, priv->base + 5); +} + +static void julian_intc_unmask_irq(struct irq_data *data) +{ + struct julian_intc_priv *priv = irq_data_to_priv(data); + int irq = data->irq; + u8 mask; + + mask = __raw_readb(priv->base + 5); + mask |= (1 << (irq - 5)); + __raw_writeb(mask, priv->base + 5); +} + +static struct irq_chip julian_intc_chip = { + .name = "JULIAN-INTC", + .irq_unmask = julian_intc_unmask_irq, + .irq_mask = julian_intc_mask_irq, +}; + +static __init int julian_intc_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_and_handler(virq, &julian_intc_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 julian_intc_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, unsigned long *hwirq, + unsigned int *type) +{ + if (fwspec->param[0] >= NUM_IRQ) + return -EINVAL; + + switch (fwspec->param_count) { + case 2: + *type = fwspec->param[1]; + fallthrough; + case 1: + *hwirq = fwspec->param[0]; + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct irq_domain_ops julian_intc_domain_ops = { + .map = julian_intc_map, + .translate = julian_intc_translate, +}; + +static int julian_intc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct julian_intc_priv *priv; + struct irq_domain *d; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + d = irq_domain_add_tree(np, &julian_intc_domain_ops, priv); + if (d == NULL) { + dev_err(dev, "cannot initialize irq domain\n"); + kfree(priv); + ret = -ENOMEM; + } else { + priv->irq_domain = d; + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED); + } + return ret; +} + +static int julian_intc_remove(struct platform_device *pdev) +{ + struct julian_intc_priv *priv = platform_get_drvdata(pdev); + + irq_domain_remove(priv->irq_domain); + return 0; +} + +static const struct of_device_id julian_intc_dt_ids[] = { + { .compatible = "iodata,julian-intc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, julian_intc_dt_ids); + +static struct platform_driver julian_intc_device_driver = { + .probe = julian_intc_probe, + .remove = julian_intc_remove, + .driver = { + .name = "iodata_julian_intc", + .of_match_table = julian_intc_dt_ids, + } +}; + +static int __init julian_intc_init(void) +{ + return platform_driver_register(&julian_intc_device_driver); +} +postcore_initcall(julian_intc_init); + +static void __exit julian_intc_exit(void) +{ + platform_driver_unregister(&julian_intc_device_driver); +} +module_exit(julian_intc_exit); + +MODULE_AUTHOR("Yoshinori Sato"); +MODULE_DESCRIPTION("IO-DATA JULIAN external interrupt encoder"); diff --git a/drivers/irqchip/irq-renesas-r2d.c b/drivers/irqchip/irq-renesas-r2d.c new file mode 100644 index 000000000000..595057c1f5aa --- /dev/null +++ b/drivers/irqchip/irq-renesas-r2d.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RTS7751R2D interrupt encoder + * + * Copyright (C) 2023 Yoshinori Sato + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irqdomain.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define NUM_IRQ 15 + +struct r2d_intc_priv { + struct device *dev; + void __iomem *base; + struct irq_domain *irq_domain; +}; + +static const int imask[] = { + 1 << 11, /* PCI INTD */ + 1 << 9, /* CF IDE */ + 1 << 8, /* CF CD */ + 1 << 12, /* PCI INTC */ + 1 << 10, /* SM501 */ + 1 << 6, /* AX88796 / KEY */ + 1 << 5, /* RTC ALARM */ + 1 << 4, /* RTC T */ + 1 << 7, /* SDCARD */ + 1 << 14, /* PCI INTA */ + 1 << 13, /* PCI INTB */ + 1 << 0, /* EXT */ + 1 << 15, /* TP */ +}; + +static struct r2d_intc_priv *irq_data_to_priv(struct irq_data *data) +{ + return data->domain->host_data; +} + +static void r2d_intc_mask_irq(struct irq_data *data) +{ + struct r2d_intc_priv *priv = irq_data_to_priv(data); + u16 mask; + + mask = __raw_readw(priv->base); + mask &= ~(imask[data->irq]); + __raw_writew(mask, priv->base); +} + +static void r2d_intc_unmask_irq(struct irq_data *data) +{ + struct r2d_intc_priv *priv = irq_data_to_priv(data); + u16 mask; + + mask = __raw_readw(priv->base); + mask |= imask[data->irq]; + __raw_writew(mask, priv->base); +} + +static struct irq_chip r2d_intc_chip = { + .name = "R2D-INTC", + .irq_unmask = r2d_intc_unmask_irq, + .irq_mask = r2d_intc_mask_irq, +}; + +static __init int r2d_intc_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_and_handler(virq, &r2d_intc_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 r2d_intc_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, unsigned long *hwirq, + unsigned int *type) +{ + if (fwspec->param[0] >= NUM_IRQ) + return -EINVAL; + + switch (fwspec->param_count) { + case 2: + *type = fwspec->param[1]; + fallthrough; + case 1: + *hwirq = fwspec->param[0]; + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct irq_domain_ops r2d_intc_domain_ops = { +// .alloc = r2d_intc_alloc, + .map = r2d_intc_map, + .translate = r2d_intc_translate, +}; + +static int r2d_intc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct r2d_intc_priv *priv; + struct irq_domain *d; + int ret = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + d = irq_domain_add_tree(np, &r2d_intc_domain_ops, priv); + if (d == NULL) { + dev_err(dev, "cannot initialize irq domain\n"); + kfree(priv); + ret = -ENOMEM; + } else { + priv->irq_domain = d; + irq_domain_update_bus_token(d, DOMAIN_BUS_WIRED); + } + return ret; +} + +static int r2d_intc_remove(struct platform_device *pdev) +{ + struct r2d_intc_priv *priv = platform_get_drvdata(pdev); + + irq_domain_remove(priv->irq_domain); + return 0; +} + +static const struct of_device_id r2d_intc_dt_ids[] = { + { .compatible = "renesas,rts7751r2d-intc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, r2d_intc_dt_ids); + +static struct platform_driver r2d_intc_device_driver = { + .probe = r2d_intc_probe, + .remove = r2d_intc_remove, + .driver = { + .name = "renesas_r2d_intc", + .of_match_table = r2d_intc_dt_ids, + } +}; + +static int __init r2d_intc_init(void) +{ + return platform_driver_register(&r2d_intc_device_driver); +} +postcore_initcall(r2d_intc_init); + +static void __exit r2d_intc_exit(void) +{ + platform_driver_unregister(&r2d_intc_device_driver); +} +module_exit(r2d_intc_exit); + +MODULE_AUTHOR("Yoshinori Sato"); +MODULE_DESCRIPTION("Renesas RTS7751R2D external interrupt encoder"); diff --git a/drivers/irqchip/irq-renesas-sh7751.c b/drivers/irqchip/irq-renesas-sh7751.c new file mode 100644 index 000000000000..1c4b2be44c27 --- /dev/null +++ b/drivers/irqchip/irq-renesas-sh7751.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas SH7751 interrupt controller driver + * + * Copyright 2023 Yoshinori Sato <[email protected]> + */ + +#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> + +static struct sh7751_intc_regs { + void *icr; + void *ipr; + void *intpri00; + void *intreq00; + void *intmsk00; + void *intmskclr00; +} sh7751_regs; + +#define ICR_IRLM (1 << 7) + +static const unsigned int ipr_table[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0 - 7 */ + 0x41, 0xff, 0xff, 0x40, 0xff, 0xff, 0xff, 0xff, /* 8 - 15 */ + 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x11, /* 16 - 23 */ + 0x11, 0x11, 0x11, 0x13, 0x12, 0x12, 0xff, 0xff, /* 24 - 31 */ + 0x30, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x21, /* 32 - 39 */ + 0x21, 0x21, 0x21, 0x21, 0x32, 0x32, 0x32, 0x32, /* 40 - 47 */ + 0xff, 0xff, 0xff, 0x40, 0xff, 0xff, 0xff, 0xff, /* 48 - 55 */ + 0xff, 0xff, 0xff, 0x40, 0xff, 0xff, 0xff, 0xff, /* 56 - 63 */ +}; + +static const unsigned int pri_table[] = { + 0, 4, 4, 4, 4, 4, 4, 4, + 8, 32, 32, 32, 12, 32, 32, 32, +}; + +/* Bitmap of IRQ masked */ +#define IMASK_PRIORITY 15 + +static DECLARE_BITMAP(imask_mask, IMASK_PRIORITY); +static int interrupt_priority; + +static inline void set_interrupt_registers(int ip) +{ + unsigned long __dummy; + + asm volatile( +#ifdef CONFIG_CPU_HAS_SR_RB + "ldc %2, r6_bank\n\t" +#endif + "stc sr, %0\n\t" + "and #0xf0, %0\n\t" + "shlr2 %0\n\t" + "cmp/eq #0x3c, %0\n\t" + "bt/s 1f ! CLI-ed\n\t" + " stc sr, %0\n\t" + "and %1, %0\n\t" + "or %2, %0\n\t" + "ldc %0, sr\n" + "1:" + : "=&z" (__dummy) + : "r" (~0xf0), "r" (ip << 4) + : "t"); +} + +static void update_ipr(struct sh7751_intc_regs *reg, int irq, int on) +{ + unsigned int addr; + int pos; + uint16_t pri; + + if (irq < 64) { + if (ipr_table[irq] != 0xff) { + addr = (ipr_table[irq] & 0xf0) >> 2; + pos = (ipr_table[irq] & 0x0f) * 4; + pri = ~(0x000f << pos); + pri &= __raw_readw(reg->ipr + addr); + if (on) + pri |= 1 << pos; + __raw_writew(pri, reg->ipr + addr); + } + } else { + if (pri_table[irq - 64] < 32) { + pos = pri_table[irq - 64]; + pri = ~(0x000f << pos); + pri &= __raw_readw(reg->intpri00); + if (on) + pri |= 1 << pos; + __raw_writew(pri, reg->intpri00); + } + } +} + +static void sh7751_disable_irq(struct irq_data *data) +{ + unsigned int irq = data->irq; + struct sh7751_intc_regs *reg = data->chip_data; + uint16_t icr = __raw_readw(reg->icr); + + if (irq < 16 && (icr & ICR_IRLM) == 0) { + clear_bit(irq, imask_mask); + if (interrupt_priority < IMASK_PRIORITY - irq) + interrupt_priority = IMASK_PRIORITY - irq; + set_interrupt_registers(interrupt_priority); + } else + update_ipr(reg, irq, 0); +} + +static void sh7751_enable_irq(struct irq_data *data) +{ + unsigned int irq = data->irq; + struct sh7751_intc_regs *reg = data->chip_data; + uint16_t icr = __raw_readw(reg->icr); + + if (irq < 16 && (icr & ICR_IRLM) == 0) { + set_bit(irq, imask_mask); + interrupt_priority = IMASK_PRIORITY - + find_first_bit(imask_mask, IMASK_PRIORITY); + set_interrupt_registers(interrupt_priority); + } else + update_ipr(reg, irq, 1); +} + +struct irq_chip sh7751_irq_chip = { + .name = "SH7751-INTC", + .irq_unmask = sh7751_enable_irq, + .irq_mask = sh7751_disable_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 const struct irq_domain_ops irq_ops = { + .map = irq_sh7751_map, + .xlate = irq_domain_xlate_onecell, +}; + +static int __init sh7751_intc_of_init(struct device_node *intc, + struct device_node *parent) +{ + unsigned short icr; + struct irq_domain *domain; + void *intc_baseaddr; + void *intc_baseaddr2; + + intc_baseaddr = of_iomap(intc, 0); + intc_baseaddr2 = of_iomap(intc, 1); + if (!intc_baseaddr) { + pr_err("Invalid INTC address\n"); + return -EINVAL; + } + + sh7751_regs.icr = intc_baseaddr; + sh7751_regs.ipr = intc_baseaddr + 4; + sh7751_regs.intpri00 = intc_baseaddr2; + sh7751_regs.intreq00 = intc_baseaddr2 + 0x20; + sh7751_regs.intmsk00 = intc_baseaddr2 + 0x40; + sh7751_regs.intmskclr00 = intc_baseaddr2 + 0x60; + + if (of_property_read_bool(intc, "sh7751,irlm")) { + icr = __raw_readw(sh7751_regs.icr); + icr |= 0x80; // IRLM = 1 + __raw_writew(icr, sh7751_regs.icr); + } + + domain = irq_domain_add_linear(intc, NR_IRQS, &irq_ops, &sh7751_regs); + if (!domain) { + pr_err("Unable to allocate domain handle\n"); + return -ENOMEM; + } + irq_set_default_host(domain); + return 0; +} + +IRQCHIP_DECLARE(sh_7751_intc, + "renesas,sh7751-intc", sh7751_intc_of_init); -- 2.39.2