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
46struct intrhand {
47	void *ih_cookie;
48	void *ih_sc;
49	int ih_pin;
50};
51
52struct qcpdc_pin_region {
53	uint32_t pin_base;
54	uint32_t gic_base;
55	uint32_t count;
56};
57
58struct 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
70int	qcpdc_match(struct device *, void *, void *);
71void	qcpdc_attach(struct device *, struct device *, void *);
72
73const struct cfattach qcpdc_ca = {
74	sizeof(struct qcpdc_softc), qcpdc_match, qcpdc_attach
75};
76
77struct cfdriver qcpdc_cd = {
78	NULL, "qcpdc", DV_DULL
79};
80
81void	*qcpdc_intr_establish(void *, int *, int, struct cpu_info *,
82	    int (*)(void *), void *, char *);
83void	qcpdc_intr_disestablish(void *);
84void	qcpdc_intr_enable(void *);
85void	qcpdc_intr_disable(void *);
86void	qcpdc_intr_barrier(void *);
87void	qcpdc_intr_set_wakeup(void *);
88
89int
90qcpdc_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
97void
98qcpdc_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
150void *
151qcpdc_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
213void
214qcpdc_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
229void
230qcpdc_intr_enable(void *cookie)
231{
232	struct intrhand *ih = cookie;
233
234	fdt_intr_enable(ih->ih_cookie);
235}
236
237void
238qcpdc_intr_disable(void *cookie)
239{
240	struct intrhand *ih = cookie;
241
242	fdt_intr_disable(ih->ih_cookie);
243}
244
245void
246qcpdc_intr_barrier(void *cookie)
247{
248	struct intrhand *ih = cookie;
249
250	intr_barrier(ih->ih_cookie);
251}
252
253void
254qcpdc_intr_set_wakeup(void *cookie)
255{
256	struct intrhand *ih = cookie;
257
258	intr_set_wakeup(ih->ih_cookie);
259}
260