2005-04-17 02:20:36 +04:00
/*
* Mips Jazz DMA controller support
* Copyright ( C ) 1995 , 1996 by Andreas Busse
*
* NOTE : Some of the argument checking could be removed when
* things have settled down . Also , instead of returning 0xffffffff
* on failure of vdma_alloc ( ) one could leave page # 0 unused
* and return the more usual NULL pointer as logical address .
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/mm.h>
# include <linux/bootmem.h>
# include <linux/spinlock.h>
# include <asm/mipsregs.h>
# include <asm/jazz.h>
# include <asm/io.h>
# include <asm/uaccess.h>
# include <asm/dma.h>
# include <asm/jazzdma.h>
# include <asm/pgtable.h>
/*
* Set this to one to enable additional vdma debug code .
*/
# define CONF_DEBUG_VDMA 0
2007-08-25 13:01:50 +04:00
static VDMA_PGTBL_ENTRY * pgtbl ;
2005-04-17 02:20:36 +04:00
static DEFINE_SPINLOCK ( vdma_lock ) ;
/*
* Debug stuff
*/
# define vdma_debug ((CONF_DEBUG_VDMA) ? debuglvl : 0)
static int debuglvl = 3 ;
/*
* Initialize the pagetable with a one - to - one mapping of
* the first 16 Mbytes of main memory and declare all
* entries to be unused . Using this method will at least
* allow some early device driver operations to work .
*/
static inline void vdma_pgtbl_init ( void )
{
unsigned long paddr = 0 ;
int i ;
for ( i = 0 ; i < VDMA_PGTBL_ENTRIES ; i + + ) {
pgtbl [ i ] . frame = paddr ;
pgtbl [ i ] . owner = VDMA_PAGE_EMPTY ;
paddr + = VDMA_PAGESIZE ;
}
}
/*
* Initialize the Jazz R4030 dma controller
*/
2007-08-25 13:01:50 +04:00
static int __init vdma_init ( void )
2005-04-17 02:20:36 +04:00
{
/*
* Allocate 32 k of memory for DMA page tables . This needs to be page
* aligned and should be uncached to avoid cache flushing after every
* update .
*/
2007-08-25 13:01:50 +04:00
pgtbl = ( VDMA_PGTBL_ENTRY * ) __get_free_pages ( GFP_KERNEL | GFP_DMA ,
get_order ( VDMA_PGTBL_SIZE ) ) ;
2009-03-30 16:49:44 +04:00
BUG_ON ( ! pgtbl ) ;
2007-08-25 13:01:50 +04:00
dma_cache_wback_inv ( ( unsigned long ) pgtbl , VDMA_PGTBL_SIZE ) ;
pgtbl = ( VDMA_PGTBL_ENTRY * ) KSEG1ADDR ( pgtbl ) ;
2005-04-17 02:20:36 +04:00
/*
* Clear the R4030 translation table
*/
vdma_pgtbl_init ( ) ;
2007-08-25 13:01:50 +04:00
r4030_write_reg32 ( JAZZ_R4030_TRSTBL_BASE , CPHYSADDR ( pgtbl ) ) ;
2005-04-17 02:20:36 +04:00
r4030_write_reg32 ( JAZZ_R4030_TRSTBL_LIM , VDMA_PGTBL_SIZE ) ;
r4030_write_reg32 ( JAZZ_R4030_TRSTBL_INV , 0 ) ;
2007-08-25 13:01:50 +04:00
printk ( KERN_INFO " VDMA: R4030 DMA pagetables initialized. \n " ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* Allocate DMA pagetables using a simple first - fit algorithm
*/
unsigned long vdma_alloc ( unsigned long paddr , unsigned long size )
{
int first , last , pages , frame , i ;
unsigned long laddr , flags ;
/* check arguments */
if ( paddr > 0x1fffffff ) {
if ( vdma_debug )
printk ( " vdma_alloc: Invalid physical address: %08lx \n " ,
paddr ) ;
return VDMA_ERROR ; /* invalid physical address */
}
if ( size > 0x400000 | | size = = 0 ) {
if ( vdma_debug )
printk ( " vdma_alloc: Invalid size: %08lx \n " , size ) ;
return VDMA_ERROR ; /* invalid physical address */
}
spin_lock_irqsave ( & vdma_lock , flags ) ;
/*
* Find free chunk
*/
2007-08-25 13:01:50 +04:00
pages = VDMA_PAGE ( paddr + size ) - VDMA_PAGE ( paddr ) + 1 ;
2005-04-17 02:20:36 +04:00
first = 0 ;
while ( 1 ) {
2007-08-25 13:01:50 +04:00
while ( pgtbl [ first ] . owner ! = VDMA_PAGE_EMPTY & &
2005-04-17 02:20:36 +04:00
first < VDMA_PGTBL_ENTRIES ) first + + ;
if ( first + pages > VDMA_PGTBL_ENTRIES ) { /* nothing free */
spin_unlock_irqrestore ( & vdma_lock , flags ) ;
return VDMA_ERROR ;
}
last = first + 1 ;
2007-08-25 13:01:50 +04:00
while ( pgtbl [ last ] . owner = = VDMA_PAGE_EMPTY
2005-04-17 02:20:36 +04:00
& & last - first < pages )
last + + ;
if ( last - first = = pages )
break ; /* found */
2007-08-25 13:01:50 +04:00
first = last + 1 ;
2005-04-17 02:20:36 +04:00
}
/*
* Mark pages as allocated
*/
laddr = ( first < < 12 ) + ( paddr & ( VDMA_PAGESIZE - 1 ) ) ;
frame = paddr & ~ ( VDMA_PAGESIZE - 1 ) ;
for ( i = first ; i < last ; i + + ) {
2007-08-25 13:01:50 +04:00
pgtbl [ i ] . frame = frame ;
pgtbl [ i ] . owner = laddr ;
2005-04-17 02:20:36 +04:00
frame + = VDMA_PAGESIZE ;
}
/*
* Update translation table and return logical start address
*/
r4030_write_reg32 ( JAZZ_R4030_TRSTBL_INV , 0 ) ;
if ( vdma_debug > 1 )
printk ( " vdma_alloc: Allocated %d pages starting from %08lx \n " ,
pages , laddr ) ;
if ( vdma_debug > 2 ) {
printk ( " LADDR: " ) ;
for ( i = first ; i < last ; i + + )
printk ( " %08x " , i < < 12 ) ;
printk ( " \n PADDR: " ) ;
for ( i = first ; i < last ; i + + )
2007-08-25 13:01:50 +04:00
printk ( " %08x " , pgtbl [ i ] . frame ) ;
2005-04-17 02:20:36 +04:00
printk ( " \n OWNER: " ) ;
for ( i = first ; i < last ; i + + )
2007-08-25 13:01:50 +04:00
printk ( " %08x " , pgtbl [ i ] . owner ) ;
2005-04-17 02:20:36 +04:00
printk ( " \n " ) ;
}
spin_unlock_irqrestore ( & vdma_lock , flags ) ;
return laddr ;
}
EXPORT_SYMBOL ( vdma_alloc ) ;
/*
* Free previously allocated dma translation pages
* Note that this does NOT change the translation table ,
* it just marks the free ' d pages as unused !
*/
int vdma_free ( unsigned long laddr )
{
int i ;
i = laddr > > 12 ;
if ( pgtbl [ i ] . owner ! = laddr ) {
printk
( " vdma_free: trying to free other's dma pages, laddr=%8lx \n " ,
laddr ) ;
return - 1 ;
}
while ( pgtbl [ i ] . owner = = laddr & & i < VDMA_PGTBL_ENTRIES ) {
pgtbl [ i ] . owner = VDMA_PAGE_EMPTY ;
i + + ;
}
if ( vdma_debug > 1 )
printk ( " vdma_free: freed %ld pages starting from %08lx \n " ,
i - ( laddr > > 12 ) , laddr ) ;
return 0 ;
}
EXPORT_SYMBOL ( vdma_free ) ;
/*
* Map certain page ( s ) to another physical address .
* Caller must have allocated the page ( s ) before .
*/
int vdma_remap ( unsigned long laddr , unsigned long paddr , unsigned long size )
{
int first , pages , npages ;
if ( laddr > 0xffffff ) {
if ( vdma_debug )
printk
( " vdma_map: Invalid logical address: %08lx \n " ,
laddr ) ;
return - EINVAL ; /* invalid logical address */
}
if ( paddr > 0x1fffffff ) {
if ( vdma_debug )
printk
( " vdma_map: Invalid physical address: %08lx \n " ,
paddr ) ;
return - EINVAL ; /* invalid physical address */
}
npages = pages =
( ( ( paddr & ( VDMA_PAGESIZE - 1 ) ) + size ) > > 12 ) + 1 ;
first = laddr > > 12 ;
if ( vdma_debug )
printk ( " vdma_remap: first=%x, pages=%x \n " , first , pages ) ;
if ( first + pages > VDMA_PGTBL_ENTRIES ) {
if ( vdma_debug )
printk ( " vdma_alloc: Invalid size: %08lx \n " , size ) ;
return - EINVAL ;
}
paddr & = ~ ( VDMA_PAGESIZE - 1 ) ;
while ( pages > 0 & & first < VDMA_PGTBL_ENTRIES ) {
if ( pgtbl [ first ] . owner ! = laddr ) {
if ( vdma_debug )
printk ( " Trying to remap other's pages. \n " ) ;
return - EPERM ; /* not owner */
}
pgtbl [ first ] . frame = paddr ;
paddr + = VDMA_PAGESIZE ;
first + + ;
pages - - ;
}
/*
* Update translation table
*/
r4030_write_reg32 ( JAZZ_R4030_TRSTBL_INV , 0 ) ;
if ( vdma_debug > 2 ) {
int i ;
pages = ( ( ( paddr & ( VDMA_PAGESIZE - 1 ) ) + size ) > > 12 ) + 1 ;
first = laddr > > 12 ;
printk ( " LADDR: " ) ;
for ( i = first ; i < first + pages ; i + + )
printk ( " %08x " , i < < 12 ) ;
printk ( " \n PADDR: " ) ;
for ( i = first ; i < first + pages ; i + + )
printk ( " %08x " , pgtbl [ i ] . frame ) ;
printk ( " \n OWNER: " ) ;
for ( i = first ; i < first + pages ; i + + )
printk ( " %08x " , pgtbl [ i ] . owner ) ;
printk ( " \n " ) ;
}
return 0 ;
}
/*
* Translate a physical address to a logical address .
* This will return the logical address of the first
* match .
*/
unsigned long vdma_phys2log ( unsigned long paddr )
{
int i ;
int frame ;
frame = paddr & ~ ( VDMA_PAGESIZE - 1 ) ;
for ( i = 0 ; i < VDMA_PGTBL_ENTRIES ; i + + ) {
if ( pgtbl [ i ] . frame = = frame )
break ;
}
if ( i = = VDMA_PGTBL_ENTRIES )
return ~ 0UL ;
return ( i < < 12 ) + ( paddr & ( VDMA_PAGESIZE - 1 ) ) ;
}
EXPORT_SYMBOL ( vdma_phys2log ) ;
/*
* Translate a logical DMA address to a physical address
*/
unsigned long vdma_log2phys ( unsigned long laddr )
{
return pgtbl [ laddr > > 12 ] . frame + ( laddr & ( VDMA_PAGESIZE - 1 ) ) ;
}
EXPORT_SYMBOL ( vdma_log2phys ) ;
/*
* Print DMA statistics
*/
void vdma_stats ( void )
{
int i ;
printk ( " vdma_stats: CONFIG: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_CONFIG ) ) ;
printk ( " R4030 translation table base: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_TRSTBL_BASE ) ) ;
printk ( " R4030 translation table limit: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_TRSTBL_LIM ) ) ;
printk ( " vdma_stats: INV_ADDR: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_INV_ADDR ) ) ;
printk ( " vdma_stats: R_FAIL_ADDR: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_R_FAIL_ADDR ) ) ;
printk ( " vdma_stats: M_FAIL_ADDR: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_M_FAIL_ADDR ) ) ;
printk ( " vdma_stats: IRQ_SOURCE: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_IRQ_SOURCE ) ) ;
printk ( " vdma_stats: I386_ERROR: %08x \n " ,
r4030_read_reg32 ( JAZZ_R4030_I386_ERROR ) ) ;
printk ( " vdma_chnl_modes: " ) ;
for ( i = 0 ; i < 8 ; i + + )
printk ( " %04x " ,
( unsigned ) r4030_read_reg32 ( JAZZ_R4030_CHNL_MODE +
( i < < 5 ) ) ) ;
printk ( " \n " ) ;
printk ( " vdma_chnl_enables: " ) ;
for ( i = 0 ; i < 8 ; i + + )
printk ( " %04x " ,
( unsigned ) r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( i < < 5 ) ) ) ;
printk ( " \n " ) ;
}
/*
* DMA transfer functions
*/
/*
* Enable a DMA channel . Also clear any error conditions .
*/
void vdma_enable ( int channel )
{
int status ;
if ( vdma_debug )
printk ( " vdma_enable: channel %d \n " , channel ) ;
/*
* Check error conditions first
*/
status = r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ) ;
if ( status & 0x400 )
printk ( " VDMA: Channel %d: Address error! \n " , channel ) ;
if ( status & 0x200 )
printk ( " VDMA: Channel %d: Memory error! \n " , channel ) ;
/*
* Clear all interrupt flags
*/
r4030_write_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ,
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) | R4030_TC_INTR
| R4030_MEM_INTR | R4030_ADDR_INTR ) ;
/*
* Enable the desired channel
*/
r4030_write_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ,
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) |
R4030_CHNL_ENABLE ) ;
}
EXPORT_SYMBOL ( vdma_enable ) ;
/*
* Disable a DMA channel
*/
void vdma_disable ( int channel )
{
if ( vdma_debug ) {
int status =
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) ;
printk ( " vdma_disable: channel %d \n " , channel ) ;
printk ( " VDMA: channel %d status: %04x (%s) mode: "
" %02x addr: %06x count: %06x \n " ,
channel , status ,
( ( status & 0x600 ) ? " ERROR " : " OK " ) ,
( unsigned ) r4030_read_reg32 ( JAZZ_R4030_CHNL_MODE +
( channel < < 5 ) ) ,
( unsigned ) r4030_read_reg32 ( JAZZ_R4030_CHNL_ADDR +
( channel < < 5 ) ) ,
( unsigned ) r4030_read_reg32 ( JAZZ_R4030_CHNL_COUNT +
( channel < < 5 ) ) ) ;
}
r4030_write_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ,
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) &
~ R4030_CHNL_ENABLE ) ;
/*
* After disabling a DMA channel a remote bus register should be
* read to ensure that the current DMA acknowledge cycle is completed .
*/
* ( ( volatile unsigned int * ) JAZZ_DUMMY_DEVICE ) ;
}
EXPORT_SYMBOL ( vdma_disable ) ;
/*
* Set DMA mode . This function accepts the mode values used
* to set a PC - style DMA controller . For the SCSI and FDC
* channels , we also set the default modes each time we ' re
* called .
* NOTE : The FAST and BURST dma modes are supported by the
* R4030 Rev . 2 and PICA chipsets only . I leave them disabled
* for now .
*/
void vdma_set_mode ( int channel , int mode )
{
if ( vdma_debug )
printk ( " vdma_set_mode: channel %d, mode 0x%x \n " , channel ,
mode ) ;
switch ( channel ) {
case JAZZ_SCSI_DMA : /* scsi */
r4030_write_reg32 ( JAZZ_R4030_CHNL_MODE + ( channel < < 5 ) ,
/* R4030_MODE_FAST | */
/* R4030_MODE_BURST | */
R4030_MODE_INTR_EN |
R4030_MODE_WIDTH_16 |
R4030_MODE_ATIME_80 ) ;
break ;
case JAZZ_FLOPPY_DMA : /* floppy */
r4030_write_reg32 ( JAZZ_R4030_CHNL_MODE + ( channel < < 5 ) ,
/* R4030_MODE_FAST | */
/* R4030_MODE_BURST | */
R4030_MODE_INTR_EN |
R4030_MODE_WIDTH_8 |
R4030_MODE_ATIME_120 ) ;
break ;
case JAZZ_AUDIOL_DMA :
case JAZZ_AUDIOR_DMA :
printk ( " VDMA: Audio DMA not supported yet. \n " ) ;
break ;
default :
printk
( " VDMA: vdma_set_mode() called with unsupported channel %d! \n " ,
channel ) ;
}
switch ( mode ) {
case DMA_MODE_READ :
r4030_write_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ,
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) &
~ R4030_CHNL_WRITE ) ;
break ;
case DMA_MODE_WRITE :
r4030_write_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ,
r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE +
( channel < < 5 ) ) |
R4030_CHNL_WRITE ) ;
break ;
default :
printk
( " VDMA: vdma_set_mode() called with unknown dma mode 0x%x \n " ,
mode ) ;
}
}
EXPORT_SYMBOL ( vdma_set_mode ) ;
/*
* Set Transfer Address
*/
void vdma_set_addr ( int channel , long addr )
{
if ( vdma_debug )
printk ( " vdma_set_addr: channel %d, addr %lx \n " , channel ,
addr ) ;
r4030_write_reg32 ( JAZZ_R4030_CHNL_ADDR + ( channel < < 5 ) , addr ) ;
}
EXPORT_SYMBOL ( vdma_set_addr ) ;
/*
* Set Transfer Count
*/
void vdma_set_count ( int channel , int count )
{
if ( vdma_debug )
printk ( " vdma_set_count: channel %d, count %08x \n " , channel ,
( unsigned ) count ) ;
r4030_write_reg32 ( JAZZ_R4030_CHNL_COUNT + ( channel < < 5 ) , count ) ;
}
EXPORT_SYMBOL ( vdma_set_count ) ;
/*
* Get Residual
*/
int vdma_get_residue ( int channel )
{
int residual ;
residual = r4030_read_reg32 ( JAZZ_R4030_CHNL_COUNT + ( channel < < 5 ) ) ;
if ( vdma_debug )
printk ( " vdma_get_residual: channel %d: residual=%d \n " ,
channel , residual ) ;
return residual ;
}
/*
* Get DMA channel enable register
*/
int vdma_get_enable ( int channel )
{
int enable ;
enable = r4030_read_reg32 ( JAZZ_R4030_CHNL_ENABLE + ( channel < < 5 ) ) ;
if ( vdma_debug )
printk ( " vdma_get_enable: channel %d: enable=%d \n " , channel ,
enable ) ;
return enable ;
}
2007-08-25 13:01:50 +04:00
arch_initcall ( vdma_init ) ;