/* * 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 #include #include #include #include #include #include #include #include /* 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");