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> 1328528fcaSAisheng 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 2528528fcaSAisheng Dong #define CHAN_MAX_OUTPUT_INT 0x8 2628528fcaSAisheng Dong 270136afa0SLucas Stach struct irqsteer_data { 280136afa0SLucas Stach void __iomem *regs; 290136afa0SLucas Stach struct clk *ipg_clk; 3028528fcaSAisheng Dong int irq[CHAN_MAX_OUTPUT_INT]; 3128528fcaSAisheng 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 73*e9a50f12SLucas Stach static const 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 9428528fcaSAisheng Dong static int imx_irqsteer_get_hwirq_base(struct irqsteer_data *data, u32 irq) 9528528fcaSAisheng Dong { 9628528fcaSAisheng Dong int i; 9728528fcaSAisheng Dong 9828528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 9928528fcaSAisheng Dong if (data->irq[i] == irq) 10028528fcaSAisheng Dong return i * 64; 10128528fcaSAisheng Dong } 10228528fcaSAisheng Dong 10328528fcaSAisheng Dong return -EINVAL; 10428528fcaSAisheng Dong } 10528528fcaSAisheng 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); 10928528fcaSAisheng Dong int hwirq; 11028528fcaSAisheng Dong int irq, i; 1110136afa0SLucas Stach 1120136afa0SLucas Stach chained_irq_enter(irq_desc_get_chip(desc), desc); 1130136afa0SLucas Stach 11428528fcaSAisheng Dong irq = irq_desc_get_irq(desc); 11528528fcaSAisheng Dong hwirq = imx_irqsteer_get_hwirq_base(data, irq); 11628528fcaSAisheng Dong if (hwirq < 0) { 11728528fcaSAisheng Dong pr_warn("%s: unable to get hwirq base for irq %d\n", 11828528fcaSAisheng Dong __func__, irq); 11928528fcaSAisheng Dong return; 12028528fcaSAisheng Dong } 12128528fcaSAisheng Dong 12228528fcaSAisheng Dong for (i = 0; i < 2; i++, hwirq += 32) { 12328528fcaSAisheng Dong int idx = imx_irqsteer_get_reg_index(data, hwirq); 1240136afa0SLucas Stach unsigned long irqmap; 125046a6ee2SMarc Zyngier int pos; 1260136afa0SLucas Stach 12728528fcaSAisheng Dong if (hwirq >= data->reg_num * 32) 12828528fcaSAisheng Dong break; 12928528fcaSAisheng Dong 1300136afa0SLucas Stach irqmap = readl_relaxed(data->regs + 131deb904e4SAisheng Dong CHANSTATUS(idx, data->reg_num)); 1320136afa0SLucas Stach 133046a6ee2SMarc Zyngier for_each_set_bit(pos, &irqmap, 32) 134046a6ee2SMarc Zyngier generic_handle_domain_irq(data->domain, pos + hwirq); 1350136afa0SLucas Stach } 1360136afa0SLucas Stach 1370136afa0SLucas Stach chained_irq_exit(irq_desc_get_chip(desc), desc); 1380136afa0SLucas Stach } 1390136afa0SLucas Stach 1400136afa0SLucas Stach static int imx_irqsteer_probe(struct platform_device *pdev) 1410136afa0SLucas Stach { 1420136afa0SLucas Stach struct device_node *np = pdev->dev.of_node; 1430136afa0SLucas Stach struct irqsteer_data *data; 14428528fcaSAisheng Dong u32 irqs_num; 14528528fcaSAisheng Dong int i, ret; 1460136afa0SLucas Stach 1470136afa0SLucas Stach data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1480136afa0SLucas Stach if (!data) 1490136afa0SLucas Stach return -ENOMEM; 1500136afa0SLucas Stach 151358b9d24SAnson Huang data->regs = devm_platform_ioremap_resource(pdev, 0); 1520136afa0SLucas Stach if (IS_ERR(data->regs)) { 1530136afa0SLucas Stach dev_err(&pdev->dev, "failed to initialize reg\n"); 1540136afa0SLucas Stach return PTR_ERR(data->regs); 1550136afa0SLucas Stach } 1560136afa0SLucas Stach 1570136afa0SLucas Stach data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); 158e0c45b10SAnson Huang if (IS_ERR(data->ipg_clk)) 159e0c45b10SAnson Huang return dev_err_probe(&pdev->dev, PTR_ERR(data->ipg_clk), 160e0c45b10SAnson Huang "failed to get ipg clk\n"); 1610136afa0SLucas Stach 1620136afa0SLucas Stach raw_spin_lock_init(&data->lock); 1630136afa0SLucas Stach 1647d3a5eb7SArnd Bergmann ret = of_property_read_u32(np, "fsl,num-irqs", &irqs_num); 1657d3a5eb7SArnd Bergmann if (ret) 1667d3a5eb7SArnd Bergmann return ret; 1677d3a5eb7SArnd Bergmann ret = of_property_read_u32(np, "fsl,channel", &data->channel); 1687d3a5eb7SArnd Bergmann if (ret) 1697d3a5eb7SArnd Bergmann return ret; 1700136afa0SLucas Stach 17128528fcaSAisheng Dong /* 17228528fcaSAisheng Dong * There is one output irq for each group of 64 inputs. 17328528fcaSAisheng Dong * One register bit map can represent 32 input interrupts. 17428528fcaSAisheng Dong */ 17528528fcaSAisheng Dong data->irq_count = DIV_ROUND_UP(irqs_num, 64); 17628528fcaSAisheng Dong data->reg_num = irqs_num / 32; 177deb904e4SAisheng Dong 1780136afa0SLucas Stach if (IS_ENABLED(CONFIG_PM_SLEEP)) { 1790136afa0SLucas Stach data->saved_reg = devm_kzalloc(&pdev->dev, 180deb904e4SAisheng Dong sizeof(u32) * data->reg_num, 1810136afa0SLucas Stach GFP_KERNEL); 1820136afa0SLucas Stach if (!data->saved_reg) 1830136afa0SLucas Stach return -ENOMEM; 1840136afa0SLucas Stach } 1850136afa0SLucas Stach 1860136afa0SLucas Stach ret = clk_prepare_enable(data->ipg_clk); 1870136afa0SLucas Stach if (ret) { 1880136afa0SLucas Stach dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); 1890136afa0SLucas Stach return ret; 1900136afa0SLucas Stach } 1910136afa0SLucas Stach 1920136afa0SLucas Stach /* steer all IRQs into configured channel */ 1930136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 1940136afa0SLucas Stach 195deb904e4SAisheng Dong data->domain = irq_domain_add_linear(np, data->reg_num * 32, 1960136afa0SLucas Stach &imx_irqsteer_domain_ops, data); 1970136afa0SLucas Stach if (!data->domain) { 1980136afa0SLucas Stach dev_err(&pdev->dev, "failed to create IRQ domain\n"); 19928528fcaSAisheng Dong ret = -ENOMEM; 20028528fcaSAisheng Dong goto out; 2010136afa0SLucas Stach } 2020136afa0SLucas Stach 20328528fcaSAisheng Dong if (!data->irq_count || data->irq_count > CHAN_MAX_OUTPUT_INT) { 20428528fcaSAisheng Dong ret = -EINVAL; 20528528fcaSAisheng Dong goto out; 20628528fcaSAisheng Dong } 20728528fcaSAisheng Dong 20828528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 20928528fcaSAisheng Dong data->irq[i] = irq_of_parse_and_map(np, i); 21028528fcaSAisheng Dong if (!data->irq[i]) { 21128528fcaSAisheng Dong ret = -EINVAL; 21228528fcaSAisheng Dong goto out; 21328528fcaSAisheng Dong } 21428528fcaSAisheng Dong 21528528fcaSAisheng Dong irq_set_chained_handler_and_data(data->irq[i], 21628528fcaSAisheng Dong imx_irqsteer_irq_handler, 2170136afa0SLucas Stach data); 21828528fcaSAisheng Dong } 2190136afa0SLucas Stach 2200136afa0SLucas Stach platform_set_drvdata(pdev, data); 2210136afa0SLucas Stach 2220136afa0SLucas Stach return 0; 22328528fcaSAisheng Dong out: 22428528fcaSAisheng Dong clk_disable_unprepare(data->ipg_clk); 22528528fcaSAisheng Dong return ret; 2260136afa0SLucas Stach } 2270136afa0SLucas Stach 2280136afa0SLucas Stach static int imx_irqsteer_remove(struct platform_device *pdev) 2290136afa0SLucas Stach { 2300136afa0SLucas Stach struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); 23128528fcaSAisheng Dong int i; 2320136afa0SLucas Stach 23328528fcaSAisheng Dong for (i = 0; i < irqsteer_data->irq_count; i++) 23428528fcaSAisheng Dong irq_set_chained_handler_and_data(irqsteer_data->irq[i], 23528528fcaSAisheng Dong NULL, NULL); 23628528fcaSAisheng Dong 2370136afa0SLucas Stach irq_domain_remove(irqsteer_data->domain); 2380136afa0SLucas Stach 2390136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2400136afa0SLucas Stach 2410136afa0SLucas Stach return 0; 2420136afa0SLucas Stach } 2430136afa0SLucas Stach 2440136afa0SLucas Stach #ifdef CONFIG_PM_SLEEP 2450136afa0SLucas Stach static void imx_irqsteer_save_regs(struct irqsteer_data *data) 2460136afa0SLucas Stach { 2470136afa0SLucas Stach int i; 2480136afa0SLucas Stach 249deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2500136afa0SLucas Stach data->saved_reg[i] = readl_relaxed(data->regs + 251deb904e4SAisheng Dong CHANMASK(i, data->reg_num)); 2520136afa0SLucas Stach } 2530136afa0SLucas Stach 2540136afa0SLucas Stach static void imx_irqsteer_restore_regs(struct irqsteer_data *data) 2550136afa0SLucas Stach { 2560136afa0SLucas Stach int i; 2570136afa0SLucas Stach 2580136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 259deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2600136afa0SLucas Stach writel_relaxed(data->saved_reg[i], 261deb904e4SAisheng Dong data->regs + CHANMASK(i, data->reg_num)); 2620136afa0SLucas Stach } 2630136afa0SLucas Stach 2640136afa0SLucas Stach static int imx_irqsteer_suspend(struct device *dev) 2650136afa0SLucas Stach { 2660136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2670136afa0SLucas Stach 2680136afa0SLucas Stach imx_irqsteer_save_regs(irqsteer_data); 2690136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2700136afa0SLucas Stach 2710136afa0SLucas Stach return 0; 2720136afa0SLucas Stach } 2730136afa0SLucas Stach 2740136afa0SLucas Stach static int imx_irqsteer_resume(struct device *dev) 2750136afa0SLucas Stach { 2760136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2770136afa0SLucas Stach int ret; 2780136afa0SLucas Stach 2790136afa0SLucas Stach ret = clk_prepare_enable(irqsteer_data->ipg_clk); 2800136afa0SLucas Stach if (ret) { 2810136afa0SLucas Stach dev_err(dev, "failed to enable ipg clk: %d\n", ret); 2820136afa0SLucas Stach return ret; 2830136afa0SLucas Stach } 2840136afa0SLucas Stach imx_irqsteer_restore_regs(irqsteer_data); 2850136afa0SLucas Stach 2860136afa0SLucas Stach return 0; 2870136afa0SLucas Stach } 2880136afa0SLucas Stach #endif 2890136afa0SLucas Stach 2900136afa0SLucas Stach static const struct dev_pm_ops imx_irqsteer_pm_ops = { 2910136afa0SLucas Stach SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_irqsteer_suspend, imx_irqsteer_resume) 2920136afa0SLucas Stach }; 2930136afa0SLucas Stach 2940136afa0SLucas Stach static const struct of_device_id imx_irqsteer_dt_ids[] = { 2950136afa0SLucas Stach { .compatible = "fsl,imx-irqsteer", }, 2960136afa0SLucas Stach {}, 2970136afa0SLucas Stach }; 2980136afa0SLucas Stach 2990136afa0SLucas Stach static struct platform_driver imx_irqsteer_driver = { 3000136afa0SLucas Stach .driver = { 3010136afa0SLucas Stach .name = "imx-irqsteer", 3020136afa0SLucas Stach .of_match_table = imx_irqsteer_dt_ids, 3030136afa0SLucas Stach .pm = &imx_irqsteer_pm_ops, 3040136afa0SLucas Stach }, 3050136afa0SLucas Stach .probe = imx_irqsteer_probe, 3060136afa0SLucas Stach .remove = imx_irqsteer_remove, 3070136afa0SLucas Stach }; 3080136afa0SLucas Stach builtin_platform_driver(imx_irqsteer_driver); 309