Flashrom: Add write-protect support on Skylake

This patch does the following:
1. Adds write-protect using hardware sequencing
   for W25R128FV flash chip.
2. Removes software sequencing in Skylake as it
   is not supported.

BUG=chrome-os-partner:37711
TEST=flashrom -p host --wp-status

flashrom -p host --wp-range 0 0
flashrom -p host --wp-disable
flashrom -p host --wp-enable

Change-Id: I74799e34eb4ae23de5d1cba87af32c3828b44fc8
Signed-off-by: Ramya Vijaykumar <ramya.vijaykumar@intel.com>
Reviewed-on: https://chromium-review.googlesource.com/270304
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Commit-Queue: Wenkai Du <wenkai.du@intel.com>
Tested-by: Wenkai Du <wenkai.du@intel.com>
diff --git a/flash.h b/flash.h
index 913333c..daa4ffd 100644
--- a/flash.h
+++ b/flash.h
@@ -229,6 +229,7 @@
 #define TIMING_ZERO	-2
 
 extern const struct flashchip flashchips[];
+extern const struct flashchip flashchips_hwseq[];
 
 /* print.c */
 char *flashbuses_to_text(enum chipbustype bustype);
diff --git a/flashchips.c b/flashchips.c
index 2914b11..deb7a85 100644
--- a/flashchips.c
+++ b/flashchips.c
@@ -10521,6 +10521,36 @@
 	{ NULL 	}
 };
 
