summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/drivers/spi/spi_flash.c22
-rw-r--r--src/drivers/spi/winbond.c154
-rw-r--r--src/include/spi_flash.h26
3 files changed, 202 insertions, 0 deletions
diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c
index f2714791db..c4840886bb 100644
--- a/src/drivers/spi/spi_flash.c
+++ b/src/drivers/spi/spi_flash.c
@@ -432,6 +432,28 @@ int spi_flash_status(const struct spi_flash *flash, u8 *reg)
return -1;
}
+int spi_flash_is_write_protected(const struct spi_flash *flash,
+ const struct region *region)
+{
+ struct region flash_region = { 0 };
+
+ if (!flash || !region)
+ return -1;
+
+ flash_region.size = flash->size;
+
+ if (!region_is_subregion(&flash_region, region))
+ return -1;
+
+ if (!flash->ops->get_write_protection) {
+ printk(BIOS_WARNING, "SPI: Write-protection gathering not "
+ "implemented for this vendor.\n");
+ return 0;
+ }
+
+ return flash->ops->get_write_protection(flash, region);
+}
+
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 a25c9a65ea..a0e3884e07 100644
--- a/src/drivers/spi/winbond.c
+++ b/src/drivers/spi/winbond.c
@@ -9,6 +9,7 @@
#include <spi_flash.h>
#include <spi-generic.h>
#include <string.h>
+#include <assert.h>
#include "spi_flash_internal.h"
@@ -17,6 +18,8 @@
#define CMD_W25_WRDI 0x04 /* Write Disable */
#define CMD_W25_RDSR 0x05 /* Read Status Register */
#define CMD_W25_WRSR 0x01 /* Write Status Register */
+#define CMD_W25_RDSR2 0x35 /* Read Status2 Register */
+#define CMD_W25_WRSR2 0x31 /* Write Status2 Register */
#define CMD_W25_READ 0x03 /* Read Data Bytes */
#define CMD_W25_FAST_READ 0x0b /* Read Data Bytes at Higher Speed */
#define CMD_W25_PP 0x02 /* Page Program */
@@ -32,9 +35,46 @@ struct winbond_spi_flash_params {
uint8_t pages_per_sector_shift : 4;
uint8_t sectors_per_block_shift : 4;
uint8_t nr_blocks_shift;
+ uint8_t bp_bits : 3;
+ uint8_t protection_granularity_shift : 5;
char name[10];
};
+union status_reg1_bp3 {
+ uint8_t u;
+ struct {
+ uint8_t busy : 1;
+ uint8_t wel : 1;
+ uint8_t bp : 3;
+ uint8_t tb : 1;
+ uint8_t sec : 1;
+ uint8_t srp0 : 1;
+ };
+};
+
+union status_reg1_bp4 {
+ uint8_t u;
+ struct {
+ uint8_t busy : 1;
+ uint8_t wel : 1;
+ uint8_t bp : 4;
+ uint8_t tb : 1;
+ uint8_t srp0 : 1;
+ };
+};
+
+union status_reg2 {
+ uint8_t u;
+ struct {
+ uint8_t srp1 : 1;
+ uint8_t qe : 1;
+ uint8_t res : 1;
+ uint8_t lb : 3;
+ uint8_t cmp : 1;
+ uint8_t sus : 1;
+ };
+};
+
static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
{
.id = 0x3015,
@@ -75,6 +115,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 5,
.name = "W25Q16",
+ .protection_granularity_shift = 16,
+ .bp_bits = 3,
},
{
.id = 0x4016,
@@ -83,6 +125,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 6,
.name = "W25Q32",
+ .protection_granularity_shift = 16,
+ .bp_bits = 3,
},
{
.id = 0x6016,
@@ -91,6 +135,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 6,
.name = "W25Q32DW",
+ .protection_granularity_shift = 16,
+ .bp_bits = 3,
},
{
.id = 0x4017,
@@ -99,6 +145,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 7,
.name = "W25Q64",
+ .protection_granularity_shift = 17,
+ .bp_bits = 3,
},
{
.id = 0x6017,
@@ -107,6 +155,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 7,
.name = "W25Q64DW",
+ .protection_granularity_shift = 17,
+ .bp_bits = 3,
},
{
.id = 0x4018,
@@ -115,6 +165,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 8,
.name = "W25Q128",
+ .protection_granularity_shift = 18,
+ .bp_bits = 3,
},
{
.id = 0x6018,
@@ -123,6 +175,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 8,
.name = "W25Q128FW",
+ .protection_granularity_shift = 18,
+ .bp_bits = 3,
},
{
.id = 0x4019,
@@ -131,6 +185,8 @@ static const struct winbond_spi_flash_params winbond_spi_flash_table[] = {
.sectors_per_block_shift = 4,
.nr_blocks_shift = 9,
.name = "W25Q256",
+ .protection_granularity_shift = 16,
+ .bp_bits = 4,
},
};
@@ -191,6 +247,102 @@ out:
return ret;
}
+/*
+ * Convert BPx, TB and CMP to a region.
+ * SEC (if available) must be zero.
+ */
+static void winbond_bpbits_to_region(const size_t granularity,
+ const u8 bp,
+ bool tb,
+ const bool cmp,
+ const size_t flash_size,
+ struct region *out)
+{
+ size_t protected_size =
+ min(bp ? granularity << (bp - 1) : 0, flash_size);
+
+ if (cmp) {
+ protected_size = flash_size - protected_size;
+ tb = !tb;
+ }
+
+ out->offset = tb ? flash_size - protected_size : 0;
+ out->size = protected_size;
+}
+
+/*
+ * Available on all devices.
+ * Read block protect bits from Status/Status2 Reg.
+ * Converts block protection bits to a region.
+ *
+ * Returns:
+ * -1 on error
+ * 1 if region is covered by write protection
+ * 0 if a part of region isn't covered by write protection
+ */
+static int winbond_get_write_protection(const struct spi_flash *flash,
+ const struct region *region)
+{
+ const struct winbond_spi_flash_params *params;
+ struct region wp_region;
+ union status_reg2 reg2;
+ u8 bp, tb;
+ int ret;
+
+ params = (const struct winbond_spi_flash_params *)flash->driver_private;
+ const size_t granularity = (1 << params->protection_granularity_shift);
+
+ if (params->bp_bits == 3) {
+ union status_reg1_bp3 reg1_bp3;
+
+ ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp3.u,
+ sizeof(reg1_bp3.u));
+ if (ret)
+ return ret;
+
+ if (reg1_bp3.sec) {
+ // FIXME: not supported
+ return -1;
+ }
+
+ bp = reg1_bp3.bp;
+ tb = reg1_bp3.tb;
+ } else if (params->bp_bits == 4) {
+ union status_reg1_bp4 reg1_bp4;
+
+ ret = spi_flash_cmd(&flash->spi, flash->status_cmd, &reg1_bp4.u,
+ sizeof(reg1_bp4.u));
+ if (ret)
+ return ret;
+
+ bp = reg1_bp4.bp;
+ tb = reg1_bp4.tb;
+ } else {
+ // FIXME: not supported
+ return -1;
+ }
+
+ ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, &reg2.u,
+ sizeof(reg2.u));
+ if (ret)
+ return ret;
+
+ winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size,
+ &wp_region);
+
+ if (!reg2.srp1 || !wp_region.size) {
+ printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
+
+ return 0;
+ }
+
+ printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
+ wp_region.offset, wp_region.size);
+
+ return region_is_subregion(&wp_region, region);
+}
+
+
static const struct spi_flash_ops spi_flash_ops = {
.write = winbond_write,
.erase = spi_flash_cmd_erase,
@@ -200,6 +352,7 @@ static const struct spi_flash_ops spi_flash_ops = {
#else
.read = spi_flash_cmd_read_fast,
#endif
+ .get_write_protection = winbond_get_write_protection,
};
int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
@@ -234,6 +387,7 @@ int spi_flash_probe_winbond(const struct spi_slave *spi, u8 *idcode,
flash->status_cmd = CMD_W25_RDSR;
flash->ops = &spi_flash_ops;
+ flash->driver_private = params;
return 0;
}
diff --git a/src/include/spi_flash.h b/src/include/spi_flash.h
index f7f3b3dbdf..9f8d2d06ea 100644
--- a/src/include/spi_flash.h
+++ b/src/include/spi_flash.h
@@ -40,6 +40,16 @@ struct spi_flash_ops {
const void *buf);
int (*erase)(const struct spi_flash *flash, u32 offset, size_t len);
int (*status)(const struct spi_flash *flash, u8 *reg);
+ /*
+ * Returns 1 if the whole region is software write protected.
+ * 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.
+ */
+ int (*get_write_protection)(const struct spi_flash *flash,
+ const struct region *region);
+
};
struct spi_flash {
@@ -51,6 +61,7 @@ struct spi_flash {
u8 erase_cmd;
u8 status_cmd;
const struct spi_flash_ops *ops;
+ const void *driver_private;
};
void lb_spi_flash(struct lb_header *header);
@@ -93,6 +104,21 @@ int spi_flash_write(const struct spi_flash *flash, u32 offset, size_t len,
const void *buf);
int spi_flash_erase(const struct spi_flash *flash, u32 offset, size_t len);
int spi_flash_status(const struct spi_flash *flash, u8 *reg);
+
+/*
+ * Return the vendor dependent SPI flash write protection state.
+ * @param flash : A SPI flash device
+ * @param region: A subregion of the device's region
+ *
+ * Returns:
+ * -1 on error
+ * 0 if the device doesn't support block protection
+ * 0 if the device doesn't enable block protection
+ * 0 if given range isn't covered by block protection
+ * 1 if given range is covered by block protection
+ */
+int spi_flash_is_write_protected(const struct spi_flash *flash,
+ const struct region *region);
/*
* Some SPI controllers require exclusive access to SPI flash when volatile
* operations like erase or write are being performed. In such cases,