15fe3bba3SYingjoe Chen /* 25fe3bba3SYingjoe Chen * Copyright (c) 2014 MediaTek Inc. 35fe3bba3SYingjoe Chen * Author: Joe.C <yingjoe.chen@mediatek.com> 45fe3bba3SYingjoe Chen * 55fe3bba3SYingjoe Chen * This program is free software; you can redistribute it and/or modify 65fe3bba3SYingjoe Chen * it under the terms of the GNU General Public License version 2 as 75fe3bba3SYingjoe Chen * published by the Free Software Foundation. 85fe3bba3SYingjoe Chen * 95fe3bba3SYingjoe Chen * This program is distributed in the hope that it will be useful, 105fe3bba3SYingjoe Chen * but WITHOUT ANY WARRANTY; without even the implied warranty of 115fe3bba3SYingjoe Chen * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 125fe3bba3SYingjoe Chen * GNU General Public License for more details. 135fe3bba3SYingjoe Chen */ 145fe3bba3SYingjoe Chen 155fe3bba3SYingjoe Chen #include <linux/irq.h> 1641a83e06SJoel Porquet #include <linux/irqchip.h> 175fe3bba3SYingjoe Chen #include <linux/irqdomain.h> 185fe3bba3SYingjoe Chen #include <linux/of.h> 195fe3bba3SYingjoe Chen #include <linux/of_irq.h> 205fe3bba3SYingjoe Chen #include <linux/of_address.h> 215fe3bba3SYingjoe Chen #include <linux/io.h> 225fe3bba3SYingjoe Chen #include <linux/slab.h> 235fe3bba3SYingjoe Chen #include <linux/spinlock.h> 245fe3bba3SYingjoe Chen 255fe3bba3SYingjoe Chen struct mtk_sysirq_chip_data { 265fe3bba3SYingjoe Chen spinlock_t lock; 2713683f9bSMars Cheng u32 nr_intpol_bases; 2813683f9bSMars Cheng void __iomem **intpol_bases; 2913683f9bSMars Cheng u32 *intpol_words; 3013683f9bSMars Cheng u8 *intpol_idx; 3113683f9bSMars Cheng u16 *which_word; 325fe3bba3SYingjoe Chen }; 335fe3bba3SYingjoe Chen 345fe3bba3SYingjoe Chen static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) 355fe3bba3SYingjoe Chen { 365fe3bba3SYingjoe Chen irq_hw_number_t hwirq = data->hwirq; 375fe3bba3SYingjoe Chen struct mtk_sysirq_chip_data *chip_data = data->chip_data; 3813683f9bSMars Cheng u8 intpol_idx = chip_data->intpol_idx[hwirq]; 3913683f9bSMars Cheng void __iomem *base; 405fe3bba3SYingjoe Chen u32 offset, reg_index, value; 415fe3bba3SYingjoe Chen unsigned long flags; 425fe3bba3SYingjoe Chen int ret; 435fe3bba3SYingjoe Chen 4413683f9bSMars Cheng base = chip_data->intpol_bases[intpol_idx]; 4513683f9bSMars Cheng reg_index = chip_data->which_word[hwirq]; 465fe3bba3SYingjoe Chen offset = hwirq & 0x1f; 475fe3bba3SYingjoe Chen 485fe3bba3SYingjoe Chen spin_lock_irqsave(&chip_data->lock, flags); 4913683f9bSMars Cheng value = readl_relaxed(base + reg_index * 4); 505fe3bba3SYingjoe Chen if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING) { 515fe3bba3SYingjoe Chen if (type == IRQ_TYPE_LEVEL_LOW) 525fe3bba3SYingjoe Chen type = IRQ_TYPE_LEVEL_HIGH; 535fe3bba3SYingjoe Chen else 545fe3bba3SYingjoe Chen type = IRQ_TYPE_EDGE_RISING; 555fe3bba3SYingjoe Chen value |= (1 << offset); 565fe3bba3SYingjoe Chen } else { 575fe3bba3SYingjoe Chen value &= ~(1 << offset); 585fe3bba3SYingjoe Chen } 5913683f9bSMars Cheng 605e11d16cSMars Cheng writel_relaxed(value, base + reg_index * 4); 615fe3bba3SYingjoe Chen 625fe3bba3SYingjoe Chen data = data->parent_data; 635fe3bba3SYingjoe Chen ret = data->chip->irq_set_type(data, type); 645fe3bba3SYingjoe Chen spin_unlock_irqrestore(&chip_data->lock, flags); 655fe3bba3SYingjoe Chen return ret; 665fe3bba3SYingjoe Chen } 675fe3bba3SYingjoe Chen 685fe3bba3SYingjoe Chen static struct irq_chip mtk_sysirq_chip = { 695fe3bba3SYingjoe Chen .name = "MT_SYSIRQ", 705fe3bba3SYingjoe Chen .irq_mask = irq_chip_mask_parent, 715fe3bba3SYingjoe Chen .irq_unmask = irq_chip_unmask_parent, 725fe3bba3SYingjoe Chen .irq_eoi = irq_chip_eoi_parent, 735fe3bba3SYingjoe Chen .irq_set_type = mtk_sysirq_set_type, 745fe3bba3SYingjoe Chen .irq_retrigger = irq_chip_retrigger_hierarchy, 755fe3bba3SYingjoe Chen .irq_set_affinity = irq_chip_set_affinity_parent, 765fe3bba3SYingjoe Chen }; 775fe3bba3SYingjoe Chen 78f833f57fSMarc Zyngier static int mtk_sysirq_domain_translate(struct irq_domain *d, 79f833f57fSMarc Zyngier struct irq_fwspec *fwspec, 80f833f57fSMarc Zyngier unsigned long *hwirq, 81f833f57fSMarc Zyngier unsigned int *type) 825fe3bba3SYingjoe Chen { 83f833f57fSMarc Zyngier if (is_of_node(fwspec->fwnode)) { 84f833f57fSMarc Zyngier if (fwspec->param_count != 3) 855fe3bba3SYingjoe Chen return -EINVAL; 865fe3bba3SYingjoe Chen 87f833f57fSMarc Zyngier /* No PPI should point to this domain */ 88f833f57fSMarc Zyngier if (fwspec->param[0] != 0) 895fe3bba3SYingjoe Chen return -EINVAL; 905fe3bba3SYingjoe Chen 91f833f57fSMarc Zyngier *hwirq = fwspec->param[1]; 92f833f57fSMarc Zyngier *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 935fe3bba3SYingjoe Chen return 0; 945fe3bba3SYingjoe Chen } 955fe3bba3SYingjoe Chen 96f833f57fSMarc Zyngier return -EINVAL; 97f833f57fSMarc Zyngier } 98f833f57fSMarc Zyngier 995fe3bba3SYingjoe Chen static int mtk_sysirq_domain_alloc(struct irq_domain *domain, unsigned int virq, 1005fe3bba3SYingjoe Chen unsigned int nr_irqs, void *arg) 1015fe3bba3SYingjoe Chen { 1025fe3bba3SYingjoe Chen int i; 1035fe3bba3SYingjoe Chen irq_hw_number_t hwirq; 104f833f57fSMarc Zyngier struct irq_fwspec *fwspec = arg; 105f833f57fSMarc Zyngier struct irq_fwspec gic_fwspec = *fwspec; 1065fe3bba3SYingjoe Chen 107f833f57fSMarc Zyngier if (fwspec->param_count != 3) 1085fe3bba3SYingjoe Chen return -EINVAL; 1095fe3bba3SYingjoe Chen 1105fe3bba3SYingjoe Chen /* sysirq doesn't support PPI */ 111f833f57fSMarc Zyngier if (fwspec->param[0]) 1125fe3bba3SYingjoe Chen return -EINVAL; 1135fe3bba3SYingjoe Chen 114f833f57fSMarc Zyngier hwirq = fwspec->param[1]; 1155fe3bba3SYingjoe Chen for (i = 0; i < nr_irqs; i++) 1165fe3bba3SYingjoe Chen irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 1175fe3bba3SYingjoe Chen &mtk_sysirq_chip, 1185fe3bba3SYingjoe Chen domain->host_data); 1195fe3bba3SYingjoe Chen 120f833f57fSMarc Zyngier gic_fwspec.fwnode = domain->parent->fwnode; 121f833f57fSMarc Zyngier return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec); 1225fe3bba3SYingjoe Chen } 1235fe3bba3SYingjoe Chen 12496009736SKrzysztof Kozlowski static const struct irq_domain_ops sysirq_domain_ops = { 125f833f57fSMarc Zyngier .translate = mtk_sysirq_domain_translate, 1265fe3bba3SYingjoe Chen .alloc = mtk_sysirq_domain_alloc, 1275fe3bba3SYingjoe Chen .free = irq_domain_free_irqs_common, 1285fe3bba3SYingjoe Chen }; 1295fe3bba3SYingjoe Chen 1305fe3bba3SYingjoe Chen static int __init mtk_sysirq_of_init(struct device_node *node, 1315fe3bba3SYingjoe Chen struct device_node *parent) 1325fe3bba3SYingjoe Chen { 1335fe3bba3SYingjoe Chen struct irq_domain *domain, *domain_parent; 1345fe3bba3SYingjoe Chen struct mtk_sysirq_chip_data *chip_data; 13513683f9bSMars Cheng int ret, size, intpol_num = 0, nr_intpol_bases = 0, i = 0; 1365fe3bba3SYingjoe Chen 1375fe3bba3SYingjoe Chen domain_parent = irq_find_host(parent); 1385fe3bba3SYingjoe Chen if (!domain_parent) { 1395fe3bba3SYingjoe Chen pr_err("mtk_sysirq: interrupt-parent not found\n"); 1405fe3bba3SYingjoe Chen return -EINVAL; 1415fe3bba3SYingjoe Chen } 1425fe3bba3SYingjoe Chen 1435fe3bba3SYingjoe Chen chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL); 1445fe3bba3SYingjoe Chen if (!chip_data) 1455fe3bba3SYingjoe Chen return -ENOMEM; 1465fe3bba3SYingjoe Chen 14713683f9bSMars Cheng while (of_get_address(node, i++, NULL, NULL)) 14813683f9bSMars Cheng nr_intpol_bases++; 14913683f9bSMars Cheng 15013683f9bSMars Cheng if (nr_intpol_bases == 0) { 15113683f9bSMars Cheng pr_err("mtk_sysirq: base address not specified\n"); 15213683f9bSMars Cheng ret = -EINVAL; 15313683f9bSMars Cheng goto out_free_chip; 15413683f9bSMars Cheng } 15513683f9bSMars Cheng 15613683f9bSMars Cheng chip_data->intpol_words = kcalloc(nr_intpol_bases, 15713683f9bSMars Cheng sizeof(*chip_data->intpol_words), 15813683f9bSMars Cheng GFP_KERNEL); 15913683f9bSMars Cheng if (!chip_data->intpol_words) { 16013683f9bSMars Cheng ret = -ENOMEM; 16113683f9bSMars Cheng goto out_free_chip; 16213683f9bSMars Cheng } 16313683f9bSMars Cheng 16413683f9bSMars Cheng chip_data->intpol_bases = kcalloc(nr_intpol_bases, 16513683f9bSMars Cheng sizeof(*chip_data->intpol_bases), 16613683f9bSMars Cheng GFP_KERNEL); 16713683f9bSMars Cheng if (!chip_data->intpol_bases) { 16813683f9bSMars Cheng ret = -ENOMEM; 16913683f9bSMars Cheng goto out_free_intpol_words; 17013683f9bSMars Cheng } 17113683f9bSMars Cheng 17213683f9bSMars Cheng for (i = 0; i < nr_intpol_bases; i++) { 17313683f9bSMars Cheng struct resource res; 17413683f9bSMars Cheng 17513683f9bSMars Cheng ret = of_address_to_resource(node, i, &res); 176cdb647a7SYingjoe Chen size = resource_size(&res); 17713683f9bSMars Cheng intpol_num += size * 8; 17813683f9bSMars Cheng chip_data->intpol_words[i] = size / 4; 17913683f9bSMars Cheng chip_data->intpol_bases[i] = of_iomap(node, i); 18013683f9bSMars Cheng if (ret || !chip_data->intpol_bases[i]) { 181e81f54c6SRob Herring pr_err("%pOF: couldn't map region %d\n", node, i); 18213683f9bSMars Cheng ret = -ENODEV; 18313683f9bSMars Cheng goto out_free_intpol; 18413683f9bSMars Cheng } 18513683f9bSMars Cheng } 18613683f9bSMars Cheng 18713683f9bSMars Cheng chip_data->intpol_idx = kcalloc(intpol_num, 18813683f9bSMars Cheng sizeof(*chip_data->intpol_idx), 18913683f9bSMars Cheng GFP_KERNEL); 19013683f9bSMars Cheng if (!chip_data->intpol_idx) { 19113683f9bSMars Cheng ret = -ENOMEM; 19213683f9bSMars Cheng goto out_free_intpol; 19313683f9bSMars Cheng } 19413683f9bSMars Cheng 19513683f9bSMars Cheng chip_data->which_word = kcalloc(intpol_num, 19613683f9bSMars Cheng sizeof(*chip_data->which_word), 19713683f9bSMars Cheng GFP_KERNEL); 19813683f9bSMars Cheng if (!chip_data->which_word) { 19913683f9bSMars Cheng ret = -ENOMEM; 20013683f9bSMars Cheng goto out_free_intpol_idx; 20113683f9bSMars Cheng } 20213683f9bSMars Cheng 20313683f9bSMars Cheng /* 20413683f9bSMars Cheng * assign an index of the intpol_bases for each irq 20513683f9bSMars Cheng * to set it fast later 20613683f9bSMars Cheng */ 20713683f9bSMars Cheng for (i = 0; i < intpol_num ; i++) { 20813683f9bSMars Cheng u32 word = i / 32, j; 20913683f9bSMars Cheng 21013683f9bSMars Cheng for (j = 0; word >= chip_data->intpol_words[j] ; j++) 21113683f9bSMars Cheng word -= chip_data->intpol_words[j]; 21213683f9bSMars Cheng 21313683f9bSMars Cheng chip_data->intpol_idx[i] = j; 21413683f9bSMars Cheng chip_data->which_word[i] = word; 2155fe3bba3SYingjoe Chen } 2165fe3bba3SYingjoe Chen 217cdb647a7SYingjoe Chen domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node, 2185fe3bba3SYingjoe Chen &sysirq_domain_ops, chip_data); 2195fe3bba3SYingjoe Chen if (!domain) { 2205fe3bba3SYingjoe Chen ret = -ENOMEM; 22113683f9bSMars Cheng goto out_free_which_word; 2225fe3bba3SYingjoe Chen } 2235fe3bba3SYingjoe Chen spin_lock_init(&chip_data->lock); 2245fe3bba3SYingjoe Chen 2255fe3bba3SYingjoe Chen return 0; 2265fe3bba3SYingjoe Chen 22713683f9bSMars Cheng out_free_which_word: 22813683f9bSMars Cheng kfree(chip_data->which_word); 22913683f9bSMars Cheng out_free_intpol_idx: 23013683f9bSMars Cheng kfree(chip_data->intpol_idx); 23113683f9bSMars Cheng out_free_intpol: 23213683f9bSMars Cheng for (i = 0; i < nr_intpol_bases; i++) 23313683f9bSMars Cheng if (chip_data->intpol_bases[i]) 23413683f9bSMars Cheng iounmap(chip_data->intpol_bases[i]); 23513683f9bSMars Cheng kfree(chip_data->intpol_bases); 23613683f9bSMars Cheng out_free_intpol_words: 23713683f9bSMars Cheng kfree(chip_data->intpol_words); 23813683f9bSMars Cheng out_free_chip: 2395fe3bba3SYingjoe Chen kfree(chip_data); 2405fe3bba3SYingjoe Chen return ret; 2415fe3bba3SYingjoe Chen } 2425fe3bba3SYingjoe Chen IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init); 243