1/*
2 *  linux/arch/arm/mach-rpc/dma.c
3 *
4 *  Copyright (C) 1998 Russell King
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 *  DMA functions specific to RiscPC architecture
11 */
12#include <linux/slab.h>
13#include <linux/mman.h>
14#include <linux/init.h>
15#include <linux/interrupt.h>
16#include <linux/dma-mapping.h>
17
18#include <asm/page.h>
19#include <asm/dma.h>
20#include <asm/fiq.h>
21#include <asm/io.h>
22#include <asm/irq.h>
23#include <asm/hardware.h>
24#include <asm/uaccess.h>
25
26#include <asm/mach/dma.h>
27#include <asm/hardware/iomd.h>
28
29
30#define TRANSFER_SIZE	2
31
32#define CURA	(0)
33#define ENDA	(IOMD_IO0ENDA - IOMD_IO0CURA)
34#define CURB	(IOMD_IO0CURB - IOMD_IO0CURA)
35#define ENDB	(IOMD_IO0ENDB - IOMD_IO0CURA)
36#define CR	(IOMD_IO0CR - IOMD_IO0CURA)
37#define ST	(IOMD_IO0ST - IOMD_IO0CURA)
38
39static void iomd_get_next_sg(struct scatterlist *sg, dma_t *dma)
40{
41	unsigned long end, offset, flags = 0;
42
43	if (dma->sg) {
44		sg->dma_address = dma->sg->dma_address;
45		offset = sg->dma_address & ~PAGE_MASK;
46
47		end = offset + dma->sg->length;
48
49		if (end > PAGE_SIZE)
50			end = PAGE_SIZE;
51
52		if (offset + TRANSFER_SIZE >= end)
53			flags |= DMA_END_L;
54
55		sg->length = end - TRANSFER_SIZE;
56
57		dma->sg->length -= end - offset;
58		dma->sg->dma_address += end - offset;
59
60		if (dma->sg->length == 0) {
61			if (dma->sgcount > 1) {
62				dma->sg++;
63				dma->sgcount--;
64			} else {
65				dma->sg = NULL;
66				flags |= DMA_END_S;
67			}
68		}
69	} else {
70		flags = DMA_END_S | DMA_END_L;
71		sg->dma_address = 0;
72		sg->length = 0;
73	}
74
75	sg->length |= flags;
76}
77
78static irqreturn_t iomd_dma_handle(int irq, void *dev_id)
79{
80	dma_t *dma = (dma_t *)dev_id;
81	unsigned long base = dma->dma_base;
82
83	do {
84		unsigned int status;
85
86		status = iomd_readb(base + ST);
87		if (!(status & DMA_ST_INT))
88			return IRQ_HANDLED;
89
90		if ((dma->state ^ status) & DMA_ST_AB)
91			iomd_get_next_sg(&dma->cur_sg, dma);
92
93		switch (status & (DMA_ST_OFL | DMA_ST_AB)) {
94		case DMA_ST_OFL:			/* OIA */
95		case DMA_ST_AB:				/* .IB */
96			iomd_writel(dma->cur_sg.dma_address, base + CURA);
97			iomd_writel(dma->cur_sg.length, base + ENDA);
98			dma->state = DMA_ST_AB;
99			break;
100
101		case DMA_ST_OFL | DMA_ST_AB:		/* OIB */
102		case 0:					/* .IA */
103			iomd_writel(dma->cur_sg.dma_address, base + CURB);
104			iomd_writel(dma->cur_sg.length, base + ENDB);
105			dma->state = 0;
106			break;
107		}
108
109		if (status & DMA_ST_OFL &&
110		    dma->cur_sg.length == (DMA_END_S|DMA_END_L))
111			break;
112	} while (1);
113
114	dma->state = ~DMA_ST_AB;
115	disable_irq(irq);
116
117	return IRQ_HANDLED;
118}
119
120static int iomd_request_dma(dmach_t channel, dma_t *dma)
121{
122	return request_irq(dma->dma_irq, iomd_dma_handle,
123			   IRQF_DISABLED, dma->device_id, dma);
124}
125
126static void iomd_free_dma(dmach_t channel, dma_t *dma)
127{
128	free_irq(dma->dma_irq, dma);
129}
130
131static void iomd_enable_dma(dmach_t channel, dma_t *dma)
132{
133	unsigned long dma_base = dma->dma_base;
134	unsigned int ctrl = TRANSFER_SIZE | DMA_CR_E;
135
136	if (dma->invalid) {
137		dma->invalid = 0;
138
139		/*
140		 * Cope with ISA-style drivers which expect cache
141		 * coherence.
142		 */
143		if (!dma->sg) {
144			dma->sg = &dma->buf;
145			dma->sgcount = 1;
146			dma->buf.length = dma->count;
147			dma->buf.dma_address = dma_map_single(NULL,
148				dma->addr, dma->count,
149				dma->dma_mode == DMA_MODE_READ ?
150				DMA_FROM_DEVICE : DMA_TO_DEVICE);
151		}
152
153		iomd_writeb(DMA_CR_C, dma_base + CR);
154		dma->state = DMA_ST_AB;
155	}
156
157	if (dma->dma_mode == DMA_MODE_READ)
158		ctrl |= DMA_CR_D;
159
160	iomd_writeb(ctrl, dma_base + CR);
161	enable_irq(dma->dma_irq);
162}
163
164static void iomd_disable_dma(dmach_t channel, dma_t *dma)
165{
166	unsigned long dma_base = dma->dma_base;
167	unsigned long flags;
168
169	local_irq_save(flags);
170	if (dma->state != ~DMA_ST_AB)
171		disable_irq(dma->dma_irq);
172	iomd_writeb(0, dma_base + CR);
173	local_irq_restore(flags);
174}
175
176static int iomd_set_dma_speed(dmach_t channel, dma_t *dma, int cycle)
177{
178	int tcr, speed;
179
180	if (cycle < 188)
181		speed = 3;
182	else if (cycle <= 250)
183		speed = 2;
184	else if (cycle < 438)
185		speed = 1;
186	else
187		speed = 0;
188
189	tcr = iomd_readb(IOMD_DMATCR);
190	speed &= 3;
191
192	switch (channel) {
193	case DMA_0:
194		tcr = (tcr & ~0x03) | speed;
195		break;
196
197	case DMA_1:
198		tcr = (tcr & ~0x0c) | (speed << 2);
199		break;
200
201	case DMA_2:
202		tcr = (tcr & ~0x30) | (speed << 4);
203		break;
204
205	case DMA_3:
206		tcr = (tcr & ~0xc0) | (speed << 6);
207		break;
208
209	default:
210		break;
211	}
212
213	iomd_writeb(tcr, IOMD_DMATCR);
214
215	return speed;
216}
217
218static struct dma_ops iomd_dma_ops = {
219	.type		= "IOMD",
220	.request	= iomd_request_dma,
221	.free		= iomd_free_dma,
222	.enable		= iomd_enable_dma,
223	.disable	= iomd_disable_dma,
224	.setspeed	= iomd_set_dma_speed,
225};
226
227static struct fiq_handler fh = {
228	.name	= "floppydma"
229};
230
231static void floppy_enable_dma(dmach_t channel, dma_t *dma)
232{
233	void *fiqhandler_start;
234	unsigned int fiqhandler_length;
235	struct pt_regs regs;
236
237	if (dma->sg)
238		BUG();
239
240	if (dma->dma_mode == DMA_MODE_READ) {
241		extern unsigned char floppy_fiqin_start, floppy_fiqin_end;
242		fiqhandler_start = &floppy_fiqin_start;
243		fiqhandler_length = &floppy_fiqin_end - &floppy_fiqin_start;
244	} else {
245		extern unsigned char floppy_fiqout_start, floppy_fiqout_end;
246		fiqhandler_start = &floppy_fiqout_start;
247		fiqhandler_length = &floppy_fiqout_end - &floppy_fiqout_start;
248	}
249
250	regs.ARM_r9  = dma->count;
251	regs.ARM_r10 = (unsigned long)dma->addr;
252	regs.ARM_fp  = (unsigned long)FLOPPYDMA_BASE;
253
254	if (claim_fiq(&fh)) {
255		printk("floppydma: couldn't claim FIQ.\n");
256		return;
257	}
258
259	set_fiq_handler(fiqhandler_start, fiqhandler_length);
260	set_fiq_regs(&regs);
261	enable_fiq(dma->dma_irq);
262}
263
264static void floppy_disable_dma(dmach_t channel, dma_t *dma)
265{
266	disable_fiq(dma->dma_irq);
267	release_fiq(&fh);
268}
269
270static int floppy_get_residue(dmach_t channel, dma_t *dma)
271{
272	struct pt_regs regs;
273	get_fiq_regs(&regs);
274	return regs.ARM_r9;
275}
276
277static struct dma_ops floppy_dma_ops = {
278	.type		= "FIQDMA",
279	.enable		= floppy_enable_dma,
280	.disable	= floppy_disable_dma,
281	.residue	= floppy_get_residue,
282};
283
284/*
285 * This is virtual DMA - we don't need anything here.
286 */
287static void sound_enable_disable_dma(dmach_t channel, dma_t *dma)
288{
289}
290
291static struct dma_ops sound_dma_ops = {
292	.type		= "VIRTUAL",
293	.enable		= sound_enable_disable_dma,
294	.disable	= sound_enable_disable_dma,
295};
296
297void __init arch_dma_init(dma_t *dma)
298{
299	iomd_writeb(0, IOMD_IO0CR);
300	iomd_writeb(0, IOMD_IO1CR);
301	iomd_writeb(0, IOMD_IO2CR);
302	iomd_writeb(0, IOMD_IO3CR);
303
304	iomd_writeb(0xa0, IOMD_DMATCR);
305
306	dma[DMA_0].dma_base		= IOMD_IO0CURA;
307	dma[DMA_0].dma_irq		= IRQ_DMA0;
308	dma[DMA_0].d_ops		= &iomd_dma_ops;
309	dma[DMA_1].dma_base		= IOMD_IO1CURA;
310	dma[DMA_1].dma_irq		= IRQ_DMA1;
311	dma[DMA_1].d_ops		= &iomd_dma_ops;
312	dma[DMA_2].dma_base		= IOMD_IO2CURA;
313	dma[DMA_2].dma_irq		= IRQ_DMA2;
314	dma[DMA_2].d_ops		= &iomd_dma_ops;
315	dma[DMA_3].dma_base		= IOMD_IO3CURA;
316	dma[DMA_3].dma_irq		= IRQ_DMA3;
317	dma[DMA_3].d_ops		= &iomd_dma_ops;
318	dma[DMA_S0].dma_base		= IOMD_SD0CURA;
319	dma[DMA_S0].dma_irq		= IRQ_DMAS0;
320	dma[DMA_S0].d_ops		= &iomd_dma_ops;
321	dma[DMA_S1].dma_base		= IOMD_SD1CURA;
322	dma[DMA_S1].dma_irq		= IRQ_DMAS1;
323	dma[DMA_S1].d_ops		= &iomd_dma_ops;
324	dma[DMA_VIRTUAL_FLOPPY].dma_irq	= FIQ_FLOPPYDATA;
325	dma[DMA_VIRTUAL_FLOPPY].d_ops	= &floppy_dma_ops;
326	dma[DMA_VIRTUAL_SOUND].d_ops	= &sound_dma_ops;
327
328	/*
329	 * Setup DMA channels 2,3 to be for podules
330	 * and channels 0,1 for internal devices
331	 */
332	iomd_writeb(DMA_EXT_IO3|DMA_EXT_IO2, IOMD_DMAEXT);
333}
334