summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/sdhci-tegra.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/sdhci-tegra.c')
-rw-r--r--drivers/mmc/host/sdhci-tegra.c1732
1 files changed, 1213 insertions, 519 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index fdda83afe1f6..abcc0e5f5020 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -146,6 +146,7 @@
#define NVQUIRK_HIGH_FREQ_TAP_PROCEDURE BIT(22)
/* Disable SDMMC3 external loopback */
#define NVQUIRK_DISABLE_EXTERNAL_LOOPBACK BIT(23)
+#define NVQUIRK_TMP_VAR_1_5_TAP_MARGIN BIT(24)
/* Common subset of quirks for Tegra3 and later sdmmc controllers */
#define TEGRA_SDHCI_NVQUIRKS (NVQUIRK_ENABLE_PADPIPE_CLKEN | \
@@ -196,7 +197,7 @@
#define TUNING_VOLTAGES_COUNT 3
#define TUNING_RETRIES 1
#define DFS_FREQ_COUNT 2
-
+#define NEG_MAR_CHK_WIN_COUNT 2
/* Tuning core voltage requirements */
#define NOMINAL_VCORE_TUN BIT(0)
#define BOOT_VCORE_TUN BIT(1)
@@ -221,7 +222,10 @@ struct sdhci_tegra_soc_data {
u32 nvquirks;
const char *parent_clk_list[2];
unsigned int tuning_freq_list[TUNING_FREQ_COUNT];
- unsigned int tuning_min_volt_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;
};
@@ -231,18 +235,137 @@ 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 tuning_t2t_coeffs t12x_tuning_coeffs[] = {
+ SET_TUNING_COEFFS("sdhci-tegra.3", 1150, 950, 27, 118295,
+ 27, 118295, 48, 188148),
+ SET_TUNING_COEFFS("sdhci-tegra.2", 1150, 950, 29, 124427,
+ 29, 124427, 54, 203707),
+ SET_TUNING_COEFFS("sdhci-tegra.0", 1150, 950, 25, 115933,
+ 25, 115933, 47, 187224),
+};
+
+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 tap_hole_coeffs t12x_tap_hole_coeffs[] = {
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 200000, 1037, 106934, 1037,
+ 106934, 558, 74315),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 136000, 1703, 186307, 1703,
+ 186307, 890, 130617),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 100000, 2452, 275601, 2452,
+ 275601, 1264, 193957),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.3", 81600, 3090, 351666, 3090,
+ 351666, 1583, 247913),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 204000, 468, 36031, 468,
+ 36031, 253, 21264),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 200000, 468, 36031, 468,
+ 36031, 253, 21264),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 136000, 1146, 117841, 1146,
+ 117841, 589, 78993),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 100000, 1879, 206195, 1879,
+ 206195, 953, 141341),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.2", 81600, 2504, 281460, 2504,
+ 281460, 1262, 194452),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 204000, 874, 85243, 874,
+ 85243, 449, 57321),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 136000, 1554, 167210, 1554,
+ 167210, 793, 115672),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 100000, 2290, 255734, 2290,
+ 255734, 1164, 178691),
+ SET_TAP_HOLE_COEFFS("sdhci-tegra.0", 81600, 2916, 331143, 2916,
+ 331143, 1480, 232373),
+};
struct freq_tuning_constraints {
unsigned int vcore_mask;
@@ -253,35 +376,57 @@ 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;
+ u8 hole_pos;
};
+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
@@ -372,7 +517,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;
@@ -382,6 +527,7 @@ struct sdhci_tegra {
/* Freq tuning information for each sampling clock freq */
struct tegra_tuning_data tuning_data[DFS_FREQ_COUNT];
struct tegra_freq_gov_data *gov_data;
+ u32 speedo;
};
static struct clk *pll_c;
@@ -802,9 +948,11 @@ static int tegra_sdhci_set_uhs_signaling(struct sdhci_host *host,
ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
- /* Select Bus Speed Mode for host */
- /* For HS200 we need to set UHS_MODE_SEL to SDR104.
+ /* Select Bus Speed Mode for host
+ * For HS200 we need to set UHS_MODE_SEL to SDR104.
* It works as SDR 104 in SD 4-bit mode and HS200 in eMMC 8-bit mode.
+ * SDR50 mode timing seems to have issues. Programming SDR104
+ * mode for SDR50 mode for reliable transfers over interface.
*/
ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
switch (uhs) {
@@ -815,7 +963,7 @@ static int tegra_sdhci_set_uhs_signaling(struct sdhci_host *host,
ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
break;
case MMC_TIMING_UHS_SDR50:
- ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
break;
case MMC_TIMING_UHS_SDR104:
case MMC_TIMING_MMC_HS200:
@@ -1016,8 +1164,17 @@ static void tegra_sdhci_reset_exit(struct sdhci_host *host, u8 mask)
/* 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);
+ (tegra_host->instance == 2)) {
+ if ((tegra_host->tuning_status == TUNING_STATUS_DONE)
+ && (host->mmc->pm_flags &
+ MMC_PM_KEEP_POWER)) {
+ misc_ctrl &= ~(1 <<
+ SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT);
+ } else {
+ misc_ctrl |= (1 <<
+ SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT);
+ }
+ }
sdhci_writel(host, misc_ctrl, SDHCI_VNDR_MISC_CTRL);
if (soc_data->nvquirks & NVQUIRK_DISABLE_AUTO_CMD23)
@@ -1144,13 +1301,12 @@ static void tegra_sdhci_clock_set_parent(struct sdhci_host *host,
if ((pll_c_freq > desired_rate) && (pll_p_freq > desired_rate)) {
if (pll_p_freq <= pll_c_freq) {
desired_rate = pll_p_freq;
- parent_clk = pll_p;
+ pll_c_freq = 0;
} else {
desired_rate = pll_c_freq;
- parent_clk = pll_c;
+ pll_p_freq = 0;
}
rc = clk_set_rate(pltfm_host->clk, desired_rate);
- goto set_clk_parent;
}
if (pll_c_freq > pll_p_freq) {
@@ -1166,7 +1322,6 @@ static void tegra_sdhci_clock_set_parent(struct sdhci_host *host,
} else
return;
-set_clk_parent:
rc = clk_set_parent(pltfm_host->clk, parent_clk);
if (rc)
pr_err("%s: failed to set pll parent clock %d\n",
@@ -1512,7 +1667,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);
@@ -1567,266 +1728,296 @@ 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);
}
- 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");
+
+ 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");
}
-/*
- * 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_start(struct sdhci_host *sdhci,
+ 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 = false;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
- tuning_ui = tap_data->full_win_end - tap_data->partial_win;
+ 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) {
+ if (soc_data->nvquirks & NVQUIRK_TMP_VAR_1_5_TAP_MARGIN)
+ tap_value += ((7 * tap_hole) / 100) + 2;
+ else
+ tap_value += ((7 * tap_hole) / 100) +
+ (((2 * (450 / tuning_data->calc_values.t2t_vmax))
+ + 1) / 2);
+ }
- /* 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));
+ if (tap_value > MAX_TAP_VALUES)
+ tap_value = MAX_TAP_VALUES;
- /*
- * 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;
-
- 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;
+}
+
+static int slide_window_end(struct sdhci_host *sdhci,
+ struct tegra_tuning_data *tuning_data,
+ int tap_value, enum tap_win_edge_attr edge_attr, int tap_hole)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
- if (best_tap_value < 0)
- best_tap_value = 0;
+ 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;
+ if (soc_data->nvquirks & NVQUIRK_TMP_VAR_1_5_TAP_MARGIN)
+ tap_value -= ((7 * tap_hole) / 100) + 2;
+ else
+ tap_value -= ((7 * tap_hole) / 100) +
+ (((2 * (450 / tuning_data->calc_values.t2t_vmin))
+ + 1) / 2);
+ }
- return best_tap_value;
+ return tap_value;
}
-/*
- * 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 adjust_window_boundaries(struct sdhci_host *sdhci,
+ 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;
- curr_clock = sdhci->max_clk / 1000000;
- max_clock = uhs_max_freq_MHz[sdhci->mmc->ios.timing];
+ for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) {
+ tap_data = &temp_tap_data[i];
+ /* 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.vmax_thole +
+ (tap_data->hole_pos - 1) *
+ tuning_data->calc_values.ui;
+ tap_data->win_start = slide_window_start(sdhci, 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_end_attr == WIN_EDGE_HOLE)
+ vmin_tap_hole = tuning_data->calc_values.vmin_thole +
+ (tap_data->hole_pos - 1) *
+ tuning_data->calc_values.ui_vmin;
+ tap_data->win_end = slide_window_end(sdhci, tuning_data,
+ tap_data->win_end, tap_data->win_end_attr,
+ vmin_tap_hole);
+ }
- /*
- * 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 <= 0)) {
- 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;
+ 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;
+}
+
+static int find_best_tap_value(struct tegra_tuning_data *tuning_data,
+ struct tap_window_data *temp_tap_data, int vmin)
+{
+ 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;
+ }
+ }
+ }
+
+ if (pref_win <= 0) {
+ pr_err("No window opening for %d vmin\n", vmin);
+ return -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_info("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;
- unsigned int vdd_core;
- int delta_vdd;
- unsigned int voltage = 0;
+ 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);
+ 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;
+ }
- if (tuning_data->tap_data[0] && tuning_data->tap_data[1] &&
- (tegra_host->soc_data->nvquirks &
- NVQUIRK_HIGH_FREQ_TAP_PROCEDURE)) {
- voltage = tuning_data->tap_data[0]->voltage -
- tuning_data->tap_data[1]->voltage;
-
- if (voltage > 0 && (freq_band == TUNING_LOW_FREQ ||
- freq_band == TUNING_HIGH_FREQ)) {
- vdd_core =
- tegra_host->soc_data->tuning_min_volt_list[freq_band];
- delta_vdd = tuning_data->tap_data[1]->voltage -
- vdd_core;
- tuning_data->tap_data[1]->partial_win =
- ((tuning_data->tap_data[0]->partial_win -
- tuning_data->tap_data[1]->partial_win) *
- delta_vdd) / voltage +
- tuning_data->tap_data[1]->partial_win;
- tuning_data->tap_data[1]->full_win_begin =
- ((tuning_data->tap_data[0]->full_win_begin -
- tuning_data->tap_data[1]->full_win_begin) *
- delta_vdd) / voltage +
- tuning_data->tap_data[1]->full_win_begin;
- tuning_data->tap_data[1]->full_win_end =
- -((tuning_data->tap_data[0]->full_win_end -
- tuning_data->tap_data[1]->full_win_end) *
- (delta_vdd / voltage)) +
- tuning_data->tap_data[1]->full_win_end;
- }
- }
-
- if (freq_band == TUNING_LOW_FREQ) {
- if (tegra_host->soc_data->nvquirks &
- NVQUIRK_HIGH_FREQ_TAP_PROCEDURE)
- tuning_data->nom_best_tap_value =
- calculate_high_freq_tap_value(sdhci,
- tuning_data->tap_data[0],
- tuning_data->tap_data[1]);
- else
- 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]);
- }
- } 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(sdhci, tuning_data, temp_tap_data);
+
+ best_tap_value = find_best_tap_value(tuning_data,
+ temp_tap_data, vmin);
+
+ 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)
@@ -1946,89 +2137,545 @@ 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;
+ int slope, inpt;
+ int vmax_thole, vmin_thole;
+
+ /* 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;
}
- /* Get the partial window data */
- tap_value = 0;
- tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, false);
- 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, size = 0;
+ u8 i = 0, j = 0, num_of_wins, hole_pos = 0;
+
+ 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;
+ hole_pos++;
+ do {
+ tap_data = &tuning_data->tap_data[i];
+ final_tap_data = &tuning_data->final_tap_data[j];
+ if (tap_hole < tap_data->win_start) {
+ tap_hole += calc_values->ui;
+ hole_pos++;
+ 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--;
+ continue;
+ } else if ((tap_hole >= tap_data->win_start) &&
+ (tap_hole <= tap_data->win_end)) {
+ size = tap_data->win_end - tap_data->win_start;
+ do {
+ final_tap_data =
+ &tuning_data->final_tap_data[j];
+ if (tap_hole == tap_data->win_start) {
+ final_tap_data->win_start =
+ tap_hole + 1;
+ final_tap_data->win_start_attr =
+ WIN_EDGE_HOLE;
+ final_tap_data->hole_pos = hole_pos;
+ tap_hole += calc_values->ui;
+ hole_pos++;
+ } else {
+ final_tap_data->win_start =
+ tap_data->win_start;
+ final_tap_data->win_start_attr =
+ WIN_EDGE_BOUN_START;
+ }
+ if (tap_hole <= tap_data->win_end) {
+ final_tap_data->win_end = tap_hole - 1;
+ final_tap_data->win_end_attr =
+ WIN_EDGE_HOLE;
+ final_tap_data->hole_pos = hole_pos;
+ tap_data->win_start = tap_hole;
+ } else if (tap_hole > tap_data->win_end) {
+ final_tap_data->win_end =
+ tap_data->win_end;
+ final_tap_data->win_end_attr =
+ WIN_EDGE_BOUN_END;
+ tap_data->win_start =
+ tap_data->win_end;
+ }
+ size = tap_data->win_end - tap_data->win_start;
+ j++;
+ } while (size > 0);
+ i++;
+ num_of_wins--;
}
+ } 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;
+}
+
+/*
+ * Insert the boundaries from negative margin calculations into the windows
+ * from auto tuning.
+ */
+static int insert_boundaries_in_tap_windows(struct sdhci_host *sdhci,
+ struct tegra_tuning_data *tuning_data, u8 boun_end)
+{
+ struct tap_window_data *tap_data;
+ struct tap_window_data *new_tap_data;
+ struct tap_window_data *temp_tap_data;
+ struct tuning_values *calc_values = &tuning_data->calc_values;
+ int curr_boun;
+ u8 i = 0, j = 0, num_of_wins;
+ bool get_next_boun = false;
+
+ temp_tap_data = devm_kzalloc(mmc_dev(sdhci->mmc),
+ sizeof(struct tap_window_data) * 42, GFP_KERNEL);
+ if (IS_ERR_OR_NULL(temp_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;
+ curr_boun = boun_end % calc_values->ui;
do {
- /* Get the full window start */
- tap_value++;
- tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, true);
- 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 (get_next_boun) {
+ curr_boun += calc_values->ui;
/*
- * If full win start is 0xFF, then set that as
- * full win end and exit.
+ * If the boun_end exceeds the intial boundary end,
+ * just copy remaining windows and return.
*/
- if (tap_value == MAX_TAP_VALUES) {
- tap_data->full_win_end = tap_value;
- goto out;
- }
+ if (curr_boun >= boun_end)
+ curr_boun += MAX_TAP_VALUES;
+ }
+
+ tap_data = &tuning_data->tap_data[i];
+ new_tap_data = &temp_tap_data[j];
+ if (curr_boun <= tap_data->win_start) {
+ get_next_boun = true;
+ continue;
+ } else if (curr_boun >= tap_data->win_end) {
+ memcpy(new_tap_data, tap_data,
+ sizeof(struct tap_window_data));
+ i++;
+ j++;
+ num_of_wins--;
+ get_next_boun = false;
+ continue;
+ } else if ((curr_boun >= tap_data->win_start) &&
+ (curr_boun <= tap_data->win_end)) {
+ new_tap_data->win_start = tap_data->win_start;
+ new_tap_data->win_start_attr =
+ tap_data->win_start_attr;
+ new_tap_data->win_end = curr_boun - 1;
+ new_tap_data->win_end_attr =
+ tap_data->win_end_attr;
+ j++;
+ new_tap_data = &temp_tap_data[j];
+ new_tap_data->win_start = curr_boun;
+ new_tap_data->win_end = curr_boun;
+ new_tap_data->win_start_attr =
+ WIN_EDGE_BOUN_START;
+ new_tap_data->win_end_attr =
+ WIN_EDGE_BOUN_END;
+ j++;
+ new_tap_data = &temp_tap_data[j];
+ new_tap_data->win_start = curr_boun + 1;
+ new_tap_data->win_start_attr = WIN_EDGE_BOUN_START;
+ new_tap_data->win_end = tap_data->win_end;
+ new_tap_data->win_end_attr =
+ tap_data->win_end_attr;
+ i++;
+ j++;
+ num_of_wins--;
+ get_next_boun = 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;
+
+ memcpy(tuning_data->tap_data, temp_tap_data,
+ j * sizeof(struct tap_window_data));
+ SDHCI_TEGRA_DBG("***tuning windows after inserting boundaries***\n");
+ SDHCI_TEGRA_DBG("WIN_ATTR legend: 0-BOUN_ST, 1-BOUN_END, 2-HOLE\n");
+ for (i = 0; i < tuning_data->num_of_valid_tap_wins; i++) {
+ new_tap_data = &tuning_data->tap_data[i];
+ SDHCI_TEGRA_DBG("win[%d]:%d(%d) - %d(%d)\n", i,
+ new_tap_data->win_start,
+ new_tap_data->win_start_attr,
+ new_tap_data->win_end, new_tap_data->win_end_attr);
+ }
+ SDHCI_TEGRA_DBG("***********************************************\n");
+
+ return 0;
+}
- /* Get the full window end */
+/*
+ * 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, partial_win_start = 0, temp_margin = 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, first_valid_full_win = 0;
+ u8 boun_end = 0, next_boun_end = 0;
+ u8 j = 0;
+ bool valid_ui_found = false;
+
+ /*
+ * 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 {
+ tap_data = &tuning_data->tap_data[num_of_wins];
+ /* Get the window start */
+ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, true);
+ 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");
+ spin_unlock(&sdhci->lock);
+ return -EINVAL;
+ } else {
+ /* All windows obtained */
+ break;
+ }
+ }
+
+ /* 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) {
+ tuning_ui[num_of_uis].ui = tap_data->win_end -
+ prev_boundary_end;
+ tuning_ui[num_of_uis].is_valid_ui = true;
+ 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;
+
+ /* Print info of all tap windows */
+ pr_info("**********Auto tuning windows*************\n");
+ pr_info("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];
+ pr_info("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);
+ }
+ pr_info("***************************************\n");
+
+ /* Mark the first last partial UIs as invalid */
+ tuning_ui[0].is_valid_ui = false;
+ tuning_ui[num_of_uis - 1].is_valid_ui = false;
+ valid_num_uis -= 2;
+
+ /* Discredit all uis at either end with size less than 30% of est ui */
+ ref_ui = (30 * tuning_data->est_values.ui) / 100;
+ for (j = 0; j < num_of_uis; j++) {
+ if (tuning_ui[j].is_valid_ui) {
+ tuning_ui[j].is_valid_ui = false;
+ valid_num_uis--;
+ }
+ if (tuning_ui[j].ui > ref_ui)
+ break;
+ }
+
+ for (j = num_of_uis; j > 0; j--) {
+ if (tuning_ui[j - 1].ui < ref_ui) {
+ if (tuning_ui[j - 1].is_valid_ui) {
+ tuning_ui[j - 1].is_valid_ui = false;
+ valid_num_uis--;
+ }
+ } else
+ break;
+ }
+
+ /* 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 = 1; j < (num_of_uis - 1); j++) {
+ if (tuning_ui[j].ui > ref_ui && tuning_ui[j].is_valid_ui) {
+ tuning_ui[j].is_valid_ui = true;
+ } else {
+ if (tuning_ui[j].is_valid_ui) {
+ tuning_ui[j].is_valid_ui = false;
+ valid_num_uis--;
+ }
+ if (!tuning_ui[j + 1].is_valid_ui ||
+ !tuning_ui[j - 1].is_valid_ui) {
+ if (tuning_ui[j - 1].is_valid_ui) {
+ tuning_ui[j - 1].is_valid_ui = false;
+ valid_num_uis--;
+ } else if (tuning_ui[j + 1].is_valid_ui) {
+ tuning_ui[j + 1].is_valid_ui = false;
+ valid_num_uis--;
+ }
+ } else {
+
+ if (tuning_ui[j - 1].ui > tuning_ui[j + 1].ui)
+ tuning_ui[j + 1].is_valid_ui = false;
+ 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 (!first_valid_full_win)
+ first_valid_full_win = j;
+ }
+ }
+
+ if (calc_ui) {
+ tuning_data->calc_values.ui = (calc_ui / valid_num_uis);
+ valid_ui_found = true;
+ } else {
+ tuning_data->calc_values.ui = tuning_data->est_values.ui;
+ valid_ui_found = false;
+ }
+
+ SDHCI_TEGRA_DBG("****Tuning UIs***********\n");
+ for (j = 0; j < num_of_uis; j++)
+ SDHCI_TEGRA_DBG("Tuning UI[%d] : %d, Is valid[%d]\n",
+ j, tuning_ui[j].ui, tuning_ui[j].is_valid_ui);
+ SDHCI_TEGRA_DBG("*************************\n");
+
+ /* Get the calculated tuning values */
+ err = calculate_actual_tuning_values(tegra_host->speedo, tuning_data,
+ tegra_host->boot_vcore_mv);
+
+ /*
+ * Calculate negative margin if partial win is valid. There are two
+ * cases here.
+ * Case 1: If Avg_UI is found, then keep subtracting avg_ui from start
+ * of first valid full window until a value <=0 is obtained.
+ * Case 2: If Avg_UI is not found, subtract avg_ui from all boundary
+ * starts until a value <=0 is found.
+ */
+ if (tuning_data->is_partial_win_valid && (num_of_wins > 1)) {
+ if (valid_ui_found) {
+ partial_win_start =
+ tuning_data->tap_data[first_valid_full_win].win_start;
+ boun_end = partial_win_start;
+ partial_win_start %= tuning_data->calc_values.ui;
+ partial_win_start -= tuning_data->calc_values.ui;
+ } else {
+ for (j = 0; j < NEG_MAR_CHK_WIN_COUNT; j++) {
+ temp_margin =
+ tuning_data->tap_data[j + 1].win_start;
+ if (!boun_end)
+ boun_end = temp_margin;
+ else if (!next_boun_end)
+ next_boun_end = temp_margin;
+ temp_margin %= tuning_data->calc_values.ui;
+ temp_margin -= tuning_data->calc_values.ui;
+ if (!partial_win_start ||
+ (temp_margin > partial_win_start))
+ partial_win_start = temp_margin;
+ }
+ }
+ if (partial_win_start <= 0)
+ tuning_data->tap_data[0].win_start = partial_win_start;
+ }
+
+ if (boun_end)
+ insert_boundaries_in_tap_windows(sdhci, tuning_data, boun_end);
+ if (next_boun_end)
+ insert_boundaries_in_tap_windows(sdhci, tuning_data, next_boun_end);
+
+ /* Insert calculated holes into the windows */
+ err = adjust_holes_in_tap_windows(sdhci, tuning_data);
+
return err;
}
@@ -2037,8 +2684,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);
@@ -2047,12 +2693,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);
- }
}
}
@@ -2074,40 +2714,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);
@@ -2127,19 +2733,96 @@ 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,
+ bool force_retuning)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ const 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 || force_retuning) {
+ 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 || force_retuning) {
+ 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,
const 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))
@@ -2154,27 +2837,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->soc_data->nvquirks &
- NVQUIRK_HIGH_FREQ_TAP_PROCEDURE) {
- if (tuning_data->freq_band == TUNING_LOW_FREQ)
- tuning_data->constraints.vcore_mask |=
- MIN_OVERRIDE_VCORE_TUN;
- }
- 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--) {
@@ -2182,51 +2857,51 @@ 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->soc_data->nvquirks &
- NVQUIRK_HIGH_FREQ_TAP_PROCEDURE) {
- if (i == TUNING_LOW_FREQ)
- tuning_data->constraints.vcore_mask |=
- MIN_OVERRIDE_VCORE_TUN;
- }
- 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)
+static int sdhci_tegra_get_tuning_constraints(struct sdhci_host *sdhci,
+ bool force_retuning)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
struct sdhci_tegra *tegra_host = pltfm_host->priv;
const unsigned int *freq_list;
int err = 0;
+ /* A valid freq count means freq constraints are already set up */
+ if (!tegra_host->tuning_freq_count || force_retuning) {
+ 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, force_retuning);
+ 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;
@@ -2288,63 +2963,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_info(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);
@@ -2360,13 +3031,14 @@ 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;
+ const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
int err;
u16 ctrl_2;
+ u32 misc_ctrl;
u32 ier;
u8 i, set_retuning = 0;
- bool is_retuning_req = false;
bool force_retuning = false;
+ bool enable_lb_clk;
/* Tuning is valid only in SDR104 and SDR50 modes */
ctrl_2 = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2);
@@ -2384,10 +3056,16 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode)
return -EINVAL;
SDHCI_TEGRA_DBG("%s: Starting freq tuning\n", mmc_hostname(sdhci->mmc));
+ enable_lb_clk = (soc_data->nvquirks &
+ NVQUIRK_DISABLE_EXTERNAL_LOOPBACK) &&
+ (tegra_host->instance == 2);
+ if (enable_lb_clk) {
+ misc_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_MISC_CTRL);
+ misc_ctrl &= ~(1 <<
+ SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT);
+ sdhci_writel(sdhci, misc_ctrl, SDHCI_VNDR_MISC_CTRL);
+ }
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;
@@ -2408,15 +3086,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) {
@@ -2425,9 +3102,10 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode)
}
tegra_host->tuning_status = 0;
- err = sdhci_tegra_get_tuning_constraints(sdhci);
+ err = sdhci_tegra_get_tuning_constraints(sdhci, force_retuning);
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;
}
@@ -2440,16 +3118,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;
@@ -2457,29 +3143,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);
@@ -2487,6 +3153,21 @@ out:
mutex_unlock(&tuning_mutex);
SDHCI_TEGRA_DBG("%s: Freq tuning done\n", mmc_hostname(sdhci->mmc));
+ if (enable_lb_clk) {
+ misc_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_MISC_CTRL);
+ if (err) {
+ /* Tuning is failed and card will try to enumerate in
+ * Legacy High Speed mode. So, Enable External Loopback
+ * for SDMMC3.
+ */
+ misc_ctrl |= (1 <<
+ SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT);
+ } else {
+ misc_ctrl &= ~(1 <<
+ SDHCI_VNDR_MISC_CTRL_EN_EXT_LOOPBACK_SHIFT);
+ }
+ sdhci_writel(sdhci, misc_ctrl, SDHCI_VNDR_MISC_CTRL);
+ }
return err;
}
@@ -2580,22 +3261,26 @@ 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. To handle this, lock
- * tuning_mutex to prevent any transfers during tuning execution. This is not
- * required once tuning is done.
+ * device might affect transfers of other SDMMC devices. Check if tuning mutex
+ * 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)
{
- int timeout = 100;
- while (mutex_is_locked(&tuning_mutex)) {
- if (!timeout) {
- dev_err(mmc_dev(sdhci->mmc),
- "Tuning timer not released\n");
- return;
- }
- msleep(10);
- timeout--;
- };
+ unsigned int timeout = 300;
+
+ while (mutex_is_locked(&tuning_mutex)) {
+ msleep(10);
+ --timeout;
+ if (!timeout) {
+ dev_err(mmc_dev(sdhci->mmc),
+ "Tuning mutex locked for long time\n");
+ return;
+ }
+ };
}
/*
@@ -2953,9 +3638,14 @@ static struct sdhci_tegra_soc_data soc_data_tegra11 = {
NVQUIRK_ENABLE_DDR50 |
NVQUIRK_ENABLE_HS200 |
NVQUIRK_INFINITE_ERASE_TIMEOUT |
+ NVQUIRK_DISABLE_EXTERNAL_LOOPBACK |
NVQUIRK_DISABLE_SDMMC4_CALIB,
.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 struct sdhci_pltfm_data sdhci_tegra12_pdata = {
@@ -2978,7 +3668,10 @@ static struct sdhci_tegra_soc_data soc_data_tegra12 = {
NVQUIRK_DISABLE_EXTERNAL_LOOPBACK,
.parent_clk_list = {"pll_p", "pll_c"},
.tuning_freq_list = {81600000, 136000000, 200000000},
- .tuning_min_volt_list = {950, 1000, 0},
+ .t2t_coeffs = t12x_tuning_coeffs,
+ .t2t_coeffs_count = 3,
+ .tap_hole_coeffs = t12x_tap_hole_coeffs,
+ .tap_hole_coeffs_count = 13,
};
static const struct of_device_id sdhci_tegra_dt_match[] = {
@@ -3308,7 +4001,8 @@ static int sdhci_tegra_probe(struct platform_device *pdev)
tegra_host->ddr_clk_limit = plat->ddr_clk_limit;
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;