This patch appends a unit (milliseconds) to time-out macro names for better understanding the code which is using the macros. Change-Id: Ibc4beda2660a83fd5f0ed325b2ee3148c6d96639 Signed-off-by: Uwe Poeche <uwe.poeche@siemens.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/34384 Reviewed-by: Werner Zeh <werner.zeh@siemens.com> Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net> Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
710 lines
16 KiB
C
710 lines
16 KiB
C
/*
|
|
* Copyright 2008, Network Appliance Inc.
|
|
* Jason McMullan <mcmullan@netapp.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <console/console.h>
|
|
#include <stdlib.h>
|
|
#include <spi_flash.h>
|
|
#include <spi-generic.h>
|
|
#include <string.h>
|
|
#include <delay.h>
|
|
#include <lib.h>
|
|
|
|
#include "spi_flash_internal.h"
|
|
#include "spi_winbond.h"
|
|
|
|
struct winbond_spi_flash_params {
|
|
uint16_t id;
|
|
uint8_t dual_spi : 1;
|
|
uint8_t _reserved_for_flags : 3;
|
|
uint8_t l2_page_size_shift : 4;
|
|
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;
|
|
};
|
|
};
|
|
|
|
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 = 0x2014,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 4,
|
|
.name = "W25P80",
|
|
},
|
|
{
|
|
.id = 0x2015,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 5,
|
|
.name = "W25P16",
|
|
},
|
|
{
|
|
.id = 0x2016,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 6,
|
|
.name = "W25P32",
|
|
},
|
|
{
|
|
.id = 0x3014,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 4,
|
|
.name = "W25X80",
|
|
.dual_spi = 1,
|
|
},
|
|
{
|
|
.id = 0x3015,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 5,
|
|
.name = "W25X16",
|
|
.dual_spi = 1,
|
|
},
|
|
{
|
|
.id = 0x3016,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 6,
|
|
.name = "W25X32",
|
|
.dual_spi = 1,
|
|
},
|
|
{
|
|
.id = 0x3017,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 7,
|
|
.name = "W25X64",
|
|
.dual_spi = 1,
|
|
},
|
|
{
|
|
.id = 0x4014,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 4,
|
|
.name = "W25Q80_V",
|
|
.dual_spi = 1,
|
|
},
|
|
{
|
|
.id = 0x4015,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 5,
|
|
.name = "W25Q16_V",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x6015,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 5,
|
|
.name = "W25Q16DW",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x4016,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 6,
|
|
.name = "W25Q32_V",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x6016,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 6,
|
|
.name = "W25Q32DW",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x4017,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 7,
|
|
.name = "W25Q64_V",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 17,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x6017,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 7,
|
|
.name = "W25Q64DW",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 17,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x4018,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 8,
|
|
.name = "W25Q128_V",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 18,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x6018,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 8,
|
|
.name = "W25Q128FW",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 18,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x7018,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 8,
|
|
.name = "W25Q128J",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 18,
|
|
.bp_bits = 3,
|
|
},
|
|
{
|
|
.id = 0x4019,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 9,
|
|
.name = "W25Q256_V",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 4,
|
|
},
|
|
{
|
|
.id = 0x7019,
|
|
.l2_page_size_shift = 8,
|
|
.pages_per_sector_shift = 4,
|
|
.sectors_per_block_shift = 4,
|
|
.nr_blocks_shift = 9,
|
|
.name = "W25Q256J",
|
|
.dual_spi = 1,
|
|
.protection_granularity_shift = 16,
|
|
.bp_bits = 4,
|
|
},
|
|
};
|
|
|
|
static int winbond_write(const struct spi_flash *flash, u32 offset, size_t len,
|
|
const void *buf)
|
|
{
|
|
unsigned long byte_addr;
|
|
unsigned long page_size;
|
|
size_t chunk_len;
|
|
size_t actual;
|
|
int ret = 0;
|
|
u8 cmd[4];
|
|
|
|
page_size = flash->page_size;
|
|
|
|
for (actual = 0; actual < len; actual += chunk_len) {
|
|
byte_addr = offset % page_size;
|
|
chunk_len = min(len - actual, page_size - byte_addr);
|
|
chunk_len = spi_crop_chunk(&flash->spi, sizeof(cmd), chunk_len);
|
|
|
|
cmd[0] = CMD_W25_PP;
|
|
cmd[1] = (offset >> 16) & 0xff;
|
|
cmd[2] = (offset >> 8) & 0xff;
|
|
cmd[3] = offset & 0xff;
|
|
#if CONFIG(DEBUG_SPI_FLASH)
|
|
printk(BIOS_SPEW, "PP: 0x%p => cmd = { 0x%02x 0x%02x%02x%02x }"
|
|
" chunk_len = %zu\n", buf + actual,
|
|
cmd[0], cmd[1], cmd[2], cmd[3], chunk_len);
|
|
#endif
|
|
|
|
ret = spi_flash_cmd(&flash->spi, CMD_W25_WREN, NULL, 0);
|
|
if (ret < 0) {
|
|
printk(BIOS_WARNING, "SF: Enabling Write failed\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = spi_flash_cmd_write(&flash->spi, cmd, sizeof(cmd),
|
|
buf + actual, chunk_len);
|
|
if (ret < 0) {
|
|
printk(BIOS_WARNING, "SF: Winbond Page Program failed\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = spi_flash_cmd_wait_ready(flash,
|
|
SPI_FLASH_PROG_TIMEOUT_MS);
|
|
if (ret)
|
|
goto out;
|
|
|
|
offset += chunk_len;
|
|
}
|
|
|
|
#if CONFIG(DEBUG_SPI_FLASH)
|
|
printk(BIOS_SPEW, "SF: Winbond: Successfully programmed %zu bytes @"
|
|
" 0x%lx\n", len, (unsigned long)(offset - len));
|
|
#endif
|
|
ret = 0;
|
|
|
|
out:
|
|
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 ? 0 : flash_size - protected_size;
|
|
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 = { .u = 0 };
|
|
|
|
ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp3.u,
|
|
sizeof(reg1_bp3.u));
|
|
|
|
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 = { .u = 0 };
|
|
|
|
ret = spi_flash_cmd(&flash->spi, flash->status_cmd, ®1_bp4.u,
|
|
sizeof(reg1_bp4.u));
|
|
|
|
bp = reg1_bp4.bp;
|
|
tb = reg1_bp4.tb;
|
|
} else {
|
|
// FIXME: not supported
|
|
return -1;
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = spi_flash_cmd(&flash->spi, CMD_W25_RDSR2, ®2.u,
|
|
sizeof(reg2.u));
|
|
if (ret)
|
|
return ret;
|
|
|
|
winbond_bpbits_to_region(granularity, bp, tb, reg2.cmp, flash->size,
|
|
&wp_region);
|
|
|
|
if (!region_sz(&wp_region)) {
|
|
printk(BIOS_DEBUG, "WINBOND: flash isn't protected\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
printk(BIOS_DEBUG, "WINBOND: flash protected range 0x%08zx-0x%08zx\n",
|
|
region_offset(&wp_region),
|
|
region_offset(&wp_region) + region_sz(&wp_region));
|
|
|
|
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 = 1;
|
|
else
|
|
tb = 0;
|
|
|
|
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,
|
|
.erase = spi_flash_cmd_erase,
|
|
.status = spi_flash_cmd_status,
|
|
.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,
|
|
struct spi_flash *flash)
|
|
{
|
|
const struct winbond_spi_flash_params *params;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(winbond_spi_flash_table); i++) {
|
|
params = &winbond_spi_flash_table[i];
|
|
if (params->id == ((idcode[1] << 8) | idcode[2]))
|
|
break;
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(winbond_spi_flash_table)) {
|
|
printk(BIOS_WARNING, "SF: Unsupported Winbond ID %02x%02x\n",
|
|
idcode[1], idcode[2]);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(&flash->spi, spi, sizeof(*spi));
|
|
flash->name = params->name;
|
|
|
|
/* Params are in power-of-two. */
|
|
flash->page_size = 1 << params->l2_page_size_shift;
|
|
flash->sector_size = flash->page_size *
|
|
(1 << params->pages_per_sector_shift);
|
|
flash->size = flash->sector_size *
|
|
(1 << params->sectors_per_block_shift) *
|
|
(1 << params->nr_blocks_shift);
|
|
flash->erase_cmd = CMD_W25_SE;
|
|
flash->status_cmd = CMD_W25_RDSR;
|
|
|
|
flash->flags.dual_spi = params->dual_spi;
|
|
|
|
flash->ops = &spi_flash_ops;
|
|
flash->driver_private = params;
|
|
|
|
return 0;
|
|
}
|