2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-07-02 21:28:27 +04:00
/*
* Copyright ( c ) 2014 Tomasz Figa < t . figa @ samsung . com >
*
* Based on Exynos Audio Subsystem Clock Controller driver :
*
* Copyright ( c ) 2013 Samsung Electronics Co . , Ltd .
* Author : Padmavathi Venna < padma . v @ samsung . com >
*
* Driver for Audio Subsystem Clock Controller of S5PV210 - compatible SoCs .
*/
# include <linux/io.h>
2015-06-20 01:00:46 +03:00
# include <linux/clk.h>
2014-07-02 21:28:27 +04:00
# include <linux/clk-provider.h>
# include <linux/of_address.h>
# include <linux/syscore_ops.h>
2016-07-05 00:12:17 +03:00
# include <linux/init.h>
2014-07-02 21:28:27 +04:00
# include <linux/platform_device.h>
# include <dt-bindings/clock/s5pv210-audss.h>
static DEFINE_SPINLOCK ( lock ) ;
static void __iomem * reg_base ;
2017-05-15 09:51:04 +03:00
static struct clk_hw_onecell_data * clk_data ;
2014-07-02 21:28:27 +04:00
# define ASS_CLK_SRC 0x0
# define ASS_CLK_DIV 0x4
# define ASS_CLK_GATE 0x8
# ifdef CONFIG_PM_SLEEP
static unsigned long reg_save [ ] [ 2 ] = {
{ ASS_CLK_SRC , 0 } ,
{ ASS_CLK_DIV , 0 } ,
{ ASS_CLK_GATE , 0 } ,
} ;
static int s5pv210_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 s5pv210_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 s5pv210_audss_clk_syscore_ops = {
. suspend = s5pv210_audss_clk_suspend ,
. resume = s5pv210_audss_clk_resume ,
} ;
# endif /* CONFIG_PM_SLEEP */
/* register s5pv210_audss clocks */
static int s5pv210_audss_clk_probe ( struct platform_device * pdev )
{
int i , ret = 0 ;
struct resource * res ;
const char * mout_audss_p [ 2 ] ;
const char * mout_i2s_p [ 3 ] ;
const char * hclk_p ;
2017-05-15 09:51:04 +03:00
struct clk_hw * * clk_table ;
2014-07-02 21:28:27 +04:00
struct clk * hclk , * pll_ref , * pll_in , * cdclk , * sclk_audio ;
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 ) ;
}
2017-05-15 09:51:04 +03:00
clk_data = devm_kzalloc ( & pdev - > dev ,
treewide: Use struct_size() for devm_kmalloc() and friends
Replaces open-coded struct size calculations with struct_size() for
devm_*, f2fs_*, and sock_* allocations. Automatically generated (and
manually adjusted) from the following Coccinelle script:
// Direct reference to struct field.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(*VAR->ELEMENT), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// mr = kzalloc(sizeof(*mr) + m * sizeof(mr->map[0]), GFP_KERNEL);
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
identifier VAR, ELEMENT;
expression COUNT;
@@
- alloc(HANDLE, sizeof(*VAR) + COUNT * sizeof(VAR->ELEMENT[0]), GFP)
+ alloc(HANDLE, struct_size(VAR, ELEMENT, COUNT), GFP)
// Same pattern, but can't trivially locate the trailing element name,
// or variable name.
@@
identifier alloc =~ "devm_kmalloc|devm_kzalloc|sock_kmalloc|f2fs_kmalloc|f2fs_kzalloc";
expression HANDLE;
expression GFP;
expression SOMETHING, COUNT, ELEMENT;
@@
- alloc(HANDLE, sizeof(SOMETHING) + COUNT * sizeof(ELEMENT), GFP)
+ alloc(HANDLE, CHECKME_struct_size(&SOMETHING, ELEMENT, COUNT), GFP)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-05-09 02:08:53 +03:00
struct_size ( clk_data , hws , AUDSS_MAX_CLKS ) ,
2014-07-02 21:28:27 +04:00
GFP_KERNEL ) ;
2017-05-15 09:51:04 +03:00
if ( ! clk_data )
2014-07-02 21:28:27 +04:00
return - ENOMEM ;
2017-05-15 09:51:04 +03:00
clk_data - > num = AUDSS_MAX_CLKS ;
clk_table = clk_data - > hws ;
2014-07-02 21:28:27 +04:00
hclk = devm_clk_get ( & pdev - > dev , " hclk " ) ;
if ( IS_ERR ( hclk ) ) {
dev_err ( & pdev - > dev , " failed to get hclk clock \n " ) ;
return PTR_ERR ( hclk ) ;
}
pll_in = devm_clk_get ( & pdev - > dev , " fout_epll " ) ;
if ( IS_ERR ( pll_in ) ) {
dev_err ( & pdev - > dev , " failed to get fout_epll clock \n " ) ;
return PTR_ERR ( pll_in ) ;
}
sclk_audio = devm_clk_get ( & pdev - > dev , " sclk_audio0 " ) ;
if ( IS_ERR ( sclk_audio ) ) {
dev_err ( & pdev - > dev , " failed to get sclk_audio0 clock \n " ) ;
return PTR_ERR ( sclk_audio ) ;
}
/* iiscdclk0 is an optional external I2S codec clock */
cdclk = devm_clk_get ( & pdev - > dev , " iiscdclk0 " ) ;
pll_ref = devm_clk_get ( & pdev - > dev , " xxti " ) ;
if ( ! IS_ERR ( pll_ref ) )
mout_audss_p [ 0 ] = __clk_get_name ( pll_ref ) ;
else
mout_audss_p [ 0 ] = " xxti " ;
mout_audss_p [ 1 ] = __clk_get_name ( pll_in ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_MOUT_AUDSS ] = clk_hw_register_mux ( NULL , " mout_audss " ,
2014-07-02 21:28:27 +04:00
mout_audss_p , ARRAY_SIZE ( mout_audss_p ) ,
CLK_SET_RATE_NO_REPARENT ,
reg_base + ASS_CLK_SRC , 0 , 1 , 0 , & lock ) ;
mout_i2s_p [ 0 ] = " mout_audss " ;
if ( ! IS_ERR ( cdclk ) )
mout_i2s_p [ 1 ] = __clk_get_name ( cdclk ) ;
else
mout_i2s_p [ 1 ] = " iiscdclk0 " ;
mout_i2s_p [ 2 ] = __clk_get_name ( sclk_audio ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_MOUT_I2S_A ] = clk_hw_register_mux ( NULL , " mout_i2s_audss " ,
2014-07-02 21:28:27 +04:00
mout_i2s_p , ARRAY_SIZE ( mout_i2s_p ) ,
CLK_SET_RATE_NO_REPARENT ,
reg_base + ASS_CLK_SRC , 2 , 2 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_DOUT_AUD_BUS ] = clk_hw_register_divider ( NULL ,
2014-07-02 21:28:27 +04:00
" dout_aud_bus " , " mout_audss " , 0 ,
reg_base + ASS_CLK_DIV , 0 , 4 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_DOUT_I2S_A ] = clk_hw_register_divider ( NULL ,
" dout_i2s_audss " , " mout_i2s_audss " , 0 ,
reg_base + ASS_CLK_DIV , 4 , 4 , 0 , & lock ) ;
2014-07-02 21:28:27 +04:00
2017-05-15 09:51:04 +03:00
clk_table [ CLK_I2S ] = clk_hw_register_gate ( NULL , " i2s_audss " ,
2014-07-02 21:28:27 +04:00
" dout_i2s_audss " , CLK_SET_RATE_PARENT ,
reg_base + ASS_CLK_GATE , 6 , 0 , & lock ) ;
hclk_p = __clk_get_name ( hclk ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_I2S ] = clk_hw_register_gate ( NULL , " hclk_i2s_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 5 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_UART ] = clk_hw_register_gate ( NULL , " hclk_uart_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 4 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_HWA ] = clk_hw_register_gate ( NULL , " hclk_hwa_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 3 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_DMA ] = clk_hw_register_gate ( NULL , " hclk_dma_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 2 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_BUF ] = clk_hw_register_gate ( NULL , " hclk_buf_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 1 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
clk_table [ CLK_HCLK_RP ] = clk_hw_register_gate ( NULL , " hclk_rp_audss " ,
2014-07-02 21:28:27 +04:00
hclk_p , CLK_IGNORE_UNUSED ,
reg_base + ASS_CLK_GATE , 0 , 0 , & lock ) ;
2017-05-15 09:51:04 +03:00
for ( i = 0 ; i < clk_data - > num ; i + + ) {
2014-07-02 21:28:27 +04:00
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 ;
}
}
2017-05-15 09:51:04 +03:00
ret = of_clk_add_hw_provider ( pdev - > dev . of_node , of_clk_hw_onecell_get ,
clk_data ) ;
2014-07-02 21:28:27 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " failed to add clock provider \n " ) ;
goto unregister ;
}
# ifdef CONFIG_PM_SLEEP
register_syscore_ops ( & s5pv210_audss_clk_syscore_ops ) ;
# endif
return 0 ;
unregister :
2017-05-15 09:51:04 +03:00
for ( i = 0 ; i < clk_data - > num ; i + + ) {
2014-07-02 21:28:27 +04:00
if ( ! IS_ERR ( clk_table [ i ] ) )
2017-05-15 09:51:04 +03:00
clk_hw_unregister ( clk_table [ i ] ) ;
2014-07-02 21:28:27 +04:00
}
return ret ;
}
static const struct of_device_id s5pv210_audss_clk_of_match [ ] = {
{ . compatible = " samsung,s5pv210-audss-clock " , } ,
{ } ,
} ;
static struct platform_driver s5pv210_audss_clk_driver = {
. driver = {
. name = " s5pv210-audss-clk " ,
2016-07-05 00:12:17 +03:00
. suppress_bind_attrs = true ,
2014-07-02 21:28:27 +04:00
. of_match_table = s5pv210_audss_clk_of_match ,
} ,
. probe = s5pv210_audss_clk_probe ,
} ;
static int __init s5pv210_audss_clk_init ( void )
{
return platform_driver_register ( & s5pv210_audss_clk_driver ) ;
}
core_initcall ( s5pv210_audss_clk_init ) ;