[U-Boot] [PATCH 19/33] mmc: dw_mmc: rockchip: implement tuning with clock phase framework
Ziyuan Xu
xzy.xu at rock-chips.com
Mon May 15 06:07:13 UTC 2017
This algorithm will try 1 degree increment, since there's no way to tell
what resolution the underlying phase code uses. As an added bonus, doing
many tunings yields better results since some tests are run more than
once (ex: if the underlying driver use 45 degree increments, the tuning
code will try the same angle more than once).
It will then construct a list of good phase ranges (even range that
cross 270/0), will pick the biggest range then it will set the
sample_clk to the middle of that range.
Please notice that it tuning only 0-270 degree in U-Boot, but kernel
tuning range is 0-360 degree. Below are two reasons about this:
1. Expect data-related interrupt may miss during 270-360 degree on
rockchip platform, dw_mmc driver will poll for data interrupt until
240 seconds timeout afterwards. And the host controller will be left in
an unpredictable state.
2. The phase of a clock signal is shift by some delay elements on
rockchip platform. And the delay element affected by logic voltage and
temperature in runtime. These factors wouldn't have changed a lot in
U-Boot stage.
Signed-off-by: Ziyuan Xu <xzy.xu at rock-chips.com>
---
drivers/mmc/rockchip_dw_mmc.c | 121 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/drivers/mmc/rockchip_dw_mmc.c b/drivers/mmc/rockchip_dw_mmc.c
index 2885ef2..474ca1c 100644
--- a/drivers/mmc/rockchip_dw_mmc.c
+++ b/drivers/mmc/rockchip_dw_mmc.c
@@ -30,6 +30,7 @@ struct rockchip_mmc_plat {
struct rockchip_dwmmc_priv {
struct clk clk;
+ struct clk sample_clk;
struct dwmci_host host;
int fifo_depth;
bool fifo_mode;
@@ -99,6 +100,123 @@ static int rockchip_dwmmc_ofdata_to_platdata(struct udevice *dev)
return 0;
}
+#define NUM_PHASES 270
+#define TUNING_ITERATION_TO_PHASE(i) (DIV_ROUND_UP((i) * 270, NUM_PHASES))
+
+static int rockchip_dwmmc_execute_tuning(struct dwmci_host *host, u32 opcode)
+{
+ int ret = 0;
+ int i;
+ bool v, prev_v = 0, first_v;
+ struct range_t {
+ int start;
+ int end; /* inclusive */
+ };
+ struct range_t *ranges;
+ unsigned int range_count = 0;
+ int longest_range_len = -1;
+ int longest_range = -1;
+ int middle_phase;
+ struct udevice *dev = host->priv;
+ struct rockchip_dwmmc_priv *priv = dev_get_priv(dev);
+ struct mmc *mmc = host->mmc;
+
+ if (IS_ERR(&priv->sample_clk))
+ return -EIO;
+
+ ranges = calloc(sizeof(*ranges), NUM_PHASES / 2 + 1);
+ if (!ranges)
+ return -ENOMEM;
+
+ /* Try each phase and extract good ranges */
+ for (i = 0; i < NUM_PHASES; ) {
+ clk_set_phase(&priv->sample_clk, TUNING_ITERATION_TO_PHASE(i));
+
+ v = !mmc_send_tuning(mmc, opcode);
+
+ if (i == 0)
+ first_v = v;
+
+ if ((!prev_v) && v) {
+ range_count++;
+ ranges[range_count - 1].start = i;
+ }
+ if (v) {
+ ranges[range_count - 1].end = i;
+ i++;
+ } else if (i == NUM_PHASES - 1) {
+ /* No extra skipping rules if we're at the end */
+ i++;
+ } else {
+ /*
+ * No need to check too close to an invalid
+ * one since testing bad phases is slow. Skip
+ * 20 degrees.
+ */
+ i += DIV_ROUND_UP(20 * NUM_PHASES, NUM_PHASES);
+
+ /* Always test the last one */
+ if (i >= NUM_PHASES)
+ i = NUM_PHASES - 1;
+ }
+
+ prev_v = v;
+ }
+
+ if (range_count == 0) {
+ debug("All phases bad!");
+ ret = -EIO;
+ goto free;
+ }
+
+ /* wrap around case, merge the end points */
+ if ((range_count > 1) && first_v && v) {
+ ranges[0].start = ranges[range_count - 1].start;
+ range_count--;
+ }
+
+ if (ranges[0].start == 0 && ranges[0].end == NUM_PHASES - 1) {
+ clk_set_phase(&priv->sample_clk,
+ TUNING_ITERATION_TO_PHASE(NUM_PHASES / 2));
+ debug("All phases work, using middle phase.\n");
+ goto free;
+ }
+
+ /* Find the longest range */
+ for (i = 0; i < range_count; i++) {
+ int len = (ranges[i].end - ranges[i].start + 1);
+
+ if (len < 0)
+ len += NUM_PHASES;
+
+ if (longest_range_len < len) {
+ longest_range_len = len;
+ longest_range = i;
+ }
+
+ debug("Good phase range %d-%d (%d len)\n",
+ TUNING_ITERATION_TO_PHASE(ranges[i].start),
+ TUNING_ITERATION_TO_PHASE(ranges[i].end), len);
+ }
+
+ printf("Best phase range %d-%d (%d len)\n",
+ TUNING_ITERATION_TO_PHASE(ranges[longest_range].start),
+ TUNING_ITERATION_TO_PHASE(ranges[longest_range].end),
+ longest_range_len);
+
+ middle_phase = ranges[longest_range].start + longest_range_len / 2;
+ middle_phase %= NUM_PHASES;
+ debug("Successfully tuned phase to %d\n",
+ TUNING_ITERATION_TO_PHASE(middle_phase));
+
+ clk_set_phase(&priv->sample_clk,
+ TUNING_ITERATION_TO_PHASE(middle_phase));
+
+free:
+ free(ranges);
+ return ret;
+}
+
static int rockchip_dwmmc_probe(struct udevice *dev)
{
struct rockchip_mmc_plat *plat = dev_get_platdata(dev);
@@ -115,6 +233,7 @@ static int rockchip_dwmmc_probe(struct udevice *dev)
host->ioaddr = map_sysmem(dtplat->reg[0], dtplat->reg[1]);
host->buswidth = dtplat->bus_width;
host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk;
+ host->execute_tuning = rockchip_dwmmc_execute_tuning;
host->priv = dev;
host->dev_index = 0;
priv->fifo_depth = dtplat->fifo_depth;
@@ -128,6 +247,8 @@ static int rockchip_dwmmc_probe(struct udevice *dev)
ret = clk_get_by_name(dev, "ciu", &priv->clk);
if (ret < 0)
return ret;
+ clk_get_by_name(dev, "ciu_sample", &priv->sample_clk);
+ host->execute_tuning = rockchip_dwmmc_execute_tuning;
#endif
host->fifoth_val = MSIZE(0x2) |
RX_WMARK(priv->fifo_depth / 2 - 1) |
--
2.7.4
More information about the U-Boot
mailing list