ARM: DRA7: Add support for IO delay configuration

On DRA7, in addition to the regular muxing of pins, an additional
hardware module called IODelay which is also expected to be
configured. This "IODelay" module has it's own register space that is
independent of the control module.

It is advocated strongly in TI's official documentation considering
the existing design of the DRA7 family of processors during mux or
IODelay recalibration, there is a potential for a significant glitch
which may cause functional impairment to certain hardware. It is
hence recommended to do muxing as part of IOdelay recalibration.

IODELAY recalibration sequence:
- Complete AVS voltage change on VDD_CORE_L
- Unlock IODLAY config registers.
- Perform IO delay calibration with predefined values.
- Isolate all the IOs
- Update the delay mechanism for each IO with new calibrated values.
- Configure PAD configuration registers
- De-isolate all the IOs.
- Relock IODELAY config registers.

Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com>
Signed-off-by: Nishanth Menon <nm@ti.com>
diff --git a/arch/arm/cpu/armv7/omap-common/clocks-common.c b/arch/arm/cpu/armv7/omap-common/clocks-common.c
index 03674e6..fa04bbe 100644
--- a/arch/arm/cpu/armv7/omap-common/clocks-common.c
+++ b/arch/arm/cpu/armv7/omap-common/clocks-common.c
@@ -508,6 +508,12 @@
 	return val;
 }
 
+#ifdef CONFIG_IODELAY_RECALIBRATION
+void __weak recalibrate_iodelay(void)
+{
+}
+#endif
+
 /*
  * Setup the voltages for the main SoC core power domains.
  * We start with the maximum voltages allowed here, as set in the corresponding
@@ -561,6 +567,16 @@
 
 	debug("cor: %d\n", vcores->core.value);
 	do_scale_vcore(vcores->core.addr, vcores->core.value, vcores->core.pmic);
+	/*
+	 * IO delay recalibration should be done immediately after
+	 * adjusting AVS voltages for VDD_CORE_L.
+	 * Respective boards should call __recalibrate_iodelay()
+	 * with proper mux, virtual and manual mode configurations.
+	 */
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	recalibrate_iodelay();
+#endif
+
 	debug("mpu: %d\n", vcores->mpu.value);
 	do_scale_vcore(vcores->mpu.addr, vcores->mpu.value, vcores->mpu.pmic);
 	/* Configure MPU ABB LDO after scale */
@@ -587,6 +603,16 @@
 	val = optimize_vcore_voltage(&vcores->core);
 	do_scale_vcore(vcores->core.addr, val, vcores->core.pmic);
 
+	/*
+	 * IO delay recalibration should be done immediately after
+	 * adjusting AVS voltages for VDD_CORE_L.
+	 * Respective boards should call __recalibrate_iodelay()
+	 * with proper mux, virtual and manual mode configurations.
+	 */
+#ifdef CONFIG_IODELAY_RECALIBRATION
+	recalibrate_iodelay();
+#endif
+
 	val = optimize_vcore_voltage(&vcores->mpu);
 	do_scale_vcore(vcores->mpu.addr, val, vcores->mpu.pmic);
 
diff --git a/arch/arm/cpu/armv7/omap5/Makefile b/arch/arm/cpu/armv7/omap5/Makefile
index 64c6879..e709f14 100644
--- a/arch/arm/cpu/armv7/omap5/Makefile
+++ b/arch/arm/cpu/armv7/omap5/Makefile
@@ -11,3 +11,4 @@
 obj-y	+= prcm-regs.o
 obj-y	+= hw_data.o
 obj-y	+= abb.o
