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:
parent
61322d7ad2
commit
e63a5f1e7f
|
@ -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)
|
||||
|
|
|
@ -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, ®8, sizeof(reg8));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cmdbuf.sreg = reg8;
|
||||
|
||||
ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, 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, ®8, sizeof(reg8));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cmdbuf.sreg = reg8;
|
||||
|
||||
ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®8, 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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue