a10_dmac.c revision 295635
1/*-
2 * Copyright (c) 2014-2016 Jared D. McNeill <jmcneill@invisible.ca>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28/*
29 * Allwinner A10/A20 DMA controller
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD: head/sys/arm/allwinner/a10_dmac.c 295635 2016-02-15 19:56:35Z andrew $");
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/bus.h>
38#include <sys/rman.h>
39#include <sys/condvar.h>
40#include <sys/kernel.h>
41#include <sys/module.h>
42
43#include <machine/bus.h>
44
45#include <dev/ofw/ofw_bus.h>
46#include <dev/ofw/ofw_bus_subr.h>
47
48#include <arm/allwinner/a10_dmac.h>
49#include <arm/allwinner/a10_clk.h>
50
51#include "sunxi_dma_if.h"
52
53#define	NDMA_CHANNELS	8
54#define	DDMA_CHANNELS	8
55
56enum a10dmac_type {
57	CH_NDMA,
58	CH_DDMA
59};
60
61struct a10dmac_softc;
62
63struct a10dmac_channel {
64	struct a10dmac_softc *	ch_sc;
65	uint8_t			ch_index;
66	enum a10dmac_type	ch_type;
67	void			(*ch_callback)(void *);
68	void *			ch_callbackarg;
69	uint32_t		ch_regoff;
70};
71
72struct a10dmac_softc {
73	struct resource *	sc_res[2];
74	struct mtx		sc_mtx;
75	void *			sc_ih;
76
77	struct a10dmac_channel	sc_ndma_channels[NDMA_CHANNELS];
78	struct a10dmac_channel	sc_ddma_channels[DDMA_CHANNELS];
79};
80
81static struct resource_spec a10dmac_spec[] = {
82	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
83	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
84	{ -1, 0 }
85};
86
87#define	DMA_READ(sc, reg)	bus_read_4((sc)->sc_res[0], (reg))
88#define	DMA_WRITE(sc, reg, val)	bus_write_4((sc)->sc_res[0], (reg), (val))
89#define	DMACH_READ(ch, reg)		\
90    DMA_READ((ch)->ch_sc, (reg) + (ch)->ch_regoff)
91#define	DMACH_WRITE(ch, reg, val)	\
92    DMA_WRITE((ch)->ch_sc, (reg) + (ch)->ch_regoff, (val))
93
94static void a10dmac_intr(void *);
95
96static int
97a10dmac_probe(device_t dev)
98{
99	if (!ofw_bus_status_okay(dev))
100		return (ENXIO);
101
102	if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-dma"))
103		return (ENXIO);
104
105	device_set_desc(dev, "Allwinner DMA controller");
106	return (BUS_PROBE_DEFAULT);
107}
108
109static int
110a10dmac_attach(device_t dev)
111{
112	struct a10dmac_softc *sc;
113	unsigned int index;
114	int error;
115
116	sc = device_get_softc(dev);
117
118	if (bus_alloc_resources(dev, a10dmac_spec, sc->sc_res)) {
119		device_printf(dev, "cannot allocate resources for device\n");
120		return (ENXIO);
121	}
122
123	mtx_init(&sc->sc_mtx, "a10 dmac", NULL, MTX_SPIN);
124
125	/* Activate DMA controller clock */
126	a10_clk_dmac_activate();
127
128	/* Disable all interrupts and clear pending status */
129	DMA_WRITE(sc, AWIN_DMA_IRQ_EN_REG, 0);
130	DMA_WRITE(sc, AWIN_DMA_IRQ_PEND_STA_REG, ~0);
131
132	/* Initialize channels */
133	for (index = 0; index < NDMA_CHANNELS; index++) {
134		sc->sc_ndma_channels[index].ch_sc = sc;
135		sc->sc_ndma_channels[index].ch_index = index;
136		sc->sc_ndma_channels[index].ch_type = CH_NDMA;
137		sc->sc_ndma_channels[index].ch_callback = NULL;
138		sc->sc_ndma_channels[index].ch_callbackarg = NULL;
139		sc->sc_ndma_channels[index].ch_regoff = AWIN_NDMA_REG(index);
140		DMACH_WRITE(&sc->sc_ndma_channels[index], AWIN_NDMA_CTL_REG, 0);
141	}
142	for (index = 0; index < DDMA_CHANNELS; index++) {
143		sc->sc_ddma_channels[index].ch_sc = sc;
144		sc->sc_ddma_channels[index].ch_index = index;
145		sc->sc_ddma_channels[index].ch_type = CH_DDMA;
146		sc->sc_ddma_channels[index].ch_callback = NULL;
147		sc->sc_ddma_channels[index].ch_callbackarg = NULL;
148		sc->sc_ddma_channels[index].ch_regoff = AWIN_DDMA_REG(index);
149		DMACH_WRITE(&sc->sc_ddma_channels[index], AWIN_DDMA_CTL_REG, 0);
150	}
151
152	error = bus_setup_intr(dev, sc->sc_res[1], INTR_MPSAFE | INTR_TYPE_MISC,
153	    NULL, a10dmac_intr, sc, &sc->sc_ih);
154	if (error != 0) {
155		device_printf(dev, "could not setup interrupt handler\n");
156		bus_release_resources(dev, a10dmac_spec, sc->sc_res);
157		mtx_destroy(&sc->sc_mtx);
158		return (ENXIO);
159	}
160
161	return (0);
162}
163
164static void
165a10dmac_intr(void *priv)
166{
167	struct a10dmac_softc *sc = priv;
168	uint32_t sta, bit, mask;
169	uint8_t index;
170
171	sta = DMA_READ(sc, AWIN_DMA_IRQ_PEND_STA_REG);
172	DMA_WRITE(sc, AWIN_DMA_IRQ_PEND_STA_REG, sta);
173
174	while ((bit = ffs(sta & AWIN_DMA_IRQ_END_MASK)) != 0) {
175		mask = (1U << (bit - 1));
176		sta &= ~mask;
177		/*
178		 * Map status bit to channel number. The status register is
179		 * encoded with two bits of status per channel (lowest bit
180		 * is half transfer pending, highest bit is end transfer
181		 * pending). The 8 normal DMA channel status are in the lower
182		 * 16 bits and the 8 dedicated DMA channel status are in
183		 * the upper 16 bits. The output is a channel number from 0-7.
184		 */
185		index = ((bit - 1) / 2) & 7;
186		if (mask & AWIN_DMA_IRQ_NDMA) {
187			if (sc->sc_ndma_channels[index].ch_callback == NULL)
188				continue;
189			sc->sc_ndma_channels[index].ch_callback(
190			    sc->sc_ndma_channels[index].ch_callbackarg);
191		} else {
192			if (sc->sc_ddma_channels[index].ch_callback == NULL)
193				continue;
194			sc->sc_ddma_channels[index].ch_callback(
195			    sc->sc_ddma_channels[index].ch_callbackarg);
196		}
197	}
198}
199
200static uint32_t
201a10dmac_read_ctl(struct a10dmac_channel *ch)
202{
203	if (ch->ch_type == CH_NDMA) {
204		return (DMACH_READ(ch, AWIN_NDMA_CTL_REG));
205	} else {
206		return (DMACH_READ(ch, AWIN_DDMA_CTL_REG));
207	}
208}
209
210static void
211a10dmac_write_ctl(struct a10dmac_channel *ch, uint32_t val)
212{
213	if (ch->ch_type == CH_NDMA) {
214		DMACH_WRITE(ch, AWIN_NDMA_CTL_REG, val);
215	} else {
216		DMACH_WRITE(ch, AWIN_DDMA_CTL_REG, val);
217	}
218}
219
220static int
221a10dmac_set_config(device_t dev, void *priv, const struct sunxi_dma_config *cfg)
222{
223	struct a10dmac_channel *ch = priv;
224	uint32_t val;
225	unsigned int dst_dw, dst_bl, dst_bs, dst_wc;
226	unsigned int src_dw, src_bl, src_bs, src_wc;
227
228	switch (cfg->dst_width) {
229	case 8:
230		dst_dw = AWIN_DMA_CTL_DATA_WIDTH_8;
231		break;
232	case 16:
233		dst_dw = AWIN_DMA_CTL_DATA_WIDTH_16;
234		break;
235	case 32:
236		dst_dw = AWIN_DMA_CTL_DATA_WIDTH_32;
237		break;
238	default:
239		return (EINVAL);
240	}
241	switch (cfg->dst_burst_len) {
242	case 1:
243		dst_bl = AWIN_DMA_CTL_BURST_LEN_1;
244		break;
245	case 4:
246		dst_bl = AWIN_DMA_CTL_BURST_LEN_4;
247		break;
248	case 8:
249		dst_bl = AWIN_DMA_CTL_BURST_LEN_8;
250		break;
251	default:
252		return (EINVAL);
253	}
254	switch (cfg->src_width) {
255	case 8:
256		src_dw = AWIN_DMA_CTL_DATA_WIDTH_8;
257		break;
258	case 16:
259		src_dw = AWIN_DMA_CTL_DATA_WIDTH_16;
260		break;
261	case 32:
262		src_dw = AWIN_DMA_CTL_DATA_WIDTH_32;
263		break;
264	default:
265		return (EINVAL);
266	}
267	switch (cfg->src_burst_len) {
268	case 1:
269		src_bl = AWIN_DMA_CTL_BURST_LEN_1;
270		break;
271	case 4:
272		src_bl = AWIN_DMA_CTL_BURST_LEN_4;
273		break;
274	case 8:
275		src_bl = AWIN_DMA_CTL_BURST_LEN_8;
276		break;
277	default:
278		return (EINVAL);
279	}
280
281	val = (dst_dw << AWIN_DMA_CTL_DST_DATA_WIDTH_SHIFT) |
282	      (dst_bl << AWIN_DMA_CTL_DST_BURST_LEN_SHIFT) |
283	      (cfg->dst_drqtype << AWIN_DMA_CTL_DST_DRQ_TYPE_SHIFT) |
284	      (src_dw << AWIN_DMA_CTL_SRC_DATA_WIDTH_SHIFT) |
285	      (src_bl << AWIN_DMA_CTL_SRC_BURST_LEN_SHIFT) |
286	      (cfg->src_drqtype << AWIN_DMA_CTL_SRC_DRQ_TYPE_SHIFT);
287	if (cfg->dst_noincr) {
288		val |= AWIN_NDMA_CTL_DST_ADDR_NOINCR;
289	}
290	if (cfg->src_noincr) {
291		val |= AWIN_NDMA_CTL_SRC_ADDR_NOINCR;
292	}
293
294	if (ch->ch_type == CH_NDMA) {
295		DMACH_WRITE(ch, AWIN_NDMA_CTL_REG, val);
296	} else {
297		DMACH_WRITE(ch, AWIN_DDMA_CTL_REG, val);
298
299		dst_bs = cfg->dst_blksize - 1;
300		dst_wc = cfg->dst_wait_cyc - 1;
301		src_bs = cfg->src_blksize - 1;
302		src_wc = cfg->src_wait_cyc - 1;
303
304		DMACH_WRITE(ch, AWIN_DDMA_PARA_REG,
305		    (dst_bs << AWIN_DDMA_PARA_DST_DATA_BLK_SIZ_SHIFT) |
306		    (dst_wc << AWIN_DDMA_PARA_DST_WAIT_CYC_SHIFT) |
307		    (src_bs << AWIN_DDMA_PARA_SRC_DATA_BLK_SIZ_SHIFT) |
308		    (src_wc << AWIN_DDMA_PARA_SRC_WAIT_CYC_SHIFT));
309	}
310
311	return (0);
312}
313
314static void *
315a10dmac_alloc(device_t dev, bool dedicated, void (*cb)(void *), void *cbarg)
316{
317	struct a10dmac_softc *sc = device_get_softc(dev);
318	struct a10dmac_channel *ch_list;
319	struct a10dmac_channel *ch = NULL;
320	uint32_t irqen;
321	uint8_t ch_count, index;
322
323	if (dedicated) {
324		ch_list = sc->sc_ddma_channels;
325		ch_count = DDMA_CHANNELS;
326	} else {
327		ch_list = sc->sc_ndma_channels;
328		ch_count = NDMA_CHANNELS;
329	}
330
331	mtx_lock_spin(&sc->sc_mtx);
332	for (index = 0; index < ch_count; index++) {
333		if (ch_list[index].ch_callback == NULL) {
334			ch = &ch_list[index];
335			ch->ch_callback = cb;
336			ch->ch_callbackarg = cbarg;
337
338			irqen = DMA_READ(sc, AWIN_DMA_IRQ_EN_REG);
339			if (ch->ch_type == CH_NDMA)
340				irqen |= AWIN_DMA_IRQ_NDMA_END(index);
341			else
342				irqen |= AWIN_DMA_IRQ_DDMA_END(index);
343			DMA_WRITE(sc, AWIN_DMA_IRQ_EN_REG, irqen);
344
345			break;
346		}
347	}
348	mtx_unlock_spin(&sc->sc_mtx);
349
350	return (ch);
351}
352
353static void
354a10dmac_free(device_t dev, void *priv)
355{
356	struct a10dmac_channel *ch = priv;
357	struct a10dmac_softc *sc = ch->ch_sc;
358	uint32_t irqen, sta, cfg;
359
360	mtx_lock_spin(&sc->sc_mtx);
361
362	irqen = DMA_READ(sc, AWIN_DMA_IRQ_EN_REG);
363	cfg = a10dmac_read_ctl(ch);
364	if (ch->ch_type == CH_NDMA) {
365		sta = AWIN_DMA_IRQ_NDMA_END(ch->ch_index);
366		cfg &= ~AWIN_NDMA_CTL_DMA_LOADING;
367	} else {
368		sta = AWIN_DMA_IRQ_DDMA_END(ch->ch_index);
369		cfg &= ~AWIN_DDMA_CTL_DMA_LOADING;
370	}
371	irqen &= ~sta;
372	a10dmac_write_ctl(ch, cfg);
373	DMA_WRITE(sc, AWIN_DMA_IRQ_EN_REG, irqen);
374	DMA_WRITE(sc, AWIN_DMA_IRQ_PEND_STA_REG, sta);
375
376	ch->ch_callback = NULL;
377	ch->ch_callbackarg = NULL;
378
379	mtx_unlock_spin(&sc->sc_mtx);
380}
381
382static int
383a10dmac_transfer(device_t dev, void *priv, bus_addr_t src, bus_addr_t dst,
384    size_t nbytes)
385{
386	struct a10dmac_channel *ch = priv;
387	uint32_t cfg;
388
389	cfg = a10dmac_read_ctl(ch);
390	if (ch->ch_type == CH_NDMA) {
391		if (cfg & AWIN_NDMA_CTL_DMA_LOADING)
392			return (EBUSY);
393
394		DMACH_WRITE(ch, AWIN_NDMA_SRC_ADDR_REG, src);
395		DMACH_WRITE(ch, AWIN_NDMA_DEST_ADDR_REG, dst);
396		DMACH_WRITE(ch, AWIN_NDMA_BC_REG, nbytes);
397
398		cfg |= AWIN_NDMA_CTL_DMA_LOADING;
399		a10dmac_write_ctl(ch, cfg);
400	} else {
401		if (cfg & AWIN_DDMA_CTL_DMA_LOADING)
402			return (EBUSY);
403
404		DMACH_WRITE(ch, AWIN_DDMA_SRC_START_ADDR_REG, src);
405		DMACH_WRITE(ch, AWIN_DDMA_DEST_START_ADDR_REG, dst);
406		DMACH_WRITE(ch, AWIN_DDMA_BC_REG, nbytes);
407
408		cfg |= AWIN_DDMA_CTL_DMA_LOADING;
409		a10dmac_write_ctl(ch, cfg);
410	}
411
412	return (0);
413}
414
415static void
416a10dmac_halt(device_t dev, void *priv)
417{
418	struct a10dmac_channel *ch = priv;
419	uint32_t cfg;
420
421	cfg = a10dmac_read_ctl(ch);
422	if (ch->ch_type == CH_NDMA) {
423		cfg &= ~AWIN_NDMA_CTL_DMA_LOADING;
424	} else {
425		cfg &= ~AWIN_DDMA_CTL_DMA_LOADING;
426	}
427	a10dmac_write_ctl(ch, cfg);
428}
429
430static device_method_t a10dmac_methods[] = {
431	/* Device interface */
432	DEVMETHOD(device_probe,		a10dmac_probe),
433	DEVMETHOD(device_attach,	a10dmac_attach),
434
435	/* sunxi DMA interface */
436	DEVMETHOD(sunxi_dma_alloc,	a10dmac_alloc),
437	DEVMETHOD(sunxi_dma_free,	a10dmac_free),
438	DEVMETHOD(sunxi_dma_set_config,	a10dmac_set_config),
439	DEVMETHOD(sunxi_dma_transfer,	a10dmac_transfer),
440	DEVMETHOD(sunxi_dma_halt,	a10dmac_halt),
441
442	DEVMETHOD_END
443};
444
445static driver_t a10dmac_driver = {
446	"a10dmac",
447	a10dmac_methods,
448	sizeof(struct a10dmac_softc)
449};
450
451static devclass_t a10dmac_devclass;
452
453DRIVER_MODULE(a10dmac, simplebus, a10dmac_driver, a10dmac_devclass, 0, 0);
454