1 /* $OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $ */
2 /*
3 * Copyright (c) 2023 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/bus.h>
24 #include <machine/fdt.h>
25
26 #include <dev/ofw/openfirm.h>
27 #include <dev/ofw/ofw_misc.h>
28 #include <dev/ofw/fdt.h>
29
30 #define IPCC_SEND_ID 0x0c
31 #define IPCC_RECV_ID 0x10
32 #define IPCC_RECV_SIGNAL_ENABLE 0x14
33 #define IPCC_RECV_SIGNAL_DISABLE 0x18
34 #define IPCC_RECV_SIGNAL_CLEAR 0x1c
35
36 #define IPCC_SIGNAL_ID_SHIFT 0
37 #define IPCC_SIGNAL_ID_MASK 0xffff
38 #define IPCC_CLIENT_ID_SHIFT 16
39 #define IPCC_CLIENT_ID_MASK 0xffff
40
41 #define HREAD4(sc, reg) \
42 (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
43 #define HWRITE4(sc, reg, val) \
44 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
45
46 struct qcipcc_intrhand {
47 TAILQ_ENTRY(qcipcc_intrhand) ih_q;
48 int (*ih_func)(void *);
49 void *ih_arg;
50 void *ih_sc;
51 uint16_t ih_client_id;
52 uint16_t ih_signal_id;
53 };
54
55 struct qcipcc_softc {
56 struct device sc_dev;
57 bus_space_tag_t sc_iot;
58 bus_space_handle_t sc_ioh;
59
60 void *sc_ih;
61
62 struct interrupt_controller sc_ic;
63 TAILQ_HEAD(,qcipcc_intrhand) sc_intrq;
64
65 struct mbox_device sc_md;
66 };
67
68 struct qcipcc_channel {
69 struct qcipcc_softc *ch_sc;
70 uint32_t ch_client_id;
71 uint32_t ch_signal_id;
72 };
73
74 int qcipcc_match(struct device *, void *, void *);
75 void qcipcc_attach(struct device *, struct device *, void *);
76
77 const struct cfattach qcipcc_ca = {
78 sizeof (struct qcipcc_softc), qcipcc_match, qcipcc_attach
79 };
80
81 struct cfdriver qcipcc_cd = {
82 NULL, "qcipcc", DV_DULL
83 };
84
85 int qcipcc_intr(void *);
86 void *qcipcc_intr_establish(void *, int *, int, struct cpu_info *,
87 int (*)(void *), void *, char *);
88 void qcipcc_intr_disestablish(void *);
89 void qcipcc_intr_enable(void *);
90 void qcipcc_intr_disable(void *);
91 void qcipcc_intr_barrier(void *);
92
93 void *qcipcc_channel(void *, uint32_t *, struct mbox_client *);
94 int qcipcc_send(void *, const void *, size_t);
95
96 int
qcipcc_match(struct device * parent,void * match,void * aux)97 qcipcc_match(struct device *parent, void *match, void *aux)
98 {
99 struct fdt_attach_args *faa = aux;
100
101 return OF_is_compatible(faa->fa_node, "qcom,ipcc");
102 }
103
104 void
qcipcc_attach(struct device * parent,struct device * self,void * aux)105 qcipcc_attach(struct device *parent, struct device *self, void *aux)
106 {
107 struct qcipcc_softc *sc = (struct qcipcc_softc *)self;
108 struct fdt_attach_args *faa = aux;
109
110 if (faa->fa_nreg < 1) {
111 printf(": no registers\n");
112 return;
113 }
114
115 sc->sc_iot = faa->fa_iot;
116 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
117 faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
118 printf(": can't map registers\n");
119 return;
120 }
121
122 TAILQ_INIT(&sc->sc_intrq);
123
124 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO,
125 qcipcc_intr, sc, sc->sc_dev.dv_xname);
126 if (sc->sc_ih == NULL) {
127 printf(": can't establish interrupt\n");
128 return;
129 }
130
131 printf("\n");
132
133 sc->sc_ic.ic_node = faa->fa_node;
134 sc->sc_ic.ic_cookie = sc;
135 sc->sc_ic.ic_establish = qcipcc_intr_establish;
136 sc->sc_ic.ic_disestablish = qcipcc_intr_disestablish;
137 sc->sc_ic.ic_enable = qcipcc_intr_enable;
138 sc->sc_ic.ic_disable = qcipcc_intr_disable;
139 sc->sc_ic.ic_barrier = qcipcc_intr_barrier;
140 fdt_intr_register(&sc->sc_ic);
141
142 sc->sc_md.md_node = faa->fa_node;
143 sc->sc_md.md_cookie = sc;
144 sc->sc_md.md_channel = qcipcc_channel;
145 sc->sc_md.md_send = qcipcc_send;
146 mbox_register(&sc->sc_md);
147 }
148
149 int
qcipcc_intr(void * arg)150 qcipcc_intr(void *arg)
151 {
152 struct qcipcc_softc *sc = arg;
153 struct qcipcc_intrhand *ih;
154 uint16_t client_id, signal_id;
155 uint32_t reg;
156 int handled = 0;
157
158 while ((reg = HREAD4(sc, IPCC_RECV_ID)) != ~0) {
159 HWRITE4(sc, IPCC_RECV_SIGNAL_CLEAR, reg);
160
161 client_id = (reg >> IPCC_CLIENT_ID_SHIFT) &
162 IPCC_CLIENT_ID_MASK;
163 signal_id = (reg >> IPCC_SIGNAL_ID_SHIFT) &
164 IPCC_SIGNAL_ID_MASK;
165
166 TAILQ_FOREACH(ih, &sc->sc_intrq, ih_q) {
167 if (ih->ih_client_id != client_id ||
168 ih->ih_signal_id != signal_id)
169 continue;
170 ih->ih_func(ih->ih_arg);
171 handled = 1;
172 }
173 }
174
175 return handled;
176 }
177
178 void *
qcipcc_intr_establish(void * cookie,int * cells,int ipl,struct cpu_info * ci,int (* func)(void *),void * arg,char * name)179 qcipcc_intr_establish(void *cookie, int *cells, int ipl,
180 struct cpu_info *ci, int (*func)(void *), void *arg, char *name)
181 {
182 struct qcipcc_softc *sc = cookie;
183 struct qcipcc_intrhand *ih;
184
185 ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK | M_ZERO);
186 ih->ih_func = func;
187 ih->ih_arg = arg;
188 ih->ih_sc = sc;
189 ih->ih_client_id = cells[0] & IPCC_CLIENT_ID_MASK;
190 ih->ih_signal_id = cells[1] & IPCC_SIGNAL_ID_MASK;
191 TAILQ_INSERT_TAIL(&sc->sc_intrq, ih, ih_q);
192
193 qcipcc_intr_enable(ih);
194
195 if (ipl & IPL_WAKEUP)
196 intr_set_wakeup(sc->sc_ih);
197
198 return ih;
199 }
200
201 void
qcipcc_intr_disestablish(void * cookie)202 qcipcc_intr_disestablish(void *cookie)
203 {
204 struct qcipcc_intrhand *ih = cookie;
205 struct qcipcc_softc *sc = ih->ih_sc;
206
207 qcipcc_intr_disable(ih);
208
209 TAILQ_REMOVE(&sc->sc_intrq, ih, ih_q);
210 free(ih, M_DEVBUF, sizeof(*ih));
211 }
212
213 void
qcipcc_intr_enable(void * cookie)214 qcipcc_intr_enable(void *cookie)
215 {
216 struct qcipcc_intrhand *ih = cookie;
217 struct qcipcc_softc *sc = ih->ih_sc;
218
219 HWRITE4(sc, IPCC_RECV_SIGNAL_ENABLE,
220 (ih->ih_client_id << IPCC_CLIENT_ID_SHIFT) |
221 (ih->ih_signal_id << IPCC_SIGNAL_ID_SHIFT));
222 }
223
224 void
qcipcc_intr_disable(void * cookie)225 qcipcc_intr_disable(void *cookie)
226 {
227 struct qcipcc_intrhand *ih = cookie;
228 struct qcipcc_softc *sc = ih->ih_sc;
229
230 HWRITE4(sc, IPCC_RECV_SIGNAL_DISABLE,
231 (ih->ih_client_id << IPCC_CLIENT_ID_SHIFT) |
232 (ih->ih_signal_id << IPCC_SIGNAL_ID_SHIFT));
233 }
234
235 void
qcipcc_intr_barrier(void * cookie)236 qcipcc_intr_barrier(void *cookie)
237 {
238 struct qcipcc_intrhand *ih = cookie;
239 struct qcipcc_softc *sc = ih->ih_sc;
240
241 intr_barrier(sc->sc_ih);
242 }
243
244 void *
qcipcc_channel(void * cookie,uint32_t * cells,struct mbox_client * mc)245 qcipcc_channel(void *cookie, uint32_t *cells, struct mbox_client *mc)
246 {
247 struct qcipcc_softc *sc = cookie;
248 struct qcipcc_channel *ch;
249
250 ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK);
251 ch->ch_sc = sc;
252 ch->ch_client_id = cells[0] & IPCC_CLIENT_ID_MASK;
253 ch->ch_signal_id = cells[1] & IPCC_SIGNAL_ID_MASK;
254
255 return ch;
256 }
257
258 int
qcipcc_send(void * cookie,const void * data,size_t len)259 qcipcc_send(void *cookie, const void *data, size_t len)
260 {
261 struct qcipcc_channel *ch = cookie;
262 struct qcipcc_softc *sc = ch->ch_sc;
263
264 HWRITE4(sc, IPCC_SEND_ID,
265 (ch->ch_client_id << IPCC_CLIENT_ID_SHIFT) |
266 (ch->ch_signal_id << IPCC_SIGNAL_ID_SHIFT));
267
268 return 0;
269 }
270