blob: 4d1429a95e818593faf8f93829bb4d607d80e67f [file] [log] [blame]
/*
* Copyright (C) 2009-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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/fsl_devices.h>
#include "mxc_iim.h"
static struct mxc_iim_platform_data *iim_data;
#ifdef MXC_IIM_DEBUG
static inline void dump_reg(void)
{
struct iim_regs *iim_reg_base =
(struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "stat: 0x%08x\n",
__raw_readl(&iim_reg_base->stat));
dev_dbg(iim_data->dev, "statm: 0x%08x\n",
__raw_readl(&iim_reg_base->statm));
dev_dbg(iim_data->dev, "err: 0x%08x\n",
__raw_readl(&iim_reg_base->err));
dev_dbg(iim_data->dev, "emask: 0x%08x\n",
__raw_readl(&iim_reg_base->emask));
dev_dbg(iim_data->dev, "fctl: 0x%08x\n",
__raw_readl(&iim_reg_base->fctl));
dev_dbg(iim_data->dev, "ua: 0x%08x\n",
__raw_readl(&iim_reg_base->ua));
dev_dbg(iim_data->dev, "la: 0x%08x\n",
__raw_readl(&iim_reg_base->la));
dev_dbg(iim_data->dev, "sdat: 0x%08x\n",
__raw_readl(&iim_reg_base->sdat));
dev_dbg(iim_data->dev, "prev: 0x%08x\n",
__raw_readl(&iim_reg_base->prev));
dev_dbg(iim_data->dev, "srev: 0x%08x\n",
__raw_readl(&iim_reg_base->srev));
dev_dbg(iim_data->dev, "preg_p: 0x%08x\n",
__raw_readl(&iim_reg_base->preg_p));
dev_dbg(iim_data->dev, "scs0: 0x%08x\n",
__raw_readl(&iim_reg_base->scs0));
dev_dbg(iim_data->dev, "scs1: 0x%08x\n",
__raw_readl(&iim_reg_base->scs1));
dev_dbg(iim_data->dev, "scs2: 0x%08x\n",
__raw_readl(&iim_reg_base->scs2));
dev_dbg(iim_data->dev, "scs3: 0x%08x\n",
__raw_readl(&iim_reg_base->scs3));
}
#endif
static inline void mxc_iim_disable_irq(void)
{
struct iim_regs *iim_reg_base = (struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
__raw_writel(0x0, &(iim_reg_base->statm));
__raw_writel(0x0, &(iim_reg_base->emask));
dev_dbg(iim_data->dev, "<= %s\n", __func__);
}
static inline void fuse_op_start(void)
{
struct iim_regs *iim_reg_base = (struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
#ifdef MXC_IIM_DEBUG
dump_reg();
#endif
/* Clear the status bits and error bits */
__raw_writel(0x3, &(iim_reg_base->stat));
__raw_writel(0xfe, &(iim_reg_base->err));
/* Generate interrupt */
__raw_writel(0x3, &(iim_reg_base->statm));
__raw_writel(0xfe, &(iim_reg_base->emask));
dev_dbg(iim_data->dev, "<= %s\n", __func__);
}
static u32 sense_fuse(u32 bank, u32 row, u32 bit)
{
u32 addr, addr_l, addr_h;
s32 err = 0;
struct iim_regs *iim_reg_base = (struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
init_completion(&(iim_data->completion));
iim_data->action = POLL_FUSE_SNSD;
fuse_op_start();
addr = ((bank << 11) | (row << 3) | (bit & 0x7));
/* Set IIM Program Upper Address */
addr_h = (addr >> 8) & 0x000000FF;
/* Set IIM Program Lower Address */
addr_l = (addr & 0x000000FF);
dev_dbg(iim_data->dev, "%s: addr_h=0x%x, addr_l=0x%x\n",
__func__, addr_h, addr_l);
__raw_writel(addr_h, &(iim_reg_base->ua));
__raw_writel(addr_l, &(iim_reg_base->la));
/* Start sensing */
#ifdef MXC_IIM_DEBUG
dump_reg();
#endif
__raw_writel(0x8, &(iim_reg_base->fctl));
err = wait_for_completion_timeout(&(iim_data->completion),
msecs_to_jiffies(1000));
err = (!err) ? -ETIMEDOUT : 0;
if (err)
dev_dbg(iim_data->dev, "Sense timeout!");
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return __raw_readl(&(iim_reg_base->sdat));
}
/* Blow fuses based on the bank, row and bit positions (all 0-based)
*/
static s32 fuse_blow_bit(u32 bank, u32 row, u32 bit)
{
int addr, addr_l, addr_h, err;
struct iim_regs *iim_reg_base = (struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
init_completion(&iim_data->completion);
iim_data->action = POLL_FUSE_PRGD;
fuse_op_start();
/* Disable IIM Program Protect */
__raw_writel(0xaa, &(iim_reg_base->preg_p));
addr = ((bank << 11) | (row << 3) | (bit & 0x7));
/* Set IIM Program Upper Address */
addr_h = (addr >> 8) & 0x000000FF;
/* Set IIM Program Lower Address */
addr_l = (addr & 0x000000FF);
dev_dbg(iim_data->dev, "blowing addr_h=0x%x, addr_l=0x%x\n",
addr_h, addr_l);
__raw_writel(addr_h, &(iim_reg_base->ua));
__raw_writel(addr_l, &(iim_reg_base->la));
/* Start Programming */
#ifdef MXC_IIM_DEBUG
dump_reg();
#endif
__raw_writel(0x31, &(iim_reg_base->fctl));
err = wait_for_completion_timeout(&(iim_data->completion),
msecs_to_jiffies(1000));
err = (!err) ? -ETIMEDOUT : 0;
if (err)
dev_dbg(iim_data->dev, "Fuse timeout!\n");
/* Enable IIM Program Protect */
__raw_writel(0x0, &(iim_reg_base->preg_p));
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return err;
}
static void fuse_blow_row(u32 bank, u32 row, u32 value)
{
u32 i;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
/* Enable fuse blown */
if (iim_data->enable_fuse)
iim_data->enable_fuse();
for (i = 0; i < 8; i++) {
if (((value >> i) & 0x1) == 0)
continue;
if (fuse_blow_bit(bank, row, i) != 0) {
dev_dbg(iim_data->dev,
"fuse_blow_bit(bank: %d, row: %d, "
"bit: %d failed\n",
bank, row, i);
}
}
if (iim_data->disable_fuse)
iim_data->disable_fuse();
dev_dbg(iim_data->dev, "<= %s\n", __func__);
}
static irqreturn_t mxc_iim_irq(int irq, void *dev_id)
{
u32 status, error, rtn = 0;
ulong flags;
struct iim_regs *iim_reg_base = (struct iim_regs *)iim_data->virt_base;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
spin_lock_irqsave(&iim_data->lock, flags);
mxc_iim_disable_irq();
#ifdef MXC_IIM_DEBUG
dump_reg();
#endif
if (iim_data->action != POLL_FUSE_PRGD &&
iim_data->action != POLL_FUSE_SNSD) {
dev_dbg(iim_data->dev, "%s(%d) invalid operation\n",
__func__, iim_data->action);
rtn = 1;
goto out;
}
/* Test for successful write */
status = readl(&(iim_reg_base->stat));
error = readl(&(iim_reg_base->err));
if ((status & iim_data->action) != 0 && \
(error & (iim_data->action >> IIM_ERR_SHIFT)) == 0) {
if (error) {
printk(KERN_NOTICE "Even though the operation"
"seems successful...\n");
printk(KERN_NOTICE "There are some error(s) "
"at addr=0x%x: 0x%x\n",
(u32)&(iim_reg_base->err), error);
}
__raw_writel(0x3, &(iim_reg_base->stat));
rtn = 0;
goto out;
}
printk(KERN_NOTICE "%s(%d) failed\n", __func__, iim_data->action);
printk(KERN_NOTICE "status address=0x%x, value=0x%x\n",
(u32)&(iim_reg_base->stat), status);
printk(KERN_NOTICE "There are some error(s) at addr=0x%x: 0x%x\n",
(u32)&(iim_reg_base->err), error);
#ifdef MXC_IIM_DEBUG
dump_reg();
#endif
out:
complete(&(iim_data->completion));
spin_unlock_irqrestore(&iim_data->lock, flags);
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return IRQ_RETVAL(rtn);
}
static loff_t mxc_iim_llseek(struct file *filp, loff_t off, int whence)
{
loff_t newpos;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
dev_dbg(iim_data->dev, "off: %lld, whence: %d\n", off, whence);
if (off < iim_data->bank_start ||
off > iim_data->bank_end) {
dev_dbg(iim_data->dev, "invalid seek offset: %lld\n", off);
goto invald_arg_out;
}
switch (whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = iim_data->bank_end + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
dev_dbg(iim_data->dev, "newpos: %lld\n", newpos);
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return newpos;
invald_arg_out:
return -EINVAL;
}
static ssize_t mxc_iim_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
u32 bank, row, fuse_val;
ssize_t retval = 0;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
dev_dbg(iim_data->dev, "count: %d f_pos: %lld\n", count, *f_pos);
if (1 != count)
goto invald_arg_out;
if (*f_pos & 0x3)
goto invald_arg_out;
if (*f_pos < iim_data->bank_start ||
*f_pos > iim_data->bank_end) {
dev_dbg(iim_data->dev, "bank_start: 0x%08x, bank_end: 0x%08x\n",
iim_data->bank_start, iim_data->bank_end);
goto out;
}
bank = (*f_pos - iim_data->bank_start) >> 10;
row = ((*f_pos - iim_data->bank_start) & 0x3ff) >> 2;
dev_dbg(iim_data->dev, "Read fuse at bank:%d row:%d\n",
bank, row);
mutex_lock(&iim_data->mutex);
fuse_val = sense_fuse(bank, row, 0);
mutex_unlock(&iim_data->mutex);
dev_info(iim_data->dev, "fuses at (bank:%d, row:%d) = 0x%x\n",
bank, row, fuse_val);
if (copy_to_user(buf, &fuse_val, count)) {
retval = -EFAULT;
goto out;
}
out:
retval = count;
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return retval;
invald_arg_out:
retval = -EINVAL;
return retval;
}
static ssize_t mxc_iim_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
u32 bank;
ulong fuse_val;
u8 row;
u8 *tmp_buf = NULL;
loff_t file_pos = *f_pos;
ssize_t retval = 0;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
dev_dbg(iim_data->dev, "count: %d f_pos: %lld\n", count, file_pos);
tmp_buf = kmalloc(count + 1, GFP_KERNEL);
if (unlikely(!tmp_buf)) {
retval = -ENOMEM;
goto out;
}
memset(tmp_buf, 0, count + 1);
if (copy_from_user(tmp_buf, buf, count)) {
retval = -EFAULT;
goto out;
}
if (count > 1 && 0 == file_pos) {
/* Check if file_pos and fuse val are in tmp_buf */
if (sscanf(tmp_buf, "%x %x",
(s32 *)&file_pos, (s32 *)&fuse_val) < 0) {
dev_info(iim_data->dev,
"Invalid input string: %s\n", tmp_buf);
retval = -EINVAL;
goto out;
}
dev_dbg(iim_data->dev,
"file_pos: 0x%08x, fuse_val: 0x%08x\n",
(s32)file_pos, (s32)fuse_val);
} else if (1 == count)
fuse_val = tmp_buf[0];
else {
dev_info(iim_data->dev,
"Invalid input: %s, count: %d, file pos: %d\n",
tmp_buf, count, (s32)file_pos);
retval = -EINVAL;
goto out;
}
if (fuse_val > 0xff) {
dev_info(iim_data->dev,
"Invalid blow value: 0x%08x\n", (s32)fuse_val);
retval = -EINVAL;
goto out;
}
if (file_pos & 0x3) {
dev_info(iim_data->dev, "file pos is not 4-byte aligned!\n");
retval = -EINVAL;
goto out;
}
if (file_pos < iim_data->bank_start ||
file_pos > iim_data->bank_end) {
dev_dbg(iim_data->dev,
"bank_start: 0x%08x, bank_end: 0x%08x\n",
iim_data->bank_start, iim_data->bank_end);
dev_info(iim_data->dev,
"file pos out of range: 0x%08x\n", (u32)file_pos);
retval = -EINVAL;
goto out;
}
bank = (file_pos - iim_data->bank_start) >> 10;
row = ((file_pos - iim_data->bank_start) & 0x3ff) >> 2;
dev_info(iim_data->dev, "Blowing fuse at bank:%d row:%d value:%d\n",
bank, row, (int)fuse_val);
mutex_lock(&iim_data->mutex);
fuse_blow_row(bank, row, fuse_val);
fuse_val = sense_fuse(bank, row, 0);
mutex_unlock(&iim_data->mutex);
dev_info(iim_data->dev, "fuses at (bank:%d, row:%d) = 0x%x\n",
bank, row, (unsigned int)fuse_val);
retval = count;
out:
if (tmp_buf)
kfree(tmp_buf);
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return retval;
}
/*!
* MXC IIM interface - memory map function
* This function maps IIM registers from IIM base address.
*
* @param file struct file *
* @param vma structure vm_area_struct *
*
* @return Return 0 on success or negative error code on error
*/
static int mxc_iim_mmap(struct file *file, struct vm_area_struct *vma)
{
dev_dbg(iim_data->dev, "=> %s\n", __func__);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
if (remap_pfn_range(vma,
vma->vm_start,
iim_data->reg_base >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot))
return -EAGAIN;
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return 0;
}
/*!
* MXC IIM interface - open function
*
* @param inode struct inode *
* @param filp struct file *
*
* @return Return 0 on success or negative error code on error
*/
static int mxc_iim_open(struct inode *inode, struct file *filp)
{
dev_dbg(iim_data->dev, "=> %s\n", __func__);
dev_dbg(iim_data->dev, "iim_data addr: 0x%08x\n", (u32)iim_data);
iim_data->clk = clk_get(NULL, "iim_clk");
if (IS_ERR(iim_data->clk)) {
dev_err(iim_data->dev, "No IIM clock defined\n");
return -ENODEV;
}
clk_enable(iim_data->clk);
mxc_iim_disable_irq();
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return 0;
}
/*!
* MXC IIM interface - release function
*
* @param inode struct inode *
* @param filp struct file *
*
* @return Return 0 on success or negative error code on error
*/
static int mxc_iim_release(struct inode *inode, struct file *filp)
{
clk_disable(iim_data->clk);
clk_put(iim_data->clk);
return 0;
}
static const struct file_operations mxc_iim_fops = {
.read = mxc_iim_read,
.write = mxc_iim_write,
.llseek = mxc_iim_llseek,
.mmap = mxc_iim_mmap,
.open = mxc_iim_open,
.release = mxc_iim_release,
};
static struct miscdevice mxc_iim_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mxc_iim",
.fops = &mxc_iim_fops,
};
/*!
* This function is called by the driver framework to get iim base/end address
* and register iim misc device.
*
* @param dev The device structure for IIM passed in by the driver
* framework.
*
* @return Returns 0 on success or negative error code on error
*/
static __devinit int mxc_iim_probe(struct platform_device *pdev)
{
struct resource *res;
int ret;
iim_data = pdev->dev.platform_data;
iim_data->dev = &pdev->dev;
iim_data->name = mxc_iim_miscdev.name;
dev_dbg(iim_data->dev, "=> %s\n", __func__);
dev_dbg(iim_data->dev, "iim_data addr: 0x%08x "
"bank_start: 0x%04x bank_end: 0x%04x "
"enable_fuse: 0x%08x disable_fuse: 0x%08x\n",
(u32)iim_data,
iim_data->bank_start, iim_data->bank_end,
(u32)iim_data->enable_fuse,
(u32)iim_data->disable_fuse);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (IS_ERR(res)) {
dev_err(iim_data->dev, "Unable to get IIM resource\n");
return -ENODEV;
}
iim_data->irq = platform_get_irq(pdev, 0);
dev_dbg(iim_data->dev, "irq number: %d\n", iim_data->irq);
if (!iim_data->irq) {
ret = -ENOMEM;
return ret;
}
ret = request_irq(iim_data->irq, mxc_iim_irq, IRQF_DISABLED,
iim_data->name, iim_data);
if (ret)
return ret;
iim_data->reg_base = res->start;
iim_data->reg_end = res->end;
iim_data->reg_size =
iim_data->reg_end - iim_data->reg_base + 1;
iim_data->virt_base =
(u32)ioremap(iim_data->reg_base, iim_data->reg_size);
mutex_init(&(iim_data->mutex));
spin_lock_init(&(iim_data->lock));
ret = misc_register(&mxc_iim_miscdev);
if (ret)
return ret;
dev_dbg(iim_data->dev, "<= %s\n", __func__);
return 0;
}
static int __devexit mxc_iim_remove(struct platform_device *pdev)
{
free_irq(iim_data->irq, iim_data);
iounmap((void *)iim_data->virt_base);
misc_deregister(&mxc_iim_miscdev);
return 0;
}
static struct platform_driver mxc_iim_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "mxc_iim",
},
.probe = mxc_iim_probe,
.remove = mxc_iim_remove,
};
static int __init mxc_iim_dev_init(void)
{
return platform_driver_register(&mxc_iim_driver);
}
static void __exit mxc_iim_dev_cleanup(void)
{
platform_driver_unregister(&mxc_iim_driver);
}
module_init(mxc_iim_dev_init);
module_exit(mxc_iim_dev_cleanup);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC IIM driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(MISC_DYNAMIC_MINOR);