summaryrefslogtreecommitdiff
path: root/arch/arm/mach-bcm2708/dma.c
blob: a5e58d1f847564e1e70c006c9b739b568da894fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
/*
 *  linux/arch/arm/mach-bcm2708/dma.c
 *
 *  Copyright (C) 2010 Broadcom
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/scatterlist.h>

#include <mach/dma.h>
#include <mach/irqs.h>

/*****************************************************************************\
 *									     *
 * Configuration							     *
 *									     *
\*****************************************************************************/

#define CACHE_LINE_MASK 31
#define DRIVER_NAME BCM_DMAMAN_DRIVER_NAME
#define DEFAULT_DMACHAN_BITMAP 0x10  /* channel 4 only */

/* valid only for channels 0 - 14, 15 has its own base address */
#define BCM2708_DMA_CHAN(n)	((n)<<8) /* base address */
#define BCM2708_DMA_CHANIO(dma_base, n) \
   ((void __iomem *)((char *)(dma_base)+BCM2708_DMA_CHAN(n)))


/*****************************************************************************\
 *									     *
 * DMA Auxilliary Functions						     *
 *									     *
\*****************************************************************************/

/* A DMA buffer on an arbitrary boundary may separate a cache line into a
   section inside the DMA buffer and another section outside it.
   Even if we flush DMA buffers from the cache there is always the chance that
   during a DMA someone will access the part of a cache line that is outside
   the DMA buffer - which will then bring in unwelcome data.
   Without being able to dictate our own buffer pools we must insist that
   DMA buffers consist of a whole number of cache lines.
*/

extern int
bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len)
{
	int i;

	for (i = 0; i < sg_len; i++) {
		if (sg_ptr[i].offset & CACHE_LINE_MASK ||
		    sg_ptr[i].length & CACHE_LINE_MASK)
			return 0;
	}

	return 1;
}
EXPORT_SYMBOL_GPL(bcm_sg_suitable_for_dma);

extern void
bcm_dma_start(void __iomem *dma_chan_base, dma_addr_t control_block)
{
	dsb();	/* ARM data synchronization (push) operation */

	writel(control_block,	     dma_chan_base + BCM2708_DMA_ADDR);
	writel(BCM2708_DMA_ACTIVE,   dma_chan_base + BCM2708_DMA_CS);
}

extern void bcm_dma_wait_idle(void __iomem *dma_chan_base)
{
  dsb();

  /* ugly busy wait only option for now */
  while (readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE)
    cpu_relax();
}

EXPORT_SYMBOL_GPL(bcm_dma_start);

extern bool bcm_dma_is_busy(void __iomem *dma_chan_base)
{
	dsb();

	return readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE;
}
EXPORT_SYMBOL_GPL(bcm_dma_is_busy);

/* Complete an ongoing DMA (assuming its results are to be ignored)
   Does nothing if there is no DMA in progress.
   This routine waits for the current AXI transfer to complete before
   terminating the current DMA.	 If the current transfer is hung on a DREQ used
   by an uncooperative peripheral the AXI transfer may never complete.	In this
   case the routine times out and return a non-zero error code.
   Use of this routine doesn't guarantee that the ongoing or aborted DMA
   does not produce an interrupt.
*/
extern int
bcm_dma_abort(void __iomem *dma_chan_base)
{
	unsigned long int cs;
	int rc = 0;

	cs = readl(dma_chan_base + BCM2708_DMA_CS);

	if (BCM2708_DMA_ACTIVE & cs) {
		long int timeout = 10000;

		/* write 0 to the active bit - pause the DMA */
		writel(0, dma_chan_base + BCM2708_DMA_CS);

		/* wait for any current AXI transfer to complete */
		while (0 != (cs & BCM2708_DMA_ISPAUSED) && --timeout >= 0)
			cs = readl(dma_chan_base + BCM2708_DMA_CS);

		if (0 != (cs & BCM2708_DMA_ISPAUSED)) {
			/* we'll un-pause when we set of our next DMA */
			rc = -ETIMEDOUT;

		} else if (BCM2708_DMA_ACTIVE & cs) {
			/* terminate the control block chain */
			writel(0, dma_chan_base + BCM2708_DMA_NEXTCB);

			/* abort the whole DMA */
			writel(BCM2708_DMA_ABORT | BCM2708_DMA_ACTIVE,
			       dma_chan_base + BCM2708_DMA_CS);
		}
	}

	return rc;
}
EXPORT_SYMBOL_GPL(bcm_dma_abort);


