1*d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 20494e11aSStefan Agner /* 30494e11aSStefan Agner * Copyright (C) 2014-2015 Toradex AG 40494e11aSStefan Agner * Author: Stefan Agner <stefan@agner.ch> 50494e11aSStefan Agner * 60494e11aSStefan Agner * IRQ chip driver for MSCM interrupt router available on Vybrid SoC's. 70494e11aSStefan Agner * The interrupt router is between the CPU's interrupt controller and the 80494e11aSStefan Agner * peripheral. The router allows to route the peripheral interrupts to 90494e11aSStefan Agner * one of the two available CPU's on Vybrid VF6xx SoC's (Cortex-A5 or 100494e11aSStefan Agner * Cortex-M4). The router will be configured transparently on a IRQ 110494e11aSStefan Agner * request. 120494e11aSStefan Agner * 130494e11aSStefan Agner * o All peripheral interrupts of the Vybrid SoC can be routed to 140494e11aSStefan Agner * CPU 0, CPU 1 or both. The routing is useful for dual-core 150494e11aSStefan Agner * variants of Vybrid SoC such as VF6xx. This driver routes the 160494e11aSStefan Agner * requested interrupt to the CPU currently running on. 170494e11aSStefan Agner * 180494e11aSStefan Agner * o It is required to setup the interrupt router even on single-core 190494e11aSStefan Agner * variants of Vybrid. 200494e11aSStefan Agner */ 210494e11aSStefan Agner 220494e11aSStefan Agner #include <linux/cpu_pm.h> 230494e11aSStefan Agner #include <linux/io.h> 240494e11aSStefan Agner #include <linux/irq.h> 2541a83e06SJoel Porquet #include <linux/irqchip.h> 260494e11aSStefan Agner #include <linux/irqdomain.h> 270494e11aSStefan Agner #include <linux/mfd/syscon.h> 280494e11aSStefan Agner #include <dt-bindings/interrupt-controller/arm-gic.h> 290494e11aSStefan Agner #include <linux/of.h> 300494e11aSStefan Agner #include <linux/of_address.h> 310494e11aSStefan Agner #include <linux/slab.h> 320494e11aSStefan Agner #include <linux/regmap.h> 330494e11aSStefan Agner 340494e11aSStefan Agner #define MSCM_CPxNUM 0x4 350494e11aSStefan Agner 360494e11aSStefan Agner #define MSCM_IRSPRC(n) (0x80 + 2 * (n)) 370494e11aSStefan Agner #define MSCM_IRSPRC_CPEN_MASK 0x3 380494e11aSStefan Agner 390494e11aSStefan Agner #define MSCM_IRSPRC_NUM 112 400494e11aSStefan Agner 410494e11aSStefan Agner struct vf610_mscm_ir_chip_data { 420494e11aSStefan Agner void __iomem *mscm_ir_base; 430494e11aSStefan Agner u16 cpu_mask; 440494e11aSStefan Agner u16 saved_irsprc[MSCM_IRSPRC_NUM]; 45b5cc5cbcSStefan Agner bool is_nvic; 460494e11aSStefan Agner }; 470494e11aSStefan Agner 480494e11aSStefan Agner static struct vf610_mscm_ir_chip_data *mscm_ir_data; 490494e11aSStefan Agner 500494e11aSStefan Agner static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data) 510494e11aSStefan Agner { 520494e11aSStefan Agner int i; 530494e11aSStefan Agner 540494e11aSStefan Agner for (i = 0; i < MSCM_IRSPRC_NUM; i++) 550494e11aSStefan Agner data->saved_irsprc[i] = readw_relaxed(data->mscm_ir_base + MSCM_IRSPRC(i)); 560494e11aSStefan Agner } 570494e11aSStefan Agner 580494e11aSStefan Agner static inline void vf610_mscm_ir_restore(struct vf610_mscm_ir_chip_data *data) 590494e11aSStefan Agner { 600494e11aSStefan Agner int i; 610494e11aSStefan Agner 620494e11aSStefan Agner for (i = 0; i < MSCM_IRSPRC_NUM; i++) 630494e11aSStefan Agner writew_relaxed(data->saved_irsprc[i], data->mscm_ir_base + MSCM_IRSPRC(i)); 640494e11aSStefan Agner } 650494e11aSStefan Agner 660494e11aSStefan Agner static int vf610_mscm_ir_notifier(struct notifier_block *self, 670494e11aSStefan Agner unsigned long cmd, void *v) 680494e11aSStefan Agner { 690494e11aSStefan Agner switch (cmd) { 700494e11aSStefan Agner case CPU_CLUSTER_PM_ENTER: 710494e11aSStefan Agner vf610_mscm_ir_save(mscm_ir_data); 720494e11aSStefan Agner break; 730494e11aSStefan Agner case CPU_CLUSTER_PM_ENTER_FAILED: 740494e11aSStefan Agner case CPU_CLUSTER_PM_EXIT: 750494e11aSStefan Agner vf610_mscm_ir_restore(mscm_ir_data); 760494e11aSStefan Agner break; 770494e11aSStefan Agner } 780494e11aSStefan Agner 790494e11aSStefan Agner return NOTIFY_OK; 800494e11aSStefan Agner } 810494e11aSStefan Agner 820494e11aSStefan Agner static struct notifier_block mscm_ir_notifier_block = { 830494e11aSStefan Agner .notifier_call = vf610_mscm_ir_notifier, 840494e11aSStefan Agner }; 850494e11aSStefan Agner 860494e11aSStefan Agner static void vf610_mscm_ir_enable(struct irq_data *data) 870494e11aSStefan Agner { 880494e11aSStefan Agner irq_hw_number_t hwirq = data->hwirq; 890494e11aSStefan Agner struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; 900494e11aSStefan Agner u16 irsprc; 910494e11aSStefan Agner 920494e11aSStefan Agner irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); 930494e11aSStefan Agner irsprc &= MSCM_IRSPRC_CPEN_MASK; 940494e11aSStefan Agner 950494e11aSStefan Agner WARN_ON(irsprc & ~chip_data->cpu_mask); 960494e11aSStefan Agner 970494e11aSStefan Agner writew_relaxed(chip_data->cpu_mask, 980494e11aSStefan Agner chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); 990494e11aSStefan Agner 100b5cc5cbcSStefan Agner irq_chip_enable_parent(data); 1010494e11aSStefan Agner } 1020494e11aSStefan Agner 1030494e11aSStefan Agner static void vf610_mscm_ir_disable(struct irq_data *data) 1040494e11aSStefan Agner { 1050494e11aSStefan Agner irq_hw_number_t hwirq = data->hwirq; 1060494e11aSStefan Agner struct vf610_mscm_ir_chip_data *chip_data = data->chip_data; 1070494e11aSStefan Agner 1080494e11aSStefan Agner writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq)); 1090494e11aSStefan Agner 110b5cc5cbcSStefan Agner irq_chip_disable_parent(data); 1110494e11aSStefan Agner } 1120494e11aSStefan Agner 1130494e11aSStefan Agner static struct irq_chip vf610_mscm_ir_irq_chip = { 1140494e11aSStefan Agner .name = "mscm-ir", 1150494e11aSStefan Agner .irq_mask = irq_chip_mask_parent, 1160494e11aSStefan Agner .irq_unmask = irq_chip_unmask_parent, 1170494e11aSStefan Agner .irq_eoi = irq_chip_eoi_parent, 1180494e11aSStefan Agner .irq_enable = vf610_mscm_ir_enable, 1190494e11aSStefan Agner .irq_disable = vf610_mscm_ir_disable, 1200494e11aSStefan Agner .irq_retrigger = irq_chip_retrigger_hierarchy, 1210494e11aSStefan Agner .irq_set_affinity = irq_chip_set_affinity_parent, 1220494e11aSStefan Agner }; 1230494e11aSStefan Agner 1240494e11aSStefan Agner static int vf610_mscm_ir_domain_alloc(struct irq_domain *domain, unsigned int virq, 1250494e11aSStefan Agner unsigned int nr_irqs, void *arg) 1260494e11aSStefan Agner { 1270494e11aSStefan Agner int i; 1280494e11aSStefan Agner irq_hw_number_t hwirq; 129f833f57fSMarc Zyngier struct irq_fwspec *fwspec = arg; 130f833f57fSMarc Zyngier struct irq_fwspec parent_fwspec; 1310494e11aSStefan Agner 132f833f57fSMarc Zyngier if (!irq_domain_get_of_node(domain->parent)) 1330494e11aSStefan Agner return -EINVAL; 1340494e11aSStefan Agner 135f833f57fSMarc Zyngier if (fwspec->param_count != 2) 136f833f57fSMarc Zyngier return -EINVAL; 137f833f57fSMarc Zyngier 138f833f57fSMarc Zyngier hwirq = fwspec->param[0]; 1390494e11aSStefan Agner for (i = 0; i < nr_irqs; i++) 1400494e11aSStefan Agner irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 1410494e11aSStefan Agner &vf610_mscm_ir_irq_chip, 1420494e11aSStefan Agner domain->host_data); 1430494e11aSStefan Agner 144f833f57fSMarc Zyngier parent_fwspec.fwnode = domain->parent->fwnode; 145b5cc5cbcSStefan Agner 146b5cc5cbcSStefan Agner if (mscm_ir_data->is_nvic) { 147f833f57fSMarc Zyngier parent_fwspec.param_count = 1; 148f833f57fSMarc Zyngier parent_fwspec.param[0] = fwspec->param[0]; 149b5cc5cbcSStefan Agner } else { 150f833f57fSMarc Zyngier parent_fwspec.param_count = 3; 151f833f57fSMarc Zyngier parent_fwspec.param[0] = GIC_SPI; 152f833f57fSMarc Zyngier parent_fwspec.param[1] = fwspec->param[0]; 153f833f57fSMarc Zyngier parent_fwspec.param[2] = fwspec->param[1]; 154b5cc5cbcSStefan Agner } 155b5cc5cbcSStefan Agner 156f833f57fSMarc Zyngier return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, 157f833f57fSMarc Zyngier &parent_fwspec); 158f833f57fSMarc Zyngier } 159f833f57fSMarc Zyngier 160f833f57fSMarc Zyngier static int vf610_mscm_ir_domain_translate(struct irq_domain *d, 161f833f57fSMarc Zyngier struct irq_fwspec *fwspec, 162f833f57fSMarc Zyngier unsigned long *hwirq, 163f833f57fSMarc Zyngier unsigned int *type) 164f833f57fSMarc Zyngier { 165f833f57fSMarc Zyngier if (WARN_ON(fwspec->param_count < 2)) 166f833f57fSMarc Zyngier return -EINVAL; 167f833f57fSMarc Zyngier *hwirq = fwspec->param[0]; 168f833f57fSMarc Zyngier *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; 169f833f57fSMarc Zyngier return 0; 1700494e11aSStefan Agner } 1710494e11aSStefan Agner 1720494e11aSStefan Agner static const struct irq_domain_ops mscm_irq_domain_ops = { 173f833f57fSMarc Zyngier .translate = vf610_mscm_ir_domain_translate, 1740494e11aSStefan Agner .alloc = vf610_mscm_ir_domain_alloc, 1750494e11aSStefan Agner .free = irq_domain_free_irqs_common, 1760494e11aSStefan Agner }; 1770494e11aSStefan Agner 1780494e11aSStefan Agner static int __init vf610_mscm_ir_of_init(struct device_node *node, 1790494e11aSStefan Agner struct device_node *parent) 1800494e11aSStefan Agner { 1810494e11aSStefan Agner struct irq_domain *domain, *domain_parent; 1820494e11aSStefan Agner struct regmap *mscm_cp_regmap; 1830494e11aSStefan Agner int ret, cpuid; 1840494e11aSStefan Agner 1850494e11aSStefan Agner domain_parent = irq_find_host(parent); 1860494e11aSStefan Agner if (!domain_parent) { 1870494e11aSStefan Agner pr_err("vf610_mscm_ir: interrupt-parent not found\n"); 1880494e11aSStefan Agner return -EINVAL; 1890494e11aSStefan Agner } 1900494e11aSStefan Agner 1910494e11aSStefan Agner mscm_ir_data = kzalloc(sizeof(*mscm_ir_data), GFP_KERNEL); 1920494e11aSStefan Agner if (!mscm_ir_data) 1930494e11aSStefan Agner return -ENOMEM; 1940494e11aSStefan Agner 1950494e11aSStefan Agner mscm_ir_data->mscm_ir_base = of_io_request_and_map(node, 0, "mscm-ir"); 196dbf07cf0SMaxime Ripard if (IS_ERR(mscm_ir_data->mscm_ir_base)) { 1970494e11aSStefan Agner pr_err("vf610_mscm_ir: unable to map mscm register\n"); 198dbf07cf0SMaxime Ripard ret = PTR_ERR(mscm_ir_data->mscm_ir_base); 1990494e11aSStefan Agner goto out_free; 2000494e11aSStefan Agner } 2010494e11aSStefan Agner 2020494e11aSStefan Agner mscm_cp_regmap = syscon_regmap_lookup_by_phandle(node, "fsl,cpucfg"); 2030494e11aSStefan Agner if (IS_ERR(mscm_cp_regmap)) { 2040494e11aSStefan Agner ret = PTR_ERR(mscm_cp_regmap); 2050494e11aSStefan Agner pr_err("vf610_mscm_ir: regmap lookup for cpucfg failed\n"); 2060494e11aSStefan Agner goto out_unmap; 2070494e11aSStefan Agner } 2080494e11aSStefan Agner 2090494e11aSStefan Agner regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid); 2100494e11aSStefan Agner mscm_ir_data->cpu_mask = 0x1 << cpuid; 2110494e11aSStefan Agner 2120494e11aSStefan Agner domain = irq_domain_add_hierarchy(domain_parent, 0, 2130494e11aSStefan Agner MSCM_IRSPRC_NUM, node, 2140494e11aSStefan Agner &mscm_irq_domain_ops, mscm_ir_data); 2150494e11aSStefan Agner if (!domain) { 2160494e11aSStefan Agner ret = -ENOMEM; 2170494e11aSStefan Agner goto out_unmap; 2180494e11aSStefan Agner } 2190494e11aSStefan Agner 2205d4c9bc7SMarc Zyngier if (of_device_is_compatible(irq_domain_get_of_node(domain->parent), 2215d4c9bc7SMarc Zyngier "arm,armv7m-nvic")) 222b5cc5cbcSStefan Agner mscm_ir_data->is_nvic = true; 223b5cc5cbcSStefan Agner 2240494e11aSStefan Agner cpu_pm_register_notifier(&mscm_ir_notifier_block); 2250494e11aSStefan Agner 2260494e11aSStefan Agner return 0; 2270494e11aSStefan Agner 2280494e11aSStefan Agner out_unmap: 2290494e11aSStefan Agner iounmap(mscm_ir_data->mscm_ir_base); 2300494e11aSStefan Agner out_free: 2310494e11aSStefan Agner kfree(mscm_ir_data); 2320494e11aSStefan Agner return ret; 2330494e11aSStefan Agner } 2340494e11aSStefan Agner IRQCHIP_DECLARE(vf610_mscm_ir, "fsl,vf610-mscm-ir", vf610_mscm_ir_of_init); 235