summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/drivers/spi/spi_flash.c49
-rw-r--r--src/drivers/spi/winbond.c250
-rw-r--r--src/include/spi_flash.h58
3 files changed, 356 insertions, 1 deletions
diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c
index c4840886bb..cc21ccb20e 100644
--- a/src/drivers/spi/spi_flash.c
+++ b/src/drivers/spi/spi_flash.c
@@ -454,6 +454,55 @@ int spi_flash_is_write_protected(const struct spi_flash *flash,
return flash->ops->get_write_protection(flash, region);
}
+int spi_flash_set_write_protected(const struct spi_flash *flash,
+ const struct region *region,
+ const bool non_volatile,
+ const enum spi_flash_status_reg_lockdown mode)
+{
+ struct region flash_region = { 0 };
+ int ret;
+
+ if (!flash)
+ return -1;
+
+ flash_region.size = flash->size;
+
+ if (!region_is_subregion(&flash_region, region))
+ return -1;
+
+ if (!flash->ops->set_write_protection) {
+ printk(BIOS_WARNING, "SPI: Setting write-protection is not "
+ "implemented for this vendor.\n");
+ return 0;
+ }
+
+ ret = flash->ops->set_write_protection(flash, region, non_volatile,
+ mode);
+
+ if (ret == 0 && mode != SPI_WRITE_PROTECTION_PRESERVE) {
+ printk(BIOS_INFO, "SPI: SREG lock-down was set to ");
+ switch (mode) {
+ case SPI_WRITE_PROTECTION_NONE:
+ printk(BIOS_INFO, "NEVER\n");
+ break;
+ case SPI_WRITE_PROTECTION_PIN:
+ printk(BIOS_INFO, "WP\n");
+ break;
+ case SPI_WRITE_PROTECTION_REBOOT:
+ printk(BIOS_INFO, "REBOOT\n");
+ break;
+ case SPI_WRITE_PROTECTION_PERMANENT:
+ printk(BIOS_INFO, "PERMANENT\n");
+ break;
+ default:
+ printk(BIOS_INFO, "UNKNOWN\n");
+ break;
+ }
+ }
+
+ return ret;
+}
+
static uint32_t volatile_group_count CAR_GLOBAL;
int spi_flash_volatile_group_begin(const struct spi_flash *flash)
diff --git a/src/drivers/spi/winbond.c b/src/drivers/spi/winbond.c
index e2c653865d..9eb335262d 100644
--- a/src/drivers/spi/winbond.c
+++ b/src/drivers/spi/winbond.c
@@ -10,6 +10,8 @@
#include <spi-generic.h>
#include <string.h>
#include <assert.h>
+#include <delay.h>
+#include <lib.h>
#include "spi_flash_internal.h"
@@ -28,6 +30,10 @@
#define CMD_W25_CE 0xc7 /* Chip Erase */
#define CMD_W25_DP 0xb9 /* Deep Power-down */
#define CMD_W25_RES 0xab /* Release from DP, and Read Signature */
+#define CMD_VOLATILE_SREG_WREN 0x50 /* Write Enable for Volatile SREG */
+
+/* tw: Maximum time to write a flash cell in milliseconds */
+#define WINBOND_FLASH_TIMEOUT 30
struct winbond_spi_flash_params {
uint16_t id;
@@ -75,6 +81,27 @@ union status_reg2 {
};
};
+struct status_regs {
+ union {
+ struct {
+#if defined(__BIG_ENDIAN)
+ union status_reg2 reg2;
+ union {
+ union status_reg1_bp3 reg1_bp3;
+ union status_reg1_bp4 reg1_bp4;
+ };
+#else
+ union {
+ union status_reg1_bp3 reg1_bp3;
+ union status_reg1_bp4 reg1_bp4;
+ };
+ union status_reg2 reg2;
+#endif
+ };
+ u16 u;
+ };
+};
+
static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
{
.id = 0x3015,
@@ -351,6 +378,228 @@ static int winbond_get_write_protection(const struct spi_flash *flash,
return region_is_subregion(&wp_region, region);
}
+/**
+ * Common method to write some bit of the status register 1 & 2 at the same
+ * time. Only change bits that are one in @mask.
+ * Compare the final result to make sure that the register isn't locked.
+ *
+ * @param mask: The bits that are affected by @val
+ * @param val: The bits to write
+ * @param non_volatile: Make setting permanent
+ *
+ * @return 0 on success
+ */
+static int winbond_flash_cmd_status(const struct spi_flash *flash,
+ const u16 mask,
+ const u16 val,
+ const bool non_volatile)
+{
+ struct {
+ u8 cmd;
+ u16 sreg;
+ } __packed cmdbuf;
+ u8 reg8;
+ int ret;
+
+ if (!flash)
+ return -1;
+
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
+ if (ret)
+ return ret;
+
+ cmdbuf.sreg = reg8;
+
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
+ if (ret)
+ return ret;
+
+ cmdbuf.sreg |= reg8 << 8;
+
+ if ((val & mask) == (cmdbuf.sreg & mask))
+ return 0;
+
+ if (non_volatile) {
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
+ } else {
+ ret = spi_flash_cmd(&flash->spi, CMD_VOLATILE_SREG_WREN, NULL,
+ 0);
+ }
+ if (ret)
+ return ret;
+
+ cmdbuf.sreg &= ~mask;
+ cmdbuf.sreg |= val & mask;
+ cmdbuf.cmd = CMD_W25_WRSR;
+
+ /* Legacy method of writing status register 1 & 2 */
+ ret = spi_flash_cmd_write(&flash->spi, (u8 *)&cmdbuf, sizeof(cmdbuf),
+ NULL, 0);
+ if (ret)
+ return ret;
+
+ if (non_volatile) {
+ /* Wait tw */
+ ret = spi_flash_cmd_wait_ready(flash, WINBOND_FLASH_TIMEOUT);
+ if (ret)
+ return ret;
+ } else {
+ /* Wait tSHSL */
+ udelay(1);
+ }
+
+ /* Now read the status register to make sure it's not locked */
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR, &reg8, sizeof(reg8));
+ if (ret)
+ return ret;
+
+ cmdbuf.sreg = reg8;
+
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg8, sizeof(reg8));
+ if (ret)
+ return ret;
+
+ cmdbuf.sreg |= reg8 << 8;
+
+ printk(BIOS_DEBUG, "WINBOND: SREG=%02x SREG2=%02x\n",
+ cmdbuf.sreg & 0xff,
+ cmdbuf.sreg >> 8);
+
+ /* Compare against expected result */
+ if ((val & mask) != (cmdbuf.sreg & mask)) {
+ printk(BIOS_ERR, "WINBOND: SREG is locked!\n");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/*
+ * Available on all devices.
+ * Protect a region starting from start of flash or end of flash.
+ * The caller must provide a supported protected region size.
+ * SEC isn't supported and set to zero.
+ * Write block protect bits to Status/Status2 Reg.
+ * Optionally lock the status register if lock_sreg is set with the provided
+ * mode.
+ *
+ * @param flash: The flash to operate on
+ * @param region: The region to write protect
+ * @param non_volatile: Make setting permanent
+ * @param mode: Optional status register lock-down mode
+ *
+ * @return 0 on success
+ */
+static int
+winbond_set_write_protection(const struct spi_flash *flash,
+ const struct region *region,
+ const bool non_volatile,
+ const enum spi_flash_status_reg_lockdown mode)
+{
+ const struct winbond_spi_flash_params *params;
+ struct status_regs mask, val;
+ struct region wp_region;
+ u8 cmp, bp, tb;
+ int ret;
+
+ /* Need to touch TOP or BOTTOM */
+ if (region_offset(region) != 0 &&
+ (region_offset(region) + region_sz(region)) != flash->size)
+ return -1;
+
+ params = (const struct winbond_spi_flash_params *)flash->driver_private;
+ if (!params)
+ return -1;
+
+ if (params->bp_bits != 3 && params->bp_bits != 4) {
+ /* FIXME: not implemented */
+ return -1;
+ }
+
+ wp_region = *region;
+
+ if (region_offset(&wp_region) == 0)
+ tb = 0;
+ else
+ tb = 1;
+
+ if (region_sz(&wp_region) > flash->size / 2) {
+ cmp = 1;
+ wp_region.offset = tb ? 0 : region_sz(&wp_region);
+ wp_region.size = flash->size - region_sz(&wp_region);
+ tb = !tb;
+ } else {
+ cmp = 0;
+ }
+
+ if (region_sz(&wp_region) == 0) {
+ bp = 0;
+ } else if (IS_POWER_OF_2(region_sz(&wp_region)) &&
+ (region_sz(&wp_region) >=
+ (1 << params->protection_granularity_shift))) {
+ bp = log2(region_sz(&wp_region)) -
+ params->protection_granularity_shift + 1;
+ } else {
+ printk(BIOS_ERR, "WINBOND: ERROR: unsupported region size\n");
+ return -1;
+ }
+
+ /* Write block protection bits */
+
+ if (params->bp_bits == 3) {
+ val.reg1_bp3 = (union status_reg1_bp3) { .bp = bp, .tb = tb,
+ .sec = 0 };
+ mask.reg1_bp3 = (union status_reg1_bp3) { .bp = ~0, .tb = 1,
+ .sec = 1 };
+ } else {
+ val.reg1_bp4 = (union status_reg1_bp4) { .bp = bp, .tb = tb };
+ mask.reg1_bp4 = (union status_reg1_bp4) { .bp = ~0, .tb = 1 };
+ }
+
+ val.reg2 = (union status_reg2) { .cmp = cmp };
+ mask.reg2 = (union status_reg2) { .cmp = 1 };
+
+ if (mode != SPI_WRITE_PROTECTION_PRESERVE) {
+ u8 srp;
+ switch (mode) {
+ case SPI_WRITE_PROTECTION_NONE:
+ srp = 0;
+ break;
+ case SPI_WRITE_PROTECTION_PIN:
+ srp = 1;
+ break;
+ case SPI_WRITE_PROTECTION_REBOOT:
+ srp = 2;
+ break;
+ case SPI_WRITE_PROTECTION_PERMANENT:
+ srp = 3;
+ break;
+ default:
+ return -1;
+ }
+
+ if (params->bp_bits == 3) {
+ val.reg1_bp3.srp0 = !!(srp & 1);
+ mask.reg1_bp3.srp0 = 1;
+ } else {
+ val.reg1_bp4.srp0 = !!(srp & 1);
+ mask.reg1_bp4.srp0 = 1;
+ }
+
+ val.reg2.srp1 = !!(srp & 2);
+ mask.reg2.srp1 = 1;
+ }
+
+ ret = winbond_flash_cmd_status(flash, mask.u, val.u, non_volatile);
+ if (ret)
+ return ret;
+
+ printk(BIOS_DEBUG, "WINBOND: write-protection set to range "
+ "0x%08zx-0x%08zx\n", region_offset(region),
+ region_offset(region) + region_sz(region));
+
+ return ret;
+}
static const struct spi_flash_ops spi_flash_ops = {
.write = winbond_write,
@@ -362,6 +611,7 @@ static const struct spi_flash_ops spi_flash_ops = {
.read = spi_flash_cmd_read_fast,
#endif
.get_write_protection = winbond_get_write_protection,
+ .set_write_protection = winbond_set_write_protection,
};
int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
diff --git a/src/include/spi_flash.h b/src/include/spi_flash.h
index 9f8d2d06ea..64ad7fe04a 100644
--- a/src/include/spi_flash.h
+++ b/src/include/spi_flash.h
@@ -27,6 +27,25 @@
struct spi_flash;
/*
+ * SPI write protection is enforced by locking the status register.
+ * The following modes are known. It depends on the flash chip if the
+ * mode is actually supported.
+ *
+ * PRESERVE : Keep the previous status register lock-down setting (noop)
+ * NONE : Status register isn't locked
+ * PIN : Status register is locked as long as the ~WP pin is active
+ * REBOOT : Status register is locked until power failure
+ * PERMANENT: Status register is permanently locked
+ */
+enum spi_flash_status_reg_lockdown {
+ SPI_WRITE_PROTECTION_PRESERVE = -1,
+ SPI_WRITE_PROTECTION_NONE = 0,
+ SPI_WRITE_PROTECTION_PIN,
+ SPI_WRITE_PROTECTION_REBOOT,
+ SPI_WRITE_PROTECTION_PERMANENT
+};
+
+/*
* Representation of SPI flash operations:
* read: Flash read operation.
* write: Flash write operation.
@@ -45,10 +64,26 @@ struct spi_flash_ops {
* Hardware write protection mechanism aren't accounted.
* If the write protection could be changed, due to unlocked status
* register for example, 0 should be returned.
- * Returns -1 on error.
+ * Returns 0 on success.
*/
int (*get_write_protection)(const struct spi_flash *flash,
const struct region *region);
+ /*
+ * Enable the status register write protection, if supported on the
+ * requested region, and optionally enable status register lock-down.
+ * Returns 0 if the whole region was software write protected.
+ * Hardware write protection mechanism aren't accounted.
+ * If the status register is locked and the requested configuration
+ * doesn't match the selected one, return an error.
+ * Only a single region is supported !
+ *
+ * @return 0 on success
+ */
+ int
+ (*set_write_protection)(const struct spi_flash *flash,
+ const struct region *region,
+ const bool non_volatile,
+ const enum spi_flash_status_reg_lockdown mode);
};
@@ -120,6 +155,27 @@ int spi_flash_status(const struct spi_flash *flash, u8 *reg);
int spi_flash_is_write_protected(const struct spi_flash *flash,
const struct region *region);
/*
+ * Enable the vendor dependent SPI flash write protection. The region not
+ * covered by write-protection will be set to write-able state.
+ * Only a single write-protected region is supported.
+ * Some flash ICs require the region to be aligned in the block size, sector
+ * size or page size.
+ * Some flash ICs require the region to start at TOP or BOTTOM.
+ *
+ * @param flash : A SPI flash device
+ * @param region: A subregion of the device's region
+ * @param non_volatile: Write status register non-volatile
+ * @param mode: Optional lock-down of status register
+
+ * @return 0 on success
+ */
+int
+spi_flash_set_write_protected(const struct spi_flash *flash,
+ const struct region *region,
+ const bool non_volatile,
+ const enum spi_flash_status_reg_lockdown mode);
+
+/*
* Some SPI controllers require exclusive access to SPI flash when volatile
* operations like erase or write are being performed. In such cases,
* volatile_group_begin will gain exclusive access to SPI flash if not already