• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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