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> 130136afa0SLucas Stach #include <linux/of_platform.h> 140136afa0SLucas Stach #include <linux/spinlock.h> 150136afa0SLucas Stach 16*deb904e4SAisheng Dong #define CTRL_STRIDE_OFF(_t, _r) (_t * 4 * _r) 170136afa0SLucas Stach #define CHANCTRL 0x0 180136afa0SLucas Stach #define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4) 190136afa0SLucas Stach #define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4) 200136afa0SLucas Stach #define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4) 210136afa0SLucas Stach #define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4) 220136afa0SLucas Stach #define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8) 230136afa0SLucas Stach 240136afa0SLucas Stach struct irqsteer_data { 250136afa0SLucas Stach void __iomem *regs; 260136afa0SLucas Stach struct clk *ipg_clk; 270136afa0SLucas Stach int irq; 280136afa0SLucas Stach raw_spinlock_t lock; 29*deb904e4SAisheng Dong int reg_num; 300136afa0SLucas Stach int channel; 310136afa0SLucas Stach struct irq_domain *domain; 320136afa0SLucas Stach u32 *saved_reg; 330136afa0SLucas Stach }; 340136afa0SLucas Stach 350136afa0SLucas Stach static int imx_irqsteer_get_reg_index(struct irqsteer_data *data, 360136afa0SLucas Stach unsigned long irqnum) 370136afa0SLucas Stach { 38*deb904e4SAisheng Dong return (data->reg_num - irqnum / 32 - 1); 390136afa0SLucas Stach } 400136afa0SLucas Stach 410136afa0SLucas Stach static void imx_irqsteer_irq_unmask(struct irq_data *d) 420136afa0SLucas Stach { 430136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 440136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 450136afa0SLucas Stach unsigned long flags; 460136afa0SLucas Stach u32 val; 470136afa0SLucas Stach 480136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 49*deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 500136afa0SLucas Stach val |= BIT(d->hwirq % 32); 51*deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 520136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 530136afa0SLucas Stach } 540136afa0SLucas Stach 550136afa0SLucas Stach static void imx_irqsteer_irq_mask(struct irq_data *d) 560136afa0SLucas Stach { 570136afa0SLucas Stach struct irqsteer_data *data = d->chip_data; 580136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 590136afa0SLucas Stach unsigned long flags; 600136afa0SLucas Stach u32 val; 610136afa0SLucas Stach 620136afa0SLucas Stach raw_spin_lock_irqsave(&data->lock, flags); 63*deb904e4SAisheng Dong val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 640136afa0SLucas Stach val &= ~BIT(d->hwirq % 32); 65*deb904e4SAisheng Dong writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 660136afa0SLucas Stach raw_spin_unlock_irqrestore(&data->lock, flags); 670136afa0SLucas Stach } 680136afa0SLucas Stach 690136afa0SLucas Stach static struct irq_chip imx_irqsteer_irq_chip = { 700136afa0SLucas Stach .name = "irqsteer", 710136afa0SLucas Stach .irq_mask = imx_irqsteer_irq_mask, 720136afa0SLucas Stach .irq_unmask = imx_irqsteer_irq_unmask, 730136afa0SLucas Stach }; 740136afa0SLucas Stach 750136afa0SLucas Stach static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq, 760136afa0SLucas Stach irq_hw_number_t hwirq) 770136afa0SLucas Stach { 780136afa0SLucas Stach irq_set_status_flags(irq, IRQ_LEVEL); 790136afa0SLucas Stach irq_set_chip_data(irq, h->host_data); 800136afa0SLucas Stach irq_set_chip_and_handler(irq, &imx_irqsteer_irq_chip, handle_level_irq); 810136afa0SLucas Stach 820136afa0SLucas Stach return 0; 830136afa0SLucas Stach } 840136afa0SLucas Stach 850136afa0SLucas Stach static const struct irq_domain_ops imx_irqsteer_domain_ops = { 860136afa0SLucas Stach .map = imx_irqsteer_irq_map, 870136afa0SLucas Stach .xlate = irq_domain_xlate_onecell, 880136afa0SLucas Stach }; 890136afa0SLucas Stach 900136afa0SLucas Stach static void imx_irqsteer_irq_handler(struct irq_desc *desc) 910136afa0SLucas Stach { 920136afa0SLucas Stach struct irqsteer_data *data = irq_desc_get_handler_data(desc); 930136afa0SLucas Stach int i; 940136afa0SLucas Stach 950136afa0SLucas Stach chained_irq_enter(irq_desc_get_chip(desc), desc); 960136afa0SLucas Stach 97*deb904e4SAisheng Dong for (i = 0; i < data->reg_num * 32; i += 32) { 980136afa0SLucas Stach int idx = imx_irqsteer_get_reg_index(data, i); 990136afa0SLucas Stach unsigned long irqmap; 1000136afa0SLucas Stach int pos, virq; 1010136afa0SLucas Stach 1020136afa0SLucas Stach irqmap = readl_relaxed(data->regs + 103*deb904e4SAisheng Dong CHANSTATUS(idx, data->reg_num)); 1040136afa0SLucas Stach 1050136afa0SLucas Stach for_each_set_bit(pos, &irqmap, 32) { 1060136afa0SLucas Stach virq = irq_find_mapping(data->domain, pos + i); 1070136afa0SLucas Stach if (virq) 1080136afa0SLucas Stach generic_handle_irq(virq); 1090136afa0SLucas Stach } 1100136afa0SLucas Stach } 1110136afa0SLucas Stach 1120136afa0SLucas Stach chained_irq_exit(irq_desc_get_chip(desc), desc); 1130136afa0SLucas Stach } 1140136afa0SLucas Stach 1150136afa0SLucas Stach static int imx_irqsteer_probe(struct platform_device *pdev) 1160136afa0SLucas Stach { 1170136afa0SLucas Stach struct device_node *np = pdev->dev.of_node; 1180136afa0SLucas Stach struct irqsteer_data *data; 1190136afa0SLucas Stach struct resource *res; 1200136afa0SLucas Stach int ret; 1210136afa0SLucas Stach 1220136afa0SLucas Stach data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1230136afa0SLucas Stach if (!data) 1240136afa0SLucas Stach return -ENOMEM; 1250136afa0SLucas Stach 1260136afa0SLucas Stach res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1270136afa0SLucas Stach data->regs = devm_ioremap_resource(&pdev->dev, res); 1280136afa0SLucas Stach if (IS_ERR(data->regs)) { 1290136afa0SLucas Stach dev_err(&pdev->dev, "failed to initialize reg\n"); 1300136afa0SLucas Stach return PTR_ERR(data->regs); 1310136afa0SLucas Stach } 1320136afa0SLucas Stach 1330136afa0SLucas Stach data->irq = platform_get_irq(pdev, 0); 1340136afa0SLucas Stach if (data->irq <= 0) { 1350136afa0SLucas Stach dev_err(&pdev->dev, "failed to get irq\n"); 1360136afa0SLucas Stach return -ENODEV; 1370136afa0SLucas Stach } 1380136afa0SLucas Stach 1390136afa0SLucas Stach data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); 1400136afa0SLucas Stach if (IS_ERR(data->ipg_clk)) { 1410136afa0SLucas Stach ret = PTR_ERR(data->ipg_clk); 1420136afa0SLucas Stach if (ret != -EPROBE_DEFER) 1430136afa0SLucas Stach dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret); 1440136afa0SLucas Stach return ret; 1450136afa0SLucas Stach } 1460136afa0SLucas Stach 1470136afa0SLucas Stach raw_spin_lock_init(&data->lock); 1480136afa0SLucas Stach 149*deb904e4SAisheng Dong of_property_read_u32(np, "fsl,num-irqs", &data->reg_num); 1500136afa0SLucas Stach of_property_read_u32(np, "fsl,channel", &data->channel); 1510136afa0SLucas Stach 152*deb904e4SAisheng Dong /* one register bit map represents 32 input interrupts */ 153*deb904e4SAisheng Dong data->reg_num /= 32; 154*deb904e4SAisheng Dong 1550136afa0SLucas Stach if (IS_ENABLED(CONFIG_PM_SLEEP)) { 1560136afa0SLucas Stach data->saved_reg = devm_kzalloc(&pdev->dev, 157*deb904e4SAisheng Dong sizeof(u32) * data->reg_num, 1580136afa0SLucas Stach GFP_KERNEL); 1590136afa0SLucas Stach if (!data->saved_reg) 1600136afa0SLucas Stach return -ENOMEM; 1610136afa0SLucas Stach } 1620136afa0SLucas Stach 1630136afa0SLucas Stach ret = clk_prepare_enable(data->ipg_clk); 1640136afa0SLucas Stach if (ret) { 1650136afa0SLucas Stach dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); 1660136afa0SLucas Stach return ret; 1670136afa0SLucas Stach } 1680136afa0SLucas Stach 1690136afa0SLucas Stach /* steer all IRQs into configured channel */ 1700136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 1710136afa0SLucas Stach 172*deb904e4SAisheng Dong data->domain = irq_domain_add_linear(np, data->reg_num * 32, 1730136afa0SLucas Stach &imx_irqsteer_domain_ops, data); 1740136afa0SLucas Stach if (!data->domain) { 1750136afa0SLucas Stach dev_err(&pdev->dev, "failed to create IRQ domain\n"); 1760136afa0SLucas Stach clk_disable_unprepare(data->ipg_clk); 1770136afa0SLucas Stach return -ENOMEM; 1780136afa0SLucas Stach } 1790136afa0SLucas Stach 1800136afa0SLucas Stach irq_set_chained_handler_and_data(data->irq, imx_irqsteer_irq_handler, 1810136afa0SLucas Stach data); 1820136afa0SLucas Stach 1830136afa0SLucas Stach platform_set_drvdata(pdev, data); 1840136afa0SLucas Stach 1850136afa0SLucas Stach return 0; 1860136afa0SLucas Stach } 1870136afa0SLucas Stach 1880136afa0SLucas Stach static int imx_irqsteer_remove(struct platform_device *pdev) 1890136afa0SLucas Stach { 1900136afa0SLucas Stach struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); 1910136afa0SLucas Stach 1920136afa0SLucas Stach irq_set_chained_handler_and_data(irqsteer_data->irq, NULL, NULL); 1930136afa0SLucas Stach irq_domain_remove(irqsteer_data->domain); 1940136afa0SLucas Stach 1950136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 1960136afa0SLucas Stach 1970136afa0SLucas Stach return 0; 1980136afa0SLucas Stach } 1990136afa0SLucas Stach 2000136afa0SLucas Stach #ifdef CONFIG_PM_SLEEP 2010136afa0SLucas Stach static void imx_irqsteer_save_regs(struct irqsteer_data *data) 2020136afa0SLucas Stach { 2030136afa0SLucas Stach int i; 2040136afa0SLucas Stach 205*deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2060136afa0SLucas Stach data->saved_reg[i] = readl_relaxed(data->regs + 207*deb904e4SAisheng Dong CHANMASK(i, data->reg_num)); 2080136afa0SLucas Stach } 2090136afa0SLucas Stach 2100136afa0SLucas Stach static void imx_irqsteer_restore_regs(struct irqsteer_data *data) 2110136afa0SLucas Stach { 2120136afa0SLucas Stach int i; 2130136afa0SLucas Stach 2140136afa0SLucas Stach writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 215*deb904e4SAisheng Dong for (i = 0; i < data->reg_num; i++) 2160136afa0SLucas Stach writel_relaxed(data->saved_reg[i], 217*deb904e4SAisheng Dong data->regs + CHANMASK(i, data->reg_num)); 2180136afa0SLucas Stach } 2190136afa0SLucas Stach 2200136afa0SLucas Stach static int imx_irqsteer_suspend(struct device *dev) 2210136afa0SLucas Stach { 2220136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2230136afa0SLucas Stach 2240136afa0SLucas Stach imx_irqsteer_save_regs(irqsteer_data); 2250136afa0SLucas Stach clk_disable_unprepare(irqsteer_data->ipg_clk); 2260136afa0SLucas Stach 2270136afa0SLucas Stach return 0; 2280136afa0SLucas Stach } 2290136afa0SLucas Stach 2300136afa0SLucas Stach static int imx_irqsteer_resume(struct device *dev) 2310136afa0SLucas Stach { 2320136afa0SLucas Stach struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2330136afa0SLucas Stach int ret; 2340136afa0SLucas Stach 2350136afa0SLucas Stach ret = clk_prepare_enable(irqsteer_data->ipg_clk); 2360136afa0SLucas Stach if (ret) { 2370136afa0SLucas Stach dev_err(dev, "failed to enable ipg clk: %d\n", ret); 2380136afa0SLucas Stach return ret; 2390136afa0SLucas Stach } 2400136afa0SLucas Stach imx_irqsteer_restore_regs(irqsteer_data); 2410136afa0SLucas Stach 2420136afa0SLucas Stach return 0; 2430136afa0SLucas Stach } 2440136afa0SLucas Stach #endif 2450136afa0SLucas Stach 2460136afa0SLucas Stach static const struct dev_pm_ops imx_irqsteer_pm_ops = { 2470136afa0SLucas Stach SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_irqsteer_suspend, imx_irqsteer_resume) 2480136afa0SLucas Stach }; 2490136afa0SLucas Stach 2500136afa0SLucas Stach static const struct of_device_id imx_irqsteer_dt_ids[] = { 2510136afa0SLucas Stach { .compatible = "fsl,imx-irqsteer", }, 2520136afa0SLucas Stach {}, 2530136afa0SLucas Stach }; 2540136afa0SLucas Stach 2550136afa0SLucas Stach static struct platform_driver imx_irqsteer_driver = { 2560136afa0SLucas Stach .driver = { 2570136afa0SLucas Stach .name = "imx-irqsteer", 2580136afa0SLucas Stach .of_match_table = imx_irqsteer_dt_ids, 2590136afa0SLucas Stach .pm = &imx_irqsteer_pm_ops, 2600136afa0SLucas Stach }, 2610136afa0SLucas Stach .probe = imx_irqsteer_probe, 2620136afa0SLucas Stach .remove = imx_irqsteer_remove, 2630136afa0SLucas Stach }; 2640136afa0SLucas Stach builtin_platform_driver(imx_irqsteer_driver); 265