18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2017 NXP 48c2ecf20Sopenharmony_ci * Copyright (C) 2018 Pengutronix, Lucas Stach <kernel@pengutronix.de> 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/clk.h> 88c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 98c2ecf20Sopenharmony_ci#include <linux/irq.h> 108c2ecf20Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 118c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 128c2ecf20Sopenharmony_ci#include <linux/kernel.h> 138c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 148c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 158c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define CTRL_STRIDE_OFF(_t, _r) (_t * 4 * _r) 188c2ecf20Sopenharmony_ci#define CHANCTRL 0x0 198c2ecf20Sopenharmony_ci#define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4) 208c2ecf20Sopenharmony_ci#define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4) 218c2ecf20Sopenharmony_ci#define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4) 228c2ecf20Sopenharmony_ci#define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4) 238c2ecf20Sopenharmony_ci#define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8) 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define CHAN_MAX_OUTPUT_INT 0x8 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistruct irqsteer_data { 288c2ecf20Sopenharmony_ci void __iomem *regs; 298c2ecf20Sopenharmony_ci struct clk *ipg_clk; 308c2ecf20Sopenharmony_ci int irq[CHAN_MAX_OUTPUT_INT]; 318c2ecf20Sopenharmony_ci int irq_count; 328c2ecf20Sopenharmony_ci raw_spinlock_t lock; 338c2ecf20Sopenharmony_ci int reg_num; 348c2ecf20Sopenharmony_ci int channel; 358c2ecf20Sopenharmony_ci struct irq_domain *domain; 368c2ecf20Sopenharmony_ci u32 *saved_reg; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic int imx_irqsteer_get_reg_index(struct irqsteer_data *data, 408c2ecf20Sopenharmony_ci unsigned long irqnum) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci return (data->reg_num - irqnum / 32 - 1); 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic void imx_irqsteer_irq_unmask(struct irq_data *d) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci struct irqsteer_data *data = d->chip_data; 488c2ecf20Sopenharmony_ci int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 498c2ecf20Sopenharmony_ci unsigned long flags; 508c2ecf20Sopenharmony_ci u32 val; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&data->lock, flags); 538c2ecf20Sopenharmony_ci val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 548c2ecf20Sopenharmony_ci val |= BIT(d->hwirq % 32); 558c2ecf20Sopenharmony_ci writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 568c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&data->lock, flags); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void imx_irqsteer_irq_mask(struct irq_data *d) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct irqsteer_data *data = d->chip_data; 628c2ecf20Sopenharmony_ci int idx = imx_irqsteer_get_reg_index(data, d->hwirq); 638c2ecf20Sopenharmony_ci unsigned long flags; 648c2ecf20Sopenharmony_ci u32 val; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&data->lock, flags); 678c2ecf20Sopenharmony_ci val = readl_relaxed(data->regs + CHANMASK(idx, data->reg_num)); 688c2ecf20Sopenharmony_ci val &= ~BIT(d->hwirq % 32); 698c2ecf20Sopenharmony_ci writel_relaxed(val, data->regs + CHANMASK(idx, data->reg_num)); 708c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&data->lock, flags); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic struct irq_chip imx_irqsteer_irq_chip = { 748c2ecf20Sopenharmony_ci .name = "irqsteer", 758c2ecf20Sopenharmony_ci .irq_mask = imx_irqsteer_irq_mask, 768c2ecf20Sopenharmony_ci .irq_unmask = imx_irqsteer_irq_unmask, 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq, 808c2ecf20Sopenharmony_ci irq_hw_number_t hwirq) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci irq_set_status_flags(irq, IRQ_LEVEL); 838c2ecf20Sopenharmony_ci irq_set_chip_data(irq, h->host_data); 848c2ecf20Sopenharmony_ci irq_set_chip_and_handler(irq, &imx_irqsteer_irq_chip, handle_level_irq); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic const struct irq_domain_ops imx_irqsteer_domain_ops = { 908c2ecf20Sopenharmony_ci .map = imx_irqsteer_irq_map, 918c2ecf20Sopenharmony_ci .xlate = irq_domain_xlate_onecell, 928c2ecf20Sopenharmony_ci}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic int imx_irqsteer_get_hwirq_base(struct irqsteer_data *data, u32 irq) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci int i; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci for (i = 0; i < data->irq_count; i++) { 998c2ecf20Sopenharmony_ci if (data->irq[i] == irq) 1008c2ecf20Sopenharmony_ci return i * 64; 1018c2ecf20Sopenharmony_ci } 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci return -EINVAL; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void imx_irqsteer_irq_handler(struct irq_desc *desc) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct irqsteer_data *data = irq_desc_get_handler_data(desc); 1098c2ecf20Sopenharmony_ci int hwirq; 1108c2ecf20Sopenharmony_ci int irq, i; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci chained_irq_enter(irq_desc_get_chip(desc), desc); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci irq = irq_desc_get_irq(desc); 1158c2ecf20Sopenharmony_ci hwirq = imx_irqsteer_get_hwirq_base(data, irq); 1168c2ecf20Sopenharmony_ci if (hwirq < 0) { 1178c2ecf20Sopenharmony_ci pr_warn("%s: unable to get hwirq base for irq %d\n", 1188c2ecf20Sopenharmony_ci __func__, irq); 1198c2ecf20Sopenharmony_ci return; 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci for (i = 0; i < 2; i++, hwirq += 32) { 1238c2ecf20Sopenharmony_ci int idx = imx_irqsteer_get_reg_index(data, hwirq); 1248c2ecf20Sopenharmony_ci unsigned long irqmap; 1258c2ecf20Sopenharmony_ci int pos, virq; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci if (hwirq >= data->reg_num * 32) 1288c2ecf20Sopenharmony_ci break; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci irqmap = readl_relaxed(data->regs + 1318c2ecf20Sopenharmony_ci CHANSTATUS(idx, data->reg_num)); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci for_each_set_bit(pos, &irqmap, 32) { 1348c2ecf20Sopenharmony_ci virq = irq_find_mapping(data->domain, pos + hwirq); 1358c2ecf20Sopenharmony_ci if (virq) 1368c2ecf20Sopenharmony_ci generic_handle_irq(virq); 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci chained_irq_exit(irq_desc_get_chip(desc), desc); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int imx_irqsteer_probe(struct platform_device *pdev) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1468c2ecf20Sopenharmony_ci struct irqsteer_data *data; 1478c2ecf20Sopenharmony_ci u32 irqs_num; 1488c2ecf20Sopenharmony_ci int i, ret; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 1518c2ecf20Sopenharmony_ci if (!data) 1528c2ecf20Sopenharmony_ci return -ENOMEM; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci data->regs = devm_platform_ioremap_resource(pdev, 0); 1558c2ecf20Sopenharmony_ci if (IS_ERR(data->regs)) { 1568c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to initialize reg\n"); 1578c2ecf20Sopenharmony_ci return PTR_ERR(data->regs); 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); 1618c2ecf20Sopenharmony_ci if (IS_ERR(data->ipg_clk)) 1628c2ecf20Sopenharmony_ci return dev_err_probe(&pdev->dev, PTR_ERR(data->ipg_clk), 1638c2ecf20Sopenharmony_ci "failed to get ipg clk\n"); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci raw_spin_lock_init(&data->lock); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, "fsl,num-irqs", &irqs_num); 1688c2ecf20Sopenharmony_ci if (ret) 1698c2ecf20Sopenharmony_ci return ret; 1708c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, "fsl,channel", &data->channel); 1718c2ecf20Sopenharmony_ci if (ret) 1728c2ecf20Sopenharmony_ci return ret; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci /* 1758c2ecf20Sopenharmony_ci * There is one output irq for each group of 64 inputs. 1768c2ecf20Sopenharmony_ci * One register bit map can represent 32 input interrupts. 1778c2ecf20Sopenharmony_ci */ 1788c2ecf20Sopenharmony_ci data->irq_count = DIV_ROUND_UP(irqs_num, 64); 1798c2ecf20Sopenharmony_ci data->reg_num = irqs_num / 32; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (IS_ENABLED(CONFIG_PM_SLEEP)) { 1828c2ecf20Sopenharmony_ci data->saved_reg = devm_kzalloc(&pdev->dev, 1838c2ecf20Sopenharmony_ci sizeof(u32) * data->reg_num, 1848c2ecf20Sopenharmony_ci GFP_KERNEL); 1858c2ecf20Sopenharmony_ci if (!data->saved_reg) 1868c2ecf20Sopenharmony_ci return -ENOMEM; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci ret = clk_prepare_enable(data->ipg_clk); 1908c2ecf20Sopenharmony_ci if (ret) { 1918c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); 1928c2ecf20Sopenharmony_ci return ret; 1938c2ecf20Sopenharmony_ci } 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci /* steer all IRQs into configured channel */ 1968c2ecf20Sopenharmony_ci writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci data->domain = irq_domain_add_linear(np, data->reg_num * 32, 1998c2ecf20Sopenharmony_ci &imx_irqsteer_domain_ops, data); 2008c2ecf20Sopenharmony_ci if (!data->domain) { 2018c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "failed to create IRQ domain\n"); 2028c2ecf20Sopenharmony_ci ret = -ENOMEM; 2038c2ecf20Sopenharmony_ci goto out; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci if (!data->irq_count || data->irq_count > CHAN_MAX_OUTPUT_INT) { 2078c2ecf20Sopenharmony_ci ret = -EINVAL; 2088c2ecf20Sopenharmony_ci goto out; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci for (i = 0; i < data->irq_count; i++) { 2128c2ecf20Sopenharmony_ci data->irq[i] = irq_of_parse_and_map(np, i); 2138c2ecf20Sopenharmony_ci if (!data->irq[i]) { 2148c2ecf20Sopenharmony_ci ret = -EINVAL; 2158c2ecf20Sopenharmony_ci goto out; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(data->irq[i], 2198c2ecf20Sopenharmony_ci imx_irqsteer_irq_handler, 2208c2ecf20Sopenharmony_ci data); 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return 0; 2268c2ecf20Sopenharmony_ciout: 2278c2ecf20Sopenharmony_ci clk_disable_unprepare(data->ipg_clk); 2288c2ecf20Sopenharmony_ci return ret; 2298c2ecf20Sopenharmony_ci} 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_cistatic int imx_irqsteer_remove(struct platform_device *pdev) 2328c2ecf20Sopenharmony_ci{ 2338c2ecf20Sopenharmony_ci struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev); 2348c2ecf20Sopenharmony_ci int i; 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci for (i = 0; i < irqsteer_data->irq_count; i++) 2378c2ecf20Sopenharmony_ci irq_set_chained_handler_and_data(irqsteer_data->irq[i], 2388c2ecf20Sopenharmony_ci NULL, NULL); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci irq_domain_remove(irqsteer_data->domain); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci clk_disable_unprepare(irqsteer_data->ipg_clk); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci return 0; 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 2488c2ecf20Sopenharmony_cistatic void imx_irqsteer_save_regs(struct irqsteer_data *data) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci int i; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci for (i = 0; i < data->reg_num; i++) 2538c2ecf20Sopenharmony_ci data->saved_reg[i] = readl_relaxed(data->regs + 2548c2ecf20Sopenharmony_ci CHANMASK(i, data->reg_num)); 2558c2ecf20Sopenharmony_ci} 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic void imx_irqsteer_restore_regs(struct irqsteer_data *data) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci int i; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci writel_relaxed(BIT(data->channel), data->regs + CHANCTRL); 2628c2ecf20Sopenharmony_ci for (i = 0; i < data->reg_num; i++) 2638c2ecf20Sopenharmony_ci writel_relaxed(data->saved_reg[i], 2648c2ecf20Sopenharmony_ci data->regs + CHANMASK(i, data->reg_num)); 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_cistatic int imx_irqsteer_suspend(struct device *dev) 2688c2ecf20Sopenharmony_ci{ 2698c2ecf20Sopenharmony_ci struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci imx_irqsteer_save_regs(irqsteer_data); 2728c2ecf20Sopenharmony_ci clk_disable_unprepare(irqsteer_data->ipg_clk); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci return 0; 2758c2ecf20Sopenharmony_ci} 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_cistatic int imx_irqsteer_resume(struct device *dev) 2788c2ecf20Sopenharmony_ci{ 2798c2ecf20Sopenharmony_ci struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev); 2808c2ecf20Sopenharmony_ci int ret; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci ret = clk_prepare_enable(irqsteer_data->ipg_clk); 2838c2ecf20Sopenharmony_ci if (ret) { 2848c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable ipg clk: %d\n", ret); 2858c2ecf20Sopenharmony_ci return ret; 2868c2ecf20Sopenharmony_ci } 2878c2ecf20Sopenharmony_ci imx_irqsteer_restore_regs(irqsteer_data); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci return 0; 2908c2ecf20Sopenharmony_ci} 2918c2ecf20Sopenharmony_ci#endif 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic const struct dev_pm_ops imx_irqsteer_pm_ops = { 2948c2ecf20Sopenharmony_ci SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_irqsteer_suspend, imx_irqsteer_resume) 2958c2ecf20Sopenharmony_ci}; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic const struct of_device_id imx_irqsteer_dt_ids[] = { 2988c2ecf20Sopenharmony_ci { .compatible = "fsl,imx-irqsteer", }, 2998c2ecf20Sopenharmony_ci {}, 3008c2ecf20Sopenharmony_ci}; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cistatic struct platform_driver imx_irqsteer_driver = { 3038c2ecf20Sopenharmony_ci .driver = { 3048c2ecf20Sopenharmony_ci .name = "imx-irqsteer", 3058c2ecf20Sopenharmony_ci .of_match_table = imx_irqsteer_dt_ids, 3068c2ecf20Sopenharmony_ci .pm = &imx_irqsteer_pm_ops, 3078c2ecf20Sopenharmony_ci }, 3088c2ecf20Sopenharmony_ci .probe = imx_irqsteer_probe, 3098c2ecf20Sopenharmony_ci .remove = imx_irqsteer_remove, 3108c2ecf20Sopenharmony_ci}; 3118c2ecf20Sopenharmony_cibuiltin_platform_driver(imx_irqsteer_driver); 312