2013-04-15 19:19:50 +02:00
/*
* Copyright ( C ) 2013 , Analog Devices Inc .
* Author : Lars - Peter Clausen < lars @ metafoo . de >
*
* 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 <linux/dma-mapping.h>
# include <linux/of.h>
# include <sound/dmaengine_pcm.h>
struct dmaengine_pcm {
2013-10-25 18:06:09 +02:00
struct dma_chan * chan [ SNDRV_PCM_STREAM_LAST + 1 ] ;
2013-04-15 19:19:50 +02:00
const struct snd_dmaengine_pcm_config * config ;
struct snd_soc_platform platform ;
2013-04-20 19:29:00 +02:00
unsigned int flags ;
2013-04-15 19:19:50 +02:00
} ;
static struct dmaengine_pcm * soc_platform_to_pcm ( struct snd_soc_platform * p )
{
return container_of ( p , struct dmaengine_pcm , platform ) ;
}
2013-10-08 15:07:59 +02:00
static struct device * dmaengine_dma_dev ( struct dmaengine_pcm * pcm ,
struct snd_pcm_substream * substream )
{
if ( ! pcm - > chan [ substream - > stream ] )
return NULL ;
return pcm - > chan [ substream - > stream ] - > device - > dev ;
}
2013-04-15 19:19:50 +02:00
/**
* snd_dmaengine_pcm_prepare_slave_config ( ) - Generic prepare_slave_config callback
* @ substream : PCM substream
* @ params : hw_params
* @ slave_config : DMA slave config to prepare
*
* This function can be used as a generic prepare_slave_config callback for
* platforms which make use of the snd_dmaengine_dai_dma_data struct for their
* DAI DMA data . Internally the function will first call
* snd_hwparams_to_dma_slave_config to fill in the slave config based on the
* hw_params , followed by snd_dmaengine_set_config_from_dai_data to fill in the
* remaining fields based on the DAI DMA data .
*/
int snd_dmaengine_pcm_prepare_slave_config ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params , struct dma_slave_config * slave_config )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_dmaengine_dai_dma_data * dma_data ;
int ret ;
dma_data = snd_soc_dai_get_dma_data ( rtd - > cpu_dai , substream ) ;
ret = snd_hwparams_to_dma_slave_config ( substream , params , slave_config ) ;
if ( ret )
return ret ;
snd_dmaengine_pcm_set_config_from_dai_data ( substream , dma_data ,
slave_config ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_prepare_slave_config ) ;
static int dmaengine_pcm_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct dmaengine_pcm * pcm = soc_platform_to_pcm ( rtd - > platform ) ;
struct dma_chan * chan = snd_dmaengine_pcm_get_chan ( substream ) ;
2013-10-08 15:08:00 +02:00
int ( * prepare_slave_config ) ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct dma_slave_config * slave_config ) ;
2013-04-15 19:19:50 +02:00
struct dma_slave_config slave_config ;
int ret ;
2013-11-06 10:16:20 +00:00
memset ( & slave_config , 0 , sizeof ( slave_config ) ) ;
2013-10-08 15:08:00 +02:00
if ( ! pcm - > config )
prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config ;
else
prepare_slave_config = pcm - > config - > prepare_slave_config ;
if ( prepare_slave_config ) {
ret = prepare_slave_config ( substream , params , & slave_config ) ;
2013-04-15 19:19:50 +02:00
if ( ret )
return ret ;
ret = dmaengine_slave_config ( chan , & slave_config ) ;
if ( ret )
return ret ;
}
return snd_pcm_lib_malloc_pages ( substream , params_buffer_bytes ( params ) ) ;
}
2013-10-08 15:07:59 +02:00
static int dmaengine_pcm_set_runtime_hwparams ( struct snd_pcm_substream * substream )
2013-04-15 19:19:50 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct dmaengine_pcm * pcm = soc_platform_to_pcm ( rtd - > platform ) ;
2013-10-08 15:07:59 +02:00
struct device * dma_dev = dmaengine_dma_dev ( pcm , substream ) ;
2013-04-15 19:19:50 +02:00
struct dma_chan * chan = pcm - > chan [ substream - > stream ] ;
2013-10-08 15:07:59 +02:00
struct snd_dmaengine_dai_dma_data * dma_data ;
struct dma_slave_caps dma_caps ;
struct snd_pcm_hardware hw ;
2013-04-15 19:19:50 +02:00
int ret ;
2013-10-08 15:08:00 +02:00
if ( pcm - > config & & pcm - > config - > pcm_hardware )
2013-10-08 15:07:59 +02:00
return snd_soc_set_runtime_hwparams ( substream ,
2013-04-15 19:19:50 +02:00
pcm - > config - > pcm_hardware ) ;
2013-10-08 15:07:59 +02:00
dma_data = snd_soc_dai_get_dma_data ( rtd - > cpu_dai , substream ) ;
memset ( & hw , 0 , sizeof ( hw ) ) ;
hw . info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED ;
hw . periods_min = 2 ;
hw . periods_max = UINT_MAX ;
hw . period_bytes_min = 256 ;
hw . period_bytes_max = dma_get_max_seg_size ( dma_dev ) ;
hw . buffer_bytes_max = SIZE_MAX ;
hw . fifo_size = dma_data - > fifo_size ;
ret = dma_get_slave_caps ( chan , & dma_caps ) ;
if ( ret = = 0 ) {
if ( dma_caps . cmd_pause )
hw . info | = SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME ;
}
return snd_soc_set_runtime_hwparams ( substream , & hw ) ;
2013-04-15 19:19:50 +02:00
}
2013-10-08 15:07:59 +02:00
static int dmaengine_pcm_open ( struct snd_pcm_substream * substream )
2013-04-15 19:19:50 +02:00
{
2013-10-08 15:07:59 +02:00
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct dmaengine_pcm * pcm = soc_platform_to_pcm ( rtd - > platform ) ;
struct dma_chan * chan = pcm - > chan [ substream - > stream ] ;
int ret ;
2013-04-15 19:19:50 +02:00
2013-10-08 15:07:59 +02:00
ret = dmaengine_pcm_set_runtime_hwparams ( substream ) ;
if ( ret )
return ret ;
return snd_dmaengine_pcm_open ( substream , chan ) ;
2013-04-15 19:19:50 +02:00
}
static void dmaengine_pcm_free ( struct snd_pcm * pcm )
{
snd_pcm_lib_preallocate_free_for_all ( pcm ) ;
}
2013-04-15 19:19:51 +02:00
static struct dma_chan * dmaengine_pcm_compat_request_channel (
struct snd_soc_pcm_runtime * rtd ,
struct snd_pcm_substream * substream )
{
struct dmaengine_pcm * pcm = soc_platform_to_pcm ( rtd - > platform ) ;
2013-10-19 21:38:26 +01:00
struct snd_dmaengine_dai_dma_data * dma_data ;
dma_data = snd_soc_dai_get_dma_data ( rtd - > cpu_dai , substream ) ;
2013-04-15 19:19:51 +02:00
2013-04-20 19:29:00 +02:00
if ( ( pcm - > flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX ) & & pcm - > chan [ 0 ] )
return pcm - > chan [ 0 ] ;
2013-04-15 19:19:51 +02:00
if ( pcm - > config - > compat_request_channel )
return pcm - > config - > compat_request_channel ( rtd , substream ) ;
return snd_dmaengine_pcm_request_channel ( pcm - > config - > compat_filter_fn ,
2013-10-19 21:38:26 +01:00
dma_data - > filter_data ) ;
2013-04-15 19:19:51 +02:00
}
2013-04-15 19:19:50 +02:00
static int dmaengine_pcm_new ( struct snd_soc_pcm_runtime * rtd )
{
struct dmaengine_pcm * pcm = soc_platform_to_pcm ( rtd - > platform ) ;
const struct snd_dmaengine_pcm_config * config = pcm - > config ;
2013-10-19 17:43:51 +01:00
struct device * dev = rtd - > platform - > dev ;
struct snd_dmaengine_dai_dma_data * dma_data ;
2013-04-15 19:19:50 +02:00
struct snd_pcm_substream * substream ;
2013-10-08 15:08:00 +02:00
size_t prealloc_buffer_size ;
size_t max_buffer_size ;
2013-04-15 19:19:50 +02:00
unsigned int i ;
int ret ;
2013-10-08 15:08:00 +02:00
if ( config & & config - > prealloc_buffer_size ) {
prealloc_buffer_size = config - > prealloc_buffer_size ;
max_buffer_size = config - > pcm_hardware - > buffer_bytes_max ;
} else {
prealloc_buffer_size = 512 * 1024 ;
max_buffer_size = SIZE_MAX ;
}
2013-04-15 19:19:50 +02:00
for ( i = SNDRV_PCM_STREAM_PLAYBACK ; i < = SNDRV_PCM_STREAM_CAPTURE ; i + + ) {
substream = rtd - > pcm - > streams [ i ] . substream ;
if ( ! substream )
continue ;
2013-10-19 17:43:51 +01:00
dma_data = snd_soc_dai_get_dma_data ( rtd - > cpu_dai , substream ) ;
if ( ! pcm - > chan [ i ] & &
( pcm - > flags & SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME ) )
pcm - > chan [ i ] = dma_request_slave_channel ( dev ,
dma_data - > chan_name ) ;
2013-04-20 19:29:00 +02:00
if ( ! pcm - > chan [ i ] & & ( pcm - > flags & SND_DMAENGINE_PCM_FLAG_COMPAT ) ) {
2013-04-15 19:19:51 +02:00
pcm - > chan [ i ] = dmaengine_pcm_compat_request_channel ( rtd ,
substream ) ;
}
2013-04-15 19:19:50 +02:00
if ( ! pcm - > chan [ i ] ) {
dev_err ( rtd - > platform - > dev ,
" Missing dma channel for stream: %d \n " , i ) ;
ret = - EINVAL ;
goto err_free ;
}
ret = snd_pcm_lib_preallocate_pages ( substream ,
2013-11-07 14:45:16 +08:00
SNDRV_DMA_TYPE_DEV_IRAM ,
2013-04-15 19:19:50 +02:00
dmaengine_dma_dev ( pcm , substream ) ,
2013-10-08 15:08:00 +02:00
prealloc_buffer_size ,
max_buffer_size ) ;
2013-04-15 19:19:50 +02:00
if ( ret )
goto err_free ;
}
return 0 ;
err_free :
dmaengine_pcm_free ( rtd - > pcm ) ;
return ret ;
}
static const struct snd_pcm_ops dmaengine_pcm_ops = {
. open = dmaengine_pcm_open ,
. close = snd_dmaengine_pcm_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = dmaengine_pcm_hw_params ,
. hw_free = snd_pcm_lib_free_pages ,
. trigger = snd_dmaengine_pcm_trigger ,
. pointer = snd_dmaengine_pcm_pointer ,
} ;
static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
. ops = & dmaengine_pcm_ops ,
. pcm_new = dmaengine_pcm_new ,
. pcm_free = dmaengine_pcm_free ,
2013-04-15 19:19:51 +02:00
. probe_order = SND_SOC_COMP_ORDER_LATE ,
2013-04-15 19:19:50 +02:00
} ;
2013-04-15 19:19:55 +02:00
static const struct snd_pcm_ops dmaengine_no_residue_pcm_ops = {
. open = dmaengine_pcm_open ,
. close = snd_dmaengine_pcm_close ,
. ioctl = snd_pcm_lib_ioctl ,
. hw_params = dmaengine_pcm_hw_params ,
. hw_free = snd_pcm_lib_free_pages ,
. trigger = snd_dmaengine_pcm_trigger ,
. pointer = snd_dmaengine_pcm_pointer_no_residue ,
} ;
static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform = {
. ops = & dmaengine_no_residue_pcm_ops ,
. pcm_new = dmaengine_pcm_new ,
. pcm_free = dmaengine_pcm_free ,
. probe_order = SND_SOC_COMP_ORDER_LATE ,
} ;
2013-04-15 19:19:50 +02:00
static const char * const dmaengine_pcm_dma_channel_names [ ] = {
[ SNDRV_PCM_STREAM_PLAYBACK ] = " tx " ,
[ SNDRV_PCM_STREAM_CAPTURE ] = " rx " ,
} ;
2013-04-20 19:29:00 +02:00
static void dmaengine_pcm_request_chan_of ( struct dmaengine_pcm * pcm ,
2013-04-22 21:57:24 +08:00
struct device * dev )
2013-04-20 19:29:00 +02:00
{
unsigned int i ;
2013-10-19 17:43:51 +01:00
if ( ( pcm - > flags & ( SND_DMAENGINE_PCM_FLAG_NO_DT |
SND_DMAENGINE_PCM_FLAG_CUSTOM_CHANNEL_NAME ) ) | |
! dev - > of_node )
2013-04-20 19:29:00 +02:00
return ;
if ( pcm - > flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX ) {
2013-04-22 21:57:24 +08:00
pcm - > chan [ 0 ] = dma_request_slave_channel ( dev , " rx-tx " ) ;
2013-04-20 19:29:00 +02:00
pcm - > chan [ 1 ] = pcm - > chan [ 0 ] ;
} else {
for ( i = SNDRV_PCM_STREAM_PLAYBACK ; i < = SNDRV_PCM_STREAM_CAPTURE ; i + + ) {
2013-04-22 21:57:24 +08:00
pcm - > chan [ i ] = dma_request_slave_channel ( dev ,
2013-04-20 19:29:00 +02:00
dmaengine_pcm_dma_channel_names [ i ] ) ;
}
}
}
2013-04-15 19:19:50 +02:00
/**
* snd_dmaengine_pcm_register - Register a dmaengine based PCM device
* @ dev : The parent device for the PCM device
* @ config : Platform specific PCM configuration
* @ flags : Platform specific quirks
*/
int snd_dmaengine_pcm_register ( struct device * dev ,
const struct snd_dmaengine_pcm_config * config , unsigned int flags )
{
struct dmaengine_pcm * pcm ;
pcm = kzalloc ( sizeof ( * pcm ) , GFP_KERNEL ) ;
if ( ! pcm )
return - ENOMEM ;
pcm - > config = config ;
2013-04-20 19:29:00 +02:00
pcm - > flags = flags ;
2013-04-15 19:19:50 +02:00
2013-04-22 21:57:24 +08:00
dmaengine_pcm_request_chan_of ( pcm , dev ) ;
2013-04-15 19:19:50 +02:00
2013-04-15 19:19:55 +02:00
if ( flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE )
return snd_soc_add_platform ( dev , & pcm - > platform ,
& dmaengine_no_residue_pcm_platform ) ;
else
return snd_soc_add_platform ( dev , & pcm - > platform ,
2013-04-15 19:19:50 +02:00
& dmaengine_pcm_platform ) ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_register ) ;
/**
* snd_dmaengine_pcm_unregister - Removes a dmaengine based PCM device
* @ dev : Parent device the PCM was register with
*
* Removes a dmaengine based PCM device previously registered with
* snd_dmaengine_pcm_register .
*/
void snd_dmaengine_pcm_unregister ( struct device * dev )
{
struct snd_soc_platform * platform ;
struct dmaengine_pcm * pcm ;
unsigned int i ;
platform = snd_soc_lookup_platform ( dev ) ;
if ( ! platform )
return ;
pcm = soc_platform_to_pcm ( platform ) ;
for ( i = SNDRV_PCM_STREAM_PLAYBACK ; i < = SNDRV_PCM_STREAM_CAPTURE ; i + + ) {
2013-04-20 19:29:00 +02:00
if ( pcm - > chan [ i ] ) {
2013-04-15 19:19:50 +02:00
dma_release_channel ( pcm - > chan [ i ] ) ;
2013-04-20 19:29:00 +02:00
if ( pcm - > flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX )
break ;
}
2013-04-15 19:19:50 +02:00
}
snd_soc_remove_platform ( platform ) ;
kfree ( pcm ) ;
}
EXPORT_SYMBOL_GPL ( snd_dmaengine_pcm_unregister ) ;
MODULE_LICENSE ( " GPL " ) ;