drivers/spi: Winbond specific write-protection enable

Extend the SPI interface to enable write-protection.

Tested on Cavium EVB CN81xx using W25Q128.

Change-Id: Ie3765b013855538eca37bc7800d3f9d5d09b8402
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/25105
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
This commit is contained in:
Patrick Rudolph 2018-03-12 11:34:53 +01:00 committed by Philipp Deppenwiese
parent 61322d7ad2
commit e63a5f1e7f
3 changed files with 356 additions and 1 deletions

View File

@ -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)

View File

@ -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,

View File

@ -26,6 +26,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.
@ -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);
};
@ -119,6 +154,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,