diff options
Diffstat (limited to 'arch/arm/plat-mxc/zq_calib.c')
-rw-r--r-- | arch/arm/plat-mxc/zq_calib.c | 436 |
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"); |