diff mbox series

[v3,11/24] mmc: omap_hsmmc: Add support to set IODELAY values

Message ID 1517324513-13875-12-git-send-email-jjhiblot@ti.com
State New
Headers show
Series None | expand

Commit Message

Jean-Jacques Hiblot Jan. 30, 2018, 3:01 p.m. UTC
From: Kishon Vijay Abraham I <kishon@ti.com>

The data manual of J6/J6 Eco recommends to set different IODELAY values
depending on the mode in which the MMC/SD is enumerated in order to
ensure IO timings are met.

Add support to parse mux values and iodelay values from device tree
and set these depending on the enumerated MMC mode.

Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com>
---

Changes in v3: None

 drivers/mmc/omap_hsmmc.c | 372 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 372 insertions(+)
diff mbox series

Patch

diff --git a/drivers/mmc/omap_hsmmc.c b/drivers/mmc/omap_hsmmc.c
index 57548ee..2b77422 100644
--- a/drivers/mmc/omap_hsmmc.c
+++ b/drivers/mmc/omap_hsmmc.c
@@ -34,6 +34,10 @@ 
 #endif
 #include <asm/io.h>
 #include <asm/arch/mmc_host_def.h>
+#ifdef CONFIG_OMAP54XX
+#include <asm/arch/mux_dra7xx.h>
+#include <asm/arch/dra7xx_iodelay.h>
+#endif
 #if !defined(CONFIG_SOC_KEYSTONE)
 #include <asm/gpio.h>
 #include <asm/arch/sys_proto.h>
@@ -57,6 +61,15 @@  DECLARE_GLOBAL_DATA_PTR;
 #define SYSCTL_SRC	(1 << 25)
 #define SYSCTL_SRD	(1 << 26)
 
+#ifdef CONFIG_IODELAY_RECALIBRATION
+struct omap_hsmmc_pinctrl_state {
+	struct pad_conf_entry *padconf;
+	int npads;
+	struct iodelay_cfg_entry *iodelay;
+	int niodelays;
+};
+#endif
+
 struct omap_hsmmc_data {
 	struct hsmmc *base_addr;
 #if !CONFIG_IS_ENABLED(DM_MMC)
@@ -83,6 +96,21 @@  struct omap_hsmmc_data {
 	struct omap_hsmmc_adma_desc *adma_desc_table;
 	uint desc_slot;
 #endif
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	struct omap_hsmmc_pinctrl_state *default_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *hs_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *hs200_1_8v_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *ddr_1_8v_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *sdr12_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *sdr25_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *ddr50_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *sdr50_pinctrl_state;
+	struct omap_hsmmc_pinctrl_state *sdr104_pinctrl_state;
+#endif
+};
+
+struct omap_mmc_of_data {
+	u8 controller_flags;
 };
 
 #ifndef CONFIG_OMAP34XX
@@ -119,6 +147,7 @@  struct omap_hsmmc_adma_desc {
 #define OMAP_HSMMC_SUPPORTS_DUAL_VOLT		BIT(0)
 #define OMAP_HSMMC_NO_1_8_V			BIT(1)
 #define OMAP_HSMMC_USE_ADMA			BIT(2)
+#define OMAP_HSMMC_REQUIRE_IODELAY		BIT(3)
 
 static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size);
 static int mmc_write_data(struct hsmmc *mmc_base, const char *buf,
@@ -261,6 +290,56 @@  void mmc_init_stream(struct hsmmc *mmc_base)
 }
 
 #if CONFIG_IS_ENABLED(DM_MMC)
+#ifdef CONFIG_IODELAY_RECALIBRATION
+static void omap_hsmmc_io_recalibrate(struct mmc *mmc)
+{
+	struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+	struct omap_hsmmc_pinctrl_state *pinctrl_state;
+
+	switch (priv->mode) {
+	case MMC_HS_200:
+		pinctrl_state = priv->hs200_1_8v_pinctrl_state;
+		break;
+	case UHS_SDR104:
+		pinctrl_state = priv->sdr104_pinctrl_state;
+		break;
+	case UHS_SDR50:
+		pinctrl_state = priv->sdr50_pinctrl_state;
+		break;
+	case UHS_DDR50:
+		pinctrl_state = priv->ddr50_pinctrl_state;
+		break;
+	case UHS_SDR25:
+		pinctrl_state = priv->sdr25_pinctrl_state;
+		break;
+	case UHS_SDR12:
+		pinctrl_state = priv->sdr12_pinctrl_state;
+		break;
+	case SD_HS:
+	case MMC_HS:
+	case MMC_HS_52:
+		pinctrl_state = priv->hs_pinctrl_state;
+		break;
+	case MMC_DDR_52:
+		pinctrl_state = priv->ddr_1_8v_pinctrl_state;
+	default:
+		pinctrl_state = priv->default_pinctrl_state;
+		break;
+	}
+
+	if (priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY) {
+		if (pinctrl_state->iodelay)
+			late_recalibrate_iodelay(pinctrl_state->padconf,
+						 pinctrl_state->npads,
+						 pinctrl_state->iodelay,
+						 pinctrl_state->niodelays);
+		else
+			do_set_mux32((*ctrl)->control_padconf_core_base,
+				     pinctrl_state->padconf,
+				     pinctrl_state->npads);
+	}
+}
+#endif
 static void omap_hsmmc_set_timing(struct mmc *mmc)
 {
 	u32 val;
@@ -269,6 +348,7 @@  static void omap_hsmmc_set_timing(struct mmc *mmc)
 
 	mmc_base = priv->base_addr;
 
+	omap_hsmmc_stop_clock(mmc_base);
 	val = readl(&mmc_base->ac12);
 	val &= ~AC12_UHSMC_MASK;
 	priv->mode = mmc->selected_mode;
@@ -306,6 +386,11 @@  static void omap_hsmmc_set_timing(struct mmc *mmc)
 		break;
 	}
 	writel(val, &mmc_base->ac12);
+
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	omap_hsmmc_io_recalibrate(mmc);
+#endif
+	omap_hsmmc_start_clock(mmc_base);
 }
 
 static void omap_hsmmc_conf_bus_power(struct mmc *mmc)
