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> 15*4730d223SLucas Stach #include <linux/pm_runtime.h> 160136afa0SLucas Stach #include <linux/spinlock.h> 170136afa0SLucas Stach 18deb904e4SAisheng Dong #define CTRL_STRIDE_OFF(_t, _r) (_t * 4 * _r) 190136afa0SLucas Stach #define CHANCTRL 0x0 200136afa0SLucas Stach #define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4) 210136afa0SLucas Stach #define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4) 220136afa0SLucas Stach #define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4) 230136afa0SLucas Stach #define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4) 240136afa0SLucas Stach #define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8) 250136afa0SLucas Stach 2628528fcaSAisheng Dong #define CHAN_MAX_OUTPUT_INT 0x8 2728528fcaSAisheng Dong 280136afa0SLucas Stach struct irqsteer_data { 290136afa0SLucas Stach void __iomem *regs; 300136afa0SLucas Stach struct clk *ipg_clk; 3128528fcaSAisheng Dong int irq[CHAN_MAX_OUTPUT_INT]; 3228528fcaSAisheng Dong int irq_count; 330136afa0SLucas Stach raw_spinlock_t lock; 34deb904e4SAisheng Dong int reg_num; 350136afa0SLucas Stach int channel; 360136afa0SLucas Stach struct irq_domain *domain; 370136afa0SLucas Stach u32 *saved_reg; 380136afa0SLucas Stach }; 390136afa0SLucas Stach 400136afa0SLucas Stach static int imx_irqsteer_get_reg_index(struct irqsteer_data *data, 410136afa0SLucas Stach unsigned long irqnum) 420136afa0SLucas Stach { 43deb904e4SAisheng Dong return (data->reg_num - irqnum / 32 - 1); 440136afa0SLucas Stach } 450136afa0SLucas Stach 460136afa0SLucas Stach static void imx_irqsteer_irq_unmask(struct irq_data *d) 470136afa0SLucas Stach { 480136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 490136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 500136afa0SLucas Stach unsigned long flags; 510136afa0SLucas Stach u32 val; 520136afa0SLucas Stach 530136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 54deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 550136afa0SLucas Stach val |= BIT(d->hwirq % 32); 56deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 570136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 580136afa0SLucas Stach } 590136afa0SLucas Stach 600136afa0SLucas Stach static void imx_irqsteer_irq_mask(struct irq_data *d) 610136afa0SLucas Stach { 620136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 630136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 640136afa0SLucas Stach unsigned long flags; 650136afa0SLucas Stach u32 val; 660136afa0SLucas Stach 670136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 68deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 690136afa0SLucas Stach val &= ~BIT(d->hwirq % 32); 70deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 710136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 720136afa0SLucas Stach } 730136afa0SLucas Stach 74e9a50f12SLucas Stach static const struct irq_chip imx_irqsteer_irq_chip = { 750136afa0SLucas Stach .name = "irqsteer", 760136afa0SLucas Stach .irq_mask = imx_irqsteer_irq_mask, 770136afa0SLucas Stach .irq_unmask = imx_irqsteer_irq_unmask, 780136afa0SLucas Stach }; 790136afa0SLucas Stach 800136afa0SLucas Stach static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq, 810136afa0SLucas Stach irq_hw_number_t hwirq) 820136afa0SLucas Stach { 830136afa0SLucas Stach irq_set_status_flags(irq, IRQ_LEVEL); 840136afa0SLucas Stach irq_set_chip_data(irq, h->host_data); 850136afa0SLucas Stach irq_set_chip_and_handler(irq, &imx_irqsteer_irq_chip, handle_level_irq); 860136afa0SLucas Stach 870136afa0SLucas Stach return 0; 880136afa0SLucas Stach } 890136afa0SLucas Stach 900136afa0SLucas Stach static const struct irq_domain_ops imx_irqsteer_domain_ops = { 910136afa0SLucas Stach .map = imx_irqsteer_irq_map, 920136afa0SLucas Stach .xlate = irq_domain_xlate_onecell, 930136afa0SLucas Stach }; 940136afa0SLucas Stach 9528528fcaSAisheng Dong static int imx_irqsteer_get_hwirq_base(struct irqsteer_data *data, u32 irq) 9628528fcaSAisheng Dong { 9728528fcaSAisheng Dong int i; 9828528fcaSAisheng Dong 9928528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 10028528fcaSAisheng Dong if (data->irq[i] == irq) 10128528fcaSAisheng Dong return i * 64; 10228528fcaSAisheng Dong } 10328528fcaSAisheng Dong 10428528fcaSAisheng Dong return -EINVAL; 10528528fcaSAisheng Dong } 10628528fcaSAisheng Dong 1070136afa0SLucas Stach static void imx_irqsteer_irq_handler(struct irq_desc *desc) 1080136afa0SLucas Stach { 1090136afa0SLucas Stach struct irqsteer_data *data = irq_desc_get_handler_data(desc); 11028528fcaSAisheng Dong int hwirq; 11128528fcaSAisheng Dong int irq, i; 1120136afa0SLucas Stach 1130136afa0SLucas Stach chained_irq_enter(irq_desc_get_chip(desc), desc); 1140136afa0SLucas Stach 11528528fcaSAisheng Dong irq = irq_desc_get_irq(desc); 11628528fcaSAisheng Dong hwirq = imx_irqsteer_get_hwirq_base(data, irq); 11728528fcaSAisheng Dong if (hwirq < 0) { 11828528fcaSAisheng Dong pr_warn("%s: unable to get hwirq base for irq %d\n", 11928528fcaSAisheng Dong __func__, irq); 12028528fcaSAisheng Dong return; 12128528fcaSAisheng Dong } 12228528fcaSAisheng Dong 12328528fcaSAisheng Dong for (i = 0; i < 2; i++, hwirq += 32) { 12428528fcaSAisheng Dong int idx = imx_irqsteer_get_reg_index(data, hwirq); 1250136afa0SLucas Stach unsigned long irqmap; 126046a6ee2SMarc Zyngier int pos; 1270136afa0SLucas Stach 12828528fcaSAisheng Dong if (hwirq >= data->reg_num * 32) 12928528fcaSAisheng Dong break; 13028528fcaSAisheng Dong 1310136afa0SLucas Stach irqmap = readl_relaxed(data->regs + 132deb904e4SAisheng Dong CHANSTATUS(idx, data->reg_num)); 1330136afa0SLucas Stach 134046a6ee2SMarc Zyngier for_each_set_bit(pos, &irqmap, 32) 135046a6ee2SMarc Zyngier generic_handle_domain_irq(data->domain, pos + hwirq); 1360136afa0SLucas Stach } 1370136afa0SLucas Stach 1380136afa0SLucas Stach chained_irq_exit(irq_desc_get_chip(desc), desc); 1390136afa0SLucas Stach } 1400136afa0SLucas Stach 1410136afa0SLucas Stach static int imx_irqsteer_probe(struct platform_device *pdev) 1420136afa0SLucas Stach { 1430136afa0SLucas Stach struct device_node *np = pdev->dev.of_node; 1440136afa0SLucas Stach struct irqsteer_data *data; 14528528fcaSAisheng Dong u32 irqs_num; 14628528fcaSAisheng Dong int i, ret; 1470136afa0SLucas Stach 1480136afa0SLucas Stach data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1490136afa0SLucas Stach if (!data) 1500136afa0SLucas Stach return -ENOMEM; 1510136afa0SLucas Stach 152358b9d24SAnson Huang data->regs = devm_platform_ioremap_resource(pdev, 0); 1530136afa0SLucas Stach if (IS_ERR(data->regs)) { 1540136afa0SLucas Stach dev_err(&pdev->dev, "failed to initialize reg\n"); 1550136afa0SLucas Stach return PTR_ERR(data->regs); 1560136afa0SLucas Stach } 1570136afa0SLucas Stach 1580136afa0SLucas Stach data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); 159e0c45b10SAnson Huang if (IS_ERR(data->ipg_clk)) 160e0c45b10SAnson Huang return dev_err_probe(&pdev->dev, PTR_ERR(data->ipg_clk), 161e0c45b10SAnson Huang "failed to get ipg clk\n"); 1620136afa0SLucas Stach 1630136afa0SLucas Stach raw_spin_lock_init(&data->lock); 1640136afa0SLucas Stach 1657d3a5eb7SArnd Bergmann ret = of_property_read_u32(np, "fsl,num-irqs", &irqs_num); 1667d3a5eb7SArnd Bergmann if (ret) 1677d3a5eb7SArnd Bergmann return ret; 1687d3a5eb7SArnd Bergmann ret = of_property_read_u32(np, "fsl,channel", &data->channel); 1697d3a5eb7SArnd Bergmann if (ret) 1707d3a5eb7SArnd Bergmann return ret; 1710136afa0SLucas Stach 17228528fcaSAisheng Dong /* 17328528fcaSAisheng Dong * There is one output irq for each group of 64 inputs. 17428528fcaSAisheng Dong * One register bit map can represent 32 input interrupts. 17528528fcaSAisheng Dong */ 17628528fcaSAisheng Dong data->irq_count = DIV_ROUND_UP(irqs_num, 64); 17728528fcaSAisheng Dong data->reg_num = irqs_num / 32; 178deb904e4SAisheng Dong 179*4730d223SLucas Stach if (IS_ENABLED(CONFIG_PM)) { 1800136afa0SLucas Stach data->saved_reg = devm_kzalloc(&pdev->dev, 181deb904e4SAisheng Dong sizeof(u32) * data->reg_num, 1820136afa0SLucas Stach GFP_KERNEL); 1830136afa0SLucas Stach if (!data->saved_reg) 1840136afa0SLucas Stach return -ENOMEM; 1850136afa0SLucas Stach } 1860136afa0SLucas Stach 1870136afa0SLucas Stach ret = clk_prepare_enable(data->ipg_clk); 1880136afa0SLucas Stach if (ret) { 1890136afa0SLucas Stach dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); 1900136afa0SLucas Stach return ret; 1910136afa0SLucas Stach } 1920136afa0SLucas Stach 1930136afa0SLucas Stach /* steer all IRQs into configured channel */ 1940136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 1950136afa0SLucas Stach 196deb904e4SAisheng Dong data->domain = irq_domain_add_linear(np, data->reg_num * 32, 1970136afa0SLucas Stach &imx_irqsteer_domain_ops, data); 1980136afa0SLucas Stach if (!data->domain) { 1990136afa0SLucas Stach dev_err(&pdev->dev, "failed to create IRQ domain\n"); 20028528fcaSAisheng Dong ret = -ENOMEM; 20128528fcaSAisheng Dong goto out; 2020136afa0SLucas Stach } 203*4730d223SLucas Stach irq_domain_set_pm_device(data->domain, &pdev->dev); 2040136afa0SLucas Stach 20528528fcaSAisheng Dong if (!data->irq_count || data->irq_count > CHAN_MAX_OUTPUT_INT) { 20628528fcaSAisheng Dong ret = -EINVAL; 20728528fcaSAisheng Dong goto out; 20828528fcaSAisheng Dong } 20928528fcaSAisheng Dong 21028528fcaSAisheng Dong for (i = 0; i < data->irq_count; i++) { 21128528fcaSAisheng Dong data->irq[i] = irq_of_parse_and_map(np, i); 21228528fcaSAisheng Dong if (!data->irq[i]) { 21328528fcaSAisheng Dong ret = -EINVAL; 21428528fcaSAisheng Dong goto out; 21528528fcaSAisheng Dong } 21628528fcaSAisheng Dong 21728528fcaSAisheng Dong irq_set_chained_handler_and_data(data->irq[i], 21828528fcaSAisheng Dong imx_irqsteer_irq_handler, 2190136afa0SLucas Stach data); 22028528fcaSAisheng Dong } 2210136afa0SLucas Stach 2220136afa0SLucas Stach platform_set_drvdata(pdev, data); 2230136afa0SLucas Stach 224*4730d223SLucas Stach pm_runtime_set_active(&pdev->dev); 225*4730d223SLucas Stach pm_runtime_enable(&pdev->dev); 226*4730d223SLucas Stach 2270136afa0SLucas Stach return 0; 22828528fcaSAisheng Dong out: 22928528fcaSAisheng Dong clk_disable_unprepare(data->ipg_clk); 23028528fcaSAisheng Dong return ret; 2310136afa0SLucas Stach } 2320136afa0SLucas Stach 2330136afa0SLucas Stach static int imx_irqsteer_remove(struct platform_device *pdev) 2340136afa0SLucas Stach { 2350136afa0SLucas Stach struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); 23628528fcaSAisheng Dong int i; 2370136afa0SLucas Stach 23828528fcaSAisheng Dong for (i = 0; i < irqsteer_data->irq_count; i++) 23928528fcaSAisheng Dong irq_set_chained_handler_and_data(irqsteer_data->irq[i], 24028528fcaSAisheng Dong NULL, NULL); 24128528fcaSAisheng Dong 2420136afa0SLucas Stach irq_domain_remove(irqsteer_data->domain); 2430136afa0SLucas Stach 2440136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2450136afa0SLucas Stach 2460136afa0SLucas Stach return 0; 2470136afa0SLucas Stach } 2480136afa0SLucas Stach 249*4730d223SLucas Stach #ifdef CONFIG_PM 2500136afa0SLucas Stach static void imx_irqsteer_save_regs(struct irqsteer_data *data) 2510136afa0SLucas Stach { 2520136afa0SLucas Stach int i; 2530136afa0SLucas Stach 254deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2550136afa0SLucas Stach data->saved_reg[i] = readl_relaxed(data->regs + 256deb904e4SAisheng Dong CHANMASK(i, data->reg_num)); 2570136afa0SLucas Stach } 2580136afa0SLucas Stach 2590136afa0SLucas Stach static void imx_irqsteer_restore_regs(struct irqsteer_data *data) 2600136afa0SLucas Stach { 2610136afa0SLucas Stach int i; 2620136afa0SLucas Stach 2630136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 264deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2650136afa0SLucas Stach writel_relaxed(data->saved_reg[i], 266deb904e4SAisheng Dong data->regs + CHANMASK(i, data->reg_num)); 2670136afa0SLucas Stach } 2680136afa0SLucas Stach 2690136afa0SLucas Stach static int imx_irqsteer_suspend(struct device *dev) 2700136afa0SLucas Stach { 2710136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2720136afa0SLucas Stach 2730136afa0SLucas Stach imx_irqsteer_save_regs(irqsteer_data); 2740136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2750136afa0SLucas Stach 2760136afa0SLucas Stach return 0; 2770136afa0SLucas Stach } 2780136afa0SLucas Stach 2790136afa0SLucas Stach static int imx_irqsteer_resume(struct device *dev) 2800136afa0SLucas Stach { 2810136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2820136afa0SLucas Stach int ret; 2830136afa0SLucas Stach 2840136afa0SLucas Stach ret = clk_prepare_enable(irqsteer_data->ipg_clk); 2850136afa0SLucas Stach if (ret) { 2860136afa0SLucas Stach dev_err(dev, "failed to enable ipg clk: %d\n", ret); 2870136afa0SLucas Stach return ret; 2880136afa0SLucas Stach } 2890136afa0SLucas Stach imx_irqsteer_restore_regs(irqsteer_data); 2900136afa0SLucas Stach 2910136afa0SLucas Stach return 0; 2920136afa0SLucas Stach } 2930136afa0SLucas Stach #endif 2940136afa0SLucas Stach 2950136afa0SLucas Stach static const struct dev_pm_ops imx_irqsteer_pm_ops = { 296*4730d223SLucas Stach SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 297*4730d223SLucas Stach pm_runtime_force_resume) 298*4730d223SLucas Stach SET_RUNTIME_PM_OPS(imx_irqsteer_suspend, 299*4730d223SLucas Stach imx_irqsteer_resume, NULL) 3000136afa0SLucas Stach }; 3010136afa0SLucas Stach 3020136afa0SLucas Stach static const struct of_device_id imx_irqsteer_dt_ids[] = { 3030136afa0SLucas Stach { .compatible = "fsl,imx-irqsteer", }, 3040136afa0SLucas Stach {}, 3050136afa0SLucas Stach }; 3060136afa0SLucas Stach 3070136afa0SLucas Stach static struct platform_driver imx_irqsteer_driver = { 3080136afa0SLucas Stach .driver = { 3090136afa0SLucas Stach .name = "imx-irqsteer", 3100136afa0SLucas Stach .of_match_table = imx_irqsteer_dt_ids, 3110136afa0SLucas Stach .pm = &imx_irqsteer_pm_ops, 3120136afa0SLucas Stach }, 3130136afa0SLucas Stach .probe = imx_irqsteer_probe, 3140136afa0SLucas Stach .remove = imx_irqsteer_remove, 3150136afa0SLucas Stach }; 3160136afa0SLucas Stach builtin_platform_driver(imx_irqsteer_driver); 317