diff options
author | Pavan Kunapuli <pkunapuli@nvidia.com> | 2014-01-09 17:16:13 +0530 |
---|---|---|
committer | Harry Hong <hhong@nvidia.com> | 2014-01-27 22:25:27 -0800 |
commit | 4a084ac0fb7e801a1ce24d099edf8eef3d79f5ad (patch) | |
tree | 9cd1f8a7864c09157afbd4866b9375612ccc5ddd /drivers | |
parent | 7155355d224be8661ff3bc0604dbfa1a20fffc51 (diff) |
mmc: tegra: Implementation of new tuning algorithm
Implemented the new tuning algorithm that
- Removes the tap holes from auto tuning windows
- Adjust window ends with margins for Vmin for the corresponding freq
- Dynamically updates dvfs table entry if a new Vmin is found from
auto tuning calculations
- Calculates the best tap value from the windows after removing tap
holes and margin additions.
Disabling external loopback clock for SDMMC3 as per characterization
results.
Bug 1423423
Reviewed-on: http://git-master/r/352306
(cherry picked from commit 26615d96514fc9ccbdcdc9ab89d92def5c1bf977)
Change-Id: I56fd58473ab5b7642d703fc17f0afd5820fe64ea
Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com>
Reviewed-on: http://git-master/r/356523
Reviewed-by: Harry Hong <hhong@nvidia.com>
Tested-by: Harry Hong <hhong@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mmc/host/sdhci-tegra.c | 1382 |
1 files changed, 915 insertions, 467 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index bcca95d2b9cd..4878ddc7d6fa 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -60,19 +60,20 @@ #define SDHCI_VNDR_CLK_CTRL_SPI_MODE_CLKEN_OVERRIDE 0x4 #define SDHCI_VNDR_CLK_CNTL_INPUT_IO_CLK 0x2 #define SDHCI_VNDR_CLK_CTRL_BASE_CLK_FREQ_SHIFT 8 -#define SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT 16 -#define SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT 24 +#define SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT 16 +#define SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT 24 #define SDHCI_VNDR_CLK_CTRL_SDR50_TUNING 0x20 -#define SDHCI_VNDR_MISC_CTRL 0x120 +#define SDHCI_VNDR_MISC_CTRL 0x120 #define SDHCI_VNDR_MISC_CTRL_ENABLE_SDR104_SUPPORT 0x8 #define SDHCI_VNDR_MISC_CTRL_ENABLE_SDR50_SUPPORT 0x10 #define SDHCI_VNDR_MISC_CTRL_ENABLE_DDR50_SUPPORT 0x200 -#define SDHCI_VNDR_MISC_CTRL_ENABLE_SD_3_0 0x20 +#define SDHCI_VNDR_MISC_CTRL_ENABLE_SD_3_0 0x20 #define SDHCI_VNDR_MISC_CTRL_INFINITE_ERASE_TIMEOUT 0x1 +#define SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT 17 -#define SDMMC_SDMEMCOMPPADCTRL 0x1E0 -#define SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK 0xF +#define SDMMC_SDMEMCOMPPADCTRL 0x1E0 +#define SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK 0xF #define SDMMC_AUTO_CAL_CONFIG 0x1E4 #define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_START 0x80000000 @@ -124,6 +125,8 @@ #define NVQUIRK_EN_FEEDBACK_CLK BIT(17) /* Shadow write xfer mode reg and write it alongwith CMD register */ #define NVQUIRK_SHADOW_XFER_MODE_REG BIT(18) +/* Disable SDMMC3 external loopback */ +#define NVQUIRK_DISABLE_EXTERNAL_LOOPBACK BIT(19) /* Common subset of quirks for Tegra3 and later sdmmc controllers */ #define TEGRA_SDHCI_NVQUIRKS (NVQUIRK_ENABLE_PADPIPE_CLKEN | \ @@ -196,6 +199,10 @@ struct sdhci_tegra_soc_data { u32 nvquirks; const char *parent_clk_list[2]; unsigned int tuning_freq_list[TUNING_FREQ_COUNT]; + u8 t2t_coeffs_count; + u8 tap_hole_coeffs_count; + struct tuning_t2t_coeffs *t2t_coeffs; + struct tap_hole_coeffs *tap_hole_coeffs; }; @@ -205,18 +212,99 @@ enum tegra_regulator_config_ops { CONFIG_REG_SET_VOLT, }; -static unsigned int uhs_max_freq_MHz[] = { - [MMC_TIMING_UHS_SDR50] = 100, - [MMC_TIMING_UHS_SDR104] = 208, - [MMC_TIMING_MMC_HS200] = 200, -}; - enum tegra_tuning_freq { TUNING_LOW_FREQ, TUNING_HIGH_FREQ, TUNING_MAX_FREQ, }; +struct tuning_t2t_coeffs { + const char *dev_id; + int vmax; + int vmin; + unsigned int t2t_vnom_slope; + unsigned int t2t_vnom_int; + unsigned int t2t_vmax_slope; + unsigned int t2t_vmax_int; + unsigned int t2t_vmin_slope; + unsigned int t2t_vmin_int; +}; + +#define SET_TUNING_COEFFS(_device_id, _vmax, _vmin, _t2t_vnom_slope, \ + _t2t_vnom_int, _t2t_vmax_slope, _t2t_vmax_int, _t2t_vmin_slope, \ + _t2t_vmin_int) \ + { \ + .dev_id = _device_id, \ + .vmax = _vmax, \ + .vmin = _vmin, \ + .t2t_vnom_slope = _t2t_vnom_slope, \ + .t2t_vnom_int = _t2t_vnom_int, \ + .t2t_vmax_slope = _t2t_vmax_slope, \ + .t2t_vmax_int = _t2t_vmax_int, \ + .t2t_vmin_slope = _t2t_vmin_slope, \ + .t2t_vmin_int = _t2t_vmin_int, \ + } + +struct tuning_t2t_coeffs t11x_tuning_coeffs[] = { + SET_TUNING_COEFFS("sdhci-tegra.3", 1250, 950, 55, 135434, + 73, 170493, 243, 455948), + SET_TUNING_COEFFS("sdhci-tegra.2", 1250, 950, 50, 129738, + 73, 168898, 241, 453050), + SET_TUNING_COEFFS("sdhci-tegra.0", 1250, 950, 62, 143469, + 82, 180096, 238, 444285), +}; + +struct tap_hole_coeffs { + const char *dev_id; + unsigned int freq_khz; + unsigned int thole_vnom_slope; + unsigned int thole_vnom_int; + unsigned int thole_vmax_slope; + unsigned int thole_vmax_int; + unsigned int thole_vmin_slope; + unsigned int thole_vmin_int; +}; + +#define SET_TAP_HOLE_COEFFS(_device_id, _freq_khz, _thole_vnom_slope, \ + _thole_vnom_int, _thole_vmax_slope, _thole_vmax_int, \ + _thole_vmin_slope, _thole_vmin_int) \ + { \ + .dev_id = _device_id, \ + .freq_khz = _freq_khz, \ + .thole_vnom_slope = _thole_vnom_slope, \ + .thole_vnom_int = _thole_vnom_int, \ + .thole_vmax_slope = _thole_vmax_slope, \ + .thole_vmax_int = _thole_vmax_int, \ + .thole_vmin_slope = _thole_vmin_slope, \ + .thole_vmin_int = _thole_vmin_int, \ + } + +struct tap_hole_coeffs t11x_tap_hole_coeffs[] = { + SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 200000, 765, 102357, 507, + 81144, 131, 36346), + SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 156000, 1042, 142044, 776, + 121659, 152, 48728), + SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 136000, 1215, 167702, 905, + 143825, 207, 63477), + SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 81600, 1925, 284516, 1528, + 253188, 366, 120001), + SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 204000, 472, 53312, 318, + 41756, 84, 15496), + SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 156000, 765, 95512, 526, + 77404, 134, 33032), + SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 136000, 949, 121887, 656, + 99684, 165, 43992), + SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 81600, 1901, 259035, 1334, + 215539, 326, 100986), + SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 204000, 411, 54495, 305, + 46415, 91, 20366), + SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 156000, 715, 97623, 516, + 82375, 145, 38278), + SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 136000, 905, 124579, 648, + 104850, 179, 50204), + SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 81600, 1893, 264746, 1333, + 221722, 354, 109880), +}; struct freq_tuning_constraints { unsigned int vcore_mask; @@ -227,35 +315,56 @@ static struct freq_tuning_constraints tuning_vcore_constraints[3] = { .vcore_mask = BOOT_VCORE_TUN, }, [1] = { - .vcore_mask = BOOT_VCORE_TUN | MIN_OVERRIDE_VCORE_TUN | - NOMINAL_VCORE_TUN, + .vcore_mask = BOOT_VCORE_TUN, }, [2] = { - .vcore_mask = BOOT_VCORE_TUN | NOMINAL_VCORE_TUN, + .vcore_mask = BOOT_VCORE_TUN, }, }; +struct tuning_ui { + int ui; + bool is_valid_ui; +}; + +enum tap_win_edge_attr { + WIN_EDGE_BOUN_START, + WIN_EDGE_BOUN_END, + WIN_EDGE_HOLE, +}; + struct tap_window_data { - bool abandon_partial_win; - bool abandon_full_win; - unsigned int voltage; - u8 partial_win; - u8 full_win_begin; - u8 full_win_end; - u8 vcore_set_status; - u8 found_tuning_window; - u8 tuning_done; + int win_start; + int win_end; + enum tap_win_edge_attr win_start_attr; + enum tap_win_edge_attr win_end_attr; + u8 win_size; }; +struct tuning_values { + int t2t_vmax; + int t2t_vmin; + int ui; + int ui_vmin; + int vmax_thole; + int vmin_thole; +}; struct tegra_tuning_data { unsigned int freq_hz; - unsigned int best_tap_value; - unsigned int nom_best_tap_value; + int best_tap_value; + int nom_best_tap_value; struct freq_tuning_constraints constraints; - struct tap_window_data *tap_data[TUNING_VOLTAGES_COUNT]; + struct tap_hole_coeffs *thole_coeffs; + struct tuning_t2t_coeffs *t2t_coeffs; + struct tuning_values est_values; + struct tuning_values calc_values; + struct tap_window_data *tap_data; + struct tap_window_data *final_tap_data; + u8 num_of_valid_tap_wins; u8 nr_voltages; u8 freq_band; bool tuning_done; + bool is_partial_win_valid; }; #ifdef CONFIG_MMC_FREQ_SCALING @@ -339,7 +448,7 @@ struct sdhci_tegra { /* Tuning packet size */ unsigned int tuning_bsize; /* Num of tuning freqs selected */ - unsigned int tuning_freq_count; + int tuning_freq_count; unsigned int tap_cmd; /* Tuning status */ unsigned int tuning_status; @@ -351,6 +460,7 @@ struct sdhci_tegra { bool is_parent_pllc; struct tegra_freq_gov_data *gov_data; bool power_off_rail; + int speedo; }; static struct clk *pll_c; @@ -796,7 +906,7 @@ static int tegra_sdhci_set_uhs_signaling(struct sdhci_host *host, static void tegra_sdhci_reset_exit(struct sdhci_host *sdhci, u8 mask) { - u16 misc_ctrl; + u32 misc_ctrl; u32 vendor_ctrl; unsigned int best_tap_value; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); @@ -858,7 +968,7 @@ static void tegra_sdhci_reset_exit(struct sdhci_host *sdhci, u8 mask) vendor_ctrl |= SDHCI_VNDR_CLK_CTRL_SDR50_TUNING; sdhci_writel(sdhci, vendor_ctrl, SDHCI_VNDR_CLK_CTRL); - misc_ctrl = sdhci_readw(sdhci, SDHCI_VNDR_MISC_CTRL); + misc_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_MISC_CTRL); if (soc_data->nvquirks & NVQUIRK_ENABLE_SD_3_0) misc_ctrl |= SDHCI_VNDR_MISC_CTRL_ENABLE_SD_3_0; if (soc_data->nvquirks & NVQUIRK_ENABLE_SDR104) { @@ -880,7 +990,12 @@ static void tegra_sdhci_reset_exit(struct sdhci_host *sdhci, u8 mask) else if (soc_data->nvquirks & NVQUIRK_ENABLE_DDR50) misc_ctrl |= SDHCI_VNDR_MISC_CTRL_ENABLE_DDR50_SUPPORT; - sdhci_writew(sdhci, misc_ctrl, SDHCI_VNDR_MISC_CTRL); + /* External loopback is valid for sdmmc3 only */ + if ((soc_data->nvquirks & NVQUIRK_DISABLE_EXTERNAL_LOOPBACK) && + (tegra_host->instance == 2)) + misc_ctrl &= ~(1 << + SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT); + sdhci_writel(sdhci, misc_ctrl, SDHCI_VNDR_MISC_CTRL); /* Mask Auto CMD23 if CMD23 is enabled */ if ((sdhci->mmc->caps & MMC_CAP_CMD23) && @@ -1392,7 +1507,13 @@ static void sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci, u32 vendor_ctrl; /* Max tap delay value is 255 */ - BUG_ON(tap_delay > MAX_TAP_VALUES); + if (tap_delay > MAX_TAP_VALUES) { + dev_err(mmc_dev(sdhci->mmc), + "Valid tap range (0-255). Setting tap value %d\n", + tap_delay); + dump_stack(); + return; + } vendor_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL); vendor_ctrl &= ~(0xFF << SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT); @@ -1448,235 +1569,269 @@ out: return tuning_data; } -static void sdhci_tegra_dump_tuning_data(struct sdhci_host *sdhci) +static void calculate_vmin_values(struct sdhci_host *sdhci, + struct tegra_tuning_data *tuning_data, int vmin, int boot_mv) { - struct tegra_tuning_data *tuning_data; - struct tap_window_data *tap_data; - u8 i; + struct tuning_values *est_values = &tuning_data->est_values; + struct tuning_values *calc_values = &tuning_data->calc_values; + struct tuning_t2t_coeffs *t2t_coeffs = tuning_data->t2t_coeffs; + struct tap_hole_coeffs *thole_coeffs = tuning_data->thole_coeffs; + int vmin_slope, vmin_int, temp_calc_vmin; + int t2t_vmax, t2t_vmin; + int vmax_thole, vmin_thole; - pr_info("********%s: Tuning window data********\n", - mmc_hostname(sdhci->mmc)); - tuning_data = sdhci_tegra_get_tuning_data(sdhci, sdhci->max_clk); - for (i = 0; i < tuning_data->nr_voltages; i++) { - tap_data = tuning_data->tap_data[i]; - pr_info("%dHz: voltage %dmv:\n", sdhci->max_clk, - tap_data->voltage); - pr_info("Partial Win %d, Full win start %d, full win end %d\n", - tap_data->partial_win, - tap_data->full_win_begin, - tap_data->full_win_end); + /* + * If current vmin is equal to vmin or vmax of tuning data, use the + * previously calculated estimated T2T values directly. Note that the + * estimated T2T_vmax is not at Vmax specified in tuning data. It is + * the T2T at the boot or max voltage for the current SKU. Hence, + * boot_mv is used in place of t2t_coeffs->vmax. + */ + if (vmin == t2t_coeffs->vmin) { + t2t_vmin = est_values->t2t_vmin; + } else if (vmin == boot_mv) { + t2t_vmin = est_values->t2t_vmax; + } else { + /* + * For any intermediate voltage between boot voltage and vmin + * of tuning data, calculate the slope and intercept from the + * t2t at boot_mv and vmin and calculate the actual values. + */ + t2t_vmax = 1000 / est_values->t2t_vmax; + t2t_vmin = 1000 / est_values->t2t_vmin; + vmin_slope = ((t2t_vmax - t2t_vmin) * 1000) / + (boot_mv - t2t_coeffs->vmin); + vmin_int = (t2t_vmax * 1000 - (vmin_slope * boot_mv)) / 1000; + t2t_vmin = (vmin_slope * vmin) / 1000 + vmin_int; + t2t_vmin = (1000 / t2t_vmin); + } + + calc_values->t2t_vmin = (t2t_vmin * calc_values->t2t_vmax) / + est_values->t2t_vmax; + + calc_values->ui_vmin = (1000000 / (tuning_data->freq_hz / 1000000)) / + calc_values->t2t_vmin; + + /* Calculate the vmin tap hole at vmin of tuning data */ + temp_calc_vmin = (est_values->t2t_vmin * calc_values->t2t_vmax) / + est_values->t2t_vmax; + vmin_thole = (thole_coeffs->thole_vmin_int - + (thole_coeffs->thole_vmin_slope * temp_calc_vmin)) / + 1000; + vmax_thole = calc_values->vmax_thole; + + if (vmin == t2t_coeffs->vmin) { + calc_values->vmin_thole = vmin_thole; + } else if (vmin == boot_mv) { + calc_values->vmin_thole = vmax_thole; + } else { + /* + * Interpolate the tap hole for any intermediate voltage. + * Calculate the slope and intercept from the available data + * and use them to calculate the actual values. + */ + vmin_slope = ((vmax_thole - vmin_thole) * 1000) / + (boot_mv - t2t_coeffs->vmin); + vmin_int = (vmax_thole * 1000 - (vmin_slope * boot_mv)) / 1000; + calc_values->vmin_thole = (vmin_slope * vmin) / 1000 + vmin_int; + } + + /* Adjust the partial win start for Vmin boundary */ + if (tuning_data->is_partial_win_valid) + tuning_data->final_tap_data[0].win_start = + (tuning_data->final_tap_data[0].win_start * + tuning_data->calc_values.t2t_vmax) / + tuning_data->calc_values.t2t_vmin; + + pr_info("**********Tuning values*********\n"); + pr_info("**estimated values**\n"); + pr_info("T2T_Vmax %d, T2T_Vmin %d, 1'st_hole_Vmax %d, UI_Vmax %d\n", + est_values->t2t_vmax, est_values->t2t_vmin, + est_values->vmax_thole, est_values->ui); + pr_info("**Calculated values**\n"); + pr_info("T2T_Vmax %d, 1'st_hole_Vmax %d, UI_Vmax %d\n", + calc_values->t2t_vmax, calc_values->vmax_thole, + calc_values->ui); + pr_info("T2T_Vmin %d, 1'st_hole_Vmin %d, UI_Vmin %d\n", + calc_values->t2t_vmin, calc_values->vmin_thole, + calc_values->ui_vmin); + pr_info("***********************************\n"); +} + +static int slide_window_start(struct tegra_tuning_data *tuning_data, + int tap_value, enum tap_win_edge_attr edge_attr, int tap_hole) +{ + if (edge_attr == WIN_EDGE_BOUN_START) { + if (tap_value < 0) + tap_value += (1000 / tuning_data->calc_values.t2t_vmin); + else + tap_value += (1000 / tuning_data->calc_values.t2t_vmax); + } else if (edge_attr == WIN_EDGE_HOLE) { + tap_value += ((7 * tap_hole) / 100) + 2; } - pr_info("Best tap value %d, best nom voltage tap value %d\n", - tuning_data->best_tap_value, - tuning_data->nom_best_tap_value); - pr_info("*****************************\n"); + + return tap_value; } -/* - * Calculation of best tap value for low frequencies(82MHz). - * X = Partial win, Y = Full win start, Z = Full win end. - * UI = Z - X. - * Full Window = Z - Y. - * Taps margin = mid-point of 1/2*(curr_freq/max_frequency)*UI - * = (1/2)*(1/2)*(82/200)*UI - * = (0.1025)*UI - * if Partial win<(0.22)*UI - * best tap = Y+(0.1025*UI) - * else - * best tap = (X-(Z-Y))+(0.1025*UI) - * If best tap<0, best tap = 0 - */ -static unsigned int calculate_low_freq_tap_value(struct sdhci_host *sdhci, - struct tap_window_data *tap_data) +static int slide_window_end(struct tegra_tuning_data *tuning_data, + int tap_value, enum tap_win_edge_attr edge_attr, int tap_hole) { - unsigned int curr_clock; - unsigned int max_clock; - int best_tap_value; - unsigned int tuning_ui; - unsigned int sampling_point = 0; - bool select_partial_win; - - if (tap_data->abandon_full_win) { - if (tap_data->abandon_partial_win) { - return 0; - } else { - select_partial_win = true; - goto calculate_best_tap; - } + if (edge_attr == WIN_EDGE_BOUN_END) { + tap_value = (tap_value * tuning_data->calc_values.t2t_vmax) / + tuning_data->calc_values.t2t_vmin; + tap_value -= (1000 / tuning_data->calc_values.t2t_vmin); + } else if (edge_attr == WIN_EDGE_HOLE) { + if (tap_hole > 0) + tap_value = tap_hole; + tap_value -= ((7 * tap_hole) / 100) + 2; } - tuning_ui = tap_data->full_win_end - tap_data->partial_win; - - /* Calculate the sampling point */ - curr_clock = sdhci->max_clk / 1000000; - max_clock = uhs_max_freq_MHz[sdhci->mmc->ios.timing]; - sampling_point = ((tuning_ui * curr_clock) / (max_clock << 2)); - - /* - * Check whether partial window should be used. Use partial window - * if partial window > 0.22(UI). - */ - if ((!tap_data->abandon_partial_win) && - (tap_data->partial_win > ((22 * tuning_ui) / 100))) - select_partial_win = true; - -calculate_best_tap: - if (select_partial_win) - best_tap_value = (tap_data->partial_win - - (tap_data->full_win_end - tap_data->full_win_begin)) + - sampling_point; - else - best_tap_value = tap_data->full_win_begin + - sampling_point; + return tap_value; +} - if (best_tap_value < 0) - best_tap_value = 0; +static int adjust_window_boundaries(struct tegra_tuning_data *tuning_data, + struct tap_window_data *temp_tap_data) +{ + struct tap_window_data *tap_data; + int vmin_tap_hole; + int vmax_tap_hole; + u8 i = 0; - return best_tap_value; + vmax_tap_hole = tuning_data->calc_values.vmax_thole; + vmin_tap_hole = tuning_data->calc_values.vmin_thole; + for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) { + tap_data = &temp_tap_data[i]; + tap_data->win_start = slide_window_start(tuning_data, + tap_data->win_start, tap_data->win_start_attr, + vmax_tap_hole); + /* Update with next hole if first hole is taken care of */ + if (tap_data->win_start_attr == WIN_EDGE_HOLE) + vmax_tap_hole += tuning_data->calc_values.ui; + + tap_data->win_end = slide_window_end(tuning_data, + tap_data->win_end, tap_data->win_end_attr, + vmin_tap_hole); + /* Update with next hole if first hole is taken care of */ + if (tap_data->win_end_attr == WIN_EDGE_HOLE) + vmin_tap_hole += tuning_data->calc_values.ui_vmin; + } + + pr_info("***********final tuning windows**********\n"); + for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) { + tap_data = &temp_tap_data[i]; + pr_info("win[%d]: %d - %d\n", i, tap_data->win_start, + tap_data->win_end); + } + pr_info("********************************\n"); + return 0; } -/* - * Calculation of best tap value for high frequencies(156MHz). - * Tap window data at 1.25V core voltage - * X = Partial win, Y = Full win start, Z = Full win end. - * Full Window = Z-Y. - * UI = Z-X. - * Tap_margin = (0.20375)UI - * - * Tap window data at 1.1V core voltage - * X' = Partial win, Y' = Full win start, Z' = Full win end. - * UI' = Z'-X'. - * Full Window' = Z'-Y'. - * Tap_margin' = (0.20375)UI' - * - * Full_window_tap=[(Z'-0.20375UI')+(Y+0.20375UI)]/2 - * Partial_window_tap=[(X'-0.20375UI')+(X-(Z-Y)+0x20375UI)]/2 - * if(Partial_window_tap < 0), Partial_window_tap=0 - * - * Full_window_quality=[(Z'-0.20375UI')-(Y+0.20375UI)]/2 - * Partial_window_quality=(X'-0.20375UI')-Partial_window_tap - * if(Full_window_quality>Partial_window_quality) choose full window, - * else choose partial window. - * If there is no margin window for both cases, - * best tap=(Y+Z')/2. - */ -static unsigned int calculate_high_freq_tap_value(struct sdhci_host *sdhci, - struct tap_window_data *vmax_tap_data, - struct tap_window_data *vmid_tap_data) -{ - unsigned int curr_clock; - unsigned int max_clock; - unsigned int vmax_tuning_ui; - unsigned int vmax_sampling_point; - unsigned int vmid_tuning_ui; - unsigned int vmid_sampling_point; - unsigned int full_win_tap; - int partial_win_start; - int partial_win_tap; - int full_win_quality; - int partial_win_quality; - int best_tap_value; +static int find_best_tap_value(struct tegra_tuning_data *tuning_data, + struct tap_window_data *temp_tap_data) +{ + struct tap_window_data *tap_data; + u8 i = 0, sel_win = 0; + int pref_win = 0, curr_win_size = 0; + int best_tap_value = 0; + + for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) { + tap_data = &temp_tap_data[i]; + if (!i && tuning_data->is_partial_win_valid) { + pref_win = tap_data->win_end - tap_data->win_start; + if ((tap_data->win_end * 2) < pref_win) + pref_win = tap_data->win_end * 2; + sel_win = 0; + } else { + curr_win_size = tap_data->win_end - tap_data->win_start; + if ((curr_win_size > 0) && (curr_win_size > pref_win)) { + pref_win = curr_win_size; + sel_win = i; + } + } + } - curr_clock = sdhci->max_clk / 1000000; - max_clock = uhs_max_freq_MHz[sdhci->mmc->ios.timing]; + if (pref_win <= 0) { + pr_err("No window opening for this vmin\n"); + return -1; + } - /* - * Calculate the tuning_ui and sampling points for tap windows found - * at all core voltages. - */ - vmax_tuning_ui = vmax_tap_data->full_win_end - - vmax_tap_data->partial_win; - vmax_sampling_point = (vmax_tuning_ui * curr_clock) / (max_clock << 2); - - vmid_tuning_ui = vmid_tap_data->full_win_end - - vmid_tap_data->partial_win; - vmid_sampling_point = (vmid_tuning_ui * curr_clock) / (max_clock << 2); - - full_win_tap = ((vmid_tap_data->full_win_end - vmid_sampling_point) + - (vmax_tap_data->full_win_begin + vmax_sampling_point)); - full_win_tap >>= 1; - full_win_quality = (vmid_tap_data->full_win_end - - vmid_sampling_point) - (vmax_tap_data->full_win_begin + - vmax_sampling_point); - full_win_quality >>= 1; - - partial_win_start = (vmax_tap_data->partial_win - - (vmax_tap_data->full_win_end - - vmax_tap_data->full_win_begin)); - - partial_win_tap = ((vmid_tap_data->partial_win - vmid_sampling_point) + - (partial_win_start + vmax_sampling_point)); - partial_win_tap >>= 1; - if (partial_win_tap < 0) - partial_win_tap = 0; - partial_win_quality = (vmid_tap_data->partial_win - - vmid_sampling_point) - partial_win_tap; - - if ((full_win_quality <= 0) && (partial_win_quality)) { - dev_warn(mmc_dev(sdhci->mmc), - "No margin window for both windows\n"); - best_tap_value = vmax_tap_data->full_win_begin + - vmid_tap_data->full_win_end; - best_tap_value >>= 1; + tap_data = &temp_tap_data[sel_win]; + if (!sel_win && tuning_data->is_partial_win_valid) { + i = sel_win; + best_tap_value = tap_data->win_end - (pref_win / 2); + if (best_tap_value < 0) + best_tap_value = 0; } else { - if (full_win_quality > partial_win_quality) - best_tap_value = full_win_tap; - else - best_tap_value = partial_win_tap; + best_tap_value = tap_data->win_start + + ((tap_data->win_end - tap_data->win_start) * + tuning_data->calc_values.t2t_vmin) / + (tuning_data->calc_values.t2t_vmin + + tuning_data->calc_values.t2t_vmax); } - if (best_tap_value < 0) - best_tap_value = 0; + pr_err("best tap win - (%d-%d), best tap value %d\n", + tap_data->win_start, tap_data->win_end, best_tap_value); return best_tap_value; } -static void sdhci_tegra_calculate_best_tap(struct sdhci_host *sdhci, - u8 freq_band) +static int sdhci_tegra_calculate_best_tap(struct sdhci_host *sdhci, + struct tegra_tuning_data *tuning_data) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); struct sdhci_tegra *tegra_host = pltfm_host->priv; - struct tegra_tuning_data *tuning_data; + struct tap_window_data *temp_tap_data = NULL; + int vmin, curr_vmin, best_tap_value = 0; + int err = 0; - SDHCI_TEGRA_DBG("%s: calculating best tap for freq band %d\n", - mmc_hostname(sdhci->mmc), freq_band); - SDHCI_TEGRA_DBG("%s: Best tap %s nom best tap\n", - mmc_hostname(sdhci->mmc), - (tegra_host->plat->en_nominal_vcore_tuning) ? "different from" : - "same as"); + curr_vmin = tegra_dvfs_predict_millivolts(pltfm_host->clk, + tuning_data->freq_hz); + vmin = curr_vmin; - tuning_data = sdhci_tegra_get_tuning_data(sdhci, sdhci->max_clk); - if (freq_band == TUNING_LOW_FREQ) { - tuning_data->nom_best_tap_value = - calculate_low_freq_tap_value(sdhci, - tuning_data->tap_data[0]); - tuning_data->best_tap_value = tuning_data->nom_best_tap_value; - } else if (freq_band == TUNING_HIGH_FREQ) { - tuning_data->nom_best_tap_value = - calculate_high_freq_tap_value(sdhci, - tuning_data->tap_data[0], tuning_data->tap_data[1]); - if (!tegra_host->plat->en_nominal_vcore_tuning) { - tuning_data->best_tap_value = - tuning_data->nom_best_tap_value; - } else { - tuning_data->best_tap_value = - calculate_high_freq_tap_value(sdhci, - tuning_data->tap_data[1], - tuning_data->tap_data[2]); + do { + SDHCI_TEGRA_DBG("%s: checking for win opening with vmin %d\n", + mmc_hostname(sdhci->mmc), vmin); + if ((best_tap_value < 0) && + (vmin > tegra_host->boot_vcore_mv)) { + dev_err(mmc_dev(sdhci->mmc), + "No best tap for any vcore range\n"); + return -EINVAL; } - } else if (freq_band == TUNING_MAX_FREQ) { - tuning_data->nom_best_tap_value = calculate_high_freq_tap_value( - sdhci, tuning_data->tap_data[0], - tuning_data->tap_data[0]); - if (!tegra_host->plat->en_nominal_vcore_tuning) { - tuning_data->best_tap_value = - tuning_data->nom_best_tap_value; - } else { - tuning_data->best_tap_value = - calculate_high_freq_tap_value(sdhci, - tuning_data->tap_data[1], - tuning_data->tap_data[1]); + + calculate_vmin_values(sdhci, tuning_data, vmin, + tegra_host->boot_vcore_mv); + + if (temp_tap_data == NULL) + temp_tap_data = kzalloc(sizeof(struct tap_window_data) * + tuning_data->num_of_valid_tap_wins, GFP_KERNEL); + if (IS_ERR_OR_NULL(temp_tap_data)) { + dev_err(mmc_dev(sdhci->mmc), + "No memory for final tap value calculation\n"); + return -ENOMEM; } - } + + memcpy(temp_tap_data, tuning_data->final_tap_data, + sizeof(struct tap_window_data) * + tuning_data->num_of_valid_tap_wins); + + adjust_window_boundaries(tuning_data, temp_tap_data); + + best_tap_value = find_best_tap_value(tuning_data, + temp_tap_data); + + if (best_tap_value < 0) + vmin += 50; + } while (best_tap_value < 0); + + tuning_data->best_tap_value = best_tap_value; + tuning_data->nom_best_tap_value = best_tap_value; + + /* Set the new vmin if there is any change. */ + if ((tuning_data->best_tap_value >= 0) && (curr_vmin != vmin)) + err = tegra_dvfs_set_fmax_at_vmin(pltfm_host->clk, + tuning_data->freq_hz, vmin); + + kfree(temp_tap_data); + return err; } static int sdhci_tegra_issue_tuning_cmd(struct sdhci_host *sdhci) @@ -1803,95 +1958,359 @@ static int sdhci_tegra_scan_tap_values(struct sdhci_host *sdhci, return tap_value; } -/* - * While scanning for tap values, first get the partial window followed by the - * full window. Note that, when scanning for full win start, tuning has to be - * run until a passing tap value is found. Hence, failure is expected during - * this process and ignored. - */ -static int sdhci_tegra_get_tap_window_data(struct sdhci_host *sdhci, - struct tap_window_data *tap_data) +static int calculate_actual_tuning_values(int speedo, + struct tegra_tuning_data *tuning_data, int voltage_mv) { - unsigned int tap_value; - unsigned int full_win_percentage = 0; - int err = 0; + struct tuning_t2t_coeffs *t2t_coeffs = tuning_data->t2t_coeffs; + struct tap_hole_coeffs *thole_coeffs = tuning_data->thole_coeffs; + struct tuning_values *calc_values = &tuning_data->calc_values; + struct tap_window_data *tap_data; + int slope, inpt; + int vmax_thole, vmin_thole; + u8 i; + + /* T2T_Vmax = (1000000/freq_MHz)/Calc_UI */ + calc_values->t2t_vmax = (1000000 / (tuning_data->freq_hz / 1000000)) / + calc_values->ui; - if (!tap_data) { - dev_err(mmc_dev(sdhci->mmc), "Invalid tap data\n"); - return -ENODATA; + /* + * Interpolate the tap hole. + * Vmax_1'st_hole = (Calc_T2T_Vmax*(-thole_slope)+thole_tint. + */ + vmax_thole = (thole_coeffs->thole_vmax_int - + (thole_coeffs->thole_vmax_slope * calc_values->t2t_vmax)) / + 1000; + vmin_thole = (thole_coeffs->thole_vmin_int - + (thole_coeffs->thole_vmin_slope * calc_values->t2t_vmax)) / + 1000; + if (voltage_mv == t2t_coeffs->vmin) { + calc_values->vmax_thole = vmin_thole; + } else if (voltage_mv == t2t_coeffs->vmax) { + calc_values->vmax_thole = vmax_thole; + } else { + slope = (vmax_thole - vmin_thole) / + (t2t_coeffs->vmax - t2t_coeffs->vmin); + inpt = ((vmax_thole * 1000) - (slope * 1250)) / 1000; + calc_values->vmax_thole = slope * voltage_mv + inpt; + } + + /* Calculate negative margin if partial win is valid */ + if (tuning_data->is_partial_win_valid) { + /* Find second boundary start and adjust partial win start */ + for (i = 1; i < tuning_data->num_of_valid_tap_wins; i++) { + tap_data = &tuning_data->tap_data[i]; + if (tap_data->win_start_attr == WIN_EDGE_BOUN_START) { + tuning_data->tap_data[0].win_start = + (tap_data->win_start - calc_values->ui); + break; + } + } } - /* Get the partial window data */ - tap_value = 0; - tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, false); - if (tap_value < 0) { - err = -EIO; - goto out; - } else if (!tap_value) { - tap_data->abandon_partial_win = true; - tap_data->partial_win = 0; - } else if (tap_value > MAX_TAP_VALUES) { + return 0; +} + +/* + * All coeffs are filled up in the table after multiplying by 1000. So, all + * calculations should have a divide by 1000 at the end. + */ +static int calculate_estimated_tuning_values(int speedo, + struct tegra_tuning_data *tuning_data, int voltage_mv) +{ + struct tuning_t2t_coeffs *t2t_coeffs = tuning_data->t2t_coeffs; + struct tap_hole_coeffs *thole_coeffs = tuning_data->thole_coeffs; + struct tuning_values *est_values = &tuning_data->est_values; + int slope, inpt; + int vmax_t2t, vmin_t2t; + int vmax_thole, vmin_thole; + + /* Est_T2T_Vmax = (speedo*(-t2t_slope)+t2t_int */ + vmax_t2t = (t2t_coeffs->t2t_vmax_int - (speedo * + t2t_coeffs->t2t_vmax_slope)) / 1000; + vmin_t2t = (t2t_coeffs->t2t_vmin_int - (speedo * + t2t_coeffs->t2t_vmin_slope)) / 1000; + est_values->t2t_vmin = vmin_t2t; + + if (voltage_mv == t2t_coeffs->vmin) { + est_values->t2t_vmax = vmin_t2t; + } else if (voltage_mv == t2t_coeffs->vmax) { + est_values->t2t_vmax = vmax_t2t; + } else { + vmax_t2t = 1000 / vmax_t2t; + vmin_t2t = 1000 / vmin_t2t; /* - * If tap value is more than 0xFF, we have hit the miracle case - * of all tap values passing. Discard full window as passing - * window has covered all taps. + * For any intermediate voltage between 0.95V and 1.25V, + * calculate the slope and intercept from the T2T and tap hole + * values of 0.95V and 1.25V and use them to calculate the + * actual values. 1/T2T is a linear function of voltage. */ - tap_data->partial_win = MAX_TAP_VALUES; - tap_data->abandon_full_win = true; - goto out; + slope = ((vmax_t2t - vmin_t2t) * 1000) / + (t2t_coeffs->vmax - t2t_coeffs->vmin); + inpt = (vmax_t2t * 1000 - (slope * t2t_coeffs->vmax)) / 1000; + est_values->t2t_vmax = (slope * voltage_mv) / 1000 + inpt; + est_values->t2t_vmax = (1000 / est_values->t2t_vmax); + } + + /* Est_UI = (1000000/freq_MHz)/Est_T2T_Vmax */ + est_values->ui = (1000000 / (thole_coeffs->freq_khz / 1000)) / + est_values->t2t_vmax; + + /* + * Est_1'st_hole = (Est_T2T_Vmax*(-thole_slope)) + thole_int. + */ + vmax_thole = (thole_coeffs->thole_vmax_int - + (thole_coeffs->thole_vmax_slope * est_values->t2t_vmax)) / 1000; + vmin_thole = (thole_coeffs->thole_vmin_int - + (thole_coeffs->thole_vmin_slope * est_values->t2t_vmax)) / 1000; + + if (voltage_mv == t2t_coeffs->vmin) { + est_values->vmax_thole = vmin_thole; + } else if (voltage_mv == t2t_coeffs->vmax) { + est_values->vmax_thole = vmax_thole; } else { - tap_data->partial_win = tap_value - 1; - if (tap_value == MAX_TAP_VALUES) { - /* All tap values exhausted. No full window */ - tap_data->abandon_full_win = true; - goto out; + /* + * For any intermediate voltage between 0.95V and 1.25V, + * calculate the slope and intercept from the t2t and tap hole + * values of 0.95V and 1.25V and use them to calculate the + * actual values. Tap hole is a linear function of voltage. + */ + slope = ((vmax_thole - vmin_thole) * 1000) / + (t2t_coeffs->vmax - t2t_coeffs->vmin); + inpt = (vmax_thole * 1000 - (slope * t2t_coeffs->vmax)) / 1000; + est_values->vmax_thole = (slope * voltage_mv) / 1000 + inpt; + } + est_values->vmin_thole = vmin_thole; + + return 0; +} + +/* + * Insert the calculated holes and get the final tap windows + * with the boundaries and holes set. + */ +static int adjust_holes_in_tap_windows(struct sdhci_host *sdhci, + struct tegra_tuning_data *tuning_data) +{ + struct tap_window_data *tap_data; + struct tap_window_data *final_tap_data; + struct tuning_values *calc_values = &tuning_data->calc_values; + int tap_hole; + u8 i = 0, j = 0, num_of_wins; + bool get_next_hole = false; + + tuning_data->final_tap_data = + devm_kzalloc(mmc_dev(sdhci->mmc), + sizeof(struct tap_window_data) * 42, GFP_KERNEL); + if (IS_ERR_OR_NULL(tuning_data->final_tap_data)) { + dev_err(mmc_dev(sdhci->mmc), "No mem for final tap wins\n"); + return -ENOMEM; + } + + num_of_wins = tuning_data->num_of_valid_tap_wins; + tap_hole = calc_values->vmax_thole; + do { + if (get_next_hole) + tap_hole = tap_hole + calc_values->ui; + + tap_data = &tuning_data->tap_data[i]; + final_tap_data = &tuning_data->final_tap_data[j]; + if (tap_hole < tap_data->win_start) { + get_next_hole = true; + continue; + } else if (tap_hole > tap_data->win_end) { + memcpy(final_tap_data, tap_data, + sizeof(struct tap_window_data)); + i++; + j++; + num_of_wins--; + get_next_hole = false; + continue; + } else if ((tap_hole >= tap_data->win_start) && + (tap_hole <= tap_data->win_end)) { + final_tap_data->win_start = tap_data->win_start; + final_tap_data->win_start_attr = + tap_data->win_start_attr; + final_tap_data->win_end = tap_hole - 1; + final_tap_data->win_end_attr = WIN_EDGE_HOLE; + j++; + final_tap_data = + &tuning_data->final_tap_data[j]; + final_tap_data->win_start = tap_hole + 1; + final_tap_data->win_start_attr = WIN_EDGE_HOLE; + final_tap_data->win_end = tap_data->win_end; + final_tap_data->win_end_attr = + tap_data->win_end_attr; + i++; + j++; + num_of_wins--; + get_next_hole = true; } + } while (num_of_wins > 0); + + /* Update the num of valid wins count after tap holes insertion */ + tuning_data->num_of_valid_tap_wins = j; + + pr_info("********tuning windows after inserting holes*****\n"); + pr_info("WIN_ATTR legend: 0-BOUN_ST, 1-BOUN_END, 2-HOLE\n"); + for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) { + final_tap_data = &tuning_data->final_tap_data[i]; + pr_info("win[%d]:%d(%d) - %d(%d)\n", i, + final_tap_data->win_start, + final_tap_data->win_start_attr, + final_tap_data->win_end, final_tap_data->win_end_attr); } + pr_info("***********************************************\n"); + + return 0; +} +/* + * Scan for all tap values and get all passing tap windows. + */ +static int sdhci_tegra_get_tap_window_data(struct sdhci_host *sdhci, + struct tegra_tuning_data *tuning_data) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + struct tap_window_data *tap_data; + struct tuning_ui tuning_ui[10]; + int err = 0; + unsigned int tap_value, calc_ui = 0; + u8 prev_boundary_end = 0, num_of_wins = 0; + u8 num_of_uis = 0, valid_num_uis = 0; + u8 ref_ui; + u8 j = 0; + + /* + * Assume there are a max of 10 windows and allocate tap window + * structures for the same. If there are more windows, the array + * size can be adjusted later using realloc. + */ + tuning_data->tap_data = devm_kzalloc(mmc_dev(sdhci->mmc), + sizeof(struct tap_window_data) * 42, GFP_KERNEL); + if (IS_ERR_OR_NULL(tuning_data->tap_data)) { + dev_err(mmc_dev(sdhci->mmc), "No memory for tap data\n"); + return -ENOMEM; + } + + spin_lock(&sdhci->lock); + tap_value = 0; do { - /* Get the full window start */ - tap_value++; + tap_data = &tuning_data->tap_data[num_of_wins]; + /* Get the window start */ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, true); - if (tap_value < 0) { - err = -EIO; - goto out; - } else if (tap_value > MAX_TAP_VALUES) { - /* All tap values exhausted. No full window */ - tap_data->abandon_full_win = true; - goto out; - } else { - tap_data->full_win_begin = tap_value; - /* - * If full win start is 0xFF, then set that as - * full win end and exit. - */ - if (tap_value == MAX_TAP_VALUES) { - tap_data->full_win_end = tap_value; - goto out; + tap_data->win_start = min_t(u8, tap_value, MAX_TAP_VALUES); + tap_value++; + if (tap_value >= MAX_TAP_VALUES) { + /* If it's first iteration, then all taps failed */ + if (!num_of_wins) { + dev_err(mmc_dev(sdhci->mmc), + "All tap values(0-255) failed\n"); + return -EINVAL; + } else { + /* All windows obtained */ + break; } } - /* Get the full window end */ - tap_value++; + /* Get the window end */ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, false); - tap_data->full_win_end = tap_value - 1; - if (tap_value > MAX_TAP_VALUES) - tap_data->full_win_end = MAX_TAP_VALUES; - full_win_percentage = ((tap_data->full_win_end - - tap_data->full_win_begin) * 100) / - (tap_data->partial_win + 1); - } while (full_win_percentage < 50 && tap_value < MAX_TAP_VALUES); - - if (full_win_percentage < 50) - tap_data->abandon_full_win = true; -out: + tap_data->win_end = min_t(u8, (tap_value - 1), MAX_TAP_VALUES); + tap_data->win_size = tap_data->win_end - tap_data->win_start; + tap_value++; + + /* + * If the size of window is more than 4 taps wide, then it is a + * valid window. If tap value 0 has passed, then a partial + * window exists. Mark all the window edges as boundary edges. + */ + if (tap_data->win_size > 4) { + if (tap_data->win_start == 0) + tuning_data->is_partial_win_valid = true; + tap_data->win_start_attr = WIN_EDGE_BOUN_START; + tap_data->win_end_attr = WIN_EDGE_BOUN_END; + } else { + /* Invalid window as size is less than 5 taps */ + SDHCI_TEGRA_DBG("Invalid tuning win (%d-%d) ignored\n", + tap_data->win_start, tap_data->win_end); + continue; + } + + /* Ignore first and last partial UIs */ + if (tap_data->win_end_attr == WIN_EDGE_BOUN_END) { + if (prev_boundary_end && + (tap_data->win_end != MAX_TAP_VALUES)) { + tuning_ui[num_of_uis].ui = tap_data->win_end - + prev_boundary_end; + num_of_uis++; + } + prev_boundary_end = tap_data->win_end; + } + num_of_wins++; + } while (tap_value < MAX_TAP_VALUES); + spin_unlock(&sdhci->lock); + + tuning_data->num_of_valid_tap_wins = num_of_wins; + valid_num_uis = num_of_uis; + + /* Calculate 0.75*est_UI */ + ref_ui = (75 * tuning_data->est_values.ui) / 100; + /* - * Mark tuning as failed if both partial and full windows are - * abandoned. + * Check for valid UIs and discredit invalid UIs. A UI is considered + * valid if it's greater than (0.75*est_UI). If an invalid UI is found, + * also discredit the smaller of the two adjacent windows. */ - if (tap_data->abandon_partial_win && tap_data->abandon_full_win) - err = -EIO; + for (j = 0; j < num_of_uis; j++) { + if (tuning_ui[j].ui > ref_ui) { + tuning_ui[j].is_valid_ui = true; + } else { + tuning_ui[j].is_valid_ui = false; + valid_num_uis--; + /* Compare the adjacent uis */ + if (j > 0) { + if (tuning_ui[j - 1].ui > tuning_ui[j + 1].ui) { + tuning_ui[j + 1].is_valid_ui = false; + j++; + } else { + tuning_ui[j - 1].is_valid_ui = false; + } + valid_num_uis--; + } + } + } + + /* Calculate the cumulative UI if there are valid UIs left */ + if (valid_num_uis) { + for (j = 0; j < num_of_uis; j++) + if (tuning_ui[j].is_valid_ui) + calc_ui += tuning_ui[j].ui; + } + + if (calc_ui) + tuning_data->calc_values.ui = (calc_ui / valid_num_uis); + else + tuning_data->calc_values.ui = tuning_data->est_values.ui; + + /* Get the calculated tuning values */ + err = calculate_actual_tuning_values(tegra_host->speedo, tuning_data, + tegra_host->boot_vcore_mv); + + /* Print info of all tap windows */ + SDHCI_TEGRA_DBG("**********Auto tuning windows*************\n"); + SDHCI_TEGRA_DBG("WIN_ATTR legend: 0-BOUN_ST, 1-BOUN_END, 2-HOLE\n"); + for (j = 0; j < tuning_data->num_of_valid_tap_wins; j++) { + tap_data = &tuning_data->tap_data[j]; + SDHCI_TEGRA_DBG("win[%d]: %d(%d) - %d(%d)\n", + j, tap_data->win_start, tap_data->win_start_attr, + tap_data->win_end, tap_data->win_end_attr); + } + SDHCI_TEGRA_DBG("***************************************\n"); + + /* Insert calculated holes into the windows */ + err = adjust_holes_in_tap_windows(sdhci, tuning_data); + return err; } @@ -1900,8 +2319,7 @@ static void sdhci_tegra_dump_tuning_constraints(struct sdhci_host *sdhci) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); struct sdhci_tegra *tegra_host = pltfm_host->priv; struct tegra_tuning_data *tuning_data; - struct tap_window_data *tap_data; - u8 i, j; + u8 i; SDHCI_TEGRA_DBG("%s: Num of tuning frequencies%d\n", mmc_hostname(sdhci->mmc), tegra_host->tuning_freq_count); @@ -1910,12 +2328,6 @@ static void sdhci_tegra_dump_tuning_constraints(struct sdhci_host *sdhci) SDHCI_TEGRA_DBG("%s: Tuning freq[%d]: %d, freq band %d\n", mmc_hostname(sdhci->mmc), i, tuning_data->freq_hz, tuning_data->freq_band); - SDHCI_TEGRA_DBG("%s: Supported voltages:", - mmc_hostname(sdhci->mmc)); - for (j = 0; j < tuning_data->nr_voltages; ++j) { - tap_data = tuning_data->tap_data[j]; - SDHCI_TEGRA_DBG("%d,", tap_data->voltage); - } } } @@ -1937,40 +2349,6 @@ static unsigned int get_tuning_voltage(struct sdhci_tegra *tegra_host, u8 *mask) return tegra_host->boot_vcore_mv; } -static int sdhci_tegra_setup_vcore_constraints(struct sdhci_host *sdhci) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); - struct sdhci_tegra *tegra_host = pltfm_host->priv; - struct freq_tuning_constraints *constraints; - struct tegra_tuning_data *tuning_data; - struct tap_window_data *tap_data; - u8 freq_count = tegra_host->tuning_freq_count; - u8 nr_voltages, i, j, vcore_mask; - - for (i = 0; i < freq_count; i++) { - tuning_data = &tegra_host->tuning_data[i]; - constraints = &tuning_data->constraints; - nr_voltages = hweight32(constraints->vcore_mask); - SDHCI_TEGRA_DBG("%s: %dHz: vcore mask %#x, nr voltages %d\n", - mmc_hostname(sdhci->mmc), tuning_data->freq_hz, - constraints->vcore_mask, nr_voltages); - vcore_mask = constraints->vcore_mask; - for (j = 0; j < nr_voltages; j++) { - tap_data = devm_kzalloc( - mmc_dev(sdhci->mmc), - sizeof(struct tap_window_data), - GFP_KERNEL); - if (!tap_data) - return -ENOMEM; - tap_data->voltage = get_tuning_voltage(tegra_host, - &vcore_mask); - tuning_data->tap_data[j] = tap_data; - } - tuning_data->nr_voltages = nr_voltages; - } - return 0; -} - static u8 sdhci_tegra_get_freq_point(struct sdhci_host *sdhci) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); @@ -1990,19 +2368,95 @@ static u8 sdhci_tegra_get_freq_point(struct sdhci_host *sdhci) } /* + * The frequency tuning algorithm tries to calculate the tap-to-tap delay + * UI and estimate holes using equations and predetermined coefficients from + * the characterization data. The algorithm will not work without this data. + */ +static int find_tuning_coeffs_data(struct sdhci_host *sdhci) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + struct tegra_tuning_data *tuning_data; + struct tuning_t2t_coeffs *t2t_coeffs; + struct tap_hole_coeffs *thole_coeffs; + const char *dev_id; + unsigned int freq_khz; + u8 i, j; + bool coeffs_set = false; + + dev_id = dev_name(mmc_dev(sdhci->mmc)); + /* Find the coeffs data for all supported frequencies */ + for (i = 0; i < tegra_host->tuning_freq_count; i++) { + tuning_data = &tegra_host->tuning_data[i]; + + /* Skip if T2T coeffs are already found */ + if (tuning_data->t2t_coeffs == NULL) { + t2t_coeffs = soc_data->t2t_coeffs; + for (j = 0; j < soc_data->t2t_coeffs_count; j++) { + if (!strcmp(dev_id, t2t_coeffs->dev_id)) { + tuning_data->t2t_coeffs = t2t_coeffs; + coeffs_set = true; + dev_info(mmc_dev(sdhci->mmc), + "Found T2T coeffs data\n"); + break; + } + t2t_coeffs++; + } + if (!coeffs_set) { + dev_err(mmc_dev(sdhci->mmc), + "T2T coeffs data missing\n"); + tuning_data->t2t_coeffs = NULL; + return -ENODATA; + } + } + + coeffs_set = false; + /* Skip if tap hole coeffs are already found */ + if (tuning_data->thole_coeffs == NULL) { + thole_coeffs = soc_data->tap_hole_coeffs; + freq_khz = tuning_data->freq_hz / 1000; + for (j = 0; j < soc_data->tap_hole_coeffs_count; j++) { + if (!strcmp(dev_id, thole_coeffs->dev_id) && + (freq_khz == thole_coeffs->freq_khz)) { + tuning_data->thole_coeffs = + thole_coeffs; + coeffs_set = true; + dev_info(mmc_dev(sdhci->mmc), + "%dMHz tap hole coeffs found\n", + (freq_khz / 1000)); + break; + } + thole_coeffs++; + } + + if (!coeffs_set) { + dev_err(mmc_dev(sdhci->mmc), + "%dMHz Tap hole coeffs data missing\n", + (freq_khz / 1000)); + tuning_data->thole_coeffs = NULL; + return -ENODATA; + } + } + } + + return 0; +} + +/* * Determines the numbers of frequencies required and then fills up the tuning * constraints for each of the frequencies. The data of lower frequency is - * filled first and then the higher frequency data. Currently fills constraints - * for number of frequencies 1 and 2. + * filled first and then the higher frequency data. Max supported frequencies + * is currently two. */ -static u8 sdhci_tegra_setup_freq_constraints(struct sdhci_host *sdhci, +static int setup_freq_constraints(struct sdhci_host *sdhci, unsigned int *freq_list) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); struct sdhci_tegra *tegra_host = pltfm_host->priv; struct tegra_tuning_data *tuning_data; - int i; - u8 freq_count, freq_band; + int i, freq_count; + u8 freq_band; if ((sdhci->mmc->ios.timing != MMC_TIMING_UHS_SDR50) && (sdhci->mmc->caps2 & MMC_CAP2_FREQ_SCALING)) @@ -2017,21 +2471,19 @@ static u8 sdhci_tegra_setup_freq_constraints(struct sdhci_host *sdhci, tuning_data = &tegra_host->tuning_data[0]; tuning_data->freq_hz = sdhci->max_clk; tuning_data->freq_band = freq_band; - tuning_data->constraints = - tuning_vcore_constraints[freq_band]; - if (!tegra_host->plat->en_nominal_vcore_tuning) - tuning_data->constraints.vcore_mask &= - ~NOMINAL_VCORE_TUN; + tuning_data->constraints.vcore_mask = + tuning_vcore_constraints[freq_band].vcore_mask; + tuning_data->nr_voltages = + hweight32(tuning_data->constraints.vcore_mask); break; case 2: tuning_data = &tegra_host->tuning_data[1]; tuning_data->freq_hz = sdhci->max_clk; tuning_data->freq_band = freq_band; - tuning_data->constraints = - tuning_vcore_constraints[freq_band]; - if (!tegra_host->plat->en_nominal_vcore_tuning) - tuning_data->constraints.vcore_mask &= - ~NOMINAL_VCORE_TUN; + tuning_data->constraints.vcore_mask = + tuning_vcore_constraints[freq_band].vcore_mask; + tuning_data->nr_voltages = + hweight32(tuning_data->constraints.vcore_mask); tuning_data = &tegra_host->tuning_data[0]; for (i = (freq_band - 1); i >= 0; i--) { @@ -2039,26 +2491,26 @@ static u8 sdhci_tegra_setup_freq_constraints(struct sdhci_host *sdhci, continue; tuning_data->freq_hz = freq_list[i]; tuning_data->freq_band = i; - tuning_data->constraints = - tuning_vcore_constraints[i]; - if (!tegra_host->plat->en_nominal_vcore_tuning) - tuning_data->constraints.vcore_mask &= - ~NOMINAL_VCORE_TUN; + tuning_data->nr_voltages = 1; + tuning_data->constraints.vcore_mask = + tuning_vcore_constraints[i].vcore_mask; + tuning_data->nr_voltages = + hweight32(tuning_data->constraints.vcore_mask); } break; default: dev_err(mmc_dev(sdhci->mmc), "Unsupported freq count\n"); + freq_count = -1; } + return freq_count; } /* - * Get the supported frequencies, core voltage levels for each frequency and - * other tuning related constraints. - * The supported frequencies should be determined from the list of frequencies - * in the soc data and also consider the platform clock limits as well as any - * DFS related restrictions. - * Check if tuning at nominal core voltage is required. + * Get the supported frequencies and other tuning related constraints for each + * frequency. The supported frequencies should be determined from the list of + * frequencies in the soc data and also consider the platform clock limits as + * well as any DFS related restrictions. */ static int sdhci_tegra_get_tuning_constraints(struct sdhci_host *sdhci) { @@ -2067,17 +2519,22 @@ static int sdhci_tegra_get_tuning_constraints(struct sdhci_host *sdhci) unsigned int *freq_list; int err = 0; + /* A valid freq count means freq constraints are already set up */ + if (!tegra_host->tuning_freq_count) { + freq_list = tegra_host->soc_data->tuning_freq_list; + tegra_host->tuning_freq_count = + setup_freq_constraints(sdhci, freq_list); + if (tegra_host->tuning_freq_count < 0) { + dev_err(mmc_dev(sdhci->mmc), + "Invalid tuning freq count\n"); + return -EINVAL; + } + } - /* Check if the constraints are already filled up */ - if (tegra_host->tuning_freq_count) + err = find_tuning_coeffs_data(sdhci); + if (err) return err; - freq_list = tegra_host->soc_data->tuning_freq_list; - tegra_host->tuning_freq_count = - sdhci_tegra_setup_freq_constraints(sdhci, freq_list); - - err = sdhci_tegra_setup_vcore_constraints(sdhci); - sdhci_tegra_dump_tuning_constraints(sdhci); return err; @@ -2138,63 +2595,59 @@ static int sdhci_tegra_set_tuning_voltage(struct sdhci_host *sdhci, return err; } -static u8 get_curr_voltage_tuning_status(struct tap_window_data *tap_data) -{ - if (!tap_data->vcore_set_status) - maintain_boot_voltage = true; - - if (!tap_data->vcore_set_status || !tap_data->found_tuning_window) - return 1; - - return 0; -} - -static u8 sdhci_tegra_run_tuning(struct sdhci_host *sdhci, - struct tegra_tuning_data *tuning_data, bool force_retuning) +static int sdhci_tegra_run_tuning(struct sdhci_host *sdhci, + struct tegra_tuning_data *tuning_data) { - struct tap_window_data *tap_data; - int err; - u8 i, retuning_req = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + int err = 0; + int voltage = 0; + u8 i, vcore_mask = 0; + vcore_mask = tuning_data->constraints.vcore_mask; for (i = 0; i < tuning_data->nr_voltages; i++) { - tap_data = tuning_data->tap_data[i]; - /* Skip if tuning is already completed successfully */ - if (tap_data->vcore_set_status && - tap_data->found_tuning_window && !force_retuning) - continue; - err = sdhci_tegra_set_tuning_voltage(sdhci, - tap_data->voltage); - tap_data->vcore_set_status = !err; + voltage = get_tuning_voltage(tegra_host, &vcore_mask); + err = sdhci_tegra_set_tuning_voltage(sdhci, voltage); + if (err) { + dev_err(mmc_dev(sdhci->mmc), + "Unable to set override voltage.\n"); + return err; + } + /* Get the tuning window info */ - spin_lock(&sdhci->lock); - err = sdhci_tegra_get_tap_window_data(sdhci, tap_data); - spin_unlock(&sdhci->lock); - SDHCI_TEGRA_DBG("%s: Tap data[%d] obtained\n", - mmc_hostname(sdhci->mmc), i); - tap_data->found_tuning_window = !err; - if (err) + SDHCI_TEGRA_DBG("Getting tuning windows...\n"); + err = sdhci_tegra_get_tap_window_data(sdhci, tuning_data); + if (err) { dev_err(mmc_dev(sdhci->mmc), - "Invalid tap win. Retuning req\n"); - retuning_req |= get_curr_voltage_tuning_status(tap_data); - SDHCI_TEGRA_DBG("%s: Retuning req %s\n", - mmc_hostname(sdhci->mmc), retuning_req ? "set" : - "not set"); + "Failed to get tap win %d\n", err); + return err; + } + SDHCI_TEGRA_DBG("%s: %d tuning window data obtained\n", + mmc_hostname(sdhci->mmc), tuning_data->freq_hz); } - return retuning_req; + return err; } -static int sdhci_tegra_verify_best_tap(struct sdhci_host *sdhci, - u8 freq_band) +static int sdhci_tegra_verify_best_tap(struct sdhci_host *sdhci) { struct tegra_tuning_data *tuning_data; - unsigned int best_tap_value = 0; - int err; + int err = 0; tuning_data = sdhci_tegra_get_tuning_data(sdhci, sdhci->max_clk); - best_tap_value = tuning_data->best_tap_value; + if ((tuning_data->best_tap_value < 0) || + (tuning_data->best_tap_value > MAX_TAP_VALUES)) { + dev_err(mmc_dev(sdhci->mmc), + "Trying to verify invalid best tap value\n"); + return -EINVAL; + } else { + dev_err(mmc_dev(sdhci->mmc), + "%s: tuning freq %dhz, best tap %d\n", + __func__, tuning_data->freq_hz, + tuning_data->best_tap_value); + } /* Set the best tap value */ - sdhci_tegra_set_tap_delay(sdhci, best_tap_value); + sdhci_tegra_set_tap_delay(sdhci, tuning_data->best_tap_value); /* Run tuning after setting the best tap value */ err = sdhci_tegra_issue_tuning_cmd(sdhci); @@ -2210,12 +2663,10 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); struct sdhci_tegra *tegra_host = pltfm_host->priv; struct tegra_tuning_data *tuning_data; - unsigned int freq_band; int err; u16 ctrl_2; u32 ier; u8 i, set_retuning = 0; - bool is_retuning_req = false; bool force_retuning = false; /* Tuning is valid only in SDR104 and SDR50 modes */ @@ -2235,9 +2686,6 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) pr_err("%s: Starting freq tuning\n", mmc_hostname(sdhci->mmc)); mutex_lock(&tuning_mutex); - if (sdhci->flags & SDHCI_NEEDS_RETUNING) - is_retuning_req = true; - sdhci->flags &= ~SDHCI_NEEDS_RETUNING; /* Set the tuning command to be used */ tegra_host->tuning_opcode = opcode; @@ -2258,15 +2706,14 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) * previous best tap value verification failed, force retuning. */ if (tegra_host->tuning_status == TUNING_STATUS_DONE) { - freq_band = sdhci_tegra_get_freq_point(sdhci); - dev_info(mmc_dev(sdhci->mmc), - "Tuning already done. Setting tuned tap value %d\n", - tegra_host->tuning_data[freq_band].best_tap_value); - err = sdhci_tegra_verify_best_tap(sdhci, freq_band); - if (err) + err = sdhci_tegra_verify_best_tap(sdhci); + if (err) { + dev_err(mmc_dev(sdhci->mmc), + "Prev best tap failed. Re-running tuning\n"); force_retuning = true; - else + } else { goto out; + } } if (tegra_host->force_retune == true) { @@ -2277,7 +2724,8 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) tegra_host->tuning_status = 0; err = sdhci_tegra_get_tuning_constraints(sdhci); if (err) { - dev_err(mmc_dev(sdhci->mmc), "Failed to get tuning constraints\n"); + dev_err(mmc_dev(sdhci->mmc), + "Failed to get tuning constraints\n"); goto out; } @@ -2290,15 +2738,24 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) mmc_hostname(sdhci->mmc), tuning_data->freq_hz); tegra_sdhci_set_clock(sdhci, tuning_data->freq_hz); - set_retuning = sdhci_tegra_run_tuning(sdhci, tuning_data, force_retuning); + SDHCI_TEGRA_DBG("%s: Calculating estimated tuning values\n", + mmc_hostname(sdhci->mmc)); + err = calculate_estimated_tuning_values(tegra_host->speedo, + tuning_data, tegra_host->boot_vcore_mv); + if (err) + goto out; - sdhci_tegra_calculate_best_tap(sdhci, tuning_data->freq_band); + SDHCI_TEGRA_DBG("Running tuning...\n"); + err = sdhci_tegra_run_tuning(sdhci, tuning_data); + if (err) + goto out; - /* Dump the tuning data */ - sdhci_tegra_dump_tuning_data(sdhci); + SDHCI_TEGRA_DBG("calculating best tap value\n"); + err = sdhci_tegra_calculate_best_tap(sdhci, tuning_data); + if (err) + goto out; - err = sdhci_tegra_verify_best_tap(sdhci, - tuning_data->freq_band); + err = sdhci_tegra_verify_best_tap(sdhci); if (!err && !set_retuning) { tuning_data->tuning_done = true; tegra_host->tuning_status |= TUNING_STATUS_DONE; @@ -2306,29 +2763,9 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode) tegra_host->tuning_status |= TUNING_STATUS_RETUNE; } } - if (tegra_host->tuning_status & TUNING_STATUS_RETUNE) - mod_timer(&sdhci->tuning_timer, jiffies + 10 * HZ); out: - if (maintain_boot_voltage) { - ++boot_volt_req_refcount; - maintain_boot_voltage = false; - SDHCI_TEGRA_DBG("%s: Need fixed core volt %d, refcount %d\n", - mmc_hostname(sdhci->mmc), tegra_host->boot_vcore_mv, - boot_volt_req_refcount); - - } else { - if (boot_volt_req_refcount && is_retuning_req) - --boot_volt_req_refcount; - SDHCI_TEGRA_DBG("%s: Relax core volt constraint. refcount %d\n", - mmc_hostname(sdhci->mmc), boot_volt_req_refcount); - } - - if (boot_volt_req_refcount) - sdhci_tegra_set_tuning_voltage(sdhci, - tegra_host->boot_vcore_mv); - else - sdhci_tegra_set_tuning_voltage(sdhci, 0); - + /* Release any override core voltages set */ + sdhci_tegra_set_tuning_voltage(sdhci, 0); /* Enable interrupts. Enable full range for core voltage */ sdhci_writel(sdhci, ier, SDHCI_INT_ENABLE); @@ -2431,11 +2868,15 @@ static void tegra_sdhci_post_resume(struct sdhci_host *sdhci) * For tegra specific tuning, core voltage has to be fixed at different * voltages to get the tap values. Fixing the core voltage during tuning for one * device might affect transfers of other SDMMC devices. Check if tuning mutex - * is locked before starting a data transfer. + * is locked before starting a data transfer. The new tuning procedure might + * take at max 1.5s for completion for a single run. Taking DFS into count, + * setting the max timeout for tuning mutex check a 3 secs. Since tuning is + * run only during boot or the first time device is inserted, there wouldn't + * be any delays in cmd/xfer execution once devices enumeration is done. */ static void tegra_sdhci_get_bus(struct sdhci_host *sdhci) { - unsigned int timeout = 100; + unsigned int timeout = 300; while (mutex_is_locked(&tuning_mutex)) { msleep(10); @@ -2769,9 +3210,14 @@ static struct sdhci_tegra_soc_data soc_data_tegra11 = { NVQUIRK_SET_TRIM_DELAY | NVQUIRK_ENABLE_DDR50 | NVQUIRK_ENABLE_HS200 | - NVQUIRK_INFINITE_ERASE_TIMEOUT, + NVQUIRK_INFINITE_ERASE_TIMEOUT | + NVQUIRK_DISABLE_EXTERNAL_LOOPBACK, .parent_clk_list = {"pll_p", "pll_c"}, .tuning_freq_list = {81600000, 156000000, 200000000}, + .t2t_coeffs = t11x_tuning_coeffs, + .t2t_coeffs_count = 3, + .tap_hole_coeffs = t11x_tap_hole_coeffs, + .tap_hole_coeffs_count = 12, }; static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { @@ -3043,6 +3489,8 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) tegra_host->sd_detect_in_suspend = plat->sd_detect_in_suspend; tegra_host->instance = pdev->id; tegra_host->tap_cmd = TAP_CMD_TRIM_DEFAULT_VOLTAGE; + tegra_host->speedo = plat->cpu_speedo; + dev_info(mmc_dev(host->mmc), "Speedo value %d\n", tegra_host->speedo); host->mmc->pm_caps |= plat->pm_caps; host->mmc->pm_flags |= plat->pm_flags; |