linux/drivers/mtd/onenand/onenand_sim.c

565 lines
14 KiB
C
Raw Normal View History

/*
* linux/drivers/mtd/onenand/onenand_sim.c
*
* The OneNAND simulator
*
* Copyright © 2005-2007 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
* Vishak G <vishak.g at samsung.com>, Rohit Hagargundgi <h.rohit at samsung.com>
* Flex-OneNAND simulator support
* Copyright (C) Samsung Electronics, 2008
*
* 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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/slab.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>
#include <linux/io.h>
#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
#define CONFIG_FLEXONENAND ((CONFIG_ONENAND_SIM_DEVICE_ID >> 9) & 1)
#ifndef CONFIG_ONENAND_SIM_VERSION_ID
#define CONFIG_ONENAND_SIM_VERSION_ID 0x1e
#endif
#ifndef CONFIG_ONENAND_SIM_TECHNOLOGY_ID
#define CONFIG_ONENAND_SIM_TECHNOLOGY_ID CONFIG_FLEXONENAND
#endif
/* Initial boundary values for Flex-OneNAND Simulator */
#ifndef CONFIG_FLEXONENAND_SIM_DIE0_BOUNDARY
#define CONFIG_FLEXONENAND_SIM_DIE0_BOUNDARY 0x01
#endif
#ifndef CONFIG_FLEXONENAND_SIM_DIE1_BOUNDARY
#define CONFIG_FLEXONENAND_SIM_DIE1_BOUNDARY 0x01
#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;
static int technology_id = CONFIG_ONENAND_SIM_TECHNOLOGY_ID;
static int boundary[] = {
CONFIG_FLEXONENAND_SIM_DIE0_BOUNDARY,
CONFIG_FLEXONENAND_SIM_DIE1_BOUNDARY,
};
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 (4096 + 128)
static unsigned char *ffchars;
#if CONFIG_FLEXONENAND
#define PARTITION_NAME "Flex-OneNAND simulator partition"
#else
#define PARTITION_NAME "OneNAND simulator partition"
#endif
static struct mtd_partition os_partitions[] = {
{
.name = PARTITION_NAME,
.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;
};
static struct onenand_info *info;
#define DPRINTK(format, args...) \
do { \
printk(KERN_DEBUG "%s[%d]: " format "\n", __func__, \
__LINE__, ##args); \
} while (0)
/**
* onenand_lock_handle - Handle Lock scheme
* @this: OneNAND device structure
* @cmd: The command to be sent
*
* Send lock command to OneNAND device.
* The lock scheme 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:
case ONENAND_CMD_UNLOCK_ALL:
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
* @this: OneNAND device structure
* @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
* @this: OneNAND device structure
* @cmd: The command to be sent
*
* Update interrupt register. The status 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 if over-write happened
* @dest: The destination pointer
* @src: The source pointer
* @count: The length to be check
*
* Returns: 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
* @this: OneNAND device structure
* @cmd: The command to be sent
* @dataram: Which dataram used
* @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, die = 0;
void __iomem *src;
void __iomem *dest;
unsigned int i;
static int pi_operation;
int erasesize, rgn;
if (dataram) {
main_offset = mtd->writesize;
spare_offset = mtd->oobsize;
} else {
main_offset = 0;
spare_offset = 0;
}
if (pi_operation) {
die = readw(this->base + ONENAND_REG_START_ADDRESS2);
die >>= ONENAND_DDP_SHIFT;
}
switch (cmd) {
case FLEXONENAND_CMD_PI_ACCESS:
pi_operation = 1;
break;
case ONENAND_CMD_RESET:
pi_operation = 0;
break;
case ONENAND_CMD_READ:
src = ONENAND_CORE(flash) + offset;
dest = ONENAND_MAIN_AREA(this, main_offset);
if (pi_operation) {
writew(boundary[die], this->base + ONENAND_DATARAM);
break;
}
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;
if (pi_operation) {
boundary[die] = readw(this->base + ONENAND_DATARAM);
break;
}
/* 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 happened 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 happened at 0x%08x\n",
offset);
memcpy(dest, src, mtd->oobsize);
break;
case ONENAND_CMD_ERASE:
if (pi_operation)
break;
if (FLEXONENAND(this)) {
rgn = flexonenand_region(mtd, offset);
erasesize = mtd->eraseregions[rgn].erasesize;
} else
erasesize = mtd->erasesize;
memset(ONENAND_CORE(flash) + offset, 0xff, erasesize);
memset(ONENAND_CORE_SPARE(flash, this, offset), 0xff,
(erasesize >> 5));
break;
default:
break;
}
}
/**
* onenand_command_handle - Handle command
* @this: OneNAND device structure
* @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 = onenand_addr(this, block);
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
* @value: value to write
* @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
* @flash: OneNAND simulator data strucutres
*
* Initialize OneNAND simulator.
*/
static int __init flash_init(struct onenand_flash *flash)
{
int density, size;
int buffer_size;
flash->base = kzalloc(131072, GFP_KERNEL);
if (!flash->base) {
printk(KERN_ERR "Unable to allocate base address.\n");
return -ENOMEM;
}
density = device_id >> ONENAND_DEVICE_DENSITY_SHIFT;
density &= ONENAND_DEVICE_DENSITY_MASK;
size = ((16 << 20) << density);
ONENAND_CORE(flash) = vmalloc(size + (size >> 5));
if (!ONENAND_CORE(flash)) {
printk(KERN_ERR "Unable to allocate nand core address.\n");
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);
writew(technology_id, flash->base + ONENAND_REG_TECHNOLOGY);
if (density < 2 && (!CONFIG_FLEXONENAND))
buffer_size = 0x0400; /* 1KiB page */
else
buffer_size = 0x0800; /* 2KiB page */
writew(buffer_size, flash->base + ONENAND_REG_DATA_BUFFER_SIZE);
return 0;
}
/**
* flash_exit - Clean up OneNAND simulator
* @flash: OneNAND simulator data structures
*
* Clean up OneNAND simulator.
*/
static void flash_exit(struct onenand_flash *flash)
{
vfree(ONENAND_CORE(flash));
kfree(flash->base);
}
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 allocate 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;
}
mtd_device_register(&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");