mmc: omap_hsmmc: Workaround errata regarding SDR104/HS200 tuning failures (i929)
authorFaiz Abbas <faiz_abbas@ti.com>
Wed, 30 Jan 2019 12:38:42 +0000 (18:08 +0530)
committerTom Rini <trini@konsulko.com>
Sat, 9 Feb 2019 12:50:58 +0000 (07:50 -0500)
Errata i929 in certain OMAP5/DRA7XX/AM57XX silicon revisions
(SPRZ426D - November 2014 - Revised February 2018 [1]) mentions
unexpected tuning pattern errors. A small failure band may be present
in the tuning range which may be missed by the current algorithm.
Furthermore, the failure bands vary with temperature leading to
different optimum tuning values for different temperatures.

As suggested in the related Application Report (SPRACA9B - October 2017
- Revised July 2018 [2]), tuning should be done in two stages.
In stage 1, assign the optimum ratio in the maximum pass window for the
current temperature. In stage 2, if the chosen value is close to the
small failure band, move away from it in the appropriate direction.

References:
[1] http://www.ti.com/lit/pdf/sprz426
[2] http://www.ti.com/lit/pdf/SPRACA9

Signed-off-by: Faiz Abbas <faiz_abbas@ti.com>
drivers/mmc/omap_hsmmc.c

index 5cb97eb02af85a1ef806dcc8e848a42c02a07d81..b11c71cae778c4ab0a2e2a7ce79719a48fbfcc3a 100644 (file)
@@ -47,6 +47,7 @@
 #endif
 #include <dm.h>
 #include <power/regulator.h>
+#include <thermal.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -622,6 +623,10 @@ static int omap_hsmmc_execute_tuning(struct udevice *dev, uint opcode)
        u32 phase_delay = 0;
        u32 start_window = 0, max_window = 0;
        u32 length = 0, max_len = 0;
+       bool single_point_failure = false;
+       struct udevice *thermal_dev;
+       int temperature;
+       int i;
 
        mmc_base = priv->base_addr;
        val = readl(&mmc_base->capa2);
@@ -632,9 +637,25 @@ static int omap_hsmmc_execute_tuning(struct udevice *dev, uint opcode)
              ((mmc->selected_mode == UHS_SDR50) && (val & CAPA2_TSDR50))))
                return 0;
 
+       ret = uclass_first_device(UCLASS_THERMAL, &thermal_dev);
+       if (ret) {
+               printf("Couldn't get thermal device for tuning\n");
+               return ret;
+       }
+       ret = thermal_get_temp(thermal_dev, &temperature);
+       if (ret) {
+               printf("Couldn't get temperature for tuning\n");
+               return ret;
+       }
        val = readl(&mmc_base->dll);
        val |= DLL_SWT;
        writel(val, &mmc_base->dll);
+
+       /*
+        * 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) {
                omap_hsmmc_set_dll(mmc, phase_delay);
 
@@ -643,10 +664,16 @@ static int omap_hsmmc_execute_tuning(struct udevice *dev, uint opcode)
                if (cur_match) {
                        if (prev_match) {
                                length++;
+                       } else if (single_point_failure) {
+                               /* ignore single point failure */
+                               length++;
+                               single_point_failure = false;
                        } else {
                                start_window = phase_delay;
                                length = 1;
                        }
+               } else {
+                       single_point_failure = prev_match;
                }
 
                if (length > max_len) {
@@ -668,8 +695,71 @@ static int omap_hsmmc_execute_tuning(struct udevice *dev, uint opcode)
                ret = -EIO;
                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++) {
+               omap_hsmmc_set_dll(mmc, 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 if (temperature < 90000)
+                               phase_delay += i - 6;
+                       else
+                               phase_delay += i - 6;
+
+                       goto single_failure_found;
+               }
+       }
+
+       for (i = 2; i >= -10; i--) {
+               omap_hsmmc_set_dll(mmc, 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:
 
-       phase_delay = max_window + 4 * ((3 * max_len) >> 2);
        omap_hsmmc_set_dll(mmc, phase_delay);
 
        mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD);