162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * 362306a36Sopenharmony_ci * BRIEF MODULE DESCRIPTION 462306a36Sopenharmony_ci * A DMA channel allocator for Au1x00. API is modeled loosely off of 562306a36Sopenharmony_ci * linux/kernel/dma.c. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright 2000, 2008 MontaVista Software Inc. 862306a36Sopenharmony_ci * Author: MontaVista Software, Inc. <source@mvista.com> 962306a36Sopenharmony_ci * Copyright (C) 2005 Ralf Baechle (ralf@linux-mips.org) 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify it 1262306a36Sopenharmony_ci * under the terms of the GNU General Public License as published by the 1362306a36Sopenharmony_ci * Free Software Foundation; either version 2 of the License, or (at your 1462306a36Sopenharmony_ci * option) any later version. 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 1762306a36Sopenharmony_ci * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 1862306a36Sopenharmony_ci * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 1962306a36Sopenharmony_ci * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 2062306a36Sopenharmony_ci * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2162306a36Sopenharmony_ci * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 2262306a36Sopenharmony_ci * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 2362306a36Sopenharmony_ci * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2462306a36Sopenharmony_ci * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2562306a36Sopenharmony_ci * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * You should have received a copy of the GNU General Public License along 2862306a36Sopenharmony_ci * with this program; if not, write to the Free Software Foundation, Inc., 2962306a36Sopenharmony_ci * 675 Mass Ave, Cambridge, MA 02139, USA. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci */ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/init.h> 3462306a36Sopenharmony_ci#include <linux/export.h> 3562306a36Sopenharmony_ci#include <linux/kernel.h> 3662306a36Sopenharmony_ci#include <linux/errno.h> 3762306a36Sopenharmony_ci#include <linux/spinlock.h> 3862306a36Sopenharmony_ci#include <linux/interrupt.h> 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000.h> 4162306a36Sopenharmony_ci#include <asm/mach-au1x00/au1000_dma.h> 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci/* 4462306a36Sopenharmony_ci * A note on resource allocation: 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * All drivers needing DMA channels, should allocate and release them 4762306a36Sopenharmony_ci * through the public routines `request_dma()' and `free_dma()'. 4862306a36Sopenharmony_ci * 4962306a36Sopenharmony_ci * In order to avoid problems, all processes should allocate resources in 5062306a36Sopenharmony_ci * the same sequence and release them in the reverse order. 5162306a36Sopenharmony_ci * 5262306a36Sopenharmony_ci * So, when allocating DMAs and IRQs, first allocate the DMA, then the IRQ. 5362306a36Sopenharmony_ci * When releasing them, first release the IRQ, then release the DMA. The 5462306a36Sopenharmony_ci * main reason for this order is that, if you are requesting the DMA buffer 5562306a36Sopenharmony_ci * done interrupt, you won't know the irq number until the DMA channel is 5662306a36Sopenharmony_ci * returned from request_dma. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* DMA Channel register block spacing */ 6062306a36Sopenharmony_ci#define DMA_CHANNEL_LEN 0x00000100 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ciDEFINE_SPINLOCK(au1000_dma_spin_lock); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistruct dma_chan au1000_dma_table[NUM_AU1000_DMA_CHANNELS] = { 6562306a36Sopenharmony_ci {.dev_id = -1,}, 6662306a36Sopenharmony_ci {.dev_id = -1,}, 6762306a36Sopenharmony_ci {.dev_id = -1,}, 6862306a36Sopenharmony_ci {.dev_id = -1,}, 6962306a36Sopenharmony_ci {.dev_id = -1,}, 7062306a36Sopenharmony_ci {.dev_id = -1,}, 7162306a36Sopenharmony_ci {.dev_id = -1,}, 7262306a36Sopenharmony_ci {.dev_id = -1,} 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ciEXPORT_SYMBOL(au1000_dma_table); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Device FIFO addresses and default DMA modes */ 7762306a36Sopenharmony_cistatic const struct dma_dev { 7862306a36Sopenharmony_ci unsigned int fifo_addr; 7962306a36Sopenharmony_ci unsigned int dma_mode; 8062306a36Sopenharmony_ci} dma_dev_table[DMA_NUM_DEV] = { 8162306a36Sopenharmony_ci { AU1000_UART0_PHYS_ADDR + 0x04, DMA_DW8 }, /* UART0_TX */ 8262306a36Sopenharmony_ci { AU1000_UART0_PHYS_ADDR + 0x00, DMA_DW8 | DMA_DR }, /* UART0_RX */ 8362306a36Sopenharmony_ci { 0, 0 }, /* DMA_REQ0 */ 8462306a36Sopenharmony_ci { 0, 0 }, /* DMA_REQ1 */ 8562306a36Sopenharmony_ci { AU1000_AC97_PHYS_ADDR + 0x08, DMA_DW16 }, /* AC97 TX c */ 8662306a36Sopenharmony_ci { AU1000_AC97_PHYS_ADDR + 0x08, DMA_DW16 | DMA_DR }, /* AC97 RX c */ 8762306a36Sopenharmony_ci { AU1000_UART3_PHYS_ADDR + 0x04, DMA_DW8 | DMA_NC }, /* UART3_TX */ 8862306a36Sopenharmony_ci { AU1000_UART3_PHYS_ADDR + 0x00, DMA_DW8 | DMA_NC | DMA_DR }, /* UART3_RX */ 8962306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x00, DMA_DW8 | DMA_NC | DMA_DR }, /* EP0RD */ 9062306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x04, DMA_DW8 | DMA_NC }, /* EP0WR */ 9162306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x08, DMA_DW8 | DMA_NC }, /* EP2WR */ 9262306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x0c, DMA_DW8 | DMA_NC }, /* EP3WR */ 9362306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x10, DMA_DW8 | DMA_NC | DMA_DR }, /* EP4RD */ 9462306a36Sopenharmony_ci { AU1000_USB_UDC_PHYS_ADDR + 0x14, DMA_DW8 | DMA_NC | DMA_DR }, /* EP5RD */ 9562306a36Sopenharmony_ci /* on Au1500, these 2 are DMA_REQ2/3 (GPIO208/209) instead! */ 9662306a36Sopenharmony_ci { AU1000_I2S_PHYS_ADDR + 0x00, DMA_DW32 | DMA_NC}, /* I2S TX */ 9762306a36Sopenharmony_ci { AU1000_I2S_PHYS_ADDR + 0x00, DMA_DW32 | DMA_NC | DMA_DR}, /* I2S RX */ 9862306a36Sopenharmony_ci}; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ciint au1000_dma_read_proc(char *buf, char **start, off_t fpos, 10162306a36Sopenharmony_ci int length, int *eof, void *data) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci int i, len = 0; 10462306a36Sopenharmony_ci struct dma_chan *chan; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci for (i = 0; i < NUM_AU1000_DMA_CHANNELS; i++) { 10762306a36Sopenharmony_ci chan = get_dma_chan(i); 10862306a36Sopenharmony_ci if (chan != NULL) 10962306a36Sopenharmony_ci len += sprintf(buf + len, "%2d: %s\n", 11062306a36Sopenharmony_ci i, chan->dev_str); 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (fpos >= len) { 11462306a36Sopenharmony_ci *start = buf; 11562306a36Sopenharmony_ci *eof = 1; 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci *start = buf + fpos; 11962306a36Sopenharmony_ci len -= fpos; 12062306a36Sopenharmony_ci if (len > length) 12162306a36Sopenharmony_ci return length; 12262306a36Sopenharmony_ci *eof = 1; 12362306a36Sopenharmony_ci return len; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci/* Device FIFO addresses and default DMA modes - 2nd bank */ 12762306a36Sopenharmony_cistatic const struct dma_dev dma_dev_table_bank2[DMA_NUM_DEV_BANK2] = { 12862306a36Sopenharmony_ci { AU1100_SD0_PHYS_ADDR + 0x00, DMA_DS | DMA_DW8 }, /* coherent */ 12962306a36Sopenharmony_ci { AU1100_SD0_PHYS_ADDR + 0x04, DMA_DS | DMA_DW8 | DMA_DR }, /* coherent */ 13062306a36Sopenharmony_ci { AU1100_SD1_PHYS_ADDR + 0x00, DMA_DS | DMA_DW8 }, /* coherent */ 13162306a36Sopenharmony_ci { AU1100_SD1_PHYS_ADDR + 0x04, DMA_DS | DMA_DW8 | DMA_DR } /* coherent */ 13262306a36Sopenharmony_ci}; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_civoid dump_au1000_dma_channel(unsigned int dmanr) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct dma_chan *chan; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (dmanr >= NUM_AU1000_DMA_CHANNELS) 13962306a36Sopenharmony_ci return; 14062306a36Sopenharmony_ci chan = &au1000_dma_table[dmanr]; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci printk(KERN_INFO "Au1000 DMA%d Register Dump:\n", dmanr); 14362306a36Sopenharmony_ci printk(KERN_INFO " mode = 0x%08x\n", 14462306a36Sopenharmony_ci __raw_readl(chan->io + DMA_MODE_SET)); 14562306a36Sopenharmony_ci printk(KERN_INFO " addr = 0x%08x\n", 14662306a36Sopenharmony_ci __raw_readl(chan->io + DMA_PERIPHERAL_ADDR)); 14762306a36Sopenharmony_ci printk(KERN_INFO " start0 = 0x%08x\n", 14862306a36Sopenharmony_ci __raw_readl(chan->io + DMA_BUFFER0_START)); 14962306a36Sopenharmony_ci printk(KERN_INFO " start1 = 0x%08x\n", 15062306a36Sopenharmony_ci __raw_readl(chan->io + DMA_BUFFER1_START)); 15162306a36Sopenharmony_ci printk(KERN_INFO " count0 = 0x%08x\n", 15262306a36Sopenharmony_ci __raw_readl(chan->io + DMA_BUFFER0_COUNT)); 15362306a36Sopenharmony_ci printk(KERN_INFO " count1 = 0x%08x\n", 15462306a36Sopenharmony_ci __raw_readl(chan->io + DMA_BUFFER1_COUNT)); 15562306a36Sopenharmony_ci} 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci/* 15862306a36Sopenharmony_ci * Finds a free channel, and binds the requested device to it. 15962306a36Sopenharmony_ci * Returns the allocated channel number, or negative on error. 16062306a36Sopenharmony_ci * Requests the DMA done IRQ if irqhandler != NULL. 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_ciint request_au1000_dma(int dev_id, const char *dev_str, 16362306a36Sopenharmony_ci irq_handler_t irqhandler, 16462306a36Sopenharmony_ci unsigned long irqflags, 16562306a36Sopenharmony_ci void *irq_dev_id) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci struct dma_chan *chan; 16862306a36Sopenharmony_ci const struct dma_dev *dev; 16962306a36Sopenharmony_ci int i, ret; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (alchemy_get_cputype() == ALCHEMY_CPU_AU1100) { 17262306a36Sopenharmony_ci if (dev_id < 0 || dev_id >= (DMA_NUM_DEV + DMA_NUM_DEV_BANK2)) 17362306a36Sopenharmony_ci return -EINVAL; 17462306a36Sopenharmony_ci } else { 17562306a36Sopenharmony_ci if (dev_id < 0 || dev_id >= DMA_NUM_DEV) 17662306a36Sopenharmony_ci return -EINVAL; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci for (i = 0; i < NUM_AU1000_DMA_CHANNELS; i++) 18062306a36Sopenharmony_ci if (au1000_dma_table[i].dev_id < 0) 18162306a36Sopenharmony_ci break; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if (i == NUM_AU1000_DMA_CHANNELS) 18462306a36Sopenharmony_ci return -ENODEV; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci chan = &au1000_dma_table[i]; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (dev_id >= DMA_NUM_DEV) { 18962306a36Sopenharmony_ci dev_id -= DMA_NUM_DEV; 19062306a36Sopenharmony_ci dev = &dma_dev_table_bank2[dev_id]; 19162306a36Sopenharmony_ci } else 19262306a36Sopenharmony_ci dev = &dma_dev_table[dev_id]; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (irqhandler) { 19562306a36Sopenharmony_ci chan->irq_dev = irq_dev_id; 19662306a36Sopenharmony_ci ret = request_irq(chan->irq, irqhandler, irqflags, dev_str, 19762306a36Sopenharmony_ci chan->irq_dev); 19862306a36Sopenharmony_ci if (ret) { 19962306a36Sopenharmony_ci chan->irq_dev = NULL; 20062306a36Sopenharmony_ci return ret; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci } else { 20362306a36Sopenharmony_ci chan->irq_dev = NULL; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* fill it in */ 20762306a36Sopenharmony_ci chan->io = (void __iomem *)(KSEG1ADDR(AU1000_DMA_PHYS_ADDR) + 20862306a36Sopenharmony_ci i * DMA_CHANNEL_LEN); 20962306a36Sopenharmony_ci chan->dev_id = dev_id; 21062306a36Sopenharmony_ci chan->dev_str = dev_str; 21162306a36Sopenharmony_ci chan->fifo_addr = dev->fifo_addr; 21262306a36Sopenharmony_ci chan->mode = dev->dma_mode; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* initialize the channel before returning */ 21562306a36Sopenharmony_ci init_dma(i); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return i; 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ciEXPORT_SYMBOL(request_au1000_dma); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_civoid free_au1000_dma(unsigned int dmanr) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci struct dma_chan *chan = get_dma_chan(dmanr); 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci if (!chan) { 22662306a36Sopenharmony_ci printk(KERN_ERR "Error trying to free DMA%d\n", dmanr); 22762306a36Sopenharmony_ci return; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci disable_dma(dmanr); 23162306a36Sopenharmony_ci if (chan->irq_dev) 23262306a36Sopenharmony_ci free_irq(chan->irq, chan->irq_dev); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci chan->irq_dev = NULL; 23562306a36Sopenharmony_ci chan->dev_id = -1; 23662306a36Sopenharmony_ci} 23762306a36Sopenharmony_ciEXPORT_SYMBOL(free_au1000_dma); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic int __init au1000_dma_init(void) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci int base, i; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci switch (alchemy_get_cputype()) { 24462306a36Sopenharmony_ci case ALCHEMY_CPU_AU1000: 24562306a36Sopenharmony_ci base = AU1000_DMA_INT_BASE; 24662306a36Sopenharmony_ci break; 24762306a36Sopenharmony_ci case ALCHEMY_CPU_AU1500: 24862306a36Sopenharmony_ci base = AU1500_DMA_INT_BASE; 24962306a36Sopenharmony_ci break; 25062306a36Sopenharmony_ci case ALCHEMY_CPU_AU1100: 25162306a36Sopenharmony_ci base = AU1100_DMA_INT_BASE; 25262306a36Sopenharmony_ci break; 25362306a36Sopenharmony_ci default: 25462306a36Sopenharmony_ci goto out; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci for (i = 0; i < NUM_AU1000_DMA_CHANNELS; i++) 25862306a36Sopenharmony_ci au1000_dma_table[i].irq = base + i; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci printk(KERN_INFO "Alchemy DMA initialized\n"); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ciout: 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ciarch_initcall(au1000_dma_init); 266