1 /* 2 * Copyright (c) 2014 MediaTek Inc. 3 * Author: Joe.C <yingjoe.chen@mediatek.com> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15 #include <linux/irq.h> 16 #include <linux/irqchip.h> 17 #include <linux/irqdomain.h> 18 #include <linux/of.h> 19 #include <linux/of_irq.h> 20 #include <linux/of_address.h> 21 #include <linux/io.h> 22 #include <linux/slab.h> 23 #include <linux/spinlock.h> 24 25 struct mtk_sysirq_chip_data { 26 spinlock_t lock; 27 u32 nr_intpol_bases; 28 void __iomem **intpol_bases; 29 u32 *intpol_words; 30 u8 *intpol_idx; 31 u16 *which_word; 32 }; 33 34 static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) 35 { 36 irq_hw_number_t hwirq = data->hwirq; 37 struct mtk_sysirq_chip_data *chip_data = data->chip_data; 38 u8 intpol_idx = chip_data->intpol_idx[hwirq]; 39 void __iomem *base; 40 u32 offset, reg_index, value; 41 unsigned long flags; 42 int ret; 43 44 base = chip_data->intpol_bases[intpol_idx]; 45 reg_index = chip_data->which_word[hwirq]; 46 offset = hwirq & 0x1f; 47 48 spin_lock_irqsave(&chip_data->lock, flags); 49 value = readl_relaxed(base + reg_index * 4); 50 if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING) { 51 if (type == IRQ_TYPE_LEVEL_LOW) 52 type = IRQ_TYPE_LEVEL_HIGH; 53 else 54 type = IRQ_TYPE_EDGE_RISING; 55 value |= (1 << offset); 56 } else { 57 value &= ~(1 << offset); 58 } 59 60 writel_relaxed(value, base + reg_index * 4); 61 62 data = data->parent_data; 63 ret = data->chip->irq_set_type(data, type); 64 spin_unlock_irqrestore(&chip_data->lock, flags); 65 return ret; 66 } 67 68 static struct irq_chip mtk_sysirq_chip = { 69 .name = "MT_SYSIRQ", 70 .irq_mask = irq_chip_mask_parent, 71 .irq_unmask = irq_chip_unmask_parent, 72 .irq_eoi = irq_chip_eoi_parent, 73 .irq_set_type = mtk_sysirq_set_type, 74 .irq_retrigger = irq_chip_retrigger_hierarchy, 75 .irq_set_affinity = irq_chip_set_affinity_parent, 76 }; 77 78 static int mtk_sysirq_domain_translate(struct irq_domain *d, 79 struct irq_fwspec *fwspec, 80 unsigned long *hwirq, 81 unsigned int *type) 82 { 83 if (is_of_node(fwspec->fwnode)) { 84 if (fwspec->param_count != 3) 85 return -EINVAL; 86 87 /* No PPI should point to this domain */ 88 if (fwspec->param[0] != 0) 89 return -EINVAL; 90 91 *hwirq = fwspec->param[1]; 92 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 93 return 0; 94 } 95 96 return -EINVAL; 97 } 98 99 static int mtk_sysirq_domain_alloc(struct irq_domain *domain, unsigned int virq, 100 unsigned int nr_irqs, void *arg) 101 { 102 int i; 103 irq_hw_number_t hwirq; 104 struct irq_fwspec *fwspec = arg; 105 struct irq_fwspec gic_fwspec = *fwspec; 106 107 if (fwspec->param_count != 3) 108 return -EINVAL; 109 110 /* sysirq doesn't support PPI */ 111 if (fwspec->param[0]) 112 return -EINVAL; 113 114 hwirq = fwspec->param[1]; 115 for (i = 0; i < nr_irqs; i++) 116 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 117 &mtk_sysirq_chip, 118 domain->host_data); 119 120 gic_fwspec.fwnode = domain->parent->fwnode; 121 return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec); 122 } 123 124 static const struct irq_domain_ops sysirq_domain_ops = { 125 .translate = mtk_sysirq_domain_translate, 126 .alloc = mtk_sysirq_domain_alloc, 127 .free = irq_domain_free_irqs_common, 128 }; 129 130 static int __init mtk_sysirq_of_init(struct device_node *node, 131 struct device_node *parent) 132 { 133 struct irq_domain *domain, *domain_parent; 134 struct mtk_sysirq_chip_data *chip_data; 135 int ret, size, intpol_num = 0, nr_intpol_bases = 0, i = 0; 136 137 domain_parent = irq_find_host(parent); 138 if (!domain_parent) { 139 pr_err("mtk_sysirq: interrupt-parent not found\n"); 140 return -EINVAL; 141 } 142 143 chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL); 144 if (!chip_data) 145 return -ENOMEM; 146 147 while (of_get_address(node, i++, NULL, NULL)) 148 nr_intpol_bases++; 149 150 if (nr_intpol_bases == 0) { 151 pr_err("mtk_sysirq: base address not specified\n"); 152 ret = -EINVAL; 153 goto out_free_chip; 154 } 155 156 chip_data->intpol_words = kcalloc(nr_intpol_bases, 157 sizeof(*chip_data->intpol_words), 158 GFP_KERNEL); 159 if (!chip_data->intpol_words) { 160 ret = -ENOMEM; 161 goto out_free_chip; 162 } 163 164 chip_data->intpol_bases = kcalloc(nr_intpol_bases, 165 sizeof(*chip_data->intpol_bases), 166 GFP_KERNEL); 167 if (!chip_data->intpol_bases) { 168 ret = -ENOMEM; 169 goto out_free_intpol_words; 170 } 171 172 for (i = 0; i < nr_intpol_bases; i++) { 173 struct resource res; 174 175 ret = of_address_to_resource(node, i, &res); 176 size = resource_size(&res); 177 intpol_num += size * 8; 178 chip_data->intpol_words[i] = size / 4; 179 chip_data->intpol_bases[i] = of_iomap(node, i); 180 if (ret || !chip_data->intpol_bases[i]) { 181 pr_err("%s: couldn't map region %d\n", 182 node->full_name, i); 183 ret = -ENODEV; 184 goto out_free_intpol; 185 } 186 } 187 188 chip_data->intpol_idx = kcalloc(intpol_num, 189 sizeof(*chip_data->intpol_idx), 190 GFP_KERNEL); 191 if (!chip_data->intpol_idx) { 192 ret = -ENOMEM; 193 goto out_free_intpol; 194 } 195 196 chip_data->which_word = kcalloc(intpol_num, 197 sizeof(*chip_data->which_word), 198 GFP_KERNEL); 199 if (!chip_data->which_word) { 200 ret = -ENOMEM; 201 goto out_free_intpol_idx; 202 } 203 204 /* 205 * assign an index of the intpol_bases for each irq 206 * to set it fast later 207 */ 208 for (i = 0; i < intpol_num ; i++) { 209 u32 word = i / 32, j; 210 211 for (j = 0; word >= chip_data->intpol_words[j] ; j++) 212 word -= chip_data->intpol_words[j]; 213 214 chip_data->intpol_idx[i] = j; 215 chip_data->which_word[i] = word; 216 } 217 218 domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node, 219 &sysirq_domain_ops, chip_data); 220 if (!domain) { 221 ret = -ENOMEM; 222 goto out_free_which_word; 223 } 224 spin_lock_init(&chip_data->lock); 225 226 return 0; 227 228 out_free_which_word: 229 kfree(chip_data->which_word); 230 out_free_intpol_idx: 231 kfree(chip_data->intpol_idx); 232 out_free_intpol: 233 for (i = 0; i < nr_intpol_bases; i++) 234 if (chip_data->intpol_bases[i]) 235 iounmap(chip_data->intpol_bases[i]); 236 kfree(chip_data->intpol_bases); 237 out_free_intpol_words: 238 kfree(chip_data->intpol_words); 239 out_free_chip: 240 kfree(chip_data); 241 return ret; 242 } 243 IRQCHIP_DECLARE(mtk_sysirq, "mediatek,mt6577-sysirq", mtk_sysirq_of_init); 244