• 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  *  Loongson PCH PIC support
5  */
6 
7 #define pr_fmt(fmt) "pch-pic: " fmt
8 
9 #include <linux/interrupt.h>
10 #include <linux/irq.h>
11 #include <linux/irqchip.h>
12 #include <linux/irqdomain.h>
13 #include <linux/kernel.h>
14 #include <linux/platform_device.h>
15 #include <linux/of_address.h>
16 #include <linux/of_irq.h>
17 #include <linux/of_platform.h>
18 
19 /* Registers */
20 #define PCH_PIC_MASK		0x20
21 #define PCH_PIC_HTMSI_EN	0x40
22 #define PCH_PIC_EDGE		0x60
23 #define PCH_PIC_CLR		0x80
24 #define PCH_PIC_AUTO0		0xc0
25 #define PCH_PIC_AUTO1		0xe0
26 #define PCH_INT_ROUTE(irq)	(0x100 + irq)
27 #define PCH_INT_HTVEC(irq)	(0x200 + irq)
28 #define PCH_PIC_POL		0x3e0
29 
30 #define PIC_COUNT_PER_REG	32
31 #define PIC_REG_COUNT		2
32 #define PIC_COUNT		(PIC_COUNT_PER_REG * PIC_REG_COUNT)
33 #define PIC_REG_IDX(irq_id)	((irq_id) / PIC_COUNT_PER_REG)
34 #define PIC_REG_BIT(irq_id)	((irq_id) % PIC_COUNT_PER_REG)
35 
36 struct pch_pic {
37 	void __iomem		*base;
38 	struct irq_domain	*pic_domain;
39 	u32			ht_vec_base;
40 	raw_spinlock_t		pic_lock;
41 };
42 
pch_pic_bitset(struct pch_pic * priv,int offset,int bit)43 static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit)
44 {
45 	u32 reg;
46 	void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
47 
48 	raw_spin_lock(&priv->pic_lock);
49 	reg = readl(addr);
50 	reg |= BIT(PIC_REG_BIT(bit));
51 	writel(reg, addr);
52 	raw_spin_unlock(&priv->pic_lock);
53 }
54 
pch_pic_bitclr(struct pch_pic * priv,int offset,int bit)55 static void pch_pic_bitclr(struct pch_pic *priv, int offset, int bit)
56 {
57 	u32 reg;
58 	void __iomem *addr = priv->base + offset + PIC_REG_IDX(bit) * 4;
59 
60 	raw_spin_lock(&priv->pic_lock);
61 	reg = readl(addr);
62 	reg &= ~BIT(PIC_REG_BIT(bit));
63 	writel(reg, addr);
64 	raw_spin_unlock(&priv->pic_lock);
65 }
66 
pch_pic_mask_irq(struct irq_data * d)67 static void pch_pic_mask_irq(struct irq_data *d)
68 {
69 	struct pch_pic *priv = irq_data_get_irq_chip_data(d);
70 
71 	pch_pic_bitset(priv, PCH_PIC_MASK, d->hwirq);
72 	irq_chip_mask_parent(d);
73 }
74 
pch_pic_unmask_irq(struct irq_data * d)75 static void pch_pic_unmask_irq(struct irq_data *d)
76 {
77 	struct pch_pic *priv = irq_data_get_irq_chip_data(d);
78 
79 	writel(BIT(PIC_REG_BIT(d->hwirq)),
80 			priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);
81 
82 	irq_chip_unmask_parent(d);
83 	pch_pic_bitclr(priv, PCH_PIC_MASK, d->hwirq);
84 }
85 
pch_pic_set_type(struct irq_data * d,unsigned int type)86 static int pch_pic_set_type(struct irq_data *d, unsigned int type)
87 {
88 	struct pch_pic *priv = irq_data_get_irq_chip_data(d);
89 	int ret = 0;
90 
91 	switch (type) {
92 	case IRQ_TYPE_EDGE_RISING:
93 		pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
94 		pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
95 		irq_set_handler_locked(d, handle_edge_irq);
96 		break;
97 	case IRQ_TYPE_EDGE_FALLING:
98 		pch_pic_bitset(priv, PCH_PIC_EDGE, d->hwirq);
99 		pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
100 		irq_set_handler_locked(d, handle_edge_irq);
101 		break;
102 	case IRQ_TYPE_LEVEL_HIGH:
103 		pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
104 		pch_pic_bitclr(priv, PCH_PIC_POL, d->hwirq);
105 		irq_set_handler_locked(d, handle_level_irq);
106 		break;
107 	case IRQ_TYPE_LEVEL_LOW:
108 		pch_pic_bitclr(priv, PCH_PIC_EDGE, d->hwirq);
109 		pch_pic_bitset(priv, PCH_PIC_POL, d->hwirq);
110 		irq_set_handler_locked(d, handle_level_irq);
111 		break;
112 	default:
113 		ret = -EINVAL;
114 		break;
115 	}
116 
117 	return ret;
118 }
119 
pch_pic_ack_irq(struct irq_data * d)120 static void pch_pic_ack_irq(struct irq_data *d)
121 {
122 	unsigned int reg;
123 	struct pch_pic *priv = irq_data_get_irq_chip_data(d);
124 
125 	reg = readl(priv->base + PCH_PIC_EDGE + PIC_REG_IDX(d->hwirq) * 4);
126 	if (reg & BIT(PIC_REG_BIT(d->hwirq))) {
127 		writel(BIT(PIC_REG_BIT(d->hwirq)),
128 			priv->base + PCH_PIC_CLR + PIC_REG_IDX(d->hwirq) * 4);
129 	}
130 	irq_chip_ack_parent(d);
131 }
132 
133 static struct irq_chip pch_pic_irq_chip = {
134 	.name			= "PCH PIC",
135 	.irq_mask		= pch_pic_mask_irq,
136 	.irq_unmask		= pch_pic_unmask_irq,
137 	.irq_ack		= pch_pic_ack_irq,
138 	.irq_set_affinity	= irq_chip_set_affinity_parent,
139 	.irq_set_type		= pch_pic_set_type,
140 };
141 
pch_pic_alloc(struct irq_domain * domain,unsigned int virq,unsigned int nr_irqs,void * arg)142 static int pch_pic_alloc(struct irq_domain *domain, unsigned int virq,
143 			      unsigned int nr_irqs, void *arg)
144 {
145 	int err;
146 	unsigned int type;
147 	unsigned long hwirq;
148 	struct irq_fwspec *fwspec = arg;
149 	struct irq_fwspec parent_fwspec;
150 	struct pch_pic *priv = domain->host_data;
151 
152 	err = irq_domain_translate_twocell(domain, fwspec, &hwirq, &type);
153 	if (err)
154 		return err;
155 
156 	parent_fwspec.fwnode = domain->parent->fwnode;
157 	parent_fwspec.param_count = 1;
158 	parent_fwspec.param[0] = hwirq + priv->ht_vec_base;
159 
160 	err = irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec);
161 	if (err)
162 		return err;
163 
164 	irq_domain_set_info(domain, virq, hwirq,
165 			    &pch_pic_irq_chip, priv,
166 			    handle_level_irq, NULL, NULL);
167 	irq_set_probe(virq);
168 
169 	return 0;
170 }
171 
172 static const struct irq_domain_ops pch_pic_domain_ops = {
173 	.translate	= irq_domain_translate_twocell,
174 	.alloc		= pch_pic_alloc,
175 	.free		= irq_domain_free_irqs_parent,
176 };
177 
pch_pic_reset(struct pch_pic * priv)178 static void pch_pic_reset(struct pch_pic *priv)
179 {
180 	int i;
181 
182 	for (i = 0; i < PIC_COUNT; i++) {
183 		/* Write vectored ID */
184 		writeb(priv->ht_vec_base + i, priv->base + PCH_INT_HTVEC(i));
185 		/* Hardcode route to HT0 Lo */
186 		writeb(1, priv->base + PCH_INT_ROUTE(i));
187 	}
188 
189 	for (i = 0; i < PIC_REG_COUNT; i++) {
190 		/* Clear IRQ cause registers, mask all interrupts */
191 		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_MASK + 4 * i);
192 		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_CLR + 4 * i);
193 		/* Clear auto bounce, we don't need that */
194 		writel_relaxed(0, priv->base + PCH_PIC_AUTO0 + 4 * i);
195 		writel_relaxed(0, priv->base + PCH_PIC_AUTO1 + 4 * i);
196 		/* Enable HTMSI transformer */
197 		writel_relaxed(0xFFFFFFFF, priv->base + PCH_PIC_HTMSI_EN + 4 * i);
198 	}
199 }
200 
pch_pic_of_init(struct device_node * node,struct device_node * parent)201 static int pch_pic_of_init(struct device_node *node,
202 				struct device_node *parent)
203 {
204 	struct pch_pic *priv;
205 	struct irq_domain *parent_domain;
206 	int err;
207 
208 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
209 	if (!priv)
210 		return -ENOMEM;
211 
212 	raw_spin_lock_init(&priv->pic_lock);
213 	priv->base = of_iomap(node, 0);
214 	if (!priv->base) {
215 		err = -ENOMEM;
216 		goto free_priv;
217 	}
218 
219 	parent_domain = irq_find_host(parent);
220 	if (!parent_domain) {
221 		pr_err("Failed to find the parent domain\n");
222 		err = -ENXIO;
223 		goto iounmap_base;
224 	}
225 
226 	if (of_property_read_u32(node, "loongson,pic-base-vec",
227 				&priv->ht_vec_base)) {
228 		pr_err("Failed to determine pic-base-vec\n");
229 		err = -EINVAL;
230 		goto iounmap_base;
231 	}
232 
233 	priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0,
234 						       PIC_COUNT,
235 						       of_node_to_fwnode(node),
236 						       &pch_pic_domain_ops,
237 						       priv);
238 	if (!priv->pic_domain) {
239 		pr_err("Failed to create IRQ domain\n");
240 		err = -ENOMEM;
241 		goto iounmap_base;
242 	}
243 
244 	pch_pic_reset(priv);
245 
246 	return 0;
247 
248 iounmap_base:
249 	iounmap(priv->base);
250 free_priv:
251 	kfree(priv);
252 
253 	return err;
254 }
255 
256 IRQCHIP_DECLARE(pch_pic, "loongson,pch-pic-1.0", pch_pic_of_init);
257