|
|
|
/*
|
|
|
|
* SPI flash operations
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Atmel Corporation
|
|
|
|
* Copyright (C) 2010 Reinhard Meyer, EMK Elektronik
|
|
|
|
* Copyright (C) 2013 Jagannadha Sutradharudu Teki, Xilinx Inc.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <malloc.h>
|
|
|
|
#include <spi.h>
|
|
|
|
#include <spi_flash.h>
|
|
|
|
#include <watchdog.h>
|
|
|
|
#include <linux/compiler.h>
|
|
|
|
#include <linux/log2.h>
|
|
|
|
|
|
|
|
#include "sf_internal.h"
|
|
|
|
|
|
|
|
static void spi_flash_addr(u32 addr, u8 *cmd)
|
|
|
|
{
|
|
|
|
/* cmd[0] is actual command */
|
|
|
|
cmd[1] = addr >> 16;
|
|
|
|
cmd[2] = addr >> 8;
|
|
|
|
cmd[3] = addr >> 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_read_status(struct spi_flash *flash, u8 *rs)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u8 cmd;
|
|
|
|
|
|
|
|
cmd = CMD_READ_STATUS;
|
|
|
|
ret = spi_flash_read_common(flash, &cmd, 1, rs, 1);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: fail to read status register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_fsr(struct spi_flash *flash, u8 *fsr)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
const u8 cmd = CMD_FLAG_STATUS;
|
|
|
|
|
|
|
|
ret = spi_flash_read_common(flash, &cmd, 1, fsr, 1);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: fail to read flag status register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_write_status(struct spi_flash *flash, u8 ws)
|
|
|
|
{
|
|
|
|
u8 cmd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cmd = CMD_WRITE_STATUS;
|
|
|
|
ret = spi_flash_write_common(flash, &cmd, 1, &ws, 1);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: fail to write status register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND)
|
|
|
|
int spi_flash_cmd_read_config(struct spi_flash *flash, u8 *rc)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u8 cmd;
|
|
|
|
|
|
|
|
cmd = CMD_READ_CONFIG;
|
|
|
|
ret = spi_flash_read_common(flash, &cmd, 1, rc, 1);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: fail to read config register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_write_config(struct spi_flash *flash, u8 wc)
|
|
|
|
{
|
|
|
|
u8 data[2];
|
|
|
|
u8 cmd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_read_status(flash, &data[0]);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
cmd = CMD_WRITE_STATUS;
|
|
|
|
data[1] = wc;
|
|
|
|
ret = spi_flash_write_common(flash, &cmd, 1, &data, 2);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: fail to write config register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPI_FLASH_BAR
|
|
|
|
static int spi_flash_write_bank(struct spi_flash *flash, u32 offset)
|
|
|
|
{
|
|
|
|
u8 cmd, bank_sel;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
bank_sel = offset / (SPI_FLASH_16MB_BOUN << flash->shift);
|
|
|
|
if (bank_sel == flash->bank_curr)
|
|
|
|
goto bar_end;
|
|
|
|
|
|
|
|
cmd = flash->bank_write_cmd;
|
|
|
|
ret = spi_flash_write_common(flash, &cmd, 1, &bank_sel, 1);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: fail to write bank register\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bar_end:
|
|
|
|
flash->bank_curr = bank_sel;
|
|
|
|
return flash->bank_curr;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SF_DUAL_FLASH
|
|
|
|
static void spi_flash_dual_flash(struct spi_flash *flash, u32 *addr)
|
|
|
|
{
|
|
|
|
switch (flash->dual_flash) {
|
|
|
|
case SF_DUAL_STACKED_FLASH:
|
|
|
|
if (*addr >= (flash->size >> 1)) {
|
|
|
|
*addr -= flash->size >> 1;
|
|
|
|
flash->spi->flags |= SPI_XFER_U_PAGE;
|
|
|
|
} else {
|
|
|
|
flash->spi->flags &= ~SPI_XFER_U_PAGE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SF_DUAL_PARALLEL_FLASH:
|
|
|
|
*addr >>= flash->shift;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
debug("SF: Unsupported dual_flash=%d\n", flash->dual_flash);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int spi_flash_sr_ready(struct spi_flash *flash)
|
|
|
|
{
|
|
|
|
u8 sr;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_read_status(flash, &sr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return !(sr & STATUS_WIP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int spi_flash_fsr_ready(struct spi_flash *flash)
|
|
|
|
{
|
|
|
|
u8 fsr;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = read_fsr(flash, &fsr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return fsr & STATUS_PEC;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int spi_flash_ready(struct spi_flash *flash)
|
|
|
|
{
|
|
|
|
int sr, fsr;
|
|
|
|
|
|
|
|
sr = spi_flash_sr_ready(flash);
|
|
|
|
if (sr < 0)
|
|
|
|
return sr;
|
|
|
|
|
|
|
|
fsr = 1;
|
|
|
|
if (flash->flags & SNOR_F_USE_FSR) {
|
|
|
|
fsr = spi_flash_fsr_ready(flash);
|
|
|
|
if (fsr < 0)
|
|
|
|
return fsr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sr && fsr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_wait_ready(struct spi_flash *flash, unsigned long timeout)
|
|
|
|
{
|
|
|
|
int timebase, ret;
|
|
|
|
|
|
|
|
timebase = get_timer(0);
|
|
|
|
|
|
|
|
while (get_timer(timebase) < timeout) {
|
|
|
|
ret = spi_flash_ready(flash);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("SF: Timeout!\n");
|
|
|
|
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_write_common(struct spi_flash *flash, const u8 *cmd,
|
|
|
|
size_t cmd_len, const void *buf, size_t buf_len)
|
|
|
|
{
|
|
|
|
struct spi_slave *spi = flash->spi;
|
|
|
|
unsigned long timeout = SPI_FLASH_PROG_TIMEOUT;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (buf == NULL)
|
|
|
|
timeout = SPI_FLASH_PAGE_ERASE_TIMEOUT;
|
|
|
|
|
|
|
|
ret = spi_claim_bus(flash->spi);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: unable to claim SPI bus\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write_enable(flash);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: enabling write failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write(spi, cmd, cmd_len, buf, buf_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: write cmd failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_wait_ready(flash, timeout);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: write %s timed out\n",
|
|
|
|
timeout == SPI_FLASH_PROG_TIMEOUT ?
|
|
|
|
"program" : "page erase");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
spi_release_bus(spi);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len)
|
|
|
|
{
|
|
|
|
u32 erase_size, erase_addr;
|
|
|
|
u8 cmd[SPI_FLASH_CMD_LEN];
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
erase_size = flash->erase_size;
|
|
|
|
if (offset % erase_size || len % erase_size) {
|
|
|
|
debug("SF: Erase offset/length not multiple of erase size\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flash->flash_is_locked) {
|
|
|
|
if (flash->flash_is_locked(flash, offset, len) > 0) {
|
|
|
|
printf("offset 0x%x is protected and cannot be erased\n",
|
|
|
|
offset);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd[0] = flash->erase_cmd;
|
|
|
|
while (len) {
|
|
|
|
erase_addr = offset;
|
|
|
|
|
|
|
|
#ifdef CONFIG_SF_DUAL_FLASH
|
|
|
|
if (flash->dual_flash > SF_SINGLE_FLASH)
|
|
|
|
spi_flash_dual_flash(flash, &erase_addr);
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPI_FLASH_BAR
|
|
|
|
ret = spi_flash_write_bank(flash, erase_addr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
#endif
|
|
|
|
spi_flash_addr(erase_addr, cmd);
|
|
|
|
|
|
|
|
debug("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1],
|
|
|
|
cmd[2], cmd[3], erase_addr);
|
|
|
|
|
|
|
|
ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: erase failed\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset += erase_size;
|
|
|
|
len -= erase_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_write_ops(struct spi_flash *flash, u32 offset,
|
|
|
|
size_t len, const void *buf)
|
|
|
|
{
|
|
|
|
unsigned long byte_addr, page_size;
|
|
|
|
u32 write_addr;
|
|
|
|
size_t chunk_len, actual;
|
|
|
|
u8 cmd[SPI_FLASH_CMD_LEN];
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
page_size = flash->page_size;
|
|
|
|
|
|
|
|
if (flash->flash_is_locked) {
|
|
|
|
if (flash->flash_is_locked(flash, offset, len) > 0) {
|
|
|
|
printf("offset 0x%x is protected and cannot be written\n",
|
|
|
|
offset);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd[0] = flash->write_cmd;
|
|
|
|
for (actual = 0; actual < len; actual += chunk_len) {
|
|
|
|
write_addr = offset;
|
|
|
|
|
|
|
|
#ifdef CONFIG_SF_DUAL_FLASH
|
|
|
|
if (flash->dual_flash > SF_SINGLE_FLASH)
|
|
|
|
spi_flash_dual_flash(flash, &write_addr);
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPI_FLASH_BAR
|
|
|
|
ret = spi_flash_write_bank(flash, write_addr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
#endif
|
|
|
|
byte_addr = offset % page_size;
|
linux/kernel.h: sync min, max, min3, max3 macros with Linux
U-Boot has never cared about the type when we get max/min of two
values, but Linux Kernel does. This commit gets min, max, min3, max3
macros synced with the kernel introducing type checks.
Many of references of those macros must be fixed to suppress warnings.
We have two options:
- Use min, max, min3, max3 only when the arguments have the same type
(or add casts to the arguments)
- Use min_t/max_t instead with the appropriate type for the first
argument
Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
Acked-by: Pavel Machek <pavel@denx.de>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Tested-by: Lukasz Majewski <l.majewski@samsung.com>
[trini: Fixup arch/blackfin/lib/string.c]
Signed-off-by: Tom Rini <trini@ti.com>
10 years ago
|
|
|
chunk_len = min(len - actual, (size_t)(page_size - byte_addr));
|
|
|
|
|
|
|
|
if (flash->spi->max_write_size)
|
linux/kernel.h: sync min, max, min3, max3 macros with Linux
U-Boot has never cared about the type when we get max/min of two
values, but Linux Kernel does. This commit gets min, max, min3, max3
macros synced with the kernel introducing type checks.
Many of references of those macros must be fixed to suppress warnings.
We have two options:
- Use min, max, min3, max3 only when the arguments have the same type
(or add casts to the arguments)
- Use min_t/max_t instead with the appropriate type for the first
argument
Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
Acked-by: Pavel Machek <pavel@denx.de>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Tested-by: Lukasz Majewski <l.majewski@samsung.com>
[trini: Fixup arch/blackfin/lib/string.c]
Signed-off-by: Tom Rini <trini@ti.com>
10 years ago
|
|
|
chunk_len = min(chunk_len,
|
|
|
|
(size_t)flash->spi->max_write_size);
|
|
|
|
|
|
|
|
spi_flash_addr(write_addr, cmd);
|
|
|
|
|
|
|
|
debug("SF: 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);
|
|
|
|
|
|
|
|
ret = spi_flash_write_common(flash, cmd, sizeof(cmd),
|
|
|
|
buf + actual, chunk_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: write failed\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset += chunk_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_read_common(struct spi_flash *flash, const u8 *cmd,
|
|
|
|
size_t cmd_len, void *data, size_t data_len)
|
|
|
|
{
|
|
|
|
struct spi_slave *spi = flash->spi;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = spi_claim_bus(flash->spi);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: unable to claim SPI bus\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_read(spi, cmd, cmd_len, data, data_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: read cmd failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
spi_release_bus(spi);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void __weak spi_flash_copy_mmap(void *data, void *offset, size_t len)
|
|
|
|
{
|
|
|
|
memcpy(data, offset, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
int spi_flash_cmd_read_ops(struct spi_flash *flash, u32 offset,
|
|
|
|
size_t len, void *data)
|
|
|
|
{
|
|
|
|
u8 *cmd, cmdsz;
|
|
|
|
u32 remain_len, read_len, read_addr;
|
|
|
|
int bank_sel = 0;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
/* Handle memory-mapped SPI */
|
|
|
|
if (flash->memory_map) {
|
|
|
|
ret = spi_claim_bus(flash->spi);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: unable to claim SPI bus\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
spi_xfer(flash->spi, 0, NULL, NULL, SPI_XFER_MMAP);
|
|
|
|
spi_flash_copy_mmap(data, flash->memory_map + offset, len);
|
|
|
|
spi_xfer(flash->spi, 0, NULL, NULL, SPI_XFER_MMAP_END);
|
|
|
|
spi_release_bus(flash->spi);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmdsz = SPI_FLASH_CMD_LEN + flash->dummy_byte;
|
|
|
|
cmd = calloc(1, cmdsz);
|
|
|
|
if (!cmd) {
|
|
|
|
debug("SF: Failed to allocate cmd\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd[0] = flash->read_cmd;
|
|
|
|
while (len) {
|
|
|
|
read_addr = offset;
|
|
|
|
|
|
|
|
#ifdef CONFIG_SF_DUAL_FLASH
|
|
|
|
if (flash->dual_flash > SF_SINGLE_FLASH)
|
|
|
|
spi_flash_dual_flash(flash, &read_addr);
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPI_FLASH_BAR
|
|
|
|
ret = spi_flash_write_bank(flash, read_addr);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
bank_sel = flash->bank_curr;
|
|
|
|
#endif
|
|
|
|
remain_len = ((SPI_FLASH_16MB_BOUN << flash->shift) *
|
|
|
|
(bank_sel + 1)) - offset;
|
|
|
|
if (len < remain_len)
|
|
|
|
read_len = len;
|
|
|
|
else
|
|
|
|
read_len = remain_len;
|
|
|
|
|
|
|
|
spi_flash_addr(read_addr, cmd);
|
|
|
|
|
|
|
|
ret = spi_flash_read_common(flash, cmd, cmdsz, data, read_len);
|
|
|
|
if (ret < 0) {
|
|
|
|
debug("SF: read failed\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset += read_len;
|
|
|
|
len -= read_len;
|
|
|
|
data += read_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(cmd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPI_FLASH_SST
|
|
|
|
static int sst_byte_write(struct spi_flash *flash, u32 offset, const void *buf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u8 cmd[4] = {
|
|
|
|
CMD_SST_BP,
|
|
|
|
offset >> 16,
|
|
|
|
offset >> 8,
|
|
|
|
offset,
|
|
|
|
};
|
|
|
|
|
|
|
|
debug("BP[%02x]: 0x%p => cmd = { 0x%02x 0x%06x }\n",
|
|
|
|
spi_w8r8(flash->spi, CMD_READ_STATUS), buf, cmd[0], offset);
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write_enable(flash);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write(flash->spi, cmd, sizeof(cmd), buf, 1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
int sst_write_wp(struct spi_flash *flash, u32 offset, size_t len,
|
|
|
|
const void *buf)
|
|
|
|
{
|
|
|
|
size_t actual, cmd_len;
|
|
|
|
int ret;
|
|
|
|
u8 cmd[4];
|
|
|
|
|
|
|
|
ret = spi_claim_bus(flash->spi);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: Unable to claim SPI bus\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the data is not word aligned, write out leading single byte */
|
|
|
|
actual = offset % 2;
|
|
|
|
if (actual) {
|
|
|
|
ret = sst_byte_write(flash, offset, buf);
|
|
|
|
if (ret)
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
offset += actual;
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write_enable(flash);
|
|
|
|
if (ret)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
cmd_len = 4;
|
|
|
|
cmd[0] = CMD_SST_AAI_WP;
|
|
|
|
cmd[1] = offset >> 16;
|
|
|
|
cmd[2] = offset >> 8;
|
|
|
|
cmd[3] = offset;
|
|
|
|
|
|
|
|
for (; actual < len - 1; actual += 2) {
|
|
|
|
debug("WP[%02x]: 0x%p => cmd = { 0x%02x 0x%06x }\n",
|
|
|
|
spi_w8r8(flash->spi, CMD_READ_STATUS), buf + actual,
|
|
|
|
cmd[0], offset);
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_write(flash->spi, cmd, cmd_len,
|
|
|
|
buf + actual, 2);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: sst word program failed\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = spi_flash_cmd_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
|
|
|
|
cmd_len = 1;
|
|
|
|
offset += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
ret = spi_flash_cmd_write_disable(flash);
|
|
|
|
|
|
|
|
/* If there is a single trailing byte, write it out */
|
|
|
|
if (!ret && actual != len)
|
|
|
|
ret = sst_byte_write(flash, offset, buf + actual);
|
|
|
|
|
|
|
|
done:
|
|
|
|
debug("SF: sst: program %s %zu bytes @ 0x%zx\n",
|
|
|
|
ret ? "failure" : "success", len, offset - actual);
|
|
|
|
|
|
|
|
spi_release_bus(flash->spi);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sst_write_bp(struct spi_flash *flash, u32 offset, size_t len,
|
|
|
|
const void *buf)
|
|
|
|
{
|
|
|
|
size_t actual;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = spi_claim_bus(flash->spi);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: Unable to claim SPI bus\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (actual = 0; actual < len; actual++) {
|
|
|
|
ret = sst_byte_write(flash, offset, buf + actual);
|
|
|
|
if (ret) {
|
|
|
|
debug("SF: sst byte program failed\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
ret = spi_flash_cmd_write_disable(flash);
|
|
|
|
|
|
|
|
debug("SF: sst: program %s %zu bytes @ 0x%zx\n",
|
|
|
|
ret ? "failure" : "success", len, offset - actual);
|
|
|
|
|
|
|
|
spi_release_bus(flash->spi);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SPI_FLASH_STMICRO
|
|
|
|
static void stm_get_locked_range(struct spi_flash *flash, u8 sr, loff_t *ofs,
|
|
|
|
u32 *len)
|
|
|
|
{
|
|
|
|
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
|
int shift = ffs(mask) - 1;
|
|
|
|
int pow;
|
|
|
|
|
|
|
|
if (!(sr & mask)) {
|
|
|
|
/* No protection */
|
|
|
|
*ofs = 0;
|
|
|
|
*len = 0;
|
|
|
|
} else {
|
|
|
|
pow = ((sr & mask) ^ mask) >> shift;
|
|
|
|
*len = flash->size >> pow;
|
|
|
|
*ofs = flash->size - *len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return 1 if the entire region is locked, 0 otherwise
|
|
|
|
*/
|
|
|
|
static int stm_is_locked_sr(struct spi_flash *flash, u32 ofs, u32 len,
|
|
|
|
u8 sr)
|
|
|
|
{
|
|
|
|
loff_t lock_offs;
|
|
|
|
u32 lock_len;
|
|
|
|
|
|
|
|
stm_get_locked_range(flash, sr, &lock_offs, &lock_len);
|
|
|
|
|
|
|
|
return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a region of the flash is (completely) locked. See stm_lock() for
|
|
|
|
* more info.
|
|
|
|
*
|
|
|
|
* Returns 1 if entire region is locked, 0 if any portion is unlocked, and
|
|
|
|
* negative on errors.
|
|
|
|
*/
|
|
|
|
int stm_is_locked(struct spi_flash *flash, u32 ofs, size_t len)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
u8 sr;
|
|
|
|
|
|
|
|
status = spi_flash_cmd_read_status(flash, &sr);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return stm_is_locked_sr(flash, ofs, len, sr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Lock a region of the flash. Compatible with ST Micro and similar flash.
|
|
|
|
* Supports only the block protection bits BP{0,1,2} in the status register
|
|
|
|
* (SR). Does not support these features found in newer SR bitfields:
|
|
|
|
* - TB: top/bottom protect - only handle TB=0 (top protect)
|
|
|
|
* - SEC: sector/block protect - only handle SEC=0 (block protect)
|
|
|
|
* - CMP: complement protect - only support CMP=0 (range is not complemented)
|
|
|
|
*
|
|
|
|
* Sample table portion for 8MB flash (Winbond w25q64fw):
|
|
|
|
*
|
|
|
|
* SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion
|
|
|
|
* --------------------------------------------------------------------------
|
|
|
|
* X | X | 0 | 0 | 0 | NONE | NONE
|
|
|
|
* 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64
|
|
|
|
* 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32
|
|
|
|
* 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16
|
|
|
|
* 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8
|
|
|
|
* 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4
|
|
|
|
* 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2
|
|
|
|
* X | X | 1 | 1 | 1 | 8 MB | ALL
|
|
|
|
*
|
|
|
|
* Returns negative on errors, 0 on success.
|
|
|
|
*/
|
|
|
|
int stm_lock(struct spi_flash *flash, u32 ofs, size_t len)
|
|
|
|
{
|
|
|
|
u8 status_old, status_new;
|
|
|
|
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
|
u8 shift = ffs(mask) - 1, pow, val;
|
|
|
|
|
|
|
|
spi_flash_cmd_read_status(flash, &status_old);
|
|
|
|
|
|
|
|
/* SPI NOR always locks to the end */
|
|
|
|
if (ofs + len != flash->size) {
|
|
|
|
/* Does combined region extend to end? */
|
|
|
|
if (!stm_is_locked_sr(flash, ofs + len, flash->size - ofs - len,
|
|
|
|
status_old))
|
|
|
|
return -EINVAL;
|
|
|
|
len = flash->size - ofs;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Need smallest pow such that:
|
|
|
|
*
|
|
|
|
* 1 / (2^pow) <= (len / size)
|
|
|
|
*
|
|
|
|
* so (assuming power-of-2 size) we do:
|
|
|
|
*
|
|
|
|
* pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
|
|
|
|
*/
|
|
|
|
pow = ilog2(flash->size) - ilog2(len);
|
|
|
|
val = mask - (pow << shift);
|
|
|
|
if (val & ~mask)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Don't "lock" with no region! */
|
|
|
|
if (!(val & mask))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
status_new = (status_old & ~mask) | val;
|
|
|
|
|
|
|
|
/* Only modify protection if it will not unlock other areas */
|
|
|
|
if ((status_new & mask) <= (status_old & mask))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spi_flash_cmd_write_status(flash, status_new);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unlock a region of the flash. See stm_lock() for more info
|
|
|
|
*
|
|
|
|
* Returns negative on errors, 0 on success.
|
|
|
|
*/
|
|
|
|
int stm_unlock(struct spi_flash *flash, u32 ofs, size_t len)
|
|
|
|
{
|
|
|
|
uint8_t status_old, status_new;
|
|
|
|
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
|
|
u8 shift = ffs(mask) - 1, pow, val;
|
|
|
|
|
|
|
|
spi_flash_cmd_read_status(flash, &status_old);
|
|
|
|
|
|
|
|
/* Cannot unlock; would unlock larger region than requested */
|
|
|
|
if (stm_is_locked_sr(flash, status_old, ofs - flash->erase_size,
|
|
|
|
flash->erase_size))
|
|
|
|
return -EINVAL;
|
|
|
|
/*
|
|
|
|
* Need largest pow such that:
|
|
|
|
*
|
|
|
|
* 1 / (2^pow) >= (len / size)
|
|
|
|
*
|
|
|
|
* so (assuming power-of-2 size) we do:
|
|
|
|
*
|
|
|
|
* pow = floor(log2(size / len)) = log2(size) - ceil(log2(len))
|
|
|
|
*/
|
|
|
|
pow = ilog2(flash->size) - order_base_2(flash->size - (ofs + len));
|
|
|
|
if (ofs + len == flash->size) {
|
|
|
|
val = 0; /* fully unlocked */
|
|
|
|
} else {
|
|
|
|
val = mask - (pow << shift);
|
|
|
|
/* Some power-of-two sizes are not supported */
|
|
|
|
if (val & ~mask)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
status_new = (status_old & ~mask) | val;
|
|
|
|
|
|
|
|
/* Only modify protection if it will not lock other areas */
|
|
|
|
if ((status_new & mask) >= (status_old & mask))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spi_flash_cmd_write_status(flash, status_new);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_SPI_FLASH_STMICRO */
|