blob: 05ad55e9d6a9a11bd907117be3756a59fc3f6fd1 [file] [log] [blame]
/*
* 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 <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/fcntl.h>
#include <linux/mutex.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/fsl_devices.h>
#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 <b32955@freescale.com>");
MODULE_DESCRIPTION("Common driver for OTP controller");