2016-06-09 12:47:05 +01:00
/*
* ALSA SoC Synopsys PIO PCM for I2S driver
*
* sound / soc / dwc / designware_pcm . c
*
* Copyright ( C ) 2016 Synopsys
* Jose Abreu < joabreu @ synopsys . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/io.h>
# include <linux/rcupdate.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include "local.h"
# define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN)
# define PERIOD_BYTES_MIN 4096
# define PERIODS_MIN 2
# define dw_pcm_tx_fn(sample_bits) \
static unsigned int dw_pcm_tx_ # # sample_bits ( struct dw_i2s_dev * dev , \
struct snd_pcm_runtime * runtime , unsigned int tx_ptr , \
bool * period_elapsed ) \
{ \
const u # # sample_bits ( * p ) [ 2 ] = ( void * ) runtime - > dma_area ; \
unsigned int period_pos = tx_ptr % runtime - > period_size ; \
int i ; \
\
for ( i = 0 ; i < dev - > fifo_th ; i + + ) { \
iowrite32 ( p [ tx_ptr ] [ 0 ] , dev - > i2s_base + LRBR_LTHR ( 0 ) ) ; \
iowrite32 ( p [ tx_ptr ] [ 1 ] , dev - > i2s_base + RRBR_RTHR ( 0 ) ) ; \
period_pos + + ; \
if ( + + tx_ptr > = runtime - > buffer_size ) \
tx_ptr = 0 ; \
} \
* period_elapsed = period_pos > = runtime - > period_size ; \
return tx_ptr ; \
}
2016-12-27 14:00:53 +00:00
# define dw_pcm_rx_fn(sample_bits) \
static unsigned int dw_pcm_rx_ # # sample_bits ( struct dw_i2s_dev * dev , \
struct snd_pcm_runtime * runtime , unsigned int rx_ptr , \
bool * period_elapsed ) \
{ \
u # # sample_bits ( * p ) [ 2 ] = ( void * ) runtime - > dma_area ; \
unsigned int period_pos = rx_ptr % runtime - > period_size ; \
int i ; \
\
for ( i = 0 ; i < dev - > fifo_th ; i + + ) { \
p [ rx_ptr ] [ 0 ] = ioread32 ( dev - > i2s_base + LRBR_LTHR ( 0 ) ) ; \
p [ rx_ptr ] [ 1 ] = ioread32 ( dev - > i2s_base + RRBR_RTHR ( 0 ) ) ; \
period_pos + + ; \
if ( + + rx_ptr > = runtime - > buffer_size ) \
rx_ptr = 0 ; \
} \
* period_elapsed = period_pos > = runtime - > period_size ; \
return rx_ptr ; \
}
2016-06-09 12:47:05 +01:00
dw_pcm_tx_fn ( 16 ) ;
dw_pcm_tx_fn ( 32 ) ;
2016-12-27 14:00:53 +00:00
dw_pcm_rx_fn ( 16 ) ;
dw_pcm_rx_fn ( 32 ) ;
2016-06-09 12:47:05 +01:00
# undef dw_pcm_tx_fn
2016-12-27 14:00:53 +00:00
# undef dw_pcm_rx_fn
2016-06-09 12:47:05 +01:00
static const struct snd_pcm_hardware dw_pcm_hardware = {
. info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BLOCK_TRANSFER ,
. rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 ,
. rate_min = 32000 ,
. rate_max = 48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE |
2016-12-27 14:00:54 +00:00
SNDRV_PCM_FMTBIT_S24_LE |
2016-06-09 12:47:05 +01:00
SNDRV_PCM_FMTBIT_S32_LE ,
. channels_min = 2 ,
. channels_max = 2 ,
. buffer_bytes_max = BUFFER_BYTES_MAX ,
. period_bytes_min = PERIOD_BYTES_MIN ,
. period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN ,
. periods_min = PERIODS_MIN ,
. periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN ,
. fifo_size = 16 ,
} ;
2016-12-27 14:00:53 +00:00
static void dw_pcm_transfer ( struct dw_i2s_dev * dev , bool push )
2016-06-09 12:47:05 +01:00
{
2016-12-27 14:00:53 +00:00
struct snd_pcm_substream * substream ;
bool active , period_elapsed ;
2016-06-09 12:47:05 +01:00
rcu_read_lock ( ) ;
2016-12-27 14:00:53 +00:00
if ( push )
substream = rcu_dereference ( dev - > tx_substream ) ;
else
substream = rcu_dereference ( dev - > rx_substream ) ;
active = substream & & snd_pcm_running ( substream ) ;
if ( active ) {
unsigned int ptr ;
unsigned int new_ptr ;
if ( push ) {
ptr = READ_ONCE ( dev - > tx_ptr ) ;
new_ptr = dev - > tx_fn ( dev , substream - > runtime , ptr ,
& period_elapsed ) ;
cmpxchg ( & dev - > tx_ptr , ptr , new_ptr ) ;
} else {
ptr = READ_ONCE ( dev - > rx_ptr ) ;
new_ptr = dev - > rx_fn ( dev , substream - > runtime , ptr ,
& period_elapsed ) ;
cmpxchg ( & dev - > rx_ptr , ptr , new_ptr ) ;
}
2016-06-09 12:47:05 +01:00
if ( period_elapsed )
2016-12-27 14:00:53 +00:00
snd_pcm_period_elapsed ( substream ) ;
2016-06-09 12:47:05 +01:00
}
rcu_read_unlock ( ) ;
}
2016-12-27 14:00:53 +00:00
void dw_pcm_push_tx ( struct dw_i2s_dev * dev )
{
dw_pcm_transfer ( dev , true ) ;
}
2016-06-09 12:47:05 +01:00
2016-12-27 14:00:53 +00:00
void dw_pcm_pop_rx ( struct dw_i2s_dev * dev )
{
dw_pcm_transfer ( dev , false ) ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_open ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2016-06-09 12:47:05 +01:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct dw_i2s_dev * dev = snd_soc_dai_get_drvdata ( rtd - > cpu_dai ) ;
snd_soc_set_runtime_hwparams ( substream , & dw_pcm_hardware ) ;
snd_pcm_hw_constraint_integer ( runtime , SNDRV_PCM_HW_PARAM_PERIODS ) ;
runtime - > private_data = dev ;
return 0 ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_close ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2016-06-09 12:47:05 +01:00
{
synchronize_rcu ( ) ;
return 0 ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_hw_params ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * hw_params )
2016-06-09 12:47:05 +01:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct dw_i2s_dev * dev = runtime - > private_data ;
int ret ;
switch ( params_channels ( hw_params ) ) {
case 2 :
break ;
default :
dev_err ( dev - > dev , " invalid channels number \n " ) ;
return - EINVAL ;
}
switch ( params_format ( hw_params ) ) {
case SNDRV_PCM_FORMAT_S16_LE :
dev - > tx_fn = dw_pcm_tx_16 ;
2016-12-27 14:00:53 +00:00
dev - > rx_fn = dw_pcm_rx_16 ;
2016-06-09 12:47:05 +01:00
break ;
2016-12-27 14:00:54 +00:00
case SNDRV_PCM_FORMAT_S24_LE :
2016-06-09 12:47:05 +01:00
case SNDRV_PCM_FORMAT_S32_LE :
dev - > tx_fn = dw_pcm_tx_32 ;
2016-12-27 14:00:53 +00:00
dev - > rx_fn = dw_pcm_rx_32 ;
2016-06-09 12:47:05 +01:00
break ;
default :
dev_err ( dev - > dev , " invalid format \n " ) ;
return - EINVAL ;
}
ret = snd_pcm_lib_malloc_pages ( substream ,
params_buffer_bytes ( hw_params ) ) ;
if ( ret < 0 )
return ret ;
else
return 0 ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_hw_free ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2016-06-09 12:47:05 +01:00
{
return snd_pcm_lib_free_pages ( substream ) ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_trigger ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream , int cmd )
2016-06-09 12:47:05 +01:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct dw_i2s_dev * dev = runtime - > private_data ;
int ret = 0 ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
2016-12-27 14:00:53 +00:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
WRITE_ONCE ( dev - > tx_ptr , 0 ) ;
rcu_assign_pointer ( dev - > tx_substream , substream ) ;
} else {
WRITE_ONCE ( dev - > rx_ptr , 0 ) ;
rcu_assign_pointer ( dev - > rx_substream , substream ) ;
}
2016-06-09 12:47:05 +01:00
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
2016-12-27 14:00:53 +00:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
rcu_assign_pointer ( dev - > tx_substream , NULL ) ;
else
rcu_assign_pointer ( dev - > rx_substream , NULL ) ;
2016-06-09 12:47:05 +01:00
break ;
default :
ret = - EINVAL ;
break ;
}
return ret ;
}
2019-10-02 14:34:15 +09:00
static snd_pcm_uframes_t dw_pcm_pointer ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2016-06-09 12:47:05 +01:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
struct dw_i2s_dev * dev = runtime - > private_data ;
2016-12-27 14:00:53 +00:00
snd_pcm_uframes_t pos ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
pos = READ_ONCE ( dev - > tx_ptr ) ;
else
pos = READ_ONCE ( dev - > rx_ptr ) ;
2016-06-09 12:47:05 +01:00
return pos < runtime - > buffer_size ? pos : 0 ;
}
2019-10-02 14:34:15 +09:00
static int dw_pcm_new ( struct snd_soc_component * component ,
struct snd_soc_pcm_runtime * rtd )
2016-06-09 12:47:05 +01:00
{
size_t size = dw_pcm_hardware . buffer_bytes_max ;
2019-02-04 16:38:20 +01:00
snd_pcm_lib_preallocate_pages_for_all ( rtd - > pcm ,
2016-06-09 12:47:05 +01:00
SNDRV_DMA_TYPE_CONTINUOUS ,
2019-11-08 10:46:34 +01:00
NULL , size , size ) ;
2019-02-04 16:38:20 +01:00
return 0 ;
2016-06-09 12:47:05 +01:00
}
2019-10-02 14:34:15 +09:00
static void dw_pcm_free ( struct snd_soc_component * component ,
struct snd_pcm * pcm )
2016-06-09 12:47:05 +01:00
{
snd_pcm_lib_preallocate_free_for_all ( pcm ) ;
}
2018-01-29 02:47:38 +00:00
static const struct snd_soc_component_driver dw_pcm_component = {
2019-10-02 14:34:15 +09:00
. open = dw_pcm_open ,
. close = dw_pcm_close ,
. ioctl = snd_soc_pcm_lib_ioctl ,
. hw_params = dw_pcm_hw_params ,
. hw_free = dw_pcm_hw_free ,
. trigger = dw_pcm_trigger ,
. pointer = dw_pcm_pointer ,
. pcm_construct = dw_pcm_new ,
. pcm_destruct = dw_pcm_free ,
2016-06-09 12:47:05 +01:00
} ;
int dw_pcm_register ( struct platform_device * pdev )
{
2018-01-29 02:47:38 +00:00
return devm_snd_soc_register_component ( & pdev - > dev , & dw_pcm_component ,
NULL , 0 ) ;
2016-06-09 12:47:05 +01:00
}