diff options
| author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 | 
|---|---|---|
| committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 | 
| commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
| tree | 65ca85f13617aee1dce474596800950f266a456c /roms/u-boot/drivers/mtd/onenand | |
| download | qemu-master.tar.gz qemu-master.tar.bz2 qemu-master.zip  | |
Diffstat (limited to 'roms/u-boot/drivers/mtd/onenand')
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/Makefile | 13 | ||||
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/onenand_base.c | 2784 | ||||
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/onenand_bbt.c | 266 | ||||
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/onenand_spl.c | 128 | ||||
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/onenand_uboot.c | 56 | ||||
| -rw-r--r-- | roms/u-boot/drivers/mtd/onenand/samsung.c | 577 | 
6 files changed, 3824 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/mtd/onenand/Makefile b/roms/u-boot/drivers/mtd/onenand/Makefile new file mode 100644 index 00000000..b2493488 --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/Makefile @@ -0,0 +1,13 @@ +# +# Copyright (C) 2005-2007 Samsung Electronics. +# Kyungmin Park <kyungmin.park@samsung.com> +# +# SPDX-License-Identifier:	GPL-2.0+ +# + +ifndef	CONFIG_SPL_BUILD +obj-$(CONFIG_CMD_ONENAND)	:= onenand_uboot.o onenand_base.o onenand_bbt.o +obj-$(CONFIG_SAMSUNG_ONENAND)	+= samsung.o +else +obj-y				:= onenand_spl.o +endif diff --git a/roms/u-boot/drivers/mtd/onenand/onenand_base.c b/roms/u-boot/drivers/mtd/onenand/onenand_base.c new file mode 100644 index 00000000..e33e8d38 --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/onenand_base.c @@ -0,0 +1,2784 @@ +/* + *  linux/drivers/mtd/onenand/onenand_base.c + * + *  Copyright (C) 2005-2007 Samsung Electronics + *  Kyungmin Park <kyungmin.park@samsung.com> + * + *  Credits: + *      Adrian Hunter <ext-adrian.hunter@nokia.com>: + *      auto-placement support, read-while load support, various fixes + *      Copyright (C) Nokia Corporation, 2007 + * + *      Rohit Hagargundgi <h.rohit at samsung.com>, + *      Amul Kumar Saha <amul.saha@samsung.com>: + *      Flex-OneNAND support + *      Copyright (C) Samsung Electronics, 2009 + * + * 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 <common.h> +#include <linux/compat.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> + +#include <asm/io.h> +#include <asm/errno.h> +#include <malloc.h> + +/* It should access 16-bit instead of 8-bit */ +static void *memcpy_16(void *dst, const void *src, unsigned int len) +{ +	void *ret = dst; +	short *d = dst; +	const short *s = src; + +	len >>= 1; +	while (len-- > 0) +		*d++ = *s++; +	return ret; +} + +/** + *  onenand_oob_128 - oob info for Flex-Onenand with 4KB page + *  For now, we expose only 64 out of 80 ecc bytes + */ +static struct nand_ecclayout onenand_oob_128 = { +	.eccbytes	= 64, +	.eccpos		= { +		6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +		22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +		38, 39, 40, 41, 42, 43, 44, 45, 46, 47, +		54, 55, 56, 57, 58, 59, 60, 61, 62, 63, +		70, 71, 72, 73, 74, 75, 76, 77, 78, 79, +		86, 87, 88, 89, 90, 91, 92, 93, 94, 95, +		102, 103, 104, 105 +		}, +	.oobfree	= { +		{2, 4}, {18, 4}, {34, 4}, {50, 4}, +		{66, 4}, {82, 4}, {98, 4}, {114, 4} +	} +}; + +/** + * onenand_oob_64 - oob info for large (2KB) page + */ +static struct nand_ecclayout onenand_oob_64 = { +	.eccbytes	= 20, +	.eccpos		= { +		8, 9, 10, 11, 12, +		24, 25, 26, 27, 28, +		40, 41, 42, 43, 44, +		56, 57, 58, 59, 60, +		}, +	.oobfree	= { +		{2, 3}, {14, 2}, {18, 3}, {30, 2}, +		{34, 3}, {46, 2}, {50, 3}, {62, 2} +	} +}; + +/** + * onenand_oob_32 - oob info for middle (1KB) page + */ +static struct nand_ecclayout onenand_oob_32 = { +	.eccbytes	= 10, +	.eccpos		= { +		8, 9, 10, 11, 12, +		24, 25, 26, 27, 28, +		}, +	.oobfree	= { {2, 3}, {14, 2}, {18, 3}, {30, 2} } +}; + +/* + * Warning! This array is used with the memcpy_16() function, thus + * it must be aligned to 2 bytes. GCC can make this array unaligned + * as the array is made of unsigned char, which memcpy16() doesn't + * like and will cause unaligned access. + */ +static const unsigned char __aligned(2) ffchars[] = { +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 16 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 32 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 48 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 64 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 80 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 96 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 112 */ +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* 128 */ +}; + +/** + * onenand_readw - [OneNAND Interface] Read OneNAND register + * @param addr		address to read + * + * Read OneNAND register + */ +static unsigned short onenand_readw(void __iomem * addr) +{ +	return readw(addr); +} + +/** + * onenand_writew - [OneNAND Interface] Write OneNAND register with value + * @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) +{ +	writew(value, addr); +} + +/** + * onenand_block_address - [DEFAULT] Get block address + * @param device	the device id + * @param block		the block + * @return		translated block address if DDP, otherwise same + * + * Setup Start Address 1 Register (F100h) + */ +static int onenand_block_address(struct onenand_chip *this, int block) +{ +	/* Device Flash Core select, NAND Flash Block Address */ +	if (block & this->density_mask) +		return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); + +	return block; +} + +/** + * onenand_bufferram_address - [DEFAULT] Get bufferram address + * @param device	the device id + * @param block		the block + * @return		set DBS value if DDP, otherwise 0 + * + * Setup Start Address 2 Register (F101h) for DDP + */ +static int onenand_bufferram_address(struct onenand_chip *this, int block) +{ +	/* Device BufferRAM Select */ +	if (block & this->density_mask) +		return ONENAND_DDP_CHIP1; + +	return ONENAND_DDP_CHIP0; +} + +/** + * onenand_page_address - [DEFAULT] Get page address + * @param page		the page address + * @param sector	the sector address + * @return		combined page and sector address + * + * Setup Start Address 8 Register (F107h) + */ +static int onenand_page_address(int page, int sector) +{ +	/* Flash Page Address, Flash Sector Address */ +	int fpa, fsa; + +	fpa = page & ONENAND_FPA_MASK; +	fsa = sector & ONENAND_FSA_MASK; + +	return ((fpa << ONENAND_FPA_SHIFT) | fsa); +} + +/** + * onenand_buffer_address - [DEFAULT] Get buffer address + * @param dataram1	DataRAM index + * @param sectors	the sector address + * @param count		the number of sectors + * @return		the start buffer value + * + * Setup Start Buffer Register (F200h) + */ +static int onenand_buffer_address(int dataram1, int sectors, int count) +{ +	int bsa, bsc; + +	/* BufferRAM Sector Address */ +	bsa = sectors & ONENAND_BSA_MASK; + +	if (dataram1) +		bsa |= ONENAND_BSA_DATARAM1;	/* DataRAM1 */ +	else +		bsa |= ONENAND_BSA_DATARAM0;	/* DataRAM0 */ + +	/* BufferRAM Sector Count */ +	bsc = count & ONENAND_BSC_MASK; + +	return ((bsa << ONENAND_BSA_SHIFT) | bsc); +} + +/** + * flexonenand_block - Return block number for flash address + * @param this		- OneNAND device structure + * @param addr		- Address for which block number is needed + */ +static unsigned int flexonenand_block(struct onenand_chip *this, loff_t addr) +{ +	unsigned int boundary, blk, die = 0; + +	if (ONENAND_IS_DDP(this) && addr >= this->diesize[0]) { +		die = 1; +		addr -= this->diesize[0]; +	} + +	boundary = this->boundary[die]; + +	blk = addr >> (this->erase_shift - 1); +	if (blk > boundary) +		blk = (blk + boundary + 1) >> 1; + +	blk += die ? this->density_mask : 0; +	return blk; +} + +unsigned int onenand_block(struct onenand_chip *this, loff_t addr) +{ +	if (!FLEXONENAND(this)) +		return addr >> this->erase_shift; +	return flexonenand_block(this, addr); +} + +/** + * flexonenand_addr - Return address of the block + * @this:		OneNAND device structure + * @block:		Block number on Flex-OneNAND + * + * Return address of the block + */ +static loff_t flexonenand_addr(struct onenand_chip *this, int block) +{ +	loff_t ofs = 0; +	int die = 0, boundary; + +	if (ONENAND_IS_DDP(this) && block >= this->density_mask) { +		block -= this->density_mask; +		die = 1; +		ofs = this->diesize[0]; +	} + +	boundary = this->boundary[die]; +	ofs += (loff_t) block << (this->erase_shift - 1); +	if (block > (boundary + 1)) +		ofs += (loff_t) (block - boundary - 1) +			<< (this->erase_shift - 1); +	return ofs; +} + +loff_t onenand_addr(struct onenand_chip *this, int block) +{ +	if (!FLEXONENAND(this)) +		return (loff_t) block << this->erase_shift; +	return flexonenand_addr(this, block); +} + +/** + * flexonenand_region - [Flex-OneNAND] Return erase region of addr + * @param mtd		MTD device structure + * @param addr		address whose erase region needs to be identified + */ +int flexonenand_region(struct mtd_info *mtd, loff_t addr) +{ +	int i; + +	for (i = 0; i < mtd->numeraseregions; i++) +		if (addr < mtd->eraseregions[i].offset) +			break; +	return i - 1; +} + +/** + * onenand_get_density - [DEFAULT] Get OneNAND density + * @param dev_id        OneNAND device ID + * + * Get OneNAND density from device ID + */ +static inline int onenand_get_density(int dev_id) +{ +	int density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; +	return (density & ONENAND_DEVICE_DENSITY_MASK); +} + +/** + * onenand_command - [DEFAULT] Send command to OneNAND device + * @param mtd		MTD device structure + * @param cmd		the command to be sent + * @param addr		offset to read from or write to + * @param len		number of bytes to read or write + * + * Send command to OneNAND device. This function is used for middle/large page + * devices (1KB/2KB Bytes per page) + */ +static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, +			   size_t len) +{ +	struct onenand_chip *this = mtd->priv; +	int value; +	int block, page; + +	/* Now we use page size operation */ +	int sectors = 0, count = 0; + +	/* Address translation */ +	switch (cmd) { +	case ONENAND_CMD_UNLOCK: +	case ONENAND_CMD_LOCK: +	case ONENAND_CMD_LOCK_TIGHT: +	case ONENAND_CMD_UNLOCK_ALL: +		block = -1; +		page = -1; +		break; + +	case FLEXONENAND_CMD_PI_ACCESS: +		/* addr contains die index */ +		block = addr * this->density_mask; +		page = -1; +		break; + +	case ONENAND_CMD_ERASE: +	case ONENAND_CMD_BUFFERRAM: +		block = onenand_block(this, addr); +		page = -1; +		break; + +	case FLEXONENAND_CMD_READ_PI: +		cmd = ONENAND_CMD_READ; +		block = addr * this->density_mask; +		page = 0; +		break; + +	default: +		block = onenand_block(this, addr); +		page = (int) (addr +			- onenand_addr(this, block)) >> this->page_shift; +		page &= this->page_mask; +		break; +	} + +	/* NOTE: The setting order of the registers is very important! */ +	if (cmd == ONENAND_CMD_BUFFERRAM) { +		/* Select DataRAM for DDP */ +		value = onenand_bufferram_address(this, block); +		this->write_word(value, +				 this->base + ONENAND_REG_START_ADDRESS2); + +		if (ONENAND_IS_4KB_PAGE(this)) +			ONENAND_SET_BUFFERRAM0(this); +		else +			/* Switch to the next data buffer */ +			ONENAND_SET_NEXT_BUFFERRAM(this); + +		return 0; +	} + +	if (block != -1) { +		/* Write 'DFS, FBA' of Flash */ +		value = onenand_block_address(this, block); +		this->write_word(value, +				 this->base + ONENAND_REG_START_ADDRESS1); + +		/* Select DataRAM for DDP */ +		value = onenand_bufferram_address(this, block); +		this->write_word(value, +				 this->base + ONENAND_REG_START_ADDRESS2); +	} + +	if (page != -1) { +		int dataram; + +		switch (cmd) { +		case FLEXONENAND_CMD_RECOVER_LSB: +		case ONENAND_CMD_READ: +		case ONENAND_CMD_READOOB: +			if (ONENAND_IS_4KB_PAGE(this)) +				dataram = ONENAND_SET_BUFFERRAM0(this); +			else +				dataram = ONENAND_SET_NEXT_BUFFERRAM(this); + +			break; + +		default: +			dataram = ONENAND_CURRENT_BUFFERRAM(this); +			break; +		} + +		/* Write 'FPA, FSA' of Flash */ +		value = onenand_page_address(page, sectors); +		this->write_word(value, +				 this->base + ONENAND_REG_START_ADDRESS8); + +		/* Write 'BSA, BSC' of DataRAM */ +		value = onenand_buffer_address(dataram, sectors, count); +		this->write_word(value, this->base + ONENAND_REG_START_BUFFER); +	} + +	/* Interrupt clear */ +	this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); +	/* Write command */ +	this->write_word(cmd, this->base + ONENAND_REG_COMMAND); + +	return 0; +} + +/** + * onenand_read_ecc - return ecc status + * @param this		onenand chip structure + */ +static int onenand_read_ecc(struct onenand_chip *this) +{ +	int ecc, i; + +	if (!FLEXONENAND(this)) +		return this->read_word(this->base + ONENAND_REG_ECC_STATUS); + +	for (i = 0; i < 4; i++) { +		ecc = this->read_word(this->base +				+ ((ONENAND_REG_ECC_STATUS + i) << 1)); +		if (likely(!ecc)) +			continue; +		if (ecc & FLEXONENAND_UNCORRECTABLE_ERROR) +			return ONENAND_ECC_2BIT_ALL; +	} + +	return 0; +} + +/** + * onenand_wait - [DEFAULT] wait until the command is done + * @param mtd		MTD device structure + * @param state		state to select the max. timeout value + * + * Wait for command done. This applies to all OneNAND command + * Read can take up to 30us, erase up to 2ms and program up to 350us + * according to general OneNAND specs + */ +static int onenand_wait(struct mtd_info *mtd, int state) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int flags = ONENAND_INT_MASTER; +	unsigned int interrupt = 0; +	unsigned int ctrl; + +	while (1) { +		interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); +		if (interrupt & flags) +			break; +	} + +	ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + +	if (interrupt & ONENAND_INT_READ) { +		int ecc = onenand_read_ecc(this); +		if (ecc & ONENAND_ECC_2BIT_ALL) { +			printk("onenand_wait: ECC error = 0x%04x\n", ecc); +			return -EBADMSG; +		} +	} + +	if (ctrl & ONENAND_CTRL_ERROR) { +		printk("onenand_wait: controller error = 0x%04x\n", ctrl); +		if (ctrl & ONENAND_CTRL_LOCK) +			printk("onenand_wait: it's locked error = 0x%04x\n", +				ctrl); + +		return -EIO; +	} + + +	return 0; +} + +/** + * onenand_bufferram_offset - [DEFAULT] BufferRAM offset + * @param mtd		MTD data structure + * @param area		BufferRAM area + * @return		offset given area + * + * Return BufferRAM offset given area + */ +static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ +	struct onenand_chip *this = mtd->priv; + +	if (ONENAND_CURRENT_BUFFERRAM(this)) { +		if (area == ONENAND_DATARAM) +			return mtd->writesize; +		if (area == ONENAND_SPARERAM) +			return mtd->oobsize; +	} + +	return 0; +} + +/** + * onenand_read_bufferram - [OneNAND Interface] Read the bufferram area + * @param mtd		MTD data structure + * @param area		BufferRAM area + * @param buffer	the databuffer to put/get data + * @param offset	offset to read from or write to + * @param count		number of bytes to read/write + * + * Read the BufferRAM area + */ +static int onenand_read_bufferram(struct mtd_info *mtd, loff_t addr, int area, +				  unsigned char *buffer, int offset, +				  size_t count) +{ +	struct onenand_chip *this = mtd->priv; +	void __iomem *bufferram; + +	bufferram = this->base + area; +	bufferram += onenand_bufferram_offset(mtd, area); + +	memcpy_16(buffer, bufferram + offset, count); + +	return 0; +} + +/** + * onenand_sync_read_bufferram - [OneNAND Interface] Read the bufferram area with Sync. Burst mode + * @param mtd		MTD data structure + * @param area		BufferRAM area + * @param buffer	the databuffer to put/get data + * @param offset	offset to read from or write to + * @param count		number of bytes to read/write + * + * Read the BufferRAM area with Sync. Burst Mode + */ +static int onenand_sync_read_bufferram(struct mtd_info *mtd, loff_t addr, int area, +				       unsigned char *buffer, int offset, +				       size_t count) +{ +	struct onenand_chip *this = mtd->priv; +	void __iomem *bufferram; + +	bufferram = this->base + area; +	bufferram += onenand_bufferram_offset(mtd, area); + +	this->mmcontrol(mtd, ONENAND_SYS_CFG1_SYNC_READ); + +	memcpy_16(buffer, bufferram + offset, count); + +	this->mmcontrol(mtd, 0); + +	return 0; +} + +/** + * onenand_write_bufferram - [OneNAND Interface] Write the bufferram area + * @param mtd		MTD data structure + * @param area		BufferRAM area + * @param buffer	the databuffer to put/get data + * @param offset	offset to read from or write to + * @param count		number of bytes to read/write + * + * Write the BufferRAM area + */ +static int onenand_write_bufferram(struct mtd_info *mtd, loff_t addr, int area, +				   const unsigned char *buffer, int offset, +				   size_t count) +{ +	struct onenand_chip *this = mtd->priv; +	void __iomem *bufferram; + +	bufferram = this->base + area; +	bufferram += onenand_bufferram_offset(mtd, area); + +	memcpy_16(bufferram + offset, buffer, count); + +	return 0; +} + +/** + * onenand_get_2x_blockpage - [GENERIC] Get blockpage at 2x program mode + * @param mtd		MTD data structure + * @param addr		address to check + * @return		blockpage address + * + * Get blockpage address at 2x program mode + */ +static int onenand_get_2x_blockpage(struct mtd_info *mtd, loff_t addr) +{ +	struct onenand_chip *this = mtd->priv; +	int blockpage, block, page; + +	/* Calculate the even block number */ +	block = (int) (addr >> this->erase_shift) & ~1; +	/* Is it the odd plane? */ +	if (addr & this->writesize) +		block++; +	page = (int) (addr >> (this->page_shift + 1)) & this->page_mask; +	blockpage = (block << 7) | page; + +	return blockpage; +} + +/** + * onenand_check_bufferram - [GENERIC] Check BufferRAM information + * @param mtd		MTD data structure + * @param addr		address to check + * @return		1 if there are valid data, otherwise 0 + * + * Check bufferram if there is data we required + */ +static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) +{ +	struct onenand_chip *this = mtd->priv; +	int blockpage, found = 0; +	unsigned int i; + +	if (ONENAND_IS_2PLANE(this)) +		blockpage = onenand_get_2x_blockpage(mtd, addr); +	else +		blockpage = (int) (addr >> this->page_shift); + +	/* Is there valid data? */ +	i = ONENAND_CURRENT_BUFFERRAM(this); +	if (this->bufferram[i].blockpage == blockpage) +		found = 1; +	else { +		/* Check another BufferRAM */ +		i = ONENAND_NEXT_BUFFERRAM(this); +		if (this->bufferram[i].blockpage == blockpage) { +			ONENAND_SET_NEXT_BUFFERRAM(this); +			found = 1; +		} +	} + +	if (found && ONENAND_IS_DDP(this)) { +		/* Select DataRAM for DDP */ +		int block = onenand_block(this, addr); +		int value = onenand_bufferram_address(this, block); +		this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); +	} + +	return found; +} + +/** + * onenand_update_bufferram - [GENERIC] Update BufferRAM information + * @param mtd		MTD data structure + * @param addr		address to update + * @param valid		valid flag + * + * Update BufferRAM information + */ +static int onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, +				    int valid) +{ +	struct onenand_chip *this = mtd->priv; +	int blockpage; +	unsigned int i; + +	if (ONENAND_IS_2PLANE(this)) +		blockpage = onenand_get_2x_blockpage(mtd, addr); +	else +		blockpage = (int)(addr >> this->page_shift); + +	/* Invalidate another BufferRAM */ +	i = ONENAND_NEXT_BUFFERRAM(this); +	if (this->bufferram[i].blockpage == blockpage) +		this->bufferram[i].blockpage = -1; + +	/* Update BufferRAM */ +	i = ONENAND_CURRENT_BUFFERRAM(this); +	if (valid) +		this->bufferram[i].blockpage = blockpage; +	else +		this->bufferram[i].blockpage = -1; + +	return 0; +} + +/** + * onenand_invalidate_bufferram - [GENERIC] Invalidate BufferRAM information + * @param mtd           MTD data structure + * @param addr          start address to invalidate + * @param len           length to invalidate + * + * Invalidate BufferRAM information + */ +static void onenand_invalidate_bufferram(struct mtd_info *mtd, loff_t addr, +					 unsigned int len) +{ +	struct onenand_chip *this = mtd->priv; +	int i; +	loff_t end_addr = addr + len; + +	/* Invalidate BufferRAM */ +	for (i = 0; i < MAX_BUFFERRAM; i++) { +		loff_t buf_addr = this->bufferram[i].blockpage << this->page_shift; + +		if (buf_addr >= addr && buf_addr < end_addr) +			this->bufferram[i].blockpage = -1; +	} +} + +/** + * onenand_get_device - [GENERIC] Get chip for selected access + * @param mtd		MTD device structure + * @param new_state	the state which is requested + * + * Get the device and lock it for exclusive access + */ +static void onenand_get_device(struct mtd_info *mtd, int new_state) +{ +	/* Do nothing */ +} + +/** + * onenand_release_device - [GENERIC] release chip + * @param mtd		MTD device structure + * + * Deselect, release chip lock and wake up anyone waiting on the device + */ +static void onenand_release_device(struct mtd_info *mtd) +{ +	/* Do nothing */ +} + +/** + * onenand_transfer_auto_oob - [INTERN] oob auto-placement transfer + * @param mtd		MTD device structure + * @param buf		destination address + * @param column	oob offset to read from + * @param thislen	oob length to read + */ +static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, +					int column, int thislen) +{ +	struct onenand_chip *this = mtd->priv; +	struct nand_oobfree *free; +	int readcol = column; +	int readend = column + thislen; +	int lastgap = 0; +	unsigned int i; +	uint8_t *oob_buf = this->oob_buf; + +	free = this->ecclayout->oobfree; +	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES_LARGE && free->length; +	     i++, free++) { +		if (readcol >= lastgap) +			readcol += free->offset - lastgap; +		if (readend >= lastgap) +			readend += free->offset - lastgap; +		lastgap = free->offset + free->length; +	} +	this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); +	free = this->ecclayout->oobfree; +	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES_LARGE && free->length; +	     i++, free++) { +		int free_end = free->offset + free->length; +		if (free->offset < readend && free_end > readcol) { +			int st = max_t(int,free->offset,readcol); +			int ed = min_t(int,free_end,readend); +			int n = ed - st; +			memcpy(buf, oob_buf + st, n); +			buf += n; +		} else if (column == 0) +			break; +	} +	return 0; +} + +/** + * onenand_recover_lsb - [Flex-OneNAND] Recover LSB page data + * @param mtd		MTD device structure + * @param addr		address to recover + * @param status	return value from onenand_wait + * + * MLC NAND Flash cell has paired pages - LSB page and MSB page. LSB page has + * lower page address and MSB page has higher page address in paired pages. + * If power off occurs during MSB page program, the paired LSB page data can + * become corrupt. LSB page recovery read is a way to read LSB page though page + * data are corrupted. When uncorrectable error occurs as a result of LSB page + * read after power up, issue LSB page recovery read. + */ +static int onenand_recover_lsb(struct mtd_info *mtd, loff_t addr, int status) +{ +	struct onenand_chip *this = mtd->priv; +	int i; + +	/* Recovery is only for Flex-OneNAND */ +	if (!FLEXONENAND(this)) +		return status; + +	/* check if we failed due to uncorrectable error */ +	if (!mtd_is_eccerr(status) && status != ONENAND_BBT_READ_ECC_ERROR) +		return status; + +	/* check if address lies in MLC region */ +	i = flexonenand_region(mtd, addr); +	if (mtd->eraseregions[i].erasesize < (1 << this->erase_shift)) +		return status; + +	printk("onenand_recover_lsb:" +		"Attempting to recover from uncorrectable read\n"); + +	/* Issue the LSB page recovery command */ +	this->command(mtd, FLEXONENAND_CMD_RECOVER_LSB, addr, this->writesize); +	return this->wait(mtd, FL_READING); +} + +/** + * onenand_read_ops_nolock - [OneNAND Interface] OneNAND read main and/or out-of-band + * @param mtd		MTD device structure + * @param from		offset to read from + * @param ops		oob operation description structure + * + * OneNAND read main and/or out-of-band data + */ +static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, +		struct mtd_oob_ops *ops) +{ +	struct onenand_chip *this = mtd->priv; +	struct mtd_ecc_stats stats; +	size_t len = ops->len; +	size_t ooblen = ops->ooblen; +	u_char *buf = ops->datbuf; +	u_char *oobbuf = ops->oobbuf; +	int read = 0, column, thislen; +	int oobread = 0, oobcolumn, thisooblen, oobsize; +	int ret = 0, boundary = 0; +	int writesize = this->writesize; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ops_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); + +	if (ops->mode == MTD_OPS_AUTO_OOB) +		oobsize = this->ecclayout->oobavail; +	else +		oobsize = mtd->oobsize; + +	oobcolumn = from & (mtd->oobsize - 1); + +	/* Do not allow reads past end of device */ +	if ((from + len) > mtd->size) { +		printk(KERN_ERR "onenand_read_ops_nolock: Attempt read beyond end of device\n"); +		ops->retlen = 0; +		ops->oobretlen = 0; +		return -EINVAL; +	} + +	stats = mtd->ecc_stats; + +	/* Read-while-load method */ +	/* Note: We can't use this feature in MLC */ + +	/* Do first load to bufferRAM */ +	if (read < len) { +		if (!onenand_check_bufferram(mtd, from)) { +			this->main_buf = buf; +			this->command(mtd, ONENAND_CMD_READ, from, writesize); +			ret = this->wait(mtd, FL_READING); +			if (unlikely(ret)) +				ret = onenand_recover_lsb(mtd, from, ret); +			onenand_update_bufferram(mtd, from, !ret); +			if (ret == -EBADMSG) +				ret = 0; +		} +	} + +	thislen = min_t(int, writesize, len - read); +	column = from & (writesize - 1); +	if (column + thislen > writesize) +		thislen = writesize - column; + +	while (!ret) { +		/* If there is more to load then start next load */ +		from += thislen; +		if (!ONENAND_IS_4KB_PAGE(this) && read + thislen < len) { +			this->main_buf = buf + thislen; +			this->command(mtd, ONENAND_CMD_READ, from, writesize); +			/* +			 * Chip boundary handling in DDP +			 * Now we issued chip 1 read and pointed chip 1 +			 * bufferam so we have to point chip 0 bufferam. +			 */ +			if (ONENAND_IS_DDP(this) && +					unlikely(from == (this->chipsize >> 1))) { +				this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2); +				boundary = 1; +			} else +				boundary = 0; +			ONENAND_SET_PREV_BUFFERRAM(this); +		} + +		/* While load is going, read from last bufferRAM */ +		this->read_bufferram(mtd, from - thislen, ONENAND_DATARAM, buf, column, thislen); + +		/* Read oob area if needed */ +		if (oobbuf) { +			thisooblen = oobsize - oobcolumn; +			thisooblen = min_t(int, thisooblen, ooblen - oobread); + +			if (ops->mode == MTD_OPS_AUTO_OOB) +				onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); +			else +				this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); +			oobread += thisooblen; +			oobbuf += thisooblen; +			oobcolumn = 0; +		} + +		if (ONENAND_IS_4KB_PAGE(this) && (read + thislen < len)) { +			this->command(mtd, ONENAND_CMD_READ, from, writesize); +			ret = this->wait(mtd, FL_READING); +			if (unlikely(ret)) +				ret = onenand_recover_lsb(mtd, from, ret); +			onenand_update_bufferram(mtd, from, !ret); +			if (mtd_is_eccerr(ret)) +				ret = 0; +		} + +		/* See if we are done */ +		read += thislen; +		if (read == len) +			break; +		/* Set up for next read from bufferRAM */ +		if (unlikely(boundary)) +			this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2); +		if (!ONENAND_IS_4KB_PAGE(this)) +			ONENAND_SET_NEXT_BUFFERRAM(this); +		buf += thislen; +		thislen = min_t(int, writesize, len - read); +		column = 0; + +		if (!ONENAND_IS_4KB_PAGE(this)) { +			/* Now wait for load */ +			ret = this->wait(mtd, FL_READING); +			onenand_update_bufferram(mtd, from, !ret); +			if (mtd_is_eccerr(ret)) +				ret = 0; +		} +	} + +	/* +	 * Return success, if no ECC failures, else -EBADMSG +	 * fs driver will take care of that, because +	 * retlen == desired len and result == -EBADMSG +	 */ +	ops->retlen = read; +	ops->oobretlen = oobread; + +	if (ret) +		return ret; + +	if (mtd->ecc_stats.failed - stats.failed) +		return -EBADMSG; + +	/* return max bitflips per ecc step; ONENANDs correct 1 bit only */ +	return mtd->ecc_stats.corrected != stats.corrected ? 1 : 0; +} + +/** + * onenand_read_oob_nolock - [MTD Interface] OneNAND read out-of-band + * @param mtd		MTD device structure + * @param from		offset to read from + * @param ops		oob operation description structure + * + * OneNAND read out-of-band data from the spare area + */ +static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, +		struct mtd_oob_ops *ops) +{ +	struct onenand_chip *this = mtd->priv; +	struct mtd_ecc_stats stats; +	int read = 0, thislen, column, oobsize; +	size_t len = ops->ooblen; +	unsigned int mode = ops->mode; +	u_char *buf = ops->oobbuf; +	int ret = 0, readcmd; + +	from += ops->ooboffs; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len); + +	/* Initialize return length value */ +	ops->oobretlen = 0; + +	if (mode == MTD_OPS_AUTO_OOB) +		oobsize = this->ecclayout->oobavail; +	else +		oobsize = mtd->oobsize; + +	column = from & (mtd->oobsize - 1); + +	if (unlikely(column >= oobsize)) { +		printk(KERN_ERR "onenand_read_oob_nolock: Attempted to start read outside oob\n"); +		return -EINVAL; +	} + +	/* Do not allow reads past end of device */ +	if (unlikely(from >= mtd->size || +		column + len > ((mtd->size >> this->page_shift) - +				(from >> this->page_shift)) * oobsize)) { +		printk(KERN_ERR "onenand_read_oob_nolock: Attempted to read beyond end of device\n"); +		return -EINVAL; +	} + +	stats = mtd->ecc_stats; + +	readcmd = ONENAND_IS_4KB_PAGE(this) ? +		ONENAND_CMD_READ : ONENAND_CMD_READOOB; + +	while (read < len) { +		thislen = oobsize - column; +		thislen = min_t(int, thislen, len); + +		this->spare_buf = buf; +		this->command(mtd, readcmd, from, mtd->oobsize); + +		onenand_update_bufferram(mtd, from, 0); + +		ret = this->wait(mtd, FL_READING); +		if (unlikely(ret)) +			ret = onenand_recover_lsb(mtd, from, ret); + +		if (ret && ret != -EBADMSG) { +			printk(KERN_ERR "onenand_read_oob_nolock: read failed = 0x%x\n", ret); +			break; +		} + +		if (mode == MTD_OPS_AUTO_OOB) +			onenand_transfer_auto_oob(mtd, buf, column, thislen); +		else +			this->read_bufferram(mtd, 0, ONENAND_SPARERAM, buf, column, thislen); + +		read += thislen; + +		if (read == len) +			break; + +		buf += thislen; + +		/* Read more? */ +		if (read < len) { +			/* Page size */ +			from += mtd->writesize; +			column = 0; +		} +	} + +	ops->oobretlen = read; + +	if (ret) +		return ret; + +	if (mtd->ecc_stats.failed - stats.failed) +		return -EBADMSG; + +	return 0; +} + +/** + * onenand_read - [MTD Interface] MTD compability function for onenand_read_ecc + * @param mtd		MTD device structure + * @param from		offset to read from + * @param len		number of bytes to read + * @param retlen	pointer to variable to store the number of read bytes + * @param buf		the databuffer to put data + * + * This function simply calls onenand_read_ecc with oob buffer and oobsel = NULL +*/ +int onenand_read(struct mtd_info *mtd, loff_t from, size_t len, +		 size_t * retlen, u_char * buf) +{ +	struct mtd_oob_ops ops = { +		.len    = len, +		.ooblen = 0, +		.datbuf = buf, +		.oobbuf = NULL, +	}; +	int ret; + +	onenand_get_device(mtd, FL_READING); +	ret = onenand_read_ops_nolock(mtd, from, &ops); +	onenand_release_device(mtd); + +	*retlen = ops.retlen; +	return ret; +} + +/** + * onenand_read_oob - [MTD Interface] OneNAND read out-of-band + * @param mtd		MTD device structure + * @param from		offset to read from + * @param ops		oob operations description structure + * + * OneNAND main and/or out-of-band + */ +int onenand_read_oob(struct mtd_info *mtd, loff_t from, +			struct mtd_oob_ops *ops) +{ +	int ret; + +	switch (ops->mode) { +	case MTD_OPS_PLACE_OOB: +	case MTD_OPS_AUTO_OOB: +		break; +	case MTD_OPS_RAW: +		/* Not implemented yet */ +	default: +		return -EINVAL; +	} + +	onenand_get_device(mtd, FL_READING); +	if (ops->datbuf) +		ret = onenand_read_ops_nolock(mtd, from, ops); +	else +		ret = onenand_read_oob_nolock(mtd, from, ops); +	onenand_release_device(mtd); + +	return ret; +} + +/** + * onenand_bbt_wait - [DEFAULT] wait until the command is done + * @param mtd		MTD device structure + * @param state		state to select the max. timeout value + * + * Wait for command done. + */ +static int onenand_bbt_wait(struct mtd_info *mtd, int state) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int flags = ONENAND_INT_MASTER; +	unsigned int interrupt; +	unsigned int ctrl; + +	while (1) { +		interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); +		if (interrupt & flags) +			break; +	} + +	/* To get correct interrupt status in timeout case */ +	interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); +	ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + +	if (interrupt & ONENAND_INT_READ) { +		int ecc = onenand_read_ecc(this); +		if (ecc & ONENAND_ECC_2BIT_ALL) { +			printk(KERN_INFO "onenand_bbt_wait: ecc error = 0x%04x" +				", controller = 0x%04x\n", ecc, ctrl); +			return ONENAND_BBT_READ_ERROR; +		} +	} else { +		printk(KERN_ERR "onenand_bbt_wait: read timeout!" +				"ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt); +		return ONENAND_BBT_READ_FATAL_ERROR; +	} + +	/* Initial bad block case: 0x2400 or 0x0400 */ +	if (ctrl & ONENAND_CTRL_ERROR) { +		printk(KERN_DEBUG "onenand_bbt_wait: controller error = 0x%04x\n", ctrl); +		return ONENAND_BBT_READ_ERROR; +	} + +	return 0; +} + +/** + * onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan + * @param mtd		MTD device structure + * @param from		offset to read from + * @param ops		oob operation description structure + * + * OneNAND read out-of-band data from the spare area for bbt scan + */ +int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, +		struct mtd_oob_ops *ops) +{ +	struct onenand_chip *this = mtd->priv; +	int read = 0, thislen, column; +	int ret = 0, readcmd; +	size_t len = ops->ooblen; +	u_char *buf = ops->oobbuf; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", (unsigned int) from, len); + +	readcmd = ONENAND_IS_4KB_PAGE(this) ? +		ONENAND_CMD_READ : ONENAND_CMD_READOOB; + +	/* Initialize return value */ +	ops->oobretlen = 0; + +	/* Do not allow reads past end of device */ +	if (unlikely((from + len) > mtd->size)) { +		printk(KERN_ERR "onenand_bbt_read_oob: Attempt read beyond end of device\n"); +		return ONENAND_BBT_READ_FATAL_ERROR; +	} + +	/* Grab the lock and see if the device is available */ +	onenand_get_device(mtd, FL_READING); + +	column = from & (mtd->oobsize - 1); + +	while (read < len) { + +		thislen = mtd->oobsize - column; +		thislen = min_t(int, thislen, len); + +		this->spare_buf = buf; +		this->command(mtd, readcmd, from, mtd->oobsize); + +		onenand_update_bufferram(mtd, from, 0); + +		ret = this->bbt_wait(mtd, FL_READING); +		if (unlikely(ret)) +			ret = onenand_recover_lsb(mtd, from, ret); + +		if (ret) +			break; + +		this->read_bufferram(mtd, 0, ONENAND_SPARERAM, buf, column, thislen); +		read += thislen; +		if (read == len) +			break; + +		buf += thislen; + +		/* Read more? */ +		if (read < len) { +			/* Update Page size */ +			from += this->writesize; +			column = 0; +		} +	} + +	/* Deselect and wake up anyone waiting on the device */ +	onenand_release_device(mtd); + +	ops->oobretlen = read; +	return ret; +} + + +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE +/** + * onenand_verify_oob - [GENERIC] verify the oob contents after a write + * @param mtd           MTD device structure + * @param buf           the databuffer to verify + * @param to            offset to read from + */ +static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to) +{ +	struct onenand_chip *this = mtd->priv; +	u_char *oob_buf = this->oob_buf; +	int status, i, readcmd; + +	readcmd = ONENAND_IS_4KB_PAGE(this) ? +		ONENAND_CMD_READ : ONENAND_CMD_READOOB; + +	this->command(mtd, readcmd, to, mtd->oobsize); +	onenand_update_bufferram(mtd, to, 0); +	status = this->wait(mtd, FL_READING); +	if (status) +		return status; + +	this->read_bufferram(mtd, 0, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); +	for (i = 0; i < mtd->oobsize; i++) +		if (buf[i] != 0xFF && buf[i] != oob_buf[i]) +			return -EBADMSG; + +	return 0; +} + +/** + * onenand_verify - [GENERIC] verify the chip contents after a write + * @param mtd          MTD device structure + * @param buf          the databuffer to verify + * @param addr         offset to read from + * @param len          number of bytes to read and compare + */ +static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len) +{ +	struct onenand_chip *this = mtd->priv; +	void __iomem *dataram; +	int ret = 0; +	int thislen, column; + +	while (len != 0) { +		thislen = min_t(int, this->writesize, len); +		column = addr & (this->writesize - 1); +		if (column + thislen > this->writesize) +			thislen = this->writesize - column; + +		this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); + +		onenand_update_bufferram(mtd, addr, 0); + +		ret = this->wait(mtd, FL_READING); +		if (ret) +			return ret; + +		onenand_update_bufferram(mtd, addr, 1); + +		dataram = this->base + ONENAND_DATARAM; +		dataram += onenand_bufferram_offset(mtd, ONENAND_DATARAM); + +		if (memcmp(buf, dataram + column, thislen)) +			return -EBADMSG; + +		len -= thislen; +		buf += thislen; +		addr += thislen; +	} + +	return 0; +} +#else +#define onenand_verify(...)             (0) +#define onenand_verify_oob(...)         (0) +#endif + +#define NOTALIGNED(x)	((x & (this->subpagesize - 1)) != 0) + +/** + * onenand_fill_auto_oob - [INTERN] oob auto-placement transfer + * @param mtd           MTD device structure + * @param oob_buf       oob buffer + * @param buf           source address + * @param column        oob offset to write to + * @param thislen       oob length to write + */ +static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf, +		const u_char *buf, int column, int thislen) +{ +	struct onenand_chip *this = mtd->priv; +	struct nand_oobfree *free; +	int writecol = column; +	int writeend = column + thislen; +	int lastgap = 0; +	unsigned int i; + +	free = this->ecclayout->oobfree; +	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES_LARGE && free->length; +	     i++, free++) { +		if (writecol >= lastgap) +			writecol += free->offset - lastgap; +		if (writeend >= lastgap) +			writeend += free->offset - lastgap; +		lastgap = free->offset + free->length; +	} +	free = this->ecclayout->oobfree; +	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES_LARGE && free->length; +	     i++, free++) { +		int free_end = free->offset + free->length; +		if (free->offset < writeend && free_end > writecol) { +			int st = max_t(int,free->offset,writecol); +			int ed = min_t(int,free_end,writeend); +			int n = ed - st; +			memcpy(oob_buf + st, buf, n); +			buf += n; +		} else if (column == 0) +			break; +	} +	return 0; +} + +/** + * onenand_write_ops_nolock - [OneNAND Interface] write main and/or out-of-band + * @param mtd           MTD device structure + * @param to            offset to write to + * @param ops           oob operation description structure + * + * Write main and/or oob with ECC + */ +static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, +		struct mtd_oob_ops *ops) +{ +	struct onenand_chip *this = mtd->priv; +	int written = 0, column, thislen, subpage; +	int oobwritten = 0, oobcolumn, thisooblen, oobsize; +	size_t len = ops->len; +	size_t ooblen = ops->ooblen; +	const u_char *buf = ops->datbuf; +	const u_char *oob = ops->oobbuf; +	u_char *oobbuf; +	int ret = 0; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_ops_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); + +	/* Initialize retlen, in case of early exit */ +	ops->retlen = 0; +	ops->oobretlen = 0; + +	/* Reject writes, which are not page aligned */ +	if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) { +		printk(KERN_ERR "onenand_write_ops_nolock: Attempt to write not page aligned data\n"); +		return -EINVAL; +	} + +	if (ops->mode == MTD_OPS_AUTO_OOB) +		oobsize = this->ecclayout->oobavail; +	else +		oobsize = mtd->oobsize; + +	oobcolumn = to & (mtd->oobsize - 1); + +	column = to & (mtd->writesize - 1); + +	/* Loop until all data write */ +	while (written < len) { +		u_char *wbuf = (u_char *) buf; + +		thislen = min_t(int, mtd->writesize - column, len - written); +		thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten); + +		this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); + +		/* Partial page write */ +		subpage = thislen < mtd->writesize; +		if (subpage) { +			memset(this->page_buf, 0xff, mtd->writesize); +			memcpy(this->page_buf + column, buf, thislen); +			wbuf = this->page_buf; +		} + +		this->write_bufferram(mtd, to, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + +		if (oob) { +			oobbuf = this->oob_buf; + +			/* We send data to spare ram with oobsize +			 *                          * to prevent byte access */ +			memset(oobbuf, 0xff, mtd->oobsize); +			if (ops->mode == MTD_OPS_AUTO_OOB) +				onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen); +			else +				memcpy(oobbuf + oobcolumn, oob, thisooblen); + +			oobwritten += thisooblen; +			oob += thisooblen; +			oobcolumn = 0; +		} else +			oobbuf = (u_char *) ffchars; + +		this->write_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + +		this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); + +		ret = this->wait(mtd, FL_WRITING); + +		/* In partial page write we don't update bufferram */ +		onenand_update_bufferram(mtd, to, !ret && !subpage); +		if (ONENAND_IS_2PLANE(this)) { +			ONENAND_SET_BUFFERRAM1(this); +			onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage); +		} + +		if (ret) { +			printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret); +			break; +		} + +		/* Only check verify write turn on */ +		ret = onenand_verify(mtd, buf, to, thislen); +		if (ret) { +			printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret); +			break; +		} + +		written += thislen; + +		if (written == len) +			break; + +		column = 0; +		to += thislen; +		buf += thislen; +	} + +	ops->retlen = written; + +	return ret; +} + +/** + * onenand_write_oob_nolock - [INTERN] OneNAND write out-of-band + * @param mtd           MTD device structure + * @param to            offset to write to + * @param len           number of bytes to write + * @param retlen        pointer to variable to store the number of written bytes + * @param buf           the data to write + * @param mode          operation mode + * + * OneNAND write out-of-band + */ +static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to, +		struct mtd_oob_ops *ops) +{ +	struct onenand_chip *this = mtd->priv; +	int column, ret = 0, oobsize; +	int written = 0, oobcmd; +	u_char *oobbuf; +	size_t len = ops->ooblen; +	const u_char *buf = ops->oobbuf; +	unsigned int mode = ops->mode; + +	to += ops->ooboffs; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len); + +	/* Initialize retlen, in case of early exit */ +	ops->oobretlen = 0; + +	if (mode == MTD_OPS_AUTO_OOB) +		oobsize = this->ecclayout->oobavail; +	else +		oobsize = mtd->oobsize; + +	column = to & (mtd->oobsize - 1); + +	if (unlikely(column >= oobsize)) { +		printk(KERN_ERR "onenand_write_oob_nolock: Attempted to start write outside oob\n"); +		return -EINVAL; +	} + +	/* For compatibility with NAND: Do not allow write past end of page */ +	if (unlikely(column + len > oobsize)) { +		printk(KERN_ERR "onenand_write_oob_nolock: " +				"Attempt to write past end of page\n"); +		return -EINVAL; +	} + +	/* Do not allow reads past end of device */ +	if (unlikely(to >= mtd->size || +				column + len > ((mtd->size >> this->page_shift) - +					(to >> this->page_shift)) * oobsize)) { +		printk(KERN_ERR "onenand_write_oob_nolock: Attempted to write past end of device\n"); +		return -EINVAL; +	} + +	oobbuf = this->oob_buf; + +	oobcmd = ONENAND_IS_4KB_PAGE(this) ? +		ONENAND_CMD_PROG : ONENAND_CMD_PROGOOB; + +	/* Loop until all data write */ +	while (written < len) { +		int thislen = min_t(int, oobsize, len - written); + +		this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); + +		/* We send data to spare ram with oobsize +		 * to prevent byte access */ +		memset(oobbuf, 0xff, mtd->oobsize); +		if (mode == MTD_OPS_AUTO_OOB) +			onenand_fill_auto_oob(mtd, oobbuf, buf, column, thislen); +		else +			memcpy(oobbuf + column, buf, thislen); +		this->write_bufferram(mtd, 0, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + +		if (ONENAND_IS_4KB_PAGE(this)) { +			/* Set main area of DataRAM to 0xff*/ +			memset(this->page_buf, 0xff, mtd->writesize); +			this->write_bufferram(mtd, 0, ONENAND_DATARAM, +				this->page_buf,	0, mtd->writesize); +		} + +		this->command(mtd, oobcmd, to, mtd->oobsize); + +		onenand_update_bufferram(mtd, to, 0); +		if (ONENAND_IS_2PLANE(this)) { +			ONENAND_SET_BUFFERRAM1(this); +			onenand_update_bufferram(mtd, to + this->writesize, 0); +		} + +		ret = this->wait(mtd, FL_WRITING); +		if (ret) { +			printk(KERN_ERR "onenand_write_oob_nolock: write failed %d\n", ret); +			break; +		} + +		ret = onenand_verify_oob(mtd, oobbuf, to); +		if (ret) { +			printk(KERN_ERR "onenand_write_oob_nolock: verify failed %d\n", ret); +			break; +		} + +		written += thislen; +		if (written == len) +			break; + +		to += mtd->writesize; +		buf += thislen; +		column = 0; +	} + +	ops->oobretlen = written; + +	return ret; +} + +/** + * onenand_write - [MTD Interface] compability function for onenand_write_ecc + * @param mtd		MTD device structure + * @param to		offset to write to + * @param len		number of bytes to write + * @param retlen	pointer to variable to store the number of written bytes + * @param buf		the data to write + * + * Write with ECC + */ +int onenand_write(struct mtd_info *mtd, loff_t to, size_t len, +		  size_t * retlen, const u_char * buf) +{ +	struct mtd_oob_ops ops = { +		.len    = len, +		.ooblen = 0, +		.datbuf = (u_char *) buf, +		.oobbuf = NULL, +	}; +	int ret; + +	onenand_get_device(mtd, FL_WRITING); +	ret = onenand_write_ops_nolock(mtd, to, &ops); +	onenand_release_device(mtd); + +	*retlen = ops.retlen; +	return ret; +} + +/** + * onenand_write_oob - [MTD Interface] OneNAND write out-of-band + * @param mtd		MTD device structure + * @param to		offset to write to + * @param ops		oob operation description structure + * + * OneNAND write main and/or out-of-band + */ +int onenand_write_oob(struct mtd_info *mtd, loff_t to, +			struct mtd_oob_ops *ops) +{ +	int ret; + +	switch (ops->mode) { +	case MTD_OPS_PLACE_OOB: +	case MTD_OPS_AUTO_OOB: +		break; +	case MTD_OPS_RAW: +		/* Not implemented yet */ +	default: +		return -EINVAL; +	} + +	onenand_get_device(mtd, FL_WRITING); +	if (ops->datbuf) +		ret = onenand_write_ops_nolock(mtd, to, ops); +	else +		ret = onenand_write_oob_nolock(mtd, to, ops); +	onenand_release_device(mtd); + +	return ret; + +} + +/** + * onenand_block_isbad_nolock - [GENERIC] Check if a block is marked bad + * @param mtd		MTD device structure + * @param ofs		offset from device start + * @param allowbbt	1, if its allowed to access the bbt area + * + * Check, if the block is bad, Either by reading the bad block table or + * calling of the scan function. + */ +static int onenand_block_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allowbbt) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm = this->bbm; + +	/* Return info from the table */ +	return bbm->isbad_bbt(mtd, ofs, allowbbt); +} + + +/** + * onenand_erase - [MTD Interface] erase block(s) + * @param mtd		MTD device structure + * @param instr		erase instruction + * + * Erase one ore more blocks + */ +int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int block_size; +	loff_t addr = instr->addr; +	unsigned int len = instr->len; +	int ret = 0, i; +	struct mtd_erase_region_info *region = NULL; +	unsigned int region_end = 0; + +	MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_erase: start = 0x%08x, len = %i\n", +			(unsigned int) addr, len); + +	if (FLEXONENAND(this)) { +		/* Find the eraseregion of this address */ +		i = flexonenand_region(mtd, addr); +		region = &mtd->eraseregions[i]; + +		block_size = region->erasesize; +		region_end = region->offset +			+ region->erasesize * region->numblocks; + +		/* Start address within region must align on block boundary. +		 * Erase region's start offset is always block start address. +		 */ +		if (unlikely((addr - region->offset) & (block_size - 1))) { +			MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_erase:" +				" Unaligned address\n"); +			return -EINVAL; +		} +	} else { +		block_size = 1 << this->erase_shift; + +		/* Start address must align on block boundary */ +		if (unlikely(addr & (block_size - 1))) { +			MTDDEBUG(MTD_DEBUG_LEVEL0, "onenand_erase:" +						"Unaligned address\n"); +			return -EINVAL; +		} +	} + +	/* Length must align on block boundary */ +	if (unlikely(len & (block_size - 1))) { +		MTDDEBUG (MTD_DEBUG_LEVEL0, +			 "onenand_erase: Length not block aligned\n"); +		return -EINVAL; +	} + +	/* Grab the lock and see if the device is available */ +	onenand_get_device(mtd, FL_ERASING); + +	/* Loop throught the pages */ +	instr->state = MTD_ERASING; + +	while (len) { + +		/* Check if we have a bad block, we do not erase bad blocks */ +		if (instr->priv == 0 && onenand_block_isbad_nolock(mtd, addr, 0)) { +			printk(KERN_WARNING "onenand_erase: attempt to erase" +				" a bad block at addr 0x%08x\n", +				(unsigned int) addr); +			instr->state = MTD_ERASE_FAILED; +			goto erase_exit; +		} + +		this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + +		onenand_invalidate_bufferram(mtd, addr, block_size); + +		ret = this->wait(mtd, FL_ERASING); +		/* Check, if it is write protected */ +		if (ret) { +			if (ret == -EPERM) +				MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_erase: " +					  "Device is write protected!!!\n"); +			else +				MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_erase: " +					  "Failed erase, block %d\n", +					onenand_block(this, addr)); +			instr->state = MTD_ERASE_FAILED; +			instr->fail_addr = addr; + +			goto erase_exit; +		} + +		len -= block_size; +		addr += block_size; + +		if (addr == region_end) { +			if (!len) +				break; +			region++; + +			block_size = region->erasesize; +			region_end = region->offset +				+ region->erasesize * region->numblocks; + +			if (len & (block_size - 1)) { +				/* This has been checked at MTD +				 * partitioning level. */ +				printk("onenand_erase: Unaligned address\n"); +				goto erase_exit; +			} +		} +	} + +	instr->state = MTD_ERASE_DONE; + +erase_exit: + +	ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO; +	/* Do call back function */ +	if (!ret) +		mtd_erase_callback(instr); + +	/* Deselect and wake up anyone waiting on the device */ +	onenand_release_device(mtd); + +	return ret; +} + +/** + * onenand_sync - [MTD Interface] sync + * @param mtd		MTD device structure + * + * Sync is actually a wait for chip ready function + */ +void onenand_sync(struct mtd_info *mtd) +{ +	MTDDEBUG (MTD_DEBUG_LEVEL3, "onenand_sync: called\n"); + +	/* Grab the lock and see if the device is available */ +	onenand_get_device(mtd, FL_SYNCING); + +	/* Release it and go back */ +	onenand_release_device(mtd); +} + +/** + * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad + * @param mtd		MTD device structure + * @param ofs		offset relative to mtd start + * + * Check whether the block is bad + */ +int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ +	int ret; + +	/* Check for invalid offset */ +	if (ofs > mtd->size) +		return -EINVAL; + +	onenand_get_device(mtd, FL_READING); +	ret = onenand_block_isbad_nolock(mtd,ofs, 0); +	onenand_release_device(mtd); +	return ret; +} + +/** + * onenand_default_block_markbad - [DEFAULT] mark a block bad + * @param mtd           MTD device structure + * @param ofs           offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. + */ +static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm = this->bbm; +	u_char buf[2] = {0, 0}; +	struct mtd_oob_ops ops = { +		.mode = MTD_OPS_PLACE_OOB, +		.ooblen = 2, +		.oobbuf = buf, +		.ooboffs = 0, +	}; +	int block; + +	/* Get block number */ +	block = onenand_block(this, ofs); +	if (bbm->bbt) +		bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + +	/* We write two bytes, so we dont have to mess with 16 bit access */ +	ofs += mtd->oobsize + (bbm->badblockpos & ~0x01); +	return onenand_write_oob_nolock(mtd, ofs, &ops); +} + +/** + * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad + * @param mtd		MTD device structure + * @param ofs		offset relative to mtd start + * + * Mark the block as bad + */ +int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ +	int ret; + +	ret = onenand_block_isbad(mtd, ofs); +	if (ret) { +		/* If it was bad already, return success and do nothing */ +		if (ret > 0) +			return 0; +		return ret; +	} + +	ret = mtd_block_markbad(mtd, ofs); +	return ret; +} + +/** + * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s) + * @param mtd           MTD device structure + * @param ofs           offset relative to mtd start + * @param len           number of bytes to lock or unlock + * @param cmd           lock or unlock command + * + * Lock or unlock one or more blocks + */ +static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd) +{ +	struct onenand_chip *this = mtd->priv; +	int start, end, block, value, status; + +	start = onenand_block(this, ofs); +	end = onenand_block(this, ofs + len); + +	/* Continuous lock scheme */ +	if (this->options & ONENAND_HAS_CONT_LOCK) { +		/* Set start block address */ +		this->write_word(start, +				 this->base + ONENAND_REG_START_BLOCK_ADDRESS); +		/* Set end block address */ +		this->write_word(end - 1, +				 this->base + ONENAND_REG_END_BLOCK_ADDRESS); +		/* Write unlock command */ +		this->command(mtd, cmd, 0, 0); + +		/* There's no return value */ +		this->wait(mtd, FL_UNLOCKING); + +		/* Sanity check */ +		while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) +		       & ONENAND_CTRL_ONGO) +			continue; + +		/* Check lock status */ +		status = this->read_word(this->base + ONENAND_REG_WP_STATUS); +		if (!(status & ONENAND_WP_US)) +			printk(KERN_ERR "wp status = 0x%x\n", status); + +		return 0; +	} + +	/* Block lock scheme */ +	for (block = start; block < end; block++) { +		/* Set block address */ +		value = onenand_block_address(this, block); +		this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); +		/* Select DataRAM for DDP */ +		value = onenand_bufferram_address(this, block); +		this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + +		/* Set start block address */ +		this->write_word(block, +				 this->base + ONENAND_REG_START_BLOCK_ADDRESS); +		/* Write unlock command */ +		this->command(mtd, ONENAND_CMD_UNLOCK, 0, 0); + +		/* There's no return value */ +		this->wait(mtd, FL_UNLOCKING); + +		/* Sanity check */ +		while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) +		       & ONENAND_CTRL_ONGO) +			continue; + +		/* Check lock status */ +		status = this->read_word(this->base + ONENAND_REG_WP_STATUS); +		if (!(status & ONENAND_WP_US)) +			printk(KERN_ERR "block = %d, wp status = 0x%x\n", +			       block, status); +	} + +	return 0; +} + +#ifdef ONENAND_LINUX +/** + * onenand_lock - [MTD Interface] Lock block(s) + * @param mtd           MTD device structure + * @param ofs           offset relative to mtd start + * @param len           number of bytes to unlock + * + * Lock one or more blocks + */ +static int onenand_lock(struct mtd_info *mtd, loff_t ofs, size_t len) +{ +	int ret; + +	onenand_get_device(mtd, FL_LOCKING); +	ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK); +	onenand_release_device(mtd); +	return ret; +} + +/** + * onenand_unlock - [MTD Interface] Unlock block(s) + * @param mtd           MTD device structure + * @param ofs           offset relative to mtd start + * @param len           number of bytes to unlock + * + * Unlock one or more blocks + */ +static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) +{ +	int ret; + +	onenand_get_device(mtd, FL_LOCKING); +	ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +	onenand_release_device(mtd); +	return ret; +} +#endif + +/** + * onenand_check_lock_status - [OneNAND Interface] Check lock status + * @param this          onenand chip data structure + * + * Check lock status + */ +static int onenand_check_lock_status(struct onenand_chip *this) +{ +	unsigned int value, block, status; +	unsigned int end; + +	end = this->chipsize >> this->erase_shift; +	for (block = 0; block < end; block++) { +		/* Set block address */ +		value = onenand_block_address(this, block); +		this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); +		/* Select DataRAM for DDP */ +		value = onenand_bufferram_address(this, block); +		this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); +		/* Set start block address */ +		this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + +		/* Check lock status */ +		status = this->read_word(this->base + ONENAND_REG_WP_STATUS); +		if (!(status & ONENAND_WP_US)) { +			printk(KERN_ERR "block = %d, wp status = 0x%x\n", block, status); +			return 0; +		} +	} + +	return 1; +} + +/** + * onenand_unlock_all - [OneNAND Interface] unlock all blocks + * @param mtd           MTD device structure + * + * Unlock all blocks + */ +static void onenand_unlock_all(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	loff_t ofs = 0; +	size_t len = mtd->size; + +	if (this->options & ONENAND_HAS_UNLOCK_ALL) { +		/* Set start block address */ +		this->write_word(0, this->base + ONENAND_REG_START_BLOCK_ADDRESS); +		/* Write unlock command */ +		this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + +		/* There's no return value */ +		this->wait(mtd, FL_LOCKING); + +		/* Sanity check */ +		while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) +				& ONENAND_CTRL_ONGO) +			continue; + +		/* Check lock status */ +		if (onenand_check_lock_status(this)) +			return; + +		/* Workaround for all block unlock in DDP */ +		if (ONENAND_IS_DDP(this) && !FLEXONENAND(this)) { +			/* All blocks on another chip */ +			ofs = this->chipsize >> 1; +			len = this->chipsize >> 1; +		} +	} + +	onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +} + + +/** + * onenand_check_features - Check and set OneNAND features + * @param mtd           MTD data structure + * + * Check and set OneNAND features + * - lock scheme + * - two plane + */ +static void onenand_check_features(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int density, process; + +	/* Lock scheme depends on density and process */ +	density = onenand_get_density(this->device_id); +	process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT; + +	/* Lock scheme */ +	switch (density) { +	case ONENAND_DEVICE_DENSITY_4Gb: +		if (ONENAND_IS_DDP(this)) +			this->options |= ONENAND_HAS_2PLANE; +		else +			this->options |= ONENAND_HAS_4KB_PAGE; + +	case ONENAND_DEVICE_DENSITY_2Gb: +		/* 2Gb DDP don't have 2 plane */ +		if (!ONENAND_IS_DDP(this)) +			this->options |= ONENAND_HAS_2PLANE; +		this->options |= ONENAND_HAS_UNLOCK_ALL; + +	case ONENAND_DEVICE_DENSITY_1Gb: +		/* A-Die has all block unlock */ +		if (process) +			this->options |= ONENAND_HAS_UNLOCK_ALL; +		break; + +	default: +		/* Some OneNAND has continuous lock scheme */ +		if (!process) +			this->options |= ONENAND_HAS_CONT_LOCK; +		break; +	} + +	if (ONENAND_IS_MLC(this)) +		this->options |= ONENAND_HAS_4KB_PAGE; + +	if (ONENAND_IS_4KB_PAGE(this)) +		this->options &= ~ONENAND_HAS_2PLANE; + +	if (FLEXONENAND(this)) { +		this->options &= ~ONENAND_HAS_CONT_LOCK; +		this->options |= ONENAND_HAS_UNLOCK_ALL; +	} + +	if (this->options & ONENAND_HAS_CONT_LOCK) +		printk(KERN_DEBUG "Lock scheme is Continuous Lock\n"); +	if (this->options & ONENAND_HAS_UNLOCK_ALL) +		printk(KERN_DEBUG "Chip support all block unlock\n"); +	if (this->options & ONENAND_HAS_2PLANE) +		printk(KERN_DEBUG "Chip has 2 plane\n"); +	if (this->options & ONENAND_HAS_4KB_PAGE) +		printk(KERN_DEBUG "Chip has 4KiB pagesize\n"); + +} + +/** + * onenand_print_device_info - Print device ID + * @param device        device ID + * + * Print device ID + */ +char *onenand_print_device_info(int device, int version) +{ +	int vcc, demuxed, ddp, density, flexonenand; +	char *dev_info = malloc(80); +	char *p = dev_info; + +	vcc = device & ONENAND_DEVICE_VCC_MASK; +	demuxed = device & ONENAND_DEVICE_IS_DEMUX; +	ddp = device & ONENAND_DEVICE_IS_DDP; +	density = onenand_get_density(device); +	flexonenand = device & DEVICE_IS_FLEXONENAND; +	p += sprintf(dev_info, "%s%sOneNAND%s %dMB %sV 16-bit (0x%02x)", +	       demuxed ? "" : "Muxed ", +	       flexonenand ? "Flex-" : "", +	       ddp ? "(DDP)" : "", +	       (16 << density), vcc ? "2.65/3.3" : "1.8", device); + +	sprintf(p, "\nOneNAND version = 0x%04x", version); +	printk("%s\n", dev_info); + +	return dev_info; +} + +static const struct onenand_manufacturers onenand_manuf_ids[] = { +	{ONENAND_MFR_NUMONYX, "Numonyx"}, +	{ONENAND_MFR_SAMSUNG, "Samsung"}, +}; + +/** + * onenand_check_maf - Check manufacturer ID + * @param manuf         manufacturer ID + * + * Check manufacturer ID + */ +static int onenand_check_maf(int manuf) +{ +	int size = ARRAY_SIZE(onenand_manuf_ids); +	int i; +#ifdef ONENAND_DEBUG +	char *name; +#endif + +	for (i = 0; i < size; i++) +		if (manuf == onenand_manuf_ids[i].id) +			break; + +#ifdef ONENAND_DEBUG +	if (i < size) +		name = onenand_manuf_ids[i].name; +	else +		name = "Unknown"; + +	printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf); +#endif + +	return i == size; +} + +/** +* flexonenand_get_boundary	- Reads the SLC boundary +* @param onenand_info		- onenand info structure +* +* Fill up boundary[] field in onenand_chip +**/ +static int flexonenand_get_boundary(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int die, bdry; +	int syscfg, locked; + +	/* Disable ECC */ +	syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); +	this->write_word((syscfg | 0x0100), this->base + ONENAND_REG_SYS_CFG1); + +	for (die = 0; die < this->dies; die++) { +		this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); +		this->wait(mtd, FL_SYNCING); + +		this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); +		this->wait(mtd, FL_READING); + +		bdry = this->read_word(this->base + ONENAND_DATARAM); +		if ((bdry >> FLEXONENAND_PI_UNLOCK_SHIFT) == 3) +			locked = 0; +		else +			locked = 1; +		this->boundary[die] = bdry & FLEXONENAND_PI_MASK; + +		this->command(mtd, ONENAND_CMD_RESET, 0, 0); +		this->wait(mtd, FL_RESETING); + +		printk(KERN_INFO "Die %d boundary: %d%s\n", die, +		       this->boundary[die], locked ? "(Locked)" : "(Unlocked)"); +	} + +	/* Enable ECC */ +	this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); +	return 0; +} + +/** + * flexonenand_get_size - Fill up fields in onenand_chip and mtd_info + * 			  boundary[], diesize[], mtd->size, mtd->erasesize, + * 			  mtd->eraseregions + * @param mtd		- MTD device structure + */ +static void flexonenand_get_size(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	int die, i, eraseshift, density; +	int blksperdie, maxbdry; +	loff_t ofs; + +	density = onenand_get_density(this->device_id); +	blksperdie = ((loff_t)(16 << density) << 20) >> (this->erase_shift); +	blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; +	maxbdry = blksperdie - 1; +	eraseshift = this->erase_shift - 1; + +	mtd->numeraseregions = this->dies << 1; + +	/* This fills up the device boundary */ +	flexonenand_get_boundary(mtd); +	die = 0; +	ofs = 0; +	i = -1; +	for (; die < this->dies; die++) { +		if (!die || this->boundary[die-1] != maxbdry) { +			i++; +			mtd->eraseregions[i].offset = ofs; +			mtd->eraseregions[i].erasesize = 1 << eraseshift; +			mtd->eraseregions[i].numblocks = +							this->boundary[die] + 1; +			ofs += mtd->eraseregions[i].numblocks << eraseshift; +			eraseshift++; +		} else { +			mtd->numeraseregions -= 1; +			mtd->eraseregions[i].numblocks += +							this->boundary[die] + 1; +			ofs += (this->boundary[die] + 1) << (eraseshift - 1); +		} +		if (this->boundary[die] != maxbdry) { +			i++; +			mtd->eraseregions[i].offset = ofs; +			mtd->eraseregions[i].erasesize = 1 << eraseshift; +			mtd->eraseregions[i].numblocks = maxbdry ^ +							 this->boundary[die]; +			ofs += mtd->eraseregions[i].numblocks << eraseshift; +			eraseshift--; +		} else +			mtd->numeraseregions -= 1; +	} + +	/* Expose MLC erase size except when all blocks are SLC */ +	mtd->erasesize = 1 << this->erase_shift; +	if (mtd->numeraseregions == 1) +		mtd->erasesize >>= 1; + +	printk(KERN_INFO "Device has %d eraseregions\n", mtd->numeraseregions); +	for (i = 0; i < mtd->numeraseregions; i++) +		printk(KERN_INFO "[offset: 0x%08llx, erasesize: 0x%05x," +			" numblocks: %04u]\n", mtd->eraseregions[i].offset, +			mtd->eraseregions[i].erasesize, +			mtd->eraseregions[i].numblocks); + +	for (die = 0, mtd->size = 0; die < this->dies; die++) { +		this->diesize[die] = (loff_t) (blksperdie << this->erase_shift); +		this->diesize[die] -= (loff_t) (this->boundary[die] + 1) +						 << (this->erase_shift - 1); +		mtd->size += this->diesize[die]; +	} +} + +/** + * flexonenand_check_blocks_erased - Check if blocks are erased + * @param mtd_info	- mtd info structure + * @param start		- first erase block to check + * @param end		- last erase block to check + * + * Converting an unerased block from MLC to SLC + * causes byte values to change. Since both data and its ECC + * have changed, reads on the block give uncorrectable error. + * This might lead to the block being detected as bad. + * + * Avoid this by ensuring that the block to be converted is + * erased. + */ +static int flexonenand_check_blocks_erased(struct mtd_info *mtd, +					int start, int end) +{ +	struct onenand_chip *this = mtd->priv; +	int i, ret; +	int block; +	struct mtd_oob_ops ops = { +		.mode = MTD_OPS_PLACE_OOB, +		.ooboffs = 0, +		.ooblen	= mtd->oobsize, +		.datbuf	= NULL, +		.oobbuf	= this->oob_buf, +	}; +	loff_t addr; + +	printk(KERN_DEBUG "Check blocks from %d to %d\n", start, end); + +	for (block = start; block <= end; block++) { +		addr = flexonenand_addr(this, block); +		if (onenand_block_isbad_nolock(mtd, addr, 0)) +			continue; + +		/* +		 * Since main area write results in ECC write to spare, +		 * it is sufficient to check only ECC bytes for change. +		 */ +		ret = onenand_read_oob_nolock(mtd, addr, &ops); +		if (ret) +			return ret; + +		for (i = 0; i < mtd->oobsize; i++) +			if (this->oob_buf[i] != 0xff) +				break; + +		if (i != mtd->oobsize) { +			printk(KERN_WARNING "Block %d not erased.\n", block); +			return 1; +		} +	} + +	return 0; +} + +/** + * flexonenand_set_boundary	- Writes the SLC boundary + * @param mtd			- mtd info structure + */ +int flexonenand_set_boundary(struct mtd_info *mtd, int die, +				    int boundary, int lock) +{ +	struct onenand_chip *this = mtd->priv; +	int ret, density, blksperdie, old, new, thisboundary; +	loff_t addr; + +	if (die >= this->dies) +		return -EINVAL; + +	if (boundary == this->boundary[die]) +		return 0; + +	density = onenand_get_density(this->device_id); +	blksperdie = ((16 << density) << 20) >> this->erase_shift; +	blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; + +	if (boundary >= blksperdie) { +		printk("flexonenand_set_boundary:" +			"Invalid boundary value. " +			"Boundary not changed.\n"); +		return -EINVAL; +	} + +	/* Check if converting blocks are erased */ +	old = this->boundary[die] + (die * this->density_mask); +	new = boundary + (die * this->density_mask); +	ret = flexonenand_check_blocks_erased(mtd, min(old, new) +						+ 1, max(old, new)); +	if (ret) { +		printk(KERN_ERR "flexonenand_set_boundary: Please erase blocks before boundary change\n"); +		return ret; +	} + +	this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); +	this->wait(mtd, FL_SYNCING); + +	/* Check is boundary is locked */ +	this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); +	ret = this->wait(mtd, FL_READING); + +	thisboundary = this->read_word(this->base + ONENAND_DATARAM); +	if ((thisboundary >> FLEXONENAND_PI_UNLOCK_SHIFT) != 3) { +		printk(KERN_ERR "flexonenand_set_boundary: boundary locked\n"); +		goto out; +	} + +	printk(KERN_INFO "flexonenand_set_boundary: Changing die %d boundary: %d%s\n", +			die, boundary, lock ? "(Locked)" : "(Unlocked)"); + +	boundary &= FLEXONENAND_PI_MASK; +	boundary |= lock ? 0 : (3 << FLEXONENAND_PI_UNLOCK_SHIFT); + +	addr = die ? this->diesize[0] : 0; +	this->command(mtd, ONENAND_CMD_ERASE, addr, 0); +	ret = this->wait(mtd, FL_ERASING); +	if (ret) { +		printk("flexonenand_set_boundary:" +			"Failed PI erase for Die %d\n", die); +		goto out; +	} + +	this->write_word(boundary, this->base + ONENAND_DATARAM); +	this->command(mtd, ONENAND_CMD_PROG, addr, 0); +	ret = this->wait(mtd, FL_WRITING); +	if (ret) { +		printk("flexonenand_set_boundary:" +			"Failed PI write for Die %d\n", die); +		goto out; +	} + +	this->command(mtd, FLEXONENAND_CMD_PI_UPDATE, die, 0); +	ret = this->wait(mtd, FL_WRITING); +out: +	this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_REG_COMMAND); +	this->wait(mtd, FL_RESETING); +	if (!ret) +		/* Recalculate device size on boundary change*/ +		flexonenand_get_size(mtd); + +	return ret; +} + +/** + * onenand_chip_probe - [OneNAND Interface] Probe the OneNAND chip + * @param mtd		MTD device structure + * + * OneNAND detection method: + *   Compare the the values from command with ones from register + */ +static int onenand_chip_probe(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	int bram_maf_id, bram_dev_id, maf_id, dev_id; +	int syscfg; + +	/* Save system configuration 1 */ +	syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + +	/* Clear Sync. Burst Read mode to read BootRAM */ +	this->write_word((syscfg & ~ONENAND_SYS_CFG1_SYNC_READ), +			 this->base + ONENAND_REG_SYS_CFG1); + +	/* Send the command for reading device ID from BootRAM */ +	this->write_word(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM); + +	/* Read manufacturer and device IDs from BootRAM */ +	bram_maf_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x0); +	bram_dev_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x2); + +	/* Reset OneNAND to read default register values */ +	this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); + +	/* Wait reset */ +	this->wait(mtd, FL_RESETING); + +	/* Restore system configuration 1 */ +	this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + +	/* Check manufacturer ID */ +	if (onenand_check_maf(bram_maf_id)) +		return -ENXIO; + +	/* Read manufacturer and device IDs from Register */ +	maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID); +	dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); + +	/* Check OneNAND device */ +	if (maf_id != bram_maf_id || dev_id != bram_dev_id) +		return -ENXIO; + +	return 0; +} + +/** + * onenand_probe - [OneNAND Interface] Probe the OneNAND device + * @param mtd		MTD device structure + * + * OneNAND detection method: + *   Compare the the values from command with ones from register + */ +int onenand_probe(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	int dev_id, ver_id; +	int density; +	int ret; + +	ret = this->chip_probe(mtd); +	if (ret) +		return ret; + +	/* Read device IDs from Register */ +	dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); +	ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); +	this->technology = this->read_word(this->base + ONENAND_REG_TECHNOLOGY); + +	/* Flash device information */ +	mtd->name = onenand_print_device_info(dev_id, ver_id); +	this->device_id = dev_id; +	this->version_id = ver_id; + +	/* Check OneNAND features */ +	onenand_check_features(mtd); + +	density = onenand_get_density(dev_id); +	if (FLEXONENAND(this)) { +		this->dies = ONENAND_IS_DDP(this) ? 2 : 1; +		/* Maximum possible erase regions */ +		mtd->numeraseregions = this->dies << 1; +		mtd->eraseregions = malloc(sizeof(struct mtd_erase_region_info) +					* (this->dies << 1)); +		if (!mtd->eraseregions) +			return -ENOMEM; +	} + +	/* +	 * For Flex-OneNAND, chipsize represents maximum possible device size. +	 * mtd->size represents the actual device size. +	 */ +	this->chipsize = (16 << density) << 20; + +	/* OneNAND page size & block size */ +	/* The data buffer size is equal to page size */ +	mtd->writesize = +	    this->read_word(this->base + ONENAND_REG_DATA_BUFFER_SIZE); +	/* We use the full BufferRAM */ +	if (ONENAND_IS_4KB_PAGE(this)) +		mtd->writesize <<= 1; + +	mtd->oobsize = mtd->writesize >> 5; +	/* Pagers per block is always 64 in OneNAND */ +	mtd->erasesize = mtd->writesize << 6; +	/* +	 * Flex-OneNAND SLC area has 64 pages per block. +	 * Flex-OneNAND MLC area has 128 pages per block. +	 * Expose MLC erase size to find erase_shift and page_mask. +	 */ +	if (FLEXONENAND(this)) +		mtd->erasesize <<= 1; + +	this->erase_shift = ffs(mtd->erasesize) - 1; +	this->page_shift = ffs(mtd->writesize) - 1; +	this->ppb_shift = (this->erase_shift - this->page_shift); +	this->page_mask = (mtd->erasesize / mtd->writesize) - 1; +	/* Set density mask. it is used for DDP */ +	if (ONENAND_IS_DDP(this)) +		this->density_mask = this->chipsize >> (this->erase_shift + 1); +	/* It's real page size */ +	this->writesize = mtd->writesize; + +	/* REVIST: Multichip handling */ + +	if (FLEXONENAND(this)) +		flexonenand_get_size(mtd); +	else +		mtd->size = this->chipsize; + +	mtd->flags = MTD_CAP_NANDFLASH; +	mtd->_erase = onenand_erase; +	mtd->_read = onenand_read; +	mtd->_write = onenand_write; +	mtd->_read_oob = onenand_read_oob; +	mtd->_write_oob = onenand_write_oob; +	mtd->_sync = onenand_sync; +	mtd->_block_isbad = onenand_block_isbad; +	mtd->_block_markbad = onenand_block_markbad; + +	return 0; +} + +/** + * onenand_scan - [OneNAND Interface] Scan for the OneNAND device + * @param mtd		MTD device structure + * @param maxchips	Number of chips to scan for + * + * This fills out all the not initialized function pointers + * with the defaults. + * The flash ID is read and the mtd/chip structures are + * filled with the appropriate values. + */ +int onenand_scan(struct mtd_info *mtd, int maxchips) +{ +	int i; +	struct onenand_chip *this = mtd->priv; + +	if (!this->read_word) +		this->read_word = onenand_readw; +	if (!this->write_word) +		this->write_word = onenand_writew; + +	if (!this->command) +		this->command = onenand_command; +	if (!this->wait) +		this->wait = onenand_wait; +	if (!this->bbt_wait) +		this->bbt_wait = onenand_bbt_wait; + +	if (!this->read_bufferram) +		this->read_bufferram = onenand_read_bufferram; +	if (!this->write_bufferram) +		this->write_bufferram = onenand_write_bufferram; + +	if (!this->chip_probe) +		this->chip_probe = onenand_chip_probe; + +	if (!this->block_markbad) +		this->block_markbad = onenand_default_block_markbad; +	if (!this->scan_bbt) +		this->scan_bbt = onenand_default_bbt; + +	if (onenand_probe(mtd)) +		return -ENXIO; + +	/* Set Sync. Burst Read after probing */ +	if (this->mmcontrol) { +		printk(KERN_INFO "OneNAND Sync. Burst Read support\n"); +		this->read_bufferram = onenand_sync_read_bufferram; +	} + +	/* Allocate buffers, if necessary */ +	if (!this->page_buf) { +		this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL); +		if (!this->page_buf) { +			printk(KERN_ERR "onenand_scan(): Can't allocate page_buf\n"); +			return -ENOMEM; +		} +		this->options |= ONENAND_PAGEBUF_ALLOC; +	} +	if (!this->oob_buf) { +		this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL); +		if (!this->oob_buf) { +			printk(KERN_ERR "onenand_scan: Can't allocate oob_buf\n"); +			if (this->options & ONENAND_PAGEBUF_ALLOC) { +				this->options &= ~ONENAND_PAGEBUF_ALLOC; +				kfree(this->page_buf); +			} +			return -ENOMEM; +		} +		this->options |= ONENAND_OOBBUF_ALLOC; +	} + +	this->state = FL_READY; + +	/* +	 * Allow subpage writes up to oobsize. +	 */ +	switch (mtd->oobsize) { +	case 128: +		this->ecclayout = &onenand_oob_128; +		mtd->subpage_sft = 0; +		break; + +	case 64: +		this->ecclayout = &onenand_oob_64; +		mtd->subpage_sft = 2; +		break; + +	case 32: +		this->ecclayout = &onenand_oob_32; +		mtd->subpage_sft = 1; +		break; + +	default: +		printk(KERN_WARNING "No OOB scheme defined for oobsize %d\n", +			mtd->oobsize); +		mtd->subpage_sft = 0; +		/* To prevent kernel oops */ +		this->ecclayout = &onenand_oob_32; +		break; +	} + +	this->subpagesize = mtd->writesize >> mtd->subpage_sft; + +	/* +	 * The number of bytes available for a client to place data into +	 * the out of band area +	 */ +	this->ecclayout->oobavail = 0; + +	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES_LARGE && +	    this->ecclayout->oobfree[i].length; i++) +		this->ecclayout->oobavail += +			this->ecclayout->oobfree[i].length; +	mtd->oobavail = this->ecclayout->oobavail; + +	mtd->ecclayout = this->ecclayout; + +	/* Unlock whole block */ +	onenand_unlock_all(mtd); + +	return this->scan_bbt(mtd); +} + +/** + * onenand_release - [OneNAND Interface] Free resources held by the OneNAND device + * @param mtd		MTD device structure + */ +void onenand_release(struct mtd_info *mtd) +{ +} diff --git a/roms/u-boot/drivers/mtd/onenand/onenand_bbt.c b/roms/u-boot/drivers/mtd/onenand/onenand_bbt.c new file mode 100644 index 00000000..0267c2c5 --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/onenand_bbt.c @@ -0,0 +1,266 @@ +/* + *  linux/drivers/mtd/onenand/onenand_bbt.c + * + *  Bad Block Table support for the OneNAND driver + * + *  Copyright(c) 2005-2008 Samsung Electronics + *  Kyungmin Park <kyungmin.park@samsung.com> + * + *  TODO: + *    Split BBT core and chip specific BBT. + * + * 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 <common.h> +#include <linux/compat.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> +#include <malloc.h> + +#include <asm/errno.h> + +/** + * check_short_pattern - [GENERIC] check if a pattern is in the buffer + * @param buf		the buffer to search + * @param len		the length of buffer to search + * @param paglen	the pagelength + * @param td		search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block + * tables and good / bad block identifiers. Same as check_pattern, but + * no optional empty check and the pattern is expected to start + * at offset 0. + */ +static int check_short_pattern(uint8_t * buf, int len, int paglen, +			       struct nand_bbt_descr *td) +{ +	int i; +	uint8_t *p = buf; + +	/* Compare the pattern */ +	for (i = 0; i < td->len; i++) { +		if (p[i] != td->pattern[i]) +			return -1; +	} +	return 0; +} + +/** + * create_bbt - [GENERIC] Create a bad block table by scanning the device + * @param mtd		MTD device structure + * @param buf		temporary buffer + * @param bd		descriptor for the good/bad block search pattern + * @param chip		create the table for a specific chip, -1 read all chips. + *              Applies only if NAND_BBT_PERCHIP option is set + * + * Create a bad block table by scanning the device + * for the given good/bad block identify pattern + */ +static int create_bbt(struct mtd_info *mtd, uint8_t * buf, +		      struct nand_bbt_descr *bd, int chip) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm = this->bbm; +	int i, j, numblocks, len, scanlen; +	int startblock; +	loff_t from; +	size_t readlen, ooblen; +	struct mtd_oob_ops ops; +	int rgn; + +	printk(KERN_INFO "Scanning device for bad blocks\n"); + +	len = 1; + +	/* We need only read few bytes from the OOB area */ +	scanlen = ooblen = 0; +	readlen = bd->len; + +	/* chip == -1 case only */ +	/* Note that numblocks is 2 * (real numblocks) here; +	 * see i += 2 below as it makses shifting and masking less painful +	 */ +	numblocks = this->chipsize >> (bbm->bbt_erase_shift - 1); +	startblock = 0; +	from = 0; + +	ops.mode = MTD_OPS_PLACE_OOB; +	ops.ooblen = readlen; +	ops.oobbuf = buf; +	ops.len = ops.ooboffs = ops.retlen = ops.oobretlen = 0; + +	for (i = startblock; i < numblocks;) { +		int ret; + +		for (j = 0; j < len; j++) { +			/* No need to read pages fully, +			 * just read required OOB bytes */ +			ret = onenand_bbt_read_oob(mtd, +					     from + j * mtd->writesize + +					     bd->offs, &ops); + +			/* If it is a initial bad block, just ignore it */ +			if (ret == ONENAND_BBT_READ_FATAL_ERROR) +				return -EIO; + +			if (ret || check_short_pattern +			    (&buf[j * scanlen], scanlen, mtd->writesize, bd)) { +				bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); +				printk(KERN_WARNING +				       "Bad eraseblock %d at 0x%08x\n", i >> 1, +				       (unsigned int)from); +				break; +			} +		} +		i += 2; + +		if (FLEXONENAND(this)) { +			rgn = flexonenand_region(mtd, from); +			from += mtd->eraseregions[rgn].erasesize; +		} else +			from += (1 << bbm->bbt_erase_shift); +	} + +	return 0; +} + +/** + * onenand_memory_bbt - [GENERIC] create a memory based bad block table + * @param mtd		MTD device structure + * @param bd		descriptor for the good/bad block search pattern + * + * The function creates a memory based bbt by scanning the device + * for manufacturer / software marked good / bad blocks + */ +static inline int onenand_memory_bbt(struct mtd_info *mtd, +				     struct nand_bbt_descr *bd) +{ +	unsigned char data_buf[MAX_ONENAND_PAGESIZE]; + +	bd->options &= ~NAND_BBT_SCANEMPTY; +	return create_bbt(mtd, data_buf, bd, -1); +} + +/** + * onenand_isbad_bbt - [OneNAND Interface] Check if a block is bad + * @param mtd		MTD device structure + * @param offs		offset in the device + * @param allowbbt	allow access to bad block table region + */ +static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm = this->bbm; +	int block; +	uint8_t res; + +	/* Get block number * 2 */ +	block = (int) (onenand_block(this, offs) << 1); +	res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; + +	MTDDEBUG (MTD_DEBUG_LEVEL2, +		"onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", +		(unsigned int)offs, block >> 1, res); + +	switch ((int)res) { +	case 0x00: +		return 0; +	case 0x01: +		return 1; +	case 0x02: +		return allowbbt ? 0 : 1; +	} + +	return 1; +} + +/** + * onenand_scan_bbt - [OneNAND Interface] scan, find, read and maybe create bad block table(s) + * @param mtd		MTD device structure + * @param bd		descriptor for the good/bad block search pattern + * + * The function checks, if a bad block table(s) is/are already + * available. If not it scans the device for manufacturer + * marked good / bad blocks and writes the bad block table(s) to + * the selected place. + * + * The bad block table memory is allocated here. It must be freed + * by calling the onenand_free_bbt function. + * + */ +int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm = this->bbm; +	int len, ret = 0; + +	len = this->chipsize >> (this->erase_shift + 2); +	/* Allocate memory (2bit per block) */ +	bbm->bbt = malloc(len); +	if (!bbm->bbt) +		return -ENOMEM; +	/* Clear the memory bad block table */ +	memset(bbm->bbt, 0x00, len); + +	/* Set the bad block position */ +	bbm->badblockpos = ONENAND_BADBLOCK_POS; + +	/* Set erase shift */ +	bbm->bbt_erase_shift = this->erase_shift; + +	if (!bbm->isbad_bbt) +		bbm->isbad_bbt = onenand_isbad_bbt; + +	/* Scan the device to build a memory based bad block table */ +	if ((ret = onenand_memory_bbt(mtd, bd))) { +		printk(KERN_ERR +		       "onenand_scan_bbt: Can't scan flash and build the RAM-based BBT\n"); +		free(bbm->bbt); +		bbm->bbt = NULL; +	} + +	return ret; +} + +/* + * Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. + */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr largepage_memorybased = { +	.options = 0, +	.offs = 0, +	.len = 2, +	.pattern = scan_ff_pattern, +}; + +/** + * onenand_default_bbt - [OneNAND Interface] Select a default bad block table for the device + * @param mtd		MTD device structure + * + * This function selects the default bad block table + * support for the device and calls the onenand_scan_bbt function + */ +int onenand_default_bbt(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	struct bbm_info *bbm; + +	this->bbm = malloc(sizeof(struct bbm_info)); +	if (!this->bbm) +		return -ENOMEM; + +	bbm = this->bbm; + +	memset(bbm, 0, sizeof(struct bbm_info)); + +	/* 1KB page has same configuration as 2KB page */ +	if (!bbm->badblock_pattern) +		bbm->badblock_pattern = &largepage_memorybased; + +	return onenand_scan_bbt(mtd, bbm->badblock_pattern); +} diff --git a/roms/u-boot/drivers/mtd/onenand/onenand_spl.c b/roms/u-boot/drivers/mtd/onenand/onenand_spl.c new file mode 100644 index 00000000..fe6b7d92 --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/onenand_spl.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com> + * + * Based on code: + *	Copyright (C) 2005-2009 Samsung Electronics + *	Kyungmin Park <kyungmin.park@samsung.com> + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <asm/io.h> +#include <linux/mtd/onenand_regs.h> +#include <onenand_uboot.h> + +/* + * Device geometry: + * - 2048b page, 128k erase block. + * - 4096b page, 256k erase block. + */ +enum onenand_spl_pagesize { +	PAGE_2K = 2048, +	PAGE_4K = 4096, +}; + +#define ONENAND_PAGES_PER_BLOCK			64 +#define onenand_block_address(block)		(block) +#define onenand_sector_address(page)		(page << 2) +#define onenand_buffer_address()		((1 << 3) << 8) +#define onenand_bufferram_address(block)	(0) + +static inline uint16_t onenand_readw(uint32_t addr) +{ +	return readw(CONFIG_SYS_ONENAND_BASE + addr); +} + +static inline void onenand_writew(uint16_t value, uint32_t addr) +{ +	writew(value, CONFIG_SYS_ONENAND_BASE + addr); +} + +static enum onenand_spl_pagesize onenand_spl_get_geometry(void) +{ +	uint32_t dev_id, density; + +	if (!onenand_readw(ONENAND_REG_TECHNOLOGY)) { +		dev_id = onenand_readw(ONENAND_REG_DEVICE_ID); +		density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; +		density &= ONENAND_DEVICE_DENSITY_MASK; + +		if (density < ONENAND_DEVICE_DENSITY_4Gb) +			return PAGE_2K; + +		if (dev_id & ONENAND_DEVICE_IS_DDP) +			return PAGE_2K; +	} + +	return PAGE_4K; +} + +static int onenand_spl_read_page(uint32_t block, uint32_t page, uint32_t *buf, +					enum onenand_spl_pagesize pagesize) +{ +	const uint32_t addr = CONFIG_SYS_ONENAND_BASE + ONENAND_DATARAM; +	uint32_t offset; + +	onenand_writew(onenand_block_address(block), +			ONENAND_REG_START_ADDRESS1); + +	onenand_writew(onenand_bufferram_address(block), +			ONENAND_REG_START_ADDRESS2); + +	onenand_writew(onenand_sector_address(page), +			ONENAND_REG_START_ADDRESS8); + +	onenand_writew(onenand_buffer_address(), +			ONENAND_REG_START_BUFFER); + +	onenand_writew(ONENAND_INT_CLEAR, ONENAND_REG_INTERRUPT); + +	onenand_writew(ONENAND_CMD_READ, ONENAND_REG_COMMAND); + +	while (!(onenand_readw(ONENAND_REG_INTERRUPT) & ONENAND_INT_READ)) +		continue; + +	/* Check for invalid block mark */ +	if (page < 2 && (onenand_readw(ONENAND_SPARERAM) != 0xffff)) +		return 1; + +	for (offset = 0; offset < pagesize; offset += 4) +		buf[offset / 4] = readl(addr + offset); + +	return 0; +} + +void onenand_spl_load_image(uint32_t offs, uint32_t size, void *dst) +{ +	uint32_t *addr = (uint32_t *)dst; +	uint32_t to_page; +	uint32_t block; +	uint32_t page, rpage; +	enum onenand_spl_pagesize pagesize; +	int ret; + +	pagesize = onenand_spl_get_geometry(); + +	/* +	 * The page can be either 2k or 4k, avoid using DIV_ROUND_UP to avoid +	 * pulling further unwanted functions into the SPL. +	 */ +	if (pagesize == 2048) { +		page = offs / 2048; +		to_page = page + DIV_ROUND_UP(size, 2048); +	} else { +		page = offs / 4096; +		to_page = page + DIV_ROUND_UP(size, 4096); +	} + +	for (; page <= to_page; page++) { +		block = page / ONENAND_PAGES_PER_BLOCK; +		rpage = page & (ONENAND_PAGES_PER_BLOCK - 1); +		ret = onenand_spl_read_page(block, rpage, addr, pagesize); +		if (ret) +			page += ONENAND_PAGES_PER_BLOCK - 1; +		else +			addr += pagesize / 4; +	} +} diff --git a/roms/u-boot/drivers/mtd/onenand/onenand_uboot.c b/roms/u-boot/drivers/mtd/onenand/onenand_uboot.c new file mode 100644 index 00000000..ae60c3bb --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/onenand_uboot.c @@ -0,0 +1,56 @@ +/* + *  drivers/mtd/onenand/onenand_uboot.c + * + *  Copyright (C) 2005-2008 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. + */ + +/* + * OneNAND initialization at U-Boot + */ + +#include <common.h> +#include <linux/compat.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> + +struct mtd_info onenand_mtd; +struct onenand_chip onenand_chip; +static __attribute__((unused)) char dev_name[] = "onenand0"; + +void onenand_init(void) +{ +	memset(&onenand_mtd, 0, sizeof(struct mtd_info)); +	memset(&onenand_chip, 0, sizeof(struct onenand_chip)); + +	onenand_mtd.priv = &onenand_chip; + +#ifdef CONFIG_USE_ONENAND_BOARD_INIT +	/* +	 * It's used for some board init required +	 */ +	onenand_board_init(&onenand_mtd); +#else +	onenand_chip.base = (void *) CONFIG_SYS_ONENAND_BASE; +#endif + +	onenand_scan(&onenand_mtd, 1); + +	if (onenand_chip.device_id & DEVICE_IS_FLEXONENAND) +		puts("Flex-"); +	puts("OneNAND: "); +	print_size(onenand_chip.chipsize, "\n"); + +#ifdef CONFIG_MTD_DEVICE +	/* +	 * Add MTD device so that we can reference it later +	 * via the mtdcore infrastructure (e.g. ubi). +	 */ +	onenand_mtd.name = dev_name; +	add_mtd_device(&onenand_mtd); +#endif +} diff --git a/roms/u-boot/drivers/mtd/onenand/samsung.c b/roms/u-boot/drivers/mtd/onenand/samsung.c new file mode 100644 index 00000000..df04c2bb --- /dev/null +++ b/roms/u-boot/drivers/mtd/onenand/samsung.c @@ -0,0 +1,577 @@ +/* + * S5PC100 OneNAND driver at U-Boot + * + * Copyright (C) 2008-2009 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * + * Implementation: + *	Emulate the pseudo BufferRAM + * + * SPDX-License-Identifier:	GPL-2.0+ + */ + +#include <common.h> +#include <malloc.h> +#include <linux/compat.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/onenand.h> +#include <linux/mtd/samsung_onenand.h> + +#include <asm/io.h> +#include <asm/errno.h> + +#ifdef ONENAND_DEBUG +#define DPRINTK(format, args...)					\ +do {									\ +	printf("%s[%d]: " format "\n", __func__, __LINE__, ##args);	\ +} while (0) +#else +#define DPRINTK(...)			do { } while (0) +#endif + +#define ONENAND_ERASE_STATUS		0x00 +#define ONENAND_MULTI_ERASE_SET		0x01 +#define ONENAND_ERASE_START		0x03 +#define ONENAND_UNLOCK_START		0x08 +#define ONENAND_UNLOCK_END		0x09 +#define ONENAND_LOCK_START		0x0A +#define ONENAND_LOCK_END		0x0B +#define ONENAND_LOCK_TIGHT_START	0x0C +#define ONENAND_LOCK_TIGHT_END		0x0D +#define ONENAND_UNLOCK_ALL		0x0E +#define ONENAND_OTP_ACCESS		0x12 +#define ONENAND_SPARE_ACCESS_ONLY	0x13 +#define ONENAND_MAIN_ACCESS_ONLY	0x14 +#define ONENAND_ERASE_VERIFY		0x15 +#define ONENAND_MAIN_SPARE_ACCESS	0x16 +#define ONENAND_PIPELINE_READ		0x4000 + +#if defined(CONFIG_S5P) +#define MAP_00				(0x0 << 26) +#define MAP_01				(0x1 << 26) +#define MAP_10				(0x2 << 26) +#define MAP_11				(0x3 << 26) +#endif + +/* read/write of XIP buffer */ +#define CMD_MAP_00(mem_addr)		(MAP_00 | ((mem_addr) << 1)) +/* read/write to the memory device */ +#define CMD_MAP_01(mem_addr)		(MAP_01 | (mem_addr)) +/* control special functions of the memory device */ +#define CMD_MAP_10(mem_addr)		(MAP_10 | (mem_addr)) +/* direct interface(direct access) with the memory device */ +#define CMD_MAP_11(mem_addr)		(MAP_11 | ((mem_addr) << 2)) + +struct s3c_onenand { +	struct mtd_info	*mtd; +	void __iomem	*base; +	void __iomem	*ahb_addr; +	int		bootram_command; +	void __iomem	*page_buf; +	void __iomem	*oob_buf; +	unsigned int	(*mem_addr)(int fba, int fpa, int fsa); +	struct samsung_onenand *reg; +}; + +static struct s3c_onenand *onenand; + +static int s3c_read_cmd(unsigned int cmd) +{ +	return readl(onenand->ahb_addr + cmd); +} + +static void s3c_write_cmd(int value, unsigned int cmd) +{ +	writel(value, onenand->ahb_addr + cmd); +} + +/* + * MEM_ADDR + * + * fba: flash block address + * fpa: flash page address + * fsa: flash sector address + * + * return the buffer address on the memory device + * It will be combined with CMD_MAP_XX + */ +#if defined(CONFIG_S5P) +static unsigned int s3c_mem_addr(int fba, int fpa, int fsa) +{ +	return (fba << 13) | (fpa << 7) | (fsa << 5); +} +#endif + +static void s3c_onenand_reset(void) +{ +	unsigned long timeout = 0x10000; +	int stat; + +	writel(ONENAND_MEM_RESET_COLD, &onenand->reg->mem_reset); +	while (timeout--) { +		stat = readl(&onenand->reg->int_err_stat); +		if (stat & RST_CMP) +			break; +	} +	stat = readl(&onenand->reg->int_err_stat); +	writel(stat, &onenand->reg->int_err_ack); + +	/* Clear interrupt */ +	writel(0x0, &onenand->reg->int_err_ack); +	/* Clear the ECC status */ +	writel(0x0, &onenand->reg->ecc_err_stat); +} + +static unsigned short s3c_onenand_readw(void __iomem *addr) +{ +	struct onenand_chip *this = onenand->mtd->priv; +	int reg = addr - this->base; +	int word_addr = reg >> 1; +	int value; + +	/* It's used for probing time */ +	switch (reg) { +	case ONENAND_REG_MANUFACTURER_ID: +		return readl(&onenand->reg->manufact_id); +	case ONENAND_REG_DEVICE_ID: +		return readl(&onenand->reg->device_id); +	case ONENAND_REG_VERSION_ID: +		return readl(&onenand->reg->flash_ver_id); +	case ONENAND_REG_DATA_BUFFER_SIZE: +		return readl(&onenand->reg->data_buf_size); +	case ONENAND_REG_TECHNOLOGY: +		return readl(&onenand->reg->tech); +	case ONENAND_REG_SYS_CFG1: +		return readl(&onenand->reg->mem_cfg); + +	/* Used at unlock all status */ +	case ONENAND_REG_CTRL_STATUS: +		return 0; + +	case ONENAND_REG_WP_STATUS: +		return ONENAND_WP_US; + +	default: +		break; +	} + +	/* BootRAM access control */ +	if (reg < ONENAND_DATARAM && onenand->bootram_command) { +		if (word_addr == 0) +			return readl(&onenand->reg->manufact_id); +		if (word_addr == 1) +			return readl(&onenand->reg->device_id); +		if (word_addr == 2) +			return readl(&onenand->reg->flash_ver_id); +	} + +	value = s3c_read_cmd(CMD_MAP_11(word_addr)) & 0xffff; +	printk(KERN_INFO "s3c_onenand_readw:  Illegal access" +		" at reg 0x%x, value 0x%x\n", word_addr, value); +	return value; +} + +static void s3c_onenand_writew(unsigned short value, void __iomem *addr) +{ +	struct onenand_chip *this = onenand->mtd->priv; +	int reg = addr - this->base; +	int word_addr = reg >> 1; + +	/* It's used for probing time */ +	switch (reg) { +	case ONENAND_REG_SYS_CFG1: +		writel(value, &onenand->reg->mem_cfg); +		return; + +	case ONENAND_REG_START_ADDRESS1: +	case ONENAND_REG_START_ADDRESS2: +		return; + +	/* Lock/lock-tight/unlock/unlock_all */ +	case ONENAND_REG_START_BLOCK_ADDRESS: +		return; + +	default: +		break; +	} + +	/* BootRAM access control */ +	if (reg < ONENAND_DATARAM) { +		if (value == ONENAND_CMD_READID) { +			onenand->bootram_command = 1; +			return; +		} +		if (value == ONENAND_CMD_RESET) { +			writel(ONENAND_MEM_RESET_COLD, +					&onenand->reg->mem_reset); +			onenand->bootram_command = 0; +			return; +		} +	} + +	printk(KERN_INFO "s3c_onenand_writew: Illegal access" +		" at reg 0x%x, value 0x%x\n", word_addr, value); + +	s3c_write_cmd(value, CMD_MAP_11(word_addr)); +} + +static int s3c_onenand_wait(struct mtd_info *mtd, int state) +{ +	unsigned int flags = INT_ACT; +	unsigned int stat, ecc; +	unsigned long timeout = 0x100000; + +	switch (state) { +	case FL_READING: +		flags |= BLK_RW_CMP | LOAD_CMP; +		break; +	case FL_WRITING: +		flags |= BLK_RW_CMP | PGM_CMP; +		break; +	case FL_ERASING: +		flags |= BLK_RW_CMP | ERS_CMP; +		break; +	case FL_LOCKING: +		flags |= BLK_RW_CMP; +		break; +	default: +		break; +	} + +	while (timeout--) { +		stat = readl(&onenand->reg->int_err_stat); +		if (stat & flags) +			break; +	} + +	/* To get correct interrupt status in timeout case */ +	stat = readl(&onenand->reg->int_err_stat); +	writel(stat, &onenand->reg->int_err_ack); + +	/* +	 * In the Spec. it checks the controller status first +	 * However if you get the correct information in case of +	 * power off recovery (POR) test, it should read ECC status first +	 */ +	if (stat & LOAD_CMP) { +		ecc = readl(&onenand->reg->ecc_err_stat); +		if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { +			printk(KERN_INFO "%s: ECC error = 0x%04x\n", +					__func__, ecc); +			mtd->ecc_stats.failed++; +			return -EBADMSG; +		} +	} + +	if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) { +		printk(KERN_INFO "%s: controller error = 0x%04x\n", +				__func__, stat); +		if (stat & LOCKED_BLK) +			printk(KERN_INFO "%s: it's locked error = 0x%04x\n", +					__func__, stat); + +		return -EIO; +	} + +	return 0; +} + +static int s3c_onenand_command(struct mtd_info *mtd, int cmd, +		loff_t addr, size_t len) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int *m, *s; +	int fba, fpa, fsa = 0; +	unsigned int mem_addr; +	int i, mcount, scount; +	int index; + +	fba = (int) (addr >> this->erase_shift); +	fpa = (int) (addr >> this->page_shift); +	fpa &= this->page_mask; + +	mem_addr = onenand->mem_addr(fba, fpa, fsa); + +	switch (cmd) { +	case ONENAND_CMD_READ: +	case ONENAND_CMD_READOOB: +	case ONENAND_CMD_BUFFERRAM: +		ONENAND_SET_NEXT_BUFFERRAM(this); +	default: +		break; +	} + +	index = ONENAND_CURRENT_BUFFERRAM(this); + +	/* +	 * Emulate Two BufferRAMs and access with 4 bytes pointer +	 */ +	m = (unsigned int *) onenand->page_buf; +	s = (unsigned int *) onenand->oob_buf; + +	if (index) { +		m += (this->writesize >> 2); +		s += (mtd->oobsize >> 2); +	} + +	mcount = mtd->writesize >> 2; +	scount = mtd->oobsize >> 2; + +	switch (cmd) { +	case ONENAND_CMD_READ: +		/* Main */ +		for (i = 0; i < mcount; i++) +			*m++ = s3c_read_cmd(CMD_MAP_01(mem_addr)); +		return 0; + +	case ONENAND_CMD_READOOB: +		writel(TSRF, &onenand->reg->trans_spare); +		/* Main */ +		for (i = 0; i < mcount; i++) +			*m++ = s3c_read_cmd(CMD_MAP_01(mem_addr)); + +		/* Spare */ +		for (i = 0; i < scount; i++) +			*s++ = s3c_read_cmd(CMD_MAP_01(mem_addr)); + +		writel(0, &onenand->reg->trans_spare); +		return 0; + +	case ONENAND_CMD_PROG: +		/* Main */ +		for (i = 0; i < mcount; i++) +			s3c_write_cmd(*m++, CMD_MAP_01(mem_addr)); +		return 0; + +	case ONENAND_CMD_PROGOOB: +		writel(TSRF, &onenand->reg->trans_spare); + +		/* Main - dummy write */ +		for (i = 0; i < mcount; i++) +			s3c_write_cmd(0xffffffff, CMD_MAP_01(mem_addr)); + +		/* Spare */ +		for (i = 0; i < scount; i++) +			s3c_write_cmd(*s++, CMD_MAP_01(mem_addr)); + +		writel(0, &onenand->reg->trans_spare); +		return 0; + +	case ONENAND_CMD_UNLOCK_ALL: +		s3c_write_cmd(ONENAND_UNLOCK_ALL, CMD_MAP_10(mem_addr)); +		return 0; + +	case ONENAND_CMD_ERASE: +		s3c_write_cmd(ONENAND_ERASE_START, CMD_MAP_10(mem_addr)); +		return 0; + +	case ONENAND_CMD_MULTIBLOCK_ERASE: +		s3c_write_cmd(ONENAND_MULTI_ERASE_SET, CMD_MAP_10(mem_addr)); +		return 0; + +	case ONENAND_CMD_ERASE_VERIFY: +		s3c_write_cmd(ONENAND_ERASE_VERIFY, CMD_MAP_10(mem_addr)); +		return 0; + +	default: +		break; +	} + +	return 0; +} + +static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) +{ +	struct onenand_chip *this = mtd->priv; +	int index = ONENAND_CURRENT_BUFFERRAM(this); +	unsigned char *p; + +	if (area == ONENAND_DATARAM) { +		p = (unsigned char *) onenand->page_buf; +		if (index == 1) +			p += this->writesize; +	} else { +		p = (unsigned char *) onenand->oob_buf; +		if (index == 1) +			p += mtd->oobsize; +	} + +	return p; +} + +static int onenand_read_bufferram(struct mtd_info *mtd, loff_t addr, int area, +				  unsigned char *buffer, int offset, +				  size_t count) +{ +	unsigned char *p; + +	p = s3c_get_bufferram(mtd, area); +	memcpy(buffer, p + offset, count); +	return 0; +} + +static int onenand_write_bufferram(struct mtd_info *mtd, loff_t addr, int area, +				   const unsigned char *buffer, int offset, +				   size_t count) +{ +	unsigned char *p; + +	p = s3c_get_bufferram(mtd, area); +	memcpy(p + offset, buffer, count); +	return 0; +} + +static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) +{ +	struct samsung_onenand *reg = (struct samsung_onenand *)onenand->base; +	unsigned int flags = INT_ACT | LOAD_CMP; +	unsigned int stat; +	unsigned long timeout = 0x10000; + +	while (timeout--) { +		stat = readl(®->int_err_stat); +		if (stat & flags) +			break; +	} +	/* To get correct interrupt status in timeout case */ +	stat = readl(&onenand->reg->int_err_stat); +	writel(stat, &onenand->reg->int_err_ack); + +	if (stat & LD_FAIL_ECC_ERR) { +		s3c_onenand_reset(); +		return ONENAND_BBT_READ_ERROR; +	} + +	if (stat & LOAD_CMP) { +		int ecc = readl(&onenand->reg->ecc_err_stat); +		if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { +			s3c_onenand_reset(); +			return ONENAND_BBT_READ_ERROR; +		} +	} + +	return 0; +} + +static void s3c_onenand_check_lock_status(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	unsigned int block, end; + +	end = this->chipsize >> this->erase_shift; + +	for (block = 0; block < end; block++) { +		s3c_read_cmd(CMD_MAP_01(onenand->mem_addr(block, 0, 0))); + +		if (readl(&onenand->reg->int_err_stat) & LOCKED_BLK) { +			printf("block %d is write-protected!\n", block); +			writel(LOCKED_BLK, &onenand->reg->int_err_ack); +		} +	} +} + +static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, +		size_t len, int cmd) +{ +	struct onenand_chip *this = mtd->priv; +	int start, end, start_mem_addr, end_mem_addr; + +	start = ofs >> this->erase_shift; +	start_mem_addr = onenand->mem_addr(start, 0, 0); +	end = start + (len >> this->erase_shift) - 1; +	end_mem_addr = onenand->mem_addr(end, 0, 0); + +	if (cmd == ONENAND_CMD_LOCK) { +		s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(start_mem_addr)); +		s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(end_mem_addr)); +	} else { +		s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(start_mem_addr)); +		s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(end_mem_addr)); +	} + +	this->wait(mtd, FL_LOCKING); +} + +static void s3c_onenand_unlock_all(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	loff_t ofs = 0; +	size_t len = this->chipsize; + +	/* FIXME workaround */ +	this->subpagesize = mtd->writesize; +	mtd->subpage_sft = 0; + +	if (this->options & ONENAND_HAS_UNLOCK_ALL) { +		/* Write unlock command */ +		this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + +		/* No need to check return value */ +		this->wait(mtd, FL_LOCKING); + +		/* Workaround for all block unlock in DDP */ +		if (!ONENAND_IS_DDP(this)) { +			s3c_onenand_check_lock_status(mtd); +			return; +		} + +		/* All blocks on another chip */ +		ofs = this->chipsize >> 1; +		len = this->chipsize >> 1; +	} + +	s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +	s3c_onenand_check_lock_status(mtd); +} + +int s5pc110_chip_probe(struct mtd_info *mtd) +{ +	return 0; +} + +int s5pc210_chip_probe(struct mtd_info *mtd) +{ +	return 0; +} + +void s3c_onenand_init(struct mtd_info *mtd) +{ +	struct onenand_chip *this = mtd->priv; +	u32 size = (4 << 10);	/* 4 KiB */ + +	onenand = malloc(sizeof(struct s3c_onenand)); +	if (!onenand) +		return; + +	onenand->page_buf = malloc(size * sizeof(char)); +	if (!onenand->page_buf) +		return; +	memset(onenand->page_buf, 0xff, size); + +	onenand->oob_buf = malloc(128 * sizeof(char)); +	if (!onenand->oob_buf) +		return; +	memset(onenand->oob_buf, 0xff, 128); + +	onenand->mtd = mtd; + +#if defined(CONFIG_S5P) +	onenand->base = (void *)0xE7100000; +	onenand->ahb_addr = (void *)0xB0000000; +#endif +	onenand->mem_addr = s3c_mem_addr; +	onenand->reg = (struct samsung_onenand *)onenand->base; + +	this->read_word = s3c_onenand_readw; +	this->write_word = s3c_onenand_writew; + +	this->wait = s3c_onenand_wait; +	this->bbt_wait = s3c_onenand_bbt_wait; +	this->unlock_all = s3c_onenand_unlock_all; +	this->command = s3c_onenand_command; + +	this->read_bufferram = onenand_read_bufferram; +	this->write_bufferram = onenand_write_bufferram; + +	this->options |= ONENAND_RUNTIME_BADBLOCK_CHECK; +}  | 
