irq-mst-intc.c (7246B)
1// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 2/* 3 * Copyright (c) 2020 MediaTek Inc. 4 * Author Mark-PK Tsai <mark-pk.tsai@mediatek.com> 5 */ 6#include <linux/interrupt.h> 7#include <linux/io.h> 8#include <linux/irq.h> 9#include <linux/irqchip.h> 10#include <linux/irqdomain.h> 11#include <linux/of.h> 12#include <linux/of_address.h> 13#include <linux/of_irq.h> 14#include <linux/slab.h> 15#include <linux/spinlock.h> 16#include <linux/syscore_ops.h> 17 18#define MST_INTC_MAX_IRQS 64 19 20#define INTC_MASK 0x0 21#define INTC_REV_POLARITY 0x10 22#define INTC_EOI 0x20 23 24#ifdef CONFIG_PM_SLEEP 25static LIST_HEAD(mst_intc_list); 26#endif 27 28struct mst_intc_chip_data { 29 raw_spinlock_t lock; 30 unsigned int irq_start, nr_irqs; 31 void __iomem *base; 32 bool no_eoi; 33#ifdef CONFIG_PM_SLEEP 34 struct list_head entry; 35 u16 saved_polarity_conf[DIV_ROUND_UP(MST_INTC_MAX_IRQS, 16)]; 36#endif 37}; 38 39static void mst_set_irq(struct irq_data *d, u32 offset) 40{ 41 irq_hw_number_t hwirq = irqd_to_hwirq(d); 42 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 43 u16 val, mask; 44 unsigned long flags; 45 46 mask = 1 << (hwirq % 16); 47 offset += (hwirq / 16) * 4; 48 49 raw_spin_lock_irqsave(&cd->lock, flags); 50 val = readw_relaxed(cd->base + offset) | mask; 51 writew_relaxed(val, cd->base + offset); 52 raw_spin_unlock_irqrestore(&cd->lock, flags); 53} 54 55static void mst_clear_irq(struct irq_data *d, u32 offset) 56{ 57 irq_hw_number_t hwirq = irqd_to_hwirq(d); 58 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 59 u16 val, mask; 60 unsigned long flags; 61 62 mask = 1 << (hwirq % 16); 63 offset += (hwirq / 16) * 4; 64 65 raw_spin_lock_irqsave(&cd->lock, flags); 66 val = readw_relaxed(cd->base + offset) & ~mask; 67 writew_relaxed(val, cd->base + offset); 68 raw_spin_unlock_irqrestore(&cd->lock, flags); 69} 70 71static void mst_intc_mask_irq(struct irq_data *d) 72{ 73 mst_set_irq(d, INTC_MASK); 74 irq_chip_mask_parent(d); 75} 76 77static void mst_intc_unmask_irq(struct irq_data *d) 78{ 79 mst_clear_irq(d, INTC_MASK); 80 irq_chip_unmask_parent(d); 81} 82 83static void mst_intc_eoi_irq(struct irq_data *d) 84{ 85 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 86 87 if (!cd->no_eoi) 88 mst_set_irq(d, INTC_EOI); 89 90 irq_chip_eoi_parent(d); 91} 92 93static int mst_irq_chip_set_type(struct irq_data *data, unsigned int type) 94{ 95 switch (type) { 96 case IRQ_TYPE_LEVEL_LOW: 97 case IRQ_TYPE_EDGE_FALLING: 98 mst_set_irq(data, INTC_REV_POLARITY); 99 break; 100 case IRQ_TYPE_LEVEL_HIGH: 101 case IRQ_TYPE_EDGE_RISING: 102 mst_clear_irq(data, INTC_REV_POLARITY); 103 break; 104 default: 105 return -EINVAL; 106 } 107 108 return irq_chip_set_type_parent(data, IRQ_TYPE_LEVEL_HIGH); 109} 110 111static struct irq_chip mst_intc_chip = { 112 .name = "mst-intc", 113 .irq_mask = mst_intc_mask_irq, 114 .irq_unmask = mst_intc_unmask_irq, 115 .irq_eoi = mst_intc_eoi_irq, 116 .irq_get_irqchip_state = irq_chip_get_parent_state, 117 .irq_set_irqchip_state = irq_chip_set_parent_state, 118 .irq_set_affinity = irq_chip_set_affinity_parent, 119 .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, 120 .irq_set_type = mst_irq_chip_set_type, 121 .irq_retrigger = irq_chip_retrigger_hierarchy, 122 .flags = IRQCHIP_SET_TYPE_MASKED | 123 IRQCHIP_SKIP_SET_WAKE | 124 IRQCHIP_MASK_ON_SUSPEND, 125}; 126 127#ifdef CONFIG_PM_SLEEP 128static void mst_intc_polarity_save(struct mst_intc_chip_data *cd) 129{ 130 int i; 131 void __iomem *addr = cd->base + INTC_REV_POLARITY; 132 133 for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++) 134 cd->saved_polarity_conf[i] = readw_relaxed(addr + i * 4); 135} 136 137static void mst_intc_polarity_restore(struct mst_intc_chip_data *cd) 138{ 139 int i; 140 void __iomem *addr = cd->base + INTC_REV_POLARITY; 141 142 for (i = 0; i < DIV_ROUND_UP(cd->nr_irqs, 16); i++) 143 writew_relaxed(cd->saved_polarity_conf[i], addr + i * 4); 144} 145 146static void mst_irq_resume(void) 147{ 148 struct mst_intc_chip_data *cd; 149 150 list_for_each_entry(cd, &mst_intc_list, entry) 151 mst_intc_polarity_restore(cd); 152} 153 154static int mst_irq_suspend(void) 155{ 156 struct mst_intc_chip_data *cd; 157 158 list_for_each_entry(cd, &mst_intc_list, entry) 159 mst_intc_polarity_save(cd); 160 return 0; 161} 162 163static struct syscore_ops mst_irq_syscore_ops = { 164 .suspend = mst_irq_suspend, 165 .resume = mst_irq_resume, 166}; 167 168static int __init mst_irq_pm_init(void) 169{ 170 register_syscore_ops(&mst_irq_syscore_ops); 171 return 0; 172} 173late_initcall(mst_irq_pm_init); 174#endif 175 176static int mst_intc_domain_translate(struct irq_domain *d, 177 struct irq_fwspec *fwspec, 178 unsigned long *hwirq, 179 unsigned int *type) 180{ 181 struct mst_intc_chip_data *cd = d->host_data; 182 183 if (is_of_node(fwspec->fwnode)) { 184 if (fwspec->param_count != 3) 185 return -EINVAL; 186 187 /* No PPI should point to this domain */ 188 if (fwspec->param[0] != 0) 189 return -EINVAL; 190 191 if (fwspec->param[1] >= cd->nr_irqs) 192 return -EINVAL; 193 194 *hwirq = fwspec->param[1]; 195 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 196 return 0; 197 } 198 199 return -EINVAL; 200} 201 202static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq, 203 unsigned int nr_irqs, void *data) 204{ 205 int i; 206 irq_hw_number_t hwirq; 207 struct irq_fwspec parent_fwspec, *fwspec = data; 208 struct mst_intc_chip_data *cd = domain->host_data; 209 210 /* Not GIC compliant */ 211 if (fwspec->param_count != 3) 212 return -EINVAL; 213 214 /* No PPI should point to this domain */ 215 if (fwspec->param[0]) 216 return -EINVAL; 217 218 hwirq = fwspec->param[1]; 219 for (i = 0; i < nr_irqs; i++) 220 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 221 &mst_intc_chip, 222 domain->host_data); 223 224 parent_fwspec = *fwspec; 225 parent_fwspec.fwnode = domain->parent->fwnode; 226 parent_fwspec.param[1] = cd->irq_start + hwirq; 227 228 /* 229 * mst-intc latch the interrupt request if it's edge triggered, 230 * so the output signal to parent GIC is always level sensitive. 231 * And if the irq signal is active low, configure it to active high 232 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit. 233 */ 234 parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; 235 236 return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); 237} 238 239static const struct irq_domain_ops mst_intc_domain_ops = { 240 .translate = mst_intc_domain_translate, 241 .alloc = mst_intc_domain_alloc, 242 .free = irq_domain_free_irqs_common, 243}; 244 245static int __init mst_intc_of_init(struct device_node *dn, 246 struct device_node *parent) 247{ 248 struct irq_domain *domain, *domain_parent; 249 struct mst_intc_chip_data *cd; 250 u32 irq_start, irq_end; 251 252 domain_parent = irq_find_host(parent); 253 if (!domain_parent) { 254 pr_err("mst-intc: interrupt-parent not found\n"); 255 return -EINVAL; 256 } 257 258 if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) || 259 of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end)) 260 return -EINVAL; 261 262 cd = kzalloc(sizeof(*cd), GFP_KERNEL); 263 if (!cd) 264 return -ENOMEM; 265 266 cd->base = of_iomap(dn, 0); 267 if (!cd->base) { 268 kfree(cd); 269 return -ENOMEM; 270 } 271 272 cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi"); 273 raw_spin_lock_init(&cd->lock); 274 cd->irq_start = irq_start; 275 cd->nr_irqs = irq_end - irq_start + 1; 276 domain = irq_domain_add_hierarchy(domain_parent, 0, cd->nr_irqs, dn, 277 &mst_intc_domain_ops, cd); 278 if (!domain) { 279 iounmap(cd->base); 280 kfree(cd); 281 return -ENOMEM; 282 } 283 284#ifdef CONFIG_PM_SLEEP 285 INIT_LIST_HEAD(&cd->entry); 286 list_add_tail(&cd->entry, &mst_intc_list); 287#endif 288 return 0; 289} 290 291IRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init);