1 /* $OpenBSD: qcpdc.c,v 1.3 2022/12/21 23:26:54 patrick Exp $ */
2 /*
3 * Copyright (c) 2022 Patrick Wildt <patrick@blueri.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22
23 #include <machine/fdt.h>
24
25 #include <dev/ofw/openfirm.h>
26 #include <dev/ofw/fdt.h>
27
28 #define PDC_INTR_ENABLE(x) (0x10 + (sizeof(uint32_t) * ((x) / 32)))
29 #define PDC_INTR_ENABLE_BIT(x) (1U << ((x) % 32))
30 #define PDC_INTR_CONFIG(x) (0x110 + (sizeof(uint32_t) * (x)))
31 #define PDC_INTR_CONFIG_LEVEL_LOW 0x0
32 #define PDC_INTR_CONFIG_EDGE_FALLING 0x2
33 #define PDC_INTR_CONFIG_LEVEL_HIGH 0x4
34 #define PDC_INTR_CONFIG_EDGE_RISING 0x6
35 #define PDC_INTR_CONFIG_EDGE_BOTH 0x7
36
37 #define HREAD4(sc, reg) \
38 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
39 #define HWRITE4(sc, reg, val) \
40 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
41 #define HSET4(sc, reg, bits) \
42 HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits))
43 #define HCLR4(sc, reg, bits) \
44 HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits))
45
46 struct intrhand {
47 void *ih_cookie;
48 void *ih_sc;
49 int ih_pin;
50 };
51
52 struct qcpdc_pin_region {
53 uint32_t pin_base;
54 uint32_t gic_base;
55 uint32_t count;
56 };
57
58 struct qcpdc_softc {
59 struct device sc_dev;
60 bus_space_tag_t sc_iot;
61 bus_space_handle_t sc_ioh;
62 int sc_node;
63
64 struct qcpdc_pin_region *sc_pr;
65 int sc_npr;
66
67 struct interrupt_controller sc_ic;
68 };
69
70 int qcpdc_match(struct device *, void *, void *);
71 void qcpdc_attach(struct device *, struct device *, void *);
72
73 const struct cfattach qcpdc_ca = {
74 sizeof(struct qcpdc_softc), qcpdc_match, qcpdc_attach
75 };
76
77 struct cfdriver qcpdc_cd = {
78 NULL, "qcpdc", DV_DULL
79 };
80
81 void *qcpdc_intr_establish(void *, int *, int, struct cpu_info *,
82 int (*)(void *), void *, char *);
83 void qcpdc_intr_disestablish(void *);
84 void qcpdc_intr_enable(void *);
85 void qcpdc_intr_disable(void *);
86 void qcpdc_intr_barrier(void *);
87 void qcpdc_intr_set_wakeup(void *);
88
89 int
qcpdc_match(struct device * parent,void * match,void * aux)90 qcpdc_match(struct device *parent, void *match, void *aux)
91 {
92 struct fdt_attach_args *faa = aux;
93
94 return OF_is_compatible(faa->fa_node, "qcom,pdc");
95 }
96
97 void
qcpdc_attach(struct device * parent,struct device * self,void * aux)98 qcpdc_attach(struct device *parent, struct device *self, void *aux)
99 {
100 struct qcpdc_softc *sc = (struct qcpdc_softc *)self;
101 struct fdt_attach_args *faa = aux;
102 int i, j, len;
103
104 if (faa->fa_nreg < 1) {
105 printf(": no registers\n");
106 return;
107 }
108
109 len = OF_getproplen(faa->fa_node, "qcom,pdc-ranges");
110 if (len <= 0 || len % (3 * sizeof(uint32_t)) != 0) {
111 printf(": invalid ranges property\n");
112 return;
113 }
114
115 sc->sc_npr = len / (3 * sizeof(uint32_t));
116 sc->sc_pr = mallocarray(sc->sc_npr, sizeof(*sc->sc_pr),
117 M_DEVBUF, M_WAITOK);
118 OF_getpropintarray(faa->fa_node, "qcom,pdc-ranges",
119 (uint32_t *)sc->sc_pr, len);
120
121 sc->sc_node = faa->fa_node;
122 sc->sc_iot = faa->fa_iot;
123
124 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
125 faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
126 printf(": can't map registers\n");
127 return;
128 }
129
130 for (i = 0; i < sc->sc_npr; i++) {
131 for (j = 0; j < sc->sc_pr[i].count; j++) {
132 HCLR4(sc, PDC_INTR_ENABLE(sc->sc_pr[i].pin_base + j),
133 PDC_INTR_ENABLE_BIT(sc->sc_pr[i].pin_base + j));
134 }
135 }
136
137 sc->sc_ic.ic_node = faa->fa_node;
138 sc->sc_ic.ic_cookie = sc;
139 sc->sc_ic.ic_establish = qcpdc_intr_establish;
140 sc->sc_ic.ic_disestablish = qcpdc_intr_disestablish;
141 sc->sc_ic.ic_enable = qcpdc_intr_enable;
142 sc->sc_ic.ic_disable = qcpdc_intr_disable;
143 sc->sc_ic.ic_barrier = qcpdc_intr_barrier;
144 sc->sc_ic.ic_set_wakeup = qcpdc_intr_set_wakeup;
145 fdt_intr_register(&sc->sc_ic);
146
147 printf("\n");
148 }
149
150 void *
qcpdc_intr_establish(void * aux,int * cells,int ipl,struct cpu_info * ci,int (* func)(void *),void * arg,char * name)151 qcpdc_intr_establish(void *aux, int *cells, int ipl,
152 struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
153 {
154 struct qcpdc_softc *sc = aux;
155 struct intrhand *ih;
156 void *cookie;
157 uint32_t pcells[3];
158 int pin = cells[0];
159 int level = cells[1];
160 int i, s;
161
162 for (i = 0; i < sc->sc_npr; i++) {
163 if (pin >= sc->sc_pr[i].pin_base &&
164 pin < sc->sc_pr[i].pin_base + sc->sc_pr[i].count)
165 break;
166 }
167 if (i == sc->sc_npr)
168 return NULL;
169
170 switch (level) {
171 case 1:
172 HWRITE4(sc, PDC_INTR_CONFIG(pin), PDC_INTR_CONFIG_EDGE_RISING);
173 break;
174 case 2:
175 HWRITE4(sc, PDC_INTR_CONFIG(pin), PDC_INTR_CONFIG_EDGE_FALLING);
176 break;
177 case 3:
178 HWRITE4(sc, PDC_INTR_CONFIG(pin), PDC_INTR_CONFIG_EDGE_BOTH);
179 break;
180 case 4:
181 HWRITE4(sc, PDC_INTR_CONFIG(pin), PDC_INTR_CONFIG_LEVEL_HIGH);
182 break;
183 case 8:
184 HWRITE4(sc, PDC_INTR_CONFIG(pin), PDC_INTR_CONFIG_LEVEL_LOW);
185 break;
186 default:
187 printf("%s: unsupported interrupt mode/polarity\n",
188 sc->sc_dev.dv_xname);
189 return NULL;
190 }
191
192 pcells[0] = 0; /* GIC-SPI */
193 pcells[1] = pin - sc->sc_pr[i].pin_base + sc->sc_pr[i].gic_base;
194 pcells[2] = level;
195
196 cookie = fdt_intr_parent_establish(&sc->sc_ic, &pcells[0], ipl, ci,
197 func, arg, name);
198 if (cookie == NULL)
199 return NULL;
200
201 ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
202 ih->ih_cookie = cookie;
203 ih->ih_sc = sc;
204 ih->ih_pin = pin;
205
206 s = splhigh();
207 HSET4(sc, PDC_INTR_ENABLE(pin), PDC_INTR_ENABLE_BIT(pin));
208 splx(s);
209
210 return ih;
211 }
212
213 void
qcpdc_intr_disestablish(void * cookie)214 qcpdc_intr_disestablish(void *cookie)
215 {
216 struct intrhand *ih = cookie;
217 struct qcpdc_softc *sc = ih->ih_sc;
218 int s;
219
220 s = splhigh();
221 HCLR4(sc, PDC_INTR_ENABLE(ih->ih_pin), PDC_INTR_ENABLE_BIT(ih->ih_pin));
222 splx(s);
223
224 fdt_intr_parent_disestablish(ih->ih_cookie);
225
226 free(ih, M_DEVBUF, sizeof(*ih));
227 }
228
229 void
qcpdc_intr_enable(void * cookie)230 qcpdc_intr_enable(void *cookie)
231 {
232 struct intrhand *ih = cookie;
233
234 fdt_intr_enable(ih->ih_cookie);
235 }
236
237 void
qcpdc_intr_disable(void * cookie)238 qcpdc_intr_disable(void *cookie)
239 {
240 struct intrhand *ih = cookie;
241
242 fdt_intr_disable(ih->ih_cookie);
243 }
244
245 void
qcpdc_intr_barrier(void * cookie)246 qcpdc_intr_barrier(void *cookie)
247 {
248 struct intrhand *ih = cookie;
249
250 intr_barrier(ih->ih_cookie);
251 }
252
253 void
qcpdc_intr_set_wakeup(void * cookie)254 qcpdc_intr_set_wakeup(void *cookie)
255 {
256 struct intrhand *ih = cookie;
257
258 intr_set_wakeup(ih->ih_cookie);
259 }
260