diff options
Diffstat (limited to 'target/linux/olpc/files/drivers/mtd')
| -rw-r--r-- | target/linux/olpc/files/drivers/mtd/mtdcore.h | 11 | ||||
| -rw-r--r-- | target/linux/olpc/files/drivers/mtd/mtdoops.c | 365 | ||||
| -rw-r--r-- | target/linux/olpc/files/drivers/mtd/onenand/onenand_sim.c | 495 | 
3 files changed, 871 insertions, 0 deletions
diff --git a/target/linux/olpc/files/drivers/mtd/mtdcore.h b/target/linux/olpc/files/drivers/mtd/mtdcore.h new file mode 100644 index 000000000..a33251f4b --- /dev/null +++ b/target/linux/olpc/files/drivers/mtd/mtdcore.h @@ -0,0 +1,11 @@ +/* linux/drivers/mtd/mtdcore.h + * + * Header file for driver private mtdcore exports + * + */ + +/* These are exported solely for the purpose of mtd_blkdevs.c. You +   should not use them for _anything_ else */ + +extern struct mutex mtd_table_mutex; +extern struct mtd_info *mtd_table[MAX_MTD_DEVICES]; diff --git a/target/linux/olpc/files/drivers/mtd/mtdoops.c b/target/linux/olpc/files/drivers/mtd/mtdoops.c new file mode 100644 index 000000000..cfc28ab4a --- /dev/null +++ b/target/linux/olpc/files/drivers/mtd/mtdoops.c @@ -0,0 +1,365 @@ +/* + * MTD Oops/Panic logger + * + * Copyright (C) 2007 Nokia Corporation. All rights reserved. + * + * Author: Richard Purdie <rpurdie@openedhand.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. + * + * 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 St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/console.h> +#include <linux/vmalloc.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/mtd/mtd.h> + +#define OOPS_PAGE_SIZE 4096 + +static struct mtdoops_context { +	int mtd_index; +	struct work_struct work; +	struct mtd_info *mtd; +	int oops_pages; +	int nextpage; +	int nextcount; + +	void *oops_buf; +	int ready; +	int writecount; +} oops_cxt; + +static void mtdoops_erase_callback(struct erase_info *done) +{ +	wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; +	wake_up(wait_q); +} + +static int mtdoops_erase_block(struct mtd_info *mtd, int offset) +{ +	struct erase_info erase; +	DECLARE_WAITQUEUE(wait, current); +	wait_queue_head_t wait_q; +	int ret; + +	init_waitqueue_head(&wait_q); +	erase.mtd = mtd; +	erase.callback = mtdoops_erase_callback; +	erase.addr = offset; +	if (mtd->erasesize < OOPS_PAGE_SIZE) +		erase.len = OOPS_PAGE_SIZE; +	else +		erase.len = mtd->erasesize; +	erase.priv = (u_long)&wait_q; + +	set_current_state(TASK_INTERRUPTIBLE); +	add_wait_queue(&wait_q, &wait); + +	ret = mtd->erase(mtd, &erase); +	if (ret) { +		set_current_state(TASK_RUNNING); +		remove_wait_queue(&wait_q, &wait); +		printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] " +				     "on \"%s\" failed\n", +			erase.addr, erase.len, mtd->name); +		return ret; +	} + +	schedule();  /* Wait for erase to finish. */ +	remove_wait_queue(&wait_q, &wait); + +	return 0; +} + +static int mtdoops_inc_counter(struct mtdoops_context *cxt) +{ +	struct mtd_info *mtd = cxt->mtd; +	size_t retlen; +	u32 count; +	int ret; + +	cxt->nextpage++; +	if (cxt->nextpage > cxt->oops_pages) +		cxt->nextpage = 0; +	cxt->nextcount++; +	if (cxt->nextcount == 0xffffffff) +		cxt->nextcount = 0; + +	ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4, +			&retlen, (u_char *) &count); +	if ((retlen != 4) || (ret < 0)) { +		printk(KERN_ERR "mtdoops: Read failure at %d (%d of 4 read)" +				", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE, +				retlen, ret); +		return 1; +	} + +	/* See if we need to erase the next block */ +	if (count != 0xffffffff) +		return 1; + +	printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n", +			cxt->nextpage, cxt->nextcount); +	cxt->ready = 1; +	return 0; +} + +static void mtdoops_prepare(struct mtdoops_context *cxt) +{ +	struct mtd_info *mtd = cxt->mtd; +	int i = 0, j, ret, mod; + +	/* We were unregistered */ +	if (!mtd) +		return; + +	mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize; +	if (mod != 0) { +		cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE); +		if (cxt->nextpage > cxt->oops_pages) +			cxt->nextpage = 0; +	} + +	while (mtd->block_isbad && +			mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE)) { +badblock: +		printk(KERN_WARNING "mtdoops: Bad block at %08x\n", +				cxt->nextpage * OOPS_PAGE_SIZE); +		i++; +		cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE); +		if (cxt->nextpage > cxt->oops_pages) +			cxt->nextpage = 0; +		if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) { +			printk(KERN_ERR "mtdoops: All blocks bad!\n"); +			return; +		} +	} + +	for (j = 0, ret = -1; (j < 3) && (ret < 0); j++) +		ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE); + +	if (ret < 0) { +		if (mtd->block_markbad) +			mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE); +		goto badblock; +	} + +	printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount); + +	cxt->ready = 1; +} + +static void mtdoops_workfunc(struct work_struct *work) +{ +	struct mtdoops_context *cxt = +			container_of(work, struct mtdoops_context, work); + +	mtdoops_prepare(cxt); +} + +static int find_next_position(struct mtdoops_context *cxt) +{ +	struct mtd_info *mtd = cxt->mtd; +	int page, maxpos = 0; +	u32 count, maxcount = 0xffffffff; +	size_t retlen; + +	for (page = 0; page < cxt->oops_pages; page++) { +		mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count); +		if (count == 0xffffffff) +			continue; +		if (maxcount == 0xffffffff) { +			maxcount = count; +			maxpos = page; +		} else if ((count < 0x40000000) && (maxcount > 0xc0000000)) { +			maxcount = count; +			maxpos = page; +		} else if ((count > maxcount) && (count < 0xc0000000)) { +			maxcount = count; +			maxpos = page; +		} else if ((count > maxcount) && (count > 0xc0000000) +					&& (maxcount > 0x80000000)) { +			maxcount = count; +			maxpos = page; +		} +	} +	if (maxcount == 0xffffffff) { +		cxt->nextpage = 0; +		cxt->nextcount = 1; +		cxt->ready = 1; +		printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n", +				cxt->nextpage, cxt->nextcount); +		return 0; +	} + +	cxt->nextpage = maxpos; +	cxt->nextcount = maxcount; + +	return mtdoops_inc_counter(cxt); +} + + +static void mtdoops_notify_add(struct mtd_info *mtd) +{ +	struct mtdoops_context *cxt = &oops_cxt; +	int ret; + +	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) +		return; + +	if (mtd->size < (mtd->erasesize * 2)) { +		printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n", +				mtd->index); +		return; +	} + +	cxt->mtd = mtd; +	cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE; + +	ret = find_next_position(cxt); +	if (ret == 1) +		mtdoops_prepare(cxt); + +	printk(KERN_DEBUG "mtdoops: Attached to MTD device %d\n", mtd->index); +} + +static void mtdoops_notify_remove(struct mtd_info *mtd) +{ +	struct mtdoops_context *cxt = &oops_cxt; + +	if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0) +		return; + +	cxt->mtd = NULL; +	flush_scheduled_work(); +} + + +static void +mtdoops_console_write(struct console *co, const char *s, unsigned int count) +{ +	struct mtdoops_context *cxt = co->data; +	struct mtd_info *mtd = cxt->mtd; +	int i, ret; + +	if (!cxt->ready || !mtd) +		return; + +	if (!oops_in_progress && cxt->writecount != 0) { +		size_t retlen; +		if (cxt->writecount < OOPS_PAGE_SIZE) +			memset(cxt->oops_buf + cxt->writecount, 0xff, +					OOPS_PAGE_SIZE - cxt->writecount); + +		ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE, +					OOPS_PAGE_SIZE, &retlen, cxt->oops_buf); +		cxt->ready = 0; +		cxt->writecount = 0; + +		if ((retlen != OOPS_PAGE_SIZE) || (ret < 0)) +			printk(KERN_ERR "mtdoops: Write failure at %d (%d of %d" +				" written), err %d.\n", +				cxt->nextpage * OOPS_PAGE_SIZE, retlen, +				OOPS_PAGE_SIZE, ret); + +		ret = mtdoops_inc_counter(cxt); +		if (ret == 1) +			schedule_work(&cxt->work); +	} + +	if (!oops_in_progress) +		return; + +	if (cxt->writecount == 0) { +		u32 *stamp = cxt->oops_buf; +		*stamp = cxt->nextcount; +		cxt->writecount = 4; +	} + +	if ((count + cxt->writecount) > OOPS_PAGE_SIZE) +		count = OOPS_PAGE_SIZE - cxt->writecount; + +	for (i = 0; i < count; i++, s++) +		*((char *)(cxt->oops_buf) + cxt->writecount + i) = *s; + +	cxt->writecount = cxt->writecount + count; +} + +static int __init mtdoops_console_setup(struct console *co, char *options) +{ +	struct mtdoops_context *cxt = co->data; + +	if (cxt->mtd_index != -1) +		return -EBUSY; +	if (co->index == -1) +		return -EINVAL; + +	cxt->mtd_index = co->index; +	return 0; +} + +static struct mtd_notifier mtdoops_notifier = { +	.add	= mtdoops_notify_add, +	.remove	= mtdoops_notify_remove, +}; + +static struct console mtdoops_console = { +	.name		= "ttyMTD", +	.write		= mtdoops_console_write, +	.setup		= mtdoops_console_setup, +	.flags		= CON_PRINTBUFFER, +	.index		= -1, +	.data		= &oops_cxt, +}; + +static int __init mtdoops_console_init(void) +{ +	struct mtdoops_context *cxt = &oops_cxt; + +	cxt->mtd_index = -1; +	cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE); + +	if (!cxt->oops_buf) { +		printk(KERN_ERR "Failed to allocate oops buffer workspace\n"); +		return -ENOMEM; +	} + +	INIT_WORK(&cxt->work, mtdoops_workfunc); + +	register_console(&mtdoops_console); +	register_mtd_user(&mtdoops_notifier); +	return 0; +} + +static void __exit mtdoops_console_exit(void) +{ +	struct mtdoops_context *cxt = &oops_cxt; + +	unregister_mtd_user(&mtdoops_notifier); +	unregister_console(&mtdoops_console); +	vfree(cxt->oops_buf); +} + + +subsys_initcall(mtdoops_console_init); +module_exit(mtdoops_console_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); +MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver"); diff --git a/target/linux/olpc/files/drivers/mtd/onenand/onenand_sim.c b/target/linux/olpc/files/drivers/mtd/onenand/onenand_sim.c new file mode 100644 index 000000000..b80667309 --- /dev/null +++ b/target/linux/olpc/files/drivers/mtd/onenand/onenand_sim.c @@ -0,0 +1,495 @@ +/* + *  linux/drivers/mtd/onenand/onenand_sim.c + * + *  The OneNAND simulator + * + *  Copyright © 2005-2007 Samsung Electronics + *  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> + +#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 +#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 {									\ +	printk(KERN_DEBUG "%s[%d]: " format "\n", __func__,		\ +			   __LINE__, ##args);				\ +} 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; + +	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; +	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); + +	if (density < 2) +		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 + * @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");  | 
