1
linux/drivers/char/bfin-otp.c
Mike Frysinger 156dd635e2 bfin-otp: add writing support
The on-chip OTP may be written at runtime, so enable support for it in the
driver.  However, since writing should really be done only on development
systems, don't bend over backwards to make sure the simple software lock
is per-fd -- per-device is OK.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-09-24 07:21:03 -07:00

275 lines
5.9 KiB
C

/*
* Blackfin On-Chip OTP Memory Interface
*
* Copyright 2007-2009 Analog Devices Inc.
*
* Enter bugs at http://blackfin.uclinux.org/
*
* Licensed under the GPL-2 or later.
*/
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <mtd/mtd-abi.h>
#include <asm/blackfin.h>
#include <asm/bfrom.h>
#include <asm/uaccess.h>
#define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args)
#define stampit() stamp("here i am")
#define pr_init(fmt, args...) ({ static const __initconst char __fmt[] = fmt; printk(__fmt, ## args); })
#define DRIVER_NAME "bfin-otp"
#define PFX DRIVER_NAME ": "
static DEFINE_MUTEX(bfin_otp_lock);
/**
* bfin_otp_read - Read OTP pages
*
* All reads must be in half page chunks (half page == 64 bits).
*/
static ssize_t bfin_otp_read(struct file *file, char __user *buff, size_t count, loff_t *pos)
{
ssize_t bytes_done;
u32 page, flags, ret;
u64 content;
stampit();
if (count % sizeof(u64))
return -EMSGSIZE;
if (mutex_lock_interruptible(&bfin_otp_lock))
return -ERESTARTSYS;
bytes_done = 0;
page = *pos / (sizeof(u64) * 2);
while (bytes_done < count) {
flags = (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
stamp("processing page %i (0x%x:%s)", page, flags,
(flags & OTP_UPPER_HALF ? "upper" : "lower"));
ret = bfrom_OtpRead(page, flags, &content);
if (ret & OTP_MASTER_ERROR) {
stamp("error from otp: 0x%x", ret);
bytes_done = -EIO;
break;
}
if (copy_to_user(buff + bytes_done, &content, sizeof(content))) {
bytes_done = -EFAULT;
break;
}
if (flags & OTP_UPPER_HALF)
++page;
bytes_done += sizeof(content);
*pos += sizeof(content);
}
mutex_unlock(&bfin_otp_lock);
return bytes_done;
}
#ifdef CONFIG_BFIN_OTP_WRITE_ENABLE
static bool allow_writes;
/**
* bfin_otp_init_timing - setup OTP timing parameters
*
* Required before doing any write operation. Algorithms from HRM.
*/
static u32 bfin_otp_init_timing(void)
{
u32 tp1, tp2, tp3, timing;
tp1 = get_sclk() / 1000000;
tp2 = (2 * get_sclk() / 10000000) << 8;
tp3 = (0x1401) << 15;
timing = tp1 | tp2 | tp3;
if (bfrom_OtpCommand(OTP_INIT, timing))
return 0;
return timing;
}
/**
* bfin_otp_deinit_timing - set timings to only allow reads
*
* Should be called after all writes are done.
*/
static void bfin_otp_deinit_timing(u32 timing)
{
/* mask bits [31:15] so that any attempts to write fail */
bfrom_OtpCommand(OTP_CLOSE, 0);
bfrom_OtpCommand(OTP_INIT, timing & ~(-1 << 15));
bfrom_OtpCommand(OTP_CLOSE, 0);
}
/**
* bfin_otp_write - write OTP pages
*
* All writes must be in half page chunks (half page == 64 bits).
*/
static ssize_t bfin_otp_write(struct file *filp, const char __user *buff, size_t count, loff_t *pos)
{
ssize_t bytes_done;
u32 timing, page, base_flags, flags, ret;
u64 content;
if (!allow_writes)
return -EACCES;
if (count % sizeof(u64))
return -EMSGSIZE;
if (mutex_lock_interruptible(&bfin_otp_lock))
return -ERESTARTSYS;
stampit();
timing = bfin_otp_init_timing();
if (timing == 0) {
mutex_unlock(&bfin_otp_lock);
return -EIO;
}
base_flags = OTP_CHECK_FOR_PREV_WRITE;
bytes_done = 0;
page = *pos / (sizeof(u64) * 2);
while (bytes_done < count) {
flags = base_flags | (*pos % (sizeof(u64) * 2) ? OTP_UPPER_HALF : OTP_LOWER_HALF);
stamp("processing page %i (0x%x:%s) from %p", page, flags,
(flags & OTP_UPPER_HALF ? "upper" : "lower"), buff + bytes_done);
if (copy_from_user(&content, buff + bytes_done, sizeof(content))) {
bytes_done = -EFAULT;
break;
}
ret = bfrom_OtpWrite(page, flags, &content);
if (ret & OTP_MASTER_ERROR) {
stamp("error from otp: 0x%x", ret);
bytes_done = -EIO;
break;
}
if (flags & OTP_UPPER_HALF)
++page;
bytes_done += sizeof(content);
*pos += sizeof(content);
}
bfin_otp_deinit_timing(timing);
mutex_unlock(&bfin_otp_lock);
return bytes_done;
}
static long bfin_otp_ioctl(struct file *filp, unsigned cmd, unsigned long arg)
{
stampit();
switch (cmd) {
case OTPLOCK: {
u32 timing;
int ret = -EIO;
if (!allow_writes)
return -EACCES;
if (mutex_lock_interruptible(&bfin_otp_lock))
return -ERESTARTSYS;
timing = bfin_otp_init_timing();
if (timing) {
u32 otp_result = bfrom_OtpWrite(arg, OTP_LOCK, NULL);
stamp("locking page %lu resulted in 0x%x", arg, otp_result);
if (!(otp_result & OTP_MASTER_ERROR))
ret = 0;
bfin_otp_deinit_timing(timing);
}
mutex_unlock(&bfin_otp_lock);
return ret;
}
case MEMLOCK:
allow_writes = false;
return 0;
case MEMUNLOCK:
allow_writes = true;
return 0;
}
return -EINVAL;
}
#else
# define bfin_otp_write NULL
# define bfin_otp_ioctl NULL
#endif
static struct file_operations bfin_otp_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = bfin_otp_ioctl,
.read = bfin_otp_read,
.write = bfin_otp_write,
};
static struct miscdevice bfin_otp_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DRIVER_NAME,
.fops = &bfin_otp_fops,
};
/**
* bfin_otp_init - Initialize module
*
* Registers the device and notifier handler. Actual device
* initialization is handled by bfin_otp_open().
*/
static int __init bfin_otp_init(void)
{
int ret;
stampit();
ret = misc_register(&bfin_otp_misc_device);
if (ret) {
pr_init(KERN_ERR PFX "unable to register a misc device\n");
return ret;
}
pr_init(KERN_INFO PFX "initialized\n");
return 0;
}
/**
* bfin_otp_exit - Deinitialize module
*
* Unregisters the device and notifier handler. Actual device
* deinitialization is handled by bfin_otp_close().
*/
static void __exit bfin_otp_exit(void)
{
stampit();
misc_deregister(&bfin_otp_misc_device);
}
module_init(bfin_otp_init);
module_exit(bfin_otp_exit);
MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
MODULE_DESCRIPTION("Blackfin OTP Memory Interface");
MODULE_LICENSE("GPL");