summaryrefslogtreecommitdiff
path: root/arch/arm/plat-mxc/zq_calib.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/plat-mxc/zq_calib.c')
-rw-r--r--arch/arm/plat-mxc/zq_calib.c436
1 files changed, 436 insertions, 0 deletions
diff --git a/arch/arm/plat-mxc/zq_calib.c b/arch/arm/plat-mxc/zq_calib.c
new file mode 100644
index 000000000000..8b1b741eb087
--- /dev/null
+++ b/arch/arm/plat-mxc/zq_calib.c
@@ -0,0 +1,436 @@
+/*
+ * 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 <linux/platform_device.h>
+
+#include <mach/hardware.h>
+
+/* 10 secs by default, users can change it via sys */
+static int interval = 10;
+
+static struct device *zq_calib_dev;
+
+static void mxc_zq_main(struct work_struct *dummy);
+
+/* 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
+
+#define DDR_TYPE_LPDDR2 (0x5 << 8)
+static inline bool is_lpddr2(void)
+{
+ u32 v;
+ v = __raw_readl(databahn_base);
+
+ return (v & DDR_TYPE_LPDDR2) == DDR_TYPE_LPDDR2;
+}
+
+/*!
+ * 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+1 & PD+1 value
+ * when pu=0x1F, set (pu+1) = 0x1F
+ */
+ data = ((pd + 1) << 24) | ((pu + 1) << 16);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ /*
+ * set PU & PD value
+ */
+ data = (pd << 8) | pu;
+ __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~0xF */
+ /* when pd=0x0F, set (pd+1) = 0x0F */
+ data = ((pd + 1) << 24) | ((pu + 1) << 16) | (1 << 4);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ data = (pd << 8) | pu;
+ __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 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 pu_pd_sel)
+{
+ u32 data;
+ u32 pu_plus_1, pd_plus_1;
+
+ pu_plus_1 = (pu == 0x1F) ? 0x1F : pu + 1;
+ pd_plus_1 = (pd == 0x0F) ? 0x0F : pd + 1;
+
+ /*
+ * The PU/PD values stored in register
+ * DATABAHN_REG_ZQ_SW_CFG1/2 would be loaded
+ */
+ data = (pd << 8) | pu;
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+
+ data = (pd_plus_1 << 24) | (pu_plus_1 << 16); /* load PD */
+ if (pu_pd_sel)
+ data |= (1 << 4); /* load PU */
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+
+ /*
+ * bit[0]: enable hardware load
+ * bit[4]: trigger a hardware load.
+ *
+ * When the posedge of bit[4] detected, hardware trigger a load.
+ */
+ __raw_writel(0x10011, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /*
+ * Clear the zq_hw_load bit for next loading
+ */
+ __raw_writel(0x10001, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /*
+ * Delay at least 10us waiting an ddr auto-refresh occurs
+ * PU PD value are loaded on auto-refresh event
+ */
+ udelay(10);
+ /*
+ * Clear the calibration_en (bit[16]) to save power consumption
+ */
+ __raw_writel(0x1, 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 + 1) << 24) | ((pu + 1) << 16);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+ data = (pd << 8) | pu;
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG2);
+
+ /* Loading PU value, set pu_pd_sel=0 */
+ __raw_writel((0x3 << 20) | (1 << 16),
+ databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ __raw_writel(0x1 << 21,
+ databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+
+ /* Loading PD value, set pu_pd_sel=1 */
+ data = ((pd + 1) << 24) | ((pu + 1) << 16) | (1 << 4);
+ __raw_writel(data, databahn_base + DATABAHN_REG_ZQ_SW_CFG1);
+
+ /*
+ * bit[21]: select software load
+ * bit[20]: enable software load
+ */
+ __raw_writel((0x3 << 20) | (1 << 16),
+ databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+ /* clear for next load */
+ __raw_writel(0x1 << 21, databahn_base + DATABAHN_REG_ZQ_HW_CFG);
+}
+
+/*
+ * This function is for ZQ pu calibration
+ */
+static u32 pu_calib_based_on_pd(u32 start, u32 pd)
+{
+ 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
+ * Pleae take the First "1" in the sequence for PU
+ */
+ for (i = start; i < 32; i++) {
+ data = mxc_zq_pd_compare(i, pd);
+ if (data) {
+ zq_pu_val = i;
+ break;
+ }
+ }
+
+ return zq_pu_val;
+}
+
+/*!
+ * MXC ZQ interface - PU/PD calib function
+ * This function Do a complete PU/PD calib and loading process.
+ */
+static void mxc_zq_main(struct work_struct *dummy)
+{
+ 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 */
+ if (is_lpddr2()) {
+ pd = mxc_zq_pd_calib(0, pu) + 3;
+ pu = pu_calib_based_on_pd(0, pd);
+ } else
+ pd = mxc_zq_pd_calib(0, pu);
+ dev_dbg(zq_calib_dev, "za_calib: pu = %d, pd = %d\n", pu, pd);
+ mxc_zq_hw_load(pu, pd, 1); /* Load Pu */
+ mxc_zq_hw_load(pu, pd, 0); /* Load 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(interval * 1000));
+}
+
+static ssize_t interval_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", interval);
+}
+
+static ssize_t interval_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val;
+ if (sscanf(buf, "%d", &val) > 0) {
+ interval = val;
+ return count;
+ }
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(interval, 0644, interval_show,
+ interval_store);
+
+static int __devinit mxc_zq_calib_probe(struct platform_device *pdev)
+{
+ int err = 0;
+
+ zq_calib_dev = &pdev->dev;
+ zq_queue = create_singlethread_workqueue("zq_calib");;
+ if (!zq_queue)
+ return -ENOMEM;
+
+ err = device_create_file(&pdev->dev, &dev_attr_interval);
+ if (err) {
+ dev_err(&pdev->dev,
+ "Unable to create file from interval\n");
+ destroy_workqueue(zq_queue);
+ return err;
+ }
+
+ mxc_zq_main(NULL);
+
+ return 0;
+}
+
+static int __devexit mxc_zq_calib_remove(struct platform_device *pdev)
+{
+ cancel_delayed_work(&zq_work);
+ flush_workqueue(zq_queue);
+ destroy_workqueue(zq_queue);
+ device_remove_file(&pdev->dev, &dev_attr_interval);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int zq_calib_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ flush_delayed_work(&zq_work);
+
+ return 0;
+}
+
+static int zq_calib_resume(struct platform_device *pdev)
+{
+ mxc_zq_main(NULL);
+
+ return 0;
+}
+#else
+#define zq_calib_suspend NULL
+#define zq_calib_resume NULL
+#endif
+
+static struct platform_driver mxc_zq_calib_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "mxc_zq_calib",
+ },
+ .probe = mxc_zq_calib_probe,
+ .remove = __exit_p(mxc_zq_calib_remove),
+ .suspend = zq_calib_suspend,
+ .resume = zq_calib_resume,
+};
+
+static int __init mxc_zq_calib_init(void)
+{
+ return platform_driver_register(&mxc_zq_calib_driver);
+}
+
+static void __exit mxc_zq_calib_exit(void)
+{
+ platform_driver_unregister(&mxc_zq_calib_driver);
+}
+
+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");