+/* List of all flashchips on platforms
+ * that use HWSEQ host controller interface
+ */
+const struct flashchip flashchips_hwseq[] = {
+	{
+		.vendor		= "Winbond",
+		.name		= "W25R128FV",
+		.bustype	= BUS_PROG,
+		.manufacture_id	= WINBOND_NEX_ID,
+		.model_id	= WINBOND_NEX_W25R128FV,
+		.total_size	= 0,
+		.page_size	= 256,
+		/* probe is assumed to work, rest will be filled in by probe */
+		.tested		= TEST_OK_PROBE,
+		.probe		= probe_opaque,
+		/* eraseblock sizes will be set by the probing function */
+		.block_erasers	=
+		{
+			{
+				.block_erase = erase_opaque,
+			}
+		},
+		.write		= write_opaque,
+		.read		= read_opaque,
+		.wp		= &wp_w25r,
+	},
+
+	{NULL}
+};
+
 int flash_erase_value(struct flashchip *flash)
 {
 	return flash->feature_bits & FEATURE_ERASE_TO_ZERO ? 0 : 0xff;
diff --git a/flashchips.h b/flashchips.h
index a314f7b..24bb6e1 100644
--- a/flashchips.h
+++ b/flashchips.h
@@ -655,6 +655,7 @@
 #define WINBOND_NEX_W25Q128	0x4018
 #define WINBOND_NEX_W25Q32DW	0x6016
 #define WINBOND_NEX_W25Q64DW	0x6017
+#define WINBOND_NEX_W25R128FV	0x4018
 
 #define WINBOND_ID		0xDA	/* Winbond */
 #define WINBOND_W19B160BB	0x49
diff --git a/flashrom.c b/flashrom.c
index 0afe188..37c6f05 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -1029,14 +1029,27 @@
 
 int probe_flash(int startchip, struct flashchip *fill_flash, int force)
 {
-	const struct flashchip *flash;
+	const struct flashchip *flash, *flash_list;
 	unsigned long base = 0;
 	char location[64];
 	uint32_t size;
 	enum chipbustype buses_common;
 	char *tmp;
 
-	for (flash = flashchips + startchip; flash && flash->name; flash++) {
+	/* Based on the host controller interface that a platform
+	 * needs to use (hwseq or swseq),
+	 * set the flashchips list here.
+	 */
+	switch (ich_generation) {
+	case CHIPSET_100_SERIES_SUNRISE_POINT:
+		flash_list = flashchips_hwseq;
+		break;
+	default:
+		flash_list = flashchips;
+		break;
+	}
+
+	for (flash = flash_list + startchip; flash && flash->name; flash++) {
 		if (chip_to_probe && strcmp(flash->name, chip_to_probe) != 0)
 			continue;
 		buses_common = buses_supported & flash->bustype;
@@ -1115,7 +1128,7 @@
 			fill_flash->printlock(fill_flash);
 
 	/* Return position of matching chip. */
-	return flash - flashchips;
+	return flash - flash_list;
 }
 
 int verify_flash(struct flashchip *flash, uint8_t *buf, int verify_it)
diff --git a/ichspi.c b/ichspi.c
index 5d1ba54..994ebdb 100644
--- a/ichspi.c
+++ b/ichspi.c
@@ -171,7 +171,7 @@
 
 /*SUNRISE point*/
 /* 32 Bits Hardware Sequencing Flash Status */
-#define PCH100_REG_HSFSC		0x04
+#define PCH100_REG_HSFSC	0x04
 /*Status bits*/
 #define HSFSC_FDONE_OFF		0	/* 0: Flash Cycle Done */
 #define HSFSC_FDONE		(0x1 << HSFSC_FDONE_OFF)
@@ -188,7 +188,7 @@
 #define HSFSC_PRR34LCKDN_OFF	12
 #define HSFSC_PRR34LCKDN	(0x1 << HSFSC_PRR34LCKDN_OFF)
 /* 13: Flash Descriptor Override Pin-Strap Status */
-#define HSFSC_FDOPSS_OFF		13
+#define HSFSC_FDOPSS_OFF	13
 #define HSFSC_FDOPSS		(0x1 << HSFSC_FDOPSS_OFF)
 #define HSFSC_FDV_OFF		14	/* 14: Flash Descriptor Valid */
 #define HSFSC_FDV		(0x1 << HSFSC_FDV_OFF)
@@ -197,21 +197,17 @@
 /*Control bits*/
 #define HSFSC_FGO_OFF		0	/* 0: Flash Cycle Go */
 #define HSFSC_FGO		(0x1 << HSFSC_FGO_OFF)
-#define HSFSC_FCYCLE_OFF		1	/* 1-3: FLASH Cycle */
-#define HSFSC_FCYCLE		(0x3 << HSFSC_FCYCLE_OFF)
+#define HSFSC_FCYCLE_OFF	1	/* 1-3: FLASH Cycle */
+#define HSFSC_FCYCLE		(0xf << HSFSC_FCYCLE_OFF)
 #define HSFSC_FDBC_OFF		8	/*8-13 : Flash Data Byte Count */
 #define HSFSC_FDBC		(0x3f << HSFSC_FDBC_OFF)
 
-#define PCH100_REG_FADDR		0x08	/* 32 Bits */
-#define PCH100_REG_FDATA0		0x10	/* 64 Bytes */
+#define PCH100_REG_FADDR	0x08	/* 32 Bits */
+#define PCH100_REG_FDATA0	0x10	/* 64 Bytes */
 
 #define PCH100_REG_FPR0	0x84	/* 32 Bytes Protected Range 0 */
-#define PCH_WP_OFF		31	/* 31: write protection enable */
-#define PCH_RP_OFF		15	/* 15: read protection enable */
-
-#define PCH100_REG_PREOP_OPTYPE	0xA4	/* 32 Bits */
-#define PCH100_REG_OPMENU_LOWER	0xA8	/* 32 Bits */
-#define PCH100_REG_OPMENU_UPPER	0xAC	/* 32 Bits */
+#define PCH100_WP_OFF	31	/* 31: write protection enable */
+#define PCH100_RP_OFF	15	/* 15: read protection enable */
 
 /* The minimum erase block size in PCH which is 4k
 *	256,
@@ -225,7 +221,7 @@
 /* ICH SPI configuration lock-down. May be set during chipset enabling. */
 static int ichspi_lock = 0;
 
-static enum ich_chipset ich_generation = CHIPSET_ICH_UNKNOWN;
+enum ich_chipset ich_generation = CHIPSET_ICH_UNKNOWN;
 uint32_t ichspi_bbar = 0;
 
 static void *ich_spibar = NULL;
@@ -525,12 +521,6 @@
 		opmenu[0] = REGREAD32(ICH7_REG_OPMENU);
 		opmenu[1] = REGREAD32(ICH7_REG_OPMENU + 4);
 		break;
-	case CHIPSET_100_SERIES_SUNRISE_POINT:
-		preop = REGREAD16(PCH100_REG_PREOP_OPTYPE);
-		optype = REGREAD16(PCH100_REG_PREOP_OPTYPE + 2);
-		opmenu[0] = REGREAD32(PCH100_REG_OPMENU_LOWER);
-		opmenu[1] = REGREAD32(PCH100_REG_OPMENU_UPPER);
-		break;
 	case CHIPSET_ICH8:
 	default:		/* Future version might behave the same */
 		preop = REGREAD16(ICH9_REG_PREOP);
@@ -610,19 +600,6 @@
 		mmio_writel(opmenu[0], ich_spibar + ICH7_REG_OPMENU);
 		mmio_writel(opmenu[1], ich_spibar + ICH7_REG_OPMENU + 4);
 		break;
-	case CHIPSET_100_SERIES_SUNRISE_POINT:
-		/* Register undo only for enable_undo=1, i.e. first call. */
-		if (enable_undo) {
-			rmmio_valw(ich_spibar  + PCH100_REG_PREOP_OPTYPE);
-			rmmio_valw(ich_spibar  + PCH100_REG_PREOP_OPTYPE + 2);
-			rmmio_vall(ich_spibar  + PCH100_REG_OPMENU_LOWER);
-			rmmio_vall(ich_spibar  + PCH100_REG_OPMENU_UPPER);
-		}
-		mmio_writew(preop, ich_spibar + PCH100_REG_PREOP_OPTYPE);
-		mmio_writew(optype, ich_spibar + PCH100_REG_PREOP_OPTYPE + 2);
-		mmio_writel(opmenu[0], ich_spibar + PCH100_REG_OPMENU_LOWER);
-		mmio_writel(opmenu[1], ich_spibar + PCH100_REG_OPMENU_UPPER);
-		break;
 	case CHIPSET_ICH8:
 	default:		/* Future version might behave the same */
 		/* Register undo only for enable_undo=1, i.e. first call. */
@@ -1674,7 +1651,6 @@
 	uint16_t timeout = 100 * 60;
 	uint8_t block_len;
 
-
 	if ((addr + len) > (flash->total_size * 1024)) {
 		msg_perr("Request to read from an inaccessible memory address "
 			 "(addr=0x%x, len=%d).\n", addr, len);
@@ -1691,6 +1667,10 @@
 		pch_hwseq_set_addr(addr);
 		hsfc = REGREAD16(PCH100_REG_HSFSC + 2);
 		hsfc &= ~HSFSC_FCYCLE; /* set read operation */
+		if (!addr && len == 1) {
+			/* read status register */
+			hsfc |= (0x8 << HSFSC_FCYCLE_OFF);
+		}
 		hsfc &= ~HSFSC_FDBC; /* clear byte count */
 		/* set byte count */
 		hsfc |= (((block_len - 1) << HSFSC_FDBC_OFF) & HSFSC_FDBC);
@@ -1730,7 +1710,13 @@
 		ich_fill_data(buf, block_len, PCH100_REG_FDATA0);
 		hsfc = REGREAD16(PCH100_REG_HSFSC + 2);
 		hsfc &= ~HSFSC_FCYCLE; /* clear operation */
-		hsfc |= (0x2 << HSFSC_FCYCLE_OFF); /* set write operation */
+		if (!addr && len == 1) {
+			/* write status register */
+			hsfc |= (0x7 << HSFSC_FCYCLE_OFF);
+		} else {
+			/* set write operation */
+			hsfc |= (0x2 << HSFSC_FCYCLE_OFF);
+		}
 		hsfc &= ~HSFSC_FDBC; /* clear byte count */
 		/* set byte count */
 		hsfc |= (((block_len - 1) << HSFSC_FDBC_OFF) & HSFSC_FDBC);
@@ -2023,11 +2009,14 @@
 			ich_spi_mode = ich_hwseq;
 			msg_pspew("user selected hwseq\n");
 		} else if (arg && !strcmp(arg, "swseq")) {
-			ich_spi_mode = ich_swseq;
-			msg_pspew("user selected swseq\n");
+			/* Swseq not supported in SP */
+			msg_perr("swseq not supported\n");
+			free(arg);
+			return ERROR_FATAL;
 		} else if (arg && !strcmp(arg, "auto")) {
 			msg_pspew("user selected auto\n");
-			ich_spi_mode = ich_auto;
+			/* default mode in SP */
+			ich_spi_mode = ich_hwseq;
 		} else if (arg && !strlen(arg)) {
 			msg_perr("Missing argument for ich_spi_mode.\n");
 			free(arg);
@@ -2037,6 +2026,9 @@
 				 arg);
 			free(arg);
 			return ERROR_FATAL;
+		} else {
+			/* default mode in SP */
+			ich_spi_mode = ich_hwseq;
 		}
 		free(arg);
 		tmp = mmio_readl(ich_spibar + PCH100_REG_HSFSC);
@@ -2055,7 +2047,6 @@
 			 "by the FRAP and FREG registers are NOT in "
 			 "effect. Please note that Protected\n"
 			 "Range (PR) restrictions still apply.\n");
-		ich_init_opcodes();
 
 		if (desc_valid) {
 			num_fd_regions = DEFAULT_NUM_FD_REGIONS;
@@ -2085,37 +2076,15 @@
 					ich_generation) == ICH_RET_OK)
 				prettyprint_ich_descriptors(CHIPSET_ICH_UNKNOWN,
 							    &desc);
