2005-04-17 02:20:36 +04:00
/*
* linux / arch / arm / mach - rpc / dma . c
*
* Copyright ( C ) 1998 Russell King
*
* 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 .
*
* DMA functions specific to RiscPC architecture
*/
# include <linux/slab.h>
# include <linux/mman.h>
# include <linux/init.h>
# include <linux/interrupt.h>
2006-01-04 18:08:30 +03:00
# include <linux/dma-mapping.h>
2005-04-17 02:20:36 +04:00
# include <asm/page.h>
# include <asm/dma.h>
# include <asm/fiq.h>
# include <asm/io.h>
# include <asm/irq.h>
2008-08-04 13:41:28 +04:00
# include <asm/arch/hardware.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <asm/mach/dma.h>
# include <asm/hardware/iomd.h>
#if 0
typedef enum {
dma_size_8 = 1 ,
dma_size_16 = 2 ,
dma_size_32 = 4 ,
dma_size_128 = 16
} dma_size_t ;
# endif
# define TRANSFER_SIZE 2
# define CURA (0)
# define ENDA (IOMD_IO0ENDA - IOMD_IO0CURA)
# define CURB (IOMD_IO0CURB - IOMD_IO0CURA)
# define ENDB (IOMD_IO0ENDB - IOMD_IO0CURA)
# define CR (IOMD_IO0CR - IOMD_IO0CURA)
# define ST (IOMD_IO0ST - IOMD_IO0CURA)
static void iomd_get_next_sg ( struct scatterlist * sg , dma_t * dma )
{
unsigned long end , offset , flags = 0 ;
if ( dma - > sg ) {
sg - > dma_address = dma - > sg - > dma_address ;
offset = sg - > dma_address & ~ PAGE_MASK ;
end = offset + dma - > sg - > length ;
if ( end > PAGE_SIZE )
end = PAGE_SIZE ;
if ( offset + TRANSFER_SIZE > = end )
flags | = DMA_END_L ;
sg - > length = end - TRANSFER_SIZE ;
dma - > sg - > length - = end - offset ;
dma - > sg - > dma_address + = end - offset ;
if ( dma - > sg - > length = = 0 ) {
if ( dma - > sgcount > 1 ) {
dma - > sg + + ;
dma - > sgcount - - ;
} else {
dma - > sg = NULL ;
flags | = DMA_END_S ;
}
}
} else {
flags = DMA_END_S | DMA_END_L ;
sg - > dma_address = 0 ;
sg - > length = 0 ;
}
sg - > length | = flags ;
}
2006-10-06 21:53:39 +04:00
static irqreturn_t iomd_dma_handle ( int irq , void * dev_id )
2005-04-17 02:20:36 +04:00
{
dma_t * dma = ( dma_t * ) dev_id ;
unsigned long base = dma - > dma_base ;
do {
unsigned int status ;
status = iomd_readb ( base + ST ) ;
if ( ! ( status & DMA_ST_INT ) )
return IRQ_HANDLED ;
if ( ( dma - > state ^ status ) & DMA_ST_AB )
iomd_get_next_sg ( & dma - > cur_sg , dma ) ;
switch ( status & ( DMA_ST_OFL | DMA_ST_AB ) ) {
case DMA_ST_OFL : /* OIA */
case DMA_ST_AB : /* .IB */
iomd_writel ( dma - > cur_sg . dma_address , base + CURA ) ;
iomd_writel ( dma - > cur_sg . length , base + ENDA ) ;
dma - > state = DMA_ST_AB ;
break ;
case DMA_ST_OFL | DMA_ST_AB : /* OIB */
case 0 : /* .IA */
iomd_writel ( dma - > cur_sg . dma_address , base + CURB ) ;
iomd_writel ( dma - > cur_sg . length , base + ENDB ) ;
dma - > state = 0 ;
break ;
}
if ( status & DMA_ST_OFL & &
dma - > cur_sg . length = = ( DMA_END_S | DMA_END_L ) )
break ;
} while ( 1 ) ;
dma - > state = ~ DMA_ST_AB ;
disable_irq ( irq ) ;
return IRQ_HANDLED ;
}
static int iomd_request_dma ( dmach_t channel , dma_t * dma )
{
return request_irq ( dma - > dma_irq , iomd_dma_handle ,
2006-07-03 04:20:05 +04:00
IRQF_DISABLED , dma - > device_id , dma ) ;
2005-04-17 02:20:36 +04:00
}
static void iomd_free_dma ( dmach_t channel , dma_t * dma )
{
free_irq ( dma - > dma_irq , dma ) ;
}
static void iomd_enable_dma ( dmach_t channel , dma_t * dma )
{
unsigned long dma_base = dma - > dma_base ;
unsigned int ctrl = TRANSFER_SIZE | DMA_CR_E ;
if ( dma - > invalid ) {
dma - > invalid = 0 ;
/*
* Cope with ISA - style drivers which expect cache
* coherence .
*/
2006-01-04 18:08:30 +03:00
if ( ! dma - > sg ) {
dma - > sg = & dma - > buf ;
dma - > sgcount = 1 ;
dma - > buf . length = dma - > count ;
dma - > buf . dma_address = dma_map_single ( NULL ,
dma - > addr , dma - > count ,
2005-04-17 02:20:36 +04:00
dma - > dma_mode = = DMA_MODE_READ ?
2006-01-04 18:08:30 +03:00
DMA_FROM_DEVICE : DMA_TO_DEVICE ) ;
2005-04-17 02:20:36 +04:00
}
iomd_writeb ( DMA_CR_C , dma_base + CR ) ;
dma - > state = DMA_ST_AB ;
}
if ( dma - > dma_mode = = DMA_MODE_READ )
ctrl | = DMA_CR_D ;
iomd_writeb ( ctrl , dma_base + CR ) ;
enable_irq ( dma - > dma_irq ) ;
}
static void iomd_disable_dma ( dmach_t channel , dma_t * dma )
{
unsigned long dma_base = dma - > dma_base ;
unsigned long flags ;
local_irq_save ( flags ) ;
if ( dma - > state ! = ~ DMA_ST_AB )
disable_irq ( dma - > dma_irq ) ;
iomd_writeb ( 0 , dma_base + CR ) ;
local_irq_restore ( flags ) ;
}
static int iomd_set_dma_speed ( dmach_t channel , dma_t * dma , int cycle )
{
int tcr , speed ;
if ( cycle < 188 )
speed = 3 ;
else if ( cycle < = 250 )
speed = 2 ;
else if ( cycle < 438 )
speed = 1 ;
else
speed = 0 ;
tcr = iomd_readb ( IOMD_DMATCR ) ;
speed & = 3 ;
switch ( channel ) {
case DMA_0 :
tcr = ( tcr & ~ 0x03 ) | speed ;
break ;
case DMA_1 :
tcr = ( tcr & ~ 0x0c ) | ( speed < < 2 ) ;
break ;
case DMA_2 :
tcr = ( tcr & ~ 0x30 ) | ( speed < < 4 ) ;
break ;
case DMA_3 :
tcr = ( tcr & ~ 0xc0 ) | ( speed < < 6 ) ;
break ;
default :
break ;
}
iomd_writeb ( tcr , IOMD_DMATCR ) ;
return speed ;
}
static struct dma_ops iomd_dma_ops = {
. type = " IOMD " ,
. request = iomd_request_dma ,
. free = iomd_free_dma ,
. enable = iomd_enable_dma ,
. disable = iomd_disable_dma ,
. setspeed = iomd_set_dma_speed ,
} ;
static struct fiq_handler fh = {
. name = " floppydma "
} ;
static void floppy_enable_dma ( dmach_t channel , dma_t * dma )
{
void * fiqhandler_start ;
unsigned int fiqhandler_length ;
struct pt_regs regs ;
2006-01-04 18:08:30 +03:00
if ( dma - > sg )
2005-04-17 02:20:36 +04:00
BUG ( ) ;
if ( dma - > dma_mode = = DMA_MODE_READ ) {
extern unsigned char floppy_fiqin_start , floppy_fiqin_end ;
fiqhandler_start = & floppy_fiqin_start ;
fiqhandler_length = & floppy_fiqin_end - & floppy_fiqin_start ;
} else {
extern unsigned char floppy_fiqout_start , floppy_fiqout_end ;
fiqhandler_start = & floppy_fiqout_start ;
fiqhandler_length = & floppy_fiqout_end - & floppy_fiqout_start ;
}
2006-01-04 18:08:30 +03:00
regs . ARM_r9 = dma - > count ;
regs . ARM_r10 = ( unsigned long ) dma - > addr ;
2005-04-17 02:20:36 +04:00
regs . ARM_fp = ( unsigned long ) FLOPPYDMA_BASE ;
if ( claim_fiq ( & fh ) ) {
printk ( " floppydma: couldn't claim FIQ. \n " ) ;
return ;
}
set_fiq_handler ( fiqhandler_start , fiqhandler_length ) ;
set_fiq_regs ( & regs ) ;
enable_fiq ( dma - > dma_irq ) ;
}
static void floppy_disable_dma ( dmach_t channel , dma_t * dma )
{
disable_fiq ( dma - > dma_irq ) ;
release_fiq ( & fh ) ;
}
static int floppy_get_residue ( dmach_t channel , dma_t * dma )
{
struct pt_regs regs ;
get_fiq_regs ( & regs ) ;
return regs . ARM_r9 ;
}
static struct dma_ops floppy_dma_ops = {
. type = " FIQDMA " ,
. enable = floppy_enable_dma ,
. disable = floppy_disable_dma ,
. residue = floppy_get_residue ,
} ;
/*
* This is virtual DMA - we don ' t need anything here .
*/
static void sound_enable_disable_dma ( dmach_t channel , dma_t * dma )
{
}
static struct dma_ops sound_dma_ops = {
. type = " VIRTUAL " ,
. enable = sound_enable_disable_dma ,
. disable = sound_enable_disable_dma ,
} ;
void __init arch_dma_init ( dma_t * dma )
{
iomd_writeb ( 0 , IOMD_IO0CR ) ;
iomd_writeb ( 0 , IOMD_IO1CR ) ;
iomd_writeb ( 0 , IOMD_IO2CR ) ;
iomd_writeb ( 0 , IOMD_IO3CR ) ;
iomd_writeb ( 0xa0 , IOMD_DMATCR ) ;
dma [ DMA_0 ] . dma_base = IOMD_IO0CURA ;
dma [ DMA_0 ] . dma_irq = IRQ_DMA0 ;
dma [ DMA_0 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_1 ] . dma_base = IOMD_IO1CURA ;
dma [ DMA_1 ] . dma_irq = IRQ_DMA1 ;
dma [ DMA_1 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_2 ] . dma_base = IOMD_IO2CURA ;
dma [ DMA_2 ] . dma_irq = IRQ_DMA2 ;
dma [ DMA_2 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_3 ] . dma_base = IOMD_IO3CURA ;
dma [ DMA_3 ] . dma_irq = IRQ_DMA3 ;
dma [ DMA_3 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_S0 ] . dma_base = IOMD_SD0CURA ;
dma [ DMA_S0 ] . dma_irq = IRQ_DMAS0 ;
dma [ DMA_S0 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_S1 ] . dma_base = IOMD_SD1CURA ;
dma [ DMA_S1 ] . dma_irq = IRQ_DMAS1 ;
dma [ DMA_S1 ] . d_ops = & iomd_dma_ops ;
dma [ DMA_VIRTUAL_FLOPPY ] . dma_irq = FIQ_FLOPPYDATA ;
dma [ DMA_VIRTUAL_FLOPPY ] . d_ops = & floppy_dma_ops ;
dma [ DMA_VIRTUAL_SOUND ] . d_ops = & sound_dma_ops ;
/*
* Setup DMA channels 2 , 3 to be for podules
* and channels 0 , 1 for internal devices
*/
iomd_writeb ( DMA_EXT_IO3 | DMA_EXT_IO2 , IOMD_DMAEXT ) ;
}