From 85d90d7d61ca4edb12a2f03a39278893f02715c8 Mon Sep 17 00:00:00 2001 From: Todd Poynor Date: Tue, 21 Dec 2010 11:12:31 -0800 Subject: [PATCH 1/7] ARM: tegra: cpufreq thermal throttling cleanups Various review feedback, including: Keep a global throttling index that specifies a ceiling CPU speed, lowered by one at each delay interval (while the temperature alarm continues to be signalled). Avoid lowering the throttle too far based on a transitory lowering of speed requested by the governor. Restore governor-requested speed when throttling turned off. Add cpufreq sysfs attribute for checking throttling state. Make throttling workqueue high-priority. Cosmetic changes. Change-Id: I068bf32115927fa61282f17f4a8798f2aee0b530 Signed-off-by: Todd Poynor --- arch/arm/mach-tegra/cpu-tegra.c | 108 ++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 47 deletions(-) diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index a878545d2740..2d629461a9b8 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -37,7 +37,10 @@ #include #include -/* Frequency table index must be sequential starting at 0 and frequencies must be ascending*/ +/* + * Frequency table index must be sequential starting at 0 and frequencies + * must be ascending. + */ static struct cpufreq_frequency_table freq_table[] = { { 0, 216000 }, { 1, 312000 }, @@ -61,44 +64,35 @@ static bool is_suspended; unsigned int tegra_getspeed(unsigned int cpu); static int tegra_update_cpu_speed(unsigned long rate); - -/* CPU frequency is gradually lowered when throttling is enabled */ -#define THROTTLE_START_INDEX 2 -#define THROTTLE_END_INDEX 6 +static unsigned long tegra_cpu_highest_speed(void); #ifdef CONFIG_TEGRA_THERMAL_THROTTLE +/* CPU frequency is gradually lowered when throttling is enabled */ +#define THROTTLE_LOWEST_INDEX 2 /* 456000 */ +#define THROTTLE_HIGHEST_INDEX 6 /* 912000 */ #define THROTTLE_DELAY msecs_to_jiffies(2000) -#define NO_DELAY msecs_to_jiffies(0) -static DEFINE_MUTEX(throttling_lock); static bool is_throttling; +static int throttle_index; +static int throttle_next_index; static struct delayed_work throttle_work; static struct workqueue_struct *workqueue; #define tegra_cpu_is_throttling() (is_throttling) -static bool tegra_throttling_needed(unsigned long *rate) -{ - unsigned int current_freq = tegra_getspeed(0); - int i; - - for (i = THROTTLE_END_INDEX; i >= THROTTLE_START_INDEX; i--) { - if (freq_table[i].frequency < current_freq) { - *rate = freq_table[i].frequency; - return true; - } - } - - return false; -} - static void tegra_throttle_work_func(struct work_struct *work) { - unsigned long rate; + unsigned int current_freq; mutex_lock(&tegra_cpu_lock); + current_freq = tegra_getspeed(0); + throttle_index = throttle_next_index; - if (tegra_throttling_needed(&rate) && tegra_update_cpu_speed(rate) == 0) { + if (freq_table[throttle_index].frequency < current_freq) + tegra_update_cpu_speed(freq_table[throttle_index].frequency); + + if (throttle_index > THROTTLE_LOWEST_INDEX) { + throttle_next_index = throttle_index - 1; queue_delayed_work(workqueue, &throttle_work, THROTTLE_DELAY); } @@ -111,20 +105,48 @@ static void tegra_throttle_work_func(struct work_struct *work) */ void tegra_throttling_enable(bool enable) { - mutex_lock(&throttling_lock); + mutex_lock(&tegra_cpu_lock); if (enable && !is_throttling) { + unsigned int current_freq = tegra_getspeed(0); + is_throttling = true; - queue_delayed_work(workqueue, &throttle_work, NO_DELAY); + + for (throttle_index = THROTTLE_HIGHEST_INDEX; + throttle_index >= THROTTLE_LOWEST_INDEX; + throttle_index--) + if (freq_table[throttle_index].frequency + < current_freq) + break; + + throttle_index = max(throttle_index, THROTTLE_LOWEST_INDEX); + throttle_next_index = throttle_index; + queue_delayed_work(workqueue, &throttle_work, 0); } else if (!enable && is_throttling) { cancel_delayed_work_sync(&throttle_work); is_throttling = false; + /* restore speed requested by governor */ + tegra_update_cpu_speed(tegra_cpu_highest_speed()); } - mutex_unlock(&throttling_lock); + mutex_unlock(&tegra_cpu_lock); } EXPORT_SYMBOL_GPL(tegra_throttling_enable); +static unsigned int throttle_governor_speed(unsigned int requested_speed) +{ + return tegra_cpu_is_throttling() ? + min(requested_speed, freq_table[throttle_index].frequency) : + requested_speed; +} + +static ssize_t show_throttle(struct cpufreq_policy *policy, char *buf) +{ + return sprintf(buf, "%u\n", is_throttling); +} + +cpufreq_freq_attr_ro(throttle); + #ifdef CONFIG_DEBUG_FS static int throttle_debug_set(void *data, u64 val) { @@ -170,6 +192,7 @@ module_exit(tegra_cpu_debug_exit); #else /* CONFIG_TEGRA_THERMAL_THROTTLE */ #define tegra_cpu_is_throttling() (0) +#define throttle_governor_speed(requested_speed) (requested_speed) void tegra_throttling_enable(bool enable) { @@ -289,25 +312,7 @@ static int tegra_target(struct cpufreq_policy *policy, freq = freq_table[idx].frequency; target_cpu_speed[policy->cpu] = freq; - - new_speed = tegra_cpu_highest_speed(); - - /* Do not go above this frequency when throttling */ - - if (tegra_cpu_is_throttling()) { - unsigned int throttle_limit = - freq_table[THROTTLE_START_INDEX].frequency; - - if (new_speed > throttle_limit) { - if (tegra_getspeed(0) < throttle_limit) { - new_speed = throttle_limit; - } else { - ret = -EBUSY; - goto out; - } - } - } - + new_speed = throttle_governor_speed(tegra_cpu_highest_speed()); ret = tegra_update_cpu_speed(new_speed); out: mutex_unlock(&tegra_cpu_lock); @@ -381,6 +386,9 @@ static int tegra_cpu_exit(struct cpufreq_policy *policy) static struct freq_attr *tegra_cpufreq_attr[] = { &cpufreq_freq_attr_scaling_available_freqs, +#ifdef CONFIG_TEGRA_THERMAL_THROTTLE + &throttle, +#endif NULL, }; @@ -397,7 +405,13 @@ static struct cpufreq_driver tegra_cpufreq_driver = { static int __init tegra_cpufreq_init(void) { #ifdef CONFIG_TEGRA_THERMAL_THROTTLE - workqueue = create_singlethread_workqueue("cpu-tegra"); + /* + * High-priority, others flags default: not bound to a specific + * CPU, has rescue worker task (in case of allocation deadlock, + * etc.). Single-threaded. + */ + workqueue = alloc_workqueue("cpu-tegra", + WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1); if (!workqueue) return -ENOMEM; INIT_DELAYED_WORK(&throttle_work, tegra_throttle_work_func); From e5603f1350136048ea2bf735771fc00eeff20532 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 29 Dec 2010 19:51:35 -0800 Subject: [PATCH 2/7] ARM: smp_twd: Avoid recalibrating local timer Change-Id: I10af3139ecd0dc1ef54e7a8e5258ee6fb29bfb0c Signed-off-by: Colin Cross --- arch/arm/kernel/smp_twd.c | 77 +++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 014862ae170c..97f934967850 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -26,7 +26,7 @@ void __iomem *twd_base; static unsigned long twd_timer_rate; static unsigned long twd_periphclk_prescaler; -static unsigned long twd_target_rate; +static unsigned long twd_cpu_rate; static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) @@ -82,6 +82,12 @@ int twd_timer_ack(void) return 0; } +/* + * Recalculate the twd prescaler value when the cpu frequency changes. To + * prevent early timer interrupts, must be called before changing the cpu + * frequency if the frequency is increasing, or after if the frequency is + * decreasing. + */ void twd_recalc_prescaler(unsigned long new_rate) { u32 ctrl; @@ -90,6 +96,8 @@ void twd_recalc_prescaler(unsigned long new_rate) BUG_ON(twd_periphclk_prescaler == 0 || twd_timer_rate == 0); + twd_cpu_rate = new_rate; + periphclk_rate = new_rate / twd_periphclk_prescaler; prescaler = DIV_ROUND_UP(periphclk_rate, twd_timer_rate); @@ -106,55 +114,60 @@ static void __cpuinit twd_calibrate_rate(unsigned long target_rate, { unsigned long load, count; u64 waitjiffies; - unsigned long cpu_rate; /* * If this is the first time round, we need to work out how fast * the timer ticks */ - printk(KERN_INFO "Calibrating local timer... "); + if (twd_timer_rate == 0) { + printk(KERN_INFO "Calibrating local timer... "); - /* Wait for a tick to start */ - waitjiffies = get_jiffies_64() + 1; + /* Wait for a tick to start */ + waitjiffies = get_jiffies_64() + 1; - while (get_jiffies_64() < waitjiffies) - udelay(10); + while (get_jiffies_64() < waitjiffies) + udelay(10); - /* OK, now the tick has started, let's get the timer going */ - waitjiffies += 5; + /* OK, now the tick has started, let's get the timer going */ + waitjiffies += 5; - /* enable, no interrupt or reload */ - __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL); + /* enable, no interrupt or reload */ + __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL); - /* maximum value */ - __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER); + /* maximum value */ + __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER); - while (get_jiffies_64() < waitjiffies) - udelay(10); + while (get_jiffies_64() < waitjiffies) + udelay(10); - count = __raw_readl(twd_base + TWD_TIMER_COUNTER); + count = __raw_readl(twd_base + TWD_TIMER_COUNTER); - twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5); + twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5); - /* - * If a target rate has been requested, adjust the TWD prescaler - * to get the closest lower frequency. - */ - if (target_rate) { - twd_periphclk_prescaler = periphclk_prescaler; - twd_target_rate = target_rate; + /* + * If a target rate has been requested, adjust the TWD prescaler + * to get the closest lower frequency. + */ + if (target_rate) { + twd_periphclk_prescaler = periphclk_prescaler; - printk("%lu.%02luMHz, setting to ", - twd_timer_rate / 1000000, + printk("%lu.%02luMHz, setting to ", + twd_timer_rate / 1000000, + (twd_timer_rate / 10000) % 100); + twd_cpu_rate = twd_timer_rate * periphclk_prescaler; + twd_timer_rate = target_rate; + twd_recalc_prescaler(twd_cpu_rate); + } + + printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000, (twd_timer_rate / 10000) % 100); - cpu_rate = twd_timer_rate * periphclk_prescaler; - twd_timer_rate = twd_target_rate; - twd_recalc_prescaler(cpu_rate); + } else { + if (target_rate) { + BUG_ON(target_rate != twd_timer_rate); + twd_recalc_prescaler(twd_cpu_rate); + } } - printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000, - (twd_timer_rate / 10000) % 100); - load = twd_timer_rate / HZ; __raw_writel(load, twd_base + TWD_TIMER_LOAD); From b27fa20574a6dc91e912c3bdecd41614563c97f4 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 29 Dec 2010 20:43:50 -0800 Subject: [PATCH 3/7] ARM: tegra: cpufreq: Remove direct calls to localtimer The localtimer code will use a cpufreq notifier to update the prescalers. Change-Id: Ie0587d7eaec628ff11bf40636f78597574cd63ec Signed-off-by: Colin Cross --- arch/arm/mach-tegra/cpu-tegra.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index 2d629461a9b8..3b8a6b56783e 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -215,23 +215,6 @@ unsigned int tegra_getspeed(unsigned int cpu) return rate; } -#ifdef CONFIG_HAVE_ARM_TWD -static void tegra_cpufreq_rescale_twd_other_cpu(void *data) { - unsigned long new_rate = *(unsigned long *)data; - twd_recalc_prescaler(new_rate); -} - -static void tegra_cpufreq_rescale_twds(unsigned long new_rate) -{ - twd_recalc_prescaler(new_rate); - smp_call_function(tegra_cpufreq_rescale_twd_other_cpu, &new_rate, 1); -} -#else -static inline void tegra_cpufreq_rescale_twds(unsigned long new_rate) -{ -} -#endif - static int tegra_update_cpu_speed(unsigned long rate) { int ret = 0; @@ -257,9 +240,6 @@ static int tegra_update_cpu_speed(unsigned long rate) for_each_online_cpu(freqs.cpu) cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - if (freqs.new > freqs.old) - tegra_cpufreq_rescale_twds(freqs.new * 1000); - #ifdef CONFIG_CPU_FREQ_DEBUG printk(KERN_DEBUG "cpufreq-tegra: transition: %u --> %u\n", freqs.old, freqs.new); @@ -272,9 +252,6 @@ static int tegra_update_cpu_speed(unsigned long rate) return ret; } - if (freqs.new < freqs.old) - tegra_cpufreq_rescale_twds(freqs.new * 1000); - for_each_online_cpu(freqs.cpu) cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); From 0429f17bd8c8d2610cdb21472239805adc074338 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 29 Dec 2010 20:45:10 -0800 Subject: [PATCH 4/7] ARM: smp_twd: Use cpufreq notifiers to update prescalers Change-Id: I957d5ca8580d4e7a98fb9fc754ca8f00133940d9 Signed-off-by: Colin Cross --- arch/arm/include/asm/smp_twd.h | 8 ------- arch/arm/kernel/smp_twd.c | 41 +++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h index 79aebe2ad7a8..ce0a8b95da39 100644 --- a/arch/arm/include/asm/smp_twd.h +++ b/arch/arm/include/asm/smp_twd.h @@ -37,12 +37,4 @@ void twd_timer_setup(struct clock_event_device *); void twd_timer_setup_scalable(struct clock_event_device *, unsigned long target_rate, unsigned int periphclk_prescaler); -/* - * Recalculate the twd prescaler value when the cpu frequency changes. To - * prevent early timer interrupts, must be called before changing the cpu - * frequency if the frequency is increasing, or after if the frequency is - * decreasing. - */ -void twd_recalc_prescaler(unsigned long new_rate); - #endif diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 97f934967850..3dc62ee45639 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -88,7 +89,7 @@ int twd_timer_ack(void) * frequency if the frequency is increasing, or after if the frequency is * decreasing. */ -void twd_recalc_prescaler(unsigned long new_rate) +static void twd_update_prescaler(void *data) { u32 ctrl; int prescaler; @@ -96,9 +97,7 @@ void twd_recalc_prescaler(unsigned long new_rate) BUG_ON(twd_periphclk_prescaler == 0 || twd_timer_rate == 0); - twd_cpu_rate = new_rate; - - periphclk_rate = new_rate / twd_periphclk_prescaler; + periphclk_rate = twd_cpu_rate / twd_periphclk_prescaler; prescaler = DIV_ROUND_UP(periphclk_rate, twd_timer_rate); prescaler = clamp(prescaler - 1, 0, 0xFF); @@ -109,6 +108,36 @@ void twd_recalc_prescaler(unsigned long new_rate) __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); } +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + if (((freqs->new > freqs->old) && state == CPUFREQ_PRECHANGE) || + ((freqs->old > freqs->new) && state == CPUFREQ_POSTCHANGE)) { + /* freqs->new is in kHz, twd_cpu_rate is in Hz */ + twd_cpu_rate = freqs->new * 1000; + + smp_call_function_single(freqs->cpu, twd_update_prescaler, + NULL, 1); + } + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (twd_cpu_rate) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + static void __cpuinit twd_calibrate_rate(unsigned long target_rate, unsigned int periphclk_prescaler) { @@ -156,7 +185,7 @@ static void __cpuinit twd_calibrate_rate(unsigned long target_rate, (twd_timer_rate / 10000) % 100); twd_cpu_rate = twd_timer_rate * periphclk_prescaler; twd_timer_rate = target_rate; - twd_recalc_prescaler(twd_cpu_rate); + twd_update_prescaler(NULL); } printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000, @@ -164,7 +193,7 @@ static void __cpuinit twd_calibrate_rate(unsigned long target_rate, } else { if (target_rate) { BUG_ON(target_rate != twd_timer_rate); - twd_recalc_prescaler(twd_cpu_rate); + twd_update_prescaler(NULL); } } From 619ec9a64fc0d4e99109f12bd8f30cf0761f1d5d Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 29 Dec 2010 21:15:12 -0800 Subject: [PATCH 5/7] i2c: i2c-tegra: Return error code on partial master_xfer transfer It is unclear what the correct return value is when master_xfer gets an error part way through a set of messages, but other drivers seem to return the error code of the individual failed message instead of the number of successful messages. Convert the Tegra i2c driver to do the same. Change-Id: Iacda4b6d7591bfe644b93564b93356a0cda3134f Signed-off-by: Colin Cross --- drivers/i2c/busses/i2c-tegra.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index b7481f496ec3..91aa11ce0de2 100755 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -512,13 +512,16 @@ static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int stop = (i == (num - 1)) ? 1 : 0; ret = tegra_i2c_xfer_msg(i2c_bus, &msgs[i], stop); if (ret) - break; + goto out; } + ret = i; + +out: clk_disable(i2c_dev->clk); rt_mutex_unlock(&i2c_dev->dev_lock); - return i; + return ret; } static u32 tegra_i2c_func(struct i2c_adapter *adap) From 1af1da3f6bb77f993dce12311101d2b3336ba92a Mon Sep 17 00:00:00 2001 From: Ari Hirvonen Date: Thu, 30 Dec 2010 15:18:18 +0200 Subject: [PATCH 6/7] video: tegra: fix typo from register header Change-Id: Ifa7b454791f2d32cd1d12a8930890e061e835ef6 Signed-off-by: Michael I. Gold --- drivers/video/tegra/dc/dc_reg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/tegra/dc/dc_reg.h b/drivers/video/tegra/dc/dc_reg.h index f643ec9ec742..bd1750b78e44 100644 --- a/drivers/video/tegra/dc/dc_reg.h +++ b/drivers/video/tegra/dc/dc_reg.h @@ -396,7 +396,7 @@ #define BLEND_WEIGHT1(x) (((x) & 0xff) << 16) #define BLEND(key, control, weight0, weight1) \ (CKEY_ ## key | BLEND_CONTROL_ ## control | \ - BLEND_WEIGHT0(weight0) | BLEND_WEIGHT0(weight1)) + BLEND_WEIGHT0(weight0) | BLEND_WEIGHT1(weight1)) #define DC_WIN_HP_FETCH_CONTROL 0x714 From b4c92f36c0368fe9bc7a780bc6ca9fe9ef199c55 Mon Sep 17 00:00:00 2001 From: Ari Hirvonen Date: Thu, 30 Dec 2010 15:27:28 +0200 Subject: [PATCH 7/7] video: tegra: fix three overlay window blending Change-Id: I36e2540b5b98817b87efbe4ca2b1f4d4f19ceba4 Signed-off-by: Michael I. Gold --- drivers/video/tegra/dc/dc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index 3c3a4754b7dc..946d3da7dc2c 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -369,7 +369,7 @@ static int get_topmost_window(u32 *depths, unsigned long *wins) { int idx, best = -1; - for_each_set_bit(idx, wins, sizeof(*wins)) { + for_each_set_bit(idx, wins, DC_N_WINDOWS) { if (best == -1 || depths[idx] < depths[best]) best = idx; } @@ -406,13 +406,15 @@ static u32 blend_2win(int idx, unsigned long behind_mask, u32* flags, int xy) static u32 blend_3win(int idx, unsigned long behind_mask, u32* flags) { unsigned long infront_mask; + int first; infront_mask = ~(behind_mask | BIT(idx)); infront_mask &= (BIT(DC_N_WINDOWS) - 1); + first = ffs(infront_mask) - 1; if (!infront_mask) return blend_topwin(flags[idx]); - else if (behind_mask && flags[ffs(infront_mask)]) + else if (behind_mask && first != -1 && flags[first]) return BLEND(NOKEY, DEPENDANT, 0x00, 0x00); else return BLEND(NOKEY, FIX, 0x0, 0x0);