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/clk-provider.h>
# include <linux/cpufreq.h>
# include <linux/of.h>
# 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
*
* The frequencies are set at runtime before registering this *
* table .
*/
static struct cpufreq_frequency_table kirkwood_freq_table [ ] = {
{ STATE_CPU_FREQ , 0 } , /* CPU uses cpuclk */
{ STATE_DDR_FREQ , 0 } , /* CPU uses ddrclk */
{ 0 , CPUFREQ_TABLE_END } ,
} ;
static unsigned int kirkwood_cpufreq_get_cpu_frequency ( unsigned int cpu )
{
if ( __clk_is_enabled ( priv . powersave_clk ) )
return kirkwood_freq_table [ 1 ] . frequency ;
return kirkwood_freq_table [ 0 ] . frequency ;
}
2013-03-24 10:26:43 +04:00
static void kirkwood_cpufreq_set_cpu_state ( struct cpufreq_policy * policy ,
unsigned int index )
2013-02-06 01:52:51 +04:00
{
struct cpufreq_freqs freqs ;
unsigned int state = kirkwood_freq_table [ index ] . index ;
unsigned long reg ;
freqs . old = kirkwood_cpufreq_get_cpu_frequency ( 0 ) ;
freqs . new = kirkwood_freq_table [ index ] . frequency ;
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_PRECHANGE ) ;
2013-02-06 01:52:51 +04:00
dev_dbg ( priv . dev , " Attempting to set frequency to %i KHz \n " ,
kirkwood_freq_table [ index ] . frequency ) ;
dev_dbg ( priv . dev , " old frequency was %i KHz \n " ,
kirkwood_cpufreq_get_cpu_frequency ( 0 ) ) ;
if ( freqs . old ! = freqs . new ) {
local_irq_disable ( ) ;
/* Disable interrupts to the CPU */
reg = readl_relaxed ( priv . base ) ;
reg | = CPU_SW_INT_BLK ;
writel_relaxed ( reg , priv . base ) ;
switch ( state ) {
case STATE_CPU_FREQ :
clk_disable ( priv . powersave_clk ) ;
break ;
case STATE_DDR_FREQ :
clk_enable ( priv . powersave_clk ) ;
break ;
}
/* Wait-for-Interrupt, while the hardware changes frequency */
cpu_do_idle ( ) ;
/* Enable interrupts to the CPU */
reg = readl_relaxed ( priv . base ) ;
reg & = ~ CPU_SW_INT_BLK ;
writel_relaxed ( reg , priv . base ) ;
local_irq_enable ( ) ;
}
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_POSTCHANGE ) ;
2013-02-06 01:52:51 +04:00
} ;
static int kirkwood_cpufreq_verify ( struct cpufreq_policy * policy )
{
return cpufreq_frequency_table_verify ( policy , kirkwood_freq_table ) ;
}
static int kirkwood_cpufreq_target ( struct cpufreq_policy * policy ,
unsigned int target_freq ,
unsigned int relation )
{
unsigned int index = 0 ;
if ( cpufreq_frequency_table_target ( policy , kirkwood_freq_table ,
target_freq , relation , & index ) )
return - EINVAL ;
2013-03-24 10:26:43 +04:00
kirkwood_cpufreq_set_cpu_state ( policy , index ) ;
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 )
{
int result ;
/* cpuinfo and default policy values */
policy - > cpuinfo . transition_latency = 5000 ; /* 5uS */
policy - > cur = kirkwood_cpufreq_get_cpu_frequency ( 0 ) ;
result = cpufreq_frequency_table_cpuinfo ( policy , kirkwood_freq_table ) ;
if ( result )
return result ;
cpufreq_frequency_table_get_attr ( kirkwood_freq_table , policy - > cpu ) ;
return 0 ;
}
static int kirkwood_cpufreq_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
return 0 ;
}
static struct freq_attr * kirkwood_cpufreq_attr [ ] = {
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
static struct cpufreq_driver kirkwood_cpufreq_driver = {
. get = kirkwood_cpufreq_get_cpu_frequency ,
. verify = kirkwood_cpufreq_verify ,
. target = kirkwood_cpufreq_target ,
. init = kirkwood_cpufreq_cpu_init ,
. exit = kirkwood_cpufreq_cpu_exit ,
. name = " kirkwood-cpufreq " ,
. owner = THIS_MODULE ,
. attr = kirkwood_cpufreq_attr ,
} ;
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
np = of_find_node_by_path ( " /cpus/cpu@0 " ) ;
if ( ! np )
return - ENODEV ;
priv . cpu_clk = of_clk_get_by_name ( np , " cpu_clk " ) ;
if ( IS_ERR ( priv . cpu_clk ) ) {
dev_err ( priv . dev , " Unable to get cpuclk " ) ;
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 ) ) {
dev_err ( priv . dev , " Unable to get ddrclk " ) ;
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 ) ) {
dev_err ( priv . dev , " Unable to get powersave " ) ;
err = PTR_ERR ( priv . powersave_clk ) ;
goto out_ddr ;
}
clk_prepare ( priv . powersave_clk ) ;
of_node_put ( np ) ;
np = NULL ;
err = cpufreq_register_driver ( & kirkwood_cpufreq_driver ) ;
if ( ! err )
return 0 ;
dev_err ( priv . dev , " Failed to register cpufreq driver " ) ;
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 " ,
. owner = THIS_MODULE ,
} ,
} ;
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 " ) ;