1083 lines
30 KiB
C
1083 lines
30 KiB
C
|
/* Copyright 2015 The Chromium OS Authors. All rights reserved.
|
||
|
* Use of this source code is governed by a BSD-style license that can be
|
||
|
* found in the LICENSE file.
|
||
|
*/
|
||
|
|
||
|
/* SFDP-based Serial NOR flash device module for Chrome EC */
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "spi_nor.h"
|
||
|
#include "shared_mem.h"
|
||
|
#include "util.h"
|
||
|
#include "task.h"
|
||
|
#include "spi.h"
|
||
|
#include "sfdp.h"
|
||
|
#include "timer.h"
|
||
|
#include "watchdog.h"
|
||
|
|
||
|
#ifdef CONFIG_SPI_NOR_DEBUG
|
||
|
#define CPRINTS(dev, string, args...) \
|
||
|
cprints(CC_SPI, "SPI NOR %s: " string, (dev)->name, ## args)
|
||
|
#else
|
||
|
#define CPRINTS(dev, string, args...)
|
||
|
#endif
|
||
|
|
||
|
/* Time to sleep while serial NOR flash write is in progress. */
|
||
|
#define SPI_NOR_WIP_SLEEP_USEC 10
|
||
|
|
||
|
/* This driver only supports v1.* SFDP. */
|
||
|
#define SPI_NOR_SUPPORTED_SFDP_MAJOR_VERSION 1
|
||
|
|
||
|
/* Ensure a Serial NOR Flash read command in 4B addressing mode fits. */
|
||
|
BUILD_ASSERT(CONFIG_SPI_NOR_MAX_READ_SIZE + 5 <=
|
||
|
CONFIG_SPI_NOR_MAX_MESSAGE_SIZE);
|
||
|
/* The maximum write size must be a power of two so it can be used as an
|
||
|
* emulated maximum page size. */
|
||
|
BUILD_ASSERT(POWER_OF_TWO(CONFIG_SPI_NOR_MAX_WRITE_SIZE));
|
||
|
/* Ensure a Serial NOR Flash page program command in 4B addressing mode fits. */
|
||
|
BUILD_ASSERT(CONFIG_SPI_NOR_MAX_WRITE_SIZE + 5 <=
|
||
|
CONFIG_SPI_NOR_MAX_MESSAGE_SIZE);
|
||
|
|
||
|
/* A single mutex is used to protect the single buffer, SPI port, and all of the
|
||
|
* device mutable board defined device states, if the contention is too high it
|
||
|
* may be worthwhile to change the global mutex granularity to a finer-grained
|
||
|
* mutex granularity. */
|
||
|
static struct mutex driver_mutex;
|
||
|
|
||
|
/* Single internal buffer used to stage serial NOR flash commands for the
|
||
|
* public APIs (read, write, erase). */
|
||
|
static uint8_t buf[CONFIG_SPI_NOR_MAX_MESSAGE_SIZE];
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Internal driver functions. */
|
||
|
|
||
|
/**
|
||
|
* Blocking read of the Serial Flash's first status register.
|
||
|
*/
|
||
|
static int spi_nor_read_status(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t *status_register_value)
|
||
|
{
|
||
|
uint8_t cmd = SPI_NOR_OPCODE_READ_STATUS;
|
||
|
|
||
|
return spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&cmd, 1, status_register_value, 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the write enable latch. Device and shared buffer mutexes must be held!
|
||
|
*/
|
||
|
static int spi_nor_write_enable(const struct spi_nor_device_t *spi_nor_device)
|
||
|
{
|
||
|
uint8_t cmd = SPI_NOR_OPCODE_WRITE_ENABLE;
|
||
|
uint8_t status_register_value;
|
||
|
int rv = EC_SUCCESS;
|
||
|
|
||
|
/* Set the write enable latch. */
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&cmd, 1, NULL, 0);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
/* Verify the write enabled latch got set. */
|
||
|
rv = spi_nor_read_status(spi_nor_device, &status_register_value);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
if ((status_register_value & SPI_NOR_STATUS_REGISTER_WEL) == 0)
|
||
|
return EC_ERROR_UNKNOWN; /* WEL not set but should be. */
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read from the extended address register.
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param value The value to read to.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
static int spi_nor_read_ear(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t *value)
|
||
|
{
|
||
|
uint8_t command = SPI_NOR_OPCODE_RDEAR;
|
||
|
|
||
|
return spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&command, sizeof(command), value, 1);
|
||
|
}
|
||
|
|
||
|
int spi_nor_write_ear(const struct spi_nor_device_t *spi_nor_device,
|
||
|
const uint8_t value)
|
||
|
{
|
||
|
uint8_t buf[2];
|
||
|
int rv;
|
||
|
uint8_t ear;
|
||
|
|
||
|
mutex_lock(&driver_mutex);
|
||
|
|
||
|
rv = spi_nor_write_enable(spi_nor_device);
|
||
|
if (rv) {
|
||
|
CPRINTS(spi_nor_device, "Failed to write enable");
|
||
|
goto err_free;
|
||
|
}
|
||
|
|
||
|
buf[0] = SPI_NOR_OPCODE_WREAR;
|
||
|
buf[1] = value;
|
||
|
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
buf, sizeof(buf), NULL, 0);
|
||
|
if (rv) {
|
||
|
CPRINTS(spi_nor_device, "Failed to write EAR, rv=%d", rv);
|
||
|
goto err_free;
|
||
|
}
|
||
|
|
||
|
rv = spi_nor_read_ear(spi_nor_device, &ear);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
if (ear != value) {
|
||
|
CPRINTS(spi_nor_device,
|
||
|
"Write EAR error: write=%d, read=%d", value, ear);
|
||
|
rv = EC_ERROR_UNKNOWN; /* WEL not set but should be. */
|
||
|
goto err_free;
|
||
|
}
|
||
|
|
||
|
err_free:
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Block until the Serial NOR Flash clears the BUSY/WIP bit in its status reg.
|
||
|
*/
|
||
|
static int spi_nor_wait(const struct spi_nor_device_t *spi_nor_device)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
timestamp_t timeout;
|
||
|
uint8_t status_register_value;
|
||
|
|
||
|
rv = spi_nor_read_status(spi_nor_device, &status_register_value);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
timeout.val =
|
||
|
get_time().val + spi_nor_device->timeout_usec;
|
||
|
while (status_register_value & SPI_NOR_STATUS_REGISTER_WIP) {
|
||
|
/* Reload the watchdog before sleeping. */
|
||
|
watchdog_reload();
|
||
|
usleep(SPI_NOR_WIP_SLEEP_USEC);
|
||
|
|
||
|
/* Give up if the deadline has been exceeded. */
|
||
|
if (get_time().val > timeout.val)
|
||
|
return EC_ERROR_TIMEOUT;
|
||
|
|
||
|
/* Re-read the status register. */
|
||
|
rv = spi_nor_read_status(spi_nor_device,
|
||
|
&status_register_value);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read the Manufacturer bank and ID out of the JEDEC ID.
|
||
|
*/
|
||
|
static int spi_nor_read_jedec_mfn_id(
|
||
|
const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t *out_mfn_bank,
|
||
|
uint8_t *out_mfn_id)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
uint8_t jedec_id[SPI_NOR_JEDEC_ID_BANKS];
|
||
|
size_t i;
|
||
|
uint8_t cmd = SPI_NOR_OPCODE_JEDEC_ID;
|
||
|
|
||
|
/* Read the standardized part of the JEDEC ID. */
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&cmd, 1, jedec_id, SPI_NOR_JEDEC_ID_BANKS);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
*out_mfn_bank = 0;
|
||
|
/* Go through the JEDEC ID a byte a time to looking for a manufacturer
|
||
|
* ID instead of the next bank indicator (0x7F). */
|
||
|
for (i = 0; i < SPI_NOR_JEDEC_ID_BANKS; i++) {
|
||
|
*out_mfn_id = jedec_id[i];
|
||
|
if (*out_mfn_id != 0x7F)
|
||
|
return EC_SUCCESS;
|
||
|
*out_mfn_bank += 1;
|
||
|
}
|
||
|
/* JEDEC Manufacturer ID should be available, perhaps there is a bus
|
||
|
* problem or the JEP106 specification has grown the number of banks? */
|
||
|
return EC_ERROR_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read a doubleword out of a SFDP table (DWs are 1-based like the SFDP spec).
|
||
|
*/
|
||
|
static int spi_nor_read_sfdp_dword(
|
||
|
const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint32_t table_offset,
|
||
|
uint8_t table_double_word,
|
||
|
uint32_t *out_dw) {
|
||
|
uint8_t sfdp_cmd[5];
|
||
|
/* Calculate the byte offset based on the double word. */
|
||
|
uint32_t sfdp_offset = table_offset + ((table_double_word - 1) * 4);
|
||
|
|
||
|
/* Read the DW out of the SFDP region. */
|
||
|
sfdp_cmd[0] = SPI_NOR_OPCODE_SFDP;
|
||
|
sfdp_cmd[1] = (sfdp_offset & 0xFF0000) >> 16;
|
||
|
sfdp_cmd[2] = (sfdp_offset & 0xFF00) >> 8;
|
||
|
sfdp_cmd[3] = (sfdp_offset & 0xFF);
|
||
|
sfdp_cmd[4] = 0; /* Required dummy cycle. */
|
||
|
return spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
sfdp_cmd, 5, (uint8_t *)out_dw, 4);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a bool (1 or 0) based on whether the parameter header double words
|
||
|
* are for a SFDP v1.* Basic SPI Flash NOR Parameter Table.
|
||
|
*/
|
||
|
static int is_basic_flash_parameter_table(uint8_t sfdp_major_rev,
|
||
|
uint8_t sfdp_minor_rev,
|
||
|
uint32_t parameter_header_dw1,
|
||
|
uint32_t parameter_header_dw2)
|
||
|
{
|
||
|
if (sfdp_major_rev == 1 && sfdp_minor_rev < 5) {
|
||
|
return (SFDP_GET_BITFIELD(SFDP_1_0_PARAMETER_HEADER_DW1_ID,
|
||
|
parameter_header_dw1) ==
|
||
|
BASIC_FLASH_PARAMETER_TABLE_1_0_ID);
|
||
|
} else if (sfdp_major_rev == 1 && sfdp_minor_rev >= 5) {
|
||
|
return ((SFDP_GET_BITFIELD(SFDP_1_5_PARAMETER_HEADER_DW1_ID_LSB,
|
||
|
parameter_header_dw1) ==
|
||
|
BASIC_FLASH_PARAMETER_TABLE_1_5_ID_LSB) &&
|
||
|
(SFDP_GET_BITFIELD(SFDP_1_5_PARAMETER_HEADER_DW2_ID_MSB,
|
||
|
parameter_header_dw2) ==
|
||
|
BASIC_FLASH_PARAMETER_TABLE_1_5_ID_MSB));
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper function to locate the SFDP Basic SPI Flash NOR Parameter Table.
|
||
|
*/
|
||
|
static int locate_sfdp_basic_parameter_table(
|
||
|
const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t *out_sfdp_major_rev,
|
||
|
uint8_t *out_sfdp_minor_rev,
|
||
|
uint8_t *out_table_major_rev,
|
||
|
uint8_t *out_table_minor_rev,
|
||
|
uint32_t *out_table_offset,
|
||
|
size_t *out_table_size)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
uint8_t number_parameter_headers;
|
||
|
uint32_t table_offset = 0;
|
||
|
int table_found = 0;
|
||
|
uint32_t dw1;
|
||
|
uint32_t dw2;
|
||
|
|
||
|
/* Read the SFDP header. */
|
||
|
rv = spi_nor_read_sfdp_dword(spi_nor_device, 0, 1, &dw1);
|
||
|
rv |= spi_nor_read_sfdp_dword(spi_nor_device, 0, 2, &dw2);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
/* Ensure the SFDP table is valid. Note the versions are not checked
|
||
|
* through the SFDP table header, as there may be a backwards
|
||
|
* compatible, older basic parameter tables which are compatible with
|
||
|
* this driver in the parameter headers. */
|
||
|
if (!SFDP_HEADER_DW1_SFDP_SIGNATURE_VALID(dw1)) {
|
||
|
CPRINTS(spi_nor_device, "SFDP signature invalid");
|
||
|
return EC_ERROR_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
*out_sfdp_major_rev =
|
||
|
SFDP_GET_BITFIELD(SFDP_HEADER_DW2_SFDP_MAJOR, dw2);
|
||
|
*out_sfdp_minor_rev =
|
||
|
SFDP_GET_BITFIELD(SFDP_HEADER_DW2_SFDP_MINOR, dw2);
|
||
|
CPRINTS(spi_nor_device, "SFDP v%d.%d discovered",
|
||
|
*out_sfdp_major_rev, *out_sfdp_minor_rev);
|
||
|
|
||
|
/* NPH is 0-based, so add 1. */
|
||
|
number_parameter_headers =
|
||
|
SFDP_GET_BITFIELD(SFDP_HEADER_DW2_NPH, dw2) + 1;
|
||
|
CPRINTS(spi_nor_device, "There are %d SFDP parameter headers",
|
||
|
number_parameter_headers);
|
||
|
|
||
|
/* Search for the newest, compatible basic flash parameter table. */
|
||
|
*out_table_major_rev = 0;
|
||
|
*out_table_minor_rev = 0;
|
||
|
while (number_parameter_headers) {
|
||
|
uint8_t major_rev, minor_rev;
|
||
|
|
||
|
table_offset += 8;
|
||
|
number_parameter_headers--;
|
||
|
|
||
|
/* Read this parameter header's two dwords. */
|
||
|
rv = spi_nor_read_sfdp_dword(
|
||
|
spi_nor_device, table_offset, 1, &dw1);
|
||
|
rv |= spi_nor_read_sfdp_dword(
|
||
|
spi_nor_device, table_offset, 2, &dw2);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
/* Ensure it's the basic flash parameter table. */
|
||
|
if (!is_basic_flash_parameter_table(*out_sfdp_major_rev,
|
||
|
*out_sfdp_minor_rev,
|
||
|
dw1, dw2))
|
||
|
continue;
|
||
|
|
||
|
/* The parameter header major and minor versioning is still the
|
||
|
* same as SFDP 1.0. */
|
||
|
major_rev = SFDP_GET_BITFIELD(
|
||
|
SFDP_1_0_PARAMETER_HEADER_DW1_TABLE_MAJOR, dw1);
|
||
|
minor_rev = SFDP_GET_BITFIELD(
|
||
|
SFDP_1_0_PARAMETER_HEADER_DW1_TABLE_MINOR, dw1);
|
||
|
|
||
|
/* Skip incompatible parameter tables. */
|
||
|
if (major_rev != SPI_NOR_SUPPORTED_SFDP_MAJOR_VERSION)
|
||
|
continue;
|
||
|
|
||
|
/* If this parameter table has a lower revision compared to a
|
||
|
* previously found compatible table, skip it. */
|
||
|
if (minor_rev < *out_table_minor_rev)
|
||
|
continue;
|
||
|
|
||
|
table_found = 1;
|
||
|
*out_table_major_rev = major_rev;
|
||
|
*out_table_minor_rev = minor_rev;
|
||
|
/* The parameter header ptp and ptl are still the same as
|
||
|
* SFDP 1.0. */
|
||
|
*out_table_offset = SFDP_GET_BITFIELD(
|
||
|
SFDP_1_0_PARAMETER_HEADER_DW2_PTP, dw2);
|
||
|
/* Convert the size from DW to Bytes. */
|
||
|
*out_table_size = SFDP_GET_BITFIELD(
|
||
|
SFDP_1_0_PARAMETER_HEADER_DW1_PTL, dw1) * 4;
|
||
|
}
|
||
|
|
||
|
if (!table_found) {
|
||
|
CPRINTS(spi_nor_device,
|
||
|
"No compatible Basic Flash Parameter Table found");
|
||
|
return EC_ERROR_UNKNOWN;
|
||
|
}
|
||
|
|
||
|
CPRINTS(spi_nor_device, "Using Basic Flash Parameter Table v%d.%d",
|
||
|
*out_sfdp_major_rev, *out_sfdp_minor_rev);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper function to lookup the part's page size in the SFDP Basic SPI Flash
|
||
|
* NOR Parameter Table.
|
||
|
*/
|
||
|
static int spi_nor_device_discover_sfdp_page_size(
|
||
|
struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t basic_parameter_table_major_version,
|
||
|
uint8_t basic_parameter_table_minor_version,
|
||
|
uint32_t basic_parameter_table_offset,
|
||
|
size_t *page_size)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
uint32_t dw;
|
||
|
|
||
|
if (basic_parameter_table_major_version == 1 &&
|
||
|
basic_parameter_table_minor_version < 5) {
|
||
|
/* Use the Basic Flash Parameter v1.0 page size reporting. */
|
||
|
rv = spi_nor_read_sfdp_dword(
|
||
|
spi_nor_device, basic_parameter_table_offset, 1, &dw);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
if (SFDP_GET_BITFIELD(BFPT_1_0_DW1_WRITE_GRANULARITY, dw))
|
||
|
*page_size = 64;
|
||
|
else
|
||
|
*page_size = 1;
|
||
|
|
||
|
} else if (basic_parameter_table_major_version == 1 &&
|
||
|
basic_parameter_table_minor_version >= 5) {
|
||
|
/* Use the Basic Flash Parameter v1.5 page size reporting. */
|
||
|
rv = spi_nor_read_sfdp_dword(spi_nor_device,
|
||
|
basic_parameter_table_offset, 11, &dw);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
*page_size =
|
||
|
1 << SFDP_GET_BITFIELD(BFPT_1_5_DW11_PAGE_SIZE, dw);
|
||
|
}
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper function to lookup the part's capacity in the SFDP Basic SPI Flash
|
||
|
* NOR Parameter Table.
|
||
|
*/
|
||
|
static int spi_nor_device_discover_sfdp_capacity(
|
||
|
struct spi_nor_device_t *spi_nor_device,
|
||
|
uint8_t basic_parameter_table_major_version,
|
||
|
uint8_t basic_parameter_table_minor_version,
|
||
|
uint32_t basic_parameter_table_offset,
|
||
|
uint32_t *capacity)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
uint32_t dw;
|
||
|
|
||
|
/* First attempt to discover the device's capacity. */
|
||
|
if (basic_parameter_table_major_version == 1) {
|
||
|
/* Use the Basic Flash Parameter v1.0 capacity reporting. */
|
||
|
rv = spi_nor_read_sfdp_dword(spi_nor_device,
|
||
|
basic_parameter_table_offset, 2, &dw);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (SFDP_GET_BITFIELD(BFPT_1_0_DW2_GT_2_GIBIBITS, dw)) {
|
||
|
/* Ensure the capacity is less than 4GiB. */
|
||
|
uint64_t tmp_capacity = 1 <<
|
||
|
(SFDP_GET_BITFIELD(BFPT_1_0_DW2_N, dw) - 3);
|
||
|
if (tmp_capacity > UINT32_MAX)
|
||
|
return EC_ERROR_OVERFLOW;
|
||
|
*capacity = tmp_capacity;
|
||
|
} else {
|
||
|
*capacity =
|
||
|
1 +
|
||
|
(SFDP_GET_BITFIELD(BFPT_1_0_DW2_N, dw) >> 3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int spi_nor_read_internal(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint32_t offset, size_t size, uint8_t *data)
|
||
|
{
|
||
|
int rv;
|
||
|
|
||
|
/* Split up the read operation into multiple transactions if the size
|
||
|
* is larger than the maximum read size.
|
||
|
*/
|
||
|
while (size > 0) {
|
||
|
size_t read_size =
|
||
|
MIN(size, CONFIG_SPI_NOR_MAX_READ_SIZE);
|
||
|
size_t read_command_size;
|
||
|
|
||
|
/* Set up the read command in the TX buffer. */
|
||
|
buf[0] = SPI_NOR_OPCODE_SLOW_READ;
|
||
|
if (spi_nor_device->in_4b_addressing_mode) {
|
||
|
buf[1] = (offset & 0xFF000000) >> 24;
|
||
|
buf[2] = (offset & 0xFF0000) >> 16;
|
||
|
buf[3] = (offset & 0xFF00) >> 8;
|
||
|
buf[4] = (offset & 0xFF);
|
||
|
read_command_size = 5;
|
||
|
} else { /* in 3 byte addressing mode */
|
||
|
buf[1] = (offset & 0xFF0000) >> 16;
|
||
|
buf[2] = (offset & 0xFF00) >> 8;
|
||
|
buf[3] = (offset & 0xFF);
|
||
|
read_command_size = 4;
|
||
|
}
|
||
|
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
buf, read_command_size, data, read_size);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
data += read_size;
|
||
|
offset += read_size;
|
||
|
size -= read_size;
|
||
|
}
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* External Serial NOR Flash API available to other modules. */
|
||
|
|
||
|
/**
|
||
|
* Initialize the module, assumes the Serial NOR Flash devices are currently
|
||
|
* all available for initialization. As part of the initialization the driver
|
||
|
* will check if the part has a compatible SFDP Basic Flash Parameter table
|
||
|
* and update the part's page_size, capacity, and forces the addressing mode.
|
||
|
* Parts with more than 16MiB of capacity are initialized into 4B addressing
|
||
|
* and parts with less are initialized into 3B addressing mode.
|
||
|
*
|
||
|
* WARNING: This must successfully return before invoking any other Serial NOR
|
||
|
* Flash APIs.
|
||
|
*/
|
||
|
int spi_nor_init(void)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
size_t i;
|
||
|
|
||
|
/* Initialize the state for each serial NOR flash device. */
|
||
|
for (i = 0; i < SPI_NOR_DEVICE_COUNT; i++) {
|
||
|
uint8_t sfdp_major_rev, sfdp_minor_rev;
|
||
|
uint8_t table_major_rev, table_minor_rev;
|
||
|
uint32_t table_offset;
|
||
|
size_t table_size;
|
||
|
struct spi_nor_device_t *spi_nor_device =
|
||
|
&spi_nor_devices[i];
|
||
|
|
||
|
rv |= locate_sfdp_basic_parameter_table(spi_nor_device,
|
||
|
&sfdp_major_rev,
|
||
|
&sfdp_minor_rev,
|
||
|
&table_major_rev,
|
||
|
&table_minor_rev,
|
||
|
&table_offset,
|
||
|
&table_size);
|
||
|
|
||
|
/* If we failed to find a compatible SFDP Basic Flash Parameter
|
||
|
* table, use the default capacity, page size, and addressing
|
||
|
* mode values. */
|
||
|
if (rv == EC_SUCCESS) {
|
||
|
size_t page_size = 0;
|
||
|
uint32_t capacity = 0;
|
||
|
|
||
|
rv |= spi_nor_device_discover_sfdp_page_size(
|
||
|
spi_nor_device,
|
||
|
table_major_rev, table_minor_rev, table_offset,
|
||
|
&page_size);
|
||
|
rv |= spi_nor_device_discover_sfdp_capacity(
|
||
|
spi_nor_device,
|
||
|
table_major_rev, table_minor_rev, table_offset,
|
||
|
&capacity);
|
||
|
if (rv == EC_SUCCESS) {
|
||
|
mutex_lock(&driver_mutex);
|
||
|
spi_nor_device->capacity = capacity;
|
||
|
spi_nor_device->page_size = page_size;
|
||
|
CPRINTS(spi_nor_device,
|
||
|
"Updated to SFDP params: %dKiB w/ %dB pages",
|
||
|
spi_nor_device->capacity >> 10,
|
||
|
spi_nor_device->page_size);
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Ensure the device is in a determined addressing state by
|
||
|
* forcing a 4B addressing mode entry or exit depending on the
|
||
|
* device capacity. If the device is larger than 16MiB, enter
|
||
|
* 4B addressing mode. */
|
||
|
rv |= spi_nor_set_4b_mode(spi_nor_device,
|
||
|
spi_nor_device->capacity > 0x1000000);
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Forces the Serial NOR Flash device to enter (or exit) 4 Byte addressing mode.
|
||
|
*
|
||
|
* WARNING:
|
||
|
* 1) In 3 Byte addressing mode only 16MiB of Serial NOR Flash is accessible.
|
||
|
* 2) If there's a second SPI master communicating with this Serial NOR Flash
|
||
|
* part on the board, the user is responsible for ensuring addressing mode
|
||
|
* compatibility and cooperation.
|
||
|
* 3) The user must ensure that multiple users do not trample on each other
|
||
|
* by having multiple parties changing the device's addressing mode.
|
||
|
*
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param enter_4b_addressing_mode Whether to enter (1) or exit (0) 4B mode.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
int spi_nor_set_4b_mode(struct spi_nor_device_t *spi_nor_device,
|
||
|
int enter_4b_addressing_mode)
|
||
|
{
|
||
|
uint8_t cmd;
|
||
|
int rv;
|
||
|
|
||
|
rv = spi_nor_write_enable(spi_nor_device);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (enter_4b_addressing_mode)
|
||
|
cmd = SPI_NOR_DRIVER_SPECIFIED_OPCODE_ENTER_4B;
|
||
|
else
|
||
|
cmd = SPI_NOR_DRIVER_SPECIFIED_OPCODE_EXIT_4B;
|
||
|
|
||
|
/* Claim the driver mutex to modify the device state. */
|
||
|
mutex_lock(&driver_mutex);
|
||
|
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&cmd, 1, NULL, 0);
|
||
|
if (rv == EC_SUCCESS) {
|
||
|
spi_nor_device->in_4b_addressing_mode =
|
||
|
enter_4b_addressing_mode;
|
||
|
}
|
||
|
|
||
|
CPRINTS(spi_nor_device, "Entered %s Addressing Mode",
|
||
|
enter_4b_addressing_mode ? "4-Byte" : "3-Byte");
|
||
|
|
||
|
/* Release the driver mutex. */
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read JEDEC Identifier.
|
||
|
*
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param size Number of Bytes to read.
|
||
|
* @param data Destination buffer for data.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
int spi_nor_read_jedec_id(const struct spi_nor_device_t *spi_nor_device,
|
||
|
size_t size, uint8_t *data) {
|
||
|
int rv;
|
||
|
uint8_t cmd = SPI_NOR_OPCODE_JEDEC_ID;
|
||
|
|
||
|
if (size > CONFIG_SPI_NOR_MAX_READ_SIZE)
|
||
|
return EC_ERROR_INVAL;
|
||
|
/* Claim the driver mutex. */
|
||
|
mutex_lock(&driver_mutex);
|
||
|
/* Read the JEDEC ID. */
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
&cmd, 1, data, size);
|
||
|
/* Release the driver mutex. */
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read from the Serial NOR Flash device.
|
||
|
*
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param offset Flash offset to read.
|
||
|
* @param size Number of Bytes to read.
|
||
|
* @param data Destination buffer for data.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
int spi_nor_read(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint32_t offset, size_t size, uint8_t *data)
|
||
|
{
|
||
|
int rv;
|
||
|
|
||
|
/* Claim the driver mutex. */
|
||
|
mutex_lock(&driver_mutex);
|
||
|
rv = spi_nor_read_internal(spi_nor_device, offset, size, data);
|
||
|
/* Release the driver mutex. */
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Erase flash on the Serial Flash Device.
|
||
|
*
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param offset Flash offset to erase, must be aligned to the minimum physical
|
||
|
* erase size.
|
||
|
* @param size Number of Bytes to erase, must be a multiple of the the minimum
|
||
|
* physical erase size.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
int spi_nor_erase(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint32_t offset, size_t size)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
size_t erase_command_size, erase_size;
|
||
|
uint8_t erase_opcode;
|
||
|
#ifdef CONFIG_SPI_NOR_SMART_ERASE
|
||
|
BUILD_ASSERT((CONFIG_SPI_NOR_MAX_READ_SIZE % 4) == 0);
|
||
|
uint8_t buffer[CONFIG_SPI_NOR_MAX_READ_SIZE] __aligned(4);
|
||
|
size_t verify_offset, read_offset, read_size, read_left;
|
||
|
#endif
|
||
|
|
||
|
/* Invalid input */
|
||
|
if ((offset % 4096 != 0) || (size % 4096 != 0) || (size < 4096))
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
/* Claim the driver mutex. */
|
||
|
mutex_lock(&driver_mutex);
|
||
|
|
||
|
while (size > 0) {
|
||
|
erase_opcode = SPI_NOR_DRIVER_SPECIFIED_OPCODE_4KIB_ERASE;
|
||
|
erase_size = 4096;
|
||
|
|
||
|
#ifdef CONFIG_SPI_NOR_BLOCK_ERASE
|
||
|
if (!(offset % 65536) && size >= 65536) {
|
||
|
erase_opcode =
|
||
|
SPI_NOR_DRIVER_SPECIFIED_OPCODE_64KIB_ERASE;
|
||
|
erase_size = 65536;
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef CONFIG_SPI_NOR_SMART_ERASE
|
||
|
read_offset = offset;
|
||
|
read_left = erase_size;
|
||
|
while (read_left) {
|
||
|
read_size = MIN(read_left,
|
||
|
CONFIG_SPI_NOR_MAX_READ_SIZE);
|
||
|
/* Since CONFIG_SPI_NOR_MAX_READ_SIZE & erase_size are
|
||
|
* both guaranteed to be multiples of 4.
|
||
|
*/
|
||
|
assert(read_size >= 4 && (read_size % 4) == 0);
|
||
|
rv = spi_nor_read_internal(spi_nor_device, read_offset,
|
||
|
read_size, buffer);
|
||
|
if (rv != EC_SUCCESS)
|
||
|
break;
|
||
|
/* Aligned word verify reduced the overall (read +
|
||
|
* verify) time by ~20% (vs bytewise verify) on
|
||
|
* an m3@24MHz & SPI@24MHz.
|
||
|
*/
|
||
|
verify_offset = 0;
|
||
|
while (verify_offset <= read_size - 4) {
|
||
|
if (*(uint32_t *)(buffer + verify_offset)
|
||
|
!= 0xffffffff) {
|
||
|
break;
|
||
|
}
|
||
|
verify_offset += 4;
|
||
|
}
|
||
|
if (verify_offset != read_size)
|
||
|
break;
|
||
|
read_offset += read_size;
|
||
|
read_left -= read_size;
|
||
|
watchdog_reload();
|
||
|
}
|
||
|
if (!read_left) {
|
||
|
/* Sector/block already erased. */
|
||
|
CPRINTS(spi_nor_device,
|
||
|
"Skipping erase [%x:%x] "
|
||
|
"(already erased)",
|
||
|
offset, erase_size);
|
||
|
offset += erase_size;
|
||
|
size -= erase_size;
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
/* Wait for the previous operation to finish. */
|
||
|
rv = spi_nor_wait(spi_nor_device);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
/* Enable writing to serial NOR flash. */
|
||
|
rv = spi_nor_write_enable(spi_nor_device);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
/* Set up the erase instruction. */
|
||
|
buf[0] = erase_opcode;
|
||
|
if (spi_nor_device->in_4b_addressing_mode) {
|
||
|
buf[1] = (offset & 0xFF000000) >> 24;
|
||
|
buf[2] = (offset & 0xFF0000) >> 16;
|
||
|
buf[3] = (offset & 0xFF00) >> 8;
|
||
|
buf[4] = (offset & 0xFF);
|
||
|
erase_command_size = 5;
|
||
|
} else { /* in 3 byte addressing mode */
|
||
|
buf[1] = (offset & 0xFF0000) >> 16;
|
||
|
buf[2] = (offset & 0xFF00) >> 8;
|
||
|
buf[3] = (offset & 0xFF);
|
||
|
erase_command_size = 4;
|
||
|
}
|
||
|
|
||
|
rv = spi_transaction(
|
||
|
&spi_devices[spi_nor_device->spi_master],
|
||
|
buf, erase_command_size, NULL, 0);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
offset += erase_size;
|
||
|
size -= erase_size;
|
||
|
}
|
||
|
|
||
|
/* Wait for the previous operation to finish. */
|
||
|
rv = spi_nor_wait(spi_nor_device);
|
||
|
|
||
|
err_free:
|
||
|
/* Release the driver mutex. */
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write to the Serial NOR Flash device. Assumes already erased.
|
||
|
*
|
||
|
* @param spi_nor_device The Serial NOR Flash device to use.
|
||
|
* @param offset Flash offset to write.
|
||
|
* @param size Number of Bytes to write.
|
||
|
* @param data Data to write to flash.
|
||
|
* @return ec_error_list (non-zero on error and timeout).
|
||
|
*/
|
||
|
int spi_nor_write(const struct spi_nor_device_t *spi_nor_device,
|
||
|
uint32_t offset, size_t size, const uint8_t *data)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
size_t effective_page_size;
|
||
|
|
||
|
/* Claim the driver mutex. */
|
||
|
mutex_lock(&driver_mutex);
|
||
|
|
||
|
/* Ensure the device's page size fits in the driver's buffer, if not
|
||
|
* emulate a smaller page size based on the buffer size. */
|
||
|
effective_page_size = MIN(spi_nor_device->page_size,
|
||
|
CONFIG_SPI_NOR_MAX_WRITE_SIZE);
|
||
|
|
||
|
/* Split the write into multiple writes if the size is too large. */
|
||
|
while (size > 0) {
|
||
|
size_t prefix_size;
|
||
|
/* Figure out the size of the next write within 1 page. */
|
||
|
uint32_t page_offset = offset & (effective_page_size - 1);
|
||
|
size_t write_size =
|
||
|
MIN(size, effective_page_size - page_offset);
|
||
|
|
||
|
/* Wait for the previous operation to finish. */
|
||
|
rv = spi_nor_wait(spi_nor_device);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
/* Enable writing to serial NOR flash. */
|
||
|
rv = spi_nor_write_enable(spi_nor_device);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
/* Set up the page program command. */
|
||
|
buf[0] = SPI_NOR_OPCODE_PAGE_PROGRAM;
|
||
|
if (spi_nor_device->in_4b_addressing_mode) {
|
||
|
buf[1] = (offset & 0xFF000000) >> 24;
|
||
|
buf[2] = (offset & 0xFF0000) >> 16;
|
||
|
buf[3] = (offset & 0xFF00) >> 8;
|
||
|
buf[4] = (offset & 0xFF);
|
||
|
prefix_size = 5;
|
||
|
} else { /* in 3 byte addressing mode */
|
||
|
buf[1] = (offset & 0xFF0000) >> 16;
|
||
|
buf[2] = (offset & 0xFF00) >> 8;
|
||
|
buf[3] = (offset & 0xFF);
|
||
|
prefix_size = 4;
|
||
|
}
|
||
|
/* Copy data to write into the buffer after the prefix. */
|
||
|
memmove(buf + prefix_size, data, write_size);
|
||
|
|
||
|
rv = spi_transaction(&spi_devices[spi_nor_device->spi_master],
|
||
|
buf, prefix_size + write_size, NULL, 0);
|
||
|
if (rv)
|
||
|
goto err_free;
|
||
|
|
||
|
data += write_size;
|
||
|
offset += write_size;
|
||
|
size -= write_size;
|
||
|
}
|
||
|
|
||
|
/* Wait for the previous operation to finish. */
|
||
|
rv = spi_nor_wait(spi_nor_device);
|
||
|
|
||
|
err_free:
|
||
|
/* Release the driver mutex. */
|
||
|
mutex_unlock(&driver_mutex);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Serial NOR Flash console commands. */
|
||
|
|
||
|
#ifdef CONFIG_CMD_SPI_NOR
|
||
|
static int command_spi_nor_info(int argc, char **argv)
|
||
|
{
|
||
|
int rv = EC_SUCCESS;
|
||
|
|
||
|
uint8_t sfdp_major_rev, sfdp_minor_rev;
|
||
|
uint8_t table_major_rev, table_minor_rev;
|
||
|
uint32_t table_offset;
|
||
|
uint8_t mfn_bank = 0, mfn_id = 0;
|
||
|
size_t table_size;
|
||
|
const struct spi_nor_device_t *spi_nor_device = 0;
|
||
|
int spi_nor_device_index = 0;
|
||
|
int spi_nor_device_index_limit = spi_nor_devices_used - 1;
|
||
|
|
||
|
/* Set the device index limits if a device was specified. */
|
||
|
if (argc == 2) {
|
||
|
spi_nor_device_index = strtoi(argv[1], NULL, 0);
|
||
|
if (spi_nor_device_index >= spi_nor_devices_used)
|
||
|
return EC_ERROR_PARAM1;
|
||
|
spi_nor_device_index_limit = spi_nor_device_index;
|
||
|
} else if (argc != 1) {
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
}
|
||
|
|
||
|
for (; spi_nor_device_index <= spi_nor_device_index_limit;
|
||
|
spi_nor_device_index++) {
|
||
|
spi_nor_device = &spi_nor_devices[spi_nor_device_index];
|
||
|
|
||
|
ccprintf("Serial NOR Flash Device %d:\n", spi_nor_device_index);
|
||
|
ccprintf("\tName: %s\n", spi_nor_device->name);
|
||
|
ccprintf("\tSPI master index: %d\n",
|
||
|
spi_nor_device->spi_master);
|
||
|
ccprintf("\tTimeout: %d uSec\n",
|
||
|
spi_nor_device->timeout_usec);
|
||
|
ccprintf("\tCapacity: %d KiB\n",
|
||
|
spi_nor_device->capacity >> 10),
|
||
|
ccprintf("\tAddressing: %s addressing mode\n",
|
||
|
spi_nor_device->in_4b_addressing_mode ? "4B" : "3B");
|
||
|
ccprintf("\tPage Size: %d Bytes\n",
|
||
|
spi_nor_device->page_size);
|
||
|
|
||
|
/* Get JEDEC ID info. */
|
||
|
rv = spi_nor_read_jedec_mfn_id(spi_nor_device, &mfn_bank,
|
||
|
&mfn_id);
|
||
|
if (rv != EC_SUCCESS)
|
||
|
return rv;
|
||
|
ccprintf("\tJEDEC ID bank %d manufacturing code 0x%x\n",
|
||
|
mfn_bank, mfn_id);
|
||
|
|
||
|
/* Get SFDP info. */
|
||
|
if (locate_sfdp_basic_parameter_table(
|
||
|
spi_nor_device, &sfdp_major_rev, &sfdp_minor_rev,
|
||
|
&table_major_rev, &table_minor_rev, &table_offset,
|
||
|
&table_size) != EC_SUCCESS) {
|
||
|
ccputs("\tNo JEDEC SFDP support detected\n");
|
||
|
continue; /* Go on to the next device. */
|
||
|
}
|
||
|
ccprintf("\tSFDP v%d.%d\n", sfdp_major_rev, sfdp_minor_rev);
|
||
|
ccprintf("\tFlash Parameter Table v%d.%d (%dB @ 0x%x)\n",
|
||
|
table_major_rev, table_minor_rev,
|
||
|
table_size, table_offset);
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(spinorinfo, command_spi_nor_info,
|
||
|
"[device]",
|
||
|
"Report Serial NOR Flash device information");
|
||
|
#endif /* CONFIG_CMD_SPI_NOR */
|
||
|
|
||
|
#ifdef CONFIG_CMD_SPI_NOR
|
||
|
static int command_spi_nor_erase(int argc, char **argv)
|
||
|
{
|
||
|
const struct spi_nor_device_t *spi_nor_device;
|
||
|
int spi_nor_device_index;
|
||
|
int offset = 0;
|
||
|
int size = 4096;
|
||
|
int rv;
|
||
|
|
||
|
if (argc < 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
spi_nor_device_index = strtoi(argv[1], NULL, 0);
|
||
|
if (spi_nor_device_index >= spi_nor_devices_used)
|
||
|
return EC_ERROR_PARAM1;
|
||
|
spi_nor_device = &spi_nor_devices[spi_nor_device_index];
|
||
|
|
||
|
rv = parse_offset_size(argc, argv, 2, &offset, &size);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
ccprintf("Erasing %d bytes at 0x%x on %s...\n",
|
||
|
size, offset, spi_nor_device->name);
|
||
|
return spi_nor_erase(spi_nor_device, offset, size);
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(spinorerase, command_spi_nor_erase,
|
||
|
"device [offset] [size]",
|
||
|
"Erase flash");
|
||
|
#endif /* CONFIG_CMD_SPI_NOR */
|
||
|
|
||
|
#ifdef CONFIG_CMD_SPI_NOR
|
||
|
static int command_spi_nor_write(int argc, char **argv)
|
||
|
{
|
||
|
const struct spi_nor_device_t *spi_nor_device;
|
||
|
int spi_nor_device_index;
|
||
|
int offset = 0;
|
||
|
int size = CONFIG_SPI_NOR_MAX_WRITE_SIZE;
|
||
|
int rv;
|
||
|
char *data;
|
||
|
int i;
|
||
|
|
||
|
if (argc < 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
spi_nor_device_index = strtoi(argv[1], NULL, 0);
|
||
|
if (spi_nor_device_index >= spi_nor_devices_used)
|
||
|
return EC_ERROR_PARAM1;
|
||
|
spi_nor_device = &spi_nor_devices[spi_nor_device_index];
|
||
|
|
||
|
rv = parse_offset_size(argc, argv, 2, &offset, &size);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (size > shared_mem_size())
|
||
|
size = shared_mem_size();
|
||
|
|
||
|
/* Acquire the shared memory buffer */
|
||
|
rv = shared_mem_acquire(size, &data);
|
||
|
if (rv) {
|
||
|
ccputs("Can't get shared mem\n");
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/* Fill the data buffer with a pattern */
|
||
|
for (i = 0; i < size; i++)
|
||
|
data[i] = i;
|
||
|
|
||
|
ccprintf("Writing %d bytes to 0x%x on %s...\n",
|
||
|
size, offset, spi_nor_device->name);
|
||
|
rv = spi_nor_write(spi_nor_device, offset, size, data);
|
||
|
|
||
|
/* Free the buffer */
|
||
|
shared_mem_release(data);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(spinorwrite, command_spi_nor_write,
|
||
|
"device [offset] [size]",
|
||
|
"Write pattern to flash");
|
||
|
#endif /* CONFIG_CMD_SPI_NOR */
|
||
|
|
||
|
#ifdef CONFIG_CMD_SPI_NOR
|
||
|
static int command_spi_nor_read(int argc, char **argv)
|
||
|
{
|
||
|
const struct spi_nor_device_t *spi_nor_device;
|
||
|
int spi_nor_device_index;
|
||
|
int offset = 0;
|
||
|
int size = CONFIG_SPI_NOR_MAX_READ_SIZE;
|
||
|
int rv;
|
||
|
char *data;
|
||
|
int i;
|
||
|
|
||
|
if (argc < 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
spi_nor_device_index = strtoi(argv[1], NULL, 0);
|
||
|
if (spi_nor_device_index >= spi_nor_devices_used)
|
||
|
return EC_ERROR_PARAM1;
|
||
|
spi_nor_device = &spi_nor_devices[spi_nor_device_index];
|
||
|
|
||
|
rv = parse_offset_size(argc, argv, 2, &offset, &size);
|
||
|
if (rv)
|
||
|
return rv;
|
||
|
|
||
|
if (size > shared_mem_size())
|
||
|
size = shared_mem_size();
|
||
|
|
||
|
/* Acquire the shared memory buffer */
|
||
|
rv = shared_mem_acquire(size, &data);
|
||
|
if (rv) {
|
||
|
ccputs("Can't get shared mem\n");
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
/* Read the data */
|
||
|
ccprintf("Reading %d bytes from %s...",
|
||
|
size, spi_nor_device->name);
|
||
|
if (spi_nor_read(spi_nor_device, offset, size, data)) {
|
||
|
rv = EC_ERROR_INVAL;
|
||
|
goto err_free;
|
||
|
}
|
||
|
|
||
|
/* Dump it */
|
||
|
for (i = 0; i < size; i++) {
|
||
|
if ((offset + i) % 16) {
|
||
|
ccprintf(" %02x", data[i]);
|
||
|
} else {
|
||
|
ccprintf("\n%08x: %02x", offset + i, data[i]);
|
||
|
cflush();
|
||
|
}
|
||
|
}
|
||
|
ccprintf("\n");
|
||
|
|
||
|
err_free:
|
||
|
/* Free the buffer */
|
||
|
shared_mem_release(data);
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(spinorread, command_spi_nor_read,
|
||
|
"device [offset] [size]",
|
||
|
"Read flash");
|
||
|
#endif /* CONFIG_CMD_SPI_NOR */
|