/***************************************************************************** \
 *									     *
 * DMA Manager Device Methods						     *
 *									     *
\*****************************************************************************/

struct vc_dmaman {
	void __iomem *dma_base;
	u32 chan_available; /* bitmap of available channels */
	u32 has_feature[BCM_DMA_FEATURE_COUNT]; /* bitmap of feature presence */
};

static void vc_dmaman_init(struct vc_dmaman *dmaman, void __iomem *dma_base,
			   u32 chans_available)
{
	dmaman->dma_base = dma_base;
	dmaman->chan_available = chans_available;
	dmaman->has_feature[BCM_DMA_FEATURE_FAST_ORD] = 0x0c;  /* chans 2 & 3 */
	dmaman->has_feature[BCM_DMA_FEATURE_BULK_ORD] = 0x01;  /* chan 0 */
	dmaman->has_feature[BCM_DMA_FEATURE_NORMAL_ORD] = 0xfe;  /* chans 1 to 7 */
	dmaman->has_feature[BCM_DMA_FEATURE_LITE_ORD] = 0x7f00;  /* chans 8 to 14 */
}

static int vc_dmaman_chan_alloc(struct vc_dmaman *dmaman,
				unsigned preferred_feature_set)
{
	u32 chans;
	int feature;

	chans = dmaman->chan_available;
	for (feature = 0; feature < BCM_DMA_FEATURE_COUNT; feature++)
		/* select the subset of available channels with the desired
		   feature so long as some of the candidate channels have that
		   feature */
		if ((preferred_feature_set & (1 << feature)) &&
		    (chans & dmaman->has_feature[feature]))
			chans &= dmaman->has_feature[feature];

	if (chans) {
		int chan = 0;
		/* return the ordinal of the first channel in the bitmap */
		while (chans != 0 && (chans & 1) == 0) {
			chans >>= 1;
			chan++;
		}
		/* claim the channel */
		dmaman->chan_available &= ~(1 << chan);
		return chan;
	} else
		return -ENOMEM;
}

static int vc_dmaman_chan_free(struct vc_dmaman *dmaman, int chan)
{
	if (chan < 0)
		return -EINVAL;
	else if ((1 << chan) & dmaman->chan_available)
		return -EIDRM;
	else {
		dmaman->chan_available |= (1 << chan);
		return 0;
	}
}

/*****************************************************************************\
 *									     *
 * DMA IRQs								     *
 *									     *
\*****************************************************************************/

static unsigned char bcm_dma_irqs[] = {
	IRQ_DMA0,
	IRQ_DMA1,
	IRQ_DMA2,
	IRQ_DMA3,
	IRQ_DMA4,
	IRQ_DMA5,
	IRQ_DMA6,
	IRQ_DMA7,
	IRQ_DMA8,
	IRQ_DMA9,
	IRQ_DMA10,
	IRQ_DMA11,
	IRQ_DMA12
};


/***************************************************************************** \
 *									     *
 * DMA Manager Monitor							     *
 *									     *
\*****************************************************************************/

static struct device *dmaman_dev;	/* we assume there's only one! */

extern int bcm_dma_chan_alloc(unsigned preferred_feature_set,
			      void __iomem **out_dma_base, int *out_dma_irq)
{
	if (!dmaman_dev)
		return -ENODEV;
	else {
		struct vc_dmaman *dmaman  = dev_get_drvdata(dmaman_dev);
		int rc;

		device_lock(dmaman_dev);
		rc = vc_dmaman_chan_alloc(dmaman, preferred_feature_set);
		if (rc >= 0) {
			*out_dma_base = BCM2708_DMA_CHANIO(dmaman->dma_base,
							   rc);
			*out_dma_irq = bcm_dma_irqs[rc];
		}
		device_unlock(dmaman_dev);

		return rc;
	}
}
EXPORT_SYMBOL_GPL(bcm_dma_chan_alloc);

