common/spi_nor: Differential erase support

If CONFIG_SPI_NOR_SMART_ERASE is defined will read the sector/block
to be erased first and only initiate the erase operation if not
already in an erased state. The read operation (performed in
CONFIG_SPI_NOR_MAX_READ_SIZE chunks) is aborted early if a
non "0xff" byte is encountered.

Reduced erase time of a mostly erased EEPROM from 44s to 20s (16MB
Winbond part) / 1m22s to 40s (32MB Macronix part) @24MHz.

BUG=None
BRANCH=None
TEST=Built all targets. Successfully flashed various EEPROM images.

Change-Id: I369b1fcf72140663b8dbce5a469ebad27f7571ec
Signed-off-by: Nadim Taha <ntaha@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/437988
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
This commit is contained in:
Nadim Taha
2017-02-02 19:23:39 -08:00
committed by Nadim Taha
parent 2838114b19
commit c0feba66a2
2 changed files with 109 additions and 45 deletions

View File

@@ -394,6 +394,46 @@ static int spi_nor_device_discover_sfdp_capacity(
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. */
@@ -556,44 +596,11 @@ int spi_nor_read_jedec_id(const struct spi_nor_device_t *spi_nor_device,
int spi_nor_read(const struct spi_nor_device_t *spi_nor_device,
uint32_t offset, size_t size, uint8_t *data)
{
int rv = EC_SUCCESS;
int rv;
/* Claim the driver mutex. */
mutex_lock(&driver_mutex);
/* 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)
goto err_free;
data += read_size;
offset += read_size;
size -= read_size;
}
err_free:
rv = spi_nor_read_internal(spi_nor_device, offset, size, data);
/* Release the driver mutex. */
mutex_unlock(&driver_mutex);
@@ -616,6 +623,11 @@ int spi_nor_erase(const struct spi_nor_device_t *spi_nor_device,
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))
@@ -625,16 +637,6 @@ int spi_nor_erase(const struct spi_nor_device_t *spi_nor_device,
mutex_lock(&driver_mutex);
while (size > 0) {
/* 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;
erase_opcode = SPI_NOR_DRIVER_SPECIFIED_OPCODE_4KIB_ERASE;
erase_size = 4096;
@@ -645,6 +647,59 @@ int spi_nor_erase(const struct spi_nor_device_t *spi_nor_device,
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. */
DEBUG_CPRINTS_DEVICE(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) {

View File

@@ -1837,6 +1837,15 @@
/* If defined will enable block (64KiB) erase operations. */
#undef CONFIG_SPI_NOR_BLOCK_ERASE
/* If defined will read the sector/block to be erased first and only initiate
* the erase operation if not already in an erased state. The read operation
* (performed in CONFIG_SPI_NOR_MAX_READ_SIZE chunks) is aborted early if a
* non "0xff" byte is encountered.
* !! Make sure there is enough stack space to host a
* !! CONFIG_SPI_NOR_MAX_READ_SIZE sized buffer before enabling.
*/
#undef CONFIG_SPI_NOR_SMART_ERASE
/* SPI master feature */
#undef CONFIG_SPI_MASTER