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 ;
2013-10-25 18:33:12 +04:00
int bits ;
2012-02-22 13:49:08 +04:00
2014-07-03 08:51:53 +04:00
bits = params_physical_width ( params ) ;
2013-10-25 18:33:12 +04:00
if ( bits < 8 | | bits > 64 )
return - EINVAL ;
else if ( bits = = 8 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE ;
2013-10-25 18:33:12 +04:00
else if ( bits = = 16 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2014-07-03 08:51:55 +04:00
else if ( bits = = 24 )
buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES ;
2013-10-25 18:33:12 +04:00
else if ( bits < = 32 )
2012-02-22 13:49:08 +04:00
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES ;
2013-10-25 18:33:12 +04:00
else
buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES ;
2012-02-22 13:49:08 +04:00
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 ) ;
2013-04-03 13:06:02 +04:00
/**
* snd_dmaengine_pcm_set_config_from_dai_data ( ) - Initializes a dma slave config
* using DAI DMA data .
* @ substream : PCM substream
* @ dma_data : DAI DMA data
* @ slave_config : DMA slave configuration
*
* Initializes the { dst , src } _addr , { dst , src } _maxburst , { dst , src } _addr_width and
* slave_id fields of the DMA slave config from the same fields of the DAI DMA
* data struct . The src and dst fields will be initialized depending on the
* direction of the substream . If the substream is a playback stream the dst
* fields will be initialized , if it is a capture stream the src fields will be
* initialized . The { dst , src } _addr_width field will only be initialized if the
* addr_width field of the DAI DMA data struct is not equal to
* DMA_SLAVE_BUSWIDTH_UNDEFINED .
*/
void snd_dmaengine_pcm_set_config_from_dai_data (
const struct snd_pcm_substream * substream ,
const struct snd_dmaengine_dai_dma_data * dma_data ,
struct dma_slave_config * slave_config )
{
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
slave_config - > dst_addr = dma_data - > addr ;
slave_config - > dst_maxburst = dma_data - > maxburst ;
if ( dma_data - > addr_width ! = DMA_SLAVE_BUSWIDTH_UNDEFINED )
slave_config - > dst_addr_width = dma_data - > addr_width ;
} else {
slave_config - > src_addr = dma_data - > addr ;
slave_config - > src_maxburst = dma_data - > maxburst ;
if ( dma_data - > addr_width ! = DMA_SLAVE_BUSWIDTH_UNDEFINED )
slave_config - > src_addr_width = dma_data - > addr_width ;
}
slave_config - > slave_id = dma_data - > slave_id ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_set_config_from_dai_data ) ;
2012-02-22 13:49:08 +04:00
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 ) ;
2014-05-19 13:23:53 +04:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2012-02-22 13:49:08 +04:00
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 :
2014-05-19 13:23:53 +04:00
if ( runtime - > info & SNDRV_PCM_INFO_PAUSE )
dmaengine_pause ( prtd - > dma_chan ) ;
else
dmaengine_terminate_all ( prtd - > dma_chan ) ;
break ;
2012-02-22 13:49:08 +04:00
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 ) ;
2013-04-15 21:19:51 +04:00
/**
* snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM
* @ filter_fn : Filter function used to request the DMA channel
* @ filter_data : Data passed to the DMA filter function
*
* Returns NULL or the requested DMA channel .
*
* This function request a DMA channel for usage with dmaengine PCM .
*/
struct dma_chan * snd_dmaengine_pcm_request_channel ( dma_filter_fn filter_fn ,
2013-04-15 21:19:48 +04:00
void * filter_data )
2012-02-22 13:49:08 +04:00
{
dma_cap_mask_t mask ;
dma_cap_zero ( mask ) ;
dma_cap_set ( DMA_SLAVE , mask ) ;
dma_cap_set ( DMA_CYCLIC , mask ) ;
2013-04-15 21:19:48 +04:00
return dma_request_channel ( mask , filter_fn , filter_data ) ;
2012-02-22 13:49:08 +04:00
}
2013-04-15 21:19:51 +04:00
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_request_channel ) ;
2012-02-22 13:49:08 +04:00
/**
* snd_dmaengine_pcm_open - Open a dmaengine based PCM substream
* @ substream : PCM substream
2013-04-15 21:19:48 +04:00
* @ chan : DMA channel to use for data transfers
2012-02-22 13:49:08 +04:00
*
* Returns 0 on success , a negative error code otherwise .
*
2013-04-15 21:19:48 +04:00
* 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 .
2012-02-22 13:49:08 +04:00
*/
int snd_dmaengine_pcm_open ( struct snd_pcm_substream * substream ,
2013-04-15 21:19:48 +04:00
struct dma_chan * chan )
2012-02-22 13:49:08 +04:00
{
struct dmaengine_pcm_runtime_data * prtd ;
int ret ;
2013-04-15 21:19:48 +04:00
if ( ! chan )
return - ENXIO ;
2012-02-22 13:49:08 +04:00
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 ;
2013-04-15 21:19:48 +04:00
prtd - > dma_chan = chan ;
2012-02-22 13:49:08 +04:00
substream - > runtime - > private_data = prtd ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_open ) ;
2013-04-15 21:19:48 +04:00
/**
* snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel
* @ 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 .
*/
int snd_dmaengine_pcm_open_request_chan ( struct snd_pcm_substream * substream ,
dma_filter_fn filter_fn , void * filter_data )
{
return snd_dmaengine_pcm_open ( substream ,
2013-04-15 21:19:51 +04:00
snd_dmaengine_pcm_request_channel ( filter_fn , filter_data ) ) ;
2013-04-15 21:19:48 +04:00
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_open_request_chan ) ;
2012-02-22 13:49:08 +04:00
/**
* 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 ) ;
kfree ( prtd ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_close ) ;
2012-11-22 16:31:10 +04:00
2013-04-15 21:19:48 +04:00
/**
* snd_dmaengine_pcm_release_chan_close - Close a dmaengine based PCM substream and release channel
* @ substream : PCM substream
*
* Releases the DMA channel associated with the PCM substream .
*/
int snd_dmaengine_pcm_close_release_chan ( struct snd_pcm_substream * substream )
{
struct dmaengine_pcm_runtime_data * prtd = substream_to_prtd ( substream ) ;
dma_release_channel ( prtd - > dma_chan ) ;
return snd_dmaengine_pcm_close ( substream ) ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_close_release_chan ) ;
2012-11-22 16:31:10 +04:00
MODULE_LICENSE ( " GPL " ) ;