2009-03-02 21:27:38 +01:00
/*
* arch / arm / mach - ep93xx / dma - m2p . c
* M2P DMA handling for Cirrus EP93xx chips .
*
* Copyright ( C ) 2006 Lennert Buytenhek < buytenh @ wantstofly . org >
* Copyright ( C ) 2006 Applied Data Systems
*
* Copyright ( C ) 2009 Ryan Mallon < ryan @ bluewatersys . 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 .
*/
/*
* On the EP93xx chip the following peripherals my be allocated to the 10
* Memory to Internal Peripheral ( M2P ) channels ( 5 transmit + 5 receive ) .
*
* I2S contains 3 Tx and 3 Rx DMA Channels
* AAC contains 3 Tx and 3 Rx DMA Channels
* UART1 contains 1 Tx and 1 Rx DMA Channels
* UART2 contains 1 Tx and 1 Rx DMA Channels
* UART3 contains 1 Tx and 1 Rx DMA Channels
* IrDA contains 1 Tx and 1 Rx DMA Channels
*
* SSP and IDE use the Memory to Memory ( M2M ) channels and are not covered
* with this implementation .
*/
2010-01-11 18:36:02 +01:00
# define pr_fmt(fmt) "ep93xx " KBUILD_MODNAME ": " fmt
2009-03-02 21:27:38 +01:00
# include <linux/kernel.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/module.h>
2009-07-06 22:17:54 +01:00
# include <linux/io.h>
2009-03-02 21:27:38 +01:00
# include <mach/dma.h>
# include <mach/hardware.h>
# define M2P_CONTROL 0x00
# define M2P_CONTROL_STALL_IRQ_EN (1 << 0)
# define M2P_CONTROL_NFB_IRQ_EN (1 << 1)
# define M2P_CONTROL_ERROR_IRQ_EN (1 << 3)
# define M2P_CONTROL_ENABLE (1 << 4)
# define M2P_INTERRUPT 0x04
# define M2P_INTERRUPT_STALL (1 << 0)
# define M2P_INTERRUPT_NFB (1 << 1)
# define M2P_INTERRUPT_ERROR (1 << 3)
# define M2P_PPALLOC 0x08
# define M2P_STATUS 0x0c
# define M2P_REMAIN 0x14
# define M2P_MAXCNT0 0x20
# define M2P_BASE0 0x24
# define M2P_MAXCNT1 0x30
# define M2P_BASE1 0x34
# define STATE_IDLE 0 /* Channel is inactive. */
# define STATE_STALL 1 /* Channel is active, no buffers pending. */
# define STATE_ON 2 /* Channel is active, one buffer pending. */
# define STATE_NEXT 3 /* Channel is active, two buffers pending. */
struct m2p_channel {
char * name ;
void __iomem * base ;
int irq ;
struct clk * clk ;
spinlock_t lock ;
void * client ;
unsigned next_slot : 1 ;
struct ep93xx_dma_buffer * buffer_xfer ;
struct ep93xx_dma_buffer * buffer_next ;
struct list_head buffers_pending ;
} ;
static struct m2p_channel m2p_rx [ ] = {
{ " m2p1 " , EP93XX_DMA_BASE + 0x0040 , IRQ_EP93XX_DMAM2P1 } ,
{ " m2p3 " , EP93XX_DMA_BASE + 0x00c0 , IRQ_EP93XX_DMAM2P3 } ,
{ " m2p5 " , EP93XX_DMA_BASE + 0x0200 , IRQ_EP93XX_DMAM2P5 } ,
{ " m2p7 " , EP93XX_DMA_BASE + 0x0280 , IRQ_EP93XX_DMAM2P7 } ,
{ " m2p9 " , EP93XX_DMA_BASE + 0x0300 , IRQ_EP93XX_DMAM2P9 } ,
{ NULL } ,
} ;
static struct m2p_channel m2p_tx [ ] = {
{ " m2p0 " , EP93XX_DMA_BASE + 0x0000 , IRQ_EP93XX_DMAM2P0 } ,
{ " m2p2 " , EP93XX_DMA_BASE + 0x0080 , IRQ_EP93XX_DMAM2P2 } ,
{ " m2p4 " , EP93XX_DMA_BASE + 0x0240 , IRQ_EP93XX_DMAM2P4 } ,
{ " m2p6 " , EP93XX_DMA_BASE + 0x02c0 , IRQ_EP93XX_DMAM2P6 } ,
{ " m2p8 " , EP93XX_DMA_BASE + 0x0340 , IRQ_EP93XX_DMAM2P8 } ,
{ NULL } ,
} ;
static void feed_buf ( struct m2p_channel * ch , struct ep93xx_dma_buffer * buf )
{
if ( ch - > next_slot = = 0 ) {
writel ( buf - > size , ch - > base + M2P_MAXCNT0 ) ;
writel ( buf - > bus_addr , ch - > base + M2P_BASE0 ) ;
} else {
writel ( buf - > size , ch - > base + M2P_MAXCNT1 ) ;
writel ( buf - > bus_addr , ch - > base + M2P_BASE1 ) ;
}
ch - > next_slot ^ = 1 ;
}
static void choose_buffer_xfer ( struct m2p_channel * ch )
{
struct ep93xx_dma_buffer * buf ;
ch - > buffer_xfer = NULL ;
if ( ! list_empty ( & ch - > buffers_pending ) ) {
buf = list_entry ( ch - > buffers_pending . next ,
struct ep93xx_dma_buffer , list ) ;
list_del ( & buf - > list ) ;
feed_buf ( ch , buf ) ;
ch - > buffer_xfer = buf ;
}
}
static void choose_buffer_next ( struct m2p_channel * ch )
{
struct ep93xx_dma_buffer * buf ;
ch - > buffer_next = NULL ;
if ( ! list_empty ( & ch - > buffers_pending ) ) {
buf = list_entry ( ch - > buffers_pending . next ,
struct ep93xx_dma_buffer , list ) ;
list_del ( & buf - > list ) ;
feed_buf ( ch , buf ) ;
ch - > buffer_next = buf ;
}
}
static inline void m2p_set_control ( struct m2p_channel * ch , u32 v )
{
/*
* The control register must be read immediately after being written so
* that the internal state machine is correctly updated . See the ep93xx
* users ' guide for details .
*/
writel ( v , ch - > base + M2P_CONTROL ) ;
readl ( ch - > base + M2P_CONTROL ) ;
}
static inline int m2p_channel_state ( struct m2p_channel * ch )
{
return ( readl ( ch - > base + M2P_STATUS ) > > 4 ) & 0x3 ;
}
static irqreturn_t m2p_irq ( int irq , void * dev_id )
{
struct m2p_channel * ch = dev_id ;
struct ep93xx_dma_m2p_client * cl ;
u32 irq_status , v ;
int error = 0 ;
cl = ch - > client ;
spin_lock ( & ch - > lock ) ;
irq_status = readl ( ch - > base + M2P_INTERRUPT ) ;
if ( irq_status & M2P_INTERRUPT_ERROR ) {
writel ( M2P_INTERRUPT_ERROR , ch - > base + M2P_INTERRUPT ) ;
error = 1 ;
}
if ( ( irq_status & ( M2P_INTERRUPT_STALL | M2P_INTERRUPT_NFB ) ) = = 0 ) {
spin_unlock ( & ch - > lock ) ;
return IRQ_NONE ;
}
switch ( m2p_channel_state ( ch ) ) {
case STATE_IDLE :
2010-01-11 18:36:02 +01:00
pr_crit ( " dma interrupt without a dma buffer \n " ) ;
2009-03-02 21:27:38 +01:00
BUG ( ) ;
break ;
case STATE_STALL :
cl - > buffer_finished ( cl - > cookie , ch - > buffer_xfer , 0 , error ) ;
if ( ch - > buffer_next ! = NULL ) {
cl - > buffer_finished ( cl - > cookie , ch - > buffer_next ,
0 , error ) ;
}
choose_buffer_xfer ( ch ) ;
choose_buffer_next ( ch ) ;
if ( ch - > buffer_xfer ! = NULL )
cl - > buffer_started ( cl - > cookie , ch - > buffer_xfer ) ;
break ;
case STATE_ON :
cl - > buffer_finished ( cl - > cookie , ch - > buffer_xfer , 0 , error ) ;
ch - > buffer_xfer = ch - > buffer_next ;
choose_buffer_next ( ch ) ;
cl - > buffer_started ( cl - > cookie , ch - > buffer_xfer ) ;
break ;
case STATE_NEXT :
2010-01-11 18:36:02 +01:00
pr_crit ( " dma interrupt while next \n " ) ;
2009-03-02 21:27:38 +01:00
BUG ( ) ;
break ;
}
v = readl ( ch - > base + M2P_CONTROL ) & ~ ( M2P_CONTROL_STALL_IRQ_EN |
M2P_CONTROL_NFB_IRQ_EN ) ;
if ( ch - > buffer_xfer ! = NULL )
v | = M2P_CONTROL_STALL_IRQ_EN ;
if ( ch - > buffer_next ! = NULL )
v | = M2P_CONTROL_NFB_IRQ_EN ;
m2p_set_control ( ch , v ) ;
spin_unlock ( & ch - > lock ) ;
return IRQ_HANDLED ;
}
static struct m2p_channel * find_free_channel ( struct ep93xx_dma_m2p_client * cl )
{
struct m2p_channel * ch ;
int i ;
if ( cl - > flags & EP93XX_DMA_M2P_RX )
ch = m2p_rx ;
else
ch = m2p_tx ;
for ( i = 0 ; ch [ i ] . base ; i + + ) {
struct ep93xx_dma_m2p_client * client ;
client = ch [ i ] . client ;
if ( client ! = NULL ) {
int port ;
port = cl - > flags & EP93XX_DMA_M2P_PORT_MASK ;
if ( port = = ( client - > flags &
EP93XX_DMA_M2P_PORT_MASK ) ) {
pr_warning ( " DMA channel already used by %s \n " ,
cl - > name ? : " unknown client " ) ;
return ERR_PTR ( - EBUSY ) ;
}
}
}
for ( i = 0 ; ch [ i ] . base ; i + + ) {
if ( ch [ i ] . client = = NULL )
return ch + i ;
}
pr_warning ( " No free DMA channel for %s \n " ,
cl - > name ? : " unknown client " ) ;
return ERR_PTR ( - ENODEV ) ;
}
static void channel_enable ( struct m2p_channel * ch )
{
struct ep93xx_dma_m2p_client * cl = ch - > client ;
u32 v ;
clk_enable ( ch - > clk ) ;
v = cl - > flags & EP93XX_DMA_M2P_PORT_MASK ;
writel ( v , ch - > base + M2P_PPALLOC ) ;
v = cl - > flags & EP93XX_DMA_M2P_ERROR_MASK ;
v | = M2P_CONTROL_ENABLE | M2P_CONTROL_ERROR_IRQ_EN ;
m2p_set_control ( ch , v ) ;
}
static void channel_disable ( struct m2p_channel * ch )
{
u32 v ;
v = readl ( ch - > base + M2P_CONTROL ) ;
v & = ~ ( M2P_CONTROL_STALL_IRQ_EN | M2P_CONTROL_NFB_IRQ_EN ) ;
m2p_set_control ( ch , v ) ;
while ( m2p_channel_state ( ch ) = = STATE_ON )
cpu_relax ( ) ;
m2p_set_control ( ch , 0x0 ) ;
while ( m2p_channel_state ( ch ) = = STATE_STALL )
cpu_relax ( ) ;
clk_disable ( ch - > clk ) ;
}
int ep93xx_dma_m2p_client_register ( struct ep93xx_dma_m2p_client * cl )
{
struct m2p_channel * ch ;
int err ;
ch = find_free_channel ( cl ) ;
if ( IS_ERR ( ch ) )
return PTR_ERR ( ch ) ;
err = request_irq ( ch - > irq , m2p_irq , 0 , cl - > name ? : " dma-m2p " , ch ) ;
if ( err )
return err ;
ch - > client = cl ;
ch - > next_slot = 0 ;
ch - > buffer_xfer = NULL ;
ch - > buffer_next = NULL ;
INIT_LIST_HEAD ( & ch - > buffers_pending ) ;
cl - > channel = ch ;
channel_enable ( ch ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ep93xx_dma_m2p_client_register ) ;
void ep93xx_dma_m2p_client_unregister ( struct ep93xx_dma_m2p_client * cl )
{
struct m2p_channel * ch = cl - > channel ;
channel_disable ( ch ) ;
free_irq ( ch - > irq , ch ) ;
ch - > client = NULL ;
}
EXPORT_SYMBOL_GPL ( ep93xx_dma_m2p_client_unregister ) ;
void ep93xx_dma_m2p_submit ( struct ep93xx_dma_m2p_client * cl ,
struct ep93xx_dma_buffer * buf )
{
struct m2p_channel * ch = cl - > channel ;
unsigned long flags ;
u32 v ;
spin_lock_irqsave ( & ch - > lock , flags ) ;
v = readl ( ch - > base + M2P_CONTROL ) ;
if ( ch - > buffer_xfer = = NULL ) {
ch - > buffer_xfer = buf ;
feed_buf ( ch , buf ) ;
cl - > buffer_started ( cl - > cookie , buf ) ;
v | = M2P_CONTROL_STALL_IRQ_EN ;
m2p_set_control ( ch , v ) ;
} else if ( ch - > buffer_next = = NULL ) {
ch - > buffer_next = buf ;
feed_buf ( ch , buf ) ;
v | = M2P_CONTROL_NFB_IRQ_EN ;
m2p_set_control ( ch , v ) ;
} else {
list_add_tail ( & buf - > list , & ch - > buffers_pending ) ;
}
spin_unlock_irqrestore ( & ch - > lock , flags ) ;
}
EXPORT_SYMBOL_GPL ( ep93xx_dma_m2p_submit ) ;
void ep93xx_dma_m2p_submit_recursive ( struct ep93xx_dma_m2p_client * cl ,
struct ep93xx_dma_buffer * buf )
{
struct m2p_channel * ch = cl - > channel ;
list_add_tail ( & buf - > list , & ch - > buffers_pending ) ;
}
EXPORT_SYMBOL_GPL ( ep93xx_dma_m2p_submit_recursive ) ;
void ep93xx_dma_m2p_flush ( struct ep93xx_dma_m2p_client * cl )
{
struct m2p_channel * ch = cl - > channel ;
channel_disable ( ch ) ;
ch - > next_slot = 0 ;
ch - > buffer_xfer = NULL ;
ch - > buffer_next = NULL ;
INIT_LIST_HEAD ( & ch - > buffers_pending ) ;
channel_enable ( ch ) ;
}
EXPORT_SYMBOL_GPL ( ep93xx_dma_m2p_flush ) ;
static int init_channel ( struct m2p_channel * ch )
{
ch - > clk = clk_get ( NULL , ch - > name ) ;
if ( IS_ERR ( ch - > clk ) )
return PTR_ERR ( ch - > clk ) ;
spin_lock_init ( & ch - > lock ) ;
ch - > client = NULL ;
return 0 ;
}
static int __init ep93xx_dma_m2p_init ( void )
{
int i ;
int ret ;
for ( i = 0 ; m2p_rx [ i ] . base ; i + + ) {
ret = init_channel ( m2p_rx + i ) ;
if ( ret )
return ret ;
}
for ( i = 0 ; m2p_tx [ i ] . base ; i + + ) {
ret = init_channel ( m2p_tx + i ) ;
if ( ret )
return ret ;
}
pr_info ( " M2P DMA subsystem initialized \n " ) ;
return 0 ;
}
arch_initcall ( ep93xx_dma_m2p_init ) ;