Message ID | 20240430083730.134918-13-herve.codina@bootlin.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add support for the LAN966x PCI device using a DT overlay | expand |
On Tue, Apr 30, 2024 at 10:37:21AM +0200, Herve Codina wrote: > The Microchip LAN966x outband interrupt controller (OIC) maps the > internal interrupt sources of the LAN966x device to an external > interrupt. > When the LAN966x device is used as a PCI device, the external interrupt > is routed to the PCI interrupt. > > Signed-off-by: Herve Codina <herve.codina@bootlin.com> Hi Herve, > +static int lan966x_oic_probe(struct platform_device *pdev) > +{ > + struct device_node *node = pdev->dev.of_node; > + struct lan966x_oic_data *lan966x_oic; > + struct device *dev = &pdev->dev; > + struct irq_chip_generic *gc; > + int ret; > + int i; > + > + lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); > + if (!lan966x_oic) > + return -ENOMEM; > + > + lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(lan966x_oic->regs)) > + return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), > + "failed to map resource\n"); > + > + lan966x_oic->domain = irq_domain_alloc_linear(of_node_to_fwnode(node), > + LAN966X_OIC_NR_IRQ, > + &irq_generic_chip_ops, NULL); nit: Please consider limiting lines to 80 columns wide in Networking code. > + if (!lan966x_oic->domain) { > + dev_err(dev, "failed to create an IRQ domain\n"); > + return -EINVAL; > + } > + > + lan966x_oic->irq = platform_get_irq(pdev, 0); > + if (lan966x_oic->irq < 0) { > + dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); > + goto err_domain_free; Hi, This will result in the function returning ret. However, ret is uninitialised here. Flagged by W=1 builds with clang-18, and Smatch. > + } > + > + ret = irq_alloc_domain_generic_chips(lan966x_oic->domain, 32, 1, "lan966x-oic", > + handle_level_irq, 0, 0, 0); > + if (ret) { > + dev_err_probe(dev, ret, "failed to alloc irq domain gc\n"); > + goto err_domain_free; > + } > + > + /* Init chips */ > + BUILD_BUG_ON(DIV_ROUND_UP(LAN966X_OIC_NR_IRQ, 32) != ARRAY_SIZE(lan966x_oic_chip_regs)); > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + lan966x_oic_chip_init(lan966x_oic, gc, &lan966x_oic_chip_regs[i]); > + } > + > + irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, > + lan966x_oic->domain); > + > + irq_domain_publish(lan966x_oic->domain); > + platform_set_drvdata(pdev, lan966x_oic); > + return 0; > + > +err_domain_free: > + irq_domain_free(lan966x_oic->domain); > + return ret; > +} > + > +static void lan966x_oic_remove(struct platform_device *pdev) > +{ > + struct lan966x_oic_data *lan966x_oic = platform_get_drvdata(pdev); > + struct irq_chip_generic *gc; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + lan966x_oic_chip_exit(gc); > + } > + > + irq_set_chained_handler_and_data(lan966x_oic->irq, NULL, NULL); > + > + for (i = 0; i < LAN966X_OIC_NR_IRQ; i++) > + irq_dispose_mapping(irq_find_mapping(lan966x_oic->domain, i)); > + > + irq_domain_unpublish(lan966x_oic->domain); > + > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + irq_remove_generic_chip(gc, ~0, 0, 0); > + } > + > + kfree(lan966x_oic->domain->gc); > + irq_domain_free(lan966x_oic->domain); > +} ...
Hi Herve, kernel test robot noticed the following build warnings: [auto build test WARNING on tip/irq/core] [also build test WARNING on linus/master v6.9-rc6 next-20240430] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Herve-Codina/mfd-syscon-Add-reference-counting-and-device-managed-support/20240430-164912 base: tip/irq/core patch link: https://lore.kernel.org/r/20240430083730.134918-13-herve.codina%40bootlin.com patch subject: [PATCH 12/17] irqchip: Add support for LAN966x OIC config: hexagon-randconfig-r071-20240501 (https://download.01.org/0day-ci/archive/20240501/202405010842.zim8X3E5-lkp@intel.com/config) compiler: clang version 15.0.7 (https://github.com/llvm/llvm-project.git 8dfdcc7b7bf66834a761bd8de445840ef68e4d1a) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240501/202405010842.zim8X3E5-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202405010842.zim8X3E5-lkp@intel.com/ All warnings (new ones prefixed by >>): In file included from drivers/irqchip/irq-lan966x-oic.c:15: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:13: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:547:31: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] val = __raw_readb(PCI_IOBASE + addr); ~~~~~~~~~~ ^ include/asm-generic/io.h:560:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] val = __le16_to_cpu((__le16 __force)__raw_readw(PCI_IOBASE + addr)); ~~~~~~~~~~ ^ include/uapi/linux/byteorder/little_endian.h:37:51: note: expanded from macro '__le16_to_cpu' #define __le16_to_cpu(x) ((__force __u16)(__le16)(x)) ^ In file included from drivers/irqchip/irq-lan966x-oic.c:15: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:13: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:573:61: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] val = __le32_to_cpu((__le32 __force)__raw_readl(PCI_IOBASE + addr)); ~~~~~~~~~~ ^ include/uapi/linux/byteorder/little_endian.h:35:51: note: expanded from macro '__le32_to_cpu' #define __le32_to_cpu(x) ((__force __u32)(__le32)(x)) ^ In file included from drivers/irqchip/irq-lan966x-oic.c:15: In file included from include/linux/interrupt.h:11: In file included from include/linux/hardirq.h:11: In file included from ./arch/hexagon/include/generated/asm/hardirq.h:1: In file included from include/asm-generic/hardirq.h:17: In file included from include/linux/irq.h:20: In file included from include/linux/io.h:13: In file included from arch/hexagon/include/asm/io.h:328: include/asm-generic/io.h:584:33: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] __raw_writeb(value, PCI_IOBASE + addr); ~~~~~~~~~~ ^ include/asm-generic/io.h:594:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] __raw_writew((u16 __force)cpu_to_le16(value), PCI_IOBASE + addr); ~~~~~~~~~~ ^ include/asm-generic/io.h:604:59: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic] __raw_writel((u32 __force)cpu_to_le32(value), PCI_IOBASE + addr); ~~~~~~~~~~ ^ >> drivers/irqchip/irq-lan966x-oic.c:225:6: warning: variable 'ret' is used uninitialized whenever 'if' condition is true [-Wsometimes-uninitialized] if (lan966x_oic->irq < 0) { ^~~~~~~~~~~~~~~~~~~~ drivers/irqchip/irq-lan966x-oic.c:253:9: note: uninitialized use occurs here return ret; ^~~ drivers/irqchip/irq-lan966x-oic.c:225:2: note: remove the 'if' if its condition is always false if (lan966x_oic->irq < 0) { ^~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/irqchip/irq-lan966x-oic.c:204:9: note: initialize the variable 'ret' to silence this warning int ret; ^ = 0 7 warnings generated. vim +225 drivers/irqchip/irq-lan966x-oic.c 197 198 static int lan966x_oic_probe(struct platform_device *pdev) 199 { 200 struct device_node *node = pdev->dev.of_node; 201 struct lan966x_oic_data *lan966x_oic; 202 struct device *dev = &pdev->dev; 203 struct irq_chip_generic *gc; 204 int ret; 205 int i; 206 207 lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); 208 if (!lan966x_oic) 209 return -ENOMEM; 210 211 lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); 212 if (IS_ERR(lan966x_oic->regs)) 213 return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), 214 "failed to map resource\n"); 215 216 lan966x_oic->domain = irq_domain_alloc_linear(of_node_to_fwnode(node), 217 LAN966X_OIC_NR_IRQ, 218 &irq_generic_chip_ops, NULL); 219 if (!lan966x_oic->domain) { 220 dev_err(dev, "failed to create an IRQ domain\n"); 221 return -EINVAL; 222 } 223 224 lan966x_oic->irq = platform_get_irq(pdev, 0); > 225 if (lan966x_oic->irq < 0) { 226 dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); 227 goto err_domain_free; 228 } 229 230 ret = irq_alloc_domain_generic_chips(lan966x_oic->domain, 32, 1, "lan966x-oic", 231 handle_level_irq, 0, 0, 0); 232 if (ret) { 233 dev_err_probe(dev, ret, "failed to alloc irq domain gc\n"); 234 goto err_domain_free; 235 } 236 237 /* Init chips */ 238 BUILD_BUG_ON(DIV_ROUND_UP(LAN966X_OIC_NR_IRQ, 32) != ARRAY_SIZE(lan966x_oic_chip_regs)); 239 for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { 240 gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); 241 lan966x_oic_chip_init(lan966x_oic, gc, &lan966x_oic_chip_regs[i]); 242 } 243 244 irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, 245 lan966x_oic->domain); 246 247 irq_domain_publish(lan966x_oic->domain); 248 platform_set_drvdata(pdev, lan966x_oic); 249 return 0; 250 251 err_domain_free: 252 irq_domain_free(lan966x_oic->domain); 253 return ret; 254 } 255
Hi Simon, On Tue, 30 Apr 2024 21:24:51 +0100 Simon Horman <horms@kernel.org> wrote: > On Tue, Apr 30, 2024 at 10:37:21AM +0200, Herve Codina wrote: > > The Microchip LAN966x outband interrupt controller (OIC) maps the > > internal interrupt sources of the LAN966x device to an external > > interrupt. > > When the LAN966x device is used as a PCI device, the external interrupt > > is routed to the PCI interrupt. > > > > Signed-off-by: Herve Codina <herve.codina@bootlin.com> > > Hi Herve, > > > +static int lan966x_oic_probe(struct platform_device *pdev) > > +{ > > + struct device_node *node = pdev->dev.of_node; > > + struct lan966x_oic_data *lan966x_oic; > > + struct device *dev = &pdev->dev; > > + struct irq_chip_generic *gc; > > + int ret; > > + int i; > > + > > + lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); > > + if (!lan966x_oic) > > + return -ENOMEM; > > + > > + lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(lan966x_oic->regs)) > > + return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), > > + "failed to map resource\n"); > > + > > + lan966x_oic->domain = irq_domain_alloc_linear(of_node_to_fwnode(node), > > + LAN966X_OIC_NR_IRQ, > > + &irq_generic_chip_ops, NULL); > > nit: Please consider limiting lines to 80 columns wide in Networking code. This will be done in the next iteration. > > > + if (!lan966x_oic->domain) { > > + dev_err(dev, "failed to create an IRQ domain\n"); > > + return -EINVAL; > > + } > > + > > + lan966x_oic->irq = platform_get_irq(pdev, 0); > > + if (lan966x_oic->irq < 0) { > > + dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); > > + goto err_domain_free; > > Hi, > > This will result in the function returning ret. > However, ret is uninitialised here. > > Flagged by W=1 builds with clang-18, and Smatch. Indeed, this fill be fixed in the next iteration. Best regards, Hervé
Hi Herve, On Tue Apr 30, 2024 at 10:37 AM CEST, Herve Codina wrote: > EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe > > The Microchip LAN966x outband interrupt controller (OIC) maps the > internal interrupt sources of the LAN966x device to an external > interrupt. > When the LAN966x device is used as a PCI device, the external interrupt > is routed to the PCI interrupt. > > Signed-off-by: Herve Codina <herve.codina@bootlin.com> > --- > drivers/irqchip/Kconfig | 12 ++ > drivers/irqchip/Makefile | 1 + > drivers/irqchip/irq-lan966x-oic.c | 301 ++++++++++++++++++++++++++++++ > 3 files changed, 314 insertions(+) > create mode 100644 drivers/irqchip/irq-lan966x-oic.c > > diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig > index 72c07a12f5e1..973eebc8d1d1 100644 > --- a/drivers/irqchip/Kconfig > +++ b/drivers/irqchip/Kconfig > @@ -169,6 +169,18 @@ config IXP4XX_IRQ > select IRQ_DOMAIN > select SPARSE_IRQ > > +config LAN966X_OIC > + tristate "Microchip LAN966x OIC Support" > + select GENERIC_IRQ_CHIP > + select IRQ_DOMAIN > + help > + Enable support for the LAN966x Outbound Interrupt Controller. > + This controller is present on the Microchip LAN966x PCI device and > + maps the internal interrupts sources to PCIe interrupt. > + > + To compile this driver as a module, choose M here: the module > + will be called irq-lan966x-oic. > + > config MADERA_IRQ > tristate > > diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile > index ec4a18380998..762d3078aa3b 100644 > --- a/drivers/irqchip/Makefile > +++ b/drivers/irqchip/Makefile > @@ -101,6 +101,7 @@ obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o > obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o > obj-$(CONFIG_IMX_MU_MSI) += irq-imx-mu-msi.o > obj-$(CONFIG_MADERA_IRQ) += irq-madera.o > +obj-$(CONFIG_LAN966X_OIC) += irq-lan966x-oic.o > obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o > obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o > obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o > diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c > new file mode 100644 > index 000000000000..342f0dbf16e3 > --- /dev/null > +++ b/drivers/irqchip/irq-lan966x-oic.c > @@ -0,0 +1,301 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Driver for the Microchip LAN966x outbound interrupt controller > + * > + * Copyright (c) 2024 Technology Inc. and its subsidiaries. > + * > + * Authors: > + * Horatiu Vultur <horatiu.vultur@microchip.com> > + * Clément Léger <clement.leger@bootlin.com> > + * Herve Codina <herve.codina@bootlin.com> > + */ > + > +#include <linux/bitops.h> > +#include <linux/build_bug.h> > +#include <linux/interrupt.h> > +#include <linux/irqchip/chained_irq.h> > +#include <linux/irqchip.h> > +#include <linux/irq.h> > +#include <linux/iopoll.h> > +#include <linux/mfd/core.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/delay.h> > + > +struct lan966x_oic_chip_regs { > + int reg_off_ena_set; > + int reg_off_ena_clr; > + int reg_off_sticky; > + int reg_off_ident; > + int reg_off_map; > +}; > + > +struct lan966x_oic_data { > + struct irq_domain *domain; > + void __iomem *regs; > + int irq; > +}; > + > +#define LAN966X_OIC_NR_IRQ 86 > + > +/* Interrupt sticky status */ > +#define LAN966X_OIC_INTR_STICKY 0x30 > +#define LAN966X_OIC_INTR_STICKY1 0x34 > +#define LAN966X_OIC_INTR_STICKY2 0x38 > + > +/* Interrupt enable */ > +#define LAN966X_OIC_INTR_ENA 0x48 > +#define LAN966X_OIC_INTR_ENA1 0x4c > +#define LAN966X_OIC_INTR_ENA2 0x50 > + > +/* Atomic clear of interrupt enable */ > +#define LAN966X_OIC_INTR_ENA_CLR 0x54 > +#define LAN966X_OIC_INTR_ENA_CLR1 0x58 > +#define LAN966X_OIC_INTR_ENA_CLR2 0x5c > + > +/* Atomic set of interrupt */ > +#define LAN966X_OIC_INTR_ENA_SET 0x60 > +#define LAN966X_OIC_INTR_ENA_SET1 0x64 > +#define LAN966X_OIC_INTR_ENA_SET2 0x68 > + > +/* Mapping of source to destination interrupts (_n = 0..8) */ Are the indices really needed on LAN966X_OIC_DST_INTR_MAP* and _IDENT* You do not appear to be using them? > +#define LAN966X_OIC_DST_INTR_MAP(_n) 0x78 > +#define LAN966X_OIC_DST_INTR_MAP1(_n) 0x9c > +#define LAN966X_OIC_DST_INTR_MAP2(_n) 0xc0 > + > +/* Currently active interrupt sources per destination (_n = 0..8) */ > +#define LAN966X_OIC_DST_INTR_IDENT(_n) 0xe4 > +#define LAN966X_OIC_DST_INTR_IDENT1(_n) 0x108 > +#define LAN966X_OIC_DST_INTR_IDENT2(_n) 0x12c > + > +static unsigned int lan966x_oic_irq_startup(struct irq_data *data) > +{ > + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); > + struct irq_chip_type *ct = irq_data_get_chip_type(data); > + struct lan966x_oic_chip_regs *chip_regs = gc->private; > + u32 map; > + > + irq_gc_lock(gc); > + > + /* Map the source interrupt to the destination */ > + map = irq_reg_readl(gc, chip_regs->reg_off_map); > + map |= data->mask; > + irq_reg_writel(gc, map, chip_regs->reg_off_map); > + > + irq_gc_unlock(gc); > + > + ct->chip.irq_ack(data); > + ct->chip.irq_unmask(data); > + > + return 0; > +} > + > +static void lan966x_oic_irq_shutdown(struct irq_data *data) > +{ > + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); > + struct irq_chip_type *ct = irq_data_get_chip_type(data); > + struct lan966x_oic_chip_regs *chip_regs = gc->private; > + u32 map; > + > + ct->chip.irq_mask(data); > + > + irq_gc_lock(gc); > + > + /* Unmap the interrupt */ > + map = irq_reg_readl(gc, chip_regs->reg_off_map); > + map &= ~data->mask; > + irq_reg_writel(gc, map, chip_regs->reg_off_map); > + > + irq_gc_unlock(gc); > +} > + > +static int lan966x_oic_irq_set_type(struct irq_data *data, unsigned int flow_type) > +{ > + if (flow_type != IRQ_TYPE_LEVEL_HIGH) { > + pr_err("lan966x oic doesn't support flow type %d\n", flow_type); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static void lan966x_oic_irq_handler_domain(struct irq_domain *d, u32 first_irq) > +{ > + struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, first_irq); > + struct lan966x_oic_chip_regs *chip_regs = gc->private; > + unsigned long ident; > + unsigned int hwirq; > + > + ident = irq_reg_readl(gc, chip_regs->reg_off_ident); > + if (!ident) > + return; > + > + for_each_set_bit(hwirq, &ident, 32) > + generic_handle_domain_irq(d, hwirq + first_irq); > +} > + > +static void lan966x_oic_irq_handler(struct irq_desc *desc) > +{ > + struct irq_domain *d = irq_desc_get_handler_data(desc); > + struct irq_chip *chip = irq_desc_get_chip(desc); > + > + chained_irq_enter(chip, desc); > + lan966x_oic_irq_handler_domain(d, 0); > + lan966x_oic_irq_handler_domain(d, 32); > + lan966x_oic_irq_handler_domain(d, 64); > + chained_irq_exit(chip, desc); > +} > + > +static struct lan966x_oic_chip_regs lan966x_oic_chip_regs[3] = { > + { > + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET, > + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR, > + .reg_off_sticky = LAN966X_OIC_INTR_STICKY, > + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT(0), > + .reg_off_map = LAN966X_OIC_DST_INTR_MAP(0), > + }, { > + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET1, > + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR1, > + .reg_off_sticky = LAN966X_OIC_INTR_STICKY1, > + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT1(0), > + .reg_off_map = LAN966X_OIC_DST_INTR_MAP1(0), > + }, { > + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET2, > + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR2, > + .reg_off_sticky = LAN966X_OIC_INTR_STICKY2, > + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT2(0), > + .reg_off_map = LAN966X_OIC_DST_INTR_MAP2(0), > + } > +}; > + > +static void lan966x_oic_chip_init(struct lan966x_oic_data *lan966x_oic, > + struct irq_chip_generic *gc, > + struct lan966x_oic_chip_regs *chip_regs) > +{ > + gc->reg_base = lan966x_oic->regs; > + gc->chip_types[0].regs.enable = chip_regs->reg_off_ena_set; > + gc->chip_types[0].regs.disable = chip_regs->reg_off_ena_clr; > + gc->chip_types[0].regs.ack = chip_regs->reg_off_sticky; > + gc->chip_types[0].chip.irq_startup = lan966x_oic_irq_startup; > + gc->chip_types[0].chip.irq_shutdown = lan966x_oic_irq_shutdown; > + gc->chip_types[0].chip.irq_set_type = lan966x_oic_irq_set_type; > + gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; > + gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; > + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; > + gc->private = chip_regs; > + > + /* Disable all interrupts handled by this chip */ > + irq_reg_writel(gc, ~0, chip_regs->reg_off_ena_clr); > +} > + > +static void lan966x_oic_chip_exit(struct irq_chip_generic *gc) > +{ > + /* Disable and ack all interrupts handled by this chip */ > + irq_reg_writel(gc, ~0, gc->chip_types[0].regs.disable); > + irq_reg_writel(gc, ~0, gc->chip_types[0].regs.ack); > +} > + > +static int lan966x_oic_probe(struct platform_device *pdev) > +{ > + struct device_node *node = pdev->dev.of_node; > + struct lan966x_oic_data *lan966x_oic; > + struct device *dev = &pdev->dev; > + struct irq_chip_generic *gc; > + int ret; > + int i; > + > + lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); > + if (!lan966x_oic) > + return -ENOMEM; > + > + lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(lan966x_oic->regs)) > + return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), > + "failed to map resource\n"); > + > + lan966x_oic->domain = irq_domain_alloc_linear(of_node_to_fwnode(node), > + LAN966X_OIC_NR_IRQ, > + &irq_generic_chip_ops, NULL); > + if (!lan966x_oic->domain) { > + dev_err(dev, "failed to create an IRQ domain\n"); > + return -EINVAL; > + } > + > + lan966x_oic->irq = platform_get_irq(pdev, 0); > + if (lan966x_oic->irq < 0) { > + dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); > + goto err_domain_free; > + } > + > + ret = irq_alloc_domain_generic_chips(lan966x_oic->domain, 32, 1, "lan966x-oic", > + handle_level_irq, 0, 0, 0); > + if (ret) { > + dev_err_probe(dev, ret, "failed to alloc irq domain gc\n"); > + goto err_domain_free; > + } > + > + /* Init chips */ > + BUILD_BUG_ON(DIV_ROUND_UP(LAN966X_OIC_NR_IRQ, 32) != ARRAY_SIZE(lan966x_oic_chip_regs)); > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + lan966x_oic_chip_init(lan966x_oic, gc, &lan966x_oic_chip_regs[i]); > + } > + > + irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, > + lan966x_oic->domain); > + > + irq_domain_publish(lan966x_oic->domain); > + platform_set_drvdata(pdev, lan966x_oic); > + return 0; > + > +err_domain_free: > + irq_domain_free(lan966x_oic->domain); > + return ret; > +} > + > +static void lan966x_oic_remove(struct platform_device *pdev) > +{ > + struct lan966x_oic_data *lan966x_oic = platform_get_drvdata(pdev); > + struct irq_chip_generic *gc; > + int i; > + > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + lan966x_oic_chip_exit(gc); > + } > + > + irq_set_chained_handler_and_data(lan966x_oic->irq, NULL, NULL); > + > + for (i = 0; i < LAN966X_OIC_NR_IRQ; i++) > + irq_dispose_mapping(irq_find_mapping(lan966x_oic->domain, i)); > + > + irq_domain_unpublish(lan966x_oic->domain); > + > + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { > + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); > + irq_remove_generic_chip(gc, ~0, 0, 0); > + } > + > + kfree(lan966x_oic->domain->gc); > + irq_domain_free(lan966x_oic->domain); > +} > + > +static const struct of_device_id lan966x_oic_of_match[] = { > + { .compatible = "microchip,lan966x-oic" }, > + {} /* sentinel */ > +}; > +MODULE_DEVICE_TABLE(of, lan966x_oic_of_match); > + > +static struct platform_driver lan966x_oic_driver = { > + .probe = lan966x_oic_probe, > + .remove_new = lan966x_oic_remove, > + .driver = { > + .name = "lan966x-oic", > + .of_match_table = lan966x_oic_of_match, > + }, > +}; > +module_platform_driver(lan966x_oic_driver); > + > +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); > +MODULE_DESCRIPTION("Microchip LAN966x OIC driver"); > +MODULE_LICENSE("GPL"); > -- > 2.44.0 Best regards Steen
Hi Steen, On Wed, 8 May 2024 08:08:30 +0000 <Steen.Hegelund@microchip.com> wrote: ... > > +/* Mapping of source to destination interrupts (_n = 0..8) */ > > Are the indices really needed on LAN966X_OIC_DST_INTR_MAP* and _IDENT* > You do not appear to be using them? > > > > +#define LAN966X_OIC_DST_INTR_MAP(_n) 0x78 Indeed, I missed them. These registers are defined from 0 to 8 in the document: https://microchip-ung.github.io/lan9662_reginfo/reginfo_LAN9662.html?select=cpu,intr The code use only the indice 0. In the next iteration, I will keep indices and update the definition of registers like that: #define LAN966X_OIC_DST_INTR_MAP(_n) (0x78 + (_n) * 4) Best regards Hervé
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 72c07a12f5e1..973eebc8d1d1 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -169,6 +169,18 @@ config IXP4XX_IRQ select IRQ_DOMAIN select SPARSE_IRQ +config LAN966X_OIC + tristate "Microchip LAN966x OIC Support" + select GENERIC_IRQ_CHIP + select IRQ_DOMAIN + help + Enable support for the LAN966x Outbound Interrupt Controller. + This controller is present on the Microchip LAN966x PCI device and + maps the internal interrupts sources to PCIe interrupt. + + To compile this driver as a module, choose M here: the module + will be called irq-lan966x-oic. + config MADERA_IRQ tristate diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index ec4a18380998..762d3078aa3b 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -101,6 +101,7 @@ obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o obj-$(CONFIG_IMX_MU_MSI) += irq-imx-mu-msi.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o +obj-$(CONFIG_LAN966X_OIC) += irq-lan966x-oic.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c new file mode 100644 index 000000000000..342f0dbf16e3 --- /dev/null +++ b/drivers/irqchip/irq-lan966x-oic.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Microchip LAN966x outbound interrupt controller + * + * Copyright (c) 2024 Technology Inc. and its subsidiaries. + * + * Authors: + * Horatiu Vultur <horatiu.vultur@microchip.com> + * Clément Léger <clement.leger@bootlin.com> + * Herve Codina <herve.codina@bootlin.com> + */ + +#include <linux/bitops.h> +#include <linux/build_bug.h> +#include <linux/interrupt.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqchip.h> +#include <linux/irq.h> +#include <linux/iopoll.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> + +struct lan966x_oic_chip_regs { + int reg_off_ena_set; + int reg_off_ena_clr; + int reg_off_sticky; + int reg_off_ident; + int reg_off_map; +}; + +struct lan966x_oic_data { + struct irq_domain *domain; + void __iomem *regs; + int irq; +}; + +#define LAN966X_OIC_NR_IRQ 86 + +/* Interrupt sticky status */ +#define LAN966X_OIC_INTR_STICKY 0x30 +#define LAN966X_OIC_INTR_STICKY1 0x34 +#define LAN966X_OIC_INTR_STICKY2 0x38 + +/* Interrupt enable */ +#define LAN966X_OIC_INTR_ENA 0x48 +#define LAN966X_OIC_INTR_ENA1 0x4c +#define LAN966X_OIC_INTR_ENA2 0x50 + +/* Atomic clear of interrupt enable */ +#define LAN966X_OIC_INTR_ENA_CLR 0x54 +#define LAN966X_OIC_INTR_ENA_CLR1 0x58 +#define LAN966X_OIC_INTR_ENA_CLR2 0x5c + +/* Atomic set of interrupt */ +#define LAN966X_OIC_INTR_ENA_SET 0x60 +#define LAN966X_OIC_INTR_ENA_SET1 0x64 +#define LAN966X_OIC_INTR_ENA_SET2 0x68 + +/* Mapping of source to destination interrupts (_n = 0..8) */ +#define LAN966X_OIC_DST_INTR_MAP(_n) 0x78 +#define LAN966X_OIC_DST_INTR_MAP1(_n) 0x9c +#define LAN966X_OIC_DST_INTR_MAP2(_n) 0xc0 + +/* Currently active interrupt sources per destination (_n = 0..8) */ +#define LAN966X_OIC_DST_INTR_IDENT(_n) 0xe4 +#define LAN966X_OIC_DST_INTR_IDENT1(_n) 0x108 +#define LAN966X_OIC_DST_INTR_IDENT2(_n) 0x12c + +static unsigned int lan966x_oic_irq_startup(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct irq_chip_type *ct = irq_data_get_chip_type(data); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + u32 map; + + irq_gc_lock(gc); + + /* Map the source interrupt to the destination */ + map = irq_reg_readl(gc, chip_regs->reg_off_map); + map |= data->mask; + irq_reg_writel(gc, map, chip_regs->reg_off_map); + + irq_gc_unlock(gc); + + ct->chip.irq_ack(data); + ct->chip.irq_unmask(data); + + return 0; +} + +static void lan966x_oic_irq_shutdown(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct irq_chip_type *ct = irq_data_get_chip_type(data); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + u32 map; + + ct->chip.irq_mask(data); + + irq_gc_lock(gc); + + /* Unmap the interrupt */ + map = irq_reg_readl(gc, chip_regs->reg_off_map); + map &= ~data->mask; + irq_reg_writel(gc, map, chip_regs->reg_off_map); + + irq_gc_unlock(gc); +} + +static int lan966x_oic_irq_set_type(struct irq_data *data, unsigned int flow_type) +{ + if (flow_type != IRQ_TYPE_LEVEL_HIGH) { + pr_err("lan966x oic doesn't support flow type %d\n", flow_type); + return -EINVAL; + } + + return 0; +} + +static void lan966x_oic_irq_handler_domain(struct irq_domain *d, u32 first_irq) +{ + struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, first_irq); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + unsigned long ident; + unsigned int hwirq; + + ident = irq_reg_readl(gc, chip_regs->reg_off_ident); + if (!ident) + return; + + for_each_set_bit(hwirq, &ident, 32) + generic_handle_domain_irq(d, hwirq + first_irq); +} + +static void lan966x_oic_irq_handler(struct irq_desc *desc) +{ + struct irq_domain *d = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + lan966x_oic_irq_handler_domain(d, 0); + lan966x_oic_irq_handler_domain(d, 32); + lan966x_oic_irq_handler_domain(d, 64); + chained_irq_exit(chip, desc); +} + +static struct lan966x_oic_chip_regs lan966x_oic_chip_regs[3] = { + { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP(0), + }, { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET1, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR1, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY1, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT1(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP1(0), + }, { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET2, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR2, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY2, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT2(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP2(0), + } +}; + +static void lan966x_oic_chip_init(struct lan966x_oic_data *lan966x_oic, + struct irq_chip_generic *gc, + struct lan966x_oic_chip_regs *chip_regs) +{ + gc->reg_base = lan966x_oic->regs; + gc->chip_types[0].regs.enable = chip_regs->reg_off_ena_set; + gc->chip_types[0].regs.disable = chip_regs->reg_off_ena_clr; + gc->chip_types[0].regs.ack = chip_regs->reg_off_sticky; + gc->chip_types[0].chip.irq_startup = lan966x_oic_irq_startup; + gc->chip_types[0].chip.irq_shutdown = lan966x_oic_irq_shutdown; + gc->chip_types[0].chip.irq_set_type = lan966x_oic_irq_set_type; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; + gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; + gc->private = chip_regs; + + /* Disable all interrupts handled by this chip */ + irq_reg_writel(gc, ~0, chip_regs->reg_off_ena_clr); +} + +static void lan966x_oic_chip_exit(struct irq_chip_generic *gc) +{ + /* Disable and ack all interrupts handled by this chip */ + irq_reg_writel(gc, ~0, gc->chip_types[0].regs.disable); + irq_reg_writel(gc, ~0, gc->chip_types[0].regs.ack); +} + +static int lan966x_oic_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct lan966x_oic_data *lan966x_oic; + struct device *dev = &pdev->dev; + struct irq_chip_generic *gc; + int ret; + int i; + + lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); + if (!lan966x_oic) + return -ENOMEM; + + lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(lan966x_oic->regs)) + return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), + "failed to map resource\n"); + + lan966x_oic->domain = irq_domain_alloc_linear(of_node_to_fwnode(node), + LAN966X_OIC_NR_IRQ, + &irq_generic_chip_ops, NULL); + if (!lan966x_oic->domain) { + dev_err(dev, "failed to create an IRQ domain\n"); + return -EINVAL; + } + + lan966x_oic->irq = platform_get_irq(pdev, 0); + if (lan966x_oic->irq < 0) { + dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); + goto err_domain_free; + } + + ret = irq_alloc_domain_generic_chips(lan966x_oic->domain, 32, 1, "lan966x-oic", + handle_level_irq, 0, 0, 0); + if (ret) { + dev_err_probe(dev, ret, "failed to alloc irq domain gc\n"); + goto err_domain_free; + } + + /* Init chips */ + BUILD_BUG_ON(DIV_ROUND_UP(LAN966X_OIC_NR_IRQ, 32) != ARRAY_SIZE(lan966x_oic_chip_regs)); + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); + lan966x_oic_chip_init(lan966x_oic, gc, &lan966x_oic_chip_regs[i]); + } + + irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, + lan966x_oic->domain); + + irq_domain_publish(lan966x_oic->domain); + platform_set_drvdata(pdev, lan966x_oic); + return 0; + +err_domain_free: + irq_domain_free(lan966x_oic->domain); + return ret; +} + +static void lan966x_oic_remove(struct platform_device *pdev) +{ + struct lan966x_oic_data *lan966x_oic = platform_get_drvdata(pdev); + struct irq_chip_generic *gc; + int i; + + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); + lan966x_oic_chip_exit(gc); + } + + irq_set_chained_handler_and_data(lan966x_oic->irq, NULL, NULL); + + for (i = 0; i < LAN966X_OIC_NR_IRQ; i++) + irq_dispose_mapping(irq_find_mapping(lan966x_oic->domain, i)); + + irq_domain_unpublish(lan966x_oic->domain); + + for (i = 0; i < ARRAY_SIZE(lan966x_oic_chip_regs); i++) { + gc = irq_get_domain_generic_chip(lan966x_oic->domain, i * 32); + irq_remove_generic_chip(gc, ~0, 0, 0); + } + + kfree(lan966x_oic->domain->gc); + irq_domain_free(lan966x_oic->domain); +} + +static const struct of_device_id lan966x_oic_of_match[] = { + { .compatible = "microchip,lan966x-oic" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, lan966x_oic_of_match); + +static struct platform_driver lan966x_oic_driver = { + .probe = lan966x_oic_probe, + .remove_new = lan966x_oic_remove, + .driver = { + .name = "lan966x-oic", + .of_match_table = lan966x_oic_of_match, + }, +}; +module_platform_driver(lan966x_oic_driver); + +MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>"); +MODULE_DESCRIPTION("Microchip LAN966x OIC driver"); +MODULE_LICENSE("GPL");
The Microchip LAN966x outband interrupt controller (OIC) maps the internal interrupt sources of the LAN966x device to an external interrupt. When the LAN966x device is used as a PCI device, the external interrupt is routed to the PCI interrupt. Signed-off-by: Herve Codina <herve.codina@bootlin.com> --- drivers/irqchip/Kconfig | 12 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-lan966x-oic.c | 301 ++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 drivers/irqchip/irq-lan966x-oic.c