blob: 7eacfd7d3df30abf622635e8d821c6e08b52c863 [file] [log] [blame]
/*
* ZTE zx297510 dvfs driver
*
* Copyright (C) 2013 ZTE Ltd.
* by zxp
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/suspend.h>
#include <linux/soc/zte/rpmsg.h>
//#include "mach/clock.h"
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include "zx-pm.h"
#define ZX_CPUFREQ_IOC_MAGIC 'W'
/*ioctl cmd usd by device*/
#define ZX_CPUFREQ_SET_FREQ _IOW(ZX_CPUFREQ_IOC_MAGIC, 1, char *)
#define ZX_CPUFREQ_GET_FREQ _IOW(ZX_CPUFREQ_IOC_MAGIC, 2, char *)
#define ZX_CPUFREQ_DEV "/dev/zx_cpufreq"
#define PM_FREQ_TRACE 1
#if PM_FREQ_TRACE
#define FREQ_CHANGE_COUNT 20
typedef struct
{
volatile unsigned int old_index;
volatile unsigned int new_idex;
volatile unsigned int time;
}freq_change_view_trace_t;
static freq_change_view_trace_t freq_change_view[FREQ_CHANGE_COUNT] ;
static unsigned int freq_change_index = 0;
static int cpufreq_driver_inited = 0;
void trace_freq_change(unsigned int old_index,unsigned int new_index)
{
freq_change_view[freq_change_index].old_index = old_index;
freq_change_view[freq_change_index].new_idex = new_index;
freq_change_view[freq_change_index].time = ktime_to_us(ktime_get());
freq_change_index++;
if(freq_change_index == FREQ_CHANGE_COUNT)
{
freq_change_index = 0;
}
}
#else
void trace_freq_change(unsigned int old_index,unsigned int new_index){}
#endif
unsigned int freq_change_enabled_by_startup = 0;
static struct delayed_work pm_freq_work;
#define PM_FREQ_DELAY msecs_to_jiffies(25000)
/* for count change time by M0 */
#define DEBUG_CPUFREQ_TIME 1
#ifdef CONFIG_DDR_FREQ
#ifdef CONFIG_ARCH_ZX297520V2
#define get_cur_ddr() pm_read_reg_16(AXI_CURRENT_FREQ)
#define set_target_ddr(f) pm_write_reg_16(AXI_AP2M0_TARGET, f)
#define set_ddr_req() pm_write_reg_16(AXI_AP2M0_FLAG, 1)
#define clr_ddr_ack() pm_write_reg_16(AXI_M02AP_ACK, 0)
#define wait_ddr_ack() while(!pm_read_reg_16(AXI_M02AP_ACK))
#else
static ddr_freq_regs *ddr_regs = (ddr_freq_regs *)IRAM_CHANGE_DDR_BASE;
#define get_cur_ddr() (ddr_regs->cur_freq)
#define set_target_ddr(f) (ddr_regs->ap_exp_freq = f)
#define set_ddr_req() (ddr_regs->ap_req_flag = 1)
#endif
#endif
//#undef CONFIG_AXI_FREQ
#ifdef CONFIG_AXI_FREQ
static DEFINE_MUTEX(axifreq_lock);
static axi_freq_regs *axi_regs; // = (axi_freq_regs *)IRAM_CHANGE_AXI_BASE;
static vol_dvs_regs *vol_regs; // = (vol_dvs_regs *)IRAM_CHANGE_DVS_BASE;
#define get_cur_axi() (axi_regs->cur_freq)
#define set_target_axi_sw(f) (axi_regs->ap_exp_freq = f)
#define set_axi_req() (axi_regs->ap_req_flag = 1)
#define get_target_axi_hw(addr) (pm_read_reg(addr)&(0x7))
#if 1
#define DDR_FREQ_156M_HW (0x4e)
#define DDR_FREQ_208M_HW (0x68)
#define DDR_FREQ_312M_HW (0x9c)
#define DDR_FREQ_400M_HW (0xc8)
#define set_ddr_freq_hw(addr,f) (pm_read_reg(addr)&(~0xff)|f)
#define set_ddr_freq_sync(addr,f) (pm_read_reg(addr)&(~0x1)|f)
#endif
#define get_cur_vol() (vol_regs->cur_vol)
#define set_target_vol(f) (vol_regs->ap_exp_vol = f)
#define set_vol_req() (vol_regs->ap_req_flag = 1)
#if 0
#define WAIT_AXI_ACK_TIMEOUT (jiffies + msecs_to_jiffies(2)) /* wait 2 ms, we count max 200us also */
#define wait_axi_ack(timeout) while(!pm_read_reg_16(AXI_M02AP_ACK) && time_before(jiffies, timeout))
#else
#define WAIT_AXI_ACK_TIMEOUT (200) /* wait 120us, we count max 200us also */
static void wait_axi_ack(unsigned timeout)
{
ktime_t begin_time = ktime_get();
while(((vol_regs->ap_req_flag) ||(axi_regs->ap_req_flag) )&& (unsigned)ktime_to_us(ktime_sub(ktime_get(), begin_time))<timeout);
}
#endif
static int send_msg_to_m0(void)
{
unsigned int ap_m0_buf = AXI_VOL_CHANGE_ICP_BUF; /* the icp interface need a buffer */
T_RpMsg_Msg Icp_Msg;
int ret;
Icp_Msg.coreID = CORE_M0;
Icp_Msg.chID = 1;
Icp_Msg.flag = RPMSG_WRITE_INT; /* 1- means send an icp interrupt> */
Icp_Msg.buf = &ap_m0_buf;
Icp_Msg.len = 0x4;
ret = rpmsgWrite(&Icp_Msg);
if(Icp_Msg.len == ret)
return 0;
else
return ret;
}
static int axi_freq_change_allowed(void)
{
if(pm_get_mask_info()&PM_NO_AXI_FREQ)
return false;
return true;
}
/**
* request to change vol.
*
* vol_dvs: input vol enum
*/
int request_vol(zx29_vol vol_dvs)
{
unsigned int current_vol = get_cur_vol();
set_target_vol(vol_dvs);
#if DEBUG_CPUFREQ_TIME
pm_printk("[CPUFREQ] current_vol(%d) request_vol(%d) \n",(u32)current_vol,(u32)vol_dvs);
#endif
if(vol_dvs != current_vol)
{
/* request freq */
set_vol_req();
}
return 0;
}
/**
* input axi freq.
*/
static zx29_vol request_vol_by_axi(zx29_axi_freq axi_freq)
{
if(axi_freq == AXI_FREQ_156M)
return VOL_VO_900;
else
return VOL_VO_850;
}
/**
* set vol .
*
* we will do this by M0.
*/
static int set_vol_by_axi(zx29_axi_freq axi_freq)
{
zx29_vol vol_dvs= request_vol_by_axi(axi_freq);
/* set new vol*/
return request_vol(vol_dvs);
}
/**
* request to change axi freq.
*
* axi_freq: input freq enum
*/
int request_axi_freq(zx29_axi_freq axi_freq)
{
unsigned int current_axi_freq = get_cur_axi();
unsigned int tmp;
int ret = 0;
#if DEBUG_CPUFREQ_TIME
ktime_t begin_time, end_time;
s64 total_time;
#endif
if(!axi_freq_change_allowed())
return 0;
#ifdef SET_AXI_BY_HW
tmp = (pm_read_reg(PS_MATRIX_AXI_SEL)&(~0x7))|axi_freq;
pm_write_reg(PS_MATRIX_AXI_SEL,tmp);
pm_printk("[CPUFREQ] current_axi_freq(%d) request_axi_freq(%d) after_request_axi_freq(%d) after_request_vol(%d)\n",(u32)current_axi_freq,(u32)axi_freq,get_cur_axi(),get_cur_vol());
#else
set_target_axi_sw(axi_freq);
if(axi_freq != current_axi_freq)
{
/* request freq */
set_axi_req();
// set_vol_by_axi(axi_freq);//set vol
ret = send_msg_to_m0();
#if DEBUG_CPUFREQ_TIME
begin_time = ktime_get();
#endif
if(!ret)
{
/* wait axi freq changed ok! we will set a timeout for safety~ */
wait_axi_ack(WAIT_AXI_ACK_TIMEOUT);
}
else
{
pm_printk("[CPUFREQ] request_axi_freq(%d) failed: (%d) \n",(u32)axi_freq, ret);
}
#if DEBUG_CPUFREQ_TIME
end_time = ktime_get();
total_time = ktime_to_us(ktime_sub(end_time, begin_time));
pm_printk("[CPUFREQ] total axi time: %d us current_axi_freq(%d) request_axi_freq(%d) after_request_axi_freq(%d) after_request_vol(%d)\n",(u32)total_time,(u32)current_axi_freq,(u32)axi_freq,get_cur_axi(),get_cur_vol());
}
else
{
pm_printk("[CPUFREQ] current_axi_freq(%d) request_axi_freq(%d) \n",(u32)current_axi_freq,(u32)axi_freq);
#endif
}
#endif
return 0;
}
/**
* input cpu freq [KHz].
*/
static zx29_axi_freq request_axi_freq_by_cpu(unsigned int freq)
{
if(freq >= 600*1000)
return AXI_FREQ_156M;
else
return AXI_FREQ_78M;
}
/**
* set axi freq .
*
* we will do this by M0.
*/
static int set_axi_frequency_by_cpu(unsigned int freq)
{
zx29_axi_freq axi_freq = request_axi_freq_by_cpu(freq);
/* set new freq */
return request_axi_freq(axi_freq);
}
int zx_request_axi_freq(unsigned int axifreq)
{
zx29_axi_freq axi_freq;
if (axifreq == 0xff)
return -EINVAL;
if(cpufreq_driver_inited==0)
return -EPERM;
if(axifreq >= 600*1000*1000)
return AXI_FREQ_156M;
else
return AXI_FREQ_78M;
return request_axi_freq(axi_freq);
}
#endif
#ifdef CONFIG_AXI_FREQ
/**
* zx_axifreq_pm_notifier - acquire axifreq in suspend-resume context
*
* @notifier
* @pm_event
* @v
*
*/
static int zx_axifreq_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *v)
{
mutex_lock(&axifreq_lock);
switch (pm_event)
{
case PM_SUSPEND_PREPARE:
request_axi_freq(AXI_FREQ_78M);
break;
case PM_POST_SUSPEND:
request_axi_freq(AXI_FREQ_156M);
break;
}
mutex_unlock(&axifreq_lock);
return NOTIFY_OK;
}
static struct notifier_block zx_axifreq_nb =
{
.notifier_call = zx_axifreq_pm_notifier,
};
static int __init zx29_axifreq_init(void)
{
/* pm notify */
register_pm_notifier(&zx_axifreq_nb);
// request_vol(VOL_VO_900);
request_axi_freq(AXI_FREQ_156M);
return 0;
}
//late_initcall(zx29_axifreq_init);
#endif
/*=============================================================================
*======== zx29 DDR freq ===============================================
*** ap/phy request --> m0 notify --> jump to iram --> wait completely --> ***
*** -->jump to ddr ***====
*=============================================================================
*/
#ifdef CONFIG_DDR_FREQ
static DEFINE_MUTEX(ddrfreq_lock);
static int ddr_freq_change_allowed(void)
{
if(pm_get_mask_info()&PM_NO_DDR_FREQ)
return false;
return true;
}
static int send_msg_to_ps(void)
{
unsigned int ap_m0_buf = AXI_VOL_CHANGE_ICP_BUF; /* the icp interface need a buffer */
T_ZDrvRpMsg_Msg Icp_Msg;
int ret;
Icp_Msg.actorID = PS_ID;
Icp_Msg.chID = ICP_CHANNEL_PSM;
Icp_Msg.flag = RPMSG_WRITE_INT; /* 1- means send an icp interrupt> */
Icp_Msg.buf = &ap_m0_buf;
Icp_Msg.len = 0x4;
ret = zDrvRpMsg_Write(&Icp_Msg);
if(Icp_Msg.len == ret)
return 0;
else
return ret;
}
int request_ddr_freq_hw(unsigned int ddr_freq)
{
if(!ddr_freq_change_allowed())
return 0;
pm_write_reg(AP_DDR_FFC_SEL_SYNC,0x0);
pm_write_reg(AP_DDR_FFC_SEL,ddr_freq);
pm_write_reg(AP_DDR_FFC_SEL_SYNC,0x1);
return 0;
}
int request_ddr_freq(zx29_ddr_freq ddr_freq)
{
int ret = 0;
unsigned current_ddr_freq = get_cur_ddr();
if(!ddr_freq_change_allowed())
return 0;
if(ddr_freq == current_ddr_freq)
return 0;
#ifdef SET_DDR_BY_HW
//set_ddr_freq_hw(AP_DDR_FFC_SEL, ddr_exp_freq);
set_ddr_freq_sync(AP_DDR_FFC_SEL_SYNC,0x1);
#else
set_target_ddr(ddr_freq);
ret = send_msg_to_ps();
if(!ret)
{
printk("[DDRFREQ] ddr_freq [%d]\n",get_cur_ddr());
}
else
{
printk("[DDRFREQ] request_ddr_freq failed\n");
}
#endif
#if 0
unsigned current_ddr_freq = get_cur_ddr();
int ret = 0;
#if DEBUG_CPUFREQ_TIME
ktime_t begin_time, end_time;
s64 total_time;
#endif
if(!ddr_freq_change_allowed())
return 0;
set_target_ddr(ddr_freq);
if(ddr_freq != current_ddr_freq)
{
/* request freq */
clr_ddr_ack();
set_ddr_req();
#if DEBUG_CPUFREQ_TIME
begin_time = ktime_get();
#endif
ret = send_msg_to_m0();
if(!ret)
/* wait axi freq changed ok! we will set a timeout for safety~ */
wait_ddr_ack();
else
pr_info("[CPUFREQ] request_ddr_freq(%d) failed: (%d) \n",(u32)ddr_freq, ret);
#if DEBUG_CPUFREQ_TIME
end_time = ktime_get();
total_time = ktime_to_us(ktime_sub(end_time, begin_time));
pr_info("[CPUFREQ] total ddr time: %d us\n",(u32)total_time);
#endif
}
#endif
return 0;
}
#ifdef CONFIG_ARCH_ZX297520V2
static void ddr_freq_handler(void)
{
local_irq_disable();
waiting_ddr_dfs((unsigned long)DDR_DFS_CODE_ADDR);
local_irq_enable();
}
#else
static int zx_ddrfreq_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *v)
{
mutex_lock(&ddrfreq_lock);
switch (pm_event)
{
case PM_SUSPEND_PREPARE:
request_ddr_freq_hw(0);
break;
case PM_POST_SUSPEND:
request_ddr_freq_hw(0x9c);
break;
}
mutex_unlock(&ddrfreq_lock);
return NOTIFY_OK;
}
static struct notifier_block zx_ddrfreq_nb =
{
.notifier_call = zx_ddrfreq_pm_notifier,
};
#endif
static int __init zx29_ddrfreq_init(void)
{
#ifdef CONFIG_ARCH_ZX297520V2
#else
register_pm_notifier(&zx_ddrfreq_nb);
#endif
return 0;
}
#endif
static void pm_m0_handler(void *buf, unsigned int len)
{
/* deal msg from m0 */
}
int zx29_cpufreq_init(void)
{
if(cpufreq_driver_inited)
return 0;
axi_regs = (axi_freq_regs *)IRAM_CHANGE_AXI_BASE;
vol_regs = (vol_dvs_regs *)IRAM_CHANGE_DVS_BASE;
cpufreq_driver_inited = 1;
pr_info("[CPUFREQ] zx29_cpufreq_init ok \n");
return 0;
}