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>
# 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
2013-09-26 01:12:51 +04:00
enum exynos_audss_clk_type {
TYPE_EXYNOS4210 ,
TYPE_EXYNOS5250 ,
TYPE_EXYNOS5420 ,
} ;
2013-06-17 19:02:17 +04:00
static DEFINE_SPINLOCK ( lock ) ;
static struct clk * * clk_table ;
static void __iomem * reg_base ;
static struct clk_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 ] = {
{ ASS_CLK_SRC , 0 } ,
{ ASS_CLK_DIV , 0 } ,
{ ASS_CLK_GATE , 0 } ,
} ;
static int exynos_audss_clk_suspend ( void )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( reg_save ) ; i + + )
reg_save [ i ] [ 1 ] = readl ( reg_base + reg_save [ i ] [ 0 ] ) ;
return 0 ;
}
static void exynos_audss_clk_resume ( void )
{
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( reg_save ) ; i + + )
writel ( reg_save [ i ] [ 1 ] , reg_base + reg_save [ i ] [ 0 ] ) ;
}
static struct syscore_ops exynos_audss_clk_syscore_ops = {
. suspend = exynos_audss_clk_suspend ,
. resume = exynos_audss_clk_resume ,
} ;
# endif /* CONFIG_PM_SLEEP */
2013-09-26 01:12:51 +04:00
static const struct of_device_id exynos_audss_clk_of_match [ ] = {
{ . compatible = " samsung,exynos4210-audss-clock " ,
. data = ( void * ) TYPE_EXYNOS4210 , } ,
{ . compatible = " samsung,exynos5250-audss-clock " ,
. data = ( void * ) TYPE_EXYNOS5250 , } ,
{ . compatible = " samsung,exynos5420-audss-clock " ,
. data = ( void * ) TYPE_EXYNOS5420 , } ,
{ } ,
} ;
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 + + ) {
if ( ! IS_ERR ( clk_table [ i ] ) )
clk_unregister_mux ( clk_table [ i ] ) ;
}
for ( ; i < EXYNOS_SRP_CLK ; i + + ) {
if ( ! IS_ERR ( clk_table [ i ] ) )
clk_unregister_divider ( clk_table [ i ] ) ;
}
for ( ; i < clk_data . clk_num ; i + + ) {
if ( ! IS_ERR ( clk_table [ i ] ) )
clk_unregister_gate ( clk_table [ i ] ) ;
}
}
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:47 +04:00
int i , ret = 0 ;
struct resource * res ;
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 ;
2013-09-26 01:12:51 +04:00
const struct of_device_id * match ;
enum exynos_audss_clk_type variant ;
match = of_match_node ( exynos_audss_clk_of_match , pdev - > dev . of_node ) ;
if ( ! match )
return - EINVAL ;
variant = ( enum exynos_audss_clk_type ) match - > data ;
2013-09-26 01:12:47 +04:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
reg_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( reg_base ) ) {
dev_err ( & pdev - > dev , " failed to map audss registers \n " ) ;
return PTR_ERR ( reg_base ) ;
2013-06-17 19:02:17 +04:00
}
2014-12-05 17:15:34 +03:00
/* EPLL don't have to be enabled for boards other than Exynos5420 */
epll = ERR_PTR ( - ENODEV ) ;
2013-06-17 19:02:17 +04:00
2013-09-26 01:12:47 +04:00
clk_table = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct clk * ) * EXYNOS_AUDSS_MAX_CLKS ,
2013-06-17 19:02:17 +04:00
GFP_KERNEL ) ;
2013-09-26 01:12:47 +04:00
if ( ! clk_table )
return - ENOMEM ;
2013-06-17 19:02:17 +04:00
clk_data . clks = clk_table ;
2013-09-26 01:12:51 +04:00
if ( variant = = TYPE_EXYNOS5420 )
clk_data . clk_num = EXYNOS_AUDSS_MAX_CLKS ;
else
clk_data . clk_num = EXYNOS_AUDSS_MAX_CLKS - 1 ;
2013-06-17 19:02:17 +04:00
2013-09-26 01:12:48 +04:00
pll_ref = devm_clk_get ( & pdev - > dev , " pll_ref " ) ;
pll_in = devm_clk_get ( & pdev - > dev , " pll_in " ) ;
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
if ( variant = = TYPE_EXYNOS5420 ) {
epll = pll_in ;
ret = clk_prepare_enable ( epll ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" failed to prepare the epll clock \n " ) ;
return ret ;
}
}
}
2013-06-17 19:02:17 +04:00
clk_table [ EXYNOS_MOUT_AUDSS ] = clk_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 ) ;
2013-09-26 01:12:48 +04:00
cdclk = devm_clk_get ( & pdev - > dev , " cdclk " ) ;
sclk_audio = devm_clk_get ( & pdev - > dev , " sclk_audio " ) ;
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 ) ;
2013-06-17 19:02:17 +04:00
clk_table [ EXYNOS_MOUT_I2S ] = clk_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 ) ;
clk_table [ EXYNOS_DOUT_SRP ] = clk_register_divider ( NULL , " dout_srp " ,
" mout_audss " , 0 , reg_base + ASS_CLK_DIV , 0 , 4 ,
0 , & lock ) ;
clk_table [ EXYNOS_DOUT_AUD_BUS ] = clk_register_divider ( NULL ,
" dout_aud_bus " , " dout_srp " , 0 ,
reg_base + ASS_CLK_DIV , 4 , 4 , 0 , & lock ) ;
clk_table [ EXYNOS_DOUT_I2S ] = clk_register_divider ( NULL , " dout_i2s " ,
" mout_i2s " , 0 , reg_base + ASS_CLK_DIV , 8 , 4 , 0 ,
& lock ) ;
clk_table [ EXYNOS_SRP_CLK ] = clk_register_gate ( NULL , " srp_clk " ,
" dout_srp " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 0 , 0 , & lock ) ;
clk_table [ EXYNOS_I2S_BUS ] = clk_register_gate ( NULL , " i2s_bus " ,
" dout_aud_bus " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 2 , 0 , & lock ) ;
clk_table [ EXYNOS_SCLK_I2S ] = clk_register_gate ( NULL , " sclk_i2s " ,
" dout_i2s " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 3 , 0 , & lock ) ;
clk_table [ EXYNOS_PCM_BUS ] = clk_register_gate ( NULL , " pcm_bus " ,
" sclk_pcm " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 4 , 0 , & lock ) ;
2013-09-26 01:12:48 +04:00
sclk_pcm_in = devm_clk_get ( & pdev - > dev , " sclk_pcm_in " ) ;
if ( ! IS_ERR ( sclk_pcm_in ) )
sclk_pcm_p = __clk_get_name ( sclk_pcm_in ) ;
2013-06-17 19:02:17 +04:00
clk_table [ EXYNOS_SCLK_PCM ] = clk_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 ) ;
2013-09-26 01:12:51 +04:00
if ( variant = = TYPE_EXYNOS5420 ) {
clk_table [ EXYNOS_ADMA ] = clk_register_gate ( NULL , " adma " ,
" dout_srp " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 9 , 0 , & lock ) ;
}
2013-09-26 01:12:47 +04:00
for ( i = 0 ; i < clk_data . clk_num ; i + + ) {
if ( IS_ERR ( clk_table [ i ] ) ) {
dev_err ( & pdev - > dev , " failed to register clock %d \n " , i ) ;
ret = PTR_ERR ( clk_table [ i ] ) ;
goto unregister ;
}
}
ret = of_clk_add_provider ( pdev - > dev . of_node , of_clk_src_onecell_get ,
& clk_data ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to add clock provider \n " ) ;
goto unregister ;
}
2013-06-17 19:02:17 +04:00
# ifdef CONFIG_PM_SLEEP
register_syscore_ops ( & exynos_audss_clk_syscore_ops ) ;
# endif
2013-09-26 01:12:47 +04:00
dev_info ( & pdev - > dev , " setup completed \n " ) ;
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 )
{
2014-11-26 17:24:13 +03:00
# ifdef CONFIG_PM_SLEEP
unregister_syscore_ops ( & exynos_audss_clk_syscore_ops ) ;
# endif
2013-09-26 01:12:47 +04:00
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
static struct platform_driver exynos_audss_clk_driver = {
. driver = {
. name = " exynos-audss-clk " ,
. of_match_table = exynos_audss_clk_of_match ,
} ,
. probe = exynos_audss_clk_probe ,
. remove = exynos_audss_clk_remove ,
} ;
static int __init exynos_audss_clk_init ( void )
{
return platform_driver_register ( & exynos_audss_clk_driver ) ;
}
core_initcall ( exynos_audss_clk_init ) ;
static void __exit exynos_audss_clk_exit ( void )
{
platform_driver_unregister ( & exynos_audss_clk_driver ) ;
}
module_exit ( exynos_audss_clk_exit ) ;
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 " ) ;