2007-06-30 14:14:43 +09:00
/*
* linux / drivers / mtd / onenand / onenand_sim . c
*
* The OneNAND simulator
*
2007-07-10 09:08:26 +01:00
* Copyright © 2005 - 2007 Samsung Electronics
2007-06-30 14:14:43 +09:00
* Kyungmin Park < kyungmin . park @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/vmalloc.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/partitions.h>
# include <linux/mtd/onenand.h>
2007-07-10 09:08:26 +01:00
# include <linux/io.h>
2007-06-30 14:14:43 +09:00
# ifndef CONFIG_ONENAND_SIM_MANUFACTURER
# define CONFIG_ONENAND_SIM_MANUFACTURER 0xec
# endif
# ifndef CONFIG_ONENAND_SIM_DEVICE_ID
# define CONFIG_ONENAND_SIM_DEVICE_ID 0x04
# endif
# ifndef CONFIG_ONENAND_SIM_VERSION_ID
# define CONFIG_ONENAND_SIM_VERSION_ID 0x1e
# endif
static int manuf_id = CONFIG_ONENAND_SIM_MANUFACTURER ;
static int device_id = CONFIG_ONENAND_SIM_DEVICE_ID ;
static int version_id = CONFIG_ONENAND_SIM_VERSION_ID ;
struct onenand_flash {
void __iomem * base ;
void __iomem * data ;
} ;
# define ONENAND_CORE(flash) (flash->data)
# define ONENAND_CORE_SPARE(flash, this, offset) \
( ( flash - > data ) + ( this - > chipsize ) + ( offset > > 5 ) )
# define ONENAND_MAIN_AREA(this, offset) \
( this - > base + ONENAND_DATARAM + offset )
# define ONENAND_SPARE_AREA(this, offset) \
( this - > base + ONENAND_SPARERAM + offset )
# define ONENAND_GET_WP_STATUS(this) \
( readw ( this - > base + ONENAND_REG_WP_STATUS ) )
# define ONENAND_SET_WP_STATUS(v, this) \
( writew ( v , this - > base + ONENAND_REG_WP_STATUS ) )
/* It has all 0xff chars */
# define MAX_ONENAND_PAGESIZE (2048 + 64)
static unsigned char * ffchars ;
static struct mtd_partition os_partitions [ ] = {
{
. name = " OneNAND simulator partition " ,
. offset = 0 ,
. size = MTDPART_SIZ_FULL ,
} ,
} ;
/*
* OneNAND simulator mtd
*/
struct onenand_info {
struct mtd_info mtd ;
struct mtd_partition * parts ;
struct onenand_chip onenand ;
struct onenand_flash flash ;
} ;
struct onenand_info * info ;
# define DPRINTK(format, args...) \
do { \
2007-07-10 09:08:26 +01:00
printk ( KERN_DEBUG " %s[%d]: " format " \n " , __func__ , \
__LINE__ , # # args ) ; \
2007-06-30 14:14:43 +09:00
} while ( 0 )
/**
* onenand_lock_handle - Handle Lock scheme
* @ param this OneNAND device structure
* @ param cmd The command to be sent
*
* Send lock command to OneNAND device .
* The lock scheme is depends on chip type .
*/
static void onenand_lock_handle ( struct onenand_chip * this , int cmd )
{
int block_lock_scheme ;
int status ;
status = ONENAND_GET_WP_STATUS ( this ) ;
block_lock_scheme = ! ( this - > options & ONENAND_HAS_CONT_LOCK ) ;
switch ( cmd ) {
case ONENAND_CMD_UNLOCK :
if ( block_lock_scheme )
ONENAND_SET_WP_STATUS ( ONENAND_WP_US , this ) ;
else
ONENAND_SET_WP_STATUS ( status | ONENAND_WP_US , this ) ;
break ;
case ONENAND_CMD_LOCK :
if ( block_lock_scheme )
ONENAND_SET_WP_STATUS ( ONENAND_WP_LS , this ) ;
else
ONENAND_SET_WP_STATUS ( status | ONENAND_WP_LS , this ) ;
break ;
case ONENAND_CMD_LOCK_TIGHT :
if ( block_lock_scheme )
ONENAND_SET_WP_STATUS ( ONENAND_WP_LTS , this ) ;
else
ONENAND_SET_WP_STATUS ( status | ONENAND_WP_LTS , this ) ;
break ;
default :
break ;
}
}
/**
* onenand_bootram_handle - Handle BootRAM area
* @ param this OneNAND device structure
* @ param cmd The command to be sent
*
* Emulate BootRAM area . It is possible to do basic operation using BootRAM .
*/
static void onenand_bootram_handle ( struct onenand_chip * this , int cmd )
{
switch ( cmd ) {
case ONENAND_CMD_READID :
writew ( manuf_id , this - > base ) ;
writew ( device_id , this - > base + 2 ) ;
writew ( version_id , this - > base + 4 ) ;
break ;
default :
/* REVIST: Handle other commands */
break ;
}
}
/**
* onenand_update_interrupt - Set interrupt register
* @ param this OneNAND device structure
* @ param cmd The command to be sent
*
* Update interrupt register . The status is depends on command .
*/
static void onenand_update_interrupt ( struct onenand_chip * this , int cmd )
{
int interrupt = ONENAND_INT_MASTER ;
switch ( cmd ) {
case ONENAND_CMD_READ :
case ONENAND_CMD_READOOB :
interrupt | = ONENAND_INT_READ ;
break ;
case ONENAND_CMD_PROG :
case ONENAND_CMD_PROGOOB :
interrupt | = ONENAND_INT_WRITE ;
break ;
case ONENAND_CMD_ERASE :
interrupt | = ONENAND_INT_ERASE ;
break ;
case ONENAND_CMD_RESET :
interrupt | = ONENAND_INT_RESET ;
break ;
default :
break ;
}
writew ( interrupt , this - > base + ONENAND_REG_INTERRUPT ) ;
}
/**
* onenand_check_overwrite - Check over - write if happend
* @ param dest The destination pointer
* @ param src The source pointer
* @ param count The length to be check
* @ return 0 on same , otherwise 1
*
* Compare the source with destination
*/
static int onenand_check_overwrite ( void * dest , void * src , size_t count )
{
unsigned int * s = ( unsigned int * ) src ;
unsigned int * d = ( unsigned int * ) dest ;
int i ;
count > > = 2 ;
for ( i = 0 ; i < count ; i + + )
if ( ( * s + + ^ * d + + ) ! = 0 )
return 1 ;
return 0 ;
}
/**
* onenand_data_handle - Handle OneNAND Core and DataRAM
* @ param this OneNAND device structure
* @ param cmd The command to be sent
* @ param dataram Which dataram used
* @ param offset The offset to OneNAND Core
*
* Copy data from OneNAND Core to DataRAM ( read )
* Copy data from DataRAM to OneNAND Core ( write )
* Erase the OneNAND Core ( erase )
*/
static void onenand_data_handle ( struct onenand_chip * this , int cmd ,
int dataram , unsigned int offset )
{
struct mtd_info * mtd = & info - > mtd ;
struct onenand_flash * flash = this - > priv ;
int main_offset , spare_offset ;
void __iomem * src ;
void __iomem * dest ;
unsigned int i ;
if ( dataram ) {
main_offset = mtd - > writesize ;
spare_offset = mtd - > oobsize ;
} else {
main_offset = 0 ;
spare_offset = 0 ;
}
switch ( cmd ) {
case ONENAND_CMD_READ :
src = ONENAND_CORE ( flash ) + offset ;
dest = ONENAND_MAIN_AREA ( this , main_offset ) ;
memcpy ( dest , src , mtd - > writesize ) ;
/* Fall through */
case ONENAND_CMD_READOOB :
src = ONENAND_CORE_SPARE ( flash , this , offset ) ;
dest = ONENAND_SPARE_AREA ( this , spare_offset ) ;
memcpy ( dest , src , mtd - > oobsize ) ;
break ;
case ONENAND_CMD_PROG :
src = ONENAND_MAIN_AREA ( this , main_offset ) ;
dest = ONENAND_CORE ( flash ) + offset ;
/* To handle partial write */
for ( i = 0 ; i < ( 1 < < mtd - > subpage_sft ) ; i + + ) {
int off = i * this - > subpagesize ;
if ( ! memcmp ( src + off , ffchars , this - > subpagesize ) )
continue ;
if ( memcmp ( dest + off , ffchars , this - > subpagesize ) & &
onenand_check_overwrite ( dest + off , src + off , this - > subpagesize ) )
printk ( KERN_ERR " over-write happend at 0x%08x \n " , offset ) ;
memcpy ( dest + off , src + off , this - > subpagesize ) ;
}
/* Fall through */
case ONENAND_CMD_PROGOOB :
src = ONENAND_SPARE_AREA ( this , spare_offset ) ;
/* Check all data is 0xff chars */
if ( ! memcmp ( src , ffchars , mtd - > oobsize ) )
break ;
dest = ONENAND_CORE_SPARE ( flash , this , offset ) ;
if ( memcmp ( dest , ffchars , mtd - > oobsize ) & &
onenand_check_overwrite ( dest , src , mtd - > oobsize ) )
printk ( KERN_ERR " OOB: over-write happend at 0x%08x \n " ,
offset ) ;
memcpy ( dest , src , mtd - > oobsize ) ;
break ;
case ONENAND_CMD_ERASE :
memset ( ONENAND_CORE ( flash ) + offset , 0xff , mtd - > erasesize ) ;
memset ( ONENAND_CORE_SPARE ( flash , this , offset ) , 0xff ,
( mtd - > erasesize > > 5 ) ) ;
break ;
default :
break ;
}
}
/**
* onenand_command_handle - Handle command
* @ param this OneNAND device structure
* @ param cmd The command to be sent
*
* Emulate OneNAND command .
*/
static void onenand_command_handle ( struct onenand_chip * this , int cmd )
{
unsigned long offset = 0 ;
int block = - 1 , page = - 1 , bufferram = - 1 ;
int dataram = 0 ;
switch ( cmd ) {
case ONENAND_CMD_UNLOCK :
case ONENAND_CMD_LOCK :
case ONENAND_CMD_LOCK_TIGHT :
case ONENAND_CMD_UNLOCK_ALL :
onenand_lock_handle ( this , cmd ) ;
break ;
case ONENAND_CMD_BUFFERRAM :
/* Do nothing */
return ;
default :
block = ( int ) readw ( this - > base + ONENAND_REG_START_ADDRESS1 ) ;
if ( block & ( 1 < < ONENAND_DDP_SHIFT ) ) {
block & = ~ ( 1 < < ONENAND_DDP_SHIFT ) ;
/* The half of chip block */
block + = this - > chipsize > > ( this - > erase_shift + 1 ) ;
}
if ( cmd = = ONENAND_CMD_ERASE )
break ;
page = ( int ) readw ( this - > base + ONENAND_REG_START_ADDRESS8 ) ;
page = ( page > > ONENAND_FPA_SHIFT ) ;
bufferram = ( int ) readw ( this - > base + ONENAND_REG_START_BUFFER ) ;
bufferram > > = ONENAND_BSA_SHIFT ;
bufferram & = ONENAND_BSA_DATARAM1 ;
dataram = ( bufferram = = ONENAND_BSA_DATARAM1 ) ? 1 : 0 ;
break ;
}
if ( block ! = - 1 )
offset + = block < < this - > erase_shift ;
if ( page ! = - 1 )
offset + = page < < this - > page_shift ;
onenand_data_handle ( this , cmd , dataram , offset ) ;
onenand_update_interrupt ( this , cmd ) ;
}
/**
* onenand_writew - [ OneNAND Interface ] Emulate write operation
* @ param value value to write
* @ param addr address to write
*
* Write OneNAND register with value
*/
static void onenand_writew ( unsigned short value , void __iomem * addr )
{
struct onenand_chip * this = info - > mtd . priv ;
/* BootRAM handling */
if ( addr < this - > base + ONENAND_DATARAM ) {
onenand_bootram_handle ( this , value ) ;
return ;
}
/* Command handling */
if ( addr = = this - > base + ONENAND_REG_COMMAND )
onenand_command_handle ( this , value ) ;
writew ( value , addr ) ;
}
/**
* flash_init - Initialize OneNAND simulator
* @ param flash OneNAND simulaotr data strucutres
*
* Initialize OneNAND simulator .
*/
static int __init flash_init ( struct onenand_flash * flash )
{
int density , size ;
int buffer_size ;
2007-07-10 09:08:26 +01:00
flash - > base = kzalloc ( 131072 , GFP_KERNEL ) ;
2007-06-30 14:14:43 +09:00
if ( ! flash - > base ) {
2007-07-10 09:08:26 +01:00
printk ( KERN_ERR " Unable to allocate base address. \n " ) ;
2007-06-30 14:14:43 +09:00
return - ENOMEM ;
}
density = device_id > > ONENAND_DEVICE_DENSITY_SHIFT ;
size = ( ( 16 < < 20 ) < < density ) ;
ONENAND_CORE ( flash ) = vmalloc ( size + ( size > > 5 ) ) ;
if ( ! ONENAND_CORE ( flash ) ) {
2007-07-10 09:08:26 +01:00
printk ( KERN_ERR " Unable to allocate nand core address. \n " ) ;
2007-06-30 14:14:43 +09:00
kfree ( flash - > base ) ;
return - ENOMEM ;
}
memset ( ONENAND_CORE ( flash ) , 0xff , size + ( size > > 5 ) ) ;
/* Setup registers */
writew ( manuf_id , flash - > base + ONENAND_REG_MANUFACTURER_ID ) ;
writew ( device_id , flash - > base + ONENAND_REG_DEVICE_ID ) ;
writew ( version_id , flash - > base + ONENAND_REG_VERSION_ID ) ;
if ( density < 2 )
2007-07-10 09:08:26 +01:00
buffer_size = 0x0400 ; /* 1KiB page */
2007-06-30 14:14:43 +09:00
else
2007-07-10 09:08:26 +01:00
buffer_size = 0x0800 ; /* 2KiB page */
2007-06-30 14:14:43 +09:00
writew ( buffer_size , flash - > base + ONENAND_REG_DATA_BUFFER_SIZE ) ;
return 0 ;
}
/**
* flash_exit - Clean up OneNAND simulator
* @ param flash OneNAND simulaotr data strucutres
*
* Clean up OneNAND simulator .
*/
static void flash_exit ( struct onenand_flash * flash )
{
vfree ( ONENAND_CORE ( flash ) ) ;
kfree ( flash - > base ) ;
kfree ( flash ) ;
}
static int __init onenand_sim_init ( void )
{
/* Allocate all 0xff chars pointer */
ffchars = kmalloc ( MAX_ONENAND_PAGESIZE , GFP_KERNEL ) ;
if ( ! ffchars ) {
printk ( KERN_ERR " Unable to allocate ff chars. \n " ) ;
return - ENOMEM ;
}
memset ( ffchars , 0xff , MAX_ONENAND_PAGESIZE ) ;
/* Allocate OneNAND simulator mtd pointer */
info = kzalloc ( sizeof ( struct onenand_info ) , GFP_KERNEL ) ;
if ( ! info ) {
printk ( KERN_ERR " Unable to allocate core structures. \n " ) ;
kfree ( ffchars ) ;
return - ENOMEM ;
}
/* Override write_word function */
info - > onenand . write_word = onenand_writew ;
if ( flash_init ( & info - > flash ) ) {
printk ( KERN_ERR " Unable to allocat flash. \n " ) ;
kfree ( ffchars ) ;
kfree ( info ) ;
return - ENOMEM ;
}
info - > parts = os_partitions ;
info - > onenand . base = info - > flash . base ;
info - > onenand . priv = & info - > flash ;
info - > mtd . name = " OneNAND simulator " ;
info - > mtd . priv = & info - > onenand ;
info - > mtd . owner = THIS_MODULE ;
if ( onenand_scan ( & info - > mtd , 1 ) ) {
flash_exit ( & info - > flash ) ;
kfree ( ffchars ) ;
kfree ( info ) ;
return - ENXIO ;
}
add_mtd_partitions ( & info - > mtd , info - > parts , ARRAY_SIZE ( os_partitions ) ) ;
return 0 ;
}
static void __exit onenand_sim_exit ( void )
{
struct onenand_chip * this = info - > mtd . priv ;
struct onenand_flash * flash = this - > priv ;
onenand_release ( & info - > mtd ) ;
flash_exit ( flash ) ;
kfree ( ffchars ) ;
kfree ( info ) ;
}
module_init ( onenand_sim_init ) ;
module_exit ( onenand_sim_exit ) ;
MODULE_AUTHOR ( " Kyungmin Park <kyungmin.park@samsung.com> " ) ;
MODULE_DESCRIPTION ( " The OneNAND flash simulator " ) ;
MODULE_LICENSE ( " GPL " ) ;