+obj-$(CONFIG_IODELAY_RECALIBRATION) += dra7xx_iodelay.o
diff --git a/arch/arm/cpu/armv7/omap5/dra7xx_iodelay.c b/arch/arm/cpu/armv7/omap5/dra7xx_iodelay.c
new file mode 100644
index 0000000..4b8ba26
--- /dev/null
+++ b/arch/arm/cpu/armv7/omap5/dra7xx_iodelay.c
@@ -0,0 +1,139 @@
+/*
+ * (C) Copyright 2015
+ * Texas Instruments Incorporated, <www.ti.com>
+ *
+ * Lokesh Vutla <lokeshvutla@ti.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <asm/utils.h>
+#include <asm/arch/dra7xx_iodelay.h>
+#include <asm/arch/omap.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/arch/clock.h>
+#include <asm/omap_common.h>
+
+static int isolate_io(u32 isolate)
+{
+	if (isolate) {
+		clrsetbits_le32((*ctrl)->control_pbias, SDCARD_PWRDNZ,
+				SDCARD_PWRDNZ);
+		clrsetbits_le32((*ctrl)->control_pbias, SDCARD_BIAS_PWRDNZ,
+				SDCARD_BIAS_PWRDNZ);
+	}
+
+	/* Override control on ISOCLKIN signal to IO pad ring. */
+	clrsetbits_le32((*prcm)->prm_io_pmctrl, PMCTRL_ISOCLK_OVERRIDE_MASK,
+			PMCTRL_ISOCLK_OVERRIDE_CTRL);
+	if (!wait_on_value(PMCTRL_ISOCLK_STATUS_MASK, PMCTRL_ISOCLK_STATUS_MASK,
+			   (u32 *)(*prcm)->prm_io_pmctrl, LDELAY))
+		return ERR_DEISOLATE_IO << isolate;
+
+	/* Isolate/Deisolate IO */
+	clrsetbits_le32((*ctrl)->ctrl_core_sma_sw_0, CTRL_ISOLATE_MASK,
+			isolate << CTRL_ISOLATE_SHIFT);
+	/* Dummy read to add delay t > 10ns */
+	readl((*ctrl)->ctrl_core_sma_sw_0);
+
+	/* Return control on ISOCLKIN to hardware */
+	clrsetbits_le32((*prcm)->prm_io_pmctrl, PMCTRL_ISOCLK_OVERRIDE_MASK,
+			PMCTRL_ISOCLK_NOT_OVERRIDE_CTRL);
+	if (!wait_on_value(PMCTRL_ISOCLK_STATUS_MASK,
+			   0 << PMCTRL_ISOCLK_STATUS_SHIFT,
+			   (u32 *)(*prcm)->prm_io_pmctrl, LDELAY))
+		return ERR_DEISOLATE_IO << isolate;
+
+	return 0;
+}
+
+static int calibrate_iodelay(u32 base)
+{
+	u32 reg;
+
+	/* Configure REFCLK period */
+	reg = readl(base + CFG_REG_2_OFFSET);
+	reg &= ~CFG_REG_REFCLK_PERIOD_MASK;
+	reg |= CFG_REG_REFCLK_PERIOD;
+	writel(reg, base + CFG_REG_2_OFFSET);
+
+	/* Initiate Calibration */
+	clrsetbits_le32(base + CFG_REG_0_OFFSET, CFG_REG_CALIB_STRT_MASK,
+			CFG_REG_CALIB_STRT << CFG_REG_CALIB_STRT_SHIFT);
+	if (!wait_on_value(CFG_REG_CALIB_STRT_MASK, CFG_REG_CALIB_END,
+			   (u32 *)(base + CFG_REG_0_OFFSET), LDELAY))
+		return ERR_CALIBRATE_IODELAY;
+
+	return 0;
+}
+
+static int update_delay_mechanism(u32 base)
+{
+	/* Initiate the reload of calibrated values. */
+	clrsetbits_le32(base + CFG_REG_0_OFFSET, CFG_REG_ROM_READ_MASK,
+			CFG_REG_ROM_READ_START);
+	if (!wait_on_value(CFG_REG_ROM_READ_MASK, CFG_REG_ROM_READ_END,
+			   (u32 *)(base + CFG_REG_0_OFFSET), LDELAY))
+		return ERR_UPDATE_DELAY;
+
+	return 0;
+}
+
+void __recalibrate_iodelay(struct pad_conf_entry const *pad, int npads)
+{
+	int ret = 0;
+
+	/* IO recalibration should be done only from SRAM */
+	if (OMAP_INIT_CONTEXT_SPL != omap_hw_init_context()) {
+		puts("IODELAY recalibration called from invalid context - use only from SPL in SRAM\n");
+		return;
+	}
+
+	/* unlock IODELAY CONFIG registers */
+	writel(CFG_IODELAY_UNLOCK_KEY, (*ctrl)->iodelay_config_base +
+	       CFG_REG_8_OFFSET);
+
+	ret = calibrate_iodelay((*ctrl)->iodelay_config_base);
+	if (ret)
+		goto err;
+
+	ret = isolate_io(ISOLATE_IO);
+	if (ret)
+		goto err;
+
+	ret = update_delay_mechanism((*ctrl)->iodelay_config_base);
+	if (ret)
+		goto err;
+
+	/* Configure Mux settings */
+	do_set_mux32((*ctrl)->control_padconf_core_base, pad, npads);
+
+	ret = isolate_io(DEISOLATE_IO);
+
+err:
+	/* lock IODELAY CONFIG registers */
+	writel(CFG_IODELAY_LOCK_KEY, (*ctrl)->iodelay_config_base +
+	       CFG_REG_8_OFFSET);
+	/*
+	 * UART cannot be used during IO recalibration sequence as IOs are in
+	 * isolation. So error handling and debug prints are done after
+	 * complete IO delay recalibration sequence
+	 */
+	switch (ret) {
+	case ERR_CALIBRATE_IODELAY:
+		puts("IODELAY: IO delay calibration sequence failed\n");
+		break;
+	case ERR_ISOLATE_IO:
+		puts("IODELAY: Isolation of Device IOs failed\n");
+		break;
+	case ERR_UPDATE_DELAY:
+		puts("IODELAY: Delay mechanism update with new calibrated values failed\n");
+		break;
+	case ERR_DEISOLATE_IO:
+		puts("IODELAY: De-isolation of Device IOs failed\n");
+		break;
+	default:
+		debug("IODELAY: IO delay recalibration successfully completed\n");
+	}
+}
diff --git a/arch/arm/cpu/armv7/omap5/prcm-regs.c b/arch/arm/cpu/armv7/omap5/prcm-regs.c
index f80d36d..0547037 100644
--- a/arch/arm/cpu/armv7/omap5/prcm-regs.c
+++ b/arch/arm/cpu/armv7/omap5/prcm-regs.c
@@ -378,6 +378,7 @@
 	.control_status				= 0x4A002134,
 	.control_phy_power_usb			= 0x4A002370,
 	.control_phy_power_sata			= 0x4A002374,
+	.ctrl_core_sma_sw_0			= 0x4A0023FC,
 	.control_core_mac_id_0_lo		= 0x4A002514,
 	.control_core_mac_id_0_hi		= 0x4A002518,
 	.control_core_mac_id_1_lo		= 0x4A00251C,
@@ -457,6 +458,7 @@
 	.control_efuse_3			= 0x4AE0C5D0,
 	.control_efuse_4			= 0x4AE0C5D4,
 	.control_efuse_13			= 0x4AE0C5F0,
+	.iodelay_config_base			= 0x4844A000,
 };
 
 struct prcm_regs const omap5_es2_prcm = {
@@ -976,6 +978,7 @@
 	.prm_rstctrl				= 0x4ae07d00,
 	.prm_rstst				= 0x4ae07d04,
 	.prm_rsttime				= 0x4ae07d08,
+	.prm_io_pmctrl				= 0x4ae07d20,
 	.prm_vc_val_bypass			= 0x4ae07da0,
 	.prm_vc_cfg_i2c_mode			= 0x4ae07db4,
 	.prm_vc_cfg_i2c_clk			= 0x4ae07db8,
diff --git a/arch/arm/include/asm/arch-omap5/dra7xx_iodelay.h b/arch/arm/include/asm/arch-omap5/dra7xx_iodelay.h
new file mode 100644
index 0000000..a924629
--- /dev/null
+++ b/arch/arm/include/asm/arch-omap5/dra7xx_iodelay.h
@@ -0,0 +1,58 @@
+/*
+ * (C) Copyright 2015
+ * Texas Instruments Incorporated
+ *
+ * Lokesh Vutla <lokeshvutla@ti.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _DRA7_IODELAY_H_
+#define _DRA7_IODELAY_H_
+
+#include <common.h>
+#include <asm/arch/sys_proto.h>
+
+/* CONFIG_REG_0 */
+#define CFG_REG_0_OFFSET		0xC
+#define CFG_REG_ROM_READ_SHIFT		1
+#define CFG_REG_ROM_READ_MASK		(1 << 1)
+#define CFG_REG_CALIB_STRT_SHIFT	0
+#define CFG_REG_CALIB_STRT_MASK		(1 << 0)
+#define CFG_REG_CALIB_STRT		1
+#define CFG_REG_CALIB_END		0
+#define CFG_REG_ROM_READ_START		(1 << 1)
+#define CFG_REG_ROM_READ_END		(0 << 1)
+
+/* CONFIG_REG_2 */
+#define CFG_REG_2_OFFSET		0x14
+#define CFG_REG_REFCLK_PERIOD_SHIFT	0
+#define CFG_REG_REFCLK_PERIOD_MASK	(0xFFFF << 0)
+#define CFG_REG_REFCLK_PERIOD		0x2EF
+
+/* CONFIG_REG_8 */
+#define CFG_REG_8_OFFSET		0x2C
+#define CFG_IODELAY_UNLOCK_KEY		0x0000AAAA
+#define CFG_IODELAY_LOCK_KEY		0x0000AAAB
+
+/* CTRL_CORE_SMA_SW_0 */
+#define CTRL_ISOLATE_SHIFT		2
+#define CTRL_ISOLATE_MASK		(1 << 2)
+#define ISOLATE_IO			1
+#define DEISOLATE_IO			0
+
+/* PRM_IO_PMCTRL */
+#define PMCTRL_ISOCLK_OVERRIDE_SHIFT	0
+#define PMCTRL_ISOCLK_OVERRIDE_MASK	(1 << 0)
+#define PMCTRL_ISOCLK_STATUS_SHIFT	1
+#define PMCTRL_ISOCLK_STATUS_MASK	(1 << 1)
+#define PMCTRL_ISOCLK_OVERRIDE_CTRL	1
+#define PMCTRL_ISOCLK_NOT_OVERRIDE_CTRL	0
+
+#define ERR_CALIBRATE_IODELAY		0x1
+#define ERR_DEISOLATE_IO		0x2
+#define ERR_ISOLATE_IO			0x4
+#define ERR_UPDATE_DELAY		0x8
+
+void __recalibrate_iodelay(struct pad_conf_entry const *array, int npads);
+#endif
diff --git a/arch/arm/include/asm/omap_common.h b/arch/arm/include/asm/omap_common.h
index 50f178b..12c2207 100644
--- a/arch/arm/include/asm/omap_common.h
+++ b/arch/arm/include/asm/omap_common.h
@@ -313,6 +313,7 @@
 	u32 prm_rstctrl;
 	u32 prm_rstst;
 	u32 prm_rsttime;
+	u32 prm_io_pmctrl;
 	u32 prm_vc_val_bypass;
 	u32 prm_vc_cfg_i2c_mode;
 	u32 prm_vc_cfg_i2c_clk;
@@ -455,6 +456,8 @@
 	u32 control_efuse_12;
 	u32 control_efuse_13;
 	u32 control_padconf_wkup_base;
+	u32 iodelay_config_base;
+	u32 ctrl_core_sma_sw_0;
 };
 
 struct dpll_params {
@@ -583,6 +586,7 @@
 
 void usb_fake_mac_from_die_id(u32 *id);
 void usb_set_serial_num_from_die_id(u32 *id);
+void recalibrate_iodelay(void);
 
 void omap_smc1(u32 service, u32 val);