diff options
| -rw-r--r-- | chipdrivers.h | 8 | ||||
| -rw-r--r-- | flash.h | 3 | ||||
| -rw-r--r-- | flashchips.c | 64 | ||||
| -rw-r--r-- | flashchips.h | 2 | ||||
| -rw-r--r-- | flashrom.c | 25 | ||||
| -rw-r--r-- | spi.h | 15 | ||||
| -rw-r--r-- | spi25.c | 218 | ||||
| -rw-r--r-- | spi25_statusreg.c | 86 | 
8 files changed, 415 insertions, 6 deletions
| diff --git a/chipdrivers.h b/chipdrivers.h index cb1e877a..aa240851 100644 --- a/chipdrivers.h +++ b/chipdrivers.h @@ -35,6 +35,9 @@ int probe_spi_res1(struct flashctx *flash);  int probe_spi_res2(struct flashctx *flash);  int probe_spi_res3(struct flashctx *flash);  int probe_spi_at25f(struct flashctx *flash); +int probe_spi_big_spansion(struct flashctx *flash); +int s25fs_software_reset(struct flashctx *flash); +int spi_poll_wip(struct flashctx *const flash, const unsigned int poll_delay);  int spi_write_enable(struct flashctx *flash);  int spi_write_disable(struct flashctx *flash);  int spi_block_erase_20(struct flashctx *flash, unsigned int addr, unsigned int blocklen); @@ -49,6 +52,7 @@ int spi_block_erase_c4(struct flashctx *flash, unsigned int addr, unsigned int b  int spi_block_erase_c7(struct flashctx *flash, unsigned int addr, unsigned int blocklen);  int spi_block_erase_d7(struct flashctx *flash, unsigned int addr, unsigned int blocklen);  int spi_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen); +int s25fs_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen);  int spi_block_erase_db(struct flashctx *flash, unsigned int addr, unsigned int blocklen);  int spi_block_erase_dc(struct flashctx *flash, unsigned int addr, unsigned int blocklen);  erasefunc_t *spi_get_erasefn_from_opcode(uint8_t opcode); @@ -60,10 +64,12 @@ int spi_enter_4ba(struct flashctx *flash);  int spi_exit_4ba(struct flashctx *flash);  int spi_set_extended_address(struct flashctx *, uint8_t addr_high); -  /* spi25_statusreg.c */  uint8_t spi_read_status_register(struct flashctx *flash);  int spi_write_status_register(struct flashctx *flash, int status); +int s25fs_read_cr(struct flashctx *const flash, uint32_t addr); +int s25fs_write_cr(struct flashctx *const flash, uint32_t addr, uint8_t data); +int s25fs_restore_cr3nv(struct flashctx *const flash, uint8_t cfg);  void spi_prettyprint_status_register_bit(uint8_t status, int bit);  int spi_prettyprint_status_register_plain(struct flashctx *flash);  int spi_prettyprint_status_register_default_welwip(struct flashctx *flash); @@ -54,6 +54,8 @@ typedef uintptr_t chipaddr;  #define PRIxPTR_WIDTH ((int)(sizeof(uintptr_t)*2))  int register_shutdown(int (*function) (void *data), void *data); +#define CHIP_RESTORE_CALLBACK	int (*func) (struct flashrom_flashctx *flash, uint8_t status) +int register_chip_restore(CHIP_RESTORE_CALLBACK, struct flashrom_flashctx *flash, uint8_t status);  int shutdown_free(void *data);  void *programmer_map_flash_region(const char *descr, uintptr_t phys_addr, size_t len);  void programmer_unmap_flash_region(void *virt_addr, size_t len); @@ -207,6 +209,7 @@ struct flashchip {  		SPI_EDI = 1,  	} spi_cmd_set; +	int (*reset) (struct flashctx *flash);  	int (*probe) (struct flashctx *flash);  	/* Delay after "enter/exit ID mode" commands in microseconds. diff --git a/flashchips.c b/flashchips.c index b4006dfa..974ba811 100644 --- a/flashchips.c +++ b/flashchips.c @@ -49,6 +49,7 @@ const struct flashchip flashchips[] = {  	 *	.eraseblocks[]	= Array of { blocksize, blockcount }  	 *	.block_erase	= Block erase function  	 * } +	 * .reset		= Reset Chip  	 * .printlock		= Chip lock status function  	 * .unlock		= Chip unlock function  	 * .write		= Chip write function @@ -15825,6 +15826,69 @@ const struct flashchip flashchips[] = {  	{  		.vendor		= "Spansion", +		.name		= "S25FS128S Large Sectors", +		.bustype	= BUS_SPI, +		.manufacture_id	= SPANSION_ID, +		.model_id	= SPANSION_S25FS128S_L, +		.total_size	= 16384, +		.page_size	= 256, +		.feature_bits	= FEATURE_WRSR_WREN, +		.tested		= TEST_UNTESTED, +		.probe		= probe_spi_big_spansion, +		.probe_timing	= TIMING_ZERO, +		.block_erasers	= +		{ +			{ +				.eraseblocks = { {64 * 1024, 256} }, +				.block_erase = s25fs_block_erase_d8, +			}, { +				.eraseblocks = { {16 * 1024 * 1024, 1} }, +				.block_erase = spi_block_erase_60, +			}, { +				.eraseblocks = { {16 * 1024 * 1024, 1} }, +				.block_erase = spi_block_erase_c7, +			}, +		}, +		.unlock		= spi_disable_blockprotect, +		.write		= spi_chip_write_256, +		.read		= spi_chip_read, +		.voltage	= {1700, 2000}, +	}, + +	{ +		.vendor		= "Spansion", +		.name		= "S25FS128S Small Sectors", +		.bustype	= BUS_SPI, +		.manufacture_id	= SPANSION_ID, +		.model_id	= SPANSION_S25FS128S_S, +		.total_size	= 16384, +		.page_size	= 256, +		.feature_bits	= FEATURE_WRSR_WREN, +		.tested		= TEST_OK_PREW, +		.probe		= probe_spi_big_spansion, +		.probe_timing	= TIMING_ZERO, +		.block_erasers	= +		{ +			{ +				.eraseblocks = { {64 * 1024, 256} }, +				.block_erase = s25fs_block_erase_d8, +			}, { +				.eraseblocks = { {16 * 1024 * 1024, 1} }, +				.block_erase = spi_block_erase_60, +			}, { +				.eraseblocks = { {16 * 1024 * 1024, 1} }, +				.block_erase = spi_block_erase_c7, +			}, +		}, +		.reset		= s25fs_software_reset, +		.unlock		= spi_disable_blockprotect, +		.write		= spi_chip_write_256, +		.read		= spi_chip_read, +		.voltage	= {1700, 2000}, +	}, + +	{ +		.vendor		= "Spansion",  		.name		= "S25FL129P......1", /* uniform 256 kB sectors */  		.bustype	= BUS_SPI,  		.manufacture_id	= SPANSION_ID, diff --git a/flashchips.h b/flashchips.h index e5ef3901..0c77d1d1 100644 --- a/flashchips.h +++ b/flashchips.h @@ -642,6 +642,8 @@  #define SPANSION_S25FL032A	0x0215	/* Same as S25FL032P, but the latter supports EDI and CFI */  #define SPANSION_S25FL064A	0x0216	/* Same as S25FL064P, but the latter supports EDI and CFI */  #define SPANSION_S25FL128	0x2018	/* Same ID for various S25FL127S, S25FL128P, S25FL128S and S25FL129P (including dual-die S70FL256P) variants (EDI supported) */ +#define SPANSION_S25FS128S_L	0x20180081  /* Large sectors. */ +#define SPANSION_S25FS128S_S	0x20180181  /* Small sectors. */  #define SPANSION_S25FL256	0x0219  #define SPANSION_S25FL512	0x0220  #define SPANSION_S25FL204	0x4013 @@ -500,6 +500,14 @@ const struct programmer_entry programmer_table[] = {  	{0}, /* This entry corresponds to PROGRAMMER_INVALID. */  }; +#define CHIP_RESTORE_MAXFN 4 +static int chip_restore_fn_count = 0; +static struct chip_restore_func_data { +	CHIP_RESTORE_CALLBACK; +	struct flashctx *flash; +	uint8_t status; +} chip_restore_fn[CHIP_RESTORE_MAXFN]; +  #define SHUTDOWN_MAXFN 32  static int shutdown_fn_count = 0;  /** @private */ @@ -550,6 +558,23 @@ int register_shutdown(int (*function) (void *data), void *data)  	return 0;  } +//int register_chip_restore(int (*function) (void *data), void *data) +int register_chip_restore(CHIP_RESTORE_CALLBACK, +                          struct flashctx *flash, uint8_t status) +{ +	if (chip_restore_fn_count >= CHIP_RESTORE_MAXFN) { +		msg_perr("Tried to register more than %i chip restore" +		         " functions.\n", CHIP_RESTORE_MAXFN); +		return 1; +	} +	chip_restore_fn[chip_restore_fn_count].func = func;	/* from macro */ +	chip_restore_fn[chip_restore_fn_count].flash = flash; +	chip_restore_fn[chip_restore_fn_count].status = status; +	chip_restore_fn_count++; + +	return 0; +} +  int programmer_init(enum programmer prog, const char *param)  {  	int ret; @@ -101,7 +101,7 @@  #define JEDEC_BE_C4_OUTSIZE	0x04  #define JEDEC_BE_C4_INSIZE	0x00 -/* Block Erase 0xd8 is supported by EON/Macronix chips. */ +/* Block Erase 0xd8 is supported by EON/Macronix/Spansion chips. */  #define JEDEC_BE_D8		0xd8  #define JEDEC_BE_D8_OUTSIZE	0x04  #define JEDEC_BE_D8_INSIZE	0x00 @@ -116,6 +116,18 @@  #define JEDEC_SE_OUTSIZE	0x04  #define JEDEC_SE_INSIZE		0x00 +/* RADR, WRAR, RSTEN, RST & CR3NV OPs and timers on Spansion S25FS chips */ +#define CMD_RDAR		0x65 +#define CMD_WRAR		0x71 +#define CMD_WRAR_LEN		5 +#define CMD_RSTEN		0x66 +#define CMD_RST			0x99 +#define CR3NV_ADDR		0x000004 +#define CR3NV_20H_NV		(1 << 3) +#define T_W			145 * 1000	/* NV register write time */ +#define T_RPH			35		/* Reset pulse hold time */ +#define T_SE			145 * 1000	/* Sector Erase Time */ +  /* Page Erase 0xDB */  #define JEDEC_PE		0xDB  #define JEDEC_PE_OUTSIZE	0x04 @@ -129,6 +141,7 @@  /* Status Register Bits */  #define SPI_SR_WIP	(0x01 << 0)  #define SPI_SR_WEL	(0x01 << 1) +#define SPI_SR_ERA_ERR	(0x01 << 5)  #define SPI_SR_AAI	(0x01 << 6)  /* Write Status Enable */ @@ -25,6 +25,7 @@  #include "flashchips.h"  #include "chipdrivers.h"  #include "programmer.h" +#include "hwaccess.h"  #include "spi.h"  static int spi_rdid(struct flashctx *flash, unsigned char *readarr, int bytes) @@ -284,13 +285,155 @@ int probe_spi_at25f(struct flashctx *flash)  	return 0;  } -static int spi_poll_wip(struct flashctx *const flash, const unsigned int poll_delay) +/* Used for probing 'big' Spansion/Cypress S25FS chips */ +int probe_spi_big_spansion(struct flashctx *flash)  { -	/* FIXME: We can't tell if spi_read_status_register() failed. */ +	static const unsigned char cmd = JEDEC_RDID; +	int ret; +	unsigned char dev_id[6]; /* We care only about 6 first bytes */ + +	ret = spi_send_command(flash, sizeof(cmd), sizeof(dev_id), &cmd, dev_id); + +	if (!ret) { +		unsigned long i; + +		for (i = 0; i < sizeof(dev_id); i++) +			msg_gdbg(" 0x%02x", dev_id[i]); +		msg_gdbg(".\n"); + +		if (dev_id[0] == flash->chip->manufacture_id) { +			union { +				uint8_t array[4]; +				uint32_t whole; +			} model_id; + +	/* +	 * The structure of the RDID output is as follows: +	 * +	 *     offset   value              meaning +	 *       00h     01h      Manufacturer ID for Spansion +	 *       01h     20h           128 Mb capacity +	 *       01h     02h           256 Mb capacity +	 *       02h     18h           128 Mb capacity +	 *       02h     19h           256 Mb capacity +	 *       03h     4Dh       Full size of the RDID output (ignored) +	 *       04h     00h       FS: 256-kB physical sectors +	 *       04h     01h       FS: 64-kB physical sectors +	 *       04h     00h       FL: 256-kB physical sectors +	 *       04h     01h       FL: Mix of 64-kB and 4KB overlayed sectors +	 *       05h     80h       FL family +	 *       05h     81h       FS family +	 * +	 * Need to use bytes 1, 2, 4, and 5 to properly identify one of eight +	 * possible chips: +	 * +	 * 2 types * 2 possible sizes * 2 possible sector layouts +	 * +	 */ +			memcpy(model_id.array, dev_id + 1, 2); +			memcpy(model_id.array + 2, dev_id + 4, 2); +			if (be_to_cpu32(model_id.whole) == flash->chip->model_id) +				return 1; +		} +	} + +	return 0; +} + +/* Used for Spansion/Cypress S25F chips */ +static int s25f_legacy_software_reset(struct flashctx *flash) +{ +	int result; +	struct spi_command cmds[] = { +	{ +		.writecnt	= 1, +		.writearr	= (const unsigned char[]){ CMD_RSTEN }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 1, +		.writearr	= (const unsigned char[]){ 0xf0 }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 0, +		.writearr	= NULL, +		.readcnt	= 0, +		.readarr	= NULL, +	}}; + +	result = spi_send_multicommand(flash, cmds); +	if (result) { +		msg_cerr("%s failed during command execution\n", __func__); +		return result; +	} +	/* Reset takes 35us according to data-sheet, double that for safety */ +	programmer_delay(T_RPH * 2); + +	return 0; +} + +/* Only for Spansion S25FS chips, where legacy reset is disabled by default */ +int s25fs_software_reset(struct flashctx *flash) +{ +	int result; +	struct spi_command cmds[] = { +	{ +		.writecnt	= 1, +		.writearr	= (const unsigned char[]){ CMD_RSTEN }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 1, +		.writearr	= (const unsigned char[]){ CMD_RST }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 0, +		.writearr	= NULL, +		.readcnt	= 0, +		.readarr	= NULL, +	}}; + +	msg_cdbg("Force resetting SPI chip.\n"); +	result = spi_send_multicommand(flash, cmds); +	if (result) { +		msg_cerr("%s failed during command execution\n", __func__); +		return result; +	} + +	programmer_delay(T_RPH * 2); + +	return 0; +} + +int spi_poll_wip(struct flashctx *const flash, const unsigned int poll_delay) +{ +	uint8_t status_reg = spi_read_status_register(flash); +  	/* FIXME: We don't time out. */ -	while (spi_read_status_register(flash) & SPI_SR_WIP) +	while (status_reg & SPI_SR_WIP) { +		/* +		 * The WIP bit on S25F chips remains set to 1 if erase or +		 * programming errors occur, so we must check for those +		 * errors here. If an error is encountered, do a software +		 * reset to clear WIP and other volatile bits, otherwise +		 * the chip will be unresponsive to further commands. +		 */ +		if (status_reg & SPI_SR_ERA_ERR) { +			msg_cerr("Erase error occurred\n"); +			s25f_legacy_software_reset(flash); +			return -1; +		} +		if (status_reg & (1 << 6)) { +			msg_cerr("Programming error occurred\n"); +			s25f_legacy_software_reset(flash); +			return -1; +		}  		programmer_delay(poll_delay); -	/* FIXME: Check the status register for errors. */ +		status_reg = spi_read_status_register(flash); +	} +  	return 0;  } @@ -486,6 +629,73 @@ int spi_block_erase_d8(struct flashctx *flash, unsigned int addr,  	return spi_write_cmd(flash, 0xd8, false, addr, NULL, 0, 100 * 1000);  } +/* Used on Spansion/Cypress S25FS chips */ +int s25fs_block_erase_d8(struct flashctx *flash, +		unsigned int addr, unsigned int blocklen) +{ +	unsigned char cfg; +	int result; +	static int cr3nv_checked = 0; + +	struct spi_command erase_cmds[] = { +	{ +		.writecnt	= JEDEC_WREN_OUTSIZE, +		.writearr	= (const unsigned char[]){ JEDEC_WREN }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= JEDEC_BE_D8_OUTSIZE, +		.writearr	= (const unsigned char[]){ +					JEDEC_BE_D8, +					(addr >> 16) & 0xff, +					(addr >> 8) & 0xff, +					(addr & 0xff) +				}, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 0, +		.writearr	= NULL, +		.readcnt	= 0, +		.readarr	= NULL, +	}}; + +	/* Check if hybrid sector architecture is in use and, if so, +	 * switch to uniform sectors. */ +	if (!cr3nv_checked) { +		cfg = s25fs_read_cr(flash, CR3NV_ADDR); +		if (!(cfg & CR3NV_20H_NV)) { +			s25fs_write_cr(flash, CR3NV_ADDR, cfg | CR3NV_20H_NV); +			s25fs_software_reset(flash); + +			cfg = s25fs_read_cr(flash, CR3NV_ADDR); +			if (!(cfg & CR3NV_20H_NV)) { +				msg_cerr("%s: Unable to enable uniform " +					"block sizes.\n", __func__); +				return 1; +			} + +			msg_cdbg("\n%s: CR3NV updated (0x%02x -> 0x%02x)\n", +					__func__, cfg, +					s25fs_read_cr(flash, CR3NV_ADDR)); +			/* Restore CR3V when flashrom exits */ +			register_chip_restore(s25fs_restore_cr3nv, flash, cfg); +		} + +		cr3nv_checked = 1; +	} + +	result = spi_send_multicommand(flash, erase_cmds); +	if (result) { +		msg_cerr("%s failed during command execution at address 0x%x\n", +			__func__, addr); +		return result; +	} + +	programmer_delay(T_SE); +	return spi_poll_wip(flash, 1000 * 10); +} +  /* Block size is usually   * 4k for PMC   */ diff --git a/spi25_statusreg.c b/spi25_statusreg.c index 8cd5a286..aa574d5a 100644 --- a/spi25_statusreg.c +++ b/spi25_statusreg.c @@ -108,6 +108,89 @@ uint8_t spi_read_status_register(struct flashctx *flash)  	return readarr[0];  } +static int spi_restore_status(struct flashctx *flash, uint8_t status) +{ +	msg_cdbg("restoring chip status (0x%02x)\n", status); +	return spi_write_status_register(flash, status); +} + +/* 'Read Any Register' used on Spansion/Cypress S25FS chips */ +int s25fs_read_cr(struct flashctx *const flash, uint32_t addr) +{ +	int result; +	uint8_t cfg; +	/* By default, 8 dummy cycles are necessary for variable-latency +	   commands such as RDAR (see CR2NV[3:0]). */ +	unsigned char read_cr_cmd[] = { +					CMD_RDAR, +					(addr >> 16) & 0xff, +					(addr >> 8) & 0xff, +					(addr & 0xff), +					0x00, 0x00, 0x00, 0x00, +					0x00, 0x00, 0x00, 0x00, +	}; + +	result = spi_send_command(flash, sizeof(read_cr_cmd), 1, read_cr_cmd, &cfg); +	if (result) { +		msg_cerr("%s failed during command execution at address 0x%x\n", +			__func__, addr); +		return -1; +	} + +	return cfg; +} + +/* 'Write Any Register' used on Spansion/Cypress S25FS chips */ +int s25fs_write_cr(struct flashctx *const flash, +			  uint32_t addr, uint8_t data) +{ +	int result; +	struct spi_command cmds[] = { +	{ +		.writecnt	= JEDEC_WREN_OUTSIZE, +		.writearr	= (const unsigned char[]){ JEDEC_WREN }, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= CMD_WRAR_LEN, +		.writearr	= (const unsigned char[]){ +					CMD_WRAR, +					(addr >> 16) & 0xff, +					(addr >> 8) & 0xff, +					(addr & 0xff), +					data +				}, +		.readcnt	= 0, +		.readarr	= NULL, +	}, { +		.writecnt	= 0, +		.writearr	= NULL, +		.readcnt	= 0, +		.readarr	= NULL, +	}}; + +	result = spi_send_multicommand(flash, cmds); +	if (result) { +		msg_cerr("%s failed during command execution at address 0x%x\n", +			__func__, addr); +		return -1; +	} + +	programmer_delay(T_W); +	return spi_poll_wip(flash, 1000 * 10); +} + +/* Used on Spansion/Cypress S25FS chips */ +int s25fs_restore_cr3nv(struct flashctx *const flash, uint8_t cfg) +{ +	int ret = 0; + +	msg_cdbg("Restoring CR3NV value to 0x%02x\n", cfg); +	ret |= s25fs_write_cr(flash, CR3NV_ADDR, cfg); +	ret |= s25fs_software_reset(flash); +	return ret; +} +  /* A generic block protection disable.   * Tests if a protection is enabled with the block protection mask (bp_mask) and returns success otherwise.   * Tests if the register bits are locked with the lock_mask (lock_mask). @@ -139,6 +222,9 @@ static int spi_disable_blockprotect_generic(struct flashctx *flash, uint8_t bp_m  		return 0;  	} +	/* restore status register content upon exit */ +	register_chip_restore(spi_restore_status, flash, status); +  	msg_cdbg("Some block protection in effect, disabling... ");  	if ((status & lock_mask) != 0) {  		msg_cdbg("\n\tNeed to disable the register lock first... "); | 