@@ -1290,10 +1375,271 @@  int omap_mmc_init(int dev_index, uint host_caps_mask, uint f_max, int cd_gpio,
 	return 0;
 }
 #else
+
+#ifdef CONFIG_IODELAY_RECALIBRATION
+static struct pad_conf_entry *
+omap_hsmmc_get_pad_conf_entry(const fdt32_t *pinctrl, int count)
+{
+	int index = 0;
+	struct pad_conf_entry *padconf;
+
+	padconf = (struct pad_conf_entry *)malloc(sizeof(*padconf) * count);
+	if (!padconf) {
+		debug("failed to allocate memory\n");
+		return 0;
+	}
+
+	while (index < count) {
+		padconf[index].offset = fdt32_to_cpu(pinctrl[2 * index]);
+		padconf[index].val = fdt32_to_cpu(pinctrl[2 * index + 1]);
+		index++;
+	}
+
+	return padconf;
+}
+
+static struct iodelay_cfg_entry *
+omap_hsmmc_get_iodelay_cfg_entry(const fdt32_t *pinctrl, int count)
+{
+	int index = 0;
+	struct iodelay_cfg_entry *iodelay;
+
+	iodelay = (struct iodelay_cfg_entry *)malloc(sizeof(*iodelay) * count);
+	if (!iodelay) {
+		debug("failed to allocate memory\n");
+		return 0;
+	}
+
+	while (index < count) {
+		iodelay[index].offset = fdt32_to_cpu(pinctrl[3 * index]);
+		iodelay[index].a_delay = fdt32_to_cpu(pinctrl[3 * index + 1]);
+		iodelay[index].g_delay = fdt32_to_cpu(pinctrl[3 * index + 2]);
+		index++;
+	}
+
+	return iodelay;
+}
+
+static const fdt32_t *omap_hsmmc_get_pinctrl_entry(u32  phandle,
+						   const char *name, int *len)
+{
+	const void *fdt = gd->fdt_blob;
+	int offset;
+	const fdt32_t *pinctrl;
+
+	offset = fdt_node_offset_by_phandle(fdt, phandle);
+	if (offset < 0) {
+		debug("failed to get pinctrl node %s.\n",
+		      fdt_strerror(offset));
+		return 0;
+	}
+
+	pinctrl = fdt_getprop(fdt, offset, name, len);
+	if (!pinctrl) {
+		debug("failed to get property %s\n", name);
+		return 0;
+	}
+
+	return pinctrl;
+}
+
+static uint32_t omap_hsmmc_get_pad_conf_phandle(struct mmc *mmc,
+						char *prop_name)
+{
+	const void *fdt = gd->fdt_blob;
+	const __be32 *phandle;
+	int node = dev_of_offset(mmc->dev);
+
+	phandle = fdt_getprop(fdt, node, prop_name, NULL);
+	if (!phandle) {
+		debug("failed to get property %s\n", prop_name);
+		return 0;
+	}
+
+	return fdt32_to_cpu(*phandle);
+}
+
+static uint32_t omap_hsmmc_get_iodelay_phandle(struct mmc *mmc,
+					       char *prop_name)
+{
+	const void *fdt = gd->fdt_blob;
+	const __be32 *phandle;
+	int len;
+	int count;
+	int node = dev_of_offset(mmc->dev);
+
+	phandle = fdt_getprop(fdt, node, prop_name, &len);
+	if (!phandle) {
+		debug("failed to get property %s\n", prop_name);
+		return 0;
+	}
+
+	/* No manual mode iodelay values if count < 2 */
+	count = len / sizeof(*phandle);
+	if (count < 2)
+		return 0;
+
+	return fdt32_to_cpu(*(phandle + 1));
+}
+
+static struct pad_conf_entry *
+omap_hsmmc_get_pad_conf(struct mmc *mmc, char *prop_name, int *npads)
+{
+	int len;
+	int count;
+	struct pad_conf_entry *padconf;
+	u32 phandle;
+	const fdt32_t *pinctrl;
+
+	phandle = omap_hsmmc_get_pad_conf_phandle(mmc, prop_name);
+	if (!phandle)
+		return ERR_PTR(-EINVAL);
+
+	pinctrl = omap_hsmmc_get_pinctrl_entry(phandle, "pinctrl-single,pins",
+					       &len);
+	if (!pinctrl)
+		return ERR_PTR(-EINVAL);
+
+	count = (len / sizeof(*pinctrl)) / 2;
+	padconf = omap_hsmmc_get_pad_conf_entry(pinctrl, count);
+	if (!padconf)
+		return ERR_PTR(-EINVAL);
+
+	*npads = count;
+
+	return padconf;
+}
+
+static struct iodelay_cfg_entry *
+omap_hsmmc_get_iodelay(struct mmc *mmc, char *prop_name, int *niodelay)
+{
+	int len;
+	int count;
+	struct iodelay_cfg_entry *iodelay;
+	u32 phandle;
+	const fdt32_t *pinctrl;
+
+	phandle = omap_hsmmc_get_iodelay_phandle(mmc, prop_name);
+	/* Not all modes have manual mode iodelay values. So its not fatal */
+	if (!phandle)
+		return 0;
+
+	pinctrl = omap_hsmmc_get_pinctrl_entry(phandle, "pinctrl-pin-array",
+					       &len);
+	if (!pinctrl)
+		return ERR_PTR(-EINVAL);
+
+	count = (len / sizeof(*pinctrl)) / 3;
+	iodelay = omap_hsmmc_get_iodelay_cfg_entry(pinctrl, count);
+	if (!iodelay)
+		return ERR_PTR(-EINVAL);
+
+	*niodelay = count;
+
+	return iodelay;
+}
+
+static struct omap_hsmmc_pinctrl_state *
+omap_hsmmc_get_pinctrl_by_mode(struct mmc *mmc, char *mode)
+{
+	int index;
+	int npads = 0;
+	int niodelays = 0;
+	const void *fdt = gd->fdt_blob;
+	int node = dev_of_offset(mmc->dev);
+	char prop_name[11];
+	struct omap_hsmmc_pinctrl_state *pinctrl_state;
+
+	pinctrl_state = (struct omap_hsmmc_pinctrl_state *)
+			 malloc(sizeof(*pinctrl_state));
+	if (!pinctrl_state) {
+		debug("failed to allocate memory\n");
+		return 0;
+	}
+
+	index = fdt_stringlist_search(fdt, node, "pinctrl-names", mode);
+	if (index < 0) {
+		debug("fail to find %s mode %s\n", mode, fdt_strerror(index));
+		goto err_pinctrl_state;
+	}
+
+	sprintf(prop_name, "pinctrl-%d", index);
+
+	pinctrl_state->padconf = omap_hsmmc_get_pad_conf(mmc, prop_name,
+							 &npads);
+	if (IS_ERR(pinctrl_state->padconf))
+		goto err_pinctrl_state;
+	pinctrl_state->npads = npads;
+
+	pinctrl_state->iodelay = omap_hsmmc_get_iodelay(mmc, prop_name,
+							&niodelays);
+	if (IS_ERR(pinctrl_state->iodelay))
+		goto err_padconf;
+	pinctrl_state->niodelays = niodelays;
+
+	return pinctrl_state;
+
+err_padconf:
+	kfree(pinctrl_state->padconf);
+
+err_pinctrl_state:
+	kfree(pinctrl_state);
+	return 0;
+}
+
+#define OMAP_HSMMC_SETUP_PINCTRL(capmask, mode)			\
+	do {							\
+		struct omap_hsmmc_pinctrl_state *s;		\
+		if (!(cfg->host_caps & capmask))		\
+			break;					\
+								\
+		s = omap_hsmmc_get_pinctrl_by_mode(mmc, #mode);	\
+		if (!s) {					\
+			debug("%s: no pinctrl for %s\n",	\
+			      mmc->dev->name, #mode);		\
+			cfg->host_caps &= ~(capmask);		\
+		} else {					\
+			priv->mode##_pinctrl_state = s;		\
+		}						\
+	} while (0)
+
+static int omap_hsmmc_get_pinctrl_state(struct mmc *mmc)
+{
+	struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc);
+	struct mmc_config *cfg = omap_hsmmc_get_cfg(mmc);
+	struct omap_hsmmc_pinctrl_state *default_pinctrl;
+
+	if (!(priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY))
+		return 0;
+
+	default_pinctrl = omap_hsmmc_get_pinctrl_by_mode(mmc, "default");
+	if (!default_pinctrl) {
+		printf("no pinctrl state for default mode\n");
+		return -EINVAL;
+	}
+
+	priv->default_pinctrl_state = default_pinctrl;
+
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR104), sdr104);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR50), sdr50);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_DDR50), ddr50);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR25), sdr25);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR12), sdr12);
+
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(MMC_HS_200), hs200_1_8v);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(MMC_DDR_52), ddr_1_8v);
+	OMAP_HSMMC_SETUP_PINCTRL(MMC_MODE_HS, hs);
+
+	return 0;
+}
+#endif
+
 #if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
 static int omap_hsmmc_ofdata_to_platdata(struct udevice *dev)
 {
 	struct omap_hsmmc_plat *plat = dev_get_platdata(dev);
+	struct omap_mmc_of_data *of_data = (void *)dev_get_driver_data(dev);
+
 	struct mmc_config *cfg = &plat->cfg;
 	const void *fdt = gd->fdt_blob;
 	int node = dev_of_offset(dev);
@@ -1315,6 +1661,8 @@  static int omap_hsmmc_ofdata_to_platdata(struct udevice *dev)
 		plat->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT;
 	if (fdtdec_get_bool(fdt, node, "no-1-8-v"))
 		plat->controller_flags |= OMAP_HSMMC_NO_1_8_V;
+	if (of_data)
+		plat->controller_flags |= of_data->controller_flags;
 
 #ifdef OMAP_HSMMC_USE_GPIO
 	plat->cd_inverted = fdtdec_get_bool(fdt, node, "cd-inverted");
@@ -1340,9 +1688,13 @@  static int omap_hsmmc_probe(struct udevice *dev)
 	struct omap_hsmmc_data *priv = dev_get_priv(dev);
 	struct mmc_config *cfg = &plat->cfg;
 	struct mmc *mmc;
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	int ret;
+#endif
 
 	cfg->name = "OMAP SD/MMC";
 	priv->base_addr = plat->base_addr;
+	priv->controller_flags = plat->controller_flags;
 #ifdef OMAP_HSMMC_USE_GPIO
 	priv->cd_inverted = plat->cd_inverted;
 #endif
@@ -1363,14 +1715,34 @@  static int omap_hsmmc_probe(struct udevice *dev)
 	mmc->dev = dev;
 	upriv->mmc = mmc;
 
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	ret = omap_hsmmc_get_pinctrl_state(mmc);
+	/*
+	 * disable high speed modes for the platforms that require IO delay
+	 * and for which we don't have this information
+	 */
+	if ((ret < 0) &&
+	    (priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY)) {
+		priv->controller_flags &= ~OMAP_HSMMC_REQUIRE_IODELAY;
+		cfg->host_caps &= ~(MMC_CAP(MMC_HS_200) | MMC_CAP(MMC_DDR_52) |
+				    UHS_CAPS);
+	}
+#endif
+
 	return omap_hsmmc_init_setup(mmc);
 }
 
 #if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)
+
+static const struct omap_mmc_of_data dra7_mmc_of_data = {
+	.controller_flags = OMAP_HSMMC_REQUIRE_IODELAY,
+};
+
 static const struct udevice_id omap_hsmmc_ids[] = {
 	{ .compatible = "ti,omap3-hsmmc" },
 	{ .compatible = "ti,omap4-hsmmc" },
 	{ .compatible = "ti,am33xx-hsmmc" },
+	{ .compatible = "ti,dra7-hsmmc", .data = (ulong)&dra7_mmc_of_data },
 	{ }
 };
 #endif