summaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2013-01-22 17:48:45 +0530
committerMandar Padmawar <mpadmawar@nvidia.com>2013-01-23 02:53:21 -0800
commit39cf979cd0a55876b8ceeaa75abbc19dd6527051 (patch)
treea5e73b7533efe60d4153da4c7e672def31b30ffd /drivers/mmc
parentbedff5775d6dab5870a777a1b5a1abfc9e23b033 (diff)
mmc: tegra: Tuning and tap selection for low freqs
Adding frequency tuning solution for frequencies below 82MHz in SDR104 and HS200 mode. Bug 1189241 Bug 1181574 Change-Id: Iec55f36de850060c71a13b5dd42d815e573c1f1b Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com> Reviewed-on: http://git-master/r/192114 Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Venkat Moganty <vmoganty@nvidia.com> Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/host/sdhci-tegra.c411
1 files changed, 320 insertions, 91 deletions
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
index ce0d72eb9d91..ee891c689fe9 100644
--- a/drivers/mmc/host/sdhci-tegra.c
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2010 Google, Inc.
*
- * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -87,7 +87,13 @@
#define MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_8 128
#define MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_4 64
-#define MAX_TAP_VALUES 256
+#define MAX_TAP_VALUES 255
+
+static unsigned int uhs_max_freq_MHz[] = {
+ [MMC_TIMING_UHS_SDR50] = 100,
+ [MMC_TIMING_UHS_SDR104] = 208,
+ [MMC_TIMING_MMC_HS200] = 200,
+};
#if defined(CONFIG_ARCH_TEGRA_3x_SOC)
static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock);
@@ -145,12 +151,29 @@ struct sdhci_tegra_sd_stats {
unsigned int cmd_to_count;
};
+struct tap_window_data {
+ unsigned int partial_win;
+ unsigned int full_win_begin;
+ unsigned int full_win_end;
+ unsigned int tuning_ui;
+ unsigned int sampling_point;
+ bool abandon_partial_win;
+ bool abandon_full_win;
+};
+
+struct tegra_tuning_data {
+ unsigned int best_tap_value;
+ bool select_partial_win;
+ struct tap_window_data *tap_data;
+};
+
struct sdhci_tegra {
const struct tegra_sdhci_platform_data *plat;
const struct sdhci_tegra_soc_data *soc_data;
bool clk_enabled;
struct regulator *vdd_io_reg;
struct regulator *vdd_slot_reg;
+ struct regulator *vcore_reg;
/* Pointer to the chip specific HW ops */
struct tegra_sdhci_hw_ops *hw_ops;
/* Host controller instance */
@@ -169,6 +192,18 @@ struct sdhci_tegra {
struct clk *emc_clk;
unsigned int emc_max_clk;
struct sdhci_tegra_sd_stats *sd_stat_head;
+ unsigned int nominal_vcore_uV;
+ /* Tuning related structures and variables */
+ /* Tuning opcode to be used */
+ unsigned int tuning_opcode;
+ /* Tuning packet size */
+ unsigned int tuning_bsize;
+ /* Tuning status */
+ unsigned int tuning_status;
+#define TUNING_STATUS_DONE 1
+#define TUNING_STATUS_RETUNE 2
+ /* Freq tuning information for each sampling clock freq */
+ struct tegra_tuning_data tuning_data;
};
static int show_error_stats_dump(struct seq_file *s, void *data)
@@ -502,7 +537,12 @@ static irqreturn_t carddetect_irq(int irq, void *data)
if (tegra_host->vdd_slot_reg)
regulator_disable(tegra_host->vdd_slot_reg);
tegra_host->is_rail_enabled = 0;
- }
+ }
+ /*
+ * Set retune request as tuning should be done next time
+ * a card is inserted.
+ */
+ tegra_host->tuning_status = TUNING_STATUS_RETUNE;
}
tasklet_schedule(&sdhost->card_tasklet);
@@ -914,8 +954,103 @@ static void sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci,
sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
}
-static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci, u32 opcode, u32 block_size)
+static int sdhci_tegra_sd_error_stats(struct sdhci_host *host, u32 int_status)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
+ struct sdhci_tegra_sd_stats *head;
+
+ if (tegra_host->sd_stat_head == NULL) {
+ tegra_host->sd_stat_head = devm_kzalloc(&pdev->dev, sizeof(
+ struct sdhci_tegra_sd_stats),
+ GFP_KERNEL);
+ if (tegra_host->sd_stat_head == NULL)
+ return -ENOMEM;
+ }
+ head = tegra_host->sd_stat_head;
+ if (int_status & SDHCI_INT_DATA_CRC)
+ head->data_crc_count++;
+ if (int_status & SDHCI_INT_CRC)
+ head->cmd_crc_count++;
+ if (int_status & SDHCI_INT_TIMEOUT)
+ head->cmd_to_count++;
+ if (int_status & SDHCI_INT_DATA_TIMEOUT)
+ head->data_to_count++;
+ return 0;
+}
+
+/*
+ * 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 void calculate_low_freq_tap_value(struct sdhci_host *sdhci)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ unsigned int curr_clock;
+ unsigned int max_clock;
+ int best_tap_value;
+ struct tap_window_data *tap_data;
+ struct tegra_tuning_data *tuning_data;
+
+ tuning_data = &tegra_host->tuning_data;
+ tap_data = tuning_data->tap_data;
+
+ if (tap_data->abandon_full_win) {
+ if (tap_data->abandon_partial_win) {
+ tuning_data->best_tap_value = 0;
+ return;
+ } else {
+ tuning_data->select_partial_win = true;
+ goto calculate_best_tap;
+ }
+ }
+
+ tap_data->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];
+ tap_data->sampling_point = ((tap_data->tuning_ui * curr_clock) /
+ max_clock);
+ tap_data->sampling_point >>= 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 * tap_data->tuning_ui) / 100)))
+ tuning_data->select_partial_win = true;
+
+calculate_best_tap:
+ if (tuning_data->select_partial_win) {
+ best_tap_value = (tap_data->partial_win -
+ (tap_data->full_win_end - tap_data->full_win_begin)) +
+ tap_data->sampling_point;
+ tuning_data->best_tap_value = (best_tap_value < 0) ? 0 :
+ best_tap_value;
+ } else {
+ tuning_data->best_tap_value = tap_data->full_win_begin +
+ tap_data->sampling_point;
+ }
+}
+
+static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci)
{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
int err = 0;
u8 ctrl;
u32 mask;
@@ -951,7 +1086,8 @@ static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci, u32 opcode
* block for MMC_BUS_WIDTH_8 and 64 bytes for MMC_BUS_WIDTH_4
* to the Host Controller. So we set the block size to 64 here.
*/
- sdhci_writew(sdhci, SDHCI_MAKE_BLKSZ(7, block_size), SDHCI_BLOCK_SIZE);
+ sdhci_writew(sdhci, SDHCI_MAKE_BLKSZ(7, tegra_host->tuning_bsize),
+ SDHCI_BLOCK_SIZE);
sdhci_writeb(sdhci, 0xE, SDHCI_TIMEOUT_CONTROL);
@@ -963,7 +1099,7 @@ static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci, u32 opcode
flags = SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_DATA;
/* Issue the command */
sdhci_writew(sdhci, SDHCI_MAKE_CMD(
- opcode, flags), SDHCI_COMMAND);
+ tegra_host->tuning_opcode, flags), SDHCI_COMMAND);
timeout = 5;
do {
@@ -1000,43 +1136,111 @@ out:
return err;
}
-static int sdhci_tegra_sd_error_stats(struct sdhci_host *host, u32 int_status)
+static int sdhci_tegra_scan_tap_values(struct sdhci_host *sdhci,
+ unsigned int starting_tap, bool expect_failure)
{
- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
- struct sdhci_tegra *tegra_host = pltfm_host->priv;
- struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
- struct sdhci_tegra_sd_stats *head;
+ unsigned int tap_value = starting_tap;
+ int err;
- if (tegra_host->sd_stat_head == NULL) {
- tegra_host->sd_stat_head = devm_kzalloc(&pdev->dev, sizeof(
- struct sdhci_tegra_sd_stats),
- GFP_KERNEL);
- if (tegra_host->sd_stat_head == NULL)
- return -ENOMEM;
+ do {
+ /* Set the tap delay */
+ sdhci_tegra_set_tap_delay(sdhci, tap_value);
+
+ /* Run frequency tuning */
+ err = sdhci_tegra_run_frequency_tuning(sdhci);
+ if ((expect_failure && !err) ||
+ (!expect_failure && err))
+ break;
+ tap_value++;
+ } while (tap_value <= MAX_TAP_VALUES);
+
+ 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 tegra_tuning_data *tuning_data)
+{
+ struct tap_window_data *tap_data;
+ unsigned int tap_value;
+ int err = 0;
+
+ if (!tuning_data || !tuning_data->tap_data) {
+ dev_err(mmc_dev(sdhci->mmc), "Invalid tuning or tap data\n");
+ return -ENODATA;
+ }
+ tap_data = tuning_data->tap_data;
+
+ /* 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) {
+ /*
+ * 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.
+ */
+ tap_data->partial_win = MAX_TAP_VALUES;
+ tap_data->abandon_full_win = true;
+ goto out;
+ } 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;
}
- head = tegra_host->sd_stat_head;
- if (int_status & SDHCI_INT_DATA_CRC)
- head->data_crc_count = head->data_crc_count + 1;
- if (int_status & SDHCI_INT_CRC)
- head->cmd_crc_count = head->cmd_crc_count + 1;
- if (int_status & SDHCI_INT_TIMEOUT)
- head->cmd_to_count = head->cmd_to_count + 1;
- if (int_status & SDHCI_INT_DATA_TIMEOUT)
- head->data_to_count = head->data_to_count + 1;
- return 0;
+ }
+
+ /* Get the full window start */
+ 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 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;
+ }
+ }
+
+ /* Get the full 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;
+
+out:
+ /*
+ * Mark tuning as failed if both partial and full windows are
+ * abandoned.
+ */
+ if (tap_data->abandon_partial_win && tap_data->abandon_full_win)
+ err = -EIO;
+ return err;
}
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;
int err;
- u32 block_size;
u16 ctrl_2;
- u8 *tap_delay_status;
- unsigned int i = 0;
- unsigned int temp_low_pass_tap = 0;
- unsigned int temp_pass_window = 0;
- unsigned int best_low_pass_tap = 0;
- unsigned int best_pass_window = 0;
u32 ier;
/* Tuning is valid only in SDR104 and SDR50 modes */
@@ -1046,20 +1250,17 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode)
(sdhci->flags & SDHCI_SDR50_NEEDS_TUNING))))
return 0;
- tap_delay_status = kzalloc(MAX_TAP_VALUES, GFP_KERNEL);
- if (tap_delay_status == NULL) {
- dev_err(mmc_dev(sdhci->mmc), "failed to allocate memory"
- "for storing tap_delay_status\n");
- return -ENOMEM;
- }
-
/* Tuning should be done only for MMC_BUS_WIDTH_8 and MMC_BUS_WIDTH_4 */
if (sdhci->mmc->ios.bus_width == MMC_BUS_WIDTH_8)
- block_size = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_8;
+ tegra_host->tuning_bsize = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_8;
else if (sdhci->mmc->ios.bus_width == MMC_BUS_WIDTH_4)
- block_size = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_4;
+ tegra_host->tuning_bsize = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_4;
else
return -EINVAL;
+
+ /* Set the tuning command to be used */
+ tegra_host->tuning_opcode = opcode;
+
/*
* Disable all interrupts signalling.Enable interrupt status
* detection for buffer read ready and data crc. We use
@@ -1071,64 +1272,89 @@ static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode)
SDHCI_INT_DATA_CRC, SDHCI_INT_ENABLE);
/*
- * Set each tap delay value and run frequency tuning. After each
- * run, update the tap delay status as working or not working.
+ * If tuning is already done and retune request is not set, then skip
+ * best tap value calculation and use the old best tap value.
*/
- do {
- /* Set the tap delay */
- sdhci_tegra_set_tap_delay(sdhci, i);
-
- /* Run frequency tuning */
- err = sdhci_tegra_run_frequency_tuning(sdhci, opcode, block_size);
-
- /* Update whether the tap delay worked or not */
- tap_delay_status[i] = (err) ? 0: 1;
- i++;
- } while (i < 0xFF);
-
- /* Find the best possible tap range */
- for (i = 0; i < 0xFF; i++) {
- temp_pass_window = 0;
-
- /* Find the first passing tap in the current window */
- if (tap_delay_status[i]) {
- temp_low_pass_tap = i;
-
- /* Find the pass window */
- do {
- temp_pass_window++;
- i++;
- if (i > 0xFF)
- break;
- } while (tap_delay_status[i]);
+ if (tegra_host->tuning_status == TUNING_STATUS_DONE)
+ goto set_best_tap;
- if ((temp_pass_window > best_pass_window) &&
- (temp_pass_window > 1)) {
- best_low_pass_tap = temp_low_pass_tap;
- best_pass_window = temp_pass_window;
+ /*
+ * Run tuning and get the passing tap window info for all frequencies
+ * and core voltages required to calculate the final tap value. The
+ * standard driver calls this platform specific tuning callback after
+ * holding a lock. The spinlock needs to be released when calling
+ * non-atomic context functions like regulator calls etc.
+ */
+ spin_unlock(&sdhci->lock);
+ if (tegra_host->nominal_vcore_uV) {
+ tegra_host->vcore_reg = regulator_get(mmc_dev(sdhci->mmc),
+ "vdd_core");
+ if (IS_ERR_OR_NULL(tegra_host->vcore_reg)) {
+ dev_info(mmc_dev(sdhci->mmc),
+ "No vdd_core_sdmmc %ld. Tuning might fail.\n",
+ PTR_ERR(tegra_host->vcore_reg));
+ tegra_host->vcore_reg = NULL;
+ } else {
+ err = regulator_set_voltage(tegra_host->vcore_reg,
+ tegra_host->nominal_vcore_uV,
+ tegra_host->nominal_vcore_uV);
+ if (err) {
+ dev_err(mmc_dev(sdhci->mmc),
+ "Setting nominal core voltage failed\n");
}
}
}
+ tuning_data = &tegra_host->tuning_data;
+ if (!tuning_data->tap_data) {
+ tuning_data->tap_data = devm_kzalloc(mmc_dev(sdhci->mmc),
+ sizeof(struct tap_window_data), GFP_KERNEL);
+ if (!tuning_data->tap_data) {
+ err = -ENOMEM;
+ dev_err(mmc_dev(sdhci->mmc),
+ "Insufficient memory for tap window info\n");
+ spin_lock(&sdhci->lock);
+ goto out;
+ }
+ }
+ spin_lock(&sdhci->lock);
- pr_debug("%s: best pass tap window: start %d, end %d\n",
- mmc_hostname(sdhci->mmc), best_low_pass_tap,
- (best_low_pass_tap + best_pass_window));
+ /* Get the tuning window info */
+ err = sdhci_tegra_get_tap_window_data(sdhci, tuning_data);
+ if (err) {
+ dev_err(mmc_dev(sdhci->mmc), "Failed to tuning window info\n");
+ goto out;
+ }
+
+ /* Calculate best tap for low freq */
+ calculate_low_freq_tap_value(sdhci);
- /* Set the best tap */
+set_best_tap:
sdhci_tegra_set_tap_delay(sdhci,
- (best_low_pass_tap + ((best_pass_window * 3) / 4)));
+ tegra_host->tuning_data.best_tap_value);
- /* Run frequency tuning */
- err = sdhci_tegra_run_frequency_tuning(sdhci, opcode, block_size);
+ /*
+ * Run tuning with the best tap value. If tuning fails, set the status
+ * for retuning next time enumeration is done.
+ */
+ err = sdhci_tegra_run_frequency_tuning(sdhci);
+ if (err)
+ tegra_host->tuning_status = TUNING_STATUS_RETUNE;
+ else
+ tegra_host->tuning_status = TUNING_STATUS_DONE;
- /* Enable the normal interrupts signalling */
+out:
+ /* Enable the full range for core voltage if vcore_reg exists */
+ if (tegra_host->vcore_reg) {
+ spin_unlock(&sdhci->lock);
+ regulator_put(tegra_host->vcore_reg);
+ tegra_host->vcore_reg = NULL;
+ spin_lock(&sdhci->lock);
+ }
+
+ /* Enable interrupts. Enable full range for core voltage */
sdhci_writel(sdhci, ier, SDHCI_INT_ENABLE);
sdhci_writel(sdhci, ier, SDHCI_SIGNAL_ENABLE);
-
- if (tap_delay_status)
- kfree(tap_delay_status);
-
return err;
}
@@ -1387,10 +1613,8 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
}
tegra_host->plat = plat;
-
- tegra_host->soc_data = soc_data;
-
tegra_host->sd_stat_head = NULL;
+ tegra_host->soc_data = soc_data;
pltfm_host->priv = tegra_host;
@@ -1575,7 +1799,12 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev)
tegra_host->hw_ops = &tegra_11x_sdhci_ops;
tegra_sdhost_std_freq = TEGRA3_SDHOST_STD_FREQ;
#endif
+
+ if (plat->nominal_vcore_uV)
+ tegra_host->nominal_vcore_uV = plat->nominal_vcore_uV;
+
rc = sdhci_add_host(host);
+
sdhci_tegra_error_stats_debugfs(host);
if (rc)
goto err_add_host;