diff options
author | Nitin Kumbhar <nkumbhar@nvidia.com> | 2010-12-10 10:01:59 +0530 |
---|---|---|
committer | Nitin Kumbhar <nkumbhar@nvidia.com> | 2010-12-10 10:01:59 +0530 |
commit | 7adb08046b793f52749e2c54be2f7434196c32cf (patch) | |
tree | 300e8e2e5270708d1b23a438bba9844bacb58d54 /arch/arm | |
parent | 845c6e1d456c92e32ad79176d418c0d2592998d4 (diff) | |
parent | 42907f1736fe39cdf39b5f583fcd6b9e4e257b18 (diff) |
merging android-tegra-2.6.36 into git-master/linux-2.6/android-tegra-2.6.36
Change-Id: I1312ec33ba8bac38dc395d7d1a2f485b13d74c14
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 4 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 8 | ||||
-rw-r--r-- | arch/arm/mach-tegra/board-ventana.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/board.h | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/clock.c | 82 | ||||
-rw-r--r-- | arch/arm/mach-tegra/clock.h | 12 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra.c | 173 | ||||
-rwxr-xr-x | arch/arm/mach-tegra/dma.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.c | 490 | ||||
-rw-r--r-- | arch/arm/mach-tegra/dvfs.h | 48 | ||||
-rw-r--r-- | arch/arm/mach-tegra/suspend.c | 4 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_clocks.c | 83 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_dvfs.c | 292 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_emc.c | 172 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_emc.h | 27 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_i2s_audio.c | 28 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_spdif_audio.c | 22 |
17 files changed, 1105 insertions, 345 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index ef028cd62734..8269eea7ee77 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -75,6 +75,10 @@ config TEGRA_FIQ_DEBUGGER endif +config TEGRA_EMC_SCALING_ENABLE + bool "Enable scaling the memory frequency" + default n + config TEGRA_CPU_DVFS bool "Enable voltage scaling on Tegra CPU" default y diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 431ecbb659dc..9262acde3f0c 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -28,9 +28,11 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_clocks.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_dvfs.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_fuse.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += suspend-t2.o -obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_save.o -obj-$(CONFIG_CPU_V7) += cortex-a9.o -obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pinmux-t2-tables.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_save.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_emc.o +obj-$(CONFIG_CPU_V7) += cortex-a9.o + +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pinmux-t2-tables.o obj-$(CONFIG_SMP) += localtimer.o obj-$(CONFIG_SMP) += platsmp.o obj-y += headsmp.o diff --git a/arch/arm/mach-tegra/board-ventana.c b/arch/arm/mach-tegra/board-ventana.c index ff0a81d93fee..2f22bea260d9 100644 --- a/arch/arm/mach-tegra/board-ventana.c +++ b/arch/arm/mach-tegra/board-ventana.c @@ -285,7 +285,7 @@ static void ventana_i2c_init(void) } static struct gpio_keys_button ventana_keys[] = { - [0] = GPIO_KEY(KEY_MENU, PQ0, 0), + [0] = GPIO_KEY(KEY_MENU, PQ3, 0), [1] = GPIO_KEY(KEY_HOME, PQ1, 0), [2] = GPIO_KEY(KEY_BACK, PQ2, 0), [3] = GPIO_KEY(KEY_VOLUMEUP, PQ5, 0), diff --git a/arch/arm/mach-tegra/board.h b/arch/arm/mach-tegra/board.h index c47a21f71260..04f1538b1a37 100644 --- a/arch/arm/mach-tegra/board.h +++ b/arch/arm/mach-tegra/board.h @@ -32,6 +32,7 @@ void __init tegra_reserve(unsigned long carveout_size, unsigned long fb_size, void __init tegra_protected_aperture_init(unsigned long aperture); void tegra_move_framebuffer(unsigned long to, unsigned long from, unsigned long size); +int tegra_dvfs_rail_disable_by_name(const char *reg_id); extern unsigned long tegra_bootloader_fb_start; extern unsigned long tegra_bootloader_fb_size; diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c index ad5f483af7fc..e3936af38356 100644 --- a/arch/arm/mach-tegra/clock.c +++ b/arch/arm/mach-tegra/clock.c @@ -87,7 +87,7 @@ static inline bool clk_is_auto_dvfs(struct clk *c) static inline bool clk_is_dvfs(struct clk *c) { - return c->is_dvfs; + return (c->dvfs != NULL); } static inline bool clk_cansleep(struct clk *c) @@ -207,22 +207,6 @@ void clk_set_cansleep(struct clk *c) mutex_unlock(&clock_list_lock); } -int tegra_dvfs_set_rate(struct clk *c, unsigned long rate) -{ - unsigned long flags; - int ret; - - if (!clk_is_dvfs(c)) - return -EINVAL; - - clk_lock_save(c, flags); - ret = tegra_dvfs_set_rate_locked(c, rate); - clk_unlock_restore(c, flags); - - return ret; -} -EXPORT_SYMBOL(tegra_dvfs_set_rate); - int clk_reparent(struct clk *c, struct clk *parent) { c->parent = parent; @@ -233,8 +217,6 @@ void clk_init(struct clk *c) { clk_lock_init(c); - INIT_LIST_HEAD(&c->dvfs); - if (c->ops && c->ops->init) c->ops->init(c); @@ -260,7 +242,7 @@ int clk_enable(struct clk *c) clk_lock_save(c, flags); if (clk_is_auto_dvfs(c)) { - ret = tegra_dvfs_set_rate_locked(c, clk_get_rate_locked(c)); + ret = tegra_dvfs_set_rate(c, clk_get_rate_locked(c)); if (ret) goto out; } @@ -313,7 +295,7 @@ void clk_disable(struct clk *c) c->refcnt--; if (clk_is_auto_dvfs(c) && c->refcnt == 0) - tegra_dvfs_set_rate_locked(c, 0); + tegra_dvfs_set_rate(c, 0); clk_unlock_restore(c, flags); } @@ -338,7 +320,7 @@ int clk_set_parent(struct clk *c, struct clk *parent) if (clk_is_auto_dvfs(c) && c->refcnt > 0 && (!c->parent || new_rate > old_rate)) { - ret = tegra_dvfs_set_rate_locked(c, new_rate); + ret = tegra_dvfs_set_rate(c, new_rate); if (ret) goto out; } @@ -349,7 +331,7 @@ int clk_set_parent(struct clk *c, struct clk *parent) if (clk_is_auto_dvfs(c) && c->refcnt > 0 && new_rate < old_rate) - ret = tegra_dvfs_set_rate_locked(c, new_rate); + ret = tegra_dvfs_set_rate(c, new_rate); out: clk_unlock_restore(c, flags); @@ -368,6 +350,7 @@ int clk_set_rate(struct clk *c, unsigned long rate) int ret = 0; unsigned long flags; unsigned long old_rate; + long new_rate; clk_lock_save(c, flags); @@ -381,8 +364,19 @@ int clk_set_rate(struct clk *c, unsigned long rate) if (rate > c->max_rate) rate = c->max_rate; + if (c->ops && c->ops->round_rate) { + new_rate = c->ops->round_rate(c, rate); + + if (new_rate < 0) { + ret = new_rate; + goto out; + } + + rate = new_rate; + } + if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) { - ret = tegra_dvfs_set_rate_locked(c, rate); + ret = tegra_dvfs_set_rate(c, rate); if (ret) goto out; } @@ -392,7 +386,7 @@ int clk_set_rate(struct clk *c, unsigned long rate) goto out; if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0) - ret = tegra_dvfs_set_rate_locked(c, rate); + ret = tegra_dvfs_set_rate(c, rate); out: clk_unlock_restore(c, flags); @@ -527,36 +521,6 @@ void __init tegra_init_clock(void) } /* - * Iterate through all clocks, setting the dvfs rate to the current clock - * rate on all auto dvfs clocks, and to the saved dvfs rate on all manual - * dvfs clocks. Used to enable dvfs during late init, after the regulators - * are available. - */ -void __init tegra_clk_set_dvfs_rates(void) -{ - unsigned long flags; - struct clk *c; - - mutex_lock(&clock_list_lock); - - list_for_each_entry(c, &clocks, node) { - clk_lock_save(c, flags); - if (clk_is_auto_dvfs(c)) { - if (c->refcnt > 0) - tegra_dvfs_set_rate_locked(c, - clk_get_rate_locked(c)); - else - tegra_dvfs_set_rate_locked(c, 0); - } else if (clk_is_dvfs(c)) { - tegra_dvfs_set_rate_locked(c, c->dvfs_rate); - } - clk_unlock_restore(c, flags); - } - - mutex_unlock(&clock_list_lock); -} - -/* * Iterate through all clocks, disabling any for which the refcount is 0 * but the clock init detected the bootloader left the clock on. */ @@ -587,7 +551,6 @@ int __init tegra_late_init_clock(void) { tegra_dvfs_late_init(); tegra_disable_boot_clocks(); - tegra_clk_set_dvfs_rates(); return 0; } late_initcall(tegra_late_init_clock); @@ -711,7 +674,7 @@ static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level) { seq_printf(s, "%*s %-*s%21s%d mV\n", level * 3 + 1, "", - 30 - level * 3, d->reg_id, + 30 - level * 3, d->dvfs_rail->reg_id, "", d->cur_millivolts); } @@ -719,7 +682,6 @@ static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level) static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) { struct clk *child; - struct dvfs *d; const char *state = "uninit"; char div[8] = {0}; @@ -752,8 +714,8 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) 30 - level * 3, c->name, state, c->refcnt, div, clk_get_rate_all_locked(c)); - list_for_each_entry(d, &c->dvfs, node) - dvfs_show_one(s, d, level + 1); + if (c->dvfs) + dvfs_show_one(s, c->dvfs, level + 1); list_for_each_entry(child, &clocks, node) { if (child->parent != c) diff --git a/arch/arm/mach-tegra/clock.h b/arch/arm/mach-tegra/clock.h index 083815487c17..1d6a9acba412 100644 --- a/arch/arm/mach-tegra/clock.h +++ b/arch/arm/mach-tegra/clock.h @@ -77,7 +77,7 @@ enum clk_state { struct clk { /* node for master clocks list */ struct list_head node; /* node for list of all clocks */ - struct list_head dvfs; /* list of dvfs dependencies */ + struct dvfs *dvfs; struct clk_lookup lookup; #ifdef CONFIG_DEBUG_FS @@ -89,7 +89,7 @@ struct clk { unsigned long dvfs_rate; unsigned long rate; unsigned long max_rate; - bool is_dvfs; + unsigned long min_rate; bool auto_dvfs; bool cansleep; u32 flags; @@ -105,6 +105,8 @@ struct clk { u32 reg; u32 reg_shift; + struct list_head shared_bus_list; + union { struct { unsigned int clk_num; @@ -128,10 +130,6 @@ struct clk { struct clk *backup; } cpu; struct { - struct list_head list; - unsigned long min_rate; - } shared_bus; - struct { struct list_head node; bool enabled; unsigned long rate; @@ -162,9 +160,7 @@ struct clk *tegra_get_clock_by_name(const char *name); unsigned long clk_measure_input_freq(void); int clk_reparent(struct clk *c, struct clk *parent); void tegra_clk_init_from_table(struct tegra_clk_init_table *table); -void tegra_clk_set_dvfs_rates(void); void clk_set_cansleep(struct clk *c); unsigned long clk_get_rate_locked(struct clk *c); -int tegra_dvfs_set_rate_locked(struct clk *c, unsigned long rate); #endif diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index 035e1bc31462..d8c103e8964c 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -29,6 +29,7 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/suspend.h> +#include <linux/debugfs.h> #include <asm/smp_twd.h> #include <asm/system.h> @@ -36,7 +37,7 @@ #include <mach/hardware.h> #include <mach/clk.h> -/* Frequency table index must be sequential starting at 0 */ +/* 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 }, @@ -49,14 +50,28 @@ static struct cpufreq_frequency_table freq_table[] = { { 8, CPUFREQ_TABLE_END }, }; +/* CPU frequency is gradually lowered when throttling is enabled */ +#define THROTTLE_START_INDEX 2 +#define THROTTLE_END_INDEX 6 +#define THROTTLE_DELAY msecs_to_jiffies(2000) +#define NO_DELAY msecs_to_jiffies(0) + #define NUM_CPUS 2 static struct clk *cpu_clk; +static struct clk *emc_clk; + +static struct workqueue_struct *workqueue; static unsigned long target_cpu_speed[NUM_CPUS]; static DEFINE_MUTEX(tegra_cpu_lock); static bool is_suspended; +static DEFINE_MUTEX(throttling_lock); +static bool is_throttling; +static struct delayed_work throttle_work; + + int tegra_verify_speed(struct cpufreq_policy *policy) { return cpufreq_frequency_table_verify(policy, freq_table); @@ -101,6 +116,17 @@ static int tegra_update_cpu_speed(unsigned long rate) if (freqs.old == freqs.new) return ret; + /* + * Vote on memory bus frequency based on cpu frequency + * This sets the minimum frequency, display or avp may request higher + */ + if (rate >= 816000) + clk_set_rate(emc_clk, 600000000); /* cpu 816 MHz, emc max */ + else if (rate >= 456000) + clk_set_rate(emc_clk, 300000000); /* cpu 456 MHz, emc 150Mhz */ + else + clk_set_rate(emc_clk, 100000000); /* emc 50Mhz */ + for_each_online_cpu(freqs.cpu) cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); @@ -143,6 +169,8 @@ static int tegra_target(struct cpufreq_policy *policy, { int idx; unsigned int freq; + unsigned int highest_speed; + unsigned int limit_when_throttling; int ret = 0; mutex_lock(&tegra_cpu_lock); @@ -159,13 +187,137 @@ static int tegra_target(struct cpufreq_policy *policy, target_cpu_speed[policy->cpu] = freq; - ret = tegra_update_cpu_speed(tegra_cpu_highest_speed()); + highest_speed = tegra_cpu_highest_speed(); + /* Do not go above this frequency when throttling */ + limit_when_throttling = freq_table[THROTTLE_START_INDEX].frequency; + + if (is_throttling && highest_speed > limit_when_throttling) { + if (tegra_getspeed(0) < limit_when_throttling) { + ret = tegra_update_cpu_speed(limit_when_throttling); + goto out; + } else { + ret = -EBUSY; + goto out; + } + } + + ret = tegra_update_cpu_speed(highest_speed); out: mutex_unlock(&tegra_cpu_lock); return ret; } +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; + + mutex_lock(&tegra_cpu_lock); + + if (tegra_throttling_needed(&rate) && tegra_update_cpu_speed(rate) == 0) { + queue_delayed_work(workqueue, &throttle_work, THROTTLE_DELAY); + } + + mutex_unlock(&tegra_cpu_lock); +} + +/** + * tegra_throttling_enable + * This functions may sleep + */ +void tegra_throttling_enable(void) +{ + mutex_lock(&throttling_lock); + + if (!is_throttling) { + is_throttling = true; + queue_delayed_work(workqueue, &throttle_work, NO_DELAY); + } + + mutex_unlock(&throttling_lock); +} +EXPORT_SYMBOL_GPL(tegra_throttling_enable); + +/** + * tegra_throttling_disable + * This functions may sleep + */ +void tegra_throttling_disable(void) +{ + mutex_lock(&throttling_lock); + + if (is_throttling) { + cancel_delayed_work_sync(&throttle_work); + is_throttling = false; + } + + mutex_unlock(&throttling_lock); +} +EXPORT_SYMBOL_GPL(tegra_throttling_disable); + +#ifdef CONFIG_DEBUG_FS +static int throttle_debug_set(void *data, u64 val) +{ + if (val) { + tegra_throttling_enable(); + } else { + tegra_throttling_disable(); + } + + return 0; +} +static int throttle_debug_get(void *data, u64 *val) +{ + *val = (u64) is_throttling; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(throttle_fops, throttle_debug_get, throttle_debug_set, "%llu\n"); + +static struct dentry *cpu_tegra_debugfs_root; + +static int __init tegra_cpu_debug_init(void) +{ + cpu_tegra_debugfs_root = debugfs_create_dir("cpu-tegra", 0); + + if (!cpu_tegra_debugfs_root) + return -ENOMEM; + + if (!debugfs_create_file("throttle", 0644, cpu_tegra_debugfs_root, NULL, &throttle_fops)) + goto err_out; + + return 0; + +err_out: + debugfs_remove_recursive(cpu_tegra_debugfs_root); + return -ENOMEM; + +} + +static void __exit tegra_cpu_debug_exit(void) +{ + debugfs_remove_recursive(cpu_tegra_debugfs_root); +} + +late_initcall(tegra_cpu_debug_init); +module_exit(tegra_cpu_debug_exit); +#endif + static int tegra_pm_notify(struct notifier_block *nb, unsigned long event, void *dummy) { @@ -196,6 +348,13 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) if (IS_ERR(cpu_clk)) return PTR_ERR(cpu_clk); + emc_clk = clk_get_sys("cpu", "emc"); + if (IS_ERR(emc_clk)) { + clk_put(cpu_clk); + return PTR_ERR(emc_clk); + } + clk_enable(emc_clk); + cpufreq_frequency_table_cpuinfo(policy, freq_table); cpufreq_frequency_table_get_attr(freq_table, policy->cpu); policy->cur = tegra_getspeed(policy->cpu); @@ -207,8 +366,10 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; cpumask_copy(policy->related_cpus, cpu_possible_mask); - if (policy->cpu == 0) + if (policy->cpu == 0) { + INIT_DELAYED_WORK(&throttle_work, tegra_throttle_work_func); register_pm_notifier(&tegra_cpu_pm_notifier); + } return 0; } @@ -216,6 +377,8 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) static int tegra_cpu_exit(struct cpufreq_policy *policy) { cpufreq_frequency_table_cpuinfo(policy, freq_table); + clk_disable(emc_clk); + clk_put(emc_clk); clk_put(cpu_clk); return 0; } @@ -237,11 +400,15 @@ static struct cpufreq_driver tegra_cpufreq_driver = { static int __init tegra_cpufreq_init(void) { + workqueue = create_singlethread_workqueue("cpu-tegra"); + if (!workqueue) + return -ENOMEM; return cpufreq_register_driver(&tegra_cpufreq_driver); } static void __exit tegra_cpufreq_exit(void) { + destroy_workqueue(workqueue); cpufreq_unregister_driver(&tegra_cpufreq_driver); } diff --git a/arch/arm/mach-tegra/dma.c b/arch/arm/mach-tegra/dma.c index 001dbcc14bfc..484a39182d62 100755 --- a/arch/arm/mach-tegra/dma.c +++ b/arch/arm/mach-tegra/dma.c @@ -752,7 +752,7 @@ static void handle_continuous_sngl_dma(struct tegra_dma_channel *ch) */ next_req = list_entry(req->node.next, typeof(*next_req), node); if (next_req->status != TEGRA_DMA_REQ_INFLIGHT) { - pr_warning("%s: interrupt during enqueue\n", __func__); + pr_debug("%s: interrupt during enqueue\n", __func__); tegra_dma_stop(ch); tegra_dma_update_hw(ch, next_req); } else if (!list_is_last(&next_req->node, &ch->list)) { diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c index ef58fae8afbd..bc1e1a391b5a 100644 --- a/arch/arm/mach-tegra/dvfs.c +++ b/arch/arm/mach-tegra/dvfs.c @@ -18,131 +18,198 @@ #include <linux/kernel.h> #include <linux/clk.h> -#include <linux/list.h> +#include <linux/debugfs.h> #include <linux/init.h> +#include <linux/list.h> #include <linux/list_sort.h> #include <linux/module.h> -#include <linux/debugfs.h> -#include <linux/slab.h> -#include <linux/seq_file.h> #include <linux/regulator/consumer.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/delay.h> + #include <asm/clkdev.h> + #include <mach/clk.h> #include "board.h" #include "clock.h" #include "dvfs.h" -struct dvfs_reg { - struct list_head node; /* node in dvfs_reg_list */ - struct list_head dvfs; /* list head of attached dvfs clocks */ - const char *reg_id; - struct regulator *reg; - int max_millivolts; - int millivolts; - struct mutex lock; -}; - -static LIST_HEAD(dvfs_debug_list); -static LIST_HEAD(dvfs_reg_list); +static LIST_HEAD(dvfs_rail_list); +static DEFINE_MUTEX(dvfs_lock); -static DEFINE_MUTEX(dvfs_debug_list_lock); -static DEFINE_MUTEX(dvfs_reg_list_lock); +static int dvfs_rail_update(struct dvfs_rail *rail); -static int dvfs_reg_set_voltage(struct dvfs_reg *dvfs_reg) +void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n) { - int millivolts = 0; - struct dvfs *d; - int ret = 0; + int i; + struct dvfs_relationship *rel; - mutex_lock(&dvfs_reg->lock); + mutex_lock(&dvfs_lock); - list_for_each_entry(d, &dvfs_reg->dvfs, reg_node) - millivolts = max(d->cur_millivolts, millivolts); + for (i = 0; i < n; i++) { + rel = &rels[i]; + list_add_tail(&rel->from_node, &rel->to->relationships_from); + list_add_tail(&rel->to_node, &rel->from->relationships_to); + } + + mutex_unlock(&dvfs_lock); +} - if (millivolts == dvfs_reg->millivolts) - goto out; +int tegra_dvfs_init_rails(struct dvfs_rail *rails[], int n) +{ + int i; + + mutex_lock(&dvfs_lock); - dvfs_reg->millivolts = millivolts; + for (i = 0; i < n; i++) { + INIT_LIST_HEAD(&rails[i]->dvfs); + INIT_LIST_HEAD(&rails[i]->relationships_from); + INIT_LIST_HEAD(&rails[i]->relationships_to); + rails[i]->millivolts = rails[i]->nominal_millivolts; + rails[i]->new_millivolts = rails[i]->nominal_millivolts; + if (!rails[i]->step) + rails[i]->step = rails[i]->max_millivolts; - if (!dvfs_reg->reg) { - pr_warn("dvfs set voltage on %s ignored\n", dvfs_reg->reg_id); - goto out; + list_add_tail(&rails[i]->node, &dvfs_rail_list); } - ret = regulator_set_voltage(dvfs_reg->reg, - millivolts * 1000, dvfs_reg->max_millivolts * 1000); + mutex_unlock(&dvfs_lock); -out: - mutex_unlock(&dvfs_reg->lock); - return ret; + return 0; +}; + +static int dvfs_solve_relationship(struct dvfs_relationship *rel) +{ + return rel->solve(rel->from, rel->to); } -static int dvfs_reg_connect_to_regulator(struct dvfs_reg *dvfs_reg) +/* Sets the voltage on a dvfs rail to a specific value, and updates any + * rails that depend on this rail. */ +static int dvfs_rail_set_voltage(struct dvfs_rail *rail, int millivolts) { - struct regulator *reg; + int ret = 0; + struct dvfs_relationship *rel; + int step = (millivolts > rail->millivolts) ? rail->step : -rail->step; + int i; + int steps; - if (!dvfs_reg->reg) { - reg = regulator_get(NULL, dvfs_reg->reg_id); - if (IS_ERR(reg)) + if (!rail->reg) { + if (millivolts == rail->millivolts) + return 0; + else return -EINVAL; } - dvfs_reg->reg = reg; + if (rail->disabled) + return 0; - return 0; + steps = DIV_ROUND_UP(abs(millivolts - rail->millivolts), rail->step); + + for (i = 0; i < steps; i++) { + if (abs(millivolts - rail->millivolts) > rail->step) + rail->new_millivolts = rail->millivolts + step; + else + rail->new_millivolts = millivolts; + + /* Before changing the voltage, tell each rail that depends + * on this rail that the voltage will change. + * This rail will be the "from" rail in the relationship, + * the rail that depends on this rail will be the "to" rail. + * from->millivolts will be the old voltage + * from->new_millivolts will be the new voltage */ + list_for_each_entry(rel, &rail->relationships_to, to_node) { + ret = dvfs_rail_update(rel->to); + if (ret) + return ret; + } + + if (!rail->disabled) { + ret = regulator_set_voltage(rail->reg, + rail->new_millivolts * 1000, + rail->max_millivolts * 1000); + } + if (ret) { + pr_err("Failed to set dvfs regulator %s\n", rail->reg_id); + return ret; + } + + rail->millivolts = rail->new_millivolts; + + /* After changing the voltage, tell each rail that depends + * on this rail that the voltage has changed. + * from->millivolts and from->new_millivolts will be the + * new voltage */ + list_for_each_entry(rel, &rail->relationships_to, to_node) { + ret = dvfs_rail_update(rel->to); + if (ret) + return ret; + } + } + + if (unlikely(rail->millivolts != millivolts)) { + pr_err("%s: rail didn't reach target %d in %d steps (%d)\n", + __func__, millivolts, steps, rail->millivolts); + return -EINVAL; + } + + return ret; } -static struct dvfs_reg *get_dvfs_reg(struct dvfs *d) +/* Determine the minimum valid voltage for a rail, taking into account + * the dvfs clocks and any rails that this rail depends on. Calls + * dvfs_rail_set_voltage with the new voltage, which will call + * dvfs_rail_update on any rails that depend on this rail. */ +static int dvfs_rail_update(struct dvfs_rail *rail) { - struct dvfs_reg *dvfs_reg; + int millivolts = 0; + struct dvfs *d; + struct dvfs_relationship *rel; + int ret = 0; - mutex_lock(&dvfs_reg_list_lock); + /* if dvfs is suspended, return and handle it during resume */ + if (rail->suspended) + return 0; - list_for_each_entry(dvfs_reg, &dvfs_reg_list, node) - if (!strcmp(d->reg_id, dvfs_reg->reg_id)) - goto out; + /* if regulators are not connected yet, return and handle it later */ + if (!rail->reg) + return 0; - dvfs_reg = kzalloc(sizeof(struct dvfs_reg), GFP_KERNEL); - if (!dvfs_reg) { - pr_err("%s: Failed to allocate dvfs_reg\n", __func__); - goto out; - } + /* Find the maximum voltage requested by any clock */ + list_for_each_entry(d, &rail->dvfs, reg_node) + millivolts = max(d->cur_millivolts, millivolts); - mutex_init(&dvfs_reg->lock); - INIT_LIST_HEAD(&dvfs_reg->dvfs); - dvfs_reg->reg_id = kstrdup(d->reg_id, GFP_KERNEL); + rail->new_millivolts = millivolts; - list_add_tail(&dvfs_reg->node, &dvfs_reg_list); + /* Check any rails that this rail depends on */ + list_for_each_entry(rel, &rail->relationships_from, from_node) + rail->new_millivolts = dvfs_solve_relationship(rel); -out: - mutex_unlock(&dvfs_reg_list_lock); - return dvfs_reg; + if (rail->new_millivolts != rail->millivolts) + ret = dvfs_rail_set_voltage(rail, rail->new_millivolts); + + return ret; } -static struct dvfs_reg *attach_dvfs_reg(struct dvfs *d) +static int dvfs_rail_connect_to_regulator(struct dvfs_rail *rail) { - struct dvfs_reg *dvfs_reg; - - dvfs_reg = get_dvfs_reg(d); - if (!dvfs_reg) - return NULL; - - mutex_lock(&dvfs_reg->lock); - list_add_tail(&d->reg_node, &dvfs_reg->dvfs); + struct regulator *reg; - d->dvfs_reg = dvfs_reg; - if (d->max_millivolts > d->dvfs_reg->max_millivolts) - d->dvfs_reg->max_millivolts = d->max_millivolts; + if (!rail->reg) { + reg = regulator_get(NULL, rail->reg_id); + if (IS_ERR(reg)) + return -EINVAL; + } - d->cur_millivolts = d->max_millivolts; - mutex_unlock(&dvfs_reg->lock); + rail->reg = reg; - return dvfs_reg; + return 0; } static int -__tegra_dvfs_set_rate(struct clk *c, struct dvfs *d, unsigned long rate) +__tegra_dvfs_set_rate(struct dvfs *d, unsigned long rate) { int i = 0; int ret; @@ -152,7 +219,7 @@ __tegra_dvfs_set_rate(struct clk *c, struct dvfs *d, unsigned long rate) if (rate > d->freqs[d->num_freqs - 1]) { pr_warn("tegra_dvfs: rate %lu too high for dvfs on %s\n", rate, - c->name); + d->clk_name); return -EINVAL; } @@ -167,54 +234,39 @@ __tegra_dvfs_set_rate(struct clk *c, struct dvfs *d, unsigned long rate) d->cur_rate = rate; - if (!d->dvfs_reg) - return 0; - - ret = dvfs_reg_set_voltage(d->dvfs_reg); + ret = dvfs_rail_update(d->dvfs_rail); if (ret) pr_err("Failed to set regulator %s for clock %s to %d mV\n", - d->dvfs_reg->reg_id, c->name, d->cur_millivolts); + d->dvfs_rail->reg_id, d->clk_name, d->cur_millivolts); return ret; } -int tegra_dvfs_set_rate_locked(struct clk *c, unsigned long rate) +int tegra_dvfs_set_rate(struct clk *c, unsigned long rate) { - struct dvfs *d; - int ret = 0; - bool freq_up; - - c->dvfs_rate = rate; - - freq_up = (c->refcnt == 0) || (rate > clk_get_rate_locked(c)); + int ret; - list_for_each_entry(d, &c->dvfs, node) { - if (d->higher == freq_up) - ret = __tegra_dvfs_set_rate(c, d, rate); - if (ret) - return ret; - } + if (!c->dvfs) + return -EINVAL; - list_for_each_entry(d, &c->dvfs, node) { - if (d->higher != freq_up) - ret = __tegra_dvfs_set_rate(c, d, rate); - if (ret) - return ret; - } + mutex_lock(&dvfs_lock); + ret = __tegra_dvfs_set_rate(c->dvfs, rate); + mutex_unlock(&dvfs_lock); - return 0; + return ret; } +EXPORT_SYMBOL(tegra_dvfs_set_rate); /* May only be called during clock init, does not take any locks on clock c. */ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) { int i; - struct dvfs_reg *dvfs_reg; - dvfs_reg = attach_dvfs_reg(d); - if (!dvfs_reg) { - pr_err("Failed to get regulator %s for clock %s\n", - d->reg_id, c->name); + if (c->dvfs) { + pr_err("Error when enabling dvfs on %s for clock %s:\n", + d->dvfs_rail->reg_id, c->name); + pr_err("DVFS already enabled for %s\n", + c->dvfs->dvfs_rail->reg_id); return -EINVAL; } @@ -235,17 +287,172 @@ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) clk_set_cansleep(c); } - c->is_dvfs = true; + c->dvfs = d; - list_add_tail(&d->node, &c->dvfs); - - mutex_lock(&dvfs_debug_list_lock); - list_add_tail(&d->debug_node, &dvfs_debug_list); - mutex_unlock(&dvfs_debug_list_lock); + mutex_lock(&dvfs_lock); + list_add_tail(&d->reg_node, &d->dvfs_rail->dvfs); + mutex_unlock(&dvfs_lock); return 0; } +static bool tegra_dvfs_all_rails_suspended(void) +{ + struct dvfs_rail *rail; + bool all_suspended = true; + + list_for_each_entry(rail, &dvfs_rail_list, node) + if (!rail->suspended && !rail->disabled) + all_suspended = false; + + return all_suspended; +} + +static bool tegra_dvfs_from_rails_suspended(struct dvfs_rail *to) +{ + struct dvfs_relationship *rel; + bool all_suspended = true; + + list_for_each_entry(rel, &to->relationships_from, from_node) + if (!rel->from->suspended && !rel->from->disabled) + all_suspended = false; + + return all_suspended; +} + +static int tegra_dvfs_suspend_one(void) +{ + struct dvfs_rail *rail; + int ret; + + list_for_each_entry(rail, &dvfs_rail_list, node) { + if (!rail->suspended && !rail->disabled && + tegra_dvfs_from_rails_suspended(rail)) { + ret = dvfs_rail_set_voltage(rail, + rail->nominal_millivolts); + if (ret) + return ret; + rail->suspended = true; + return 0; + } + } + + return -EINVAL; +} + +static void tegra_dvfs_resume(void) +{ + struct dvfs_rail *rail; + + mutex_lock(&dvfs_lock); + + list_for_each_entry(rail, &dvfs_rail_list, node) + rail->suspended = false; + + list_for_each_entry(rail, &dvfs_rail_list, node) + dvfs_rail_update(rail); + + mutex_unlock(&dvfs_lock); +} + +static int tegra_dvfs_suspend(void) +{ + int ret = 0; + + mutex_lock(&dvfs_lock); + + while (!tegra_dvfs_all_rails_suspended()) { + ret = tegra_dvfs_suspend_one(); + if (ret) + break; + } + + mutex_unlock(&dvfs_lock); + + if (ret) + tegra_dvfs_resume(); + + return ret; +} + +static int tegra_dvfs_pm_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + if (tegra_dvfs_suspend()) + return NOTIFY_STOP; + break; + case PM_POST_SUSPEND: + tegra_dvfs_resume(); + break; + } + + return NOTIFY_OK; +}; + +static struct notifier_block tegra_dvfs_nb = { + .notifier_call = tegra_dvfs_pm_notify, +}; + +/* must be called with dvfs lock held */ +static void __tegra_dvfs_rail_disable(struct dvfs_rail *rail) +{ + int ret; + + if (!rail->disabled) { + ret = dvfs_rail_set_voltage(rail, rail->nominal_millivolts); + if (ret) + pr_info("dvfs: failed to set regulator %s to disable " + "voltage %d\n", rail->reg_id, + rail->nominal_millivolts); + rail->disabled = true; + } +} + +/* must be called with dvfs lock held */ +static void __tegra_dvfs_rail_enable(struct dvfs_rail *rail) +{ + if (rail->disabled) { + rail->disabled = false; + dvfs_rail_update(rail); + } +} + +void tegra_dvfs_rail_enable(struct dvfs_rail *rail) +{ + mutex_lock(&dvfs_lock); + __tegra_dvfs_rail_enable(rail); + mutex_unlock(&dvfs_lock); +} + +void tegra_dvfs_rail_disable(struct dvfs_rail *rail) +{ + mutex_lock(&dvfs_lock); + __tegra_dvfs_rail_disable(rail); + mutex_unlock(&dvfs_lock); +} + +int tegra_dvfs_rail_disable_by_name(const char *reg_id) +{ + struct dvfs_rail *rail; + int ret = 0; + + mutex_lock(&dvfs_lock); + list_for_each_entry(rail, &dvfs_rail_list, node) { + if (!strcmp(reg_id, rail->reg_id)) { + __tegra_dvfs_rail_disable(rail); + goto out; + } + } + + ret = -EINVAL; + +out: + mutex_unlock(&dvfs_lock); + return ret; +} + /* * Iterate through all the dvfs regulators, finding the regulator exported * by the regulator api for each one. Must be called in late init, after @@ -253,12 +460,19 @@ int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) */ int __init tegra_dvfs_late_init(void) { - struct dvfs_reg *dvfs_reg; + struct dvfs_rail *rail; + + mutex_lock(&dvfs_lock); + + list_for_each_entry(rail, &dvfs_rail_list, node) + dvfs_rail_connect_to_regulator(rail); - mutex_lock(&dvfs_reg_list_lock); - list_for_each_entry(dvfs_reg, &dvfs_reg_list, node) - dvfs_reg_connect_to_regulator(dvfs_reg); - mutex_unlock(&dvfs_reg_list_lock); + list_for_each_entry(rail, &dvfs_rail_list, node) + dvfs_rail_update(rail); + + mutex_unlock(&dvfs_lock); + + register_pm_notifier(&tegra_dvfs_nb); return 0; } @@ -266,11 +480,11 @@ int __init tegra_dvfs_late_init(void) #ifdef CONFIG_DEBUG_FS static int dvfs_tree_sort_cmp(void *p, struct list_head *a, struct list_head *b) { - struct dvfs *da = list_entry(a, struct dvfs, debug_node); - struct dvfs *db = list_entry(b, struct dvfs, debug_node); + struct dvfs *da = list_entry(a, struct dvfs, reg_node); + struct dvfs *db = list_entry(b, struct dvfs, reg_node); int ret; - ret = strcmp(da->reg_id, db->reg_id); + ret = strcmp(da->dvfs_rail->reg_id, db->dvfs_rail->reg_id); if (ret != 0) return ret; @@ -285,27 +499,33 @@ static int dvfs_tree_sort_cmp(void *p, struct list_head *a, struct list_head *b) static int dvfs_tree_show(struct seq_file *s, void *data) { struct dvfs *d; - const char *last_reg = ""; + struct dvfs_rail *rail; + struct dvfs_relationship *rel; seq_printf(s, " clock rate mV\n"); seq_printf(s, "--------------------------------\n"); - mutex_lock(&dvfs_debug_list_lock); - - list_sort(NULL, &dvfs_debug_list, dvfs_tree_sort_cmp); + mutex_lock(&dvfs_lock); - list_for_each_entry(d, &dvfs_debug_list, debug_node) { - if (strcmp(last_reg, d->dvfs_reg->reg_id) != 0) { - last_reg = d->dvfs_reg->reg_id; - seq_printf(s, "%s %d mV:\n", d->dvfs_reg->reg_id, - d->dvfs_reg->millivolts); + list_for_each_entry(rail, &dvfs_rail_list, node) { + seq_printf(s, "%s %d mV%s:\n", rail->reg_id, + rail->millivolts, rail->disabled ? " disabled" : ""); + list_for_each_entry(rel, &rail->relationships_from, from_node) { + seq_printf(s, " %-10s %-7d mV %-4d mV\n", + rel->from->reg_id, + rel->from->millivolts, + dvfs_solve_relationship(rel)); } - seq_printf(s, " %-10s %-10lu %-4d mV\n", d->clk_name, - d->cur_rate, d->cur_millivolts); + list_sort(NULL, &rail->dvfs, dvfs_tree_sort_cmp); + + list_for_each_entry(d, &rail->dvfs, reg_node) { + seq_printf(s, " %-10s %-10lu %-4d mV\n", d->clk_name, + d->cur_rate, d->cur_millivolts); + } } - mutex_unlock(&dvfs_debug_list_lock); + mutex_unlock(&dvfs_lock); return 0; } diff --git a/arch/arm/mach-tegra/dvfs.h b/arch/arm/mach-tegra/dvfs.h index e5eac6cf9cd0..68622b899c59 100644 --- a/arch/arm/mach-tegra/dvfs.h +++ b/arch/arm/mach-tegra/dvfs.h @@ -22,25 +22,57 @@ #define MAX_DVFS_FREQS 16 struct clk; +struct dvfs_rail; + +/* + * dvfs_relationship between to rails, "from" and "to" + * when the rail changes, it will call dvfs_rail_update on the rails + * in the relationship_to list. + * when determining the voltage to set a rail to, it will consider each + * rail in the relationship_from list. + */ +struct dvfs_relationship { + struct dvfs_rail *to; + struct dvfs_rail *from; + int (*solve)(struct dvfs_rail *, struct dvfs_rail *); + + struct list_head to_node; /* node in relationship_to list */ + struct list_head from_node; /* node in relationship_from list */ +}; + +struct dvfs_rail { + const char *reg_id; + int min_millivolts; + int max_millivolts; + int nominal_millivolts; + int step; + bool disabled; + + struct list_head node; /* node in dvfs_rail_list */ + struct list_head dvfs; /* list head of attached dvfs clocks */ + struct list_head relationships_to; + struct list_head relationships_from; + struct regulator *reg; + int millivolts; + int new_millivolts; + bool suspended; +}; struct dvfs { /* Used only by tegra2_clock.c */ const char *clk_name; - int process_id; - bool cpu; + int cpu_process_id; /* Must be initialized before tegra_dvfs_init */ - const char *reg_id; int freqs_mult; unsigned long freqs[MAX_DVFS_FREQS]; - unsigned long millivolts[MAX_DVFS_FREQS]; + const int *millivolts; + struct dvfs_rail *dvfs_rail; bool auto_dvfs; - bool higher; /* Filled in by tegra_dvfs_init */ int max_millivolts; int num_freqs; - struct dvfs_reg *dvfs_reg; int cur_millivolts; unsigned long cur_rate; @@ -53,5 +85,9 @@ void tegra2_init_dvfs(void); int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d); int dvfs_debugfs_init(struct dentry *clk_debugfs_root); int tegra_dvfs_late_init(void); +int tegra_dvfs_init_rails(struct dvfs_rail *dvfs_rails[], int n); +void tegra_dvfs_add_relationships(struct dvfs_relationship *rels, int n); +void tegra_dvfs_rail_enable(struct dvfs_rail *rail); +void tegra_dvfs_rail_disable(struct dvfs_rail *rail); #endif diff --git a/arch/arm/mach-tegra/suspend.c b/arch/arm/mach-tegra/suspend.c index af3252401256..146b93a014f3 100644 --- a/arch/arm/mach-tegra/suspend.c +++ b/arch/arm/mach-tegra/suspend.c @@ -458,13 +458,17 @@ static void tegra_suspend_dram(bool do_lp0) suspend_cpu_complex(); flush_cache_all(); +#ifdef CONFIG_CACHE_L2X0 l2x0_shutdown(); +#endif __cortex_a9_save(mode); restore_cpu_complex(); writel(orig, evp_reset); +#ifdef CONFIG_CACHE_L2X0 l2x0_restart(); +#endif if (!do_lp0) { memcpy(iram_code, iram_save, iram_save_size); diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index c6fca17a28be..eef598e456f1 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -32,6 +32,7 @@ #include "clock.h" #include "fuse.h" +#include "tegra2_emc.h" #define RST_DEVICES 0x004 #define RST_DEVICES_SET 0x300 @@ -302,8 +303,6 @@ static void tegra2_super_clk_init(struct clk *c) } BUG_ON(sel->input == NULL); c->parent = sel->input; - - INIT_LIST_HEAD(&c->u.shared_bus.list); } static int tegra2_super_clk_enable(struct clk *c) @@ -1023,6 +1022,53 @@ static struct clk_ops tegra_periph_clk_ops = { .reset = &tegra2_periph_clk_reset, }; +/* External memory controller clock ops */ +static void tegra2_emc_clk_init(struct clk *c) +{ + tegra2_periph_clk_init(c); + c->max_rate = clk_get_rate_locked(c); +} + +static long tegra2_emc_clk_round_rate(struct clk *c, unsigned long rate) +{ + long new_rate = rate; + + new_rate = tegra_emc_round_rate(new_rate); + if (new_rate < 0) + return c->max_rate; + + BUG_ON(new_rate != tegra2_periph_clk_round_rate(c, new_rate)); + + return new_rate; +} + +static int tegra2_emc_clk_set_rate(struct clk *c, unsigned long rate) +{ + int ret; + /* The Tegra2 memory controller has an interlock with the clock + * block that allows memory shadowed registers to be updated, + * and then transfer them to the main registers at the same + * time as the clock update without glitches. */ + ret = tegra_emc_set_rate(rate); + if (ret < 0) + return ret; + + ret = tegra2_periph_clk_set_rate(c, rate); + udelay(1); + + return ret; +} + +static struct clk_ops tegra_emc_clk_ops = { + .init = &tegra2_emc_clk_init, + .enable = &tegra2_periph_clk_enable, + .disable = &tegra2_periph_clk_disable, + .set_parent = &tegra2_periph_clk_set_parent, + .set_rate = &tegra2_emc_clk_set_rate, + .round_rate = &tegra2_emc_clk_round_rate, + .reset = &tegra2_periph_clk_reset, +}; + /* Clock doubler ops */ static void tegra2_clk_double_init(struct clk *c) { @@ -1151,15 +1197,16 @@ static struct clk_ops tegra_cdev_clk_ops = { static void tegra_clk_shared_bus_update(struct clk *bus) { struct clk *c; - unsigned long rate = bus->u.shared_bus.min_rate; + unsigned long rate = bus->min_rate; - list_for_each_entry(c, &bus->u.shared_bus.list, + list_for_each_entry(c, &bus->shared_bus_list, u.shared_bus_user.node) { if (c->u.shared_bus_user.enabled) rate = max(c->u.shared_bus_user.rate, rate); } - clk_set_rate(bus, rate); + if (rate != clk_get_rate(bus)) + clk_set_rate(bus, rate); }; static void tegra_clk_shared_bus_init(struct clk *c) @@ -1170,7 +1217,7 @@ static void tegra_clk_shared_bus_init(struct clk *c) c->set = true; list_add_tail(&c->u.shared_bus_user.node, - &c->parent->u.shared_bus.list); + &c->parent->shared_bus_list); } static int tegra_clk_shared_bus_set_rate(struct clk *c, unsigned long rate) @@ -1716,9 +1763,7 @@ static struct clk tegra_clk_sclk = { .reg = 0x28, .ops = &tegra_super_ops, .max_rate = 240000000, - .u.shared_bus = { - .min_rate = 120000000, - }, + .min_rate = 120000000, }; static struct clk tegra_clk_virtual_cpu = { @@ -1843,6 +1888,18 @@ static struct clk_mux_sel mux_clk_32k[] = { { 0, 0}, }; +static struct clk tegra_clk_emc = { + .name = "emc", + .ops = &tegra_emc_clk_ops, + .reg = 0x19c, + .max_rate = 800000000, + .inputs = mux_pllm_pllc_pllp_clkm, + .flags = MUX | DIV_U71 | PERIPH_EMC_ENB, + .u.periph = { + .clk_num = 57, + }, +}; + #define PERIPH_CLK(_name, _dev, _con, _clk_num, _reg, _max, _inputs, _flags) \ { \ .name = _name, \ @@ -1931,13 +1988,17 @@ struct clk tegra_list_clks[] = { PERIPH_CLK("usbd", "fsl-tegra-udc", NULL, 22, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ PERIPH_CLK("usb2", "tegra-ehci.1", NULL, 58, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ PERIPH_CLK("usb3", "tegra-ehci.2", NULL, 59, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ - PERIPH_CLK("emc", "emc", NULL, 57, 0x19c, 800000000, mux_pllm_pllc_pllp_clkm, MUX | DIV_U71 | PERIPH_EMC_ENB), PERIPH_CLK("dsi", "dsi", NULL, 48, 0, 500000000, mux_plld, 0), /* scales with voltage */ PERIPH_CLK("csi", "tegra_camera", "csi", 52, 0, 72000000, mux_pllp_out3, 0), PERIPH_CLK("isp", "tegra_camera", "isp", 23, 0, 150000000, mux_clk_m, 0), /* same frequency as VI */ PERIPH_CLK("csus", "tegra_camera", "csus", 92, 0, 150000000, mux_clk_m, PERIPH_NO_RESET), SHARED_CLK("avp.sclk", "tegra-avp", "sclk", &tegra_clk_sclk), + SHARED_CLK("avp.emc", "tegra-avp", "emc", &tegra_clk_emc), + SHARED_CLK("cpu.emc", "cpu", "emc", &tegra_clk_emc), + SHARED_CLK("disp1.emc", "tegradc.0", "emc", &tegra_clk_emc), + SHARED_CLK("disp2.emc", "tegradc.1", "emc", &tegra_clk_emc), + SHARED_CLK("hdmi.emc", "hdmi", "emc", &tegra_clk_emc), }; #define CLK_DUPLICATE(_name, _dev, _con) \ @@ -2012,11 +2073,13 @@ struct clk *tegra_ptr_clks[] = { &tegra_clk_virtual_cpu, &tegra_clk_blink, &tegra_clk_cop, + &tegra_clk_emc, }; static void tegra2_init_one_clock(struct clk *c) { clk_init(c); + INIT_LIST_HEAD(&c->shared_bus_list); if (!c->lookup.dev_id && !c->lookup.con_id) c->lookup.con_id = c->name; c->lookup.clk = c; diff --git a/arch/arm/mach-tegra/tegra2_dvfs.c b/arch/arm/mach-tegra/tegra2_dvfs.c index 265a7b538f7f..b58a7d2ef92d 100644 --- a/arch/arm/mach-tegra/tegra2_dvfs.c +++ b/arch/arm/mach-tegra/tegra2_dvfs.c @@ -20,88 +20,137 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> +#include <linux/module.h> #include "clock.h" #include "dvfs.h" #include "fuse.h" -#define CORE_REGULATOR "vdd_core" -#define CPU_REGULATOR "vdd_cpu" +#ifdef CONFIG_TEGRA_CORE_DVFS +static bool tegra_dvfs_core_disabled; +#else +static bool tegra_dvfs_core_disabled = true; +#endif +#ifdef CONFIG_TEGRA_CPU_DVFS +static bool tegra_dvfs_cpu_disabled; +#else +static bool tegra_dvfs_cpu_disabled = true; +#endif static const int core_millivolts[MAX_DVFS_FREQS] = {950, 1000, 1100, 1200, 1275}; static const int cpu_millivolts[MAX_DVFS_FREQS] = {750, 775, 800, 825, 875, 900, 925, 975, 1000, 1050, 1100}; -static int cpu_core_millivolts[MAX_DVFS_FREQS]; - -#define CORE_MAX_MILLIVOLTS 1275 -#define CPU_MAX_MILLIVOLTS 1100 #define KHZ 1000 #define MHZ 1000000 -#ifdef CONFIG_TEGRA_CPU_DVFS -#define CPU_DVFS_CPU(_clk_name, _process_id, _mult, _freqs...) \ +static struct dvfs_rail tegra2_dvfs_rail_vdd_cpu = { + .reg_id = "vdd_cpu", + .max_millivolts = 1100, + .min_millivolts = 750, + .nominal_millivolts = 1100, +}; + +static struct dvfs_rail tegra2_dvfs_rail_vdd_core = { + .reg_id = "vdd_core", + .max_millivolts = 1275, + .min_millivolts = 950, + .nominal_millivolts = 1200, + .step = 150, /* step vdd_core by 150 mV to allow vdd_aon to follow */ +}; + +static struct dvfs_rail tegra2_dvfs_rail_vdd_aon = { + .reg_id = "vdd_aon", + .max_millivolts = 1275, + .min_millivolts = 950, + .nominal_millivolts = 1200, +#ifndef CONFIG_TEGRA_CORE_DVFS + .disabled = true, +#endif +}; + +/* vdd_core and vdd_aon must be 50 mV higher than vdd_cpu */ +static int tegra2_dvfs_rel_vdd_cpu_vdd_core(struct dvfs_rail *vdd_cpu, + struct dvfs_rail *vdd_core) +{ + if (vdd_cpu->new_millivolts > vdd_cpu->millivolts && + vdd_core->new_millivolts < vdd_cpu->new_millivolts + 50) + return vdd_cpu->new_millivolts + 50; + + if (vdd_core->new_millivolts < vdd_cpu->millivolts + 50) + return vdd_cpu->millivolts + 50; + + return vdd_core->new_millivolts; +} + +/* vdd_aon must be within 170 mV of vdd_core */ +static int tegra2_dvfs_rel_vdd_core_vdd_aon(struct dvfs_rail *vdd_core, + struct dvfs_rail *vdd_aon) +{ + BUG_ON(abs(vdd_aon->millivolts - vdd_core->millivolts) > + vdd_aon->step); + return vdd_core->millivolts; +} + +static struct dvfs_relationship tegra2_dvfs_relationships[] = { + { + /* vdd_core must be 50 mV higher than vdd_cpu */ + .from = &tegra2_dvfs_rail_vdd_cpu, + .to = &tegra2_dvfs_rail_vdd_core, + .solve = tegra2_dvfs_rel_vdd_cpu_vdd_core, + }, + { + /* vdd_aon must be 50 mV higher than vdd_cpu */ + .from = &tegra2_dvfs_rail_vdd_cpu, + .to = &tegra2_dvfs_rail_vdd_aon, + .solve = tegra2_dvfs_rel_vdd_cpu_vdd_core, + }, + { + /* vdd_aon must be within 170 mV of vdd_core */ + .from = &tegra2_dvfs_rail_vdd_core, + .to = &tegra2_dvfs_rail_vdd_aon, + .solve = tegra2_dvfs_rel_vdd_core_vdd_aon, + }, +}; + +static struct dvfs_rail *tegra2_dvfs_rails[] = { + &tegra2_dvfs_rail_vdd_cpu, + &tegra2_dvfs_rail_vdd_core, + &tegra2_dvfs_rail_vdd_aon, +}; + +#define CPU_DVFS(_clk_name, _process_id, _mult, _freqs...) \ { \ .clk_name = _clk_name, \ - .reg_id = CPU_REGULATOR, \ - .cpu = true, \ - .process_id = _process_id, \ + .cpu_process_id = _process_id, \ .freqs = {_freqs}, \ .freqs_mult = _mult, \ + .millivolts = cpu_millivolts, \ .auto_dvfs = true, \ - .max_millivolts = CPU_MAX_MILLIVOLTS \ - }, + .dvfs_rail = &tegra2_dvfs_rail_vdd_cpu, \ + } -#ifdef CONFIG_TEGRA_CORE_DVFS /* CPU_DVFS && CORE_DVFS */ -#define CPU_DVFS_CORE(_clk_name, _process_id, _mult, _freqs...) \ +#define CORE_DVFS(_clk_name, _auto, _mult, _freqs...) \ { \ .clk_name = _clk_name, \ - .reg_id = CORE_REGULATOR, \ - .cpu = false, \ - .process_id = _process_id, \ + .cpu_process_id = -1, \ .freqs = {_freqs}, \ .freqs_mult = _mult, \ - .auto_dvfs = true, \ - .higher = true, \ - .max_millivolts = CORE_MAX_MILLIVOLTS \ - }, -#else /* CPU_DVFS && !CORE_DVFS */ -#define CPU_DVFS_CORE(_clk_name, _process_id, _mult, _freqs...) -#endif -#else /* !CPU_DVFS */ -#define CPU_DVFS_CPU(_clk_name, _process_id, _mult, _freqs...) -#define CPU_DVFS_CORE(_clk_name, _process_id, _mult, _freqs...) -#endif - -#ifdef CONFIG_TEGRA_CORE_DVFS -#define CORE_DVFS(_clk_name, _auto, _mult, _freqs...) \ - { \ - .clk_name = _clk_name, \ - .reg_id = CORE_REGULATOR, \ - .process_id = -1, \ - .freqs = {_freqs}, \ - .freqs_mult = _mult, \ - .auto_dvfs = _auto, \ - .max_millivolts = CORE_MAX_MILLIVOLTS \ - }, -#else -#define CORE_DVFS(_clk_name, _process_id, _mult, _freqs...) -#endif - -#define CPU_DVFS(_clk_name, _process_id, _mult, _freqs...) \ - CPU_DVFS_CORE(_clk_name, _process_id, _mult, _freqs) \ - CPU_DVFS_CPU(_clk_name, _process_id, _mult, _freqs) \ - + .millivolts = core_millivolts, \ + .auto_dvfs = _auto, \ + .dvfs_rail = &tegra2_dvfs_rail_vdd_core, \ + } static struct dvfs dvfs_init[] = { /* Cpu voltages (mV): 750, 775, 800, 825, 875, 900, 925, 975, 1000, 1050, 1100 */ - CPU_DVFS("cpu", 0, MHZ, 314, 314, 314, 456, 456, 608, 608, 760, 817, 912, 1000) - CPU_DVFS("cpu", 1, MHZ, 314, 314, 314, 456, 456, 618, 618, 770, 827, 922, 1000) - CPU_DVFS("cpu", 2, MHZ, 494, 675, 675, 675, 817, 817, 922, 1000) - CPU_DVFS("cpu", 3, MHZ, 730, 760, 845, 845, 1000) + CPU_DVFS("cpu", 0, MHZ, 314, 314, 314, 456, 456, 608, 608, 760, 817, 912, 1000), + CPU_DVFS("cpu", 1, MHZ, 314, 314, 314, 456, 456, 618, 618, 770, 827, 922, 1000), + CPU_DVFS("cpu", 2, MHZ, 494, 675, 675, 675, 817, 817, 922, 1000), + CPU_DVFS("cpu", 3, MHZ, 730, 760, 845, 845, 1000), /* Core voltages (mV): 950, 1000, 1100, 1200, 1275 */ + CORE_DVFS("emc", 1, KHZ, 57000, 333000, 333000, 666000, 666000), #if 0 /* @@ -110,22 +159,22 @@ static struct dvfs dvfs_init[] = { * For now, boards must ensure that the core voltage does not drop * below 1V, or that the sdmmc busses are set to 44 MHz or less. */ - CORE_DVFS("sdmmc1", 1, KHZ, 44000, 52000, 52000, 52000, 52000) - CORE_DVFS("sdmmc2", 1, KHZ, 44000, 52000, 52000, 52000, 52000) - CORE_DVFS("sdmmc3", 1, KHZ, 44000, 52000, 52000, 52000, 52000) - CORE_DVFS("sdmmc4", 1, KHZ, 44000, 52000, 52000, 52000, 52000) + CORE_DVFS("sdmmc1", 1, KHZ, 44000, 52000, 52000, 52000, 52000), + CORE_DVFS("sdmmc2", 1, KHZ, 44000, 52000, 52000, 52000, 52000), + CORE_DVFS("sdmmc3", 1, KHZ, 44000, 52000, 52000, 52000, 52000), + CORE_DVFS("sdmmc4", 1, KHZ, 44000, 52000, 52000, 52000, 52000), #endif - CORE_DVFS("ndflash", 1, KHZ, 130000, 150000, 158000, 164000, 164000) - CORE_DVFS("nor", 1, KHZ, 0, 92000, 92000, 92000, 92000) - CORE_DVFS("ide", 1, KHZ, 0, 0, 100000, 100000, 100000) - CORE_DVFS("mipi", 1, KHZ, 0, 40000, 40000, 40000, 60000) - CORE_DVFS("usbd", 1, KHZ, 0, 0, 480000, 480000, 480000) - CORE_DVFS("usb2", 1, KHZ, 0, 0, 480000, 480000, 480000) - CORE_DVFS("usb3", 1, KHZ, 0, 0, 480000, 480000, 480000) - CORE_DVFS("pcie", 1, KHZ, 0, 0, 0, 250000, 250000) - CORE_DVFS("dsi", 1, KHZ, 100000, 100000, 100000, 500000, 500000) - CORE_DVFS("tvo", 1, KHZ, 0, 0, 0, 250000, 250000) + CORE_DVFS("ndflash", 1, KHZ, 130000, 150000, 158000, 164000, 164000), + CORE_DVFS("nor", 1, KHZ, 0, 92000, 92000, 92000, 92000), + CORE_DVFS("ide", 1, KHZ, 0, 0, 100000, 100000, 100000), + CORE_DVFS("mipi", 1, KHZ, 0, 40000, 40000, 40000, 60000), + CORE_DVFS("usbd", 1, KHZ, 0, 0, 480000, 480000, 480000), + CORE_DVFS("usb2", 1, KHZ, 0, 0, 480000, 480000, 480000), + CORE_DVFS("usb3", 1, KHZ, 0, 0, 480000, 480000, 480000), + CORE_DVFS("pcie", 1, KHZ, 0, 0, 0, 250000, 250000), + CORE_DVFS("dsi", 1, KHZ, 100000, 100000, 100000, 500000, 500000), + CORE_DVFS("tvo", 1, KHZ, 0, 0, 0, 250000, 250000), /* * The clock rate for the display controllers that determines the @@ -133,54 +182,99 @@ static struct dvfs dvfs_init[] = { * to the display block. Disable auto-dvfs on the display clocks, * and let the display driver call tegra_dvfs_set_rate manually */ - CORE_DVFS("disp1", 0, KHZ, 158000, 158000, 190000, 190000, 190000) - CORE_DVFS("disp2", 0, KHZ, 158000, 158000, 190000, 190000, 190000) - CORE_DVFS("hdmi", 0, KHZ, 0, 0, 0, 148500, 148500) + CORE_DVFS("disp1", 0, KHZ, 158000, 158000, 190000, 190000, 190000), + CORE_DVFS("disp2", 0, KHZ, 158000, 158000, 190000, 190000, 190000), + CORE_DVFS("hdmi", 0, KHZ, 0, 0, 0, 148500, 148500), /* * These clocks technically depend on the core process id, * but just use the worst case value for now */ - CORE_DVFS("host1x", 1, KHZ, 104500, 133000, 166000, 166000, 166000) - CORE_DVFS("epp", 1, KHZ, 133000, 171000, 247000, 300000, 300000) - CORE_DVFS("2d", 1, KHZ, 133000, 171000, 247000, 300000, 300000) - CORE_DVFS("3d", 1, KHZ, 114000, 161500, 247000, 300000, 300000) - CORE_DVFS("mpe", 1, KHZ, 104500, 152000, 228000, 250000, 250000) - CORE_DVFS("vi", 1, KHZ, 85000, 100000, 150000, 150000, 150000) - CORE_DVFS("sclk", 1, KHZ, 95000, 133000, 190000, 250000, 250000) - CORE_DVFS("vde", 1, KHZ, 95000, 123500, 209000, 250000, 250000) + CORE_DVFS("host1x", 1, KHZ, 104500, 133000, 166000, 166000, 166000), + CORE_DVFS("epp", 1, KHZ, 133000, 171000, 247000, 300000, 300000), + CORE_DVFS("2d", 1, KHZ, 133000, 171000, 247000, 300000, 300000), + CORE_DVFS("3d", 1, KHZ, 114000, 161500, 247000, 300000, 300000), + CORE_DVFS("mpe", 1, KHZ, 104500, 152000, 228000, 250000, 250000), + CORE_DVFS("vi", 1, KHZ, 85000, 100000, 150000, 150000, 150000), + CORE_DVFS("sclk", 1, KHZ, 95000, 133000, 190000, 250000, 250000), + CORE_DVFS("vde", 1, KHZ, 95000, 123500, 209000, 250000, 250000), /* What is this? */ - CORE_DVFS("NVRM_DEVID_CLK_SRC", 1, MHZ, 480, 600, 800, 1067, 1067) + CORE_DVFS("NVRM_DEVID_CLK_SRC", 1, MHZ, 480, 600, 800, 1067, 1067), }; +int tegra_dvfs_disable_core_set(const char *arg, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_bool(arg, kp); + if (ret) + return ret; + + if (tegra_dvfs_core_disabled) + tegra_dvfs_rail_disable(&tegra2_dvfs_rail_vdd_core); + else + tegra_dvfs_rail_enable(&tegra2_dvfs_rail_vdd_core); + + return 0; +} + +int tegra_dvfs_disable_cpu_set(const char *arg, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_bool(arg, kp); + if (ret) + return ret; + + if (tegra_dvfs_cpu_disabled) + tegra_dvfs_rail_disable(&tegra2_dvfs_rail_vdd_cpu); + else + tegra_dvfs_rail_enable(&tegra2_dvfs_rail_vdd_cpu); + + return 0; +} + +int tegra_dvfs_disable_get(char *buffer, const struct kernel_param *kp) +{ + return param_get_bool(buffer, kp); +} + +static struct kernel_param_ops tegra_dvfs_disable_core_ops = { + .set = tegra_dvfs_disable_core_set, + .get = tegra_dvfs_disable_get, +}; + +static struct kernel_param_ops tegra_dvfs_disable_cpu_ops = { + .set = tegra_dvfs_disable_cpu_set, + .get = tegra_dvfs_disable_get, +}; + +module_param_cb(disable_core, &tegra_dvfs_disable_core_ops, + &tegra_dvfs_core_disabled, 0644); +module_param_cb(disable_cpu, &tegra_dvfs_disable_cpu_ops, + &tegra_dvfs_cpu_disabled, 0644); + void __init tegra2_init_dvfs(void) { int i; struct clk *c; struct dvfs *d; - int process_id; int ret; - int cpu_process_id = tegra_cpu_process_id(); - int core_process_id = tegra_core_process_id(); + tegra_dvfs_init_rails(tegra2_dvfs_rails, ARRAY_SIZE(tegra2_dvfs_rails)); + tegra_dvfs_add_relationships(tegra2_dvfs_relationships, + ARRAY_SIZE(tegra2_dvfs_relationships)); /* * VDD_CORE must always be at least 50 mV higher than VDD_CPU * Fill out cpu_core_millivolts based on cpu_millivolts */ - for (i = 0; i < ARRAY_SIZE(cpu_millivolts); i++) - if (cpu_millivolts[i]) - cpu_core_millivolts[i] = cpu_millivolts[i] + 50; - for (i = 0; i < ARRAY_SIZE(dvfs_init); i++) { d = &dvfs_init[i]; - process_id = d->cpu ? cpu_process_id : core_process_id; - if (d->process_id != -1 && d->process_id != process_id) { - pr_debug("tegra_dvfs: rejected %s %d, process_id %d\n", - d->clk_name, d->process_id, process_id); + if (d->cpu_process_id != -1 && + d->cpu_process_id != cpu_process_id) continue; - } c = tegra_get_clock_by_name(d->clk_name); @@ -190,19 +284,15 @@ void __init tegra2_init_dvfs(void) continue; } - if (d->cpu) - memcpy(d->millivolts, cpu_millivolts, - sizeof(cpu_millivolts)); - else if (!strcmp(d->clk_name, "cpu")) - memcpy(d->millivolts, cpu_core_millivolts, - sizeof(cpu_core_millivolts)); - else - memcpy(d->millivolts, core_millivolts, - sizeof(core_millivolts)); - ret = tegra_enable_dvfs_on_clk(c, d); if (ret) pr_err("tegra_dvfs: failed to enable dvfs on %s\n", c->name); } + + if (tegra_dvfs_core_disabled) + tegra_dvfs_rail_disable(&tegra2_dvfs_rail_vdd_core); + + if (tegra_dvfs_cpu_disabled) + tegra_dvfs_rail_disable(&tegra2_dvfs_rail_vdd_cpu); } diff --git a/arch/arm/mach-tegra/tegra2_emc.c b/arch/arm/mach-tegra/tegra2_emc.c new file mode 100644 index 000000000000..bd4fa27b2086 --- /dev/null +++ b/arch/arm/mach-tegra/tegra2_emc.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> + +#include <mach/iomap.h> + +#include "tegra2_emc.h" + +#ifdef CONFIG_TEGRA_EMC_SCALING_ENABLE +static bool emc_enable = true; +#else +static bool emc_enable; +#endif +module_param(emc_enable, bool, 0644); + +static void __iomem *emc = IO_ADDRESS(TEGRA_EMC_BASE); +static const struct tegra_emc_table *tegra_emc_table; +static int tegra_emc_table_size; + +static inline void emc_writel(u32 val, unsigned long addr) +{ + writel(val, emc + addr); +} + +static inline u32 emc_readl(unsigned long addr) +{ + return readl(emc + addr); +} + +static const unsigned long emc_reg_addr[TEGRA_EMC_NUM_REGS] = { + 0x2c, /* RC */ + 0x30, /* RFC */ + 0x34, /* RAS */ + 0x38, /* RP */ + 0x3c, /* R2W */ + 0x40, /* W2R */ + 0x44, /* R2P */ + 0x48, /* W2P */ + 0x4c, /* RD_RCD */ + 0x50, /* WR_RCD */ + 0x54, /* RRD */ + 0x58, /* REXT */ + 0x5c, /* WDV */ + 0x60, /* QUSE */ + 0x64, /* QRST */ + 0x68, /* QSAFE */ + 0x6c, /* RDV */ + 0x70, /* REFRESH */ + 0x74, /* BURST_REFRESH_NUM */ + 0x78, /* PDEX2WR */ + 0x7c, /* PDEX2RD */ + 0x80, /* PCHG2PDEN */ + 0x84, /* ACT2PDEN */ + 0x88, /* AR2PDEN */ + 0x8c, /* RW2PDEN */ + 0x90, /* TXSR */ + 0x94, /* TCKE */ + 0x98, /* TFAW */ + 0x9c, /* TRPAB */ + 0xa0, /* TCLKSTABLE */ + 0xa4, /* TCLKSTOP */ + 0xa8, /* TREFBW */ + 0xac, /* QUSE_EXTRA */ + 0x114, /* FBIO_CFG6 */ + 0xb0, /* ODT_WRITE */ + 0xb4, /* ODT_READ */ + 0x104, /* FBIO_CFG5 */ + 0x2bc, /* CFG_DIG_DLL */ + 0x2c0, /* DLL_XFORM_DQS */ + 0x2c4, /* DLL_XFORM_QUSE */ + 0x2e0, /* ZCAL_REF_CNT */ + 0x2e4, /* ZCAL_WAIT_CNT */ + 0x2a8, /* AUTO_CAL_INTERVAL */ + 0x2d0, /* CFG_CLKTRIM_0 */ + 0x2d4, /* CFG_CLKTRIM_1 */ + 0x2d8, /* CFG_CLKTRIM_2 */ +}; + +/* Select the closest EMC rate that is higher than the requested rate */ +long tegra_emc_round_rate(unsigned long rate) +{ + int i; + int best = -1; + unsigned long distance = ULONG_MAX; + + if (!tegra_emc_table) + return -EINVAL; + + if (!emc_enable) + return -EINVAL; + + pr_debug("%s: %lu\n", __func__, rate); + + /* The EMC clock rate is twice the bus rate, and the bus rate is + * measured in kHz */ + rate = rate / 2 / 1000; + + for (i = 0; i < tegra_emc_table_size; i++) { + if (tegra_emc_table[i].rate >= rate && + (tegra_emc_table[i].rate - rate) < distance) { + distance = tegra_emc_table[i].rate - rate; + best = i; + } + } + + if (best < 0) + return -EINVAL; + + pr_debug("%s: using %lu\n", __func__, tegra_emc_table[best].rate); + + return tegra_emc_table[best].rate * 2 * 1000; +} + +/* The EMC registers have shadow registers. When the EMC clock is updated + * in the clock controller, the shadow registers are copied to the active + * registers, allowing glitchless memory bus frequency changes. + * This function updates the shadow registers for a new clock frequency, + * and relies on the clock lock on the emc clock to avoid races between + * multiple frequency changes */ +int tegra_emc_set_rate(unsigned long rate) +{ + int i; + int j; + + if (!tegra_emc_table) + return -EINVAL; + + /* The EMC clock rate is twice the bus rate, and the bus rate is + * measured in kHz */ + rate = rate / 2 / 1000; + + for (i = 0; i < tegra_emc_table_size; i++) + if (tegra_emc_table[i].rate == rate) + break; + + if (i >= tegra_emc_table_size) + return -EINVAL; + + pr_debug("%s: setting to %lu\n", __func__, rate); + + for (j = 0; j < TEGRA_EMC_NUM_REGS; j++) + emc_writel(tegra_emc_table[i].regs[j], emc_reg_addr[j]); + + emc_readl(tegra_emc_table[i].regs[TEGRA_EMC_NUM_REGS - 1]); + + return 0; +} + +void tegra_init_emc(const struct tegra_emc_table *table, int table_size) +{ + tegra_emc_table = table; + tegra_emc_table_size = table_size; +} diff --git a/arch/arm/mach-tegra/tegra2_emc.h b/arch/arm/mach-tegra/tegra2_emc.h new file mode 100644 index 000000000000..3515e57fd0d9 --- /dev/null +++ b/arch/arm/mach-tegra/tegra2_emc.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define TEGRA_EMC_NUM_REGS 46 + +struct tegra_emc_table { + unsigned long rate; + u32 regs[TEGRA_EMC_NUM_REGS]; +}; + +int tegra_emc_set_rate(unsigned long rate); +long tegra_emc_round_rate(unsigned long rate); +void tegra_init_emc(const struct tegra_emc_table *table, int table_size); diff --git a/arch/arm/mach-tegra/tegra_i2s_audio.c b/arch/arm/mach-tegra/tegra_i2s_audio.c index f3ec7b6fb5f1..f3cd2c42278e 100644 --- a/arch/arm/mach-tegra/tegra_i2s_audio.c +++ b/arch/arm/mach-tegra/tegra_i2s_audio.c @@ -695,12 +695,17 @@ static void request_stop_nosync(struct audio_stream *as) pr_debug("%s\n", __func__); if (!as->stop) { as->stop = true; - wait_till_stopped(as); + if (pending_buffer_requests(as)) + wait_till_stopped(as); for (i = 0; i < as->num_bufs; i++) { init_completion(&as->comp[i]); complete(&as->comp[i]); } } + if (!tegra_dma_is_empty(as->dma_chan)) + pr_err("%s: DMA not empty!\n", __func__); + /* Stop the DMA then dequeue anything that's in progress. */ + tegra_dma_cancel(as->dma_chan); as->active = false; /* applies to recording only */ pr_debug("%s: done\n", __func__); } @@ -826,13 +831,9 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req) complete(&aos->comp[req_num]); - if (stop_playback_if_necessary(aos)) { - pr_debug("%s: done (stopped)\n", __func__); - if (!completion_done(&aos->stop_completion)) { - pr_debug("%s: signalling stop completion\n", __func__); - complete(&aos->stop_completion); - } - return; + if (!pending_buffer_requests(aos)) { + pr_debug("%s: Playback underflow\n", __func__); + complete(&aos->stop_completion); } } @@ -851,6 +852,9 @@ static void dma_rx_complete_callback(struct tegra_dma_req *req) complete(&ais->comp[req_num]); + if (!pending_buffer_requests(ais)) + pr_debug("%s: Capture overflow\n", __func__); + spin_unlock_irqrestore(&ais->dma_req_lock, flags); } @@ -1088,6 +1092,8 @@ static long tegra_audio_out_ioctl(struct file *file, request_stop_nosync(aos); pr_debug("%s: flushed\n", __func__); } + if (stop_playback_if_necessary(aos)) + pr_debug("%s: done (stopped)\n", __func__); aos->stop = false; break; case TEGRA_AUDIO_OUT_SET_NUM_BUFS: { @@ -1111,6 +1117,7 @@ static long tegra_audio_out_ioctl(struct file *file, if (rc < 0) break; aos->num_bufs = num; + sound_ops->setup(ads); } break; case TEGRA_AUDIO_OUT_GET_NUM_BUFS: @@ -1265,10 +1272,11 @@ static long tegra_audio_in_ioctl(struct file *file, if (rc < 0) break; ais->num_bufs = num; + sound_ops->setup(ads); } break; case TEGRA_AUDIO_IN_GET_NUM_BUFS: - if (copy_from_user((void __user *)arg, + if (copy_to_user((void __user *)arg, &ais->num_bufs, sizeof(ais->num_bufs))) rc = -EFAULT; break; @@ -1404,6 +1412,8 @@ static int tegra_audio_out_release(struct inode *inode, struct file *file) mutex_lock(&ads->out.lock); ads->out.opened = 0; request_stop_nosync(&ads->out); + if (stop_playback_if_necessary(&ads->out)) + pr_debug("%s: done (stopped)\n", __func__); allow_suspend(&ads->out); mutex_unlock(&ads->out.lock); pr_debug("%s: done\n", __func__); diff --git a/arch/arm/mach-tegra/tegra_spdif_audio.c b/arch/arm/mach-tegra/tegra_spdif_audio.c index 0848b1550dfe..6613d3d5edeb 100644 --- a/arch/arm/mach-tegra/tegra_spdif_audio.c +++ b/arch/arm/mach-tegra/tegra_spdif_audio.c @@ -460,12 +460,17 @@ static void request_stop_nosync(struct audio_stream *as) pr_debug("%s\n", __func__); if (!as->stop) { as->stop = true; - wait_till_stopped(as); + if (pending_buffer_requests(as)) + wait_till_stopped(as); for (i = 0; i < as->num_bufs; i++) { init_completion(&as->comp[i]); complete(&as->comp[i]); } } + if (!tegra_dma_is_empty(as->dma_chan)) + pr_err("%s: DMA not empty!\n", __func__); + /* Stop the DMA then dequeue anything that's in progress. */ + tegra_dma_cancel(as->dma_chan); as->active = false; /* applies to recording only */ pr_debug("%s: done\n", __func__); } @@ -543,13 +548,9 @@ static void dma_tx_complete_callback(struct tegra_dma_req *req) complete(&aos->comp[req_num]); - if (stop_playback_if_necessary(aos)) { - pr_debug("%s: done (stopped)\n", __func__); - if (!completion_done(&aos->stop_completion)) { - pr_debug("%s: signalling stop completion\n", __func__); - complete(&aos->stop_completion); - } - return; + if (!pending_buffer_requests(aos)) { + pr_debug("%s: Playback underflow", __func__); + complete(&aos->stop_completion); } } @@ -729,6 +730,8 @@ static long tegra_spdif_out_ioctl(struct file *file, request_stop_nosync(aos); pr_debug("%s: flushed\n", __func__); } + if (stop_playback_if_necessary(aos)) + pr_debug("%s: done (stopped)\n", __func__); aos->stop = false; break; case TEGRA_AUDIO_OUT_SET_NUM_BUFS: { @@ -752,6 +755,7 @@ static long tegra_spdif_out_ioctl(struct file *file, if (rc < 0) break; aos->num_bufs = num; + sound_ops->setup(ads); } break; case TEGRA_AUDIO_OUT_GET_NUM_BUFS: @@ -806,6 +810,8 @@ static int tegra_spdif_out_release(struct inode *inode, struct file *file) mutex_lock(&ads->out.lock); ads->out.opened = 0; request_stop_nosync(&ads->out); + if (stop_playback_if_necessary(&ads->out)) + pr_debug("%s: done (stopped)\n", __func__); allow_suspend(&ads->out); mutex_unlock(&ads->out.lock); pr_debug("%s: done\n", __func__); |