/* linux/arch/arm/plat-s3c64xx/dma.c * * Copyright 2009 Openmoko, Inc. * Copyright 2009 Simtec Electronics * Ben Dooks * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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); return 0; } static inline void s3c64xx_dma_bufffdone(struct s3c2410_dma_chan *chan, struct s3c64xx_dma_buff *buf, enum s3c2410_dma_buffresult result) { if (chan->callback_fn != NULL) (chan->callback_fn)(chan, buf->pw, 0, result); } static void s3c64xx_dma_freebuff(struct s3c64xx_dma_buff *buff) { dma_pool_free(dma_pool, buff->lli, buff->lli_dma); kfree(buff); } static int s3c64xx_dma_flush(struct s3c2410_dma_chan *chan) { struct s3c64xx_dma_buff *buff, *next; u32 config; dbg_showchan(chan); pr_debug("%s: flushing channel\n", __func__); config = readl(chan->regs + PL080S_CH_CONFIG); config &= ~PL080_CONFIG_ENABLE; writel(config, chan->regs + PL080S_CH_CONFIG); /* dump all the buffers associated with this channel */ for (buff = chan->curr; buff != NULL; buff = next) { next = buff->next; pr_debug("%s: buff %p (next %p)\n", __func__, buff, buff->next); s3c64xx_dma_bufffdone(chan, buff, S3C2410_RES_ABORT); s3c64xx_dma_freebuff(buff); } chan->curr = chan->next = chan->end = NULL; return 0; } int s3c2410_dma_ctrl(enum dma_ch channel, enum s3c2410_chan_op op) { struct s3c2410_dma_chan *chan = s3c_dma_lookup_channel(channel); WARN_ON(!chan);