2018-01-10 11:13:15 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of STM32 DFSDM ASoC DAI driver
*
* Copyright ( C ) 2017 , STMicroelectronics - All Rights Reserved
* Authors : Arnaud Pouliquen < arnaud . pouliquen @ st . com >
* Olivier Moysan < olivier . moysan @ st . com >
*/
# include <linux/clk.h>
# include <linux/module.h>
2019-03-04 15:52:43 +01:00
# include <linux/mutex.h>
2018-01-10 11:13:15 +01:00
# include <linux/platform_device.h>
# include <linux/slab.h>
2021-11-19 11:47:51 +01:00
# include <linux/pm_runtime.h>
2018-01-10 11:13:15 +01:00
# include <linux/iio/iio.h>
# include <linux/iio/consumer.h>
# include <linux/iio/adc/stm32-dfsdm-adc.h>
# include <sound/pcm.h>
# include <sound/soc.h>
# define STM32_ADFSDM_DRV_NAME "stm32-adfsdm"
# define DFSDM_MAX_PERIOD_SIZE (PAGE_SIZE / 2)
# define DFSDM_MAX_PERIODS 6
struct stm32_adfsdm_priv {
struct snd_soc_dai_driver dai_drv ;
struct snd_pcm_substream * substream ;
struct device * dev ;
/* IIO */
struct iio_channel * iio_ch ;
struct iio_cb_buffer * iio_cb ;
bool iio_active ;
/* PCM buffer */
unsigned char * pcm_buff ;
unsigned int pos ;
2019-03-04 15:52:43 +01:00
struct mutex lock ; /* protect against race condition on iio state */
2018-01-10 11:13:15 +01:00
} ;
static const struct snd_pcm_hardware stm32_adfsdm_pcm_hw = {
. info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
2019-03-04 15:52:45 +01:00
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE ,
2019-06-19 13:40:02 +02:00
. formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE ,
2018-01-10 11:13:15 +01:00
. channels_min = 1 ,
. channels_max = 1 ,
. periods_min = 2 ,
. periods_max = DFSDM_MAX_PERIODS ,
. period_bytes_max = DFSDM_MAX_PERIOD_SIZE ,
. buffer_bytes_max = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE
} ;
static void stm32_adfsdm_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct stm32_adfsdm_priv * priv = snd_soc_dai_get_drvdata ( dai ) ;
2019-03-04 15:52:43 +01:00
mutex_lock ( & priv - > lock ) ;
2018-01-10 11:13:15 +01:00
if ( priv - > iio_active ) {
iio_channel_stop_all_cb ( priv - > iio_cb ) ;
priv - > iio_active = false ;
}
2019-03-04 15:52:43 +01:00
mutex_unlock ( & priv - > lock ) ;
2018-01-10 11:13:15 +01:00
}
static int stm32_adfsdm_dai_prepare ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct stm32_adfsdm_priv * priv = snd_soc_dai_get_drvdata ( dai ) ;
int ret ;
2019-03-04 15:52:43 +01:00
mutex_lock ( & priv - > lock ) ;
if ( priv - > iio_active ) {
iio_channel_stop_all_cb ( priv - > iio_cb ) ;
priv - > iio_active = false ;
}
2018-01-10 11:13:15 +01:00
ret = iio_write_channel_attribute ( priv - > iio_ch ,
substream - > runtime - > rate , 0 ,
IIO_CHAN_INFO_SAMP_FREQ ) ;
if ( ret < 0 ) {
dev_err ( dai - > dev , " %s: Failed to set %d sampling rate \n " ,
__func__ , substream - > runtime - > rate ) ;
2019-03-04 15:52:43 +01:00
goto out ;
2018-01-10 11:13:15 +01:00
}
if ( ! priv - > iio_active ) {
ret = iio_channel_start_all_cb ( priv - > iio_cb ) ;
if ( ! ret )
priv - > iio_active = true ;
else
dev_err ( dai - > dev , " %s: IIO channel start failed (%d) \n " ,
__func__ , ret ) ;
}
2019-03-04 15:52:43 +01:00
out :
mutex_unlock ( & priv - > lock ) ;
2018-01-10 11:13:15 +01:00
return ret ;
}
static int stm32_adfsdm_set_sysclk ( struct snd_soc_dai * dai , int clk_id ,
unsigned int freq , int dir )
{
struct stm32_adfsdm_priv * priv = snd_soc_dai_get_drvdata ( dai ) ;
ssize_t size ;
char str_freq [ 10 ] ;
dev_dbg ( dai - > dev , " %s: Enter for freq %d \n " , __func__ , freq ) ;
/* Set IIO frequency if CODEC is master as clock comes from SPI_IN */
2021-03-26 16:59:20 -05:00
snprintf ( str_freq , sizeof ( str_freq ) , " %u \n " , freq ) ;
2018-01-10 11:13:15 +01:00
size = iio_write_channel_ext_info ( priv - > iio_ch , " spi_clk_freq " ,
str_freq , sizeof ( str_freq ) ) ;
if ( size ! = sizeof ( str_freq ) ) {
dev_err ( dai - > dev , " %s: Failed to set SPI clock \n " ,
__func__ ) ;
return - EINVAL ;
}
return 0 ;
}
static const struct snd_soc_dai_ops stm32_adfsdm_dai_ops = {
. shutdown = stm32_adfsdm_shutdown ,
. prepare = stm32_adfsdm_dai_prepare ,
. set_sysclk = stm32_adfsdm_set_sysclk ,
} ;
static const struct snd_soc_dai_driver stm32_adfsdm_dai = {
. capture = {
. channels_min = 1 ,
. channels_max = 1 ,
2019-06-19 13:40:02 +02:00
. formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE ,
2020-10-07 17:34:58 +02:00
. rates = SNDRV_PCM_RATE_CONTINUOUS ,
. rate_min = 8000 ,
. rate_max = 48000 ,
2018-01-10 11:13:15 +01:00
} ,
. ops = & stm32_adfsdm_dai_ops ,
} ;
static const struct snd_soc_component_driver stm32_adfsdm_dai_component = {
. name = " stm32_dfsdm_audio " ,
2022-06-23 13:51:22 +01:00
. legacy_dai_naming = 1 ,
2018-01-10 11:13:15 +01:00
} ;
2020-01-10 14:11:31 +01:00
static void stm32_memcpy_32to16 ( void * dest , const void * src , size_t n )
2019-06-19 13:40:02 +02:00
{
unsigned int i = 0 ;
u16 * d = ( u16 * ) dest , * s = ( u16 * ) src ;
s + + ;
2020-01-10 14:11:31 +01:00
for ( i = n > > 1 ; i > 0 ; i - - ) {
2019-06-19 13:40:02 +02:00
* d + + = * s + + ;
s + + ;
}
}
2018-01-10 11:13:15 +01:00
static int stm32_afsdm_pcm_cb ( const void * data , size_t size , void * private )
{
struct stm32_adfsdm_priv * priv = private ;
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( priv - > substream ) ;
2018-01-10 11:13:15 +01:00
u8 * pcm_buff = priv - > pcm_buff ;
u8 * src_buff = ( u8 * ) data ;
unsigned int old_pos = priv - > pos ;
2019-06-19 13:40:02 +02:00
size_t buff_size = snd_pcm_lib_buffer_bytes ( priv - > substream ) ;
size_t period_size = snd_pcm_lib_period_bytes ( priv - > substream ) ;
size_t cur_size , src_size = size ;
snd_pcm_format_t format = priv - > substream - > runtime - > format ;
if ( format = = SNDRV_PCM_FORMAT_S16_LE )
src_size > > = 1 ;
cur_size = src_size ;
2018-01-10 11:13:15 +01:00
2018-07-06 15:07:03 +02:00
dev_dbg ( rtd - > dev , " %s: buff_add :%pK, pos = %d, size = %zu \n " ,
2019-06-19 13:40:02 +02:00
__func__ , & pcm_buff [ priv - > pos ] , priv - > pos , src_size ) ;
2018-01-10 11:13:15 +01:00
2019-06-19 13:40:02 +02:00
if ( ( priv - > pos + src_size ) > buff_size ) {
if ( format = = SNDRV_PCM_FORMAT_S16_LE )
2020-01-10 14:11:31 +01:00
stm32_memcpy_32to16 ( & pcm_buff [ priv - > pos ] , src_buff ,
buff_size - priv - > pos ) ;
2019-06-19 13:40:02 +02:00
else
memcpy ( & pcm_buff [ priv - > pos ] , src_buff ,
buff_size - priv - > pos ) ;
2018-01-10 11:13:15 +01:00
cur_size - = buff_size - priv - > pos ;
priv - > pos = 0 ;
}
2019-06-19 13:40:02 +02:00
if ( format = = SNDRV_PCM_FORMAT_S16_LE )
2020-01-10 14:11:31 +01:00
stm32_memcpy_32to16 ( & pcm_buff [ priv - > pos ] ,
& src_buff [ src_size - cur_size ] , cur_size ) ;
2019-06-19 13:40:02 +02:00
else
memcpy ( & pcm_buff [ priv - > pos ] , & src_buff [ src_size - cur_size ] ,
cur_size ) ;
2018-01-10 11:13:15 +01:00
priv - > pos = ( priv - > pos + cur_size ) % buff_size ;
2019-06-19 13:40:02 +02:00
if ( cur_size ! = src_size | | ( old_pos & & ( old_pos % period_size < size ) ) )
2018-01-10 11:13:15 +01:00
snd_pcm_period_elapsed ( priv - > substream ) ;
return 0 ;
}
2019-10-02 14:32:46 +09:00
static int stm32_adfsdm_trigger ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream , int cmd )
2018-01-10 11:13:15 +01:00
{
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2018-01-10 11:13:15 +01:00
struct stm32_adfsdm_priv * priv =
2020-03-23 14:20:52 +09:00
snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
priv - > pos = 0 ;
return stm32_dfsdm_get_buff_cb ( priv - > iio_ch - > indio_dev ,
stm32_afsdm_pcm_cb , priv ) ;
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_STOP :
return stm32_dfsdm_release_buff_cb ( priv - > iio_ch - > indio_dev ) ;
}
return - EINVAL ;
}
2019-10-02 14:32:46 +09:00
static int stm32_adfsdm_pcm_open ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2018-01-10 11:13:15 +01:00
{
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2020-03-23 14:20:52 +09:00
struct stm32_adfsdm_priv * priv = snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
int ret ;
ret = snd_soc_set_runtime_hwparams ( substream , & stm32_adfsdm_pcm_hw ) ;
if ( ! ret )
priv - > substream = substream ;
return ret ;
}
2019-10-02 14:32:46 +09:00
static int stm32_adfsdm_pcm_close ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream )
2018-01-10 11:13:15 +01:00
{
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2018-01-10 11:13:15 +01:00
struct stm32_adfsdm_priv * priv =
2020-03-23 14:20:52 +09:00
snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
priv - > substream = NULL ;
return 0 ;
}
static snd_pcm_uframes_t stm32_adfsdm_pcm_pointer (
2019-10-02 14:32:46 +09:00
struct snd_soc_component * component ,
2018-01-10 11:13:15 +01:00
struct snd_pcm_substream * substream )
{
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2018-01-10 11:13:15 +01:00
struct stm32_adfsdm_priv * priv =
2020-03-23 14:20:52 +09:00
snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
return bytes_to_frames ( substream - > runtime , priv - > pos ) ;
}
2019-10-02 14:32:46 +09:00
static int stm32_adfsdm_pcm_hw_params ( struct snd_soc_component * component ,
struct snd_pcm_substream * substream ,
2018-01-10 11:13:15 +01:00
struct snd_pcm_hw_params * params )
{
2020-07-20 10:18:01 +09:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2018-01-10 11:13:15 +01:00
struct stm32_adfsdm_priv * priv =
2020-03-23 14:20:52 +09:00
snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
priv - > pcm_buff = substream - > runtime - > dma_area ;
return iio_channel_cb_set_buffer_watermark ( priv - > iio_cb ,
params_period_size ( params ) ) ;
}
2019-10-02 14:32:46 +09:00
static int stm32_adfsdm_pcm_new ( struct snd_soc_component * component ,
struct snd_soc_pcm_runtime * rtd )
2018-01-10 11:13:15 +01:00
{
struct snd_pcm * pcm = rtd - > pcm ;
struct stm32_adfsdm_priv * priv =
2020-03-23 14:20:52 +09:00
snd_soc_dai_get_drvdata ( asoc_rtd_to_cpu ( rtd , 0 ) ) ;
2018-01-10 11:13:15 +01:00
unsigned int size = DFSDM_MAX_PERIODS * DFSDM_MAX_PERIOD_SIZE ;
2019-12-10 15:26:03 +01:00
snd_pcm_set_managed_buffer_all ( pcm , SNDRV_DMA_TYPE_DEV ,
priv - > dev , size , size ) ;
2019-02-04 16:40:22 +01:00
return 0 ;
2018-01-10 11:13:15 +01:00
}
2020-11-21 17:14:56 +01:00
static int stm32_adfsdm_dummy_cb ( const void * data , void * private )
{
/*
2022-05-21 13:11:26 +02:00
* This dummy callback is requested by iio_channel_get_all_cb ( ) API ,
2020-11-21 17:14:56 +01:00
* but the stm32_dfsdm_get_buff_cb ( ) API is used instead , to optimize
* DMA transfers .
*/
return 0 ;
}
2022-11-09 18:08:49 +01:00
static void stm32_adfsdm_cleanup ( void * data )
{
iio_channel_release_all_cb ( data ) ;
}
2018-01-29 02:51:58 +00:00
static struct snd_soc_component_driver stm32_adfsdm_soc_platform = {
2019-10-02 14:32:46 +09:00
. open = stm32_adfsdm_pcm_open ,
. close = stm32_adfsdm_pcm_close ,
. hw_params = stm32_adfsdm_pcm_hw_params ,
. trigger = stm32_adfsdm_trigger ,
. pointer = stm32_adfsdm_pcm_pointer ,
. pcm_construct = stm32_adfsdm_pcm_new ,
2018-01-10 11:13:15 +01:00
} ;
static const struct of_device_id stm32_adfsdm_of_match [ ] = {
{ . compatible = " st,stm32h7-dfsdm-dai " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , stm32_adfsdm_of_match ) ;
static int stm32_adfsdm_probe ( struct platform_device * pdev )
{
struct stm32_adfsdm_priv * priv ;
2019-03-04 15:52:44 +01:00
struct snd_soc_component * component ;
2018-01-10 11:13:15 +01:00
int ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > dev = & pdev - > dev ;
priv - > dai_drv = stm32_adfsdm_dai ;
2019-03-04 15:52:43 +01:00
mutex_init ( & priv - > lock ) ;
2018-01-10 11:13:15 +01:00
dev_set_drvdata ( & pdev - > dev , priv ) ;
ret = devm_snd_soc_register_component ( & pdev - > dev ,
& stm32_adfsdm_dai_component ,
& priv - > dai_drv , 1 ) ;
if ( ret < 0 )
return ret ;
/* Associate iio channel */
priv - > iio_ch = devm_iio_channel_get_all ( & pdev - > dev ) ;
if ( IS_ERR ( priv - > iio_ch ) )
return PTR_ERR ( priv - > iio_ch ) ;
2020-11-21 17:14:56 +01:00
priv - > iio_cb = iio_channel_get_all_cb ( & pdev - > dev , & stm32_adfsdm_dummy_cb , NULL ) ;
2018-01-10 11:13:15 +01:00
if ( IS_ERR ( priv - > iio_cb ) )
2018-01-11 11:11:51 +00:00
return PTR_ERR ( priv - > iio_cb ) ;
2018-01-10 11:13:15 +01:00
2022-11-09 18:08:49 +01:00
ret = devm_add_action_or_reset ( & pdev - > dev , stm32_adfsdm_cleanup , priv - > iio_cb ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " Unable to add action \n " ) ;
return ret ;
}
2019-03-04 15:52:44 +01:00
component = devm_kzalloc ( & pdev - > dev , sizeof ( * component ) , GFP_KERNEL ) ;
if ( ! component )
return - ENOMEM ;
2020-07-31 16:41:46 +02:00
ret = snd_soc_component_initialize ( component ,
& stm32_adfsdm_soc_platform ,
& pdev - > dev ) ;
if ( ret < 0 )
return ret ;
2019-03-04 15:52:44 +01:00
# ifdef CONFIG_DEBUG_FS
component - > debugfs_prefix = " pcm " ;
# endif
2020-07-31 16:41:46 +02:00
ret = snd_soc_add_component ( component , NULL , 0 ) ;
2022-09-27 22:26:00 +08:00
if ( ret < 0 ) {
2018-01-10 11:13:15 +01:00
dev_err ( & pdev - > dev , " %s: Failed to register PCM platform \n " ,
__func__ ) ;
2022-09-27 22:26:00 +08:00
return ret ;
}
pm_runtime_enable ( & pdev - > dev ) ;
2018-01-10 11:13:15 +01:00
return ret ;
}
2019-03-04 15:52:44 +01:00
static int stm32_adfsdm_remove ( struct platform_device * pdev )
{
snd_soc_unregister_component ( & pdev - > dev ) ;
2021-11-19 11:47:51 +01:00
pm_runtime_disable ( & pdev - > dev ) ;
2019-03-04 15:52:44 +01:00
return 0 ;
}
2018-01-10 11:13:15 +01:00
static struct platform_driver stm32_adfsdm_driver = {
. driver = {
. name = STM32_ADFSDM_DRV_NAME ,
. of_match_table = stm32_adfsdm_of_match ,
} ,
. probe = stm32_adfsdm_probe ,
2019-03-04 15:52:44 +01:00
. remove = stm32_adfsdm_remove ,
2018-01-10 11:13:15 +01:00
} ;
module_platform_driver ( stm32_adfsdm_driver ) ;
MODULE_DESCRIPTION ( " stm32 DFSDM DAI driver " ) ;
MODULE_AUTHOR ( " Arnaud Pouliquen <arnaud.pouliquen@st.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " STM32_ADFSDM_DRV_NAME ) ;