Nikolai Artemiev has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/46140 )
Change subject: WIP: s25f.c: import file from cros flashrom ......................................................................
WIP: s25f.c: import file from cros flashrom
Imported from cros flashrom at hash `9c4c9a56b6a0370b383df9c75d71b3bd469e672d`
Signed-off-by: Nikolai Artemiev nartemiev@google.com Change-Id: I2d23f9c36ce8b2959807fbeee7f60e02444e3763 --- M Makefile M chipdrivers.h M flash.h M flashrom.c A s25f.c M spi.h 6 files changed, 585 insertions(+), 4 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/40/46140/1
diff --git a/Makefile b/Makefile index 8af7042..2de7131 100644 --- a/Makefile +++ b/Makefile @@ -638,7 +638,7 @@ CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o edi.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o + spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o s25f.o
############################################################################### # Library code. diff --git a/chipdrivers.h b/chipdrivers.h index cf03811..225dce7 100644 --- a/chipdrivers.h +++ b/chipdrivers.h @@ -20,7 +20,8 @@ #ifndef __CHIPDRIVERS_H__ #define __CHIPDRIVERS_H__ 1
-#include "flash.h" /* for chipaddr and flashctx */ +#include "flash.h" /* for chipaddr and flashctx */ +#include "writeprotect.h" /* for modifier_bits */
/* spi.c */ int spi_aai_write(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); @@ -170,6 +171,15 @@ int printlock_sst_fwhub(struct flashctx *flash); int unlock_sst_fwhub(struct flashctx *flash);
+/* s25fl.c */ +int probe_spi_big_spansion(struct flashctx *flash); +int s25fl_block_erase(struct flashctx *flash, unsigned int addr, unsigned int blocklen); + +/* s25f.c */ +int s25f_get_modifier_bits(const struct flashctx *flash, struct modifier_bits *m); +int s25f_set_modifier_bits(const struct flashctx *flash, struct modifier_bits *m); +int s25fs_block_erase_d8(struct flashctx *flash, unsigned int addr, unsigned int blocklen); + /* w39.c */ int printlock_w39f010(struct flashctx * flash); int printlock_w39l010(struct flashctx * flash); diff --git a/flash.h b/flash.h index fefca9d..db64966 100644 --- a/flash.h +++ b/flash.h @@ -49,11 +49,17 @@ #define ERROR_OOM -100 #define TIMEOUT_ERROR -101
+struct flashrom_flashctx; +#define flashctx flashrom_flashctx /* TODO: Agree on a name and convert all occurences. */ + /* TODO: check using code for correct usage of types */ 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 flashctx *flash, uint8_t status) + +int register_chip_restore(CHIP_RESTORE_CALLBACK, struct 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); @@ -163,8 +169,6 @@ #define TEST_BAD_PRE (struct tested){ .probe = BAD, .read = BAD, .erase = BAD, .write = NT } #define TEST_BAD_PREW (struct tested){ .probe = BAD, .read = BAD, .erase = BAD, .write = BAD }
-struct flashrom_flashctx; -#define flashctx flashrom_flashctx /* TODO: Agree on a name and convert all occurences. */ typedef int (erasefunc_t)(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
struct flashchip { diff --git a/flashrom.c b/flashrom.c index c18a04f..6ed863b 100644 --- a/flashrom.c +++ b/flashrom.c @@ -536,6 +536,15 @@ {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 */ @@ -586,6 +595,23 @@ 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; diff --git a/s25f.c b/s25f.c new file mode 100644 index 0000000..a30618c --- /dev/null +++ b/s25f.c @@ -0,0 +1,535 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2014 Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Google or the names of contributors or + * licensors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. + * GOOGLE INC AND ITS LICENSORS SHALL NOT BE LIABLE + * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING + * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL + * GOOGLE OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, + * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR + * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF + * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, + * EVEN IF GOOGLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * s25f.c - Helper functions for Spansion S25FL and S25FS SPI flash chips. + * Uses 24 bit addressing for the FS chips and 32 bit addressing for the FL + * chips (which is required by the overlayed sector size devices). + * TODO: Implement fancy hybrid sector architecture helpers. + */ + +#include <string.h> + +#include "chipdrivers.h" +#include "hwaccess.h" +#include "spi.h" +#include "writeprotect.h" + +/* + * RDAR and WRAR are supported on chips which have more than one set of status + * and control registers and take an address of the register to read/write. + * WRR, RDSR2, and RDCR are used on chips with a more limited set of control/ + * status registers. + * + * WRR is somewhat peculiar. It shares the same opcode as JEDEC_WRSR, and if + * given one data byte (following the opcode) it acts the same way. If it's + * given two data bytes, the first data byte overwrites status register 1 + * and the second data byte overwrites config register 1. + */ +#define CMD_WRR 0x01 +#define CMD_WRDI 0x04 +#define CMD_RDSR2 0x07 /* note: read SR1 with JEDEC RDSR opcode */ +#define CMD_RDCR 0x35 +#define CMD_RDAR 0x65 +#define CMD_WRAR 0x71 + +/* TODO: For now, commands which use an address assume 24-bit addressing */ +#define CMD_WRR_LEN 3 +#define CMD_WRDI_LEN 1 +#define CMD_RDAR_LEN 4 +#define CMD_WRAR_LEN 5 + +#define CMD_RSTEN 0x66 +#define CMD_RST 0x99 + +#define CR1NV_ADDR 0x000002 +#define CR1_BPNV_O (1 << 3) +#define CR1_TBPROT_O (1 << 5) +#define CR3NV_ADDR 0x000004 +#define CR3NV_20H_NV (1 << 3) + +/* See "Embedded Algorithm Performance Tables for additional timing specs. */ +#define T_W 145 * 1000 /* NV register write time (145ms) */ +#define T_RPH 35 /* Reset pulse hold time (35us) */ +#define S25FS_T_SE 145 * 1000 /* Sector Erase Time (145ms) */ +#define S25FL_T_SE 130 * 1000 /* Sector Erase Time (130ms) */ + +static int s25f_legacy_software_reset(const 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; + } + + /* Allow time for reset command to execute. The datasheet specifies + * Trph = 35us, double that to be safe. */ + programmer_delay(T_RPH * 2); + + return 0; +} + +/* "Legacy software reset" is disabled by default on S25FS, use this instead. */ +static 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, + }}; + + result = spi_send_multicommand(flash, cmds); + if (result) { + msg_cerr("%s failed during command execution\n", __func__); + return result; + } + + /* Allow time for reset command to execute. Double tRPH to be safe. */ + programmer_delay(T_RPH * 2); + + return 0; +} + +static int s25f_poll_status(const struct flashctx *flash) +{ + uint8_t tmp = spi_read_status_register(flash); + + while (tmp & 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 (tmp & SPI_SR_ERA_ERR) { + msg_cerr("Erase error occurred\n"); + s25f_legacy_software_reset(flash); + return -1; + } + + if (tmp & (1 << 6)) { + msg_cerr("Programming error occurred\n"); + s25f_legacy_software_reset(flash); + return -1; + } + + programmer_delay(1000 * 10); + tmp = spi_read_status_register(flash); + } + + return 0; +} + +/* "Read Any Register" instruction only supported on S25FS */ +static int s25fs_read_cr(const struct flashctx *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; +} + +static int s25f_read_cr1(const struct flashctx *flash) +{ + int result; + uint8_t cfg; + unsigned char read_cr_cmd[] = { CMD_RDCR }; + + result = spi_send_command(flash, sizeof(read_cr_cmd), 1, read_cr_cmd, &cfg); + if (result) { + msg_cerr("%s failed during command execution\n", __func__); + return -1; + } + + return cfg; +} + +/* "Write Any Register" instruction only supported on S25FS */ +static int s25fs_write_cr(const struct flashctx *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 s25f_poll_status(flash); +} + +static int s25f_write_cr1(const struct flashctx *flash, 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_WRR_LEN, + .writearr = (const unsigned char[]){ + CMD_WRR, + spi_read_status_register(flash), + 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\n", __func__); + return -1; + } + + programmer_delay(T_W); + return s25f_poll_status(flash); +} + +static int s25fs_restore_cr3nv(struct flashctx *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; +} + +/* returns state of top/bottom block protection, or <0 to indicate error */ +static int s25f_get_tbprot_o(const struct flashctx *flash) +{ + int cr1 = s25f_read_cr1(flash); + + if (cr1 < 0) + return -1; + + /* + * 1 = BP starts at bottom (low address) + * 0 = BP start at top (high address) + */ + return cr1 & CR1_TBPROT_O ? 1 : 0; +} + +/* fills modifier_bits struct, returns 0 to indicate success */ +int s25f_get_modifier_bits(const struct flashctx *flash, + struct modifier_bits *m) +{ + int tmp; + + memset(m, 0, sizeof(*m)); + + tmp = s25f_get_tbprot_o(flash); + if (tmp < 0) + return -1; + m->tb = tmp; + + return 0; +} + +int s25f_set_modifier_bits(const struct flashctx *flash, + struct modifier_bits *m) +{ + int cr1, cr1_orig; + + cr1 = cr1_orig = s25f_read_cr1(flash); + if (cr1 < 0) + return -1; + + /* + * Clear BPNV so that setting BP2-0 in status register gets + * written to non-volatile memory. + * + * For TBPROT: + * 1 = BP starts at bottom (low address) + * 0 = BP start at top (high address) + */ + cr1 &= ~(CR1_BPNV_O | CR1_TBPROT_O); + cr1 |= m->tb ? CR1_TBPROT_O : 0; + + if (cr1 != cr1_orig) { + msg_cdbg("%s: setting cr1 bits to 0x%02x\n", __func__, cr1); + if (s25f_write_cr1(flash, cr1) < 0) + return -1; + if (s25f_read_cr1(flash) != cr1) { + msg_cerr("%s: failed to set CR1 value\n", __func__); + return -1; + } + } else { + msg_cdbg("%s: cr1 bits already match desired value: " + "0x%02x\n", __func__, cr1); + } + + return 0; +} + +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(S25FS_T_SE); + return s25f_poll_status(flash); +} + +int s25fl_block_erase(struct flashctx *flash, + unsigned int addr, unsigned int blocklen) +{ + int result; + + struct spi_command erase_cmds[] = { + { + .writecnt = JEDEC_WREN_OUTSIZE, + .writearr = (const unsigned char[]){ + JEDEC_WREN + }, + .readcnt = 0, + .readarr = NULL, + }, { + .writecnt = JEDEC_BE_DC_OUTSIZE, + .writearr = (const unsigned char[]){ + JEDEC_BE_DC, + (addr >> 24) & 0xff, + (addr >> 16) & 0xff, + (addr >> 8) & 0xff, + (addr & 0xff) + }, + .readcnt = 0, + .readarr = NULL, + }, { + .writecnt = 0, + .readcnt = 0, + } + }; + + 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(S25FL_T_SE); + return s25f_poll_status(flash); +} + + +int probe_spi_big_spansion(struct flashctx *flash) +{ + 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) { + for (size_t 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; +} diff --git a/spi.h b/spi.h index c1ca46a..09da579 100644 --- a/spi.h +++ b/spi.h @@ -111,6 +111,11 @@ #define JEDEC_BE_D7_OUTSIZE 0x04 #define JEDEC_BE_D7_INSIZE 0x00
+/* Block Erase 0xdc is supported by Spansion chips, takes 4 byte address */ +#define JEDEC_BE_DC 0xdc +#define JEDEC_BE_DC_OUTSIZE 0x05 +#define JEDEC_BE_DC_INSIZE 0x00 + /* Sector Erase 0x20 is supported by Macronix/SST chips. */ #define JEDEC_SE 0x20 #define JEDEC_SE_OUTSIZE 0x04 @@ -129,6 +134,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 */