diff options
Diffstat (limited to 'package/linux/kernel-source/arch/mips/brcm-boards/bcm947xx/sflash.c')
| -rw-r--r-- | package/linux/kernel-source/arch/mips/brcm-boards/bcm947xx/sflash.c | 346 | 
1 files changed, 346 insertions, 0 deletions
diff --git a/package/linux/kernel-source/arch/mips/brcm-boards/bcm947xx/sflash.c b/package/linux/kernel-source/arch/mips/brcm-boards/bcm947xx/sflash.c new file mode 100644 index 000000000..4f69880ca --- /dev/null +++ b/package/linux/kernel-source/arch/mips/brcm-boards/bcm947xx/sflash.c @@ -0,0 +1,346 @@ +/* + * Broadcom SiliconBackplane chipcommon serial flash interface + * + * Copyright 2004, Broadcom Corporation       + * All Rights Reserved.       + *        + * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY       + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM       + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS       + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.       + * + * $Id$ + */ + +#include <typedefs.h> +#include <bcmutils.h> +#include <osl.h> +#include <sbchipc.h> +#include <sflash.h> + +/* Private global state */ +static struct sflash sflash; + +/* Issue a serial flash command */ +static INLINE void +sflash_cmd(chipcregs_t *cc, uint opcode) +{ +	W_REG(&cc->flashcontrol, SFLASH_START | opcode); +	while (R_REG(&cc->flashcontrol) & SFLASH_BUSY); +} + +/* Initialize serial flash access */ +struct sflash * +sflash_init(chipcregs_t *cc) +{ +	uint32 id, id2; + +	bzero(&sflash, sizeof(sflash)); + +	sflash.type = R_REG(&cc->capabilities) & CAP_FLASH_MASK; + +	switch (sflash.type) { +	case SFLASH_ST: +		/* Probe for ST chips */ +		sflash_cmd(cc, SFLASH_ST_DP); +		sflash_cmd(cc, SFLASH_ST_RES); +		id = R_REG(&cc->flashdata); +		switch (id) { +		case 0x11: +			/* ST M25P20 2 Mbit Serial Flash */ +			sflash.blocksize = 64 * 1024; +			sflash.numblocks = 4; +			break; +		case 0x12: +			/* ST M25P40 4 Mbit Serial Flash */ +			sflash.blocksize = 64 * 1024; +			sflash.numblocks = 8; +			break; +		case 0x13: +			/* ST M25P80 8 Mbit Serial Flash */ +			sflash.blocksize = 64 * 1024; +			sflash.numblocks = 16; +			break; +		case 0x14: +			/* ST M25P16 16 Mbit Serial Flash */ +			sflash.blocksize = 64 * 1024; +			sflash.numblocks = 32; +			break; +		case 0xbf: +			W_REG(&cc->flashaddress, 1); +			sflash_cmd(cc, SFLASH_ST_RES); +			id2 = R_REG(&cc->flashdata); +			if (id2 == 0x44) { +				/* SST M25VF80 4 Mbit Serial Flash */ +				sflash.blocksize = 64 * 1024; +				sflash.numblocks = 8; +			} +			break; +		} +		break; + +	case SFLASH_AT: +		/* Probe for Atmel chips */ +		sflash_cmd(cc, SFLASH_AT_STATUS); +		id = R_REG(&cc->flashdata) & 0x3c; +		switch (id) { +		case 0x2c: +			/* Atmel AT45DB161 16Mbit Serial Flash */ +			sflash.blocksize = 512; +			sflash.numblocks = 4096; +			break; +		case 0x34: +			/* Atmel AT45DB321 32Mbit Serial Flash */ +			sflash.blocksize = 512; +			sflash.numblocks = 8192; +			break; +		case 0x3c: +			/* Atmel AT45DB642 64Mbit Serial Flash */ +			sflash.blocksize = 1024; +			sflash.numblocks = 8192; +			break; +		} +		break; +	} + +	sflash.size = sflash.blocksize * sflash.numblocks; +	return sflash.size ? &sflash : NULL; +} + +/* Read len bytes starting at offset into buf. Returns number of bytes read. */ +int +sflash_read(chipcregs_t *cc, uint offset, uint len, uchar *buf) +{ +	int cnt; +	uint32 *from, *to; + +	if (!len) +		return 0; + +	if ((offset + len) > sflash.size) +		return -22; + +	if ((len >= 4) && (offset & 3)) +		cnt = 4 - (offset & 3); +	else if ((len >= 4) && ((uint32)buf & 3)) +		cnt = 4 - ((uint32)buf & 3); +	else +		cnt = len; + +	from = (uint32 *)(CC_FLASH_BASE + offset); +	to = (uint32 *)buf; + +	if (cnt < 4) { +		bcopy(from, to, cnt); +		return cnt; +	} + +	while (cnt >= 4) { +		*to++ = *from++; +		cnt -= 4; +	} + +	return (len - cnt); +} + +/* Poll for command completion. Returns zero when complete. */ +int +sflash_poll(chipcregs_t *cc, uint offset) +{ +	if (offset >= sflash.size) +		return -22; + +	switch (sflash.type) { +	case SFLASH_ST: +		/* Check for ST Write In Progress bit */ +		sflash_cmd(cc, SFLASH_ST_RDSR); +		return R_REG(&cc->flashdata) & SFLASH_ST_WIP; +	case SFLASH_AT: +		/* Check for Atmel Ready bit */ +		sflash_cmd(cc, SFLASH_AT_STATUS); +		return !(R_REG(&cc->flashdata) & SFLASH_AT_READY); +	} + +	return 0; +} + +/* Write len bytes starting at offset into buf. Returns number of bytes + * written. Caller should poll for completion. + */ +int +sflash_write(chipcregs_t *cc, uint offset, uint len, const uchar *buf) +{ +	struct sflash *sfl; +	int ret = 0; +	uint32 page, byte, mask; + +	if (!len) +		return 0; + +	if ((offset + len) > sflash.size) +		return -22; + +	sfl = &sflash; +	switch (sfl->type) { +	case SFLASH_ST: +		ret = 1; +		/* Enable writes */ +		sflash_cmd(cc, SFLASH_ST_WREN); +		W_REG(&cc->flashaddress, offset); +		W_REG(&cc->flashdata, *buf); +		/* Page program */ +		sflash_cmd(cc, SFLASH_ST_PP); +		break; +	case SFLASH_AT: +		mask = sfl->blocksize - 1; +		page = (offset & ~mask) << 1; +		byte = offset & mask; +		/* Read main memory page into buffer 1 */ +		if (byte || len < sfl->blocksize) { +			W_REG(&cc->flashaddress, page); +			sflash_cmd(cc, SFLASH_AT_BUF1_LOAD); +			/* 250 us for AT45DB321B */ +			SPINWAIT(sflash_poll(cc, offset), 1000); +			ASSERT(!sflash_poll(cc, offset)); +		} +		/* Write into buffer 1 */ +		for (ret = 0; ret < len && byte < sfl->blocksize; ret++) { +			W_REG(&cc->flashaddress, byte++); +			W_REG(&cc->flashdata, *buf++); +			sflash_cmd(cc, SFLASH_AT_BUF1_WRITE); +		} +		/* Write buffer 1 into main memory page */ +		W_REG(&cc->flashaddress, page); +		sflash_cmd(cc, SFLASH_AT_BUF1_PROGRAM); +		break; +	} + +	return ret; +} + +/* Erase a region. Returns number of bytes scheduled for erasure. + * Caller should poll for completion. + */ +int +sflash_erase(chipcregs_t *cc, uint offset) +{ +	struct sflash *sfl; + +	if (offset >= sflash.size) +		return -22; + +	sfl = &sflash; +	switch (sfl->type) { +	case SFLASH_ST: +		sflash_cmd(cc, SFLASH_ST_WREN); +		W_REG(&cc->flashaddress, offset); +		sflash_cmd(cc, SFLASH_ST_SE); +		return sfl->blocksize; +	case SFLASH_AT: +		W_REG(&cc->flashaddress, offset << 1); +		sflash_cmd(cc, SFLASH_AT_PAGE_ERASE); +		return sfl->blocksize; +	} + +	return 0; +} + +/* + * writes the appropriate range of flash, a NULL buf simply erases + * the region of flash + */ +int +sflash_commit(chipcregs_t *cc, uint offset, uint len, const uchar *buf) +{ +	struct sflash *sfl; +	uchar *block = NULL, *cur_ptr, *blk_ptr; +	uint blocksize = 0, mask, cur_offset, cur_length, cur_retlen, remainder; +	uint blk_offset, blk_len, copied; +	int bytes, ret = 0; + +	/* Check address range */ +	if (len <= 0) +		return 0; + +	sfl = &sflash; +	if ((offset + len) > sfl->size) +		return -1; + +	blocksize = sfl->blocksize; +	mask = blocksize - 1; + +	/* Allocate a block of mem */ +	if (!(block = MALLOC(blocksize))) +		return -1; + +	while (len) { +		/* Align offset */ +		cur_offset = offset & ~mask; +		cur_length = blocksize; +		cur_ptr = block; + +		remainder = blocksize - (offset & mask); +		if (len < remainder) +			cur_retlen = len; +		else +			cur_retlen = remainder; + +		/* buf == NULL means erase only */ +		if (buf) { +			/* Copy existing data into holding block if necessary */ +			if ((offset & mask)  || (len < blocksize)) { +				blk_offset = cur_offset; +				blk_len = cur_length; +				blk_ptr = cur_ptr; + +				/* Copy entire block */ +				while(blk_len) { +					copied = sflash_read(cc, blk_offset, blk_len, blk_ptr);  +					blk_offset += copied; +					blk_len -= copied; +					blk_ptr += copied; +				} +			} + +			/* Copy input data into holding block */ +			memcpy(cur_ptr + (offset & mask), buf, cur_retlen); +		} + +		/* Erase block */ +		if ((ret = sflash_erase(cc, (uint) cur_offset)) < 0) +			goto done; +		while (sflash_poll(cc, (uint) cur_offset)); + +		/* buf == NULL means erase only */ +		if (!buf) { +			offset += cur_retlen; +			len -= cur_retlen; +			continue; +		} + +		/* Write holding block */ +		while (cur_length > 0) { +			if ((bytes = sflash_write(cc, +						  (uint) cur_offset, +						  (uint) cur_length, +						  (uchar *) cur_ptr)) < 0) { +				ret = bytes; +				goto done; +			} +			while (sflash_poll(cc, (uint) cur_offset)); +			cur_offset += bytes; +			cur_length -= bytes; +			cur_ptr += bytes; +		} + +		offset += cur_retlen; +		len -= cur_retlen; +		buf += cur_retlen; +	} + +done: +	if (block) +		MFREE(block, blocksize); +	return ret; +} +  | 