-			/* If the descriptor is valid and indicates multiple
-			 * flash devices we need to use hwseq to be able to
-			 * access the second flash device.
-			 */
-			if (ich_spi_mode == ich_auto && desc.content.NC != 0) {
-				msg_pinfo("Enabling hardware sequencing due to "
-					  "multiple flash chips detected.\n");
-				ich_spi_mode = ich_hwseq;
-			}
-		}
-
-		if (ich_spi_mode == ich_auto && ichspi_lock &&
-		    ich_missing_opcodes()) {
-			msg_pinfo("Enabling hardware sequencing because "
-				  "some important opcode is locked.\n");
-			ich_spi_mode = ich_hwseq;
-		}
-
-		if (ich_spi_mode == ich_hwseq) {
-			if (!desc_valid) {
-				msg_perr("Hardware sequencing was requested "
-					 "but the flash descriptor is not "
-					 "valid. Aborting.\n");
-				return ERROR_FATAL;
-			}
-			hwseq_data.size_comp0 = getFCBA_component_density(&desc, 0);
-			hwseq_data.size_comp1 = getFCBA_component_density(&desc, 1);
-			register_opaque_programmer(&opaque_programmer_pch_hwseq);
 		} else {
-			register_spi_programmer(&spi_programmer_ich9);
+			msg_perr("Hardware sequencing was requested "
+				 "but the flash descriptor is not "
+				 "valid. Aborting.\n");
+			return ERROR_FATAL;
 		}
