2013-01-28 20:13:15 +04:00
/*
* Copyright ( C ) 2012 Calxeda , Inc .
*
* 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 .
*
* This driver provides the clk notifier callbacks that are used when
2014-09-09 18:28:03 +04:00
* the cpufreq - dt driver changes to frequency to alert the highbank
2013-01-28 20:13:15 +04:00
* EnergyCore Management Engine ( ECME ) about the need to change
* voltage . The ECME interfaces with the actual voltage regulators .
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/clk.h>
# include <linux/cpu.h>
# include <linux/err.h>
# include <linux/of.h>
2014-06-12 21:00:34 +04:00
# include <linux/pl320-ipc.h>
2013-01-30 18:27:49 +04:00
# include <linux/platform_device.h>
2013-01-28 20:13:15 +04:00
# define HB_CPUFREQ_CHANGE_NOTE 0x80000001
# define HB_CPUFREQ_IPC_LEN 7
# define HB_CPUFREQ_VOLT_RETRIES 15
static int hb_voltage_change ( unsigned int freq )
{
2013-02-25 15:07:45 +04:00
u32 msg [ HB_CPUFREQ_IPC_LEN ] = { HB_CPUFREQ_CHANGE_NOTE , freq / 1000000 } ;
2013-01-28 20:13:15 +04:00
return pl320_ipc_transmit ( msg ) ;
}
static int hb_cpufreq_clk_notify ( struct notifier_block * nb ,
unsigned long action , void * hclk )
{
struct clk_notifier_data * clk_data = hclk ;
int i = 0 ;
if ( action = = PRE_RATE_CHANGE ) {
if ( clk_data - > new_rate > clk_data - > old_rate )
while ( hb_voltage_change ( clk_data - > new_rate ) )
if ( i + + > HB_CPUFREQ_VOLT_RETRIES )
return NOTIFY_BAD ;
} else if ( action = = POST_RATE_CHANGE ) {
if ( clk_data - > new_rate < clk_data - > old_rate )
while ( hb_voltage_change ( clk_data - > new_rate ) )
if ( i + + > HB_CPUFREQ_VOLT_RETRIES )
return NOTIFY_BAD ;
}
return NOTIFY_DONE ;
}
static struct notifier_block hb_cpufreq_clk_nb = {
. notifier_call = hb_cpufreq_clk_notify ,
} ;
static int hb_cpufreq_driver_init ( void )
{
2014-09-09 18:28:03 +04:00
struct platform_device_info devinfo = { . name = " cpufreq-dt " , } ;
2013-01-28 20:13:15 +04:00
struct device * cpu_dev ;
struct clk * cpu_clk ;
struct device_node * np ;
int ret ;
2013-10-01 19:30:24 +04:00
if ( ( ! of_machine_is_compatible ( " calxeda,highbank " ) ) & &
( ! of_machine_is_compatible ( " calxeda,ecx-2000 " ) ) )
2013-01-28 20:13:15 +04:00
return - ENODEV ;
cpu_dev = get_cpu_device ( 0 ) ;
if ( ! cpu_dev ) {
pr_err ( " failed to get highbank cpufreq device \n " ) ;
2013-06-17 18:07:28 +04:00
return - ENODEV ;
2013-01-28 20:13:15 +04:00
}
2013-06-17 18:07:28 +04:00
np = of_node_get ( cpu_dev - > of_node ) ;
if ( ! np ) {
pr_err ( " failed to find highbank cpufreq node \n " ) ;
return - ENOENT ;
}
2013-01-28 20:13:15 +04:00
cpu_clk = clk_get ( cpu_dev , NULL ) ;
if ( IS_ERR ( cpu_clk ) ) {
ret = PTR_ERR ( cpu_clk ) ;
pr_err ( " failed to get cpu0 clock: %d \n " , ret ) ;
goto out_put_node ;
}
ret = clk_notifier_register ( cpu_clk , & hb_cpufreq_clk_nb ) ;
if ( ret ) {
pr_err ( " failed to register clk notifier: %d \n " , ret ) ;
goto out_put_node ;
}
2014-09-09 18:28:03 +04:00
/* Instantiate cpufreq-dt */
2013-01-30 18:27:49 +04:00
platform_device_register_full ( & devinfo ) ;
2013-01-28 20:13:15 +04:00
out_put_node :
of_node_put ( np ) ;
return ret ;
}
module_init ( hb_cpufreq_driver_init ) ;
MODULE_AUTHOR ( " Mark Langsdorf <mark.langsdorf@calxeda.com> " ) ;
MODULE_DESCRIPTION ( " Calxeda Highbank cpufreq driver " ) ;
MODULE_LICENSE ( " GPL " ) ;