From 0848535425802fb233db122bda8b7dde3dcd3bef Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E9=BB=84=E6=B6=9B?= Date: Fri, 30 Nov 2012 10:59:55 +0800 Subject: [PATCH] rk: add new ddr freq implementation --- arch/arm/mach-rk2928/Makefile | 1 - arch/arm/mach-rk30/Makefile | 1 - arch/arm/plat-rk/Makefile | 2 +- arch/arm/plat-rk/ddr_freq.c | 426 ++++++++++++++++++++++++++ arch/arm/plat-rk/include/plat/board.h | 20 ++ 5 files changed, 447 insertions(+), 3 deletions(-) create mode 100644 arch/arm/plat-rk/ddr_freq.c diff --git a/arch/arm/mach-rk2928/Makefile b/arch/arm/mach-rk2928/Makefile index 4f5a6490fb59..96b8d68a4d82 100755 --- a/arch/arm/mach-rk2928/Makefile +++ b/arch/arm/mach-rk2928/Makefile @@ -8,7 +8,6 @@ obj-y += iomux.o obj-y += ../plat-rk/clock.o obj-y += clock_data.o obj-y += ddr.o -obj-$(CONFIG_DDR_FREQ) += ddr_freq.o obj-$(CONFIG_CPU_FREQ) += cpufreq.o obj-$(CONFIG_DVFS) += dvfs.o obj-$(CONFIG_PM) += pm.o diff --git a/arch/arm/mach-rk30/Makefile b/arch/arm/mach-rk30/Makefile index 93f7aa42ee2f..e3775078b630 100755 --- a/arch/arm/mach-rk30/Makefile +++ b/arch/arm/mach-rk30/Makefile @@ -23,7 +23,6 @@ CFLAGS_pm.o += -mthumb obj-$(CONFIG_CPU_IDLE) += cpuidle.o obj-$(CONFIG_CPU_FREQ) += cpufreq.o obj-$(CONFIG_DVFS) += dvfs.o -obj-$(CONFIG_DDR_FREQ) += ddr_freq.o obj-$(CONFIG_RK30_I2C_INSRAM) += i2c_sram.o CFLAGS_i2c_sram.o += -mthumb diff --git a/arch/arm/plat-rk/Makefile b/arch/arm/plat-rk/Makefile index 05a0f2857770..3349bfd4e591 100644 --- a/arch/arm/plat-rk/Makefile +++ b/arch/arm/plat-rk/Makefile @@ -12,4 +12,4 @@ obj-y += config.o obj-y += sram.o CFLAGS_sram.o += -mthumb obj-$(CONFIG_DDR_TEST) += memtester.o ddr_test.o -obj-$(CONFIG_DDR_FREQ) += video_state.o +obj-$(CONFIG_DDR_FREQ) += ddr_freq.o diff --git a/arch/arm/plat-rk/ddr_freq.c b/arch/arm/plat-rk/ddr_freq.c new file mode 100644 index 000000000000..48c7d1f9aa79 --- /dev/null +++ b/arch/arm/plat-rk/ddr_freq.c @@ -0,0 +1,426 @@ +#define pr_fmt(fmt) "ddrfreq: " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +enum { + DEBUG_DDR = 1U << 0, + DEBUG_VIDEO_STATE = 1U << 1, + DEBUG_SUSPEND = 1U << 2, + DEBUG_VERBOSE = 1U << 3, +}; +static int debug_mask = DEBUG_DDR; +module_param(debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); +#define dprintk(mask, fmt, ...) do { if (mask & debug_mask) pr_info(fmt, ##__VA_ARGS__); } while (0) + +#define MHZ (1000*1000) +#define KHZ 1000 + +enum SYS_STATUS { + SYS_STATUS_SUSPEND = 0, // 0x01 + SYS_STATUS_VIDEO, // 0x02 + SYS_STATUS_GPU, // 0x04 + SYS_STATUS_RGA, // 0x08 + SYS_STATUS_CIF0, // 0x10 + SYS_STATUS_CIF1, // 0x20 +}; + +struct ddr { +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif + struct clk *pll; + struct clk *clk; + unsigned long normal_rate; + unsigned long video_rate; + unsigned long idle_rate; + unsigned long suspend_rate; + char video_state; + bool auto_self_refresh; + char *mode; + unsigned long sys_status; + struct task_struct *task; + wait_queue_head_t wait; +} ddr; + +module_param_named(sys_status, ddr.sys_status, ulong, S_IRUGO); +module_param_named(video_state, ddr.video_state, byte, S_IRUGO); +module_param_named(auto_self_refresh, ddr.auto_self_refresh, bool, S_IRUGO); +module_param_named(mode, ddr.mode, charp, S_IRUGO); + +static noinline void ddrfreq_set_sys_status(enum SYS_STATUS status) +{ + set_bit(status, &ddr.sys_status); + wake_up(&ddr.wait); +} + +static noinline void ddrfreq_clear_sys_status(enum SYS_STATUS status) +{ + clear_bit(status, &ddr.sys_status); + wake_up(&ddr.wait); +} + +static void ddrfreq_mode(bool auto_self_refresh, unsigned long *target_rate, char *name) +{ + ddr.mode = name; + if (auto_self_refresh != ddr.auto_self_refresh) { + ddr_set_auto_self_refresh(auto_self_refresh); + ddr.auto_self_refresh = auto_self_refresh; + dprintk(DEBUG_DDR, "change auto self refresh to %d when %s\n", auto_self_refresh, name); + } + if (*target_rate != clk_get_rate(ddr.clk)) { + if (clk_set_rate(ddr.clk, *target_rate) == 0) { + *target_rate = clk_get_rate(ddr.clk); + dprintk(DEBUG_DDR, "change freq to %lu MHz when %s\n", *target_rate / MHZ, name); + } + } +} + +static noinline void ddrfreq_work(unsigned long sys_status) +{ + unsigned long s = sys_status; + + dprintk(DEBUG_VERBOSE, "sys_status %02lx\n", sys_status); + if (s & (1 << SYS_STATUS_SUSPEND)) { + ddrfreq_mode(true, &ddr.suspend_rate, "suspend"); + } else if (s & (1 << SYS_STATUS_VIDEO)) { + ddrfreq_mode(false, &ddr.video_rate, "video"); + } else if (ddr.idle_rate + && !(s & (1 << SYS_STATUS_GPU)) + && !(s & (1 << SYS_STATUS_RGA)) + && !(s & (1 << SYS_STATUS_CIF0)) + && !(s & (1 << SYS_STATUS_CIF1))) { + ddrfreq_mode(false, &ddr.idle_rate, "idle"); + } else { + ddrfreq_mode(false, &ddr.normal_rate, "normal"); + } +} + +static int ddrfreq_task(void *data) +{ + static bool is_booting = true; + set_freezable(); + + do { + unsigned long status = ddr.sys_status; + if (is_booting) { + s64 boottime_ms = ktime_to_ms(ktime_get_boottime()); + if (boottime_ms > 20 * MSEC_PER_SEC) { + is_booting = false; + } + } + if (!is_booting) { + ddrfreq_work(status); + } + wait_event_freezable(ddr.wait, (status != ddr.sys_status) || kthread_should_stop()); + } while (!kthread_should_stop()); + + return 0; +} + +#ifdef CONFIG_SMP +static volatile bool __sramdata cpu1_pause; +static inline bool is_cpu1_paused(void) { smp_rmb(); return cpu1_pause; } +static inline void set_cpu1_pause(bool pause) { cpu1_pause = pause; smp_wmb(); } +#define MAX_TIMEOUT (16000000UL << 6) //>0.64s + +static void __ddr_change_freq(void *info) +{ + uint32_t *value = info; + u32 timeout = MAX_TIMEOUT; + + while (!is_cpu1_paused() && --timeout); + if (timeout == 0) + return; + + *value = ddr_change_freq(*value); + + set_cpu1_pause(false); +} + +/* Do not use stack, safe on SMP */ +static void __sramfunc pause_cpu1(void *info) +{ + u32 timeout = MAX_TIMEOUT; + unsigned long flags; + local_irq_save(flags); + + set_cpu1_pause(true); + while (is_cpu1_paused() && --timeout); + + local_irq_restore(flags); +} + +static uint32_t _ddr_change_freq(uint32_t nMHz) +{ + int this_cpu = get_cpu(); + + set_cpu1_pause(false); + if (this_cpu == 0) { + if (smp_call_function_single(1, (smp_call_func_t)pause_cpu1, NULL, 0) == 0) { + u32 timeout = MAX_TIMEOUT; + while (!is_cpu1_paused() && --timeout); + if (timeout == 0) + goto out; + } + + nMHz = ddr_change_freq(nMHz); + + set_cpu1_pause(false); + } else { + smp_call_function_single(0, __ddr_change_freq, &nMHz, 0); + + pause_cpu1(NULL); + } + +out: + put_cpu(); + + return nMHz; +} +#else +static uint32_t _ddr_change_freq(uint32_t nMHz) +{ + return ddr_change_freq(nMHz); +} +#endif + +uint32_t ddr_set_rate(uint32_t nMHz) +{ + nMHz = _ddr_change_freq(nMHz); + clk_set_rate(ddr.pll, 0); + return nMHz; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void ddrfreq_early_suspend(struct early_suspend *h) +{ + dprintk(DEBUG_SUSPEND, "early suspend\n"); + ddrfreq_set_sys_status(SYS_STATUS_SUSPEND); +} + +static void ddrfreq_late_resume(struct early_suspend *h) +{ + dprintk(DEBUG_SUSPEND, "late resume\n"); + ddrfreq_clear_sys_status(SYS_STATUS_SUSPEND); +} +#endif + +static int video_state_release(struct inode *inode, struct file *file) +{ + dprintk(DEBUG_VIDEO_STATE, "video_state release\n"); + ddr.video_state = '0'; + ddrfreq_clear_sys_status(SYS_STATUS_VIDEO); + return 0; +} + +static ssize_t video_state_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char state; + + if (count < 1) + return count; + if (copy_from_user(&state, buffer, 1)) { + return -EFAULT; + } + + dprintk(DEBUG_VIDEO_STATE, "video_state write %c\n", state); + switch (state) { + case '0': + ddrfreq_clear_sys_status(SYS_STATUS_VIDEO); + break; + case '1': + ddrfreq_set_sys_status(SYS_STATUS_VIDEO); + break; + default: + return -EINVAL; + } + ddr.video_state = state; + return count; +} + +static const struct file_operations video_state_fops = { + .owner = THIS_MODULE, + .release= video_state_release, + .write = video_state_write, +}; + +static struct miscdevice video_state_dev = { + .fops = &video_state_fops, + .name = "video_state", + .minor = MISC_DYNAMIC_MINOR, +}; + +static int ddrfreq_clk_event(enum SYS_STATUS status, unsigned long event) +{ + switch (event) { + case CLK_PRE_ENABLE: + ddrfreq_set_sys_status(status); + break; + case CLK_ABORT_ENABLE: + case CLK_POST_DISABLE: + ddrfreq_clear_sys_status(status); + break; + } + + return NOTIFY_OK; +} + +#define CLK_NOTIFIER(name, status) \ +static int ddrfreq_clk_##name##_event(struct notifier_block *this, unsigned long event, void *ptr) \ +{ \ + return ddrfreq_clk_event(SYS_STATUS_##status, event); \ +} \ +static struct notifier_block ddrfreq_clk_##name##_notifier = { .notifier_call = ddrfreq_clk_##name##_event }; + +#define REGISTER_CLK_NOTIFIER(name) \ +do { \ + struct clk *clk = clk_get(NULL, #name); \ + clk_notifier_register(clk, &ddrfreq_clk_##name##_notifier); \ + clk_put(clk); \ +} while (0) + +#define UNREGISTER_CLK_NOTIFIER(name) \ +do { \ + struct clk *clk = clk_get(NULL, #name); \ + clk_notifier_unregister(clk, &ddrfreq_clk_##name##_notifier); \ + clk_put(clk); \ +} while (0) + +CLK_NOTIFIER(pd_gpu, GPU); +CLK_NOTIFIER(pd_rga, RGA); +CLK_NOTIFIER(pd_cif0, CIF0); +CLK_NOTIFIER(pd_cif1, CIF1); + +static int ddrfreq_init(void) +{ + int i, ret; + struct cpufreq_frequency_table *table; + + init_waitqueue_head(&ddr.wait); + ddr.video_state = '0'; + ddr.mode = "normal"; + + ddr.pll = clk_get(NULL, "ddr_pll"); + ddr.clk = clk_get(NULL, "ddr"); + if (IS_ERR(ddr.clk)) { + ret = PTR_ERR(ddr.clk); + ddr.clk = NULL; + pr_err("failed to get ddr clk, error %d\n", ret); + return ret; + } + + ddr.normal_rate = clk_get_rate(ddr.clk); + ddr.video_rate = 300 * MHZ; + ddr.suspend_rate = 200 * MHZ; + + table = dvfs_get_freq_volt_table(ddr.clk); + if (!table) { + pr_err("failed to get ddr freq volt table\n"); + } + for (i = 0; table && table[i].frequency != CPUFREQ_TABLE_END; i++) { + unsigned int mode = table[i].frequency % 1000; + unsigned long rate; + + table[i].frequency -= mode; + rate = table[i].frequency * 1000; + + switch (mode) { + case DDR_FREQ_NORMAL: + ddr.normal_rate = rate; + break; + case DDR_FREQ_VIDEO: + ddr.video_rate = rate; + break; + case DDR_FREQ_IDLE: + ddr.idle_rate = rate; + break; + case DDR_FREQ_SUSPEND: + ddr.suspend_rate = rate; + break; + } + } + + if (ddr.idle_rate) { + REGISTER_CLK_NOTIFIER(pd_gpu); + REGISTER_CLK_NOTIFIER(pd_rga); + REGISTER_CLK_NOTIFIER(pd_cif0); + REGISTER_CLK_NOTIFIER(pd_cif1); + } + + return 0; + +} +core_initcall(ddrfreq_init); + +static int ddrfreq_late_init(void) +{ + int ret; + struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; + + if (!ddr.clk) { + return -EINVAL; + } + + ret = misc_register(&video_state_dev); + if (unlikely(ret)) { + pr_err("failed to register video_state misc device! error %d\n", ret); + goto err; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + ddr.early_suspend.suspend = ddrfreq_early_suspend; + ddr.early_suspend.resume = ddrfreq_late_resume; + ddr.early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 50; + register_early_suspend(&ddr.early_suspend); +#endif + + ddr.task = kthread_create(ddrfreq_task, NULL, "ddrfreqd"); + if (IS_ERR(ddr.task)) { + ret = PTR_ERR(ddr.task); + pr_err("failed to create kthread! error %d\n", ret); + goto err1; + } + + sched_setscheduler_nocheck(ddr.task, SCHED_FIFO, ¶m); + get_task_struct(ddr.task); + kthread_bind(ddr.task, 0); + wake_up_process(ddr.task); + + pr_info("verion 1.0\n"); + dprintk(DEBUG_VERBOSE, "normal %luMHz video %luMHz idle %luMHz suspend %luMHz\n", + ddr.normal_rate / MHZ, ddr.video_rate / MHZ, ddr.idle_rate / MHZ, ddr.suspend_rate / MHZ); + + return 0; + +err1: +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ddr.early_suspend); +#endif + misc_deregister(&video_state_dev); +err: + if (ddr.idle_rate) { + UNREGISTER_CLK_NOTIFIER(pd_gpu); + UNREGISTER_CLK_NOTIFIER(pd_rga); + UNREGISTER_CLK_NOTIFIER(pd_cif0); + UNREGISTER_CLK_NOTIFIER(pd_cif1); + } + + return ret; +} +late_initcall(ddrfreq_late_init); diff --git a/arch/arm/plat-rk/include/plat/board.h b/arch/arm/plat-rk/include/plat/board.h index 19683e2f0de5..b0a21cf350cf 100755 --- a/arch/arm/plat-rk/include/plat/board.h +++ b/arch/arm/plat-rk/include/plat/board.h @@ -364,4 +364,24 @@ void rk28_send_wakeup_key(void); phys_addr_t __init board_mem_reserve_add(char *name, size_t size); void __init board_mem_reserved(void); +/* + * For DDR frequency scaling setup. Board code something like this: + * + * This array _must_ be sorted in ascending frequency (without DDR_FREQ_*) order. + * 必须按频率(不必考虑DDR_FREQ_*)递增。 + *static struct cpufreq_frequency_table dvfs_ddr_table[] = { + * {.frequency = 200 * 1000 + DDR_FREQ_SUSPEND, .index = xxxx * 1000}, + * {.frequency = 200 * 1000 + DDR_FREQ_IDLE, .index = xxxx * 1000}, + * {.frequency = 300 * 1000 + DDR_FREQ_VIDEO, .index = xxxx * 1000}, + * {.frequency = 400 * 1000 + DDR_FREQ_NORMAL, .index = xxxx * 1000}, + * {.frequency = CPUFREQ_TABLE_END}, + *}; + */ +enum ddr_freq_mode { + DDR_FREQ_NORMAL = 1, // default + DDR_FREQ_VIDEO, // when video is playing + DDR_FREQ_IDLE, // when screen is idle + DDR_FREQ_SUSPEND, // when early suspend +}; + #endif -- 2.34.1