drivers/spi: Read Winbond's flash protection bits

Extend the generic flash interface to probe for write protected regions.
Add Winbond custom code to return flash protection.

Tested on Cavium EVB CN81xx using W25Q128.

Change-Id: I933a8abdc28174ec32acf323c102d606b58c1ea5
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/25082
Reviewed-by: Julius Werner <jwerner@chromium.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/src/drivers/spi/spi_flash.c b/src/drivers/spi/spi_flash.c
index f271479..c484088 100644
--- a/src/drivers/spi/spi_flash.c
+++ b/src/drivers/spi/spi_flash.c
@@ -432,6 +432,28 @@
 	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 a25c9a6..a0e3884 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 @@
 	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 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 5,
 		.name				= "W25Q16",
+		.protection_granularity_shift	= 16,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x4016,
@@ -83,6 +125,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 6,
 		.name				= "W25Q32",
+		.protection_granularity_shift	= 16,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x6016,
@@ -91,6 +135,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 6,
 		.name				= "W25Q32DW",
+		.protection_granularity_shift	= 16,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x4017,
@@ -99,6 +145,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 7,
 		.name				= "W25Q64",
+		.protection_granularity_shift	= 17,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x6017,
@@ -107,6 +155,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 7,
 		.name				= "W25Q64DW",
+		.protection_granularity_shift	= 17,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x4018,
@@ -115,6 +165,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 8,
 		.name				= "W25Q128",
+		.protection_granularity_shift	= 18,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x6018,
@@ -123,6 +175,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 8,
 		.name				= "W25Q128FW",
+		.protection_granularity_shift	= 18,
+		.bp_bits			= 3,
 	},
 	{
 		.id				= 0x4019,
@@ -131,6 +185,8 @@
 		.sectors_per_block_shift	= 4,
 		.nr_blocks_shift		= 9,
 		.name				= "W25Q256",
+		.protection_granularity_shift	= 16,
+		.bp_bits			= 4,
 	},
 };
 
@@ -191,6 +247,102 @@
 	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 @@
 #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 @@
 	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 f7f3b3d..9f8d2d0 100644
--- a/src/include/spi_flash.h
+++ b/src/include/spi_flash.h
@@ -40,6 +40,16 @@
 			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 @@
 	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 @@
 		    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,