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;