+		hwseq_data.size_comp0 = getFCBA_component_density(&desc, 0);
+		hwseq_data.size_comp1 = getFCBA_component_density(&desc, 1);
+		register_opaque_programmer(&opaque_programmer_pch_hwseq);
 		break;
 	case CHIPSET_ICH8:
 	default:		/* Future version might behave the same */
diff --git a/print.c b/print.c
index 2b4d342..1793a7c 100644
--- a/print.c
+++ b/print.c
@@ -58,7 +58,7 @@
 	return ret;
 }
 
-static void print_supported_chips(void)
+static void print_supported_chips(int host_controller)
 {
 	const char *delim = "/";
 	const int mintoklen = 5;
@@ -67,13 +67,21 @@
 	int maxvendorlen = strlen("Vendor") + 1;
 	int maxchiplen = strlen("Device") + 1;
 	int maxtypelen = strlen("Type") + 1;
-	const struct flashchip *f;
+	const struct flashchip *f, *flash;
 	char *s;
 	char *tmpven, *tmpdev;
 	int tmpvenlen, tmpdevlen, curvenlen, curdevlen;
 
+	if (!host_controller) {
+		flash = flashchips;
+		msg_ginfo("\nList of chips that use "
+			"SPI host controller interface:\n");
+	} else {
+		flash = flashchips_hwseq;
+		msg_ginfo("\nList of chips that use Opaque interface:\n");
+	}
 	/* calculate maximum column widths and by iterating over all chips */
-	for (f = flashchips; f->name != NULL; f++) {
+	for (f = flash; f->name != NULL; f++) {
 		/* Ignore "unknown XXXX SPI chip" entries. */
 		if (!strncmp(f->name, "unknown", 7))
 			continue;
@@ -160,7 +168,7 @@
 	msg_ginfo("\n\n");
 	msg_ginfo("(P = PROBE, R = READ, E = ERASE, W = WRITE)\n\n");
 
-	for (f = flashchips; f->name != NULL; f++) {
+	for (f = flash; f->name != NULL; f++) {
 		/* Don't print "unknown XXXX SPI chip" entries. */
 		if (!strncmp(f->name, "unknown", 7))
 			continue;
@@ -417,7 +425,10 @@
 
 void print_supported(void)
 {
-	print_supported_chips();
+	/* Print the list of chips that use swseq */
+	print_supported_chips(0);
+	/* Print the list of chips that use hwseq */
+	print_supported_chips(1);
 
 	msg_ginfo("\nSupported programmers:\n");
 	list_programmers_linebreak(0, 80, 0);
diff --git a/programmer.h b/programmer.h
index aa0192e..9f3b06b 100644
--- a/programmer.h
+++ b/programmer.h
@@ -265,6 +265,7 @@
 /* chipset_enable.c */
 int chipset_flash_enable(void);
 int get_target_bus_from_chipset(enum chipbustype *target_bus);
+enum ich_chipset ich_generation;
 
 /* processor_enable.c */
 int processor_flash_enable(void);
diff --git a/writeprotect.c b/writeprotect.c
index 1d20bed..1b7fd72 100644
--- a/writeprotect.c
+++ b/writeprotect.c
@@ -527,6 +527,37 @@
 	{ 1, 1, 0x5, {0x000000, 32 * 1024} },
 };
 
+struct w25q_range w25r128_ranges[] = {
+	{ X, X, 0, {0, 0} },	/* none */
+
+	{ 0, 0, 0x1, {0xfc0000, 256 * 1024} },
+	{ 0, 0, 0x2, {0xf80000, 512 * 1024} },
+	{ 0, 0, 0x3, {0xf00000, 1024 * 1024} },
+	{ 0, 0, 0x4, {0xe00000, 2048 * 1024} },
+	{ 0, 0, 0x5, {0xc00000, 4096 * 1024} },
+	{ 0, 0, 0x6, {0x800000, 8192 * 1024} },
+
+	{ 0, 1, 0x1, {0x000000, 256 * 1024} },
+	{ 0, 1, 0x2, {0x000000, 512 * 1024} },
+	{ 0, 1, 0x3, {0x000000, 1024 * 1024} },
+	{ 0, 1, 0x4, {0x000000, 2048 * 1024} },
+	{ 0, 1, 0x5, {0x000000, 4096 * 1024} },
+	{ 0, 1, 0x6, {0x000000, 8192 * 1024} },
+	{ X, X, 0x7, {0x000000, 16384 * 1024} },
+
+	{ 1, 0, 0x1, {0xfff000, 4 * 1024} },
+	{ 1, 0, 0x2, {0xffe000, 8 * 1024} },
+	{ 1, 0, 0x3, {0xffc000, 16 * 1024} },
+	{ 1, 0, 0x4, {0xff8000, 32 * 1024} },
+	{ 1, 0, 0x5, {0xff8000, 32 * 1024} },
+
+	{ 1, 1, 0x1, {0x000000, 4 * 1024} },
+	{ 1, 1, 0x2, {0x000000, 8 * 1024} },
+	{ 1, 1, 0x3, {0x000000, 16 * 1024} },
+	{ 1, 1, 0x4, {0x000000, 32 * 1024} },
+	{ 1, 1, 0x5, {0x000000, 32 * 1024} },
+};
+
 struct w25q_range w25x10_ranges[] = {
 	{ X, X, 0, {0, 0} },    /* none */
 	{ 0, 0, 0x1, {0x010000, 64 * 1024} },
@@ -658,6 +689,10 @@
 			*w25q_ranges = w25q64_ranges;
 			*num_entries = ARRAY_SIZE(w25q64_ranges);
 			break;
+		case WINBOND_NEX_W25R128FV:
+			*w25q_ranges = w25r128_ranges;
+			*num_entries = ARRAY_SIZE(w25r128_ranges);
+			break;
 		default:
 			msg_cerr("%s() %d: WINBOND flash chip mismatch (0x%04x)"
 			         ", aborting\n", __func__, __LINE__,
@@ -911,6 +946,93 @@
 		return 1;
 	}
 }
+static int w25r_set_range(const struct flashchip *flash,
+		unsigned int start, unsigned int len)
+{
+	struct w25q_status status;
+	struct flashchip chip;
+	uint8_t arr, expected;
+	int ret;
+
+	memset(&status, 0, sizeof(status));
+	memset(&chip, 0, sizeof(chip));
+	memcpy(&chip, flash, sizeof(chip));
+
+	/* passing a copy of flash since it is read only */
+	ret = flash->read(&chip, &arr, 0, 1);
+	if (ret) {
+		msg_cerr("Read status register failed.\n");
+		return ret;
+	}
+	memcpy(&status, &arr, 1);
+	msg_cdbg("%s: old status: 0x%02x\n", __func__, arr);
+
+	if (w25_range_to_status(flash, start, len, &status))
+		return -1;
+
+	msg_cdbg("status.busy: %x\n", status.busy);
+	msg_cdbg("status.wel: %x\n", status.wel);
+	msg_cdbg("status.bp0: %x\n", status.bp0);
+	msg_cdbg("status.bp1: %x\n", status.bp1);
+	msg_cdbg("status.bp2: %x\n", status.bp2);
+	msg_cdbg("status.tb: %x\n", status.tb);
+	msg_cdbg("status.sec: %x\n", status.sec);
+	msg_cdbg("status.srp0: %x\n", status.srp0);
+
+	memcpy(&expected, &status, sizeof(status));
+	ret = flash->write(&chip, &expected, 0, 1);
+	if (ret) {
+		msg_cerr("Write status register failed.\n");
+		return ret;
+	}
+	ret = flash->read(&chip, &arr, 0, 1);
+	if (ret) {
+		msg_cerr("Read status register failed.\n");
+		return ret;
+	}
+	msg_cdbg("%s: new status: 0x%02x\n", __func__, arr);
+
+	if ((arr & MASK_WP_AREA) == (expected & MASK_WP_AREA)) {
+		return 0;
+	} else {
+		msg_cerr("expected=0x%02x, but actual=0x%02x.\n",
+			expected, arr);
+		return 1;
+	}
+}
+
+static int w25r_wp_status(const struct flashchip *flash)
+{
+	struct w25q_status sr;
+	struct flashchip chip;
+	uint8_t tmp;
+	unsigned int start, len;
+	int ret = 0;
+
+	memset(&sr, 0, sizeof(sr));
+	memset(&chip, 0, sizeof(chip));
+	memcpy(&chip, flash, sizeof(chip));
+
+	ret = flash->read(&chip, &tmp, 0, 1);
+	if (ret) {
+		msg_cerr("Read status register failed.\n");
+		return ret;
+	}
+	memcpy(&sr, &tmp, 1);
+	msg_cinfo("WP: status: 0x%02x\n", tmp);
+	msg_cinfo("WP: status.srp0: %x\n", sr.srp0);
+	msg_cinfo("WP: write protect is %s.\n",
+		(sr.srp0) ? "enabled" : "disabled");
+	msg_cinfo("WP: write protect range: ");
+	if (w25_status_to_range(flash, &sr, &start, &len)) {
+		msg_cinfo("(cannot resolve the range)\n");
+		ret = -1;
+	} else {
+		msg_cinfo("start=0x%08x, len=0x%08x\n", start, len);
+	}
+	return ret;
+}
+
 
 /* Print out the current status register value with human-readable text. */
 static int w25_wp_status(const struct flashchip *flash)
@@ -964,6 +1086,44 @@
 	return 0;
 }
 
+static int w25_set_srp(const struct flashchip *flash, int enable)
+{
+	struct w25q_status status;
+	struct flashchip chip;
+	int tmp = 0;
+	uint8_t arr, expected;
+
+	memset(&status, 0, sizeof(status));
+	memset(&chip, 0, sizeof(chip));
+	memcpy(&chip, flash, sizeof(chip));
+
+	tmp = flash->read(&chip, &arr, 0, 1);
+	if (tmp) {
+		msg_cerr("Read status register failed.\n");
+		return tmp;
+	}
+	memcpy(&status, &arr, 1);
+	msg_cdbg("%s: old status: 0x%02x\n", __func__, tmp);
+
+	status.srp0 = enable ? 1 : 0;
+	memcpy(&expected, &status, sizeof(status));
+	tmp = flash->write(&chip, &expected, 0, 1);
+	if (tmp) {
+		msg_cerr("Write status register failed.\n");
+		return tmp;
+	}
+	tmp = flash->read(&chip, &arr, 0, 1);
+	if (tmp) {
+		msg_cerr("Read status register failed.\n");
+		return tmp;
+	}
+	msg_cdbg("%s: new status: 0x%02x\n", __func__, arr);
+	if ((arr & MASK_WP_AREA) != (expected & MASK_WP_AREA))
+		return 1;
+
+	return 0;
+}
+
 static int w25_enable_writeprotect(const struct flashchip *flash,
 		enum wp_mode wp_mode)
 {
@@ -1147,6 +1307,17 @@
 	return wp_mode;
 }
 
+static int w25r_disable_writeprotect(const struct flashchip *flash)
+{
+	int ret;
+
+	ret = w25_set_srp(flash, 0);
+	if (ret)
+		msg_cerr("%s(): error=%d.\n", __func__, ret);
+
+	return ret;
+}
+
 static int w25q_disable_writeprotect(const struct flashchip *flash,
 		enum wp_mode wp_mode)
 {
@@ -1187,6 +1358,26 @@
 	return w25q_disable_writeprotect(flash, WP_MODE_HARDWARE);
 }
 
+static int w25r_enable_writeprotect(const struct flashchip *flash,
+		enum wp_mode wp_mode)
+{
+	int ret;
+
+	switch (wp_mode) {
+	case WP_MODE_HARDWARE:
+		ret = w25_set_srp(flash, 1);
+		break;
+	default:
+		msg_perr("%s(): invalid mode for Sunrise Point %d\n",
+			__func__, wp_mode);
+		break;
+	}
+	if (ret)
+		msg_cerr("%s(): error=%d.\n", __func__, ret);
+
+	return ret;
+}
+
 static int w25q_enable_writeprotect(const struct flashchip *flash,
 		enum wp_mode wp_mode)
 {
@@ -1299,6 +1490,15 @@
 	.wp_status	= w25q_wp_status,
 };
 
+/* W25R Series */
+struct wp wp_w25r = {
+	.list_ranges	= w25_list_ranges,
+	.set_range	= w25r_set_range,
+	.enable		= w25r_enable_writeprotect,
+	.disable	= w25r_disable_writeprotect,
+	.wp_status	= w25r_wp_status,
+};
+
 struct generic_range gd25q32_cmp0_ranges[] = {
 	/* none, bp4 and bp3 => don't care */
 	{ { }, 0x00, {0, 0} },
diff --git a/writeprotect.h b/writeprotect.h
index c3b8593..6429ed7 100644
--- a/writeprotect.h
+++ b/writeprotect.h
@@ -42,6 +42,7 @@
 /* winbond w25-series */
 extern struct wp wp_w25;	/* older winbond chips (w25p, w25x, etc) */
 extern struct wp wp_w25q;
+extern struct wp wp_w25r;
 
 extern struct wp wp_generic;
 extern struct wp wp_wpce775x;