diff options
Diffstat (limited to 'arch/arm/mach-tegra/tegra3_emc.c')
-rw-r--r-- | arch/arm/mach-tegra/tegra3_emc.c | 241 |
1 files changed, 203 insertions, 38 deletions
diff --git a/arch/arm/mach-tegra/tegra3_emc.c b/arch/arm/mach-tegra/tegra3_emc.c index 0725980bfa40..a138091d9197 100644 --- a/arch/arm/mach-tegra/tegra3_emc.c +++ b/arch/arm/mach-tegra/tegra3_emc.c @@ -1,7 +1,7 @@ /* * arch/arm/mach-tegra/tegra3_emc.c * - * Copyright (C) 2011 NVIDIA Corporation + * Copyright (C) 2012 NVIDIA Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,6 +45,8 @@ static bool emc_enable; #endif module_param(emc_enable, bool, 0644); +u8 tegra_emc_bw_efficiency = 35; + #define EMC_MIN_RATE_DDR3 25500000 #define EMC_STATUS_UPDATE_TIMEOUT 100 #define TEGRA_EMC_TABLE_MAX_SIZE 16 @@ -187,7 +189,13 @@ enum { static int emc_num_burst_regs; -static struct clk_mux_sel tegra_emc_clk_sel[TEGRA_EMC_TABLE_MAX_SIZE]; +struct emc_sel { + struct clk *input; + u32 value; + unsigned long input_rate; +}; + +static struct emc_sel tegra_emc_clk_sel[TEGRA_EMC_TABLE_MAX_SIZE]; static struct tegra_emc_table start_timing; static const struct tegra_emc_table *emc_timing; static unsigned long dram_over_temp_state = DRAM_OVER_TEMP_NONE; @@ -350,6 +358,12 @@ static inline void disable_early_ack(u32 mc_override) override_val |= mc_override & MC_EMEM_ARB_OVERRIDE_EACK_MASK; } +static inline void enable_early_ack(u32 mc_override) +{ + mc_writel((mc_override | MC_EMEM_ARB_OVERRIDE_EACK_MASK), + MC_EMEM_ARB_OVERRIDE); +} + static inline bool dqs_preset(const struct tegra_emc_table *next_timing, const struct tegra_emc_table *last_timing) { @@ -489,6 +503,7 @@ static inline void do_clock_change(u32 clk_setting) mc_readl(MC_EMEM_ADR_CFG); /* completes prev writes */ writel(clk_setting, (u32)clk_base + emc->reg); + readl((u32)clk_base + emc->reg);/* completes prev write */ err = wait_for_update(EMC_INTSTATUS, EMC_INTSTATUS_CLKCHANGE_COMPLETE, true); @@ -702,7 +717,7 @@ static inline void emc_cfg_power_restore(void) * 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) +static int emc_set_rate(unsigned long rate, bool use_backup) { int i; u32 clk_setting; @@ -735,7 +750,8 @@ int tegra_emc_set_rate(unsigned long rate) else last_timing = emc_timing; - clk_setting = tegra_emc_clk_sel[i].value; + clk_setting = use_backup ? emc->shared_bus_backup.value : + tegra_emc_clk_sel[i].value; spin_lock_irqsave(&emc_access_lock, flags); emc_set_clock(&tegra_emc_table[i], last_timing, clk_setting); @@ -751,6 +767,17 @@ int tegra_emc_set_rate(unsigned long rate) return 0; } +int tegra_emc_set_rate(unsigned long rate) +{ + return emc_set_rate(rate, false); +} + +int tegra_emc_backup(unsigned long rate) +{ + BUG_ON(rate != emc->shared_bus_backup.bus_rate); + return emc_set_rate(rate, true); +} + /* Select the closest EMC rate that is higher than the requested rate */ long tegra_emc_round_rate(unsigned long rate) { @@ -793,7 +820,7 @@ struct clk *tegra_emc_predict_parent(unsigned long rate, u32 *div_value) int i; if (!tegra_emc_table) - return NULL; + return ERR_PTR(-ENOENT); pr_debug("%s: %lu\n", __func__, rate); @@ -802,33 +829,94 @@ struct clk *tegra_emc_predict_parent(unsigned long rate, u32 *div_value) for (i = 0; i < tegra_emc_table_size; i++) { if (tegra_emc_table[i].rate == rate) { + struct clk *p = tegra_emc_clk_sel[i].input; + *div_value = (tegra_emc_clk_sel[i].value & EMC_CLK_DIV_MASK) >> EMC_CLK_DIV_SHIFT; - return tegra_emc_clk_sel[i].input; + if (tegra_emc_clk_sel[i].input_rate != clk_get_rate(p)) + return NULL; + + return p; } } - - return NULL; + return ERR_PTR(-ENOENT); } -static const struct clk_mux_sel *find_matching_input( - unsigned long table_rate, - u32 *div_value) +int find_matching_input(unsigned long table_rate, bool mc_same_freq, + struct emc_sel *emc_clk_sel, struct clk *cbus) { - unsigned long inp_rate; + u32 div_value = 0; + unsigned long input_rate = 0; const struct clk_mux_sel *sel; + const struct clk_mux_sel *parent_sel = NULL; + const struct clk_mux_sel *backup_sel = NULL; + + /* Table entries specify rate in kHz */ + table_rate *= 1000; for (sel = emc->inputs; sel->input != NULL; sel++) { - /* Table entries specify rate in kHz */ - inp_rate = clk_get_rate(sel->input) / 1000; + if (sel->input == emc->shared_bus_backup.input) { + backup_sel = sel; + continue; /* skip backup souce */ + } - if ((inp_rate >= table_rate) && - (inp_rate % table_rate == 0)) { - *div_value = 2 * inp_rate / table_rate - 2; - return sel; + if (sel->input == emc->parent) + parent_sel = sel; + + input_rate = clk_get_rate(sel->input); + + if ((input_rate >= table_rate) && + (input_rate % table_rate == 0)) { + div_value = 2 * input_rate / table_rate - 2; + break; + } + } + +#ifdef CONFIG_TEGRA_PLLM_RESTRICTED + /* + * When match not found, check if this rate can be backed-up by cbus + * Then, we will be able to re-lock boot parent PLLM, and use it as + * an undivided source. Backup is supported only on LPDDR2 platforms + * with restricted PLLM usage. Just one backup entry is recognized, + * and it must be between EMC maximum and half maximum rates. + */ + if ((dram_type == DRAM_TYPE_LPDDR2) && (sel->input == NULL) && + (emc->shared_bus_backup.bus_rate == 0) && cbus) { + BUG_ON(!parent_sel || !backup_sel); + + if ((table_rate == clk_round_rate(cbus, table_rate)) && + (table_rate < clk_get_max_rate(emc)) && + (table_rate >= clk_get_max_rate(emc) / 2)) { + emc->shared_bus_backup.bus_rate = table_rate; + + /* Get ready emc clock backup selection settings */ + emc->shared_bus_backup.value = + (backup_sel->value << EMC_CLK_SOURCE_SHIFT) | + (cbus->div << EMC_CLK_DIV_SHIFT) | + (mc_same_freq ? EMC_CLK_MC_SAME_FREQ : 0); + + /* Select undivided PLLM as regular source */ + sel = parent_sel; + input_rate = table_rate; + div_value = 0; } } - return NULL; +#endif + + if (sel->input) { + emc_clk_sel->input = sel->input; + emc_clk_sel->input_rate = input_rate; + + /* Get ready emc clock selection settings for this table rate */ + emc_clk_sel->value = sel->value << EMC_CLK_SOURCE_SHIFT; + emc_clk_sel->value |= (div_value << EMC_CLK_DIV_SHIFT); + if ((div_value == 0) && (emc_clk_sel->input == emc->parent)) + emc_clk_sel->value |= EMC_CLK_LOW_JITTER_ENABLE; + if (mc_same_freq) + emc_clk_sel->value |= EMC_CLK_MC_SAME_FREQ; + return 0; + } + return -EINVAL; } static void adjust_emc_dvfs_table(const struct tegra_emc_table *table, @@ -932,10 +1020,10 @@ static struct notifier_block tegra_emc_resume_nb = { void tegra_init_emc(const struct tegra_emc_table *table, int table_size) { int i, mv; - u32 reg, div_value; + u32 reg; bool max_entry = false; unsigned long boot_rate, max_rate; - const struct clk_mux_sel *sel; + struct clk *cbus = tegra_get_clock_by_name("cbus"); emc_stats.clkchange_count = 0; spin_lock_init(&emc_stats.spinlock); @@ -979,14 +1067,16 @@ void tegra_init_emc(const struct tegra_emc_table *table, int table_size) /* Match EMC source/divider settings with table entries */ for (i = 0; i < tegra_emc_table_size; i++) { + bool mc_same_freq = MC_EMEM_ARB_MISC0_EMC_SAME_FREQ & + table[i].burst_regs[MC_EMEM_ARB_MISC0_INDEX]; unsigned long table_rate = table[i].rate; if (!table_rate) continue; BUG_ON(table[i].rev != table[0].rev); - sel = find_matching_input(table_rate, &div_value); - if (!sel) + if (find_matching_input(table_rate, mc_same_freq, + &tegra_emc_clk_sel[i], cbus)) continue; if (table_rate == boot_rate) @@ -994,21 +1084,6 @@ void tegra_init_emc(const struct tegra_emc_table *table, int table_size) if (table_rate == max_rate) max_entry = true; - - tegra_emc_clk_sel[i] = *sel; - BUG_ON(div_value > - (EMC_CLK_DIV_MASK >> EMC_CLK_DIV_SHIFT)); - tegra_emc_clk_sel[i].value <<= EMC_CLK_SOURCE_SHIFT; - tegra_emc_clk_sel[i].value |= (div_value << EMC_CLK_DIV_SHIFT); - - if ((div_value == 0) && - (tegra_emc_clk_sel[i].input == emc->parent)) { - tegra_emc_clk_sel[i].value |= EMC_CLK_LOW_JITTER_ENABLE; - } - - if (table[i].burst_regs[MC_EMEM_ARB_MISC0_INDEX] & - MC_EMEM_ARB_MISC0_EMC_SAME_FREQ) - tegra_emc_clk_sel[i].value |= EMC_CLK_MC_SAME_FREQ; } /* Validate EMC rate and voltage limits */ @@ -1163,6 +1238,51 @@ int tegra_emc_set_over_temp_state(unsigned long state) return 0; } +/* non-zero state value will reduce eack_disable_refcnt */ +static int tegra_emc_set_eack_state(unsigned long state) +{ + unsigned long flags; + u32 mc_override; + static int eack_disable_refcnt = 0; + + spin_lock_irqsave(&emc_access_lock, flags); + + /* + * refcnt > 0 implies there is at least one client requiring eack + * disabled. refcnt of 0 implies eack is enabled + */ + if (eack_disable_refcnt == 1 && state) { + mc_override = mc_readl(MC_EMEM_ARB_OVERRIDE); + enable_early_ack(mc_override); + } else if (eack_disable_refcnt == 0 && !state) { + mc_override = mc_readl(MC_EMEM_ARB_OVERRIDE); + disable_early_ack(mc_override); + } + + if (state) { + if (likely(eack_disable_refcnt > 0)) { + --eack_disable_refcnt; + } else { + pr_err("%s: Ignored a request to underflow eack " + "disable reference counter\n",__func__); + dump_stack(); + } + } else { + ++eack_disable_refcnt; + } + + spin_unlock_irqrestore(&emc_access_lock, flags); + return 0; +} + +int tegra_emc_enable_eack(void) { + return tegra_emc_set_eack_state(1); +} + +int tegra_emc_disable_eack(void) { + return tegra_emc_set_eack_state(0); +} + #ifdef CONFIG_DEBUG_FS static struct dentry *emc_debugfs_root; @@ -1222,6 +1342,43 @@ static int over_temp_state_set(void *data, u64 val) DEFINE_SIMPLE_ATTRIBUTE(over_temp_state_fops, over_temp_state_get, over_temp_state_set, "%llu\n"); +static int eack_state_get(void *data, u64 *val) +{ + unsigned long flags; + u32 mc_override; + + spin_lock_irqsave(&emc_access_lock, flags); + mc_override = mc_readl(MC_EMEM_ARB_OVERRIDE); + spin_unlock_irqrestore(&emc_access_lock, flags); + + *val = (mc_override & MC_EMEM_ARB_OVERRIDE_EACK_MASK); + return 0; +} + +static int eack_state_set(void *data, u64 val) +{ + tegra_emc_set_eack_state(val); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(eack_state_fops, eack_state_get, + eack_state_set, "%llu\n"); + +static int efficiency_get(void *data, u64 *val) +{ + *val = tegra_emc_bw_efficiency; + return 0; +} +static int efficiency_set(void *data, u64 val) +{ + tegra_emc_bw_efficiency = (val > 100) ? 100 : val; + if (emc) + tegra_clk_shared_bus_update(emc); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(efficiency_fops, efficiency_get, + efficiency_set, "%llu\n"); + static int __init tegra_emc_debug_init(void) { if (!tegra_emc_table) @@ -1243,6 +1400,14 @@ static int __init tegra_emc_debug_init(void) emc_debugfs_root, NULL, &over_temp_state_fops)) goto err_out; + if (!debugfs_create_file( + "eack_state", S_IRUGO | S_IWUSR, emc_debugfs_root, NULL, &eack_state_fops)) + goto err_out; + + if (!debugfs_create_file("efficiency", S_IRUGO | S_IWUSR, + emc_debugfs_root, NULL, &efficiency_fops)) + goto err_out; + return 0; err_out: |