2013-06-17 19:02:17 +04:00
/*
* Copyright ( c ) 2013 Samsung Electronics Co . , Ltd .
* Author : Padmavathi Venna < padma . v @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* Common Clock Framework support for Audio Subsystem Clock Controller .
*/
2015-06-20 01:00:46 +03:00
# include <linux/slab.h>
2013-06-17 19:02:17 +04:00
# include <linux/io.h>
2015-06-20 01:00:46 +03:00
# include <linux/clk.h>
2013-06-17 19:02:17 +04:00
# include <linux/clk-provider.h>
# include <linux/of_address.h>
2016-09-02 19:23:47 +03:00
# include <linux/of_device.h>
2013-06-17 19:02:17 +04:00
# include <linux/syscore_ops.h>
2013-09-26 01:12:47 +04:00
# include <linux/module.h>
# include <linux/platform_device.h>
2013-06-17 19:02:17 +04:00
2014-03-20 23:31:30 +04:00
# include <dt-bindings/clock/exynos-audss-clk.h>
2013-06-17 19:02:17 +04:00
static DEFINE_SPINLOCK ( lock ) ;
static void __iomem * reg_base ;
2017-05-15 09:50:47 +03:00
static struct clk_hw_onecell_data * clk_data ;
2014-12-05 17:15:34 +03:00
/*
* On Exynos5420 this will be a clock which has to be enabled before any
* access to audss registers . Typically a child of EPLL .
*
* On other platforms this will be - ENODEV .
*/
static struct clk * epll ;
2013-06-17 19:02:17 +04:00
# define ASS_CLK_SRC 0x0
# define ASS_CLK_DIV 0x4
# define ASS_CLK_GATE 0x8
2013-12-17 13:56:39 +04:00
# ifdef CONFIG_PM_SLEEP
2013-06-17 19:02:17 +04:00
static unsigned long reg_save [ ] [ 2 ] = {
2016-09-02 19:47:54 +03:00
{ ASS_CLK_SRC , 0 } ,
{ ASS_CLK_DIV , 0 } ,
{ ASS_CLK_GATE , 0 } ,
2013-06-17 19:02:17 +04:00
} ;
2016-11-25 12:39:54 +03:00
static int exynos_audss_clk_suspend ( struct device * dev )
2013-06-17 19:02:17 +04:00
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( reg_save ) ; i + + )
reg_save [ i ] [ 1 ] = readl ( reg_base + reg_save [ i ] [ 0 ] ) ;
return 0 ;
}
2016-11-25 12:39:54 +03:00
static int exynos_audss_clk_resume ( struct device * dev )
2013-06-17 19:02:17 +04:00
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( reg_save ) ; i + + )
writel ( reg_save [ i ] [ 1 ] , reg_base + reg_save [ i ] [ 0 ] ) ;
2016-11-25 12:39:54 +03:00
return 0 ;
}
2013-06-17 19:02:17 +04:00
# endif /* CONFIG_PM_SLEEP */
2016-09-02 19:23:47 +03:00
struct exynos_audss_clk_drvdata {
unsigned int has_adma_clk : 1 ;
2016-09-02 19:47:45 +03:00
unsigned int has_mst_clk : 1 ;
2016-09-02 19:23:47 +03:00
unsigned int enable_epll : 1 ;
unsigned int num_clks ;
} ;
static const struct exynos_audss_clk_drvdata exynos4210_drvdata = {
. num_clks = EXYNOS_AUDSS_MAX_CLKS - 1 ,
2017-06-21 22:08:06 +03:00
. enable_epll = 1 ,
2016-09-02 19:23:47 +03:00
} ;
2016-09-02 19:47:45 +03:00
static const struct exynos_audss_clk_drvdata exynos5410_drvdata = {
. num_clks = EXYNOS_AUDSS_MAX_CLKS - 1 ,
. has_mst_clk = 1 ,
} ;
2016-09-02 19:23:47 +03:00
static const struct exynos_audss_clk_drvdata exynos5420_drvdata = {
. num_clks = EXYNOS_AUDSS_MAX_CLKS ,
. has_adma_clk = 1 ,
. enable_epll = 1 ,
} ;
2013-09-26 01:12:51 +04:00
static const struct of_device_id exynos_audss_clk_of_match [ ] = {
2016-09-02 19:23:47 +03:00
{
. compatible = " samsung,exynos4210-audss-clock " ,
. data = & exynos4210_drvdata ,
} , {
. compatible = " samsung,exynos5250-audss-clock " ,
. data = & exynos4210_drvdata ,
2016-09-02 19:47:45 +03:00
} , {
. compatible = " samsung,exynos5410-audss-clock " ,
. data = & exynos5410_drvdata ,
2016-09-02 19:23:47 +03:00
} , {
. compatible = " samsung,exynos5420-audss-clock " ,
. data = & exynos5420_drvdata ,
} ,
{ } ,
2013-09-26 01:12:51 +04:00
} ;
2016-10-16 16:45:07 +03:00
MODULE_DEVICE_TABLE ( of , exynos_audss_clk_of_match ) ;
2013-09-26 01:12:51 +04:00
2015-01-05 12:52:41 +03:00
static void exynos_audss_clk_teardown ( void )
{
int i ;
for ( i = EXYNOS_MOUT_AUDSS ; i < EXYNOS_DOUT_SRP ; i + + ) {
2017-05-15 09:50:47 +03:00
if ( ! IS_ERR ( clk_data - > hws [ i ] ) )
clk_hw_unregister_mux ( clk_data - > hws [ i ] ) ;
2015-01-05 12:52:41 +03:00
}
for ( ; i < EXYNOS_SRP_CLK ; i + + ) {
2017-05-15 09:50:47 +03:00
if ( ! IS_ERR ( clk_data - > hws [ i ] ) )
clk_hw_unregister_divider ( clk_data - > hws [ i ] ) ;
2015-01-05 12:52:41 +03:00
}
2017-05-15 09:50:47 +03:00
for ( ; i < clk_data - > num ; i + + ) {
if ( ! IS_ERR ( clk_data - > hws [ i ] ) )
clk_hw_unregister_gate ( clk_data - > hws [ i ] ) ;
2015-01-05 12:52:41 +03:00
}
}
2013-06-17 19:02:17 +04:00
/* register exynos_audss clocks */
2013-09-26 01:12:47 +04:00
static int exynos_audss_clk_probe ( struct platform_device * pdev )
2013-06-17 19:02:17 +04:00
{
2013-09-26 01:12:48 +04:00
const char * mout_audss_p [ ] = { " fin_pll " , " fout_epll " } ;
const char * mout_i2s_p [ ] = { " mout_audss " , " cdclk0 " , " sclk_audio0 " } ;
const char * sclk_pcm_p = " sclk_pcm0 " ;
struct clk * pll_ref , * pll_in , * cdclk , * sclk_audio , * sclk_pcm_in ;
2016-09-02 19:23:47 +03:00
const struct exynos_audss_clk_drvdata * variant ;
2017-05-15 09:50:47 +03:00
struct clk_hw * * clk_table ;
2016-09-02 19:23:47 +03:00
struct resource * res ;
2017-08-21 11:05:02 +03:00
struct device * dev = & pdev - > dev ;
2016-09-02 19:23:47 +03:00
int i , ret = 0 ;
2013-09-26 01:12:51 +04:00
2016-09-02 19:23:47 +03:00
variant = of_device_get_match_data ( & pdev - > dev ) ;
if ( ! variant )
2013-09-26 01:12:51 +04:00
return - EINVAL ;
2013-09-26 01:12:47 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2017-08-21 11:05:02 +03:00
reg_base = devm_ioremap_resource ( dev , res ) ;
2013-09-26 01:12:47 +04:00
if ( IS_ERR ( reg_base ) ) {
2017-08-21 11:05:02 +03:00
dev_err ( dev , " failed to map audss registers \n " ) ;
2013-09-26 01:12:47 +04:00
return PTR_ERR ( reg_base ) ;
2013-06-17 19:02:17 +04:00
}
2016-09-02 19:23:47 +03:00
2014-12-05 17:15:34 +03:00
epll = ERR_PTR ( - ENODEV ) ;
2013-06-17 19:02:17 +04:00
2017-08-21 11:05:02 +03:00
clk_data = devm_kzalloc ( dev ,
2017-05-15 09:50:47 +03:00
sizeof ( * clk_data ) +
sizeof ( * clk_data - > hws ) * EXYNOS_AUDSS_MAX_CLKS ,
2013-06-17 19:02:17 +04:00
GFP_KERNEL ) ;
2017-05-15 09:50:47 +03:00
if ( ! clk_data )
2013-09-26 01:12:47 +04:00
return - ENOMEM ;
2013-06-17 19:02:17 +04:00
2017-05-15 09:50:47 +03:00
clk_data - > num = variant - > num_clks ;
clk_table = clk_data - > hws ;
2013-06-17 19:02:17 +04:00
2017-08-21 11:05:02 +03:00
pll_ref = devm_clk_get ( dev , " pll_ref " ) ;
pll_in = devm_clk_get ( dev , " pll_in " ) ;
2013-09-26 01:12:48 +04:00
if ( ! IS_ERR ( pll_ref ) )
mout_audss_p [ 0 ] = __clk_get_name ( pll_ref ) ;
2014-12-05 17:15:34 +03:00
if ( ! IS_ERR ( pll_in ) ) {
2013-09-26 01:12:48 +04:00
mout_audss_p [ 1 ] = __clk_get_name ( pll_in ) ;
2014-12-05 17:15:34 +03:00
2016-09-02 19:23:47 +03:00
if ( variant - > enable_epll ) {
2014-12-05 17:15:34 +03:00
epll = pll_in ;
ret = clk_prepare_enable ( epll ) ;
if ( ret ) {
2017-08-21 11:05:02 +03:00
dev_err ( dev ,
2016-09-02 19:47:54 +03:00
" failed to prepare the epll clock \n " ) ;
2014-12-05 17:15:34 +03:00
return ret ;
}
}
}
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_MOUT_AUDSS ] = clk_hw_register_mux ( NULL , " mout_audss " ,
2013-07-29 15:25:01 +04:00
mout_audss_p , ARRAY_SIZE ( mout_audss_p ) ,
CLK_SET_RATE_NO_REPARENT ,
2013-06-17 19:02:17 +04:00
reg_base + ASS_CLK_SRC , 0 , 1 , 0 , & lock ) ;
2017-08-21 11:05:02 +03:00
cdclk = devm_clk_get ( dev , " cdclk " ) ;
sclk_audio = devm_clk_get ( dev , " sclk_audio " ) ;
2013-09-26 01:12:48 +04:00
if ( ! IS_ERR ( cdclk ) )
mout_i2s_p [ 1 ] = __clk_get_name ( cdclk ) ;
if ( ! IS_ERR ( sclk_audio ) )
mout_i2s_p [ 2 ] = __clk_get_name ( sclk_audio ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_MOUT_I2S ] = clk_hw_register_mux ( NULL , " mout_i2s " ,
2013-07-29 15:25:01 +04:00
mout_i2s_p , ARRAY_SIZE ( mout_i2s_p ) ,
CLK_SET_RATE_NO_REPARENT ,
2013-06-17 19:02:17 +04:00
reg_base + ASS_CLK_SRC , 2 , 2 , 0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_DOUT_SRP ] = clk_hw_register_divider ( NULL , " dout_srp " ,
2013-06-17 19:02:17 +04:00
" mout_audss " , 0 , reg_base + ASS_CLK_DIV , 0 , 4 ,
0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_DOUT_AUD_BUS ] = clk_hw_register_divider ( NULL ,
2013-06-17 19:02:17 +04:00
" dout_aud_bus " , " dout_srp " , 0 ,
reg_base + ASS_CLK_DIV , 4 , 4 , 0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_DOUT_I2S ] = clk_hw_register_divider ( NULL , " dout_i2s " ,
2013-06-17 19:02:17 +04:00
" mout_i2s " , 0 , reg_base + ASS_CLK_DIV , 8 , 4 , 0 ,
& lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_SRP_CLK ] = clk_hw_register_gate ( NULL , " srp_clk " ,
2013-06-17 19:02:17 +04:00
" dout_srp " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 0 , 0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_I2S_BUS ] = clk_hw_register_gate ( NULL , " i2s_bus " ,
2013-06-17 19:02:17 +04:00
" dout_aud_bus " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 2 , 0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_SCLK_I2S ] = clk_hw_register_gate ( NULL , " sclk_i2s " ,
2013-06-17 19:02:17 +04:00
" dout_i2s " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 3 , 0 , & lock ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_PCM_BUS ] = clk_hw_register_gate ( NULL , " pcm_bus " ,
2013-06-17 19:02:17 +04:00
" sclk_pcm " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 4 , 0 , & lock ) ;
2017-08-21 11:05:02 +03:00
sclk_pcm_in = devm_clk_get ( dev , " sclk_pcm_in " ) ;
2013-09-26 01:12:48 +04:00
if ( ! IS_ERR ( sclk_pcm_in ) )
sclk_pcm_p = __clk_get_name ( sclk_pcm_in ) ;
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_SCLK_PCM ] = clk_hw_register_gate ( NULL , " sclk_pcm " ,
2013-09-26 01:12:48 +04:00
sclk_pcm_p , CLK_SET_RATE_PARENT ,
2013-06-17 19:02:17 +04:00
reg_base + ASS_CLK_GATE , 5 , 0 , & lock ) ;
2016-09-02 19:23:47 +03:00
if ( variant - > has_adma_clk ) {
2017-05-15 09:50:47 +03:00
clk_table [ EXYNOS_ADMA ] = clk_hw_register_gate ( NULL , " adma " ,
2013-09-26 01:12:51 +04:00
" dout_srp " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 9 , 0 , & lock ) ;
}
2017-05-15 09:50:47 +03:00
for ( i = 0 ; i < clk_data - > num ; i + + ) {
2013-09-26 01:12:47 +04:00
if ( IS_ERR ( clk_table [ i ] ) ) {
2017-08-21 11:05:02 +03:00
dev_err ( dev , " failed to register clock %d \n " , i ) ;
2013-09-26 01:12:47 +04:00
ret = PTR_ERR ( clk_table [ i ] ) ;
goto unregister ;
}
}
2017-08-21 11:05:02 +03:00
ret = of_clk_add_hw_provider ( dev - > of_node , of_clk_hw_onecell_get ,
2017-05-15 09:50:47 +03:00
clk_data ) ;
2013-09-26 01:12:47 +04:00
if ( ret ) {
2017-08-21 11:05:02 +03:00
dev_err ( dev , " failed to add clock provider \n " ) ;
2013-09-26 01:12:47 +04:00
goto unregister ;
}
return 0 ;
unregister :
2015-01-05 12:52:41 +03:00
exynos_audss_clk_teardown ( ) ;
2013-09-26 01:12:47 +04:00
2014-12-05 17:15:34 +03:00
if ( ! IS_ERR ( epll ) )
clk_disable_unprepare ( epll ) ;
2013-09-26 01:12:47 +04:00
return ret ;
}
static int exynos_audss_clk_remove ( struct platform_device * pdev )
{
of_clk_del_provider ( pdev - > dev . of_node ) ;
2015-01-05 12:52:41 +03:00
exynos_audss_clk_teardown ( ) ;
2013-09-26 01:12:47 +04:00
2014-12-05 17:15:34 +03:00
if ( ! IS_ERR ( epll ) )
clk_disable_unprepare ( epll ) ;
2013-09-26 01:12:47 +04:00
return 0 ;
2013-06-17 19:02:17 +04:00
}
2013-09-26 01:12:47 +04:00
2016-11-25 12:39:54 +03:00
static const struct dev_pm_ops exynos_audss_clk_pm_ops = {
SET_LATE_SYSTEM_SLEEP_PM_OPS ( exynos_audss_clk_suspend ,
exynos_audss_clk_resume )
} ;
2013-09-26 01:12:47 +04:00
static struct platform_driver exynos_audss_clk_driver = {
. driver = {
. name = " exynos-audss-clk " ,
. of_match_table = exynos_audss_clk_of_match ,
2016-11-25 12:39:54 +03:00
. pm = & exynos_audss_clk_pm_ops ,
2013-09-26 01:12:47 +04:00
} ,
. probe = exynos_audss_clk_probe ,
. remove = exynos_audss_clk_remove ,
} ;
2016-07-08 17:15:00 +03:00
module_platform_driver ( exynos_audss_clk_driver ) ;
2013-09-26 01:12:47 +04:00
MODULE_AUTHOR ( " Padmavathi Venna <padma.v@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Exynos Audio Subsystem Clock Controller " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:exynos-audss-clk " ) ;