10136afa0SLucas Stach // SPDX-License-Identifier: GPL-2.0+ 20136afa0SLucas Stach /* 30136afa0SLucas Stach * Copyright 2017 NXP 40136afa0SLucas Stach * Copyright (C) 2018 Pengutronix, Lucas Stach <kernel@pengutronix.de> 50136afa0SLucas Stach */ 60136afa0SLucas Stach 70136afa0SLucas Stach #include <linux/clk.h> 80136afa0SLucas Stach #include <linux/interrupt.h> 90136afa0SLucas Stach #include <linux/irq.h> 100136afa0SLucas Stach #include <linux/irqchip/chained_irq.h> 110136afa0SLucas Stach #include <linux/irqdomain.h> 120136afa0SLucas Stach #include <linux/kernel.h> 13*28528fcaSAisheng Dong #include <linux/of_irq.h> 140136afa0SLucas Stach #include <linux/of_platform.h> 150136afa0SLucas Stach #include <linux/spinlock.h> 160136afa0SLucas Stach 17deb904e4SAisheng Dong #define CTRL_STRIDE_OFF(_t, _r) (_t * 4 * _r) 180136afa0SLucas Stach #define CHANCTRL 0x0 190136afa0SLucas Stach #define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4) 200136afa0SLucas Stach #define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4) 210136afa0SLucas Stach #define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4) 220136afa0SLucas Stach #define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4) 230136afa0SLucas Stach #define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8) 240136afa0SLucas Stach 25*28528fcaSAisheng Dong #define CHAN_MAX_OUTPUT_INT 0x8 26*28528fcaSAisheng Dong 270136afa0SLucas Stach struct irqsteer_data { 280136afa0SLucas Stach void __iomem *regs; 290136afa0SLucas Stach struct clk *ipg_clk; 30*28528fcaSAisheng Dong int irq[CHAN_MAX_OUTPUT_INT]; 31*28528fcaSAisheng Dong int irq_count; 320136afa0SLucas Stach raw_spinlock_t lock; 33deb904e4SAisheng Dong int reg_num; 340136afa0SLucas Stach int channel; 350136afa0SLucas Stach struct irq_domain *domain; 360136afa0SLucas Stach u32 *saved_reg; 370136afa0SLucas Stach }; 380136afa0SLucas Stach 390136afa0SLucas Stach static int imx_irqsteer_get_reg_index(struct irqsteer_data *data, 400136afa0SLucas Stach unsigned long irqnum) 410136afa0SLucas Stach { 42deb904e4SAisheng Dong return (data->reg_num - irqnum / 32 - 1); 430136afa0SLucas Stach } 440136afa0SLucas Stach 450136afa0SLucas Stach static void imx_irqsteer_irq_unmask(struct irq_data *d) 460136afa0SLucas Stach { 470136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 480136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 490136afa0SLucas Stach unsigned long flags; 500136afa0SLucas Stach u32 val; 510136afa0SLucas Stach 520136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 53deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 540136afa0SLucas Stach val |= BIT(d->hwirq % 32); 55deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 560136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 570136afa0SLucas Stach } 580136afa0SLucas Stach 590136afa0SLucas Stach static void imx_irqsteer_irq_mask(struct irq_data *d) 600136afa0SLucas Stach { 610136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 620136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 630136afa0SLucas Stach unsigned long flags; 640136afa0SLucas Stach u32 val; 650136afa0SLucas Stach 660136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 67deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 680136afa0SLucas Stach val &= ~BIT(d->hwirq % 32); 69deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 700136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 710136afa0SLucas Stach } 720136afa0SLucas Stach 730136afa0SLucas Stach static struct irq_chip imx_irqsteer_irq_chip = { 740136afa0SLucas Stach .name = "irqsteer", 750136afa0SLucas Stach .irq_mask = imx_irqsteer_irq_mask, 760136afa0SLucas Stach .irq_unmask = imx_irqsteer_irq_unmask, 770136afa0SLucas Stach }; 780136afa0SLucas Stach 790136afa0SLucas Stach static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq, 800136afa0SLucas Stach irq_hw_number_t hwirq) 810136afa0SLucas Stach { 820136afa0SLucas Stach irq_set_status_flags(irq, IRQ_LEVEL); 830136afa0SLucas Stach irq_set_chip_data(irq, h->host_data); 840136afa0SLucas Stach irq_set_chip_and_handler(irq, &imx_irqsteer_irq_chip, handle_level_irq); 850136afa0SLucas Stach 860136afa0SLucas Stach return 0; 870136afa0SLucas Stach } 880136afa0SLucas Stach 890136afa0SLucas Stach static const struct irq_domain_ops imx_irqsteer_domain_ops = { 900136afa0SLucas Stach .map = imx_irqsteer_irq_map, 910136afa0SLucas Stach .xlate = irq_domain_xlate_onecell, 920136afa0SLucas Stach }; 930136afa0SLucas Stach 94*28528fcaSAisheng Dong static int imx_irqsteer_get_hwirq_base(struct irqsteer_data *data, u32 irq) 95*28528fcaSAisheng Dong { 96*28528fcaSAisheng Dong int i; 97*28528fcaSAisheng Dong 98*28528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 99*28528fcaSAisheng Dong if (data->irq[i] == irq) 100*28528fcaSAisheng Dong return i * 64; 101*28528fcaSAisheng Dong } 102*28528fcaSAisheng Dong 103*28528fcaSAisheng Dong return -EINVAL; 104*28528fcaSAisheng Dong } 105*28528fcaSAisheng Dong 1060136afa0SLucas Stach static void imx_irqsteer_irq_handler(struct irq_desc *desc) 1070136afa0SLucas Stach { 1080136afa0SLucas Stach struct irqsteer_data *data = irq_desc_get_handler_data(desc); 109*28528fcaSAisheng Dong int hwirq; 110*28528fcaSAisheng Dong int irq, i; 1110136afa0SLucas Stach 1120136afa0SLucas Stach chained_irq_enter(irq_desc_get_chip(desc), desc); 1130136afa0SLucas Stach 114*28528fcaSAisheng Dong irq = irq_desc_get_irq(desc); 115*28528fcaSAisheng Dong hwirq = imx_irqsteer_get_hwirq_base(data, irq); 116*28528fcaSAisheng Dong if (hwirq < 0) { 117*28528fcaSAisheng Dong pr_warn("%s: unable to get hwirq base for irq %d\n", 118*28528fcaSAisheng Dong __func__, irq); 119*28528fcaSAisheng Dong return; 120*28528fcaSAisheng Dong } 121*28528fcaSAisheng Dong 122*28528fcaSAisheng Dong for (i = 0; i < 2; i++, hwirq += 32) { 123*28528fcaSAisheng Dong int idx = imx_irqsteer_get_reg_index(data, hwirq); 1240136afa0SLucas Stach unsigned long irqmap; 1250136afa0SLucas Stach int pos, virq; 1260136afa0SLucas Stach 127*28528fcaSAisheng Dong if (hwirq >= data->reg_num * 32) 128*28528fcaSAisheng Dong break; 129*28528fcaSAisheng Dong 1300136afa0SLucas Stach irqmap = readl_relaxed(data->regs + 131deb904e4SAisheng Dong CHANSTATUS(idx, data->reg_num)); 1320136afa0SLucas Stach 1330136afa0SLucas Stach for_each_set_bit(pos, &irqmap, 32) { 134*28528fcaSAisheng Dong virq = irq_find_mapping(data->domain, pos + hwirq); 1350136afa0SLucas Stach if (virq) 1360136afa0SLucas Stach generic_handle_irq(virq); 1370136afa0SLucas Stach } 1380136afa0SLucas Stach } 1390136afa0SLucas Stach 1400136afa0SLucas Stach chained_irq_exit(irq_desc_get_chip(desc), desc); 1410136afa0SLucas Stach } 1420136afa0SLucas Stach 1430136afa0SLucas Stach static int imx_irqsteer_probe(struct platform_device *pdev) 1440136afa0SLucas Stach { 1450136afa0SLucas Stach struct device_node *np = pdev->dev.of_node; 1460136afa0SLucas Stach struct irqsteer_data *data; 1470136afa0SLucas Stach struct resource *res; 148*28528fcaSAisheng Dong u32 irqs_num; 149*28528fcaSAisheng Dong int i, ret; 1500136afa0SLucas Stach 1510136afa0SLucas Stach data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1520136afa0SLucas Stach if (!data) 1530136afa0SLucas Stach return -ENOMEM; 1540136afa0SLucas Stach 1550136afa0SLucas Stach res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1560136afa0SLucas Stach data->regs = devm_ioremap_resource(&pdev->dev, res); 1570136afa0SLucas Stach if (IS_ERR(data->regs)) { 1580136afa0SLucas Stach dev_err(&pdev->dev, "failed to initialize reg\n"); 1590136afa0SLucas Stach return PTR_ERR(data->regs); 1600136afa0SLucas Stach } 1610136afa0SLucas Stach 1620136afa0SLucas Stach data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); 1630136afa0SLucas Stach if (IS_ERR(data->ipg_clk)) { 1640136afa0SLucas Stach ret = PTR_ERR(data->ipg_clk); 1650136afa0SLucas Stach if (ret != -EPROBE_DEFER) 1660136afa0SLucas Stach dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret); 1670136afa0SLucas Stach return ret; 1680136afa0SLucas Stach } 1690136afa0SLucas Stach 1700136afa0SLucas Stach raw_spin_lock_init(&data->lock); 1710136afa0SLucas Stach 172*28528fcaSAisheng Dong of_property_read_u32(np, "fsl,num-irqs", &irqs_num); 1730136afa0SLucas Stach of_property_read_u32(np, "fsl,channel", &data->channel); 1740136afa0SLucas Stach 175*28528fcaSAisheng Dong /* 176*28528fcaSAisheng Dong * There is one output irq for each group of 64 inputs. 177*28528fcaSAisheng Dong * One register bit map can represent 32 input interrupts. 178*28528fcaSAisheng Dong */ 179*28528fcaSAisheng Dong data->irq_count = DIV_ROUND_UP(irqs_num, 64); 180*28528fcaSAisheng Dong data->reg_num = irqs_num / 32; 181deb904e4SAisheng Dong 1820136afa0SLucas Stach if (IS_ENABLED(CONFIG_PM_SLEEP)) { 1830136afa0SLucas Stach data->saved_reg = devm_kzalloc(&pdev->dev, 184deb904e4SAisheng Dong sizeof(u32) * data->reg_num, 1850136afa0SLucas Stach GFP_KERNEL); 1860136afa0SLucas Stach if (!data->saved_reg) 1870136afa0SLucas Stach return -ENOMEM; 1880136afa0SLucas Stach } 1890136afa0SLucas Stach 1900136afa0SLucas Stach ret = clk_prepare_enable(data->ipg_clk); 1910136afa0SLucas Stach if (ret) { 1920136afa0SLucas Stach dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); 1930136afa0SLucas Stach return ret; 1940136afa0SLucas Stach } 1950136afa0SLucas Stach 1960136afa0SLucas Stach /* steer all IRQs into configured channel */ 1970136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 1980136afa0SLucas Stach 199deb904e4SAisheng Dong data->domain = irq_domain_add_linear(np, data->reg_num * 32, 2000136afa0SLucas Stach &imx_irqsteer_domain_ops, data); 2010136afa0SLucas Stach if (!data->domain) { 2020136afa0SLucas Stach dev_err(&pdev->dev, "failed to create IRQ domain\n"); 203*28528fcaSAisheng Dong ret = -ENOMEM; 204*28528fcaSAisheng Dong goto out; 2050136afa0SLucas Stach } 2060136afa0SLucas Stach 207*28528fcaSAisheng Dong if (!data->irq_count || data->irq_count > CHAN_MAX_OUTPUT_INT) { 208*28528fcaSAisheng Dong ret = -EINVAL; 209*28528fcaSAisheng Dong goto out; 210*28528fcaSAisheng Dong } 211*28528fcaSAisheng Dong 212*28528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 213*28528fcaSAisheng Dong data->irq[i] = irq_of_parse_and_map(np, i); 214*28528fcaSAisheng Dong if (!data->irq[i]) { 215*28528fcaSAisheng Dong ret = -EINVAL; 216*28528fcaSAisheng Dong goto out; 217*28528fcaSAisheng Dong } 218*28528fcaSAisheng Dong 219*28528fcaSAisheng Dong irq_set_chained_handler_and_data(data->irq[i], 220*28528fcaSAisheng Dong imx_irqsteer_irq_handler, 2210136afa0SLucas Stach data); 222*28528fcaSAisheng Dong } 2230136afa0SLucas Stach 2240136afa0SLucas Stach platform_set_drvdata(pdev, data); 2250136afa0SLucas Stach 2260136afa0SLucas Stach return 0; 227*28528fcaSAisheng Dong out: 228*28528fcaSAisheng Dong clk_disable_unprepare(data->ipg_clk); 229*28528fcaSAisheng Dong return ret; 2300136afa0SLucas Stach } 2310136afa0SLucas Stach 2320136afa0SLucas Stach static int imx_irqsteer_remove(struct platform_device *pdev) 2330136afa0SLucas Stach { 2340136afa0SLucas Stach struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); 235*28528fcaSAisheng Dong int i; 2360136afa0SLucas Stach 237*28528fcaSAisheng Dong for (i = 0; i < irqsteer_data->irq_count; i++) 238*28528fcaSAisheng Dong irq_set_chained_handler_and_data(irqsteer_data->irq[i], 239*28528fcaSAisheng Dong NULL, NULL); 240*28528fcaSAisheng Dong 2410136afa0SLucas Stach irq_domain_remove(irqsteer_data->domain); 2420136afa0SLucas Stach 2430136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2440136afa0SLucas Stach 2450136afa0SLucas Stach return 0; 2460136afa0SLucas Stach } 2470136afa0SLucas Stach 2480136afa0SLucas Stach #ifdef CONFIG_PM_SLEEP 2490136afa0SLucas Stach static void imx_irqsteer_save_regs(struct irqsteer_data *data) 2500136afa0SLucas Stach { 2510136afa0SLucas Stach int i; 2520136afa0SLucas Stach 253deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2540136afa0SLucas Stach data->saved_reg[i] = readl_relaxed(data->regs + 255deb904e4SAisheng Dong CHANMASK(i, data->reg_num)); 2560136afa0SLucas Stach } 2570136afa0SLucas Stach 2580136afa0SLucas Stach static void imx_irqsteer_restore_regs(struct irqsteer_data *data) 2590136afa0SLucas Stach { 2600136afa0SLucas Stach int i; 2610136afa0SLucas Stach 2620136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 263deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2640136afa0SLucas Stach writel_relaxed(data->saved_reg[i], 265deb904e4SAisheng Dong data->regs + CHANMASK(i, data->reg_num)); 2660136afa0SLucas Stach } 2670136afa0SLucas Stach 2680136afa0SLucas Stach static int imx_irqsteer_suspend(struct device *dev) 2690136afa0SLucas Stach { 2700136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2710136afa0SLucas Stach 2720136afa0SLucas Stach imx_irqsteer_save_regs(irqsteer_data); 2730136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2740136afa0SLucas Stach 2750136afa0SLucas Stach return 0; 2760136afa0SLucas Stach } 2770136afa0SLucas Stach 2780136afa0SLucas Stach static int imx_irqsteer_resume(struct device *dev) 2790136afa0SLucas Stach { 2800136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2810136afa0SLucas Stach int ret; 2820136afa0SLucas Stach 2830136afa0SLucas Stach ret = clk_prepare_enable(irqsteer_data->ipg_clk); 2840136afa0SLucas Stach if (ret) { 2850136afa0SLucas Stach dev_err(dev, "failed to enable ipg clk: %d\n", ret); 2860136afa0SLucas Stach return ret; 2870136afa0SLucas Stach } 2880136afa0SLucas Stach imx_irqsteer_restore_regs(irqsteer_data); 2890136afa0SLucas Stach 2900136afa0SLucas Stach return 0; 2910136afa0SLucas Stach } 2920136afa0SLucas Stach #endif 2930136afa0SLucas Stach 2940136afa0SLucas Stach static const struct dev_pm_ops imx_irqsteer_pm_ops = { 2950136afa0SLucas Stach SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_irqsteer_suspend, imx_irqsteer_resume) 2960136afa0SLucas Stach }; 2970136afa0SLucas Stach 2980136afa0SLucas Stach static const struct of_device_id imx_irqsteer_dt_ids[] = { 2990136afa0SLucas Stach { .compatible = "fsl,imx-irqsteer", }, 3000136afa0SLucas Stach {}, 3010136afa0SLucas Stach }; 3020136afa0SLucas Stach 3030136afa0SLucas Stach static struct platform_driver imx_irqsteer_driver = { 3040136afa0SLucas Stach .driver = { 3050136afa0SLucas Stach .name = "imx-irqsteer", 3060136afa0SLucas Stach .of_match_table = imx_irqsteer_dt_ids, 3070136afa0SLucas Stach .pm = &imx_irqsteer_pm_ops, 3080136afa0SLucas Stach }, 3090136afa0SLucas Stach .probe = imx_irqsteer_probe, 3100136afa0SLucas Stach .remove = imx_irqsteer_remove, 3110136afa0SLucas Stach }; 3120136afa0SLucas Stach builtin_platform_driver(imx_irqsteer_driver); 313