| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * linux/arch/unicore32/kernel/dma.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Code specific to PKUnity SoC and UniCore ISA | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> | 
					
						
							|  |  |  |  *	Copyright (C) 2001-2010 Guan Xuetao | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 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/module.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/interrupt.h>
 | 
					
						
							|  |  |  | #include <linux/errno.h>
 | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | #include <linux/io.h>
 | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <asm/irq.h>
 | 
					
						
							|  |  |  | #include <mach/hardware.h>
 | 
					
						
							|  |  |  | #include <mach/dma.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct dma_channel { | 
					
						
							|  |  |  | 	char *name; | 
					
						
							|  |  |  | 	puv3_dma_prio prio; | 
					
						
							|  |  |  | 	void (*irq_handler)(int, void *); | 
					
						
							|  |  |  | 	void (*err_handler)(int, void *); | 
					
						
							|  |  |  | 	void *data; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct dma_channel dma_channels[MAX_DMA_CHANNELS]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int puv3_request_dma(char *name, puv3_dma_prio prio, | 
					
						
							|  |  |  | 			 void (*irq_handler)(int, void *), | 
					
						
							|  |  |  | 			 void (*err_handler)(int, void *), | 
					
						
							|  |  |  | 			 void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 	int i, found = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* basic sanity checks */ | 
					
						
							|  |  |  | 	if (!name) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	local_irq_save(flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	do { | 
					
						
							|  |  |  | 		/* try grabbing a DMA channel with the requested priority */ | 
					
						
							|  |  |  | 		for (i = 0; i < MAX_DMA_CHANNELS; i++) { | 
					
						
							|  |  |  | 			if ((dma_channels[i].prio == prio) && | 
					
						
							|  |  |  | 			    !dma_channels[i].name) { | 
					
						
							|  |  |  | 				found = 1; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		/* if requested prio group is full, try a hier priority */ | 
					
						
							|  |  |  | 	} while (!found && prio--); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (found) { | 
					
						
							|  |  |  | 		dma_channels[i].name = name; | 
					
						
							|  |  |  | 		dma_channels[i].irq_handler = irq_handler; | 
					
						
							|  |  |  | 		dma_channels[i].err_handler = err_handler; | 
					
						
							|  |  |  | 		dma_channels[i].data = data; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		printk(KERN_WARNING "No more available DMA channels for %s\n", | 
					
						
							|  |  |  | 				name); | 
					
						
							|  |  |  | 		i = -ENODEV; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	local_irq_restore(flags); | 
					
						
							|  |  |  | 	return i; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(puv3_request_dma); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void puv3_free_dma(int dma_ch) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!dma_channels[dma_ch].name) { | 
					
						
							|  |  |  | 		printk(KERN_CRIT | 
					
						
							|  |  |  | 			"%s: trying to free channel %d which is already freed\n", | 
					
						
							|  |  |  | 			__func__, dma_ch); | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	local_irq_save(flags); | 
					
						
							|  |  |  | 	dma_channels[dma_ch].name = NULL; | 
					
						
							|  |  |  | 	dma_channels[dma_ch].err_handler = NULL; | 
					
						
							|  |  |  | 	local_irq_restore(flags); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL(puv3_free_dma); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static irqreturn_t dma_irq_handler(int irq, void *dev_id) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 	int i, dint; | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 	dint = readl(DMAC_ITCSR); | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 	for (i = 0; i < MAX_DMA_CHANNELS; i++) { | 
					
						
							|  |  |  | 		if (dint & DMAC_CHANNEL(i)) { | 
					
						
							|  |  |  | 			struct dma_channel *channel = &dma_channels[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Clear TC interrupt of channel i */ | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 			writel(DMAC_CHANNEL(i), DMAC_ITCCR); | 
					
						
							|  |  |  | 			writel(0, DMAC_ITCCR); | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if (channel->name && channel->irq_handler) { | 
					
						
							|  |  |  | 				channel->irq_handler(i, channel->data); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				/*
 | 
					
						
							|  |  |  | 				 * IRQ for an unregistered DMA channel: | 
					
						
							|  |  |  | 				 * let's clear the interrupts and disable it. | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 				printk(KERN_WARNING "spurious IRQ for" | 
					
						
							|  |  |  | 						" DMA channel %d\n", i); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return IRQ_HANDLED; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static irqreturn_t dma_err_handler(int irq, void *dev_id) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 	int i, dint; | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 	dint = readl(DMAC_IESR); | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 	for (i = 0; i < MAX_DMA_CHANNELS; i++) { | 
					
						
							|  |  |  | 		if (dint & DMAC_CHANNEL(i)) { | 
					
						
							|  |  |  | 			struct dma_channel *channel = &dma_channels[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Clear Err interrupt of channel i */ | 
					
						
							| 
									
										
										
										
											2011-02-26 21:21:18 +08:00
										 |  |  | 			writel(DMAC_CHANNEL(i), DMAC_IECR); | 
					
						
							|  |  |  | 			writel(0, DMAC_IECR); | 
					
						
							| 
									
										
										
										
											2011-01-15 18:18:29 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if (channel->name && channel->err_handler) { | 
					
						
							|  |  |  | 				channel->err_handler(i, channel->data); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				/*
 | 
					
						
							|  |  |  | 				 * IRQ for an unregistered DMA channel: | 
					
						
							|  |  |  | 				 * let's clear the interrupts and disable it. | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 				printk(KERN_WARNING "spurious IRQ for" | 
					
						
							|  |  |  | 						" DMA channel %d\n", i); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return IRQ_HANDLED; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int __init puv3_init_dma(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i, ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* dma channel priorities on v8 processors:
 | 
					
						
							|  |  |  | 	 * ch 0 - 1  <--> (0) DMA_PRIO_HIGH | 
					
						
							|  |  |  | 	 * ch 2 - 3  <--> (1) DMA_PRIO_MEDIUM | 
					
						
							|  |  |  | 	 * ch 4 - 5  <--> (2) DMA_PRIO_LOW | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	for (i = 0; i < MAX_DMA_CHANNELS; i++) { | 
					
						
							|  |  |  | 		puv3_stop_dma(i); | 
					
						
							|  |  |  | 		dma_channels[i].name = NULL; | 
					
						
							|  |  |  | 		dma_channels[i].prio = min((i & 0x7) >> 1, DMA_PRIO_LOW); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = request_irq(IRQ_DMA, dma_irq_handler, 0, "DMA", NULL); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		printk(KERN_CRIT "Can't register IRQ for DMA\n"); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = request_irq(IRQ_DMAERR, dma_err_handler, 0, "DMAERR", NULL); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		printk(KERN_CRIT "Can't register IRQ for DMAERR\n"); | 
					
						
							|  |  |  | 		free_irq(IRQ_DMA, "DMA"); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | postcore_initcall(puv3_init_dma); |