2023-03-01 17:32:54 +01:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for i . MX8M Plus Audio BLK_CTRL
*
* Copyright ( C ) 2022 Marek Vasut < marex @ denx . de >
*/
# include <linux/clk-provider.h>
# include <linux/device.h>
2024-03-21 21:14:02 +08:00
# include <linux/io.h>
2023-03-01 17:32:54 +01:00
# include <linux/mod_devicetable.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
2024-03-21 21:14:02 +08:00
# include <linux/pm_runtime.h>
2023-03-01 17:32:54 +01:00
# include <dt-bindings/clock/imx8mp-clock.h>
# include "clk.h"
# define CLKEN0 0x000
# define CLKEN1 0x004
2024-03-21 21:14:02 +08:00
# define EARC 0x200
2024-02-23 18:15:51 +08:00
# define SAI1_MCLK_SEL 0x300
# define SAI2_MCLK_SEL 0x304
# define SAI3_MCLK_SEL 0x308
# define SAI5_MCLK_SEL 0x30C
# define SAI6_MCLK_SEL 0x310
# define SAI7_MCLK_SEL 0x314
2023-03-01 17:32:54 +01:00
# define PDM_SEL 0x318
# define SAI_PLL_GNRL_CTL 0x400
2024-03-21 21:14:02 +08:00
# define SAI_PLL_FDIVL_CTL0 0x404
# define SAI_PLL_FDIVL_CTL1 0x408
# define SAI_PLL_SSCG_CTL 0x40C
# define SAI_PLL_MNIT_CTL 0x410
# define IPG_LP_CTRL 0x504
2023-03-01 17:32:54 +01:00
# define SAIn_MCLK1_PARENT(n) \
static const struct clk_parent_data \
clk_imx8mp_audiomix_sai # # n # # _mclk1_parents [ ] = { \
{ \
. fw_name = " sai " __stringify ( n ) , \
. name = " sai " __stringify ( n ) \
} , { \
. fw_name = " sai " __stringify ( n ) " _mclk " , \
. name = " sai " __stringify ( n ) " _mclk " \
} , \
}
SAIn_MCLK1_PARENT ( 1 ) ;
SAIn_MCLK1_PARENT ( 2 ) ;
SAIn_MCLK1_PARENT ( 3 ) ;
SAIn_MCLK1_PARENT ( 5 ) ;
SAIn_MCLK1_PARENT ( 6 ) ;
SAIn_MCLK1_PARENT ( 7 ) ;
static const struct clk_parent_data clk_imx8mp_audiomix_sai_mclk2_parents [ ] = {
{ . fw_name = " sai1 " , . name = " sai1 " } ,
{ . fw_name = " sai2 " , . name = " sai2 " } ,
{ . fw_name = " sai3 " , . name = " sai3 " } ,
{ . name = " dummy " } ,
{ . fw_name = " sai5 " , . name = " sai5 " } ,
{ . fw_name = " sai6 " , . name = " sai6 " } ,
{ . fw_name = " sai7 " , . name = " sai7 " } ,
{ . fw_name = " sai1_mclk " , . name = " sai1_mclk " } ,
{ . fw_name = " sai2_mclk " , . name = " sai2_mclk " } ,
{ . fw_name = " sai3_mclk " , . name = " sai3_mclk " } ,
{ . name = " dummy " } ,
{ . fw_name = " sai5_mclk " , . name = " sai5_mclk " } ,
{ . fw_name = " sai6_mclk " , . name = " sai6_mclk " } ,
{ . fw_name = " sai7_mclk " , . name = " sai7_mclk " } ,
{ . fw_name = " spdif_extclk " , . name = " spdif_extclk " } ,
{ . name = " dummy " } ,
} ;
static const struct clk_parent_data clk_imx8mp_audiomix_pdm_parents [ ] = {
{ . fw_name = " pdm " , . name = " pdm " } ,
{ . name = " sai_pll_out_div2 " } ,
{ . fw_name = " sai1_mclk " , . name = " sai1_mclk " } ,
{ . name = " dummy " } ,
} ;
static const struct clk_parent_data clk_imx8mp_audiomix_pll_parents [ ] = {
{ . fw_name = " osc_24m " , . name = " osc_24m " } ,
{ . name = " dummy " } ,
{ . name = " dummy " } ,
{ . name = " dummy " } ,
} ;
static const struct clk_parent_data clk_imx8mp_audiomix_pll_bypass_sels [ ] = {
{ . fw_name = " sai_pll " , . name = " sai_pll " } ,
{ . fw_name = " sai_pll_ref_sel " , . name = " sai_pll_ref_sel " } ,
} ;
# define CLK_GATE(gname, cname) \
{ \
gname " _cg " , \
IMX8MP_CLK_AUDIOMIX_ # # cname , \
{ . fw_name = " ahb " , . name = " ahb " } , NULL , 1 , \
CLKEN0 + 4 * ! ! ( IMX8MP_CLK_AUDIOMIX_ # # cname / 32 ) , \
1 , IMX8MP_CLK_AUDIOMIX_ # # cname % 32 \
}
# define CLK_SAIn(n) \
{ \
" sai " __stringify ( n ) " _mclk1_sel " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK1_SEL , { } , \
clk_imx8mp_audiomix_sai # # n # # _mclk1_parents , \
ARRAY_SIZE ( clk_imx8mp_audiomix_sai # # n # # _mclk1_parents ) , \
2024-02-23 18:15:51 +08:00
SAI # # n # # _MCLK_SEL , 1 , 0 \
2023-03-01 17:32:54 +01:00
} , { \
" sai " __stringify ( n ) " _mclk2_sel " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK2_SEL , { } , \
clk_imx8mp_audiomix_sai_mclk2_parents , \
ARRAY_SIZE ( clk_imx8mp_audiomix_sai_mclk2_parents ) , \
2024-02-23 18:15:51 +08:00
SAI # # n # # _MCLK_SEL , 4 , 1 \
2023-03-01 17:32:54 +01:00
} , { \
" sai " __stringify ( n ) " _ipg_cg " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _IPG , \
{ . fw_name = " ahb " , . name = " ahb " } , NULL , 1 , \
CLKEN0 , 1 , IMX8MP_CLK_AUDIOMIX_SAI # # n # # _IPG \
} , { \
" sai " __stringify ( n ) " _mclk1_cg " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK1 , \
{ \
. fw_name = " sai " __stringify ( n ) " _mclk1_sel " , \
. name = " sai " __stringify ( n ) " _mclk1_sel " \
} , NULL , 1 , \
CLKEN0 , 1 , IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK1 \
} , { \
" sai " __stringify ( n ) " _mclk2_cg " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK2 , \
{ \
. fw_name = " sai " __stringify ( n ) " _mclk2_sel " , \
. name = " sai " __stringify ( n ) " _mclk2_sel " \
} , NULL , 1 , \
CLKEN0 , 1 , IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK2 \
} , { \
" sai " __stringify ( n ) " _mclk3_cg " , \
IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK3 , \
{ \
. fw_name = " sai_pll_out_div2 " , \
. name = " sai_pll_out_div2 " \
} , NULL , 1 , \
CLKEN0 , 1 , IMX8MP_CLK_AUDIOMIX_SAI # # n # # _MCLK3 \
}
# define CLK_PDM \
{ \
" pdm_sel " , IMX8MP_CLK_AUDIOMIX_PDM_SEL , { } , \
clk_imx8mp_audiomix_pdm_parents , \
ARRAY_SIZE ( clk_imx8mp_audiomix_pdm_parents ) , \
PDM_SEL , 2 , 0 \
}
struct clk_imx8mp_audiomix_sel {
const char * name ;
int clkid ;
const struct clk_parent_data parent ; /* For gate */
const struct clk_parent_data * parents ; /* For mux */
int num_parents ;
u16 reg ;
u8 width ;
u8 shift ;
} ;
static struct clk_imx8mp_audiomix_sel sels [ ] = {
CLK_GATE ( " asrc " , ASRC_IPG ) ,
CLK_GATE ( " pdm " , PDM_IPG ) ,
CLK_GATE ( " earc " , EARC_IPG ) ,
CLK_GATE ( " ocrama " , OCRAMA_IPG ) ,
CLK_GATE ( " aud2htx " , AUD2HTX_IPG ) ,
CLK_GATE ( " earc_phy " , EARC_PHY ) ,
CLK_GATE ( " sdma2 " , SDMA2_ROOT ) ,
CLK_GATE ( " sdma3 " , SDMA3_ROOT ) ,
CLK_GATE ( " spba2 " , SPBA2_ROOT ) ,
CLK_GATE ( " dsp " , DSP_ROOT ) ,
CLK_GATE ( " dspdbg " , DSPDBG_ROOT ) ,
CLK_GATE ( " edma " , EDMA_ROOT ) ,
CLK_GATE ( " audpll " , AUDPLL_ROOT ) ,
CLK_GATE ( " mu2 " , MU2_ROOT ) ,
CLK_GATE ( " mu3 " , MU3_ROOT ) ,
CLK_PDM ,
CLK_SAIn ( 1 ) ,
CLK_SAIn ( 2 ) ,
CLK_SAIn ( 3 ) ,
CLK_SAIn ( 5 ) ,
CLK_SAIn ( 6 ) ,
CLK_SAIn ( 7 )
} ;
2024-03-21 21:14:02 +08:00
static const u16 audiomix_regs [ ] = {
CLKEN0 ,
CLKEN1 ,
EARC ,
SAI1_MCLK_SEL ,
SAI2_MCLK_SEL ,
SAI3_MCLK_SEL ,
SAI5_MCLK_SEL ,
SAI6_MCLK_SEL ,
SAI7_MCLK_SEL ,
PDM_SEL ,
SAI_PLL_GNRL_CTL ,
SAI_PLL_FDIVL_CTL0 ,
SAI_PLL_FDIVL_CTL1 ,
SAI_PLL_SSCG_CTL ,
SAI_PLL_MNIT_CTL ,
IPG_LP_CTRL ,
} ;
struct clk_imx8mp_audiomix_priv {
void __iomem * base ;
u32 regs_save [ ARRAY_SIZE ( audiomix_regs ) ] ;
/* Must be last */
struct clk_hw_onecell_data clk_data ;
} ;
static void clk_imx8mp_audiomix_save_restore ( struct device * dev , bool save )
{
struct clk_imx8mp_audiomix_priv * priv = dev_get_drvdata ( dev ) ;
void __iomem * base = priv - > base ;
int i ;
if ( save ) {
for ( i = 0 ; i < ARRAY_SIZE ( audiomix_regs ) ; i + + )
priv - > regs_save [ i ] = readl ( base + audiomix_regs [ i ] ) ;
} else {
for ( i = 0 ; i < ARRAY_SIZE ( audiomix_regs ) ; i + + )
writel ( priv - > regs_save [ i ] , base + audiomix_regs [ i ] ) ;
}
}
2023-03-01 17:32:54 +01:00
static int clk_imx8mp_audiomix_probe ( struct platform_device * pdev )
{
2024-03-21 21:14:02 +08:00
struct clk_imx8mp_audiomix_priv * priv ;
struct clk_hw_onecell_data * clk_hw_data ;
2023-03-01 17:32:54 +01:00
struct device * dev = & pdev - > dev ;
void __iomem * base ;
struct clk_hw * hw ;
2024-03-21 21:14:02 +08:00
int i , ret ;
2023-03-01 17:32:54 +01:00
priv = devm_kzalloc ( dev ,
2024-03-21 21:14:02 +08:00
struct_size ( priv , clk_data . hws , IMX8MP_CLK_AUDIOMIX_END ) ,
2023-03-01 17:32:54 +01:00
GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2024-03-21 21:14:02 +08:00
clk_hw_data = & priv - > clk_data ;
clk_hw_data - > num = IMX8MP_CLK_AUDIOMIX_END ;
2023-03-01 17:32:54 +01:00
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
2024-03-21 21:14:02 +08:00
priv - > base = base ;
dev_set_drvdata ( dev , priv ) ;
/*
* pm_runtime_enable needs to be called before clk register .
* That is to make core - > rpm_enabled to be true for clock
* usage .
*/
pm_runtime_get_noresume ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
2023-03-01 17:32:54 +01:00
for ( i = 0 ; i < ARRAY_SIZE ( sels ) ; i + + ) {
if ( sels [ i ] . num_parents = = 1 ) {
hw = devm_clk_hw_register_gate_parent_data ( dev ,
sels [ i ] . name , & sels [ i ] . parent , 0 ,
base + sels [ i ] . reg , sels [ i ] . shift , 0 , NULL ) ;
} else {
hw = devm_clk_hw_register_mux_parent_data_table ( dev ,
sels [ i ] . name , sels [ i ] . parents ,
sels [ i ] . num_parents , 0 ,
base + sels [ i ] . reg ,
sels [ i ] . shift , sels [ i ] . width ,
0 , NULL , NULL ) ;
}
2024-03-21 21:14:02 +08:00
if ( IS_ERR ( hw ) ) {
ret = PTR_ERR ( hw ) ;
goto err_clk_register ;
}
2023-03-01 17:32:54 +01:00
2024-03-21 21:14:02 +08:00
clk_hw_data - > hws [ sels [ i ] . clkid ] = hw ;
2023-03-01 17:32:54 +01:00
}
/* SAI PLL */
hw = devm_clk_hw_register_mux_parent_data_table ( dev ,
" sai_pll_ref_sel " , clk_imx8mp_audiomix_pll_parents ,
ARRAY_SIZE ( clk_imx8mp_audiomix_pll_parents ) ,
CLK_SET_RATE_NO_REPARENT , base + SAI_PLL_GNRL_CTL ,
0 , 2 , 0 , NULL , NULL ) ;
2024-03-21 21:14:02 +08:00
clk_hw_data - > hws [ IMX8MP_CLK_AUDIOMIX_SAI_PLL_REF_SEL ] = hw ;
2023-03-01 17:32:54 +01:00
hw = imx_dev_clk_hw_pll14xx ( dev , " sai_pll " , " sai_pll_ref_sel " ,
base + 0x400 , & imx_1443x_pll ) ;
2024-03-21 21:14:02 +08:00
if ( IS_ERR ( hw ) ) {
ret = PTR_ERR ( hw ) ;
goto err_clk_register ;
}
clk_hw_data - > hws [ IMX8MP_CLK_AUDIOMIX_SAI_PLL ] = hw ;
2023-03-01 17:32:54 +01:00
hw = devm_clk_hw_register_mux_parent_data_table ( dev ,
" sai_pll_bypass " , clk_imx8mp_audiomix_pll_bypass_sels ,
ARRAY_SIZE ( clk_imx8mp_audiomix_pll_bypass_sels ) ,
CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT ,
base + SAI_PLL_GNRL_CTL , 16 , 1 , 0 , NULL , NULL ) ;
2024-03-21 21:14:02 +08:00
if ( IS_ERR ( hw ) ) {
ret = PTR_ERR ( hw ) ;
goto err_clk_register ;
}
clk_hw_data - > hws [ IMX8MP_CLK_AUDIOMIX_SAI_PLL_BYPASS ] = hw ;
2023-03-01 17:32:54 +01:00
hw = devm_clk_hw_register_gate ( dev , " sai_pll_out " , " sai_pll_bypass " ,
0 , base + SAI_PLL_GNRL_CTL , 13 ,
0 , NULL ) ;
2024-03-21 21:14:02 +08:00
if ( IS_ERR ( hw ) ) {
ret = PTR_ERR ( hw ) ;
goto err_clk_register ;
}
clk_hw_data - > hws [ IMX8MP_CLK_AUDIOMIX_SAI_PLL_OUT ] = hw ;
2023-03-01 17:32:54 +01:00
hw = devm_clk_hw_register_fixed_factor ( dev , " sai_pll_out_div2 " ,
" sai_pll_out " , 0 , 1 , 2 ) ;
2024-03-21 21:14:02 +08:00
if ( IS_ERR ( hw ) ) {
ret = PTR_ERR ( hw ) ;
goto err_clk_register ;
}
ret = devm_of_clk_add_hw_provider ( & pdev - > dev , of_clk_hw_onecell_get ,
clk_hw_data ) ;
if ( ret )
goto err_clk_register ;
pm_runtime_put_sync ( dev ) ;
return 0 ;
err_clk_register :
pm_runtime_put_sync ( dev ) ;
pm_runtime_disable ( dev ) ;
return ret ;
}
2024-04-23 09:12:31 +02:00
static void clk_imx8mp_audiomix_remove ( struct platform_device * pdev )
2024-03-21 21:14:02 +08:00
{
pm_runtime_disable ( & pdev - > dev ) ;
}
static int clk_imx8mp_audiomix_runtime_suspend ( struct device * dev )
{
clk_imx8mp_audiomix_save_restore ( dev , true ) ;
2023-03-01 17:32:54 +01:00
2024-03-21 21:14:02 +08:00
return 0 ;
2023-03-01 17:32:54 +01:00
}
2024-03-21 21:14:02 +08:00
static int clk_imx8mp_audiomix_runtime_resume ( struct device * dev )
{
clk_imx8mp_audiomix_save_restore ( dev , false ) ;
return 0 ;
}
static const struct dev_pm_ops clk_imx8mp_audiomix_pm_ops = {
2024-04-29 18:45:02 -03:00
RUNTIME_PM_OPS ( clk_imx8mp_audiomix_runtime_suspend ,
clk_imx8mp_audiomix_runtime_resume , NULL )
2024-03-21 21:14:02 +08:00
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS ( pm_runtime_force_suspend ,
pm_runtime_force_resume )
} ;
2023-03-01 17:32:54 +01:00
static const struct of_device_id clk_imx8mp_audiomix_of_match [ ] = {
{ . compatible = " fsl,imx8mp-audio-blk-ctrl " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , clk_imx8mp_audiomix_of_match ) ;
static struct platform_driver clk_imx8mp_audiomix_driver = {
. probe = clk_imx8mp_audiomix_probe ,
2024-04-23 09:12:31 +02:00
. remove_new = clk_imx8mp_audiomix_remove ,
2023-03-01 17:32:54 +01:00
. driver = {
. name = " imx8mp-audio-blk-ctrl " ,
. of_match_table = clk_imx8mp_audiomix_of_match ,
2024-04-29 18:45:02 -03:00
. pm = pm_ptr ( & clk_imx8mp_audiomix_pm_ops ) ,
2023-03-01 17:32:54 +01:00
} ,
} ;
module_platform_driver ( clk_imx8mp_audiomix_driver ) ;
MODULE_AUTHOR ( " Marek Vasut <marex@denx.de> " ) ;
MODULE_DESCRIPTION ( " Freescale i.MX8MP Audio Block Controller driver " ) ;
MODULE_LICENSE ( " GPL " ) ;