1/*	$OpenBSD: apldma.c,v 1.6 2023/07/26 11:09:24 kettenis Exp $	*/
2/*
3 * Copyright (c) 2022 Mark Kettenis <kettenis@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
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_power.h>
28#include <dev/ofw/fdt.h>
29
30#include <dev/audio_if.h>
31
32#include <arm64/dev/apldma.h>
33
34/*
35 * The device tree bindings for this hardware use separate Tx and Rx
36 * channels with Tx channels using even channel numbers and Rx
37 * channels using odd channel numbers.
38 */
39
40#define DMA_TX_EN			0x0000
41#define DMA_TX_EN_CLR			0x0004
42#define DMA_TX_INTR(irq)		(0x0030 + (irq) * 4)
43
44#define DMA_TX_CTL(chan)		(0x8000 + ((chan) / 2) * 0x400)
45#define  DMA_TX_CTL_RESET_RINGS		(1 << 0)
46#define DMA_TX_INTRSTAT(chan, irq)	(0x8010 + ((chan) / 2) * 0x400 + ((irq) * 4))
47#define  DMA_TX_INTRSTAT_DESC_DONE	(1 << 0)
48#define  DMA_TX_INTRSTAT_ERR		(1 << 6)
49#define DMA_TX_INTRMASK(chan, irq)	(0x8020 + ((chan) / 2) * 0x400 + ((irq) * 4))
50#define  DMA_TX_INTRMASK_DESC_DONE	(1 << 0)
51#define  DMA_TX_INTRMASK_ERR		(1 << 6)
52#define DMA_TX_BUS_WIDTH(chan)		(0x8040 + ((chan) / 2) * 0x400)
53#define  DMA_TX_BUS_WIDTH_8BIT		(0 << 0)
54#define  DMA_TX_BUS_WIDTH_16BIT		(1 << 0)
55#define  DMA_TX_BUS_WIDTH_32BIT		(2 << 0)
56#define  DMA_TX_BUS_WIDTH_FRAME_2_WORDS	(1 << 4)
57#define  DMA_TX_BUS_WIDTH_FRAME_4_WORDS	(2 << 4)
58#define DMA_TX_BURST_SIZE(chan)		(0x8054 + ((chan) / 2) * 0x400)
59#define  DMA_TX_BURST_SIZE_MAGIC	0x00c00060
60#define DMA_TX_RESIDUE(chan)		(0x8064 + ((chan) / 2) * 0x400)
61#define DMA_TX_DESC_RING(chan)		(0x8070 + ((chan) / 2) * 0x400)
62#define  DMA_TX_DESC_RING_FULL		(1 << 9)
63#define DMA_TX_REPORT_RING(chan)	(0x8074 + ((chan) / 2) * 0x400)
64#define  DMA_TX_REPORT_RING_EMPTY	(1 << 8)
65#define DMA_TX_DESC_WRITE(chan)		(0x10000 + ((chan) / 2) * 4)
66#define DMA_TX_REPORT_READ(chan)	(0x10100 + ((chan) / 2) * 4)
67
68#define DMA_DESC_NOTIFY			(1 << 16)
69#define DMA_NUM_DESCRIPTORS		4
70#define DMA_NUM_INTERRUPTS		4
71
72#define HREAD4(sc, reg)							\
73	(bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)))
74#define HWRITE4(sc, reg, val)						\
75	bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
76
77struct apldma_channel {
78	struct apldma_softc	*ac_sc;
79	unsigned int		ac_chan;
80
81	bus_dmamap_t		ac_map;
82	bus_dma_segment_t	ac_seg;
83	caddr_t			ac_kva;
84	bus_size_t		ac_size;
85
86	bus_addr_t		ac_base;
87	bus_size_t		ac_len;
88	bus_size_t		ac_pos;
89	bus_size_t		ac_blksize;
90
91	void			(*ac_intr)(void *);
92	void			*ac_intrarg;
93};
94
95struct apldma_softc {
96	struct device		sc_dev;
97	bus_space_tag_t		sc_iot;
98	bus_space_handle_t	sc_ioh;
99
100	bus_dma_tag_t		sc_dmat;
101	int			sc_node;
102	void			*sc_ih;
103	int			sc_irq;
104
105	int			sc_nchannels;
106	struct apldma_channel	**sc_ac;
107};
108
109struct apldma_softc *apldma_sc;
110
111int	apldma_match(struct device *, void *, void *);
112void	apldma_attach(struct device *, struct device *, void *);
113int	apldma_activate(struct device *, int);
114
115const struct cfattach apldma_ca = {
116	sizeof (struct apldma_softc), apldma_match, apldma_attach, NULL,
117	apldma_activate
118};
119
120struct cfdriver apldma_cd = {
121	NULL, "apldma", DV_DULL
122};
123
124int	apldma_intr(void *);
125
126int
127apldma_match(struct device *parent, void *match, void *aux)
128{
129	struct fdt_attach_args *faa = aux;
130
131	return OF_is_compatible(faa->fa_node, "apple,admac");
132}
133
134void
135apldma_attach(struct device *parent, struct device *self, void *aux)
136{
137	struct apldma_softc *sc = (struct apldma_softc *)self;
138	struct fdt_attach_args *faa = aux;
139	int irq;
140
141	if (faa->fa_nreg < 1) {
142		printf(": no registers\n");
143		return;
144	}
145
146	sc->sc_iot = faa->fa_iot;
147	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
148	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
149		printf(": can't map registers\n");
150		return;
151	}
152
153	sc->sc_nchannels = OF_getpropint(faa->fa_node, "dma-channels", 0);
154	if (sc->sc_nchannels == 0) {
155		printf(": no DMA channels\n");
156		goto unmap;
157	}
158	sc->sc_ac = mallocarray(sc->sc_nchannels,
159	    sizeof(struct apldma_channel *), M_DEVBUF, M_WAITOK | M_ZERO);
160
161	sc->sc_dmat = faa->fa_dmat;
162	sc->sc_node = faa->fa_node;
163
164	power_domain_enable(sc->sc_node);
165
166	/*
167	 * The hardware supports multiple interrupts; pick the first
168	 * one that is actually connected.
169	 */
170	for (irq = 0; irq < DMA_NUM_INTERRUPTS; irq++) {
171		sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, irq,
172		    IPL_AUDIO | IPL_MPSAFE, apldma_intr, sc,
173		    sc->sc_dev.dv_xname);
174		if (sc->sc_ih)
175			break;
176	}
177	if (sc->sc_ih == NULL) {
178		printf(": can't establish interrupt\n");
179		goto free;
180	}
181
182	/*
183	 * The preliminary device tree bindings used a special
184	 * property to describe which interrupt was actually
185	 * connected.  Remove this in the near future.
186	 */
187	sc->sc_irq = OF_getpropint(faa->fa_node,
188	    "apple,internal-irq-destination", irq);
189
190	printf("\n");
191
192	apldma_sc = sc;
193	return;
194
195free:
196	free(sc->sc_ac, M_DEVBUF,
197	    sc->sc_nchannels * sizeof(struct apldma_channel *));
198unmap:
199	bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size);
200}
201
202int
203apldma_activate(struct device *self, int act)
204{
205	struct apldma_softc *sc = (struct apldma_softc *)self;
206
207	switch (act) {
208	case DVACT_SUSPEND:
209		power_domain_disable(sc->sc_node);
210		break;
211	case DVACT_RESUME:
212		power_domain_enable(sc->sc_node);
213		break;
214	}
215
216	return 0;
217}
218
219void
220apldma_fill_descriptors(struct apldma_channel *ac)
221{
222	struct apldma_softc *sc = ac->ac_sc;
223	unsigned int i;
224
225	for (i = 0; i < DMA_NUM_DESCRIPTORS; i++) {
226		bus_addr_t addr = ac->ac_base + ac->ac_pos;
227		uint32_t status;
228
229		status = HREAD4(sc, DMA_TX_DESC_RING(ac->ac_chan));
230		if (status & DMA_TX_DESC_RING_FULL)
231			break;
232
233		HWRITE4(sc, DMA_TX_DESC_WRITE(ac->ac_chan), addr);
234		HWRITE4(sc, DMA_TX_DESC_WRITE(ac->ac_chan), addr >> 32);
235		HWRITE4(sc, DMA_TX_DESC_WRITE(ac->ac_chan), ac->ac_blksize);
236		HWRITE4(sc, DMA_TX_DESC_WRITE(ac->ac_chan), DMA_DESC_NOTIFY);
237
238		ac->ac_pos += ac->ac_blksize;
239		if (ac->ac_pos > ac->ac_len - ac->ac_blksize)
240			ac->ac_pos = 0;
241	}
242}
243
244int
245apldma_intr(void *arg)
246{
247	struct apldma_softc *sc = arg;
248	uint32_t intr, intrstat;
249	unsigned int chan, i;
250
251	intr = HREAD4(sc, DMA_TX_INTR(sc->sc_irq));
252	for (chan = 0; chan < sc->sc_nchannels; chan += 2) {
253		if ((intr & (1 << (chan / 2))) == 0)
254			continue;
255
256		intrstat = HREAD4(sc, DMA_TX_INTRSTAT(chan, sc->sc_irq));
257		HWRITE4(sc, DMA_TX_INTRSTAT(chan, sc->sc_irq), intrstat);
258
259		if ((intrstat & DMA_TX_INTRSTAT_DESC_DONE) == 0)
260			continue;
261
262		for (i = 0; i < DMA_NUM_DESCRIPTORS; i++) {
263			uint32_t status;
264
265			status = HREAD4(sc, DMA_TX_REPORT_RING(chan));
266			if (status & DMA_TX_REPORT_RING_EMPTY)
267				break;
268
269			/* Consume report descriptor. */
270			HREAD4(sc, DMA_TX_REPORT_READ(chan));
271			HREAD4(sc, DMA_TX_REPORT_READ(chan));
272			HREAD4(sc, DMA_TX_REPORT_READ(chan));
273			HREAD4(sc, DMA_TX_REPORT_READ(chan));
274		}
275
276		mtx_enter(&audio_lock);
277		struct apldma_channel *ac = sc->sc_ac[chan];
278		ac->ac_intr(ac->ac_intrarg);
279		mtx_leave(&audio_lock);
280
281		apldma_fill_descriptors(sc->sc_ac[chan]);
282	}
283
284	return 1;
285}
286
287struct apldma_channel *
288apldma_alloc_channel(unsigned int chan)
289{
290	struct apldma_softc *sc = apldma_sc;
291	struct apldma_channel *ac;
292
293	if (sc == NULL || chan >= sc->sc_nchannels)
294		return NULL;
295
296	/* We only support Tx channels for now. */
297	if ((chan % 2) != 0)
298		return NULL;
299
300	ac = malloc(sizeof(*ac), M_DEVBUF, M_WAITOK);
301	ac->ac_sc = sc;
302	ac->ac_chan = chan;
303	sc->sc_ac[chan] = ac;
304	return ac;
305}
306
307void
308apldma_free_channel(struct apldma_channel *ac)
309{
310	struct apldma_softc *sc = ac->ac_sc;
311
312	sc->sc_ac[ac->ac_chan] = NULL;
313	free(ac, M_DEVBUF, sizeof(*ac));
314}
315
316void *
317apldma_allocm(struct apldma_channel *ac, size_t size, int flags)
318{
319	struct apldma_softc *sc = ac->ac_sc;
320	int nsegs;
321	int err;
322
323	flags = (flags & M_WAITOK) ? BUS_DMA_WAITOK : BUS_DMA_NOWAIT;
324
325	err = bus_dmamem_alloc(sc->sc_dmat, size, 0, 0, &ac->ac_seg, 1,
326	    &nsegs, flags);
327	if (err)
328		return NULL;
329	err = bus_dmamem_map(sc->sc_dmat, &ac->ac_seg, 1, size, &ac->ac_kva,
330	    flags | BUS_DMA_COHERENT);
331	if (err)
332		goto free;
333	err = bus_dmamap_create(sc->sc_dmat, size, 1, size, 0, flags,
334	    &ac->ac_map);
335	if (err)
336		goto unmap;
337	err = bus_dmamap_load(sc->sc_dmat, ac->ac_map, ac->ac_kva, size,
338	    NULL, flags);
339	if (err)
340		goto destroy;
341
342	ac->ac_size = size;
343	return ac->ac_kva;
344
345destroy:
346	bus_dmamap_destroy(sc->sc_dmat, ac->ac_map);
347unmap:
348	bus_dmamem_unmap(sc->sc_dmat, ac->ac_kva, size);
349free:
350	bus_dmamem_free(sc->sc_dmat, &ac->ac_seg, 1);
351	return NULL;
352}
353
354void
355apldma_freem(struct apldma_channel *ac)
356{
357	struct apldma_softc *sc = ac->ac_sc;
358
359	bus_dmamap_unload(sc->sc_dmat, ac->ac_map);
360	bus_dmamap_destroy(sc->sc_dmat, ac->ac_map);
361	bus_dmamem_unmap(sc->sc_dmat, ac->ac_kva, ac->ac_size);
362	bus_dmamem_free(sc->sc_dmat, &ac->ac_seg, 1);
363}
364
365int
366apldma_trigger_output(struct apldma_channel *ac, void *start, void *end,
367    int blksize, void (*intr)(void *), void *intrarg,
368    struct audio_params *params)
369{
370	struct apldma_softc *sc = ac->ac_sc;
371
372	KASSERT(start == ac->ac_kva);
373
374	ac->ac_base = ac->ac_map->dm_segs[0].ds_addr;
375	ac->ac_len = end - start;
376	ac->ac_pos = 0;
377	ac->ac_blksize = blksize;
378
379	ac->ac_intr = intr;
380	ac->ac_intrarg = intrarg;
381
382	switch (params->bps) {
383	case 2:
384		HWRITE4(sc, DMA_TX_BUS_WIDTH(ac->ac_chan),
385		    DMA_TX_BUS_WIDTH_16BIT | DMA_TX_BUS_WIDTH_FRAME_4_WORDS);
386		break;
387	case 4:
388		HWRITE4(sc, DMA_TX_BUS_WIDTH(ac->ac_chan),
389		    DMA_TX_BUS_WIDTH_32BIT | DMA_TX_BUS_WIDTH_FRAME_4_WORDS);
390		break;
391	default:
392		return EINVAL;
393	}
394	HWRITE4(sc, DMA_TX_BURST_SIZE(ac->ac_chan), DMA_TX_BURST_SIZE_MAGIC);
395
396	/* Reset rings. */
397	HWRITE4(sc, DMA_TX_CTL(ac->ac_chan), DMA_TX_CTL_RESET_RINGS);
398	HWRITE4(sc, DMA_TX_CTL(ac->ac_chan), 0);
399
400	/* Clear and unmask interrupts. */
401	HWRITE4(sc, DMA_TX_INTRSTAT(ac->ac_chan, sc->sc_irq),
402	   DMA_TX_INTRSTAT_DESC_DONE | DMA_TX_INTRSTAT_ERR);
403	HWRITE4(sc, DMA_TX_INTRMASK(ac->ac_chan, sc->sc_irq),
404	   DMA_TX_INTRMASK_DESC_DONE | DMA_TX_INTRMASK_ERR);
405
406	apldma_fill_descriptors(ac);
407
408	/* Start DMA transfer. */
409	HWRITE4(sc, DMA_TX_EN, 1 << (ac->ac_chan / 2));
410
411	return 0;
412}
413
414int
415apldma_halt_output(struct apldma_channel *ac)
416{
417	struct apldma_softc *sc = ac->ac_sc;
418
419	/* Stop DMA transfer. */
420	HWRITE4(sc, DMA_TX_EN_CLR, 1 << (ac->ac_chan / 2));
421
422	/* Mask all interrupts. */
423	HWRITE4(sc, DMA_TX_INTRMASK(ac->ac_chan, sc->sc_irq), 0);
424
425	return 0;
426}
427