| /* |
| * 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; |
| } |
| |