| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 | /* linux/arch/arm/plat-s3c64xx/dma.c * * Copyright 2009 Openmoko, Inc. * Copyright 2009 Simtec Electronics *	Ben Dooks <ben@simtec.co.uk> *	http://armlinux.simtec.co.uk/ * * S3C64XX DMA core * * 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/kernel.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/dmapool.h>#include <linux/device.h>#include <linux/errno.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/clk.h>#include <linux/err.h>#include <linux/io.h>#include <mach/dma.h>#include <mach/map.h>#include <mach/irqs.h>#include <mach/regs-sys.h>#include <asm/hardware/pl080.h>/* dma channel state information */struct s3c64xx_dmac {	struct device		dev;	struct clk		*clk;	void __iomem		*regs;	struct s3c2410_dma_chan *channels;	enum dma_ch		 chanbase;};/* pool to provide LLI buffers */static struct dma_pool *dma_pool;/* Debug configuration and code */static unsigned char debug_show_buffs = 0;static void dbg_showchan(struct s3c2410_dma_chan *chan){	pr_debug("DMA%d: %08x->%08x L %08x C %08x,%08x S %08x\n",		 chan->number,		 readl(chan->regs + PL080_CH_SRC_ADDR),		 readl(chan->regs + PL080_CH_DST_ADDR),		 readl(chan->regs + PL080_CH_LLI),		 readl(chan->regs + PL080_CH_CONTROL),		 readl(chan->regs + PL080S_CH_CONTROL2),		 readl(chan->regs + PL080S_CH_CONFIG));}static void show_lli(struct pl080s_lli *lli){	pr_debug("LLI[%p] %08x->%08x, NL %08x C %08x,%08x\n",		 lli, lli->src_addr, lli->dst_addr, lli->next_lli,		 lli->control0, lli->control1);}static void dbg_showbuffs(struct s3c2410_dma_chan *chan){	struct s3c64xx_dma_buff *ptr;	struct s3c64xx_dma_buff *end;	pr_debug("DMA%d: buffs next %p, curr %p, end %p\n",		 chan->number, chan->next, chan->curr, chan->end);	ptr = chan->next;	end = chan->end;	if (debug_show_buffs) {		for (; ptr != NULL; ptr = ptr->next) {			pr_debug("DMA%d: %08x ",				 chan->number, ptr->lli_dma);			show_lli(ptr->lli);		}	}}/* End of Debug */static struct s3c2410_dma_chan *s3c64xx_dma_map_channel(unsigned int channel){	struct s3c2410_dma_chan *chan;	unsigned int start, offs;	start = 0;	if (channel >= DMACH_PCM1_TX)		start = 8;	for (offs = 0; offs < 8; offs++) {		chan = &s3c2410_chans[start + offs];		if (!chan->in_use)			goto found;	}	return NULL;found:	s3c_dma_chan_map[channel] = chan;	return chan;}int s3c2410_dma_config(enum dma_ch channel, int xferunit){	struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel);	if (chan == NULL)		return -EINVAL;	switch (xferunit) {	case 1:		chan->hw_width = 0;		break;	case 2:		chan->hw_width = 1;		break;	case 4:		chan->hw_width = 2;		break;	default:		printk(KERN_ERR "%s: illegal width %d\n", __func__, xferunit);		return -EINVAL;	}	return 0;}EXPORT_SYMBOL(s3c2410_dma_config);static void s3c64xx_dma_fill_lli(struct s3c2410_dma_chan *chan,				 struct pl080s_lli *lli,				 dma_addr_t data, int size){	dma_addr_t src, dst;	u32 control0, control1;	switch (chan->source) {	case DMA_FROM_DEVICE:		src = chan->dev_addr;		dst = data;		control0 = PL080_CONTROL_SRC_AHB2;		control0 |= PL080_CONTROL_DST_INCR;		break;	case DMA_TO_DEVICE:		src = data;		dst = chan->dev_addr;		control0 = PL080_CONTROL_DST_AHB2;		control0 |= PL080_CONTROL_SRC_INCR;		break;	default:		BUG();	}	/* note, we do not currently setup any of the burst controls */	control1 = size >> chan->hw_width;	/* size in no of xfers */	control0 |= PL080_CONTROL_PROT_SYS;	/* always in priv. mode */	control0 |= PL080_CONTROL_TC_IRQ_EN;	/* always fire IRQ */	control0 |= (u32)chan->hw_width << PL080_CONTROL_DWIDTH_SHIFT;	control0 |= (u32)chan->hw_width << PL080_CONTROL_SWIDTH_SHIFT;	lli->src_addr = src;	lli->dst_addr = dst;	lli->next_lli = 0;	lli->control0 = control0;	lli->control1 = control1;}static void s3c64xx_lli_to_regs(struct s3c2410_dma_chan *chan,				struct pl080s_lli *lli){	void __iomem *regs = chan->regs;	pr_debug("%s: LLI %p => regs\n", __func__, lli);	show_lli(lli);	writel(lli->src_addr, regs + PL080_CH_SRC_ADDR);	writel(lli->dst_addr, regs + PL080_CH_DST_ADDR);	writel(lli->next_lli, regs + PL080_CH_LLI);	writel(lli->control0, regs + PL080_CH_CONTROL);	writel(lli->control1, regs + PL080S_CH_CONTROL2);}static int s3c64xx_dma_start(struct s3c2410_dma_chan *chan){	struct s3c64xx_dmac *dmac = chan->dmac;	u32 config;	u32 bit = chan->bit;	dbg_showchan(chan);	pr_debug("%s: clearing interrupts\n", __func__);	/* clear interrupts */	writel(bit, dmac->regs + PL080_TC_CLEAR);	writel(bit, dmac->regs + PL080_ERR_CLEAR);	pr_debug("%s: starting channel\n", __func__);	config = readl(chan->regs + PL080S_CH_CONFIG);	config |= PL080_CONFIG_ENABLE;	config &= ~PL080_CONFIG_HALT;	pr_debug("%s: writing config %08x\n", __func__, config);	writel(config, chan->regs + PL080S_CH_CONFIG);	return 0;}static int s3c64xx_dma_stop(struct s3c2410_dma_chan *chan){	u32 config;	int timeout;	pr_debug("%s: stopping channel\n", __func__);	dbg_showchan(chan);	config = readl(chan->regs + PL080S_CH_CONFIG);	config |= PL080_CONFIG_HALT;	writel(config, chan->regs + PL080S_CH_CONFIG);	timeout = 1000;	do {		config = readl(chan->regs + PL080S_CH_CONFIG);		pr_debug("%s: %d - config %08x\n", __func__, timeout, config);		if (config & PL080_CONFIG_ACTIVE)			udelay(10);		else			break;		} while (--timeout > 0);	if (config & PL080_CONFIG_ACTIVE) {		printk(KERN_ERR "%s: channel still active\n", __func__);		return -EFAULT;	}	config = readl(chan->regs + PL080S_CH_CONFIG);	config &= ~PL080_CONFIG_ENABLE;	writel(config, chan->regs + PL080S_CH_CONFIG);
 |