2016-04-14 18:33:31 +03:00
/*
* Marvell Armada AP806 System Controller
*
* Copyright ( C ) 2016 Marvell
*
* Thomas Petazzoni < thomas . petazzoni @ free - electrons . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# define pr_fmt(fmt) "ap806-system-controller: " fmt
# include <linux/clk-provider.h>
# include <linux/mfd/syscon.h>
2016-07-05 00:12:14 +03:00
# include <linux/init.h>
2016-04-14 18:33:31 +03:00
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# define AP806_SAR_REG 0x400
# define AP806_SAR_CLKFREQ_MODE_MASK 0x1f
2017-03-30 18:22:53 +03:00
# define AP806_CLK_NUM 5
2016-04-14 18:33:31 +03:00
static struct clk * ap806_clks [ AP806_CLK_NUM ] ;
static struct clk_onecell_data ap806_clk_data = {
. clks = ap806_clks ,
. clk_num = AP806_CLK_NUM ,
} ;
static int ap806_syscon_clk_probe ( struct platform_device * pdev )
{
unsigned int freq_mode , cpuclk_freq ;
const char * name , * fixedclk_name ;
2017-05-31 17:07:22 +03:00
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
2016-04-14 18:33:31 +03:00
struct regmap * regmap ;
u32 reg ;
int ret ;
regmap = syscon_node_to_regmap ( np ) ;
if ( IS_ERR ( regmap ) ) {
2017-05-31 17:07:22 +03:00
dev_err ( dev , " cannot get regmap \n " ) ;
2016-04-14 18:33:31 +03:00
return PTR_ERR ( regmap ) ;
}
ret = regmap_read ( regmap , AP806_SAR_REG , & reg ) ;
if ( ret ) {
2017-05-31 17:07:22 +03:00
dev_err ( dev , " cannot read from regmap \n " ) ;
2016-04-14 18:33:31 +03:00
return ret ;
}
freq_mode = reg & AP806_SAR_CLKFREQ_MODE_MASK ;
switch ( freq_mode ) {
2016-12-22 15:08:14 +03:00
case 0x0 :
case 0x1 :
2016-04-14 18:33:31 +03:00
cpuclk_freq = 2000 ;
break ;
2016-12-22 15:08:14 +03:00
case 0x6 :
case 0x7 :
2016-04-14 18:33:31 +03:00
cpuclk_freq = 1800 ;
break ;
2016-12-22 15:08:14 +03:00
case 0x4 :
case 0xB :
case 0xD :
2016-04-14 18:33:31 +03:00
cpuclk_freq = 1600 ;
break ;
2016-12-22 15:08:14 +03:00
case 0x1a :
2016-04-14 18:33:31 +03:00
cpuclk_freq = 1400 ;
break ;
2016-12-22 15:08:14 +03:00
case 0x14 :
case 0x17 :
2016-04-14 18:33:31 +03:00
cpuclk_freq = 1300 ;
break ;
2016-12-22 15:08:14 +03:00
case 0x19 :
cpuclk_freq = 1200 ;
break ;
case 0x13 :
case 0x1d :
cpuclk_freq = 1000 ;
break ;
case 0x1c :
cpuclk_freq = 800 ;
break ;
case 0x1b :
cpuclk_freq = 600 ;
break ;
2016-04-14 18:33:31 +03:00
default :
2017-05-31 17:07:22 +03:00
dev_err ( dev , " invalid SAR value \n " ) ;
2016-04-14 18:33:31 +03:00
return - EINVAL ;
}
/* Convert to hertz */
cpuclk_freq * = 1000 * 1000 ;
/* CPU clocks depend on the Sample At Reset configuration */
of_property_read_string_index ( np , " clock-output-names " ,
0 , & name ) ;
2017-05-31 17:07:22 +03:00
ap806_clks [ 0 ] = clk_register_fixed_rate ( dev , name , NULL ,
2016-04-14 18:33:31 +03:00
0 , cpuclk_freq ) ;
if ( IS_ERR ( ap806_clks [ 0 ] ) ) {
ret = PTR_ERR ( ap806_clks [ 0 ] ) ;
goto fail0 ;
}
of_property_read_string_index ( np , " clock-output-names " ,
1 , & name ) ;
2017-05-31 17:07:22 +03:00
ap806_clks [ 1 ] = clk_register_fixed_rate ( dev , name , NULL , 0 ,
2016-04-14 18:33:31 +03:00
cpuclk_freq ) ;
if ( IS_ERR ( ap806_clks [ 1 ] ) ) {
ret = PTR_ERR ( ap806_clks [ 1 ] ) ;
goto fail1 ;
}
/* Fixed clock is always 1200 Mhz */
of_property_read_string_index ( np , " clock-output-names " ,
2 , & fixedclk_name ) ;
2017-05-31 17:07:22 +03:00
ap806_clks [ 2 ] = clk_register_fixed_rate ( dev , fixedclk_name , NULL ,
2016-04-14 18:33:31 +03:00
0 , 1200 * 1000 * 1000 ) ;
if ( IS_ERR ( ap806_clks [ 2 ] ) ) {
ret = PTR_ERR ( ap806_clks [ 2 ] ) ;
goto fail2 ;
}
/* MSS Clock is fixed clock divided by 6 */
of_property_read_string_index ( np , " clock-output-names " ,
3 , & name ) ;
ap806_clks [ 3 ] = clk_register_fixed_factor ( NULL , name , fixedclk_name ,
0 , 1 , 6 ) ;
if ( IS_ERR ( ap806_clks [ 3 ] ) ) {
ret = PTR_ERR ( ap806_clks [ 3 ] ) ;
goto fail3 ;
}
2017-03-30 18:22:53 +03:00
/* eMMC Clock is fixed clock divided by 3 */
2017-03-30 18:22:54 +03:00
if ( of_property_read_string_index ( np , " clock-output-names " ,
4 , & name ) ) {
ap806_clk_data . clk_num - - ;
dev_warn ( & pdev - > dev ,
2017-04-11 16:37:43 +03:00
" eMMC clock missing: update the device tree! \n " ) ;
2017-03-30 18:22:54 +03:00
} else {
ap806_clks [ 4 ] = clk_register_fixed_factor ( NULL , name ,
fixedclk_name ,
0 , 1 , 3 ) ;
if ( IS_ERR ( ap806_clks [ 4 ] ) ) {
ret = PTR_ERR ( ap806_clks [ 4 ] ) ;
goto fail4 ;
}
2017-03-30 18:22:53 +03:00
}
of_clk_add_provider ( np , of_clk_src_onecell_get , & ap806_clk_data ) ;
2016-04-14 18:33:31 +03:00
ret = of_clk_add_provider ( np , of_clk_src_onecell_get , & ap806_clk_data ) ;
if ( ret )
goto fail_clk_add ;
return 0 ;
fail_clk_add :
2017-03-30 18:22:53 +03:00
clk_unregister_fixed_factor ( ap806_clks [ 4 ] ) ;
fail4 :
2016-04-14 18:33:31 +03:00
clk_unregister_fixed_factor ( ap806_clks [ 3 ] ) ;
fail3 :
clk_unregister_fixed_rate ( ap806_clks [ 2 ] ) ;
fail2 :
clk_unregister_fixed_rate ( ap806_clks [ 1 ] ) ;
fail1 :
clk_unregister_fixed_rate ( ap806_clks [ 0 ] ) ;
fail0 :
return ret ;
}
static const struct of_device_id ap806_syscon_of_match [ ] = {
{ . compatible = " marvell,ap806-system-controller " , } ,
{ }
} ;
static struct platform_driver ap806_syscon_driver = {
. probe = ap806_syscon_clk_probe ,
. driver = {
. name = " marvell-ap806-system-controller " ,
. of_match_table = ap806_syscon_of_match ,
2016-07-05 00:12:14 +03:00
. suppress_bind_attrs = true ,
2016-04-14 18:33:31 +03:00
} ,
} ;
2016-07-05 00:12:14 +03:00
builtin_platform_driver ( ap806_syscon_driver ) ;