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
46struct 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
55struct 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
68struct qcipcc_channel {
69	struct qcipcc_softc	*ch_sc;
70	uint32_t		ch_client_id;
71	uint32_t		ch_signal_id;
72};
73
74int	qcipcc_match(struct device *, void *, void *);
75void	qcipcc_attach(struct device *, struct device *, void *);
76
77const struct cfattach qcipcc_ca = {
78	sizeof (struct qcipcc_softc), qcipcc_match, qcipcc_attach
79};
80
81struct cfdriver qcipcc_cd = {
82	NULL, "qcipcc", DV_DULL
83};
84
85int	qcipcc_intr(void *);
86void	*qcipcc_intr_establish(void *, int *, int, struct cpu_info *,
87	    int (*)(void *), void *, char *);
88void	qcipcc_intr_disestablish(void *);
89void	qcipcc_intr_enable(void *);
90void	qcipcc_intr_disable(void *);
91void	qcipcc_intr_barrier(void *);
92
93void	*qcipcc_channel(void *, uint32_t *, struct mbox_client *);
94int	qcipcc_send(void *, const void *, size_t);
95
96int
97qcipcc_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
104void
105qcipcc_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
149int
150qcipcc_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
178void *
179qcipcc_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
201void
202qcipcc_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
213void
214qcipcc_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
224void
225qcipcc_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
235void
236qcipcc_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
244void *
245qcipcc_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
258int
259qcipcc_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