2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-04-10 17:19:56 +02:00
/*
* STM32 ALSA SoC Digital Audio Interface ( SAI ) driver .
*
* Copyright ( C ) 2016 , STMicroelectronics - All Rights Reserved
* Author ( s ) : Olivier Moysan < olivier . moysan @ st . com > for STMicroelectronics .
*/
2017-10-19 15:03:23 +02:00
# include <linux/bitfield.h>
2017-04-10 17:19:56 +02:00
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/of_platform.h>
2019-03-21 16:34:56 +01:00
# include <linux/pinctrl/consumer.h>
2017-04-10 17:19:56 +02:00
# include <linux/reset.h>
# include <sound/dmaengine_pcm.h>
# include <sound/core.h>
# include "stm32_sai.h"
2017-06-16 14:16:24 +02:00
static const struct stm32_sai_conf stm32_sai_conf_f4 = {
2019-06-03 10:16:34 +02:00
. version = STM_SAI_STM32F4 ,
. fifo_size = 8 ,
. has_spdif_pdm = false ,
2017-06-16 14:16:24 +02:00
} ;
2019-06-03 10:16:34 +02:00
/*
* Default settings for stm32 H7 socs and next .
* These default settings will be overridden if the soc provides
* support of hardware configuration registers .
*/
2017-06-16 14:16:24 +02:00
static const struct stm32_sai_conf stm32_sai_conf_h7 = {
2019-06-03 10:16:34 +02:00
. version = STM_SAI_STM32H7 ,
. fifo_size = 8 ,
. has_spdif_pdm = true ,
2017-06-16 14:16:24 +02:00
} ;
2017-04-10 17:19:56 +02:00
static const struct of_device_id stm32_sai_ids [ ] = {
2017-06-16 14:16:24 +02:00
{ . compatible = " st,stm32f4-sai " , . data = ( void * ) & stm32_sai_conf_f4 } ,
{ . compatible = " st,stm32h7-sai " , . data = ( void * ) & stm32_sai_conf_h7 } ,
2017-04-10 17:19:56 +02:00
{ }
} ;
2019-03-21 16:34:56 +01:00
static int stm32_sai_pclk_disable ( struct device * dev )
{
struct stm32_sai_data * sai = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( sai - > pclk ) ;
return 0 ;
}
static int stm32_sai_pclk_enable ( struct device * dev )
2017-10-19 15:03:23 +02:00
{
2019-03-21 16:34:56 +01:00
struct stm32_sai_data * sai = dev_get_drvdata ( dev ) ;
2017-10-19 15:03:23 +02:00
int ret ;
ret = clk_prepare_enable ( sai - > pclk ) ;
if ( ret ) {
dev_err ( & sai - > pdev - > dev , " failed to enable clock: %d \n " , ret ) ;
return ret ;
}
2019-03-21 16:34:56 +01:00
return 0 ;
}
static int stm32_sai_sync_conf_client ( struct stm32_sai_data * sai , int synci )
{
int ret ;
/* Enable peripheral clock to allow GCR register access */
ret = stm32_sai_pclk_enable ( & sai - > pdev - > dev ) ;
if ( ret )
return ret ;
2017-10-19 15:03:23 +02:00
writel_relaxed ( FIELD_PREP ( SAI_GCR_SYNCIN_MASK , ( synci - 1 ) ) , sai - > base ) ;
2019-03-21 16:34:56 +01:00
stm32_sai_pclk_disable ( & sai - > pdev - > dev ) ;
2017-10-19 15:03:23 +02:00
return 0 ;
}
2017-11-22 16:02:26 +01:00
static int stm32_sai_sync_conf_provider ( struct stm32_sai_data * sai , int synco )
2017-10-19 15:03:23 +02:00
{
u32 prev_synco ;
int ret ;
/* Enable peripheral clock to allow GCR register access */
2019-03-21 16:34:56 +01:00
ret = stm32_sai_pclk_enable ( & sai - > pdev - > dev ) ;
if ( ret )
2017-10-19 15:03:23 +02:00
return ret ;
2018-11-16 15:43:49 -06:00
dev_dbg ( & sai - > pdev - > dev , " Set %pOFn%s as synchro provider \n " ,
sai - > pdev - > dev . of_node ,
2017-10-19 15:03:23 +02:00
synco = = STM_SAI_SYNC_OUT_A ? " A " : " B " ) ;
prev_synco = FIELD_GET ( SAI_GCR_SYNCOUT_MASK , readl_relaxed ( sai - > base ) ) ;
if ( prev_synco ! = STM_SAI_SYNC_OUT_NONE & & synco ! = prev_synco ) {
2018-11-16 15:43:49 -06:00
dev_err ( & sai - > pdev - > dev , " %pOFn%s already set as sync provider \n " ,
sai - > pdev - > dev . of_node ,
2017-10-19 15:03:23 +02:00
prev_synco = = STM_SAI_SYNC_OUT_A ? " A " : " B " ) ;
2019-09-25 12:26:21 +01:00
stm32_sai_pclk_disable ( & sai - > pdev - > dev ) ;
2017-10-19 15:03:23 +02:00
return - EINVAL ;
}
writel_relaxed ( FIELD_PREP ( SAI_GCR_SYNCOUT_MASK , synco ) , sai - > base ) ;
2019-03-21 16:34:56 +01:00
stm32_sai_pclk_disable ( & sai - > pdev - > dev ) ;
2017-10-19 15:03:23 +02:00
return 0 ;
}
2017-11-22 16:02:26 +01:00
static int stm32_sai_set_sync ( struct stm32_sai_data * sai_client ,
struct device_node * np_provider ,
int synco , int synci )
2017-10-19 15:03:23 +02:00
{
2017-11-22 16:02:26 +01:00
struct platform_device * pdev = of_find_device_by_node ( np_provider ) ;
struct stm32_sai_data * sai_provider ;
2017-10-19 15:03:23 +02:00
int ret ;
2017-11-22 16:02:26 +01:00
if ( ! pdev ) {
dev_err ( & sai_client - > pdev - > dev ,
2018-08-28 10:44:28 -05:00
" Device not found for node %pOFn \n " , np_provider ) ;
2019-02-28 14:19:25 +01:00
of_node_put ( np_provider ) ;
2017-11-22 16:02:26 +01:00
return - ENODEV ;
2017-10-19 15:03:23 +02:00
}
2017-11-22 16:02:26 +01:00
sai_provider = platform_get_drvdata ( pdev ) ;
if ( ! sai_provider ) {
dev_err ( & sai_client - > pdev - > dev ,
" SAI sync provider data not found \n " ) ;
2019-02-09 10:41:09 +00:00
ret = - EINVAL ;
2019-02-28 14:19:25 +01:00
goto error ;
2017-11-22 16:02:26 +01:00
}
2017-10-19 15:03:23 +02:00
/* Configure sync client */
2017-11-22 16:02:26 +01:00
ret = stm32_sai_sync_conf_client ( sai_client , synci ) ;
if ( ret < 0 )
2019-02-28 14:19:25 +01:00
goto error ;
2017-10-19 15:03:23 +02:00
/* Configure sync provider */
2019-02-09 10:41:09 +00:00
ret = stm32_sai_sync_conf_provider ( sai_provider , synco ) ;
2019-02-28 14:19:25 +01:00
error :
2019-02-09 10:41:09 +00:00
put_device ( & pdev - > dev ) ;
2019-02-28 14:19:25 +01:00
of_node_put ( np_provider ) ;
2019-02-09 10:41:09 +00:00
return ret ;
2017-10-19 15:03:23 +02:00
}
2017-04-10 17:19:56 +02:00
static int stm32_sai_probe ( struct platform_device * pdev )
{
struct stm32_sai_data * sai ;
struct reset_control * rst ;
const struct of_device_id * of_id ;
2019-06-03 10:16:34 +02:00
u32 val ;
int ret ;
2017-04-10 17:19:56 +02:00
sai = devm_kzalloc ( & pdev - > dev , sizeof ( * sai ) , GFP_KERNEL ) ;
if ( ! sai )
return - ENOMEM ;
2019-07-27 23:07:19 +08:00
sai - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-10-19 15:03:23 +02:00
if ( IS_ERR ( sai - > base ) )
return PTR_ERR ( sai - > base ) ;
2017-04-10 17:19:56 +02:00
of_id = of_match_device ( stm32_sai_ids , & pdev - > dev ) ;
if ( of_id )
2019-06-03 10:16:34 +02:00
memcpy ( & sai - > conf , ( const struct stm32_sai_conf * ) of_id - > data ,
sizeof ( struct stm32_sai_conf ) ) ;
2017-04-10 17:19:56 +02:00
else
return - EINVAL ;
2017-10-19 15:03:23 +02:00
if ( ! STM_SAI_IS_F4 ( sai ) ) {
sai - > pclk = devm_clk_get ( & pdev - > dev , " pclk " ) ;
2021-12-14 11:08:42 +09:00
if ( IS_ERR ( sai - > pclk ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( sai - > pclk ) ,
" missing bus clock pclk \n " ) ;
2017-10-19 15:03:23 +02:00
}
2017-04-10 17:19:56 +02:00
sai - > clk_x8k = devm_clk_get ( & pdev - > dev , " x8k " ) ;
2021-12-14 11:08:42 +09:00
if ( IS_ERR ( sai - > clk_x8k ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( sai - > clk_x8k ) ,
" missing x8k parent clock \n " ) ;
2017-04-10 17:19:56 +02:00
sai - > clk_x11k = devm_clk_get ( & pdev - > dev , " x11k " ) ;
2021-12-14 11:08:42 +09:00
if ( IS_ERR ( sai - > clk_x11k ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( sai - > clk_x11k ) ,
" missing x11k parent clock \n " ) ;
2017-04-10 17:19:56 +02:00
/* init irqs */
sai - > irq = platform_get_irq ( pdev , 0 ) ;
2019-07-30 11:15:49 -07:00
if ( sai - > irq < 0 )
2017-04-10 17:19:56 +02:00
return sai - > irq ;
/* reset */
2020-02-03 11:08:09 +01:00
rst = devm_reset_control_get_optional_exclusive ( & pdev - > dev , NULL ) ;
2021-12-14 11:08:42 +09:00
if ( IS_ERR ( rst ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( rst ) ,
" Reset controller error \n " ) ;
2020-02-03 11:08:09 +01:00
reset_control_assert ( rst ) ;
udelay ( 2 ) ;
reset_control_deassert ( rst ) ;
2017-04-10 17:19:56 +02:00
2019-06-03 10:16:34 +02:00
/* Enable peripheral clock to allow register access */
ret = clk_prepare_enable ( sai - > pclk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to enable clock: %d \n " , ret ) ;
return ret ;
}
val = FIELD_GET ( SAI_IDR_ID_MASK ,
readl_relaxed ( sai - > base + STM_SAI_IDR ) ) ;
if ( val = = SAI_IPIDR_NUMBER ) {
val = readl_relaxed ( sai - > base + STM_SAI_HWCFGR ) ;
sai - > conf . fifo_size = FIELD_GET ( SAI_HWCFGR_FIFO_SIZE , val ) ;
sai - > conf . has_spdif_pdm = ! ! FIELD_GET ( SAI_HWCFGR_SPDIF_PDM ,
val ) ;
val = readl_relaxed ( sai - > base + STM_SAI_VERR ) ;
sai - > conf . version = val ;
dev_dbg ( & pdev - > dev , " SAI version: %lu.%lu registered \n " ,
FIELD_GET ( SAI_VERR_MAJ_MASK , val ) ,
FIELD_GET ( SAI_VERR_MIN_MASK , val ) ) ;
}
clk_disable_unprepare ( sai - > pclk ) ;
2017-04-10 17:19:56 +02:00
sai - > pdev = pdev ;
2017-11-22 16:02:26 +01:00
sai - > set_sync = & stm32_sai_set_sync ;
2017-04-10 17:19:56 +02:00
platform_set_drvdata ( pdev , sai ) ;
2017-11-22 16:02:27 +01:00
return devm_of_platform_populate ( & pdev - > dev ) ;
2017-04-10 17:19:56 +02:00
}
2019-03-21 16:34:56 +01:00
# ifdef CONFIG_PM_SLEEP
/*
* When pins are shared by two sai sub instances , pins have to be defined
* in sai parent node . In this case , pins state is not managed by alsa fw .
* These pins are managed in suspend / resume callbacks .
*/
static int stm32_sai_suspend ( struct device * dev )
{
struct stm32_sai_data * sai = dev_get_drvdata ( dev ) ;
int ret ;
ret = stm32_sai_pclk_enable ( dev ) ;
if ( ret )
return ret ;
sai - > gcr = readl_relaxed ( sai - > base ) ;
stm32_sai_pclk_disable ( dev ) ;
return pinctrl_pm_select_sleep_state ( dev ) ;
}
static int stm32_sai_resume ( struct device * dev )
{
struct stm32_sai_data * sai = dev_get_drvdata ( dev ) ;
int ret ;
ret = stm32_sai_pclk_enable ( dev ) ;
if ( ret )
return ret ;
writel_relaxed ( sai - > gcr , sai - > base ) ;
stm32_sai_pclk_disable ( dev ) ;
return pinctrl_pm_select_default_state ( dev ) ;
}
# endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops stm32_sai_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( stm32_sai_suspend , stm32_sai_resume )
} ;
2017-04-10 17:19:56 +02:00
MODULE_DEVICE_TABLE ( of , stm32_sai_ids ) ;
static struct platform_driver stm32_sai_driver = {
. driver = {
. name = " st,stm32-sai " ,
. of_match_table = stm32_sai_ids ,
2019-03-21 16:34:56 +01:00
. pm = & stm32_sai_pm_ops ,
2017-04-10 17:19:56 +02:00
} ,
. probe = stm32_sai_probe ,
} ;
module_platform_driver ( stm32_sai_driver ) ;
MODULE_DESCRIPTION ( " STM32 Soc SAI Interface " ) ;
2017-06-16 14:15:30 +02:00
MODULE_AUTHOR ( " Olivier Moysan <olivier.moysan@st.com> " ) ;
2017-04-10 17:19:56 +02:00
MODULE_ALIAS ( " platform:st,stm32-sai " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;