Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 295 lines 7.3 kB view raw
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 *data) 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 *data) 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 const struct syscore_ops mst_irq_syscore_ops = { 164 .suspend = mst_irq_suspend, 165 .resume = mst_irq_resume, 166}; 167 168static struct syscore mst_irq_syscore = { 169 .ops = &mst_irq_syscore_ops, 170}; 171 172static int __init mst_irq_pm_init(void) 173{ 174 register_syscore(&mst_irq_syscore); 175 return 0; 176} 177late_initcall(mst_irq_pm_init); 178#endif 179 180static int mst_intc_domain_translate(struct irq_domain *d, 181 struct irq_fwspec *fwspec, 182 unsigned long *hwirq, 183 unsigned int *type) 184{ 185 struct mst_intc_chip_data *cd = d->host_data; 186 187 if (is_of_node(fwspec->fwnode)) { 188 if (fwspec->param_count != 3) 189 return -EINVAL; 190 191 /* No PPI should point to this domain */ 192 if (fwspec->param[0] != 0) 193 return -EINVAL; 194 195 if (fwspec->param[1] >= cd->nr_irqs) 196 return -EINVAL; 197 198 *hwirq = fwspec->param[1]; 199 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 200 return 0; 201 } 202 203 return -EINVAL; 204} 205 206static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq, 207 unsigned int nr_irqs, void *data) 208{ 209 int i; 210 irq_hw_number_t hwirq; 211 struct irq_fwspec parent_fwspec, *fwspec = data; 212 struct mst_intc_chip_data *cd = domain->host_data; 213 214 /* Not GIC compliant */ 215 if (fwspec->param_count != 3) 216 return -EINVAL; 217 218 /* No PPI should point to this domain */ 219 if (fwspec->param[0]) 220 return -EINVAL; 221 222 hwirq = fwspec->param[1]; 223 for (i = 0; i < nr_irqs; i++) 224 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 225 &mst_intc_chip, 226 domain->host_data); 227 228 parent_fwspec = *fwspec; 229 parent_fwspec.fwnode = domain->parent->fwnode; 230 parent_fwspec.param[1] = cd->irq_start + hwirq; 231 232 /* 233 * mst-intc latch the interrupt request if it's edge triggered, 234 * so the output signal to parent GIC is always level sensitive. 235 * And if the irq signal is active low, configure it to active high 236 * to meet GIC SPI spec in mst_irq_chip_set_type via REV_POLARITY bit. 237 */ 238 parent_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; 239 240 return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); 241} 242 243static const struct irq_domain_ops mst_intc_domain_ops = { 244 .translate = mst_intc_domain_translate, 245 .alloc = mst_intc_domain_alloc, 246 .free = irq_domain_free_irqs_common, 247}; 248 249static int __init mst_intc_of_init(struct device_node *dn, 250 struct device_node *parent) 251{ 252 struct irq_domain *domain, *domain_parent; 253 struct mst_intc_chip_data *cd; 254 u32 irq_start, irq_end; 255 256 domain_parent = irq_find_host(parent); 257 if (!domain_parent) { 258 pr_err("mst-intc: interrupt-parent not found\n"); 259 return -EINVAL; 260 } 261 262 if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) || 263 of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end)) 264 return -EINVAL; 265 266 cd = kzalloc_obj(*cd); 267 if (!cd) 268 return -ENOMEM; 269 270 cd->base = of_iomap(dn, 0); 271 if (!cd->base) { 272 kfree(cd); 273 return -ENOMEM; 274 } 275 276 cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi"); 277 raw_spin_lock_init(&cd->lock); 278 cd->irq_start = irq_start; 279 cd->nr_irqs = irq_end - irq_start + 1; 280 domain = irq_domain_create_hierarchy(domain_parent, 0, cd->nr_irqs, of_fwnode_handle(dn), 281 &mst_intc_domain_ops, cd); 282 if (!domain) { 283 iounmap(cd->base); 284 kfree(cd); 285 return -ENOMEM; 286 } 287 288#ifdef CONFIG_PM_SLEEP 289 INIT_LIST_HEAD(&cd->entry); 290 list_add_tail(&cd->entry, &mst_intc_list); 291#endif 292 return 0; 293} 294 295IRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init);