1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (C) 2020, Jiaxun Yang <jiaxun.yang@flygoat.com>
4 * Jianmin Lv <lvjianmin@loongson.cn>
5 * Huacai Chen <chenhuacai@loongson.cn>
6 * Loongson HyperTransport Interrupt Vector support
7 */
8
9 #define pr_fmt(fmt) "htvec: " fmt
10
11 #include <linux/interrupt.h>
12 #include <linux/irq.h>
13 #include <linux/irqchip.h>
14 #include <linux/irqdomain.h>
15 #include <linux/irqchip/chained_irq.h>
16 #include <linux/kernel.h>
17 #include <linux/platform_device.h>
18 #include <linux/of_address.h>
19 #include <linux/of_irq.h>
20 #include <linux/of_platform.h>
21 #include <linux/syscore_ops.h>
22
23 /* Registers */
24 #define HTVEC_EN_OFF 0x20
25 #define HTVEC_MAX_PARENT_IRQ 8
26 #define VEC_COUNT_PER_REG 32
27 #define VEC_REG_IDX(irq_id) ((irq_id) / VEC_COUNT_PER_REG)
28 #define VEC_REG_BIT(irq_id) ((irq_id) % VEC_COUNT_PER_REG)
29 #define HTVEC_SIZE 0x400
30
31 struct htvec {
32 int num_parents;
33 void __iomem *base;
34 struct irq_domain *htvec_domain;
35 raw_spinlock_t htvec_lock;
36 struct fwnode_handle *domain_handle;
37 u32 saved_vec_en[HTVEC_MAX_PARENT_IRQ];
38 };
39
40 static struct htvec *htvec_priv;
41
htvec_irq_dispatch(struct irq_desc * desc)42 static void htvec_irq_dispatch(struct irq_desc *desc)
43 {
44 int i;
45 u32 pending;
46 bool handled = false;
47 struct irq_chip *chip = irq_desc_get_chip(desc);
48 struct htvec *priv = irq_desc_get_handler_data(desc);
49
50 chained_irq_enter(chip, desc);
51
52 for (i = 0; i < priv->num_parents; i++) {
53 pending = readl(priv->base + 4 * i);
54 while (pending) {
55 int bit = __ffs(pending);
56
57 generic_handle_irq(irq_linear_revmap(priv->htvec_domain, bit +
58 VEC_COUNT_PER_REG * i));
59 pending &= ~BIT(bit);
60 handled = true;
61 }
62 }
63
64 if (!handled)
65 spurious_interrupt();
66
67 chained_irq_exit(chip, desc);
68 }
69
htvec_ack_irq(struct irq_data * d)70 static void htvec_ack_irq(struct irq_data *d)
71 {
72 struct htvec *priv = irq_data_get_irq_chip_data(d);
73
74 writel(BIT(VEC_REG_BIT(d->hwirq)),
75 priv->base + VEC_REG_IDX(d->hwirq) * 4);
76 }
77
htvec_mask_irq(struct irq_data * d)78 static void htvec_mask_irq(struct irq_data *d)
79 {
80 u32 reg;
81 void __iomem *addr;
82 struct htvec *priv = irq_data_get_irq_chip_data(d);
83
84 raw_spin_lock(&priv->htvec_lock);
85 addr = priv->base + HTVEC_EN_OFF;
86 addr += VEC_REG_IDX(d->hwirq) * 4;
87 reg = readl(addr);
88 reg &= ~BIT(VEC_REG_BIT(d->hwirq));
89 writel(reg, addr);
90 raw_spin_unlock(&priv->htvec_lock);
91 }
92
htvec_unmask_irq(struct irq_data * d)93 static void htvec_unmask_irq(struct irq_data *d)
94 {
95 u32 reg;
96 void __iomem *addr;
97 struct htvec *priv = irq_data_get_irq_chip_data(d);
98
99 raw_spin_lock(&priv->htvec_lock);
100 addr = priv->base + HTVEC_EN_OFF;
101 addr += VEC_REG_IDX(d->hwirq) * 4;
102 reg = readl(addr);
103 reg |= BIT(VEC_REG_BIT(d->hwirq));
104 writel(reg, addr);
105 raw_spin_unlock(&priv->htvec_lock);
106 }
107
108 static struct irq_chip htvec_irq_chip = {
109 .name = "LOONGSON_HTVEC",
110 .irq_mask = htvec_mask_irq,
111 .irq_unmask = htvec_unmask_irq,
112 .irq_ack = htvec_ack_irq,
113 };
114
htvec_domain_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)115 static int htvec_domain_alloc(struct irq_domain *domain, unsigned int virq,
116 unsigned int nr_irqs, void *arg)
117 {
118 int ret;
119 unsigned long hwirq;
120 unsigned int type, i;
121 struct htvec *priv = domain->host_data;
122
123 ret = irq_domain_translate_onecell(domain, arg, &hwirq, &type);
124 if (ret)
125 return ret;
126
127 for (i = 0; i < nr_irqs; i++) {
128 irq_domain_set_info(domain, virq + i, hwirq + i, &htvec_irq_chip,
129 priv, handle_edge_irq, NULL, NULL);
130 }
131
132 return 0;
133 }
134
htvec_domain_free(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs)135 static void htvec_domain_free(struct irq_domain *domain, unsigned int virq,
136 unsigned int nr_irqs)
137 {
138 int i;
139
140 for (i = 0; i < nr_irqs; i++) {
141 struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
142
143 irq_set_handler(virq + i, NULL);
144 irq_domain_reset_irq_data(d);
145 }
146 }
147
148 static const struct irq_domain_ops htvec_domain_ops = {
149 .translate = irq_domain_translate_onecell,
150 .alloc = htvec_domain_alloc,
151 .free = htvec_domain_free,
152 };
153
htvec_reset(struct htvec * priv)154 static void htvec_reset(struct htvec *priv)
155 {
156 u32 idx;
157
158 /* Clear IRQ cause registers, mask all interrupts */
159 for (idx = 0; idx < priv->num_parents; idx++) {
160 writel_relaxed(0x0, priv->base + HTVEC_EN_OFF + 4 * idx);
161 writel_relaxed(0xFFFFFFFF, priv->base + 4 * idx);
162 }
163 }
164
htvec_suspend(void)165 static int htvec_suspend(void)
166 {
167 int i;
168 for (i = 0; i < htvec_priv->num_parents; i++) {
169 htvec_priv->saved_vec_en[i] = readl(htvec_priv->base + HTVEC_EN_OFF + 4 * i);
170 }
171 return 0;
172 }
173
htvec_resume(void)174 static void htvec_resume(void)
175 {
176 int i;
177 for (i = 0; i < htvec_priv->num_parents; i++) {
178 writel(htvec_priv->saved_vec_en[i], htvec_priv->base + HTVEC_EN_OFF + 4 * i);
179 }
180 }
181
182 static struct syscore_ops htvec_syscore_ops = {
183 .suspend = htvec_suspend,
184 .resume = htvec_resume,
185 };
186
htvec_init(phys_addr_t addr,unsigned long size,int num_parents,int parent_irq[],struct fwnode_handle * domain_handle)187 static int htvec_init(phys_addr_t addr, unsigned long size,
188 int num_parents, int parent_irq[], struct fwnode_handle *domain_handle)
189 {
190 int i;
191 struct htvec *priv;
192
193 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
194 if (!priv)
195 return -ENOMEM;
196
197 priv->num_parents = num_parents;
198 priv->base = ioremap(addr, size);
199 priv->domain_handle = domain_handle;
200 raw_spin_lock_init(&priv->htvec_lock);
201
202 /* Setup IRQ domain */
203 priv->htvec_domain = irq_domain_create_linear(priv->domain_handle,
204 (VEC_COUNT_PER_REG * priv->num_parents),
205 &htvec_domain_ops, priv);
206 if (!priv->htvec_domain) {
207 pr_err("loongson-htvec: cannot add IRQ domain\n");
208 goto iounmap_base;
209 }
210
211 htvec_reset(priv);
212
213 for (i = 0; i < priv->num_parents; i++) {
214 irq_set_chained_handler_and_data(parent_irq[i],
215 htvec_irq_dispatch, priv);
216 }
217
218 htvec_priv = priv;
219
220 register_syscore_ops(&htvec_syscore_ops);
221
222 return 0;
223
224 iounmap_base:
225 iounmap(priv->base);
226 priv->domain_handle = NULL;
227 kfree(priv);
228
229 return -EINVAL;
230 }
231
232 #ifdef CONFIG_OF
233
htvec_of_init(struct device_node * node,struct device_node * parent)234 static int htvec_of_init(struct device_node *node,
235 struct device_node *parent)
236 {
237 int i, err;
238 int parent_irq[8];
239 int num_parents = 0;
240 struct resource res;
241
242 if (of_address_to_resource(node, 0, &res))
243 return -EINVAL;
244
245 /* Interrupt may come from any of the 8 interrupt lines */
246 for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++) {
247 parent_irq[i] = irq_of_parse_and_map(node, i);
248 if (parent_irq[i] <= 0)
249 break;
250
251 num_parents++;
252 }
253
254 err = htvec_init(res.start, resource_size(&res),
255 num_parents, parent_irq, of_node_to_fwnode(node));
256 if (err < 0)
257 return err;
258
259 return 0;
260 }
261
262 IRQCHIP_DECLARE(htvec, "loongson,htvec-1.0", htvec_of_init);
263
264 #endif
265
266 #ifdef CONFIG_ACPI
267
htvec_acpi_init(struct irq_domain * parent,struct acpi_madt_ht_pic * acpi_htvec)268 struct irq_domain *htvec_acpi_init(struct irq_domain *parent,
269 struct acpi_madt_ht_pic *acpi_htvec)
270 {
271 int i, ret;
272 int num_parents, parent_irq[8];
273 struct fwnode_handle *domain_handle;
274
275 if (!acpi_htvec)
276 return NULL;
277
278 num_parents = HTVEC_MAX_PARENT_IRQ;
279
280 domain_handle = irq_domain_alloc_fwnode((phys_addr_t *)acpi_htvec);
281 if (!domain_handle) {
282 pr_err("Unable to allocate domain handle\n");
283 return NULL;
284 }
285
286 /* Interrupt may come from any of the 8 interrupt lines */
287 for (i = 0; i < HTVEC_MAX_PARENT_IRQ; i++)
288 parent_irq[i] = irq_create_mapping(parent, acpi_htvec->cascade[i]);
289
290 ret = htvec_init(acpi_htvec->address, acpi_htvec->size,
291 num_parents, parent_irq, domain_handle);
292 if (ret < 0)
293 return NULL;
294
295 return irq_find_matching_fwnode(domain_handle, DOMAIN_BUS_ANY);
296 }
297
298 #endif
299