/* * Freescale On-Chip OTP driver * * Copyright (C) 2010-2011 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsl_otp.h" static DEFINE_MUTEX(otp_mutex); static struct kobject *otp_kobj; static struct attribute **attrs; static struct kobj_attribute *kattr; static struct attribute_group attr_group; static struct mxc_otp_platform_data *otp_data; static struct clk *otp_clk; static inline unsigned int get_reg_index(struct kobj_attribute *tmp) { return tmp - kattr; } static int otp_wait_busy(u32 flags) { int count; u32 c; for (count = 10000; count >= 0; count--) { c = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CTRL); if (!(c & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR | flags))) break; cpu_relax(); } if (count < 0) return -ETIMEDOUT; return 0; } static ssize_t otp_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { unsigned int index = get_reg_index(attr); u32 value = 0; /* sanity check */ if (index >= otp_data->fuse_num) return 0; mutex_lock(&otp_mutex); if (otp_read_prepare(otp_data)) { mutex_unlock(&otp_mutex); return 0; } value = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CUSTn(index)); otp_read_post(otp_data); mutex_unlock(&otp_mutex); return sprintf(buf, "0x%x\n", value); } static int otp_write_bits(int addr, u32 data, u32 magic) { u32 c; /* for control register */ /* init the control register */ c = __raw_readl(REGS_OCOTP_BASE + HW_OCOTP_CTRL); c &= ~BM_OCOTP_CTRL_ADDR; c |= BF(addr, OCOTP_CTRL_ADDR); c |= BF(magic, OCOTP_CTRL_WR_UNLOCK); __raw_writel(c, REGS_OCOTP_BASE + HW_OCOTP_CTRL); /* init the data register */ __raw_writel(data, REGS_OCOTP_BASE + HW_OCOTP_DATA); otp_wait_busy(0); mdelay(2); /* Write Postamble */ return 0; } static ssize_t otp_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { unsigned int index = get_reg_index(attr); int value; /* sanity check */ if (index >= otp_data->fuse_num) return 0; sscanf(buf, "0x%x", &value); mutex_lock(&otp_mutex); if (otp_write_prepare(otp_data)) { mutex_unlock(&otp_mutex); return 0; } otp_write_bits(index, value, 0x3e77); otp_write_post(otp_data); mutex_unlock(&otp_mutex); return count; } static void free_otp_attr(void) { kfree(attrs); attrs = NULL; kfree(kattr); kattr = NULL; } static int __init alloc_otp_attr(struct mxc_otp_platform_data *pdata) { int i; otp_data = pdata; /* get private data */ /* The last one is NULL, which is used to detect the end */ attrs = kzalloc((otp_data->fuse_num + 1) * sizeof(attrs[0]), GFP_KERNEL); kattr = kzalloc(otp_data->fuse_num * sizeof(struct kobj_attribute), GFP_KERNEL); if (!attrs || !kattr) goto error_out; for (i = 0; i < otp_data->fuse_num; i++) { kattr[i].attr.name = pdata->fuse_name[i]; kattr[i].attr.mode = 0600; kattr[i].show = otp_show; kattr[i].store = otp_store; attrs[i] = &kattr[i].attr; } memset(&attr_group, 0, sizeof(attr_group)); attr_group.attrs = attrs; return 0; error_out: free_otp_attr(); return -ENOMEM; } static int __devinit fsl_otp_probe(struct platform_device *pdev) { int retval; struct mxc_otp_platform_data *pdata; pdata = pdev->dev.platform_data; if (pdata == NULL) return -ENODEV; /* Enable clock */ if (pdata->clock_name) { otp_clk = clk_get(&pdev->dev, pdata->clock_name); clk_enable(otp_clk); } retval = alloc_otp_attr(pdata); if (retval) return retval; retval = map_ocotp_addr(pdev); if (retval) goto error; otp_kobj = kobject_create_and_add("fsl_otp", NULL); if (!otp_kobj) { retval = -ENOMEM; goto error; } retval = sysfs_create_group(otp_kobj, &attr_group); if (retval) goto error; mutex_init(&otp_mutex); return 0; error: kobject_put(otp_kobj); otp_kobj = NULL; free_otp_attr(); unmap_ocotp_addr(); return retval; } static int otp_remove(struct platform_device *pdev) { struct mxc_otp_platform_data *pdata; pdata = pdev->dev.platform_data; if (pdata == NULL) return -ENODEV; kobject_put(otp_kobj); otp_kobj = NULL; free_otp_attr(); unmap_ocotp_addr(); if (pdata->clock_name) { clk_disable(otp_clk); clk_put(otp_clk); } return 0; } static struct platform_driver ocotp_driver = { .probe = fsl_otp_probe, .remove = otp_remove, .driver = { .name = "imx-ocotp", .owner = THIS_MODULE, }, }; static int __init fsl_otp_init(void) { return platform_driver_register(&ocotp_driver); } static void __exit fsl_otp_exit(void) { platform_driver_unregister(&ocotp_driver); } module_init(fsl_otp_init); module_exit(fsl_otp_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Huang Shijie "); MODULE_DESCRIPTION("Common driver for OTP controller");