2013-02-06 01:52:51 +04:00
/*
* kirkwood_freq . c : cpufreq driver for the Marvell kirkwood
*
* Copyright ( C ) 2013 Andrew Lunn < andrew @ lunn . ch >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/clk.h>
# include <linux/cpufreq.h>
2013-06-17 18:09:51 +04:00
# include <linux/of_device.h>
2013-02-06 01:52:51 +04:00
# include <linux/platform_device.h>
# include <linux/io.h>
# include <asm/proc-fns.h>
# define CPU_SW_INT_BLK BIT(28)
static struct priv
{
struct clk * cpu_clk ;
struct clk * ddr_clk ;
struct clk * powersave_clk ;
struct device * dev ;
void __iomem * base ;
} priv ;
# define STATE_CPU_FREQ 0x01
# define STATE_DDR_FREQ 0x02
/*
* Kirkwood can swap the clock to the CPU between two clocks :
*
* - cpu clk
* - ddr clk
*
2014-08-18 19:30:29 +04:00
* The frequencies are set at runtime before registering this table .
2013-02-06 01:52:51 +04:00
*/
static struct cpufreq_frequency_table kirkwood_freq_table [ ] = {
2014-03-28 17:41:47 +04:00
{ 0 , STATE_CPU_FREQ , 0 } , /* CPU uses cpuclk */
{ 0 , STATE_DDR_FREQ , 0 } , /* CPU uses ddrclk */
{ 0 , 0 , CPUFREQ_TABLE_END } ,
2013-02-06 01:52:51 +04:00
} ;
static unsigned int kirkwood_cpufreq_get_cpu_frequency ( unsigned int cpu )
{
2014-08-18 19:30:29 +04:00
return clk_get_rate ( priv . powersave_clk ) / 1000 ;
2013-02-06 01:52:51 +04:00
}
2013-10-25 18:15:48 +04:00
static int kirkwood_cpufreq_target ( struct cpufreq_policy * policy ,
unsigned int index )
2013-02-06 01:52:51 +04:00
{
2013-03-30 14:55:15 +04:00
unsigned int state = kirkwood_freq_table [ index ] . driver_data ;
2013-02-06 01:52:51 +04:00
unsigned long reg ;
2013-08-14 18:08:24 +04:00
local_irq_disable ( ) ;
2013-02-06 01:52:51 +04:00
2013-08-14 18:08:24 +04:00
/* Disable interrupts to the CPU */
reg = readl_relaxed ( priv . base ) ;
reg | = CPU_SW_INT_BLK ;
writel_relaxed ( reg , priv . base ) ;
2013-02-06 01:52:51 +04:00
2013-08-14 18:08:24 +04:00
switch ( state ) {
case STATE_CPU_FREQ :
2014-08-18 19:30:29 +04:00
clk_set_parent ( priv . powersave_clk , priv . cpu_clk ) ;
2013-08-14 18:08:24 +04:00
break ;
case STATE_DDR_FREQ :
2014-08-18 19:30:29 +04:00
clk_set_parent ( priv . powersave_clk , priv . ddr_clk ) ;
2013-08-14 18:08:24 +04:00
break ;
}
2013-02-06 01:52:51 +04:00
2013-08-14 18:08:24 +04:00
/* Wait-for-Interrupt, while the hardware changes frequency */
cpu_do_idle ( ) ;
2013-02-06 01:52:51 +04:00
2013-08-14 18:08:24 +04:00
/* Enable interrupts to the CPU */
reg = readl_relaxed ( priv . base ) ;
reg & = ~ CPU_SW_INT_BLK ;
writel_relaxed ( reg , priv . base ) ;
2013-02-06 01:52:51 +04:00
2013-08-14 18:08:24 +04:00
local_irq_enable ( ) ;
2013-02-06 01:52:51 +04:00
return 0 ;
}
/* Module init and exit code */
static int kirkwood_cpufreq_cpu_init ( struct cpufreq_policy * policy )
{
2013-10-03 18:59:15 +04:00
return cpufreq_generic_init ( policy , kirkwood_freq_table , 5000 ) ;
2013-02-06 01:52:51 +04:00
}
static struct cpufreq_driver kirkwood_cpufreq_driver = {
2013-12-03 09:50:45 +04:00
. flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK ,
2013-02-06 01:52:51 +04:00
. get = kirkwood_cpufreq_get_cpu_frequency ,
2013-10-03 18:58:09 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = kirkwood_cpufreq_target ,
2013-02-06 01:52:51 +04:00
. init = kirkwood_cpufreq_cpu_init ,
. name = " kirkwood-cpufreq " ,
2013-10-03 18:58:09 +04:00
. attr = cpufreq_generic_attr ,
2013-02-06 01:52:51 +04:00
} ;
static int kirkwood_cpufreq_probe ( struct platform_device * pdev )
{
struct device_node * np ;
struct resource * res ;
int err ;
priv . dev = & pdev - > dev ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-03-22 04:10:22 +04:00
priv . base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( priv . base ) )
return PTR_ERR ( priv . base ) ;
2013-02-06 01:52:51 +04:00
2013-06-17 18:09:51 +04:00
np = of_cpu_device_node_get ( 0 ) ;
if ( ! np ) {
dev_err ( & pdev - > dev , " failed to get cpu device node \n " ) ;
2013-02-06 01:52:51 +04:00
return - ENODEV ;
2013-06-17 18:09:51 +04:00
}
2013-02-06 01:52:51 +04:00
priv . cpu_clk = of_clk_get_by_name ( np , " cpu_clk " ) ;
if ( IS_ERR ( priv . cpu_clk ) ) {
2016-09-26 00:35:43 +03:00
dev_err ( priv . dev , " Unable to get cpuclk \n " ) ;
2013-02-06 01:52:51 +04:00
return PTR_ERR ( priv . cpu_clk ) ;
}
clk_prepare_enable ( priv . cpu_clk ) ;
kirkwood_freq_table [ 0 ] . frequency = clk_get_rate ( priv . cpu_clk ) / 1000 ;
priv . ddr_clk = of_clk_get_by_name ( np , " ddrclk " ) ;
if ( IS_ERR ( priv . ddr_clk ) ) {
2016-09-26 00:35:43 +03:00
dev_err ( priv . dev , " Unable to get ddrclk \n " ) ;
2013-02-06 01:52:51 +04:00
err = PTR_ERR ( priv . ddr_clk ) ;
goto out_cpu ;
}
clk_prepare_enable ( priv . ddr_clk ) ;
kirkwood_freq_table [ 1 ] . frequency = clk_get_rate ( priv . ddr_clk ) / 1000 ;
priv . powersave_clk = of_clk_get_by_name ( np , " powersave " ) ;
if ( IS_ERR ( priv . powersave_clk ) ) {
2016-09-26 00:35:43 +03:00
dev_err ( priv . dev , " Unable to get powersave \n " ) ;
2013-02-06 01:52:51 +04:00
err = PTR_ERR ( priv . powersave_clk ) ;
goto out_ddr ;
}
2014-08-18 19:30:29 +04:00
clk_prepare_enable ( priv . powersave_clk ) ;
2013-02-06 01:52:51 +04:00
of_node_put ( np ) ;
np = NULL ;
err = cpufreq_register_driver ( & kirkwood_cpufreq_driver ) ;
if ( ! err )
return 0 ;
2016-09-26 00:35:43 +03:00
dev_err ( priv . dev , " Failed to register cpufreq driver \n " ) ;
2013-02-06 01:52:51 +04:00
clk_disable_unprepare ( priv . powersave_clk ) ;
out_ddr :
clk_disable_unprepare ( priv . ddr_clk ) ;
out_cpu :
clk_disable_unprepare ( priv . cpu_clk ) ;
of_node_put ( np ) ;
return err ;
}
static int kirkwood_cpufreq_remove ( struct platform_device * pdev )
{
cpufreq_unregister_driver ( & kirkwood_cpufreq_driver ) ;
clk_disable_unprepare ( priv . powersave_clk ) ;
clk_disable_unprepare ( priv . ddr_clk ) ;
clk_disable_unprepare ( priv . cpu_clk ) ;
return 0 ;
}
static struct platform_driver kirkwood_cpufreq_platform_driver = {
. probe = kirkwood_cpufreq_probe ,
. remove = kirkwood_cpufreq_remove ,
. driver = {
. name = " kirkwood-cpufreq " ,
} ,
} ;
module_platform_driver ( kirkwood_cpufreq_platform_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Andrew Lunn <andrew@lunn.ch " ) ;
MODULE_DESCRIPTION ( " cpufreq driver for Marvell's kirkwood CPU " ) ;
MODULE_ALIAS ( " platform:kirkwood-cpufreq " ) ;