|
|
@@ -27,6 +27,7 @@
|
|
|
#include <linux/regulator/consumer.h>
|
|
|
#include <linux/pinctrl/consumer.h>
|
|
|
#include <linux/sys_soc.h>
|
|
|
+#include <linux/thermal.h>
|
|
|
|
|
|
#include "sdhci-pltfm.h"
|
|
|
|
|
|
@@ -115,6 +116,7 @@ struct sdhci_omap_host {
|
|
|
|
|
|
struct pinctrl *pinctrl;
|
|
|
struct pinctrl_state **pinctrl_state;
|
|
|
+ bool is_tuning;
|
|
|
};
|
|
|
|
|
|
static void sdhci_omap_start_clock(struct sdhci_omap_host *omap_host);
|
|
|
@@ -285,19 +287,19 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
|
struct sdhci_host *host = mmc_priv(mmc);
|
|
|
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
|
struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
+ struct thermal_zone_device *thermal_dev;
|
|
|
struct device *dev = omap_host->dev;
|
|
|
struct mmc_ios *ios = &mmc->ios;
|
|
|
u32 start_window = 0, max_window = 0;
|
|
|
+ bool single_point_failure = false;
|
|
|
+ bool dcrc_was_enabled = false;
|
|
|
u8 cur_match, prev_match = 0;
|
|
|
u32 length = 0, max_len = 0;
|
|
|
- u32 ier = host->ier;
|
|
|
u32 phase_delay = 0;
|
|
|
+ int temperature;
|
|
|
int ret = 0;
|
|
|
u32 reg;
|
|
|
-
|
|
|
- pltfm_host = sdhci_priv(host);
|
|
|
- omap_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
- dev = omap_host->dev;
|
|
|
+ int i;
|
|
|
|
|
|
/* clock tuning is not needed for upto 52MHz */
|
|
|
if (ios->clock <= 52000000)
|
|
|
@@ -307,6 +309,16 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
|
if (ios->timing == MMC_TIMING_UHS_SDR50 && !(reg & CAPA2_TSDR50))
|
|
|
return 0;
|
|
|
|
|
|
+ thermal_dev = thermal_zone_get_zone_by_name("cpu_thermal");
|
|
|
+ if (IS_ERR(thermal_dev)) {
|
|
|
+ dev_err(dev, "Unable to get thermal zone for tuning\n");
|
|
|
+ return PTR_ERR(thermal_dev);
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = thermal_zone_get_temp(thermal_dev, &temperature);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_DLL);
|
|
|
reg |= DLL_SWT;
|
|
|
sdhci_omap_writel(omap_host, SDHCI_OMAP_DLL, reg);
|
|
|
@@ -317,10 +329,18 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
|
* during the tuning procedure. So disable it during the
|
|
|
* tuning procedure.
|
|
|
*/
|
|
|
- ier &= ~SDHCI_INT_DATA_CRC;
|
|
|
- sdhci_writel(host, ier, SDHCI_INT_ENABLE);
|
|
|
- sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
|
|
|
+ if (host->ier & SDHCI_INT_DATA_CRC) {
|
|
|
+ host->ier &= ~SDHCI_INT_DATA_CRC;
|
|
|
+ dcrc_was_enabled = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ omap_host->is_tuning = true;
|
|
|
|
|
|
+ /*
|
|
|
+ * Stage 1: Search for a maximum pass window ignoring any
|
|
|
+ * any single point failures. If the tuning value ends up
|
|
|
+ * near it, move away from it in stage 2 below
|
|
|
+ */
|
|
|
while (phase_delay <= MAX_PHASE_DELAY) {
|
|
|
sdhci_omap_set_dll(omap_host, phase_delay);
|
|
|
|
|
|
@@ -328,10 +348,15 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
|
if (cur_match) {
|
|
|
if (prev_match) {
|
|
|
length++;
|
|
|
+ } else if (single_point_failure) {
|
|
|
+ /* ignore single point failure */
|
|
|
+ length++;
|
|
|
} else {
|
|
|
start_window = phase_delay;
|
|
|
length = 1;
|
|
|
}
|
|
|
+ } else {
|
|
|
+ single_point_failure = prev_match;
|
|
|
}
|
|
|
|
|
|
if (length > max_len) {
|
|
|
@@ -349,23 +374,92 @@ static int sdhci_omap_execute_tuning(struct mmc_host *mmc, u32 opcode)
|
|
|
goto tuning_error;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * Assign tuning value as a ratio of maximum pass window based
|
|
|
+ * on temperature
|
|
|
+ */
|
|
|
+ if (temperature < -20000)
|
|
|
+ phase_delay = min(max_window + 4 * max_len - 24,
|
|
|
+ max_window +
|
|
|
+ DIV_ROUND_UP(13 * max_len, 16) * 4);
|
|
|
+ else if (temperature < 20000)
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(9 * max_len, 16) * 4;
|
|
|
+ else if (temperature < 40000)
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(8 * max_len, 16) * 4;
|
|
|
+ else if (temperature < 70000)
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(7 * max_len, 16) * 4;
|
|
|
+ else if (temperature < 90000)
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(5 * max_len, 16) * 4;
|
|
|
+ else if (temperature < 120000)
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(4 * max_len, 16) * 4;
|
|
|
+ else
|
|
|
+ phase_delay = max_window + DIV_ROUND_UP(3 * max_len, 16) * 4;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Stage 2: Search for a single point failure near the chosen tuning
|
|
|
+ * value in two steps. First in the +3 to +10 range and then in the
|
|
|
+ * +2 to -10 range. If found, move away from it in the appropriate
|
|
|
+ * direction by the appropriate amount depending on the temperature.
|
|
|
+ */
|
|
|
+ for (i = 3; i <= 10; i++) {
|
|
|
+ sdhci_omap_set_dll(omap_host, phase_delay + i);
|
|
|
+
|
|
|
+ if (mmc_send_tuning(mmc, opcode, NULL)) {
|
|
|
+ if (temperature < 10000)
|
|
|
+ phase_delay += i + 6;
|
|
|
+ else if (temperature < 20000)
|
|
|
+ phase_delay += i - 12;
|
|
|
+ else if (temperature < 70000)
|
|
|
+ phase_delay += i - 8;
|
|
|
+ else
|
|
|
+ phase_delay += i - 6;
|
|
|
+
|
|
|
+ goto single_failure_found;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 2; i >= -10; i--) {
|
|
|
+ sdhci_omap_set_dll(omap_host, phase_delay + i);
|
|
|
+
|
|
|
+ if (mmc_send_tuning(mmc, opcode, NULL)) {
|
|
|
+ if (temperature < 10000)
|
|
|
+ phase_delay += i + 12;
|
|
|
+ else if (temperature < 20000)
|
|
|
+ phase_delay += i + 8;
|
|
|
+ else if (temperature < 70000)
|
|
|
+ phase_delay += i + 8;
|
|
|
+ else if (temperature < 90000)
|
|
|
+ phase_delay += i + 10;
|
|
|
+ else
|
|
|
+ phase_delay += i + 12;
|
|
|
+
|
|
|
+ goto single_failure_found;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+single_failure_found:
|
|
|
reg = sdhci_omap_readl(omap_host, SDHCI_OMAP_AC12);
|
|
|
if (!(reg & AC12_SCLK_SEL)) {
|
|
|
ret = -EIO;
|
|
|
goto tuning_error;
|
|
|
}
|
|
|
|
|
|
- phase_delay = max_window + 4 * (max_len >> 1);
|
|
|
sdhci_omap_set_dll(omap_host, phase_delay);
|
|
|
|
|
|
+ omap_host->is_tuning = false;
|
|
|
+
|
|
|
goto ret;
|
|
|
|
|
|
tuning_error:
|
|
|
+ omap_host->is_tuning = false;
|
|
|
dev_err(dev, "Tuning failed\n");
|
|
|
sdhci_omap_disable_tuning(omap_host);
|
|
|
|
|
|
ret:
|
|
|
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
|
|
|
+ /* Reenable forbidden interrupt */
|
|
|
+ if (dcrc_was_enabled)
|
|
|
+ host->ier |= SDHCI_INT_DATA_CRC;
|
|
|
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
|
|
|
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
|
|
|
return ret;
|
|
|
@@ -683,6 +777,18 @@ static void sdhci_omap_set_uhs_signaling(struct sdhci_host *host,
|
|
|
sdhci_omap_start_clock(omap_host);
|
|
|
}
|
|
|
|
|
|
+void sdhci_omap_reset(struct sdhci_host *host, u8 mask)
|
|
|
+{
|
|
|
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
|
|
|
+ struct sdhci_omap_host *omap_host = sdhci_pltfm_priv(pltfm_host);
|
|
|
+
|
|
|
+ /* Don't reset data lines during tuning operation */
|
|
|
+ if (omap_host->is_tuning)
|
|
|
+ mask &= ~SDHCI_RESET_DATA;
|
|
|
+
|
|
|
+ sdhci_reset(host, mask);
|
|
|
+}
|
|
|
+
|
|
|
static struct sdhci_ops sdhci_omap_ops = {
|
|
|
.set_clock = sdhci_omap_set_clock,
|
|
|
.set_power = sdhci_omap_set_power,
|
|
|
@@ -691,7 +797,7 @@ static struct sdhci_ops sdhci_omap_ops = {
|
|
|
.get_min_clock = sdhci_omap_get_min_clock,
|
|
|
.set_bus_width = sdhci_omap_set_bus_width,
|
|
|
.platform_send_init_74_clocks = sdhci_omap_init_74_clocks,
|
|
|
- .reset = sdhci_reset,
|
|
|
+ .reset = sdhci_omap_reset,
|
|
|
.set_uhs_signaling = sdhci_omap_set_uhs_signaling,
|
|
|
};
|
|
|
|