diff options
Diffstat (limited to 'arch/sh/drivers/dma/dma-sh.c')
| -rw-r--r-- | arch/sh/drivers/dma/dma-sh.c | 342 |
1 files changed, 246 insertions, 96 deletions
diff --git a/arch/sh/drivers/dma/dma-sh.c b/arch/sh/drivers/dma/dma-sh.c index 31dacd4444b..b2256562314 100644 --- a/arch/sh/drivers/dma/dma-sh.c +++ b/arch/sh/drivers/dma/dma-sh.c @@ -5,66 +5,81 @@ * * Copyright (C) 2000 Takashi YOSHII * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive * for more details. */ - -#include <linux/config.h> #include <linux/init.h> -#include <linux/irq.h> #include <linux/interrupt.h> #include <linux/module.h> -#include <asm/signal.h> -#include <asm/irq.h> +#include <linux/io.h> +#include <mach-dreamcast/mach/dma.h> #include <asm/dma.h> -#include <asm/io.h> -#include "dma-sh.h" +#include <asm/dma-register.h> +#include <cpu/dma-register.h> +#include <cpu/dma.h> /* - * The SuperH DMAC supports a number of transmit sizes, we list them here, - * with their respective values as they appear in the CHCR registers. - * - * Defaults to a 64-bit transfer size. + * Define the default configuration for dual address memory-memory transfer. + * The 0x400 value represents auto-request, external->external. */ -enum { - XMIT_SZ_64BIT, - XMIT_SZ_8BIT, - XMIT_SZ_16BIT, - XMIT_SZ_32BIT, - XMIT_SZ_256BIT, -}; +#define RS_DUAL (DM_INC | SM_INC | 0x400 | TS_INDEX2VAL(XMIT_SZ_32BIT)) -/* - * The DMA count is defined as the number of bytes to transfer. - */ -static unsigned int ts_shift[] = { - [XMIT_SZ_64BIT] = 3, - [XMIT_SZ_8BIT] = 0, - [XMIT_SZ_16BIT] = 1, - [XMIT_SZ_32BIT] = 2, - [XMIT_SZ_256BIT] = 5, -}; +static unsigned long dma_find_base(unsigned int chan) +{ + unsigned long base = SH_DMAC_BASE0; + +#ifdef SH_DMAC_BASE1 + if (chan >= 6) + base = SH_DMAC_BASE1; +#endif + + return base; +} + +static unsigned long dma_base_addr(unsigned int chan) +{ + unsigned long base = dma_find_base(chan); + + /* Normalize offset calculation */ + if (chan >= 9) + chan -= 6; + if (chan >= 4) + base += 0x10; + return base + (chan * 0x10); +} + +#ifdef CONFIG_SH_DMA_IRQ_MULTI static inline unsigned int get_dmte_irq(unsigned int chan) { - unsigned int irq; + return chan >= 6 ? DMTE6_IRQ : DMTE0_IRQ; +} +#else - /* - * Normally we could just do DMTE0_IRQ + chan outright, though in the - * case of the 7751R, the DMTE IRQs for channels > 4 start right above - * the SCIF - */ +static unsigned int dmte_irq_map[] = { + DMTE0_IRQ, DMTE0_IRQ + 1, DMTE0_IRQ + 2, DMTE0_IRQ + 3, - if (chan < 4) { - irq = DMTE0_IRQ + chan; - } else { - irq = DMTE4_IRQ + chan - 4; - } +#ifdef DMTE4_IRQ + DMTE4_IRQ, DMTE4_IRQ + 1, +#endif + +#ifdef DMTE6_IRQ + DMTE6_IRQ, DMTE6_IRQ + 1, +#endif + +#ifdef DMTE8_IRQ + DMTE8_IRQ, DMTE9_IRQ, DMTE10_IRQ, DMTE11_IRQ, +#endif +}; - return irq; +static inline unsigned int get_dmte_irq(unsigned int chan) +{ + return dmte_irq_map[chan]; } +#endif /* * We determine the correct shift size based off of the CHCR transmit size @@ -74,13 +89,15 @@ static inline unsigned int get_dmte_irq(unsigned int chan) * * iterations to complete the transfer. */ +static unsigned int ts_shift[] = TS_SHIFT; + static inline unsigned int calc_xmit_shift(struct dma_channel *chan) { - u32 chcr = ctrl_inl(CHCR[chan->chan]); + u32 chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + int cnt = ((chcr & CHCR_TS_LOW_MASK) >> CHCR_TS_LOW_SHIFT) | + ((chcr & CHCR_TS_HIGH_MASK) >> CHCR_TS_HIGH_SHIFT); - chcr >>= 4; - - return ts_shift[chcr & 0x0007]; + return ts_shift[cnt]; } /* @@ -89,18 +106,18 @@ static inline unsigned int calc_xmit_shift(struct dma_channel *chan) * Besides that it needs to waken any waiting process, which should handle * setting up the next transfer. */ -static irqreturn_t dma_tei(int irq, void *dev_id, struct pt_regs *regs) +static irqreturn_t dma_tei(int irq, void *dev_id) { - struct dma_channel *chan = (struct dma_channel *)dev_id; + struct dma_channel *chan = dev_id; u32 chcr; - chcr = ctrl_inl(CHCR[chan->chan]); + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); if (!(chcr & CHCR_TE)) return IRQ_NONE; chcr &= ~(CHCR_IE | CHCR_DE); - ctrl_outl(chcr, CHCR[chan->chan]); + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); wake_up(&chan->wait_queue); @@ -109,8 +126,11 @@ static irqreturn_t dma_tei(int irq, void *dev_id, struct pt_regs *regs) static int sh_dmac_request_dma(struct dma_channel *chan) { - return request_irq(get_dmte_irq(chan->chan), dma_tei, - SA_INTERRUPT, "DMAC Transfer End", chan); + if (unlikely(!(chan->flags & DMA_TEI_CAPABLE))) + return 0; + + return request_irq(get_dmte_irq(chan->chan), dma_tei, IRQF_SHARED, + chan->dev_id, chan); } static void sh_dmac_free_dma(struct dma_channel *chan) @@ -118,38 +138,57 @@ static void sh_dmac_free_dma(struct dma_channel *chan) free_irq(get_dmte_irq(chan->chan), chan); } -static void sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) +static int +sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) { if (!chcr) - chcr = RS_DUAL; + chcr = RS_DUAL | CHCR_IE; - ctrl_outl(chcr, CHCR[chan->chan]); + if (chcr & CHCR_IE) { + chcr &= ~CHCR_IE; + chan->flags |= DMA_TEI_CAPABLE; + } else { + chan->flags &= ~DMA_TEI_CAPABLE; + } + + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); chan->flags |= DMA_CONFIGURED; + return 0; } static void sh_dmac_enable_dma(struct dma_channel *chan) { - int irq = get_dmte_irq(chan->chan); + int irq; u32 chcr; - chcr = ctrl_inl(CHCR[chan->chan]); - chcr |= CHCR_DE | CHCR_IE; - ctrl_outl(chcr, CHCR[chan->chan]); + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); + chcr |= CHCR_DE; + + if (chan->flags & DMA_TEI_CAPABLE) + chcr |= CHCR_IE; + + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); - enable_irq(irq); + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + enable_irq(irq); + } } static void sh_dmac_disable_dma(struct dma_channel *chan) { - int irq = get_dmte_irq(chan->chan); + int irq; u32 chcr; - disable_irq(irq); + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + disable_irq(irq); + } - chcr = ctrl_inl(CHCR[chan->chan]); + chcr = __raw_readl(dma_base_addr(chan->chan) + CHCR); chcr &= ~(CHCR_DE | CHCR_TE | CHCR_IE); - ctrl_outl(chcr, CHCR[chan->chan]); + __raw_writel(chcr, (dma_base_addr(chan->chan) + CHCR)); } static int sh_dmac_xfer_dma(struct dma_channel *chan) @@ -158,7 +197,7 @@ static int sh_dmac_xfer_dma(struct dma_channel *chan) * If we haven't pre-configured the channel with special flags, use * the defaults. */ - if (!(chan->flags & DMA_CONFIGURED)) + if (unlikely(!(chan->flags & DMA_CONFIGURED))) sh_dmac_configure_channel(chan, 0); sh_dmac_disable_dma(chan); @@ -178,12 +217,15 @@ static int sh_dmac_xfer_dma(struct dma_channel *chan) * cascading to the PVR2 DMAC. In this case, we still need to write * SAR and DAR, regardless of value, in order for cascading to work. */ - if (chan->sar || (mach_is_dreamcast() && chan->chan == 2)) - ctrl_outl(chan->sar, SAR[chan->chan]); - if (chan->dar || (mach_is_dreamcast() && chan->chan == 2)) - ctrl_outl(chan->dar, DAR[chan->chan]); + if (chan->sar || (mach_is_dreamcast() && + chan->chan == PVR2_CASCADE_CHAN)) + __raw_writel(chan->sar, (dma_base_addr(chan->chan) + SAR)); + if (chan->dar || (mach_is_dreamcast() && + chan->chan == PVR2_CASCADE_CHAN)) + __raw_writel(chan->dar, (dma_base_addr(chan->chan) + DAR)); - ctrl_outl(chan->count >> calc_xmit_shift(chan), DMATCR[chan->chan]); + __raw_writel(chan->count >> calc_xmit_shift(chan), + (dma_base_addr(chan->chan) + TCR)); sh_dmac_enable_dma(chan); @@ -192,27 +234,133 @@ static int sh_dmac_xfer_dma(struct dma_channel *chan) static int sh_dmac_get_dma_residue(struct dma_channel *chan) { - if (!(ctrl_inl(CHCR[chan->chan]) & CHCR_DE)) + if (!(__raw_readl(dma_base_addr(chan->chan) + CHCR) & CHCR_DE)) return 0; - return ctrl_inl(DMATCR[chan->chan]) << calc_xmit_shift(chan); + return __raw_readl(dma_base_addr(chan->chan) + TCR) + << calc_xmit_shift(chan); +} + +/* + * DMAOR handling + */ +#if defined(CONFIG_CPU_SUBTYPE_SH7723) || \ + defined(CONFIG_CPU_SUBTYPE_SH7724) || \ + defined(CONFIG_CPU_SUBTYPE_SH7780) || \ + defined(CONFIG_CPU_SUBTYPE_SH7785) +#define NR_DMAOR 2 +#else +#define NR_DMAOR 1 +#endif + +/* + * DMAOR bases are broken out amongst channel groups. DMAOR0 manages + * channels 0 - 5, DMAOR1 6 - 11 (optional). + */ +#define dmaor_read_reg(n) __raw_readw(dma_find_base((n)*6)) +#define dmaor_write_reg(n, data) __raw_writew(data, dma_find_base(n)*6) + +static inline int dmaor_reset(int no) +{ + unsigned long dmaor = dmaor_read_reg(no); + + /* Try to clear the error flags first, incase they are set */ + dmaor &= ~(DMAOR_NMIF | DMAOR_AE); + dmaor_write_reg(no, dmaor); + + dmaor |= DMAOR_INIT; + dmaor_write_reg(no, dmaor); + + /* See if we got an error again */ + if ((dmaor_read_reg(no) & (DMAOR_AE | DMAOR_NMIF))) { + printk(KERN_ERR "dma-sh: Can't initialize DMAOR.\n"); + return -EINVAL; + } + + return 0; } -#if defined(CONFIG_CPU_SH4) -static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs) +/* + * DMAE handling + */ +#ifdef CONFIG_CPU_SH4 + +#if defined(DMAE1_IRQ) +#define NR_DMAE 2 +#else +#define NR_DMAE 1 +#endif + +static const char *dmae_name[] = { + "DMAC Address Error0", + "DMAC Address Error1" +}; + +#ifdef CONFIG_SH_DMA_IRQ_MULTI +static inline unsigned int get_dma_error_irq(int n) { - unsigned long dmaor = ctrl_inl(DMAOR); + return get_dmte_irq(n * 6); +} +#else + +static unsigned int dmae_irq_map[] = { + DMAE0_IRQ, - printk("DMAE: DMAOR=%lx\n", dmaor); +#ifdef DMAE1_IRQ + DMAE1_IRQ, +#endif +}; - ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_NMIF, DMAOR); - ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_AE, DMAOR); - ctrl_outl(ctrl_inl(DMAOR)|DMAOR_DME, DMAOR); +static inline unsigned int get_dma_error_irq(int n) +{ + return dmae_irq_map[n]; +} +#endif + +static irqreturn_t dma_err(int irq, void *dummy) +{ + int i; + + for (i = 0; i < NR_DMAOR; i++) + dmaor_reset(i); disable_irq(irq); return IRQ_HANDLED; } + +static int dmae_irq_init(void) +{ + int n; + + for (n = 0; n < NR_DMAE; n++) { + int i = request_irq(get_dma_error_irq(n), dma_err, + IRQF_SHARED, dmae_name[n], (void *)dmae_name[n]); + if (unlikely(i < 0)) { + printk(KERN_ERR "%s request_irq fail\n", dmae_name[n]); + return i; + } + } + + return 0; +} + +static void dmae_irq_free(void) +{ + int n; + + for (n = 0; n < NR_DMAE; n++) + free_irq(get_dma_error_irq(n), NULL); +} +#else +static inline int dmae_irq_init(void) +{ + return 0; +} + +static void dmae_irq_free(void) +{ +} #endif static struct dma_ops sh_dmac_ops = { @@ -224,8 +372,8 @@ static struct dma_ops sh_dmac_ops = { }; static struct dma_info sh_dmac_info = { - .name = "SuperH DMAC", - .nr_channels = 4, + .name = "sh_dmac", + .nr_channels = CONFIG_NR_ONCHIP_DMA_CHANNELS, .ops = &sh_dmac_ops, .flags = DMAC_CHANNELS_TEI_CAPABLE, }; @@ -233,35 +381,37 @@ static struct dma_info sh_dmac_info = { static int __init sh_dmac_init(void) { struct dma_info *info = &sh_dmac_info; - int i; + int i, rc; -#ifdef CONFIG_CPU_SH4 - make_ipr_irq(DMAE_IRQ, DMA_IPR_ADDR, DMA_IPR_POS, DMA_PRIORITY); - i = request_irq(DMAE_IRQ, dma_err, SA_INTERRUPT, "DMAC Address Error", 0); - if (i < 0) - return i; -#endif - - for (i = 0; i < info->nr_channels; i++) { - int irq = get_dmte_irq(i); + /* + * Initialize DMAE, for parts that support it. + */ + rc = dmae_irq_init(); + if (unlikely(rc != 0)) + return rc; - make_ipr_irq(irq, DMA_IPR_ADDR, DMA_IPR_POS, DMA_PRIORITY); + /* + * Initialize DMAOR, and clean up any error flags that may have + * been set. + */ + for (i = 0; i < NR_DMAOR; i++) { + rc = dmaor_reset(i); + if (unlikely(rc != 0)) + return rc; } - ctrl_outl(0x8000 | DMAOR_DME, DMAOR); - return register_dmac(info); } static void __exit sh_dmac_exit(void) { -#ifdef CONFIG_CPU_SH4 - free_irq(DMAE_IRQ, 0); -#endif + dmae_irq_free(); + unregister_dmac(&sh_dmac_info); } subsys_initcall(sh_dmac_init); module_exit(sh_dmac_exit); +MODULE_AUTHOR("Takashi YOSHII, Paul Mundt, Andriy Skulysh"); +MODULE_DESCRIPTION("SuperH On-Chip DMAC Support"); MODULE_LICENSE("GPL"); - |
