summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--chipdrivers.h5
-rw-r--r--meson.build1
-rw-r--r--s25f.c391
-rw-r--r--spi.h6
5 files changed, 404 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 327d43bb2..1f7ea4679 100644
--- a/Makefile
+++ b/Makefile
@@ -648,7 +648,7 @@ endif
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 cf0381172..b42c36387 100644
--- a/chipdrivers.h
+++ b/chipdrivers.h
@@ -170,6 +170,11 @@ int erase_sector_49lfxxxc(struct flashctx *flash, unsigned int address, unsigned
int printlock_sst_fwhub(struct flashctx *flash);
int unlock_sst_fwhub(struct flashctx *flash);
+/* s25f.c */
+int probe_spi_big_spansion(struct flashctx *flash);
+int s25fl_block_erase(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
+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/meson.build b/meson.build
index 1acbdb35e..fd5a5ef79 100644
--- a/meson.build
+++ b/meson.build
@@ -354,6 +354,7 @@ srcs += 'libflashrom.c'
srcs += 'opaque.c'
srcs += 'print.c'
srcs += 'programmer.c'
+srcs += 's25f.c'
srcs += 'sfdp.c'
srcs += 'spi25.c'
srcs += 'spi25_statusreg.c'
diff --git a/s25f.c b/s25f.c
new file mode 100644
index 000000000..32af53ef5
--- /dev/null
+++ b/s25f.c
@@ -0,0 +1,391 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2014 Google LLC.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * 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 "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)
+{
+ struct spi_command cmds[] = {
+ {
+ .writecnt = 1,
+ .writearr = (const uint8_t[]){ CMD_RSTEN },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 1,
+ .writearr = (const uint8_t[]){ 0xf0 },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 0,
+ .writearr = NULL,
+ .readcnt = 0,
+ .readarr = NULL,
+ }};
+
+ int 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)
+{
+ struct spi_command cmds[] = {
+ {
+ .writecnt = 1,
+ .writearr = (const uint8_t[]){ CMD_RSTEN },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 1,
+ .writearr = (const uint8_t[]){ CMD_RST },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 0,
+ .writearr = NULL,
+ .readcnt = 0,
+ .readarr = NULL,
+ }};
+
+ int 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)
+{
+ uint8_t cfg;
+ /* By default, 8 dummy cycles are necessary for variable-latency
+ commands such as RDAR (see CR2NV[3:0]). */
+ uint8_t read_cr_cmd[] = {
+ CMD_RDAR,
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ (addr & 0xff),
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ int 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" instruction only supported on S25FS */
+static int s25fs_write_cr(const struct flashctx *flash,
+ uint32_t addr, uint8_t data)
+{
+ struct spi_command cmds[] = {
+ {
+ .writecnt = JEDEC_WREN_OUTSIZE,
+ .writearr = (const uint8_t[]){ JEDEC_WREN },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = CMD_WRAR_LEN,
+ .writearr = (const uint8_t[]){
+ CMD_WRAR,
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ (addr & 0xff),
+ data
+ },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 0,
+ .writearr = NULL,
+ .readcnt = 0,
+ .readarr = NULL,
+ }};
+
+ int 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 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;
+}
+
+int s25fs_block_erase_d8(struct flashctx *flash,
+ uint32_t addr, uint32_t blocklen)
+{
+ static int cr3nv_checked = 0;
+
+ struct spi_command erase_cmds[] = {
+ {
+ .writecnt = JEDEC_WREN_OUTSIZE,
+ .writearr = (const uint8_t[]){ JEDEC_WREN },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = JEDEC_BE_D8_OUTSIZE,
+ .writearr = (const uint8_t[]){
+ 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) {
+ uint8_t 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;
+ }
+
+ int 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,
+ uint32_t addr, uint32_t blocklen)
+{
+ struct spi_command erase_cmds[] = {
+ {
+ .writecnt = JEDEC_WREN_OUTSIZE,
+ .writearr = (const uint8_t[]){
+ JEDEC_WREN
+ },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = JEDEC_BE_DC_OUTSIZE,
+ .writearr = (const uint8_t[]){
+ JEDEC_BE_DC,
+ (addr >> 24) & 0xff,
+ (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff,
+ (addr & 0xff)
+ },
+ .readcnt = 0,
+ .readarr = NULL,
+ }, {
+ .writecnt = 0,
+ .readcnt = 0,
+ }
+ };
+
+ int 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)
+{
+ uint8_t cmd = JEDEC_RDID;
+ uint8_t dev_id[6]; /* We care only about 6 first bytes */
+
+ if (spi_send_command(flash, sizeof(cmd), sizeof(dev_id), &cmd, dev_id))
+ return 0;
+
+ msg_gdbg("Read id bytes: ");
+ for (size_t i = 0; i < sizeof(dev_id); i++)
+ msg_gdbg(" 0x%02x", dev_id[i]);
+ msg_gdbg(".\n");
+
+ /*
+ * 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
+ *
+ */
+
+ uint32_t model_id =
+ dev_id[1] << 24 |
+ dev_id[2] << 16 |
+ dev_id[4] << 8 |
+ dev_id[5] << 0;
+
+ if (dev_id[0] == flash->chip->manufacture_id && model_id == flash->chip->model_id)
+ return 1;
+
+ return 0;
+}
diff --git a/spi.h b/spi.h
index c1ca46a21..09da5795c 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 */