2012-02-22 13:49:08 +04:00
/*
* Copyright ( C ) 2012 , Analog Devices Inc .
* Author : Lars - Peter Clausen < lars @ metafoo . de >
*
* Based on :
* imx - pcm - dma - mx2 . c , Copyright 2009 Sascha Hauer < s . hauer @ pengutronix . de >
* mxs - pcm . c , Copyright ( C ) 2011 Freescale Semiconductor , Inc .
* ep93xx - pcm . c , Copyright ( C ) 2006 Lennert Buytenhek < buytenh @ wantstofly . org >
* Copyright ( C ) 2006 Applied Data Systems
*
* 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 .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/dmaengine.h>
# include <linux/slab.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/dmaengine_pcm.h>
struct dmaengine_pcm_runtime_data {
struct dma_chan * dma_chan ;
2012-06-11 22:11:42 +04:00
dma_cookie_t cookie ;
2012-02-22 13:49:08 +04:00
unsigned int pos ;
} ;
static inline struct dmaengine_pcm_runtime_data * substream_to_prtd (
const struct snd_pcm_substream * substream )
{
return substream - > runtime - > private_data ;
}
struct dma_chan * snd_dmaengine_pcm_get_chan ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
return prtd - > dma_chan ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_get_chan ) ;
/**
* snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
* @ substream : PCM substream
* @ params : hw_params
* @ slave_config : DMA slave config
*
* This function can be used to initialize a dma_slave_config from a substream
* and hw_params in a dmaengine based PCM driver implementation .
*/
int snd_hwparams_to_dma_slave_config ( const struct snd_pcm_substream * substream ,
const struct snd_pcm_hw_params * params ,
struct dma_slave_config * slave_config )
{
enum dma_slave_buswidth buswidth ;
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S8 :
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE ;
break ;
case SNDRV_PCM_FORMAT_S16_LE :
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES ;
break ;
case SNDRV_PCM_FORMAT_S18_3LE :
case SNDRV_PCM_FORMAT_S20_3LE :
case SNDRV_PCM_FORMAT_S24_LE :
case SNDRV_PCM_FORMAT_S32_LE :
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES ;
break ;
default :
return - EINVAL ;
}
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
slave_config - > direction = DMA_MEM_TO_DEV ;
slave_config - > dst_addr_width = buswidth ;
} else {
slave_config - > direction = DMA_DEV_TO_MEM ;
slave_config - > src_addr_width = buswidth ;
}
2013-04-03 13:02:56 +04:00
slave_config - > device_fc = false ;
2012-02-22 13:49:08 +04:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_hwparams_to_dma_slave_config ) ;
static void dmaengine_pcm_dma_complete ( void * arg )
{
struct snd_pcm_substream * substream = arg ;
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
prtd - > pos + = snd_pcm_lib_period_bytes ( substream ) ;
if ( prtd - > pos > = snd_pcm_lib_buffer_bytes ( substream ) )
prtd - > pos = 0 ;
snd_pcm_period_elapsed ( substream ) ;
}
static int dmaengine_pcm_prepare_and_submit ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
struct dma_chan * chan = prtd - > dma_chan ;
struct dma_async_tx_descriptor * desc ;
enum dma_transfer_direction direction ;
2012-09-24 11:58:04 +04:00
unsigned long flags = DMA_CTRL_ACK ;
2012-02-22 13:49:08 +04:00
direction = snd_pcm_substream_to_dma_direction ( substream ) ;
2012-09-24 11:58:04 +04:00
if ( ! substream - > runtime - > no_period_wakeup )
flags | = DMA_PREP_INTERRUPT ;
2012-03-05 17:02:14 +04:00
prtd - > pos = 0 ;
2012-03-26 15:58:08 +04:00
desc = dmaengine_prep_dma_cyclic ( chan ,
2012-02-22 13:49:08 +04:00
substream - > runtime - > dma_addr ,
snd_pcm_lib_buffer_bytes ( substream ) ,
2012-09-24 11:58:04 +04:00
snd_pcm_lib_period_bytes ( substream ) , direction , flags ) ;
2012-02-22 13:49:08 +04:00
if ( ! desc )
return - ENOMEM ;
desc - > callback = dmaengine_pcm_dma_complete ;
desc - > callback_param = substream ;
2012-06-11 22:11:42 +04:00
prtd - > cookie = dmaengine_submit ( desc ) ;
2012-02-22 13:49:08 +04:00
return 0 ;
}
/**
* snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation
* @ substream : PCM substream
* @ cmd : Trigger command
*
* Returns 0 on success , a negative error code otherwise .
*
* This function can be used as the PCM trigger callback for dmaengine based PCM
* driver implementations .
*/
int snd_dmaengine_pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
int ret ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
ret = dmaengine_pcm_prepare_and_submit ( substream ) ;
if ( ret )
return ret ;
dma_async_issue_pending ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
dmaengine_resume ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
dmaengine_pause ( prtd - > dma_chan ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
dmaengine_terminate_all ( prtd - > dma_chan ) ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_trigger ) ;
/**
2012-06-11 22:11:41 +04:00
* snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation
2012-02-22 13:49:08 +04:00
* @ substream : PCM substream
*
2012-06-11 22:11:41 +04:00
* This function is deprecated and should not be used by new drivers , as its
* results may be unreliable .
2012-02-22 13:49:08 +04:00
*/
2012-06-11 22:11:41 +04:00
snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue ( struct snd_pcm_substream * substream )
2012-02-22 13:49:08 +04:00
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
return bytes_to_frames ( substream - > runtime , prtd - > pos ) ;
}
2012-06-11 22:11:41 +04:00
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_pointer_no_residue ) ;
2012-02-22 13:49:08 +04:00
2012-06-11 22:11:42 +04:00
/**
* snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation
* @ substream : PCM substream
*
* This function can be used as the PCM pointer callback for dmaengine based PCM
* driver implementations .
*/
snd_pcm_uframes_t snd_dmaengine_pcm_pointer ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
struct dma_tx_state state ;
enum dma_status status ;
unsigned int buf_size ;
unsigned int pos = 0 ;
status = dmaengine_tx_status ( prtd - > dma_chan , prtd - > cookie , & state ) ;
if ( status = = DMA_IN_PROGRESS | | status = = DMA_PAUSED ) {
buf_size = snd_pcm_lib_buffer_bytes ( substream ) ;
if ( state . residue > 0 & & state . residue < = buf_size )
pos = buf_size - state . residue ;
}
return bytes_to_frames ( substream - > runtime , pos ) ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_pointer ) ;
2012-02-22 13:49:08 +04:00
static int dmaengine_pcm_request_channel ( struct dmaengine_pcm_runtime_data * prtd ,
dma_filter_fn filter_fn , void * filter_data )
{
dma_cap_mask_t mask ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_SLAVE , mask ) ;
dma_cap_set ( DMA_CYCLIC , mask ) ;
prtd - > dma_chan = dma_request_channel ( mask , filter_fn , filter_data ) ;
if ( ! prtd - > dma_chan )
return - ENXIO ;
return 0 ;
}
/**
* snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
* @ substream : PCM substream
* @ filter_fn : Filter function used to request the DMA channel
* @ filter_data : Data passed to the DMA filter function
*
* Returns 0 on success , a negative error code otherwise .
*
* This function will request a DMA channel using the passed filter function and
* data . The function should usually be called from the pcm open callback .
*
* Note that this function will use private_data field of the substream ' s
* runtime . So it is not availabe to your pcm driver implementation . If you need
* to keep additional data attached to a substream use
2012-06-25 11:28:46 +04:00
* snd_dmaengine_pcm_ { set , get } _data .
2012-02-22 13:49:08 +04:00
*/
int snd_dmaengine_pcm_open ( struct snd_pcm_substream * substream ,
dma_filter_fn filter_fn , void * filter_data )
{
struct dmaengine_pcm_runtime_data * prtd ;
int ret ;
ret = snd_pcm_hw_constraint_integer ( substream - > runtime ,
SNDRV_PCM_HW_PARAM_PERIODS ) ;
if ( ret < 0 )
return ret ;
prtd = kzalloc ( sizeof ( * prtd ) , GFP_KERNEL ) ;
if ( ! prtd )
return - ENOMEM ;
ret = dmaengine_pcm_request_channel ( prtd , filter_fn , filter_data ) ;
if ( ret < 0 ) {
kfree ( prtd ) ;
return ret ;
}
substream - > runtime - > private_data = prtd ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_open ) ;
/**
* snd_dmaengine_pcm_close - Close a dmaengine based PCM substream
* @ substream : PCM substream
*/
int snd_dmaengine_pcm_close ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
dma_release_channel ( prtd - > dma_chan ) ;
kfree ( prtd ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_close ) ;
2012-11-22 16:31:10 +04:00
MODULE_LICENSE ( " GPL " ) ;