2011-06-09 17:45:53 +04:00
/*
* soc - pcm . c - - ALSA SoC PCM
*
* Copyright 2005 Wolfson Microelectronics PLC .
* Copyright 2005 Openedhand Ltd .
* Copyright ( C ) 2010 Slimlogic Ltd .
* Copyright ( C ) 2010 Texas Instruments Inc .
*
* Authors : Liam Girdwood < lrg @ ti . com >
* Mark Brown < broonie @ opensource . wolfsonmicro . 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 .
*
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/delay.h>
2011-12-04 00:14:31 +04:00
# include <linux/pm_runtime.h>
2011-06-09 17:45:53 +04:00
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/initval.h>
2011-08-29 13:15:14 +04:00
static int soc_pcm_apply_symmetry ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * soc_dai )
2011-06-09 17:45:53 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
int ret ;
2011-08-29 13:15:14 +04:00
if ( ! soc_dai - > driver - > symmetric_rates & &
2011-06-09 17:45:53 +04:00
! rtd - > dai_link - > symmetric_rates )
return 0 ;
/* This can happen if multiple streams are starting simultaneously -
* the second can need to get its constraints before the first has
* picked a rate . Complain and allow the application to carry on .
*/
2011-08-29 13:15:14 +04:00
if ( ! soc_dai - > rate ) {
dev_warn ( soc_dai - > dev ,
2011-06-09 17:45:53 +04:00
" Not enforcing symmetric_rates due to race \n " ) ;
return 0 ;
}
2011-08-29 13:15:14 +04:00
dev_dbg ( soc_dai - > dev , " Symmetry forces %dHz rate \n " , soc_dai - > rate ) ;
2011-06-09 17:45:53 +04:00
ret = snd_pcm_hw_constraint_minmax ( substream - > runtime ,
SNDRV_PCM_HW_PARAM_RATE ,
2011-08-29 13:15:14 +04:00
soc_dai - > rate , soc_dai - > rate ) ;
2011-06-09 17:45:53 +04:00
if ( ret < 0 ) {
2011-08-29 13:15:14 +04:00
dev_err ( soc_dai - > dev ,
2011-06-09 17:45:53 +04:00
" Unable to apply rate symmetry constraint: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
2012-01-16 22:38:51 +04:00
/*
* List of sample sizes that might go over the bus for parameter
* application . There ought to be a wildcard sample size for things
* like the DAC / ADC resolution to use but there isn ' t right now .
*/
static int sample_sizes [ ] = {
2012-01-25 12:09:41 +04:00
24 , 32 ,
2012-01-16 22:38:51 +04:00
} ;
static void soc_pcm_apply_msb ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
int ret , i , bits ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
bits = dai - > driver - > playback . sig_bits ;
else
bits = dai - > driver - > capture . sig_bits ;
if ( ! bits )
return ;
for ( i = 0 ; i < ARRAY_SIZE ( sample_sizes ) ; i + + ) {
2012-01-19 22:04:18 +04:00
if ( bits > = sample_sizes [ i ] )
continue ;
ret = snd_pcm_hw_constraint_msbits ( substream - > runtime , 0 ,
sample_sizes [ i ] , bits ) ;
2012-01-16 22:38:51 +04:00
if ( ret ! = 0 )
dev_warn ( dai - > dev ,
" Failed to set MSB %d/%d: %d \n " ,
bits , sample_sizes [ i ] , ret ) ;
}
}
2011-06-09 17:45:53 +04:00
/*
* Called by ALSA when a PCM substream is opened , the runtime - > hw record is
* then initialized and any private data can be allocated . This also calls
* startup for the cpu DAI , platform , machine and codec DAI .
*/
static int soc_pcm_open ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_dai_driver * cpu_dai_drv = cpu_dai - > driver ;
struct snd_soc_dai_driver * codec_dai_drv = codec_dai - > driver ;
int ret = 0 ;
2011-12-04 00:14:31 +04:00
pm_runtime_get_sync ( cpu_dai - > dev ) ;
pm_runtime_get_sync ( codec_dai - > dev ) ;
pm_runtime_get_sync ( platform - > dev ) ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
/* startup the audio subsystem */
if ( cpu_dai - > driver - > ops - > startup ) {
ret = cpu_dai - > driver - > ops - > startup ( substream , cpu_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( cpu_dai - > dev , " can't open interface %s: %d \n " ,
cpu_dai - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
if ( platform - > driver - > ops & & platform - > driver - > ops - > open ) {
ret = platform - > driver - > ops - > open ( substream ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( platform - > dev , " can't open platform %s: %d \n " ,
platform - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto platform_err ;
}
}
if ( codec_dai - > driver - > ops - > startup ) {
ret = codec_dai - > driver - > ops - > startup ( substream , codec_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( codec_dai - > dev , " can't open codec %s: %d \n " ,
codec_dai - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto codec_dai_err ;
}
}
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > startup ) {
ret = rtd - > dai_link - > ops - > startup ( substream ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
pr_err ( " asoc: %s startup failed: %d \n " ,
rtd - > dai_link - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto machine_err ;
}
}
/* Check that the codec and cpu DAIs are compatible */
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
runtime - > hw . rate_min =
max ( codec_dai_drv - > playback . rate_min ,
cpu_dai_drv - > playback . rate_min ) ;
runtime - > hw . rate_max =
min ( codec_dai_drv - > playback . rate_max ,
cpu_dai_drv - > playback . rate_max ) ;
runtime - > hw . channels_min =
max ( codec_dai_drv - > playback . channels_min ,
cpu_dai_drv - > playback . channels_min ) ;
runtime - > hw . channels_max =
min ( codec_dai_drv - > playback . channels_max ,
cpu_dai_drv - > playback . channels_max ) ;
runtime - > hw . formats =
codec_dai_drv - > playback . formats & cpu_dai_drv - > playback . formats ;
runtime - > hw . rates =
codec_dai_drv - > playback . rates & cpu_dai_drv - > playback . rates ;
if ( codec_dai_drv - > playback . rates
& ( SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS ) )
runtime - > hw . rates | = cpu_dai_drv - > playback . rates ;
if ( cpu_dai_drv - > playback . rates
& ( SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS ) )
runtime - > hw . rates | = codec_dai_drv - > playback . rates ;
} else {
runtime - > hw . rate_min =
max ( codec_dai_drv - > capture . rate_min ,
cpu_dai_drv - > capture . rate_min ) ;
runtime - > hw . rate_max =
min ( codec_dai_drv - > capture . rate_max ,
cpu_dai_drv - > capture . rate_max ) ;
runtime - > hw . channels_min =
max ( codec_dai_drv - > capture . channels_min ,
cpu_dai_drv - > capture . channels_min ) ;
runtime - > hw . channels_max =
min ( codec_dai_drv - > capture . channels_max ,
cpu_dai_drv - > capture . channels_max ) ;
runtime - > hw . formats =
codec_dai_drv - > capture . formats & cpu_dai_drv - > capture . formats ;
runtime - > hw . rates =
codec_dai_drv - > capture . rates & cpu_dai_drv - > capture . rates ;
if ( codec_dai_drv - > capture . rates
& ( SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS ) )
runtime - > hw . rates | = cpu_dai_drv - > capture . rates ;
if ( cpu_dai_drv - > capture . rates
& ( SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS ) )
runtime - > hw . rates | = codec_dai_drv - > capture . rates ;
}
ret = - EINVAL ;
snd_pcm_limit_hw_rates ( runtime ) ;
if ( ! runtime - > hw . rates ) {
printk ( KERN_ERR " asoc: %s <-> %s No matching rates \n " ,
codec_dai - > name , cpu_dai - > name ) ;
goto config_err ;
}
if ( ! runtime - > hw . formats ) {
printk ( KERN_ERR " asoc: %s <-> %s No matching formats \n " ,
codec_dai - > name , cpu_dai - > name ) ;
goto config_err ;
}
if ( ! runtime - > hw . channels_min | | ! runtime - > hw . channels_max | |
runtime - > hw . channels_min > runtime - > hw . channels_max ) {
printk ( KERN_ERR " asoc: %s <-> %s No matching channels \n " ,
codec_dai - > name , cpu_dai - > name ) ;
goto config_err ;
}
2012-01-16 22:38:51 +04:00
soc_pcm_apply_msb ( substream , codec_dai ) ;
soc_pcm_apply_msb ( substream , cpu_dai ) ;
2011-06-09 17:45:53 +04:00
/* Symmetry only applies if we've already got an active stream. */
2011-08-29 13:15:14 +04:00
if ( cpu_dai - > active ) {
ret = soc_pcm_apply_symmetry ( substream , cpu_dai ) ;
if ( ret ! = 0 )
goto config_err ;
}
if ( codec_dai - > active ) {
ret = soc_pcm_apply_symmetry ( substream , codec_dai ) ;
2011-06-09 17:45:53 +04:00
if ( ret ! = 0 )
goto config_err ;
}
pr_debug ( " asoc: %s <-> %s info: \n " ,
codec_dai - > name , cpu_dai - > name ) ;
pr_debug ( " asoc: rate mask 0x%x \n " , runtime - > hw . rates ) ;
pr_debug ( " asoc: min ch %d max ch %d \n " , runtime - > hw . channels_min ,
runtime - > hw . channels_max ) ;
pr_debug ( " asoc: min rate %d max rate %d \n " , runtime - > hw . rate_min ,
runtime - > hw . rate_max ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
cpu_dai - > playback_active + + ;
codec_dai - > playback_active + + ;
} else {
cpu_dai - > capture_active + + ;
codec_dai - > capture_active + + ;
}
cpu_dai - > active + + ;
codec_dai - > active + + ;
rtd - > codec - > active + + ;
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
return 0 ;
config_err :
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > shutdown )
rtd - > dai_link - > ops - > shutdown ( substream ) ;
machine_err :
if ( codec_dai - > driver - > ops - > shutdown )
codec_dai - > driver - > ops - > shutdown ( substream , codec_dai ) ;
codec_dai_err :
if ( platform - > driver - > ops & & platform - > driver - > ops - > close )
platform - > driver - > ops - > close ( substream ) ;
platform_err :
if ( cpu_dai - > driver - > ops - > shutdown )
cpu_dai - > driver - > ops - > shutdown ( substream , cpu_dai ) ;
out :
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-12-04 00:14:31 +04:00
pm_runtime_put ( platform - > dev ) ;
pm_runtime_put ( codec_dai - > dev ) ;
pm_runtime_put ( cpu_dai - > dev ) ;
2011-06-09 17:45:53 +04:00
return ret ;
}
/*
* Power down the audio subsystem pmdown_time msecs after close is called .
* This is to ensure there are no pops or clicks in between any music tracks
* due to DAPM power cycling .
*/
static void close_delayed_work ( struct work_struct * work )
{
struct snd_soc_pcm_runtime * rtd =
container_of ( work , struct snd_soc_pcm_runtime , delayed_work . work ) ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
pr_debug ( " pop wq checking: %s status: %s waiting: %s \n " ,
codec_dai - > driver - > playback . stream_name ,
codec_dai - > playback_active ? " active " : " inactive " ,
codec_dai - > pop_wait ? " yes " : " no " ) ;
/* are we waiting on this codec DAI stream */
if ( codec_dai - > pop_wait = = 1 ) {
codec_dai - > pop_wait = 0 ;
2012-02-17 03:03:27 +04:00
snd_soc_dapm_stream_event ( rtd , SNDRV_PCM_STREAM_PLAYBACK ,
codec_dai , SND_SOC_DAPM_STREAM_STOP ) ;
2011-06-09 17:45:53 +04:00
}
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
}
/*
* Called by ALSA when a PCM substream is closed . Private data can be
* freed here . The cpu DAI , codec DAI , machine and platform are also
* shutdown .
*/
2011-06-09 20:04:59 +04:00
static int soc_pcm_close ( struct snd_pcm_substream * substream )
2011-06-09 17:45:53 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_codec * codec = rtd - > codec ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
cpu_dai - > playback_active - - ;
codec_dai - > playback_active - - ;
} else {
cpu_dai - > capture_active - - ;
codec_dai - > capture_active - - ;
}
cpu_dai - > active - - ;
codec_dai - > active - - ;
codec - > active - - ;
2011-08-29 13:15:14 +04:00
/* clear the corresponding DAIs rate when inactive */
if ( ! cpu_dai - > active )
cpu_dai - > rate = 0 ;
if ( ! codec_dai - > active )
codec_dai - > rate = 0 ;
2011-08-17 11:20:01 +04:00
2011-06-09 17:45:53 +04:00
/* Muting the DAC suppresses artifacts caused during digital
* shutdown , for example from stopping clocks .
*/
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
snd_soc_dai_digital_mute ( codec_dai , 1 ) ;
if ( cpu_dai - > driver - > ops - > shutdown )
cpu_dai - > driver - > ops - > shutdown ( substream , cpu_dai ) ;
if ( codec_dai - > driver - > ops - > shutdown )
codec_dai - > driver - > ops - > shutdown ( substream , codec_dai ) ;
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > shutdown )
rtd - > dai_link - > ops - > shutdown ( substream ) ;
if ( platform - > driver - > ops & & platform - > driver - > ops - > close )
platform - > driver - > ops - > close ( substream ) ;
cpu_dai - > runtime = NULL ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2012-02-09 00:10:56 +04:00
if ( ! rtd - > pmdown_time | | codec - > ignore_pmdown_time | |
2011-10-27 14:11:26 +04:00
rtd - > dai_link - > ignore_pmdown_time ) {
2011-10-14 15:43:33 +04:00
/* powered down playback stream now */
snd_soc_dapm_stream_event ( rtd ,
2012-02-17 03:03:27 +04:00
SNDRV_PCM_STREAM_PLAYBACK ,
codec_dai ,
SND_SOC_DAPM_STREAM_STOP ) ;
2011-10-14 15:43:33 +04:00
} else {
/* start delayed pop wq here for playback streams */
codec_dai - > pop_wait = 1 ;
schedule_delayed_work ( & rtd - > delayed_work ,
msecs_to_jiffies ( rtd - > pmdown_time ) ) ;
}
2011-06-09 17:45:53 +04:00
} else {
/* capture streams can be powered down now */
2012-02-17 03:03:27 +04:00
snd_soc_dapm_stream_event ( rtd , SNDRV_PCM_STREAM_CAPTURE ,
codec_dai , SND_SOC_DAPM_STREAM_STOP ) ;
2011-06-09 17:45:53 +04:00
}
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-12-04 00:14:31 +04:00
pm_runtime_put ( platform - > dev ) ;
pm_runtime_put ( codec_dai - > dev ) ;
pm_runtime_put ( cpu_dai - > dev ) ;
2011-06-09 17:45:53 +04:00
return 0 ;
}
/*
* Called by ALSA when the PCM substream is prepared , can set format , sample
* rate , etc . This function is non atomic and can be called multiple times ,
* it can refer to the runtime info .
*/
static int soc_pcm_prepare ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
int ret = 0 ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > prepare ) {
ret = rtd - > dai_link - > ops - > prepare ( substream ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
pr_err ( " asoc: machine prepare error: %d \n " , ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
if ( platform - > driver - > ops & & platform - > driver - > ops - > prepare ) {
ret = platform - > driver - > ops - > prepare ( substream ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( platform - > dev , " platform prepare error: %d \n " ,
ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
if ( codec_dai - > driver - > ops - > prepare ) {
ret = codec_dai - > driver - > ops - > prepare ( substream , codec_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( codec_dai - > dev , " DAI prepare error: %d \n " ,
ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
if ( cpu_dai - > driver - > ops - > prepare ) {
ret = cpu_dai - > driver - > ops - > prepare ( substream , cpu_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( cpu_dai - > dev , " DAI prepare error: %d \n " ,
ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
/* cancel any delayed stream shutdown that is pending */
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK & &
codec_dai - > pop_wait ) {
codec_dai - > pop_wait = 0 ;
cancel_delayed_work ( & rtd - > delayed_work ) ;
}
2012-02-17 03:03:27 +04:00
snd_soc_dapm_stream_event ( rtd , substream - > stream , codec_dai ,
SND_SOC_DAPM_STREAM_START ) ;
2011-06-09 17:45:53 +04:00
snd_soc_dai_digital_mute ( codec_dai , 0 ) ;
out :
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
return ret ;
}
/*
* Called by ALSA when the hardware params are set by application . This
* function can also be called multiple times and can allocate buffers
* ( using snd_pcm_lib_ * ) . It ' s non - atomic .
*/
static int soc_pcm_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
int ret = 0 ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > hw_params ) {
ret = rtd - > dai_link - > ops - > hw_params ( substream , params ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
pr_err ( " asoc: machine hw_params failed: %d \n " , ret ) ;
2011-06-09 17:45:53 +04:00
goto out ;
}
}
if ( codec_dai - > driver - > ops - > hw_params ) {
ret = codec_dai - > driver - > ops - > hw_params ( substream , params , codec_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( codec_dai - > dev , " can't set %s hw params: %d \n " ,
codec_dai - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto codec_err ;
}
}
if ( cpu_dai - > driver - > ops - > hw_params ) {
ret = cpu_dai - > driver - > ops - > hw_params ( substream , params , cpu_dai ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( cpu_dai - > dev , " %s hw params failed: %d \n " ,
cpu_dai - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto interface_err ;
}
}
if ( platform - > driver - > ops & & platform - > driver - > ops - > hw_params ) {
ret = platform - > driver - > ops - > hw_params ( substream , params ) ;
if ( ret < 0 ) {
2012-02-02 01:30:32 +04:00
dev_err ( platform - > dev , " %s hw params failed: %d \n " ,
platform - > name , ret ) ;
2011-06-09 17:45:53 +04:00
goto platform_err ;
}
}
2011-08-29 13:15:14 +04:00
/* store the rate for each DAIs */
cpu_dai - > rate = params_rate ( params ) ;
codec_dai - > rate = params_rate ( params ) ;
2011-06-09 17:45:53 +04:00
out :
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
return ret ;
platform_err :
if ( cpu_dai - > driver - > ops - > hw_free )
cpu_dai - > driver - > ops - > hw_free ( substream , cpu_dai ) ;
interface_err :
if ( codec_dai - > driver - > ops - > hw_free )
codec_dai - > driver - > ops - > hw_free ( substream , codec_dai ) ;
codec_err :
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > hw_free )
rtd - > dai_link - > ops - > hw_free ( substream ) ;
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
return ret ;
}
/*
* Frees resources allocated by hw_params , can be called multiple times
*/
static int soc_pcm_hw_free ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_codec * codec = rtd - > codec ;
2011-06-09 20:04:39 +04:00
mutex_lock_nested ( & rtd - > pcm_mutex , rtd - > pcm_subclass ) ;
2011-06-09 17:45:53 +04:00
/* apply codec digital mute */
if ( ! codec - > active )
snd_soc_dai_digital_mute ( codec_dai , 1 ) ;
/* free any machine hw params */
if ( rtd - > dai_link - > ops & & rtd - > dai_link - > ops - > hw_free )
rtd - > dai_link - > ops - > hw_free ( substream ) ;
/* free any DMA resources */
if ( platform - > driver - > ops & & platform - > driver - > ops - > hw_free )
platform - > driver - > ops - > hw_free ( substream ) ;
/* now free hw params for the DAIs */
if ( codec_dai - > driver - > ops - > hw_free )
codec_dai - > driver - > ops - > hw_free ( substream , codec_dai ) ;
if ( cpu_dai - > driver - > ops - > hw_free )
cpu_dai - > driver - > ops - > hw_free ( substream , cpu_dai ) ;
2011-06-09 20:04:39 +04:00
mutex_unlock ( & rtd - > pcm_mutex ) ;
2011-06-09 17:45:53 +04:00
return 0 ;
}
static int soc_pcm_trigger ( struct snd_pcm_substream * substream , int cmd )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
int ret ;
if ( codec_dai - > driver - > ops - > trigger ) {
ret = codec_dai - > driver - > ops - > trigger ( substream , cmd , codec_dai ) ;
if ( ret < 0 )
return ret ;
}
if ( platform - > driver - > ops & & platform - > driver - > ops - > trigger ) {
ret = platform - > driver - > ops - > trigger ( substream , cmd ) ;
if ( ret < 0 )
return ret ;
}
if ( cpu_dai - > driver - > ops - > trigger ) {
ret = cpu_dai - > driver - > ops - > trigger ( substream , cmd , cpu_dai ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
/*
* soc level wrapper for pointer callback
* If cpu_dai , codec_dai , platform driver has the delay callback , than
* the runtime - > delay will be updated accordingly .
*/
static snd_pcm_uframes_t soc_pcm_pointer ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_pcm_runtime * runtime = substream - > runtime ;
snd_pcm_uframes_t offset = 0 ;
snd_pcm_sframes_t delay = 0 ;
if ( platform - > driver - > ops & & platform - > driver - > ops - > pointer )
offset = platform - > driver - > ops - > pointer ( substream ) ;
if ( cpu_dai - > driver - > ops - > delay )
delay + = cpu_dai - > driver - > ops - > delay ( substream , cpu_dai ) ;
if ( codec_dai - > driver - > ops - > delay )
delay + = codec_dai - > driver - > ops - > delay ( substream , codec_dai ) ;
if ( platform - > driver - > delay )
delay + = platform - > driver - > delay ( substream , codec_dai ) ;
runtime - > delay = delay ;
return offset ;
}
/* create a new pcm */
int soc_new_pcm ( struct snd_soc_pcm_runtime * rtd , int num )
{
struct snd_soc_codec * codec = rtd - > codec ;
struct snd_soc_platform * platform = rtd - > platform ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
2012-01-02 12:15:10 +04:00
struct snd_pcm_ops * soc_pcm_ops = & rtd - > ops ;
2011-06-09 17:45:53 +04:00
struct snd_pcm * pcm ;
char new_name [ 64 ] ;
int ret = 0 , playback = 0 , capture = 0 ;
2012-01-02 12:15:10 +04:00
soc_pcm_ops - > open = soc_pcm_open ;
soc_pcm_ops - > close = soc_pcm_close ;
soc_pcm_ops - > hw_params = soc_pcm_hw_params ;
soc_pcm_ops - > hw_free = soc_pcm_hw_free ;
soc_pcm_ops - > prepare = soc_pcm_prepare ;
soc_pcm_ops - > trigger = soc_pcm_trigger ;
soc_pcm_ops - > pointer = soc_pcm_pointer ;
2011-06-09 17:45:53 +04:00
/* check client and interface hw capabilities */
snprintf ( new_name , sizeof ( new_name ) , " %s %s-%d " ,
rtd - > dai_link - > stream_name , codec_dai - > name , num ) ;
if ( codec_dai - > driver - > playback . channels_min )
playback = 1 ;
if ( codec_dai - > driver - > capture . channels_min )
capture = 1 ;
dev_dbg ( rtd - > card - > dev , " registered pcm #%d %s \n " , num , new_name ) ;
ret = snd_pcm_new ( rtd - > card - > snd_card , new_name ,
num , playback , capture , & pcm ) ;
if ( ret < 0 ) {
printk ( KERN_ERR " asoc: can't create pcm for codec %s \n " , codec - > name ) ;
return ret ;
}
/* DAPM dai link stream work */
INIT_DELAYED_WORK ( & rtd - > delayed_work , close_delayed_work ) ;
rtd - > pcm = pcm ;
pcm - > private_data = rtd ;
if ( platform - > driver - > ops ) {
2012-01-02 12:15:10 +04:00
soc_pcm_ops - > mmap = platform - > driver - > ops - > mmap ;
soc_pcm_ops - > pointer = platform - > driver - > ops - > pointer ;
soc_pcm_ops - > ioctl = platform - > driver - > ops - > ioctl ;
soc_pcm_ops - > copy = platform - > driver - > ops - > copy ;
soc_pcm_ops - > silence = platform - > driver - > ops - > silence ;
soc_pcm_ops - > ack = platform - > driver - > ops - > ack ;
soc_pcm_ops - > page = platform - > driver - > ops - > page ;
2011-06-09 17:45:53 +04:00
}
if ( playback )
2012-01-02 12:15:10 +04:00
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_PLAYBACK , soc_pcm_ops ) ;
2011-06-09 17:45:53 +04:00
if ( capture )
2012-01-02 12:15:10 +04:00
snd_pcm_set_ops ( pcm , SNDRV_PCM_STREAM_CAPTURE , soc_pcm_ops ) ;
2011-06-09 17:45:53 +04:00
if ( platform - > driver - > pcm_new ) {
ret = platform - > driver - > pcm_new ( rtd ) ;
if ( ret < 0 ) {
pr_err ( " asoc: platform pcm constructor failed \n " ) ;
return ret ;
}
}
pcm - > private_free = platform - > driver - > pcm_free ;
printk ( KERN_INFO " asoc: %s <-> %s mapping ok \n " , codec_dai - > name ,
cpu_dai - > name ) ;
return ret ;
}