mmc: tmio: sdhi: Add SCC error checking

Check SCC for errors after check command if applicable and
optionally adjust the bus skew settings accordingly.

Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Masahiro Yamada <yamada.masahiro@socionext.com>
diff --git a/drivers/mmc/renesas-sdhi.c b/drivers/mmc/renesas-sdhi.c
index 8c690a2..dcc77dd 100644
--- a/drivers/mmc/renesas-sdhi.c
+++ b/drivers/mmc/renesas-sdhi.c
@@ -34,7 +34,12 @@
 #define RENESAS_SDHI_SCC_RVSCNTL_RVSEN		BIT(0)
 #define RENESAS_SDHI_SCC_RVSREQ			0x814
 #define RENESAS_SDHI_SCC_RVSREQ_RVSERR		BIT(2)
+#define RENESAS_SDHI_SCC_RVSREQ_REQTAPUP	BIT(1)
+#define RENESAS_SDHI_SCC_RVSREQ_REQTAPDOWN	BIT(0)
 #define RENESAS_SDHI_SCC_SMPCMP			0x818
+#define RENESAS_SDHI_SCC_SMPCMP_CMD_ERR		(BIT(24) | BIT(8))
+#define RENESAS_SDHI_SCC_SMPCMP_CMD_REQUP	BIT(24)
+#define RENESAS_SDHI_SCC_SMPCMP_CMD_REQDOWN	BIT(8)
 #define RENESAS_SDHI_SCC_TMPPORT2		0x81c
 #define RENESAS_SDHI_SCC_TMPPORT2_HS400EN	BIT(31)
 #define RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL	BIT(4)
@@ -87,6 +92,84 @@
 	tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_TMPPORT4);
 }
 
+static bool renesas_sdhi_check_scc_error(struct udevice *dev)
+{
+	struct tmio_sd_priv *priv = dev_get_priv(dev);
+	struct mmc *mmc = mmc_get_mmc_dev(dev);
+	unsigned long new_tap = priv->tap_set;
+	u32 reg, smpcmp;
+
+	if ((priv->caps & TMIO_SD_CAP_RCAR_UHS) &&
+	    (mmc->selected_mode != UHS_SDR104) &&
+	    (mmc->selected_mode != MMC_HS_200) &&
+	    (mmc->selected_mode != MMC_HS_400) &&
+	    (priv->nrtaps != 4))
+		return false;
+
+	reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL);
+	/* Handle automatic tuning correction */
+	if (reg & RENESAS_SDHI_SCC_RVSCNTL_RVSEN) {
+		reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSREQ);
+		if (reg & RENESAS_SDHI_SCC_RVSREQ_RVSERR) {
+			tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_RVSREQ);
+			return true;
+		}
+
+		return false;
+	}
+
+	/* Handle manual tuning correction */
+	reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSREQ);
+	if (!reg)	/* No error */
+		return false;
+
+	tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_RVSREQ);
+
+	if (mmc->selected_mode == MMC_HS_400) {
+		/*
+		 * Correction Error Status contains CMD and DAT signal status.
+		 * In HS400, DAT signal based on DS signal, not CLK.
+		 * Therefore, use only CMD status.
+		 */
+		smpcmp = tmio_sd_readl(priv, RENESAS_SDHI_SCC_SMPCMP) &
+			 RENESAS_SDHI_SCC_SMPCMP_CMD_ERR;
+
+		switch (smpcmp) {
+		case 0:
+			return false;	/* No error in CMD signal */
+		case RENESAS_SDHI_SCC_SMPCMP_CMD_REQUP:
+			new_tap = (priv->tap_set +
+				   priv->tap_num + 1) % priv->tap_num;
+			break;
+		case RENESAS_SDHI_SCC_SMPCMP_CMD_REQDOWN:
+			new_tap = (priv->tap_set +
+				   priv->tap_num - 1) % priv->tap_num;
+			break;
+		default:
+			return true;	/* Need re-tune */
+		}
+
+		priv->tap_set = new_tap;
+	} else {
+		if (reg & RENESAS_SDHI_SCC_RVSREQ_RVSERR)
+			return true;	/* Need re-tune */
+		else if (reg & RENESAS_SDHI_SCC_RVSREQ_REQTAPUP)
+			priv->tap_set = (priv->tap_set +
+					 priv->tap_num + 1) % priv->tap_num;
+		else if (reg & RENESAS_SDHI_SCC_RVSREQ_REQTAPDOWN)
+			priv->tap_set = (priv->tap_set +
+					 priv->tap_num - 1) % priv->tap_num;
+		else
+			return false;
+	}
+
+	/* Set TAP position */
+	tmio_sd_writel(priv, priv->tap_set >> ((priv->nrtaps == 4) ? 1 : 0),
+		       RENESAS_SDHI_SCC_TAPSET);
+
+	return false;
+}
+
 static void renesas_sdhi_adjust_hs400_mode_enable(struct tmio_sd_priv *priv)
 {
 	u32 calib_code;
@@ -536,6 +619,8 @@
     CONFIG_IS_ENABLED(MMC_HS400_SUPPORT)
 	struct tmio_sd_priv *priv = dev_get_priv(dev);
 
+	renesas_sdhi_check_scc_error(dev);
+
 	if (cmd->cmdidx == MMC_CMD_SEND_STATUS)
 		renesas_sdhi_adjust_hs400_mode_enable(priv);
 #endif