2008-04-25 04:04:56 +04:00
/*
* Blackfin On - Chip OTP Memory Interface
*
2009-09-24 02:57:16 +04:00
* Copyright 2007 - 2009 Analog Devices Inc .
2008-04-25 04:04:56 +04:00
*
* 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>
2009-09-24 02:57:16 +04:00
# include <mtd/mtd-abi.h>
2008-04-25 04:04:56 +04:00
# include <asm/blackfin.h>
2009-09-24 02:57:16 +04:00
# include <asm/bfrom.h>
2008-04-25 04:04:56 +04:00
# 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 ) ;
2009-09-24 02:57:16 +04:00
stamp ( " processing page %i (0x%x:%s) " , page , flags ,
( flags & OTP_UPPER_HALF ? " upper " : " lower " ) ) ;
ret = bfrom_OtpRead ( page , flags , & content ) ;
2008-04-25 04:04:56 +04:00
if ( ret & OTP_MASTER_ERROR ) {
2009-09-24 02:57:16 +04:00
stamp ( " error from otp: 0x%x " , ret ) ;
2008-04-25 04:04:56 +04:00
bytes_done = - EIO ;
break ;
}
if ( copy_to_user ( buff + bytes_done , & content , sizeof ( content ) ) ) {
bytes_done = - EFAULT ;
break ;
}
2009-09-24 02:57:16 +04:00
if ( flags & OTP_UPPER_HALF )
2008-04-25 04:04:56 +04:00
+ + page ;
bytes_done + = sizeof ( content ) ;
* pos + = sizeof ( content ) ;
}
mutex_unlock ( & bfin_otp_lock ) ;
return bytes_done ;
}
# ifdef CONFIG_BFIN_OTP_WRITE_ENABLE
2009-09-24 02:57:16 +04:00
static bool allow_writes ;
2008-04-25 04:04:56 +04:00
/**
2009-09-24 02:57:16 +04:00
* 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
2008-04-25 04:04:56 +04:00
*
* 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 )
{
2009-09-24 02:57:16 +04:00
ssize_t bytes_done ;
u32 timing , page , base_flags , flags , ret ;
u64 content ;
if ( ! allow_writes )
return - EACCES ;
2008-04-25 04:04:56 +04:00
if ( count % sizeof ( u64 ) )
return - EMSGSIZE ;
if ( mutex_lock_interruptible ( & bfin_otp_lock ) )
return - ERESTARTSYS ;
2009-09-24 02:57:16 +04:00
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 ) ;
2008-04-25 04:04:56 +04:00
mutex_unlock ( & bfin_otp_lock ) ;
2009-09-24 02:57:16 +04:00
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 ;
}
2008-04-25 04:04:56 +04:00
return - EINVAL ;
}
# else
# define bfin_otp_write NULL
2009-09-24 02:57:16 +04:00
# define bfin_otp_ioctl NULL
2008-04-25 04:04:56 +04:00
# endif
2009-10-02 02:43:56 +04:00
static const struct file_operations bfin_otp_fops = {
2009-09-24 02:57:16 +04:00
. owner = THIS_MODULE ,
. unlocked_ioctl = bfin_otp_ioctl ,
. read = bfin_otp_read ,
. write = bfin_otp_write ,
2008-04-25 04:04:56 +04:00
} ;
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 " ) ;