1/* $OpenBSD: xlights.c,v 1.11 2022/03/13 12:33:01 mpi Exp $ */
2/*
3 * Copyright (c) 2007 Gordon Willem Klok <gwk@openbsd,org>
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#include <sys/param.h>
18#include <sys/systm.h>
19#include <sys/proc.h>
20#include <sys/device.h>
21#include <sys/kthread.h>
22#include <sys/timeout.h>
23#include <dev/ofw/openfirm.h>
24
25#include <machine/bus.h>
26#include <machine/autoconf.h>
27#include <macppc/dev/dbdma.h>
28#include <macppc/dev/i2sreg.h>
29#include <macppc/pci/macobio.h>
30
31struct xlights_softc {
32	struct device 			sc_dev;
33	int				sc_node;
34	int				sc_intr;
35	uint32_t			sc_freq;
36	int 				sc_dmasts;
37
38	u_char				*sc_reg;
39	dbdma_regmap_t 			*sc_dma;
40	bus_dma_tag_t			sc_dmat;
41	bus_dmamap_t			sc_bufmap;
42	bus_dma_segment_t       	sc_bufseg[1];
43	dbdma_t				sc_dbdma;
44	dbdma_command_t			*sc_dmacmd;
45	uint32_t			*sc_buf;
46	uint32_t			*sc_bufpos;
47
48	struct timeout 			sc_tmo;
49};
50
51int xlights_match(struct device *, void *, void *);
52void xlights_attach(struct device *, struct device *, void *);
53int xlights_intr(void *);
54void xlights_startdma(struct xlights_softc *);
55void xlights_deferred(void *);
56void xlights_theosDOT(void *);
57void xlights_timeout(void *);
58
59const struct cfattach xlights_ca = {
60	sizeof(struct xlights_softc), xlights_match,
61	xlights_attach
62};
63
64struct cfdriver xlights_cd = {
65	NULL, "xlights", DV_DULL
66};
67
68#define BL_BUFSZ PAGE_SIZE
69#define BL_DBDMA_CMDS 2
70
71int
72xlights_match(struct device *parent, void *arg, void *aux)
73{
74	struct confargs *ca = aux;
75	int soundbus, soundchip, error;
76	char compat[32];
77
78	if (strcmp(ca->ca_name, "i2s") != 0)
79		return 0;
80	if ((soundbus = OF_child(ca->ca_node)) == 0)
81		return 0;
82	if ((soundchip = OF_child(soundbus)) == 0)
83		return 0;
84
85	error = OF_getprop(soundchip, "virtual", compat, sizeof(compat));
86	if (error == -1) {
87		error = OF_getprop(soundchip, "name", compat,
88		    sizeof(compat));
89
90		if (error == -1 || (strcmp(compat, "lightshow")) != 0)
91			return 0;
92	}
93
94	/* we require at least 4 registers */
95	if (ca->ca_nreg / sizeof(int) < 4)
96		return 0;
97	/* we require at least 3 interrupts */
98	if (ca->ca_nintr / sizeof(int) < 6)
99		return 0;
100
101	return 1;
102}
103
104void
105xlights_attach(struct device *parent, struct device *self, void *aux)
106{
107	struct xlights_softc *sc = (struct xlights_softc *)self;
108	struct confargs *ca = aux;
109	int nseg, error, intr[6];
110	u_int32_t reg[4];
111	int type;
112
113	sc->sc_node = OF_child(ca->ca_node);
114
115	OF_getprop(sc->sc_node, "reg", reg, sizeof(reg));
116	ca->ca_reg[0] += ca->ca_baseaddr;
117	ca->ca_reg[2] += ca->ca_baseaddr;
118
119	if ((sc->sc_reg = mapiodev(ca->ca_reg[0], ca->ca_reg[1])) == NULL) {
120		printf(": cannot map registers\n");
121		return;
122	}
123	sc->sc_dmat = ca->ca_dmat;
124
125	if ((sc->sc_dma = mapiodev(ca->ca_reg[2], ca->ca_reg[3])) == NULL) {
126		printf(": cannot map DMA registers\n");
127		goto nodma;
128	}
129
130	if ((sc->sc_dbdma = dbdma_alloc(sc->sc_dmat, BL_DBDMA_CMDS)) == NULL) {
131		printf(": cannot alloc DMA descriptors\n");
132		goto nodbdma;
133	 }
134	sc->sc_dmacmd = sc->sc_dbdma->d_addr;
135
136	if ((error = bus_dmamem_alloc(sc->sc_dmat, BL_BUFSZ, 0, 0,
137		sc->sc_bufseg, 1, &nseg, BUS_DMA_NOWAIT))) {
138		printf(": cannot allocate DMA mem (%d)\n", error);
139		goto nodmamem;
140	}
141
142	if ((error = bus_dmamem_map(sc->sc_dmat, sc->sc_bufseg, nseg,
143	    BL_BUFSZ, (caddr_t *)&sc->sc_buf, BUS_DMA_NOWAIT))) {
144		printf(": cannot map DMA mem (%d)\n", error);
145		goto nodmamap;
146	}
147	sc->sc_bufpos = sc->sc_buf;
148
149	if ((error = bus_dmamap_create(sc->sc_dmat, BL_BUFSZ, 1, BL_BUFSZ, 0,
150	    BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_bufmap))) {
151		printf(": cannot create DMA map (%d)\n", error);
152		goto nodmacreate;
153	}
154
155	if ((error = bus_dmamap_load(sc->sc_dmat, sc->sc_bufmap, sc->sc_buf,
156	    BL_BUFSZ, NULL, BUS_DMA_NOWAIT))) {
157		printf(": cannot load DMA map (%d)\n", error);
158		goto nodmaload;
159	}
160	/* XXX: Should probably extract this from the clock data
161	 * property of the soundchip node */
162	sc->sc_freq = 16384;
163
164	OF_getprop(sc->sc_node, "interrupts", intr, sizeof(intr));
165	/* output interrupt */
166	sc->sc_intr = intr[2];
167	type = intr[3] ? IST_LEVEL : IST_EDGE;
168
169	printf(": irq %d\n", sc->sc_intr);
170
171	macobio_enable(I2SClockOffset, I2S0EN);
172	out32rb(sc->sc_reg + I2S_INT, I2S_INT_CLKSTOPPEND);
173	macobio_disable(I2SClockOffset, I2S0CLKEN);
174	for (error = 0; error < 1000; error++) {
175		if (in32rb(sc->sc_reg + I2S_INT) & I2S_INT_CLKSTOPPEND) {
176			error = 0;
177			break;
178		}
179		delay(1);
180	}
181	if (error) {
182		printf("%s: i2s timeout\n", sc->sc_dev.dv_xname);
183		goto nodmaload;
184	}
185
186	mac_intr_establish(parent, sc->sc_intr, intr[3] ? IST_LEVEL :
187	    type, IPL_TTY, xlights_intr, sc, sc->sc_dev.dv_xname);
188
189	out32rb(sc->sc_reg + I2S_FORMAT, CLKSRC_VS);
190	macobio_enable(I2SClockOffset, I2S0CLKEN);
191
192	kthread_create_deferred(xlights_deferred, sc);
193	timeout_set(&sc->sc_tmo, xlights_timeout, sc);
194	return;
195nodmaload:
196	bus_dmamap_destroy(sc->sc_dmat, sc->sc_bufmap);
197nodmacreate:
198	bus_dmamem_unmap(sc->sc_dmat, (caddr_t)sc->sc_buf, BL_BUFSZ);
199nodmamap:
200	bus_dmamem_free(sc->sc_dmat, sc->sc_bufseg, nseg);
201nodmamem:
202	dbdma_free(sc->sc_dbdma);
203nodbdma:
204	unmapiodev((void *)sc->sc_dma, ca->ca_reg[3]);
205nodma:
206	unmapiodev(sc->sc_reg, ca->ca_reg[1]);
207}
208
209void
210xlights_deferred(void *v)
211{
212	struct xlights_softc *sc = (struct xlights_softc *)v;
213
214	kthread_create(xlights_theosDOT, v, NULL, sc->sc_dev.dv_xname);
215}
216
217/*
218 * xserv has two rows of leds laid out as follows
219 *	25 26 27 28 29 30 31 00
220 *	17 18 19 20 21 22 23 24
221 */
222
223char ledfollow_0[16] = {	25, 26, 27, 28, 29, 30, 31, 00,
224				24, 23, 22, 21, 20, 19, 18, 17 };
225char ledfollow_1[16] = {	17, 25, 26, 27, 28, 29, 30, 31,
226				00, 24, 23, 22, 21, 20, 19, 18 };
227char ledfollow_2[16] = {	18, 17, 25, 26, 27, 28, 29, 30,
228				31, 00, 24, 23, 22, 21, 20, 19 };
229void
230xlights_theosDOT(void *v)
231{
232	struct xlights_softc *sc = (struct xlights_softc *)v;
233	uint32_t *p;
234	int k, nsamp;
235	int ledpos, ledpos_high, ledpos_med, ledpos_dim;
236	uint32_t val;
237
238	while (1) {
239		/*
240		 * ldavg 0  - .5 sec ->  (8192 / 16)
241		 * ldavg 1  - 1 sec ->   (16384 / 16)
242		 * ldavg 2  - 1.5 sec -> (24576 / 16)
243		 */
244		nsamp = sc->sc_freq +
245		    sc->sc_freq / FSCALE * averunnable.ldavg[0];
246		nsamp /= 16; /* scale, per led */
247		nsamp /= 4; /* scale, why?, sizeof(uint32_t)? */
248		for (ledpos = 0; ledpos < 16; ledpos++) {
249			ledpos_high	= ledfollow_0[ledpos];
250			ledpos_med	= ledfollow_1[ledpos];
251			ledpos_dim	= ledfollow_2[ledpos];
252			p = sc->sc_bufpos;
253
254			for (k = 0; k < nsamp;) {
255				if (p - sc->sc_buf <
256				    BL_BUFSZ / sizeof(uint32_t)) {
257					val =  (1 << ledpos_high);
258					if ((k % 4) == 0)
259						val |=  (1 << ledpos_med);
260					if ((k % 16) == 0)
261						val |=  (1 << ledpos_dim);
262					*p = val;
263
264					p++;
265					k++;
266				} else {
267					xlights_startdma(sc);
268					while (sc->sc_dmasts)
269						tsleep_nsec(sc->sc_buf, PWAIT,
270						    "blinken", INFSLP);
271					p = sc->sc_buf;
272				}
273			}
274			sc->sc_bufpos = p;
275		}
276	}
277}
278
279void
280xlights_startdma(struct xlights_softc *sc)
281{
282	dbdma_command_t *cmdp = sc->sc_dmacmd;
283
284	sc->sc_dmasts = 1;
285	timeout_add_msec(&sc->sc_tmo, 2500);
286
287	DBDMA_BUILD(cmdp, DBDMA_CMD_OUT_LAST, 0,
288	    sc->sc_bufmap->dm_segs[0].ds_len,
289	    sc->sc_bufmap->dm_segs[0].ds_addr, DBDMA_INT_ALWAYS,
290	    DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
291	cmdp++;
292
293	DBDMA_BUILD(cmdp, DBDMA_CMD_STOP, 0, 0, 0, DBDMA_INT_NEVER,
294	    DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
295
296	dbdma_start(sc->sc_dma, sc->sc_dbdma);
297}
298
299void
300xlights_timeout(void *v)
301{
302	struct xlights_softc *sc = (struct xlights_softc *)v;
303
304	dbdma_reset(sc->sc_dma);
305	timeout_del(&sc->sc_tmo);
306	sc->sc_dmasts = 0;
307	wakeup(sc->sc_buf);
308}
309
310int
311xlights_intr(void *v)
312{
313	struct xlights_softc *sc = (struct xlights_softc *)v;
314	int status;
315	dbdma_command_t *cmd;
316
317	cmd = sc->sc_dmacmd;
318	status = dbdma_ld16(&cmd->d_status);
319	if (sc->sc_dmasts) {
320		sc->sc_dmasts = 0;
321		timeout_del(&sc->sc_tmo);
322		wakeup(sc->sc_buf);
323		return (1);
324	}
325	return (0);
326}
327