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 17 #define INTC_MASK 0x0 18 #define INTC_EOI 0x20 19 20 struct mst_intc_chip_data { 21 raw_spinlock_t lock; 22 unsigned int irq_start, nr_irqs; 23 void __iomem *base; 24 bool no_eoi; 25 }; 26 27 static void mst_set_irq(struct irq_data *d, u32 offset) 28 { 29 irq_hw_number_t hwirq = irqd_to_hwirq(d); 30 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 31 u16 val, mask; 32 unsigned long flags; 33 34 mask = 1 << (hwirq % 16); 35 offset += (hwirq / 16) * 4; 36 37 raw_spin_lock_irqsave(&cd->lock, flags); 38 val = readw_relaxed(cd->base + offset) | mask; 39 writew_relaxed(val, cd->base + offset); 40 raw_spin_unlock_irqrestore(&cd->lock, flags); 41 } 42 43 static void mst_clear_irq(struct irq_data *d, u32 offset) 44 { 45 irq_hw_number_t hwirq = irqd_to_hwirq(d); 46 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 47 u16 val, mask; 48 unsigned long flags; 49 50 mask = 1 << (hwirq % 16); 51 offset += (hwirq / 16) * 4; 52 53 raw_spin_lock_irqsave(&cd->lock, flags); 54 val = readw_relaxed(cd->base + offset) & ~mask; 55 writew_relaxed(val, cd->base + offset); 56 raw_spin_unlock_irqrestore(&cd->lock, flags); 57 } 58 59 static void mst_intc_mask_irq(struct irq_data *d) 60 { 61 mst_set_irq(d, INTC_MASK); 62 irq_chip_mask_parent(d); 63 } 64 65 static void mst_intc_unmask_irq(struct irq_data *d) 66 { 67 mst_clear_irq(d, INTC_MASK); 68 irq_chip_unmask_parent(d); 69 } 70 71 static void mst_intc_eoi_irq(struct irq_data *d) 72 { 73 struct mst_intc_chip_data *cd = irq_data_get_irq_chip_data(d); 74 75 if (!cd->no_eoi) 76 mst_set_irq(d, INTC_EOI); 77 78 irq_chip_eoi_parent(d); 79 } 80 81 static struct irq_chip mst_intc_chip = { 82 .name = "mst-intc", 83 .irq_mask = mst_intc_mask_irq, 84 .irq_unmask = mst_intc_unmask_irq, 85 .irq_eoi = mst_intc_eoi_irq, 86 .irq_get_irqchip_state = irq_chip_get_parent_state, 87 .irq_set_irqchip_state = irq_chip_set_parent_state, 88 .irq_set_affinity = irq_chip_set_affinity_parent, 89 .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, 90 .irq_set_type = irq_chip_set_type_parent, 91 .irq_retrigger = irq_chip_retrigger_hierarchy, 92 .flags = IRQCHIP_SET_TYPE_MASKED | 93 IRQCHIP_SKIP_SET_WAKE | 94 IRQCHIP_MASK_ON_SUSPEND, 95 }; 96 97 static int mst_intc_domain_translate(struct irq_domain *d, 98 struct irq_fwspec *fwspec, 99 unsigned long *hwirq, 100 unsigned int *type) 101 { 102 struct mst_intc_chip_data *cd = d->host_data; 103 104 if (is_of_node(fwspec->fwnode)) { 105 if (fwspec->param_count != 3) 106 return -EINVAL; 107 108 /* No PPI should point to this domain */ 109 if (fwspec->param[0] != 0) 110 return -EINVAL; 111 112 if (fwspec->param[1] >= cd->nr_irqs) 113 return -EINVAL; 114 115 *hwirq = fwspec->param[1]; 116 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 117 return 0; 118 } 119 120 return -EINVAL; 121 } 122 123 static int mst_intc_domain_alloc(struct irq_domain *domain, unsigned int virq, 124 unsigned int nr_irqs, void *data) 125 { 126 int i; 127 irq_hw_number_t hwirq; 128 struct irq_fwspec parent_fwspec, *fwspec = data; 129 struct mst_intc_chip_data *cd = domain->host_data; 130 131 /* Not GIC compliant */ 132 if (fwspec->param_count != 3) 133 return -EINVAL; 134 135 /* No PPI should point to this domain */ 136 if (fwspec->param[0]) 137 return -EINVAL; 138 139 hwirq = fwspec->param[1]; 140 for (i = 0; i < nr_irqs; i++) 141 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, 142 &mst_intc_chip, 143 domain->host_data); 144 145 parent_fwspec = *fwspec; 146 parent_fwspec.fwnode = domain->parent->fwnode; 147 parent_fwspec.param[1] = cd->irq_start + hwirq; 148 return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); 149 } 150 151 static const struct irq_domain_ops mst_intc_domain_ops = { 152 .translate = mst_intc_domain_translate, 153 .alloc = mst_intc_domain_alloc, 154 .free = irq_domain_free_irqs_common, 155 }; 156 157 static int __init mst_intc_of_init(struct device_node *dn, 158 struct device_node *parent) 159 { 160 struct irq_domain *domain, *domain_parent; 161 struct mst_intc_chip_data *cd; 162 u32 irq_start, irq_end; 163 164 domain_parent = irq_find_host(parent); 165 if (!domain_parent) { 166 pr_err("mst-intc: interrupt-parent not found\n"); 167 return -EINVAL; 168 } 169 170 if (of_property_read_u32_index(dn, "mstar,irqs-map-range", 0, &irq_start) || 171 of_property_read_u32_index(dn, "mstar,irqs-map-range", 1, &irq_end)) 172 return -EINVAL; 173 174 cd = kzalloc(sizeof(*cd), GFP_KERNEL); 175 if (!cd) 176 return -ENOMEM; 177 178 cd->base = of_iomap(dn, 0); 179 if (!cd->base) { 180 kfree(cd); 181 return -ENOMEM; 182 } 183 184 cd->no_eoi = of_property_read_bool(dn, "mstar,intc-no-eoi"); 185 raw_spin_lock_init(&cd->lock); 186 cd->irq_start = irq_start; 187 cd->nr_irqs = irq_end - irq_start + 1; 188 domain = irq_domain_add_hierarchy(domain_parent, 0, cd->nr_irqs, dn, 189 &mst_intc_domain_ops, cd); 190 if (!domain) { 191 iounmap(cd->base); 192 kfree(cd); 193 return -ENOMEM; 194 } 195 196 return 0; 197 } 198 199 IRQCHIP_DECLARE(mst_intc, "mstar,mst-intc", mst_intc_of_init); 200