diff options
Diffstat (limited to 'arch/xtensa/variants/s6000/dmac.c')
-rw-r--r-- | arch/xtensa/variants/s6000/dmac.c | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/arch/xtensa/variants/s6000/dmac.c b/arch/xtensa/variants/s6000/dmac.c new file mode 100644 index 000000000000..dc7f7c573518 --- /dev/null +++ b/arch/xtensa/variants/s6000/dmac.c @@ -0,0 +1,173 @@ +/* + * Authors: Oskar Schirmer <os@emlix.com> + * Daniel Gloeckner <dg@emlix.com> + * (c) 2008 emlix GmbH http://www.emlix.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <asm/cacheflush.h> +#include <variant/dmac.h> + +/* DMA engine lookup */ + +struct s6dmac_ctrl s6dmac_ctrl[S6_DMAC_NB]; + + +/* DMA control, per engine */ + +void s6dmac_put_fifo_cache(u32 dmac, int chan, u32 src, u32 dst, u32 size) +{ + if (xtensa_need_flush_dma_source(src)) { + u32 base = src; + u32 span = size; + u32 chunk = readl(DMA_CHNL(dmac, chan) + S6_DMA_CMONCHUNK); + if (chunk && (size > chunk)) { + s32 skip = + readl(DMA_CHNL(dmac, chan) + S6_DMA_SRCSKIP); + u32 gaps = (size+chunk-1)/chunk - 1; + if (skip >= 0) { + span += gaps * skip; + } else if (-skip > chunk) { + s32 decr = gaps * (chunk + skip); + base += decr; + span = chunk - decr; + } else { + span = max(span + gaps * skip, + (chunk + skip) * gaps - skip); + } + } + flush_dcache_unaligned(base, span); + } + if (xtensa_need_invalidate_dma_destination(dst)) { + u32 base = dst; + u32 span = size; + u32 chunk = readl(DMA_CHNL(dmac, chan) + S6_DMA_CMONCHUNK); + if (chunk && (size > chunk)) { + s32 skip = + readl(DMA_CHNL(dmac, chan) + S6_DMA_DSTSKIP); + u32 gaps = (size+chunk-1)/chunk - 1; + if (skip >= 0) { + span += gaps * skip; + } else if (-skip > chunk) { + s32 decr = gaps * (chunk + skip); + base += decr; + span = chunk - decr; + } else { + span = max(span + gaps * skip, + (chunk + skip) * gaps - skip); + } + } + invalidate_dcache_unaligned(base, span); + } + s6dmac_put_fifo(dmac, chan, src, dst, size); +} + +void s6dmac_disable_error_irqs(u32 dmac, u32 mask) +{ + unsigned long flags; + spinlock_t *spinl = &s6dmac_ctrl[_dmac_addr_index(dmac)].lock; + spin_lock_irqsave(spinl, flags); + _s6dmac_disable_error_irqs(dmac, mask); + spin_unlock_irqrestore(spinl, flags); +} + +u32 s6dmac_int_sources(u32 dmac, u32 channel) +{ + u32 mask, ret, tmp; + mask = 1 << channel; + + tmp = readl(dmac + S6_DMA_TERMCNTIRQSTAT); + tmp &= mask; + writel(tmp, dmac + S6_DMA_TERMCNTIRQCLR); + ret = tmp >> channel; + + tmp = readl(dmac + S6_DMA_PENDCNTIRQSTAT); + tmp &= mask; + writel(tmp, dmac + S6_DMA_PENDCNTIRQCLR); + ret |= (tmp >> channel) << 1; + + tmp = readl(dmac + S6_DMA_LOWWMRKIRQSTAT); + tmp &= mask; + writel(tmp, dmac + S6_DMA_LOWWMRKIRQCLR); + ret |= (tmp >> channel) << 2; + + tmp = readl(dmac + S6_DMA_INTRAW0); + tmp &= (mask << S6_DMA_INT0_OVER) | (mask << S6_DMA_INT0_UNDER); + writel(tmp, dmac + S6_DMA_INTCLEAR0); + + if (tmp & (mask << S6_DMA_INT0_UNDER)) + ret |= 1 << 3; + if (tmp & (mask << S6_DMA_INT0_OVER)) + ret |= 1 << 4; + + tmp = readl(dmac + S6_DMA_MASTERERRINFO); + mask <<= S6_DMA_INT1_CHANNEL; + if (((tmp >> S6_DMA_MASTERERR_CHAN(0)) & S6_DMA_MASTERERR_CHAN_MASK) + == channel) + mask |= 1 << S6_DMA_INT1_MASTER; + if (((tmp >> S6_DMA_MASTERERR_CHAN(1)) & S6_DMA_MASTERERR_CHAN_MASK) + == channel) + mask |= 1 << (S6_DMA_INT1_MASTER + 1); + if (((tmp >> S6_DMA_MASTERERR_CHAN(2)) & S6_DMA_MASTERERR_CHAN_MASK) + == channel) + mask |= 1 << (S6_DMA_INT1_MASTER + 2); + + tmp = readl(dmac + S6_DMA_INTRAW1) & mask; + writel(tmp, dmac + S6_DMA_INTCLEAR1); + ret |= ((tmp >> channel) & 1) << 5; + ret |= ((tmp >> S6_DMA_INT1_MASTER) & S6_DMA_INT1_MASTER_MASK) << 6; + + return ret; +} + +void s6dmac_release_chan(u32 dmac, int chan) +{ + if (chan >= 0) + s6dmac_disable_chan(dmac, chan); +} + + +/* global init */ + +static inline void __init dmac_init(u32 dmac, u8 chan_nb) +{ + s6dmac_ctrl[S6_DMAC_INDEX(dmac)].dmac = dmac; + spin_lock_init(&s6dmac_ctrl[S6_DMAC_INDEX(dmac)].lock); + s6dmac_ctrl[S6_DMAC_INDEX(dmac)].chan_nb = chan_nb; + writel(S6_DMA_INT1_MASTER_MASK << S6_DMA_INT1_MASTER, + dmac + S6_DMA_INTCLEAR1); +} + +static inline void __init dmac_master(u32 dmac, + u32 m0start, u32 m0end, u32 m1start, u32 m1end) +{ + writel(m0start, dmac + S6_DMA_MASTER0START); + writel(m0end - 1, dmac + S6_DMA_MASTER0END); + writel(m1start, dmac + S6_DMA_MASTER1START); + writel(m1end - 1, dmac + S6_DMA_MASTER1END); +} + +static void __init s6_dmac_init(void) +{ + dmac_init(S6_REG_LMSDMA, S6_LMSDMA_NB); + dmac_master(S6_REG_LMSDMA, + S6_MEM_DDR, S6_MEM_PCIE_APER, S6_MEM_EFI, S6_MEM_GMAC); + dmac_init(S6_REG_NIDMA, S6_NIDMA_NB); + dmac_init(S6_REG_DPDMA, S6_DPDMA_NB); + dmac_master(S6_REG_DPDMA, + S6_MEM_DDR, S6_MEM_PCIE_APER, S6_REG_DP, S6_REG_DPDMA); + dmac_init(S6_REG_HIFDMA, S6_HIFDMA_NB); + dmac_master(S6_REG_HIFDMA, + S6_MEM_GMAC, S6_MEM_PCIE_CFG, S6_MEM_PCIE_APER, S6_MEM_AUX); +} + +arch_initcall(s6_dmac_init); |