extern int bcm_dma_chan_free(int channel)
{
	if (dmaman_dev) {
		struct vc_dmaman *dmaman = dev_get_drvdata(dmaman_dev);
		int rc;

		device_lock(dmaman_dev);
		rc = vc_dmaman_chan_free(dmaman, channel);
		device_unlock(dmaman_dev);

		return rc;
	} else
		return -ENODEV;
}
EXPORT_SYMBOL_GPL(bcm_dma_chan_free);

static int dev_dmaman_register(const char *dev_name, struct device *dev)
{
	int rc = dmaman_dev ? -EINVAL : 0;
	dmaman_dev = dev;
	return rc;
}

static void dev_dmaman_deregister(const char *dev_name, struct device *dev)
{
	dmaman_dev = NULL;
}

/*****************************************************************************\
 *									     *
 * DMA Device								     *
 *									     *
\*****************************************************************************/

static int dmachans = -1; /* module parameter */

static int bcm_dmaman_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct vc_dmaman *dmaman;
	struct resource *dma_res = NULL;
	void __iomem *dma_base = NULL;
	int have_dma_region = 0;

	dmaman = kzalloc(sizeof(*dmaman), GFP_KERNEL);
	if (NULL == dmaman) {
		printk(KERN_ERR DRIVER_NAME ": failed to allocate "
		       "DMA management memory\n");
		ret = -ENOMEM;
	} else {

		dma_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
		if (dma_res == NULL) {
			printk(KERN_ERR DRIVER_NAME ": failed to obtain memory "
			       "resource\n");
			ret = -ENODEV;
		} else if (!request_mem_region(dma_res->start,
					       resource_size(dma_res),
					       DRIVER_NAME)) {
			dev_err(&pdev->dev, "cannot obtain DMA region\n");
			ret = -EBUSY;
		} else {
			have_dma_region = 1;
			dma_base = ioremap(dma_res->start,
					   resource_size(dma_res));
			if (!dma_base) {
				dev_err(&pdev->dev, "cannot map DMA region\n");
				ret = -ENOMEM;
			} else {
				/* use module parameter if one was provided */
				if (dmachans > 0)
					vc_dmaman_init(dmaman, dma_base,
						       dmachans);
				else
					vc_dmaman_init(dmaman, dma_base,
						       DEFAULT_DMACHAN_BITMAP);

				platform_set_drvdata(pdev, dmaman);
				dev_dmaman_register(DRIVER_NAME, &pdev->dev);

				printk(KERN_INFO DRIVER_NAME ": DMA manager "
				       "at %p\n", dma_base);
			}
		}
	}
	if (ret != 0) {
		if (dma_base)
			iounmap(dma_base);
		if (dma_res && have_dma_region)
			release_mem_region(dma_res->start,
					   resource_size(dma_res));
		if (dmaman)
			kfree(dmaman);
	}
	return ret;
}

static int bcm_dmaman_remove(struct platform_device *pdev)
{
	struct vc_dmaman *dmaman = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);
	dev_dmaman_deregister(DRIVER_NAME, &pdev->dev);
	kfree(dmaman);

	return 0;
}

static struct platform_driver bcm_dmaman_driver = {
	.probe = bcm_dmaman_probe,
	.remove = bcm_dmaman_remove,

	.driver = {
		   .name = DRIVER_NAME,
		   .owner = THIS_MODULE,
		   },
};

/*****************************************************************************\
 *									     *
 * Driver init/exit							     *
 *									     *
\*****************************************************************************/

static int __init bcm_dmaman_drv_init(void)
{
	int ret;

	ret = platform_driver_register(&bcm_dmaman_driver);
	if (ret != 0) {
		printk(KERN_ERR DRIVER_NAME ": failed to register "
		       "on platform\n");
	}

	return ret;
}

static void __exit bcm_dmaman_drv_exit(void)
{
	platform_driver_unregister(&bcm_dmaman_driver);
}

module_init(bcm_dmaman_drv_init);
module_exit(bcm_dmaman_drv_exit);

module_param(dmachans, int, 0644);

MODULE_AUTHOR("Gray Girling <grayg@broadcom.com>");
MODULE_DESCRIPTION("DMA channel manager driver");
MODULE_LICENSE("GPL");

MODULE_PARM_DESC(dmachans, "Bitmap of DMA channels available to the ARM");