summaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
authorRobby Cai <R63905@freescale.com>2010-11-08 14:02:12 +0800
committerLily Zhang <r58066@freescale.com>2010-11-22 09:39:39 +0800
commit1fa53af20dfd251d1c3368c3ef44260c138fc83a (patch)
treeea79bd341a258f978c977ccbdc05303bcce5301e /arch
parent7b109c262ee859d4790edfec8139534a91acbb33 (diff)
ENGR00132712-1: Add ZQ calibration support
Add ZQ calibration support Signed-off-by: Terry Lv <r65388@freescale.com>
Diffstat (limited to 'arch')
-rw-r--r--arch/arm/plat-mxc/Kconfig6
-rw-r--r--arch/arm/plat-mxc/Makefile3
-rw-r--r--arch/arm/plat-mxc/include/mach/mx5x.h4
-rw-r--r--arch/arm/plat-mxc/zq_calib.c285
4 files changed, 298 insertions, 0 deletions
diff --git a/arch/arm/plat-mxc/Kconfig b/arch/arm/plat-mxc/Kconfig
index 27e8dd05e9c6..926379f3f99f 100644
--- a/arch/arm/plat-mxc/Kconfig
+++ b/arch/arm/plat-mxc/Kconfig
@@ -207,4 +207,10 @@ config MXC_DVFS_PER
help
Select this if you want to enable HW supported peripheral frequency scaling.
+config MXC_ZQ_CALIBRATION
+ bool "Enable mDDR/LPDDR2/DDR2 ZQ calibration"
+ depends on ARCH_MX50
+ help
+ Select this if you're sure it's needed.
+
endif
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile
index 0c07107304af..c935fd46741b 100644
--- a/arch/arm/plat-mxc/Makefile
+++ b/arch/arm/plat-mxc/Makefile
@@ -49,4 +49,7 @@ obj-$(CONFIG_MC13783_MXC) += mc13783_xc.o
obj-$(CONFIG_UTMI_MXC) += utmixc.o
obj-$(CONFIG_USB) += serialxc.o
+# ZQ calibration
+obj-$(CONFIG_MXC_ZQ_CALIBRATION) += zq_calib.o
+
obj-y += devices/
diff --git a/arch/arm/plat-mxc/include/mach/mx5x.h b/arch/arm/plat-mxc/include/mach/mx5x.h
index f407b024eaaf..8d053e0a43f4 100644
--- a/arch/arm/plat-mxc/include/mach/mx5x.h
+++ b/arch/arm/plat-mxc/include/mach/mx5x.h
@@ -151,6 +151,10 @@
#define DATABAHN_CTL_REG43 0xac
#define DATABAHN_CTL_REG55 0xdc
#define DATABAHN_CTL_REG63 0xFC
+#define DATABAHN_CTL_REG73 0x124
+#define DATABAHN_CTL_REG74 0x128
+#define DATABAHN_CTL_REG75 0x12C
+#define DATABAHN_CTL_REG83 0x14C
#define LOWPOWER_CONTROL_MASK 0x1F
#define LOWPOWER_AUTOENABLE_MASK 0x1F
#define LOWPOWER_EXTERNAL_CNT_MASK (0xFFFF << 16)
diff --git a/arch/arm/plat-mxc/zq_calib.c b/arch/arm/plat-mxc/zq_calib.c
new file mode 100644
index 000000000000..3f7f19f7a3b0
--- /dev/null
+++ b/arch/arm/plat-mxc/zq_calib.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2010 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+
+#include <mach/hardware.h>
+
+/* 5 mins */
+#define ZQ_INTERVAL (5 * 60 * 1000)
+
+static void mxc_zq_main(void);
+
+/* Use workqueue */
+static struct workqueue_struct *zq_queue;
+static DEFINE_SPINLOCK(zq_lock);
+static DECLARE_DELAYED_WORK(zq_work, mxc_zq_main);
+
+extern void __iomem *databahn_base;
+
+#define DATABAHN_REG_ZQ_HW_CFG DATABAHN_CTL_REG73
+#define DATABAHN_REG_ZQ_SW_CFG1 DATABAHN_CTL_REG74
+#define DATABAHN_REG_ZQ_SW_CFG2 DATABAHN_CTL_REG75
+#define DATABAHN_REG_ZQ_STATUS DATABAHN_CTL_REG83
+
+/*!
+ * MXC ZQ interface - Compare PU vs the External Resistor (240/300 ohm)
+ *
+ * @param pu u32
+ * @param pd u32
+ *
+ * @return Return compare result.
+ */
+static u32 mxc_zq_pu_compare(u32 pu, u32 pd)
+{
+ u32 data;
+
+ /* set PU & PD value */
+ data = (pd << 24) | (pu << 16);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ /*
+ * set PU+1 & PD+1 value
+ * when pu=0x1F, set (pu+1) = 0x1F
+ */
+ data = ((pd + 1) << 8) | (pu + 1);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+ /*
+ * Enable the ZQ comparator,
+ * need 300ns to complete a ZQ comparison
+ */
+ __raw_writel(1 << 16, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /* TODO: wait 300ns till comparator output stable */
+ ndelay(300);
+ /* read status bit[0] */
+ data = __raw_readl(databahn_base + DATABAHN_REG_ZQ_STATUS);
+ data &= 0x1;
+ /* Disable the ZQ comparator to save power */
+ __raw_writel(0, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ return data;
+}
+
+/*!
+ * MXC ZQ interface - Compare PU vs PD
+ *
+ * @param pu u32
+ * @param pd u32
+ *
+ * @return Return compare result.
+ */
+static u32 mxc_zq_pd_compare(u32 pu, u32 pd)
+{
+ u32 data;
+
+ /* set bit[4]=1, select PU/PD comparison */
+ /* PD range: 0~0xE (0xF has problem, drop it) */
+ data = (pd << 24) | (pu << 16) | (1 << 4);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ /* when pu=0x1F, set (pu+1) = 0x1F */
+ data = ((pd + 1) << 8) | (pu + 1);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+ /*
+ * Enable the ZQ comparator,
+ * need 300ns to complete a ZQ comparison
+ */
+ __raw_writel(1 << 16, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /* TODO: wait 300ns till comparator output stable */
+ ndelay(300);
+ /* read status bit[0] */
+ data = __raw_readl(databahn_base + DATABAHN_REG_ZQ_STATUS);
+ data &= 0x1;
+ /* Disable the ZQ comparator to save power */
+ __raw_writel(0, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ return data;
+}
+
+/*!
+ * MXC ZQ interface - Do a full range search of PU to
+ * match the external resistor
+ *
+ * @param start u32
+ *
+ * @return Return pu.
+ */
+static u32 mxc_zq_pu_calib(u32 start)
+{
+ u32 i;
+ u32 data;
+ u32 zq_pu_val = 0;
+
+ /*
+ * Compare PU from 0 to 0x1F
+ * data is the result of the comparator
+ * the result sequence looks like:
+ * 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
+ * Take the First "1" the sequence for PU
+ */
+ for (i = start; i < 32; ++i) {
+ data = mxc_zq_pu_compare(i, 0);
+ if (data) {
+ zq_pu_val = i;
+ break;
+ }
+ }
+
+ return zq_pu_val;
+}
+
+/*!
+ * MXC ZQ interface - Do a full range search of PD to match
+ * the PU get from za_pu_calib()
+ *
+ * @param start u32
+ * @param pu u32
+ *
+ * @return Return pd.
+ */
+static s32 mxc_zq_pd_calib(u32 start, u32 pu)
+{
+ u32 i;
+ u32 data;
+ u32 zq_pd_val = 0;
+
+ /*
+ * Compare PD from 0 to 0x0E (please ignore 0x0F)
+ * data is the result of the comparator
+ * the result sequence looks like:
+ * 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0
+ * Take the Last "1" in the sequence for PD
+ */
+ for (i = start; i < 15; ++i) {
+ data = mxc_zq_pd_compare(pu, i);
+ if (!data && (i > 0)) {
+ zq_pd_val = i - 1;
+ break;
+ }
+ }
+
+ return zq_pd_val;
+}
+
+/*!
+ * MXC ZQ interface - Load the PU/PD value to the ZQ buffers by hardware
+ *
+ * @param pu u32
+ * @param pd u32
+ */
+static void mxc_zq_hw_load(u32 pu, u32 pd)
+{
+ u32 data;
+ /*
+ * The PU/PD values stored in register
+ * DATABAHN_REG_ZQ_SW_CFG1/2 would be loaded
+ */
+ data = (pd << 24) | (pu << 16);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ data = ((pd + 1) << 8) | (pu + 1);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+
+ /*
+ * bit[0]: enable hardware load
+ * bit[4]: trigger a hardware load.
+ *
+ * When the posedge of bit[4] detected, hardware trigger a load.
+ */
+ __raw_writel(0x11, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /* clear bit[4] for next load */
+ __raw_writel(0x01, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+}
+
+/*!
+ * MXC ZQ interface - Load the PU/PD value to the ZQ buffers by software
+ *
+ * @param pu u32
+ * @param pd u32
+ */
+
+static void mxc_zq_sw_load(u32 pu, u32 pd)
+{
+ u32 data;
+
+ /*
+ * The PU/PD values stored in register
+ * DATABAHN_REG_ZQ_SW_CFG1/2 would be loaded.
+ * */
+ data = (pd << 24) | (pu << 16);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ data = ((pd + 1) << 8) | (pu + 1);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+
+ /*
+ * bit[21]: select software load
+ * bit[20]: enable software load
+ */
+ __raw_writel(0x3 << 20, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /* clear for next load */
+ __raw_writel(0x0, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+}
+
+/*!
+ * MXC ZQ interface - PU/PD calib function
+ * This function Do a complete PU/PD calib and loading process.
+ */
+static void mxc_zq_main(void)
+{
+ u32 pu, pd;
+
+ spin_lock(&zq_lock);
+ /* Search pu value start from 0 */
+ pu = mxc_zq_pu_calib(0);
+ /* Search pd value start from 0 */
+ pd = mxc_zq_pd_calib(0, pu);
+ mxc_zq_hw_load(pu, pd);
+ /* or do software load alternatively */
+ /* zq_sw_load(pu, pd); */
+ spin_unlock(&zq_lock);
+
+ queue_delayed_work(zq_queue, &zq_work, msecs_to_jiffies(ZQ_INTERVAL));
+}
+
+static int __init mxc_zq_calib_init(void)
+{
+ zq_queue = create_singlethread_workqueue("zq_calib");;
+ if (!zq_queue)
+ return -ENOMEM;
+
+ mxc_zq_main();
+
+ return 0;
+}
+
+static void __exit mxc_zq_calib_exit(void)
+{
+ cancel_delayed_work(&zq_work);
+ flush_workqueue(zq_queue);
+ destroy_workqueue(zq_queue);
+}
+
+module_init(mxc_zq_calib_init);
+module_exit(mxc_zq_calib_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC ZQ Calibration driver");
+MODULE_LICENSE("GPL");