| /* |
| * Copyright 2014 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. |
| * |
| * SPI flash driver for Chrome EC. |
| */ |
| |
| #include "common.h" |
| #include "console.h" |
| #include "host_command.h" |
| #include "shared_mem.h" |
| #include "spi.h" |
| #include "spi_flash.h" |
| #include "spi_flash_reg.h" |
| #include "timer.h" |
| #include "util.h" |
| #include "watchdog.h" |
| #include "ec_commands.h" |
| #include "flash.h" |
| |
| /* |
| * Time to sleep when chip is busy |
| */ |
| #define SPI_FLASH_SLEEP_USEC 100 |
| |
| /* |
| * This is the max time for 32kb flash erase |
| */ |
| #define SPI_FLASH_TIMEOUT_USEC (800*MSEC) |
| |
| /* Internal buffer used by SPI flash driver */ |
| static uint8_t buf[SPI_FLASH_MAX_MESSAGE_SIZE]; |
| |
| /** |
| * Waits for chip to finish current operation. Must be called after |
| * erase/write operations to ensure successive commands are executed. |
| * |
| * @return EC_SUCCESS or error on timeout |
| */ |
| int spi_flash_wait(void) |
| { |
| timestamp_t timeout; |
| |
| timeout.val = get_time().val + SPI_FLASH_TIMEOUT_USEC; |
| /* Wait until chip is not busy */ |
| while (spi_flash_get_status1() & SPI_FLASH_SR1_BUSY) { |
| usleep(SPI_FLASH_SLEEP_USEC); |
| |
| if (get_time().val > timeout.val) |
| return EC_ERROR_TIMEOUT; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Set the write enable latch |
| */ |
| static int spi_flash_write_enable(void) |
| { |
| uint8_t cmd = SPI_FLASH_WRITE_ENABLE; |
| return spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, NULL, 0); |
| } |
| |
| /** |
| * Returns the contents of SPI flash status register 1 |
| * @return register contents or 0xff on error |
| */ |
| uint8_t spi_flash_get_status1(void) |
| { |
| uint8_t cmd = SPI_FLASH_READ_SR1; |
| uint8_t resp; |
| |
| if (spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, &resp, 1) != EC_SUCCESS) |
| return 0xff; |
| |
| return resp; |
| } |
| |
| /** |
| * Returns the contents of SPI flash status register 2 |
| * @return register contents or 0xff on error |
| */ |
| uint8_t spi_flash_get_status2(void) |
| { |
| uint8_t cmd = SPI_FLASH_READ_SR2; |
| uint8_t resp; |
| |
| /* Second status register not present */ |
| #ifndef CONFIG_SPI_FLASH_HAS_SR2 |
| return 0; |
| #endif |
| |
| if (spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, &resp, 1) != EC_SUCCESS) |
| return 0xff; |
| |
| return resp; |
| } |
| |
| /** |
| * Sets the SPI flash status registers (non-volatile bits only) |
| * Pass reg2 == -1 to only set reg1. |
| * |
| * @param reg1 Status register 1 |
| * @param reg2 Status register 2 (optional) |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| int spi_flash_set_status(int reg1, int reg2) |
| { |
| uint8_t cmd[3] = {SPI_FLASH_WRITE_SR, reg1, reg2}; |
| int rv = EC_SUCCESS; |
| |
| /* fail if both HW pin is asserted and SRP(s) is 1 */ |
| if (spi_flash_check_wp() != SPI_WP_NONE && |
| (flash_get_protect() & EC_FLASH_PROTECT_GPIO_ASSERTED) != 0) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Enable writing to SPI flash */ |
| rv = spi_flash_write_enable(); |
| if (rv) |
| return rv; |
| |
| /* Second status register not present */ |
| #ifndef CONFIG_SPI_FLASH_HAS_SR2 |
| reg2 = -1; |
| #endif |
| |
| if (reg2 == -1) |
| rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 2, NULL, 0); |
| else |
| rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 3, NULL, 0); |
| if (rv) |
| return rv; |
| |
| /* SRP update takes up to 10 ms, so wait for transaction to finish */ |
| spi_flash_wait(); |
| |
| return rv; |
| } |
| |
| /** |
| * Returns the content of SPI flash |
| * |
| * @param buf_usr Buffer to write flash contents |
| * @param offset Flash offset to start reading from |
| * @param bytes Number of bytes to read. |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| int spi_flash_read(uint8_t *buf_usr, unsigned int offset, unsigned int bytes) |
| { |
| int i, read_size, ret, spi_addr; |
| uint8_t cmd[4]; |
| if (offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| cmd[0] = SPI_FLASH_READ; |
| for (i = 0; i < bytes; i += read_size) { |
| spi_addr = offset + i; |
| cmd[1] = (spi_addr >> 16) & 0xFF; |
| cmd[2] = (spi_addr >> 8) & 0xFF; |
| cmd[3] = spi_addr & 0xFF; |
| read_size = MIN((bytes - i), SPI_FLASH_MAX_READ_SIZE); |
| ret = spi_transaction(SPI_FLASH_DEVICE, |
| cmd, |
| 4, |
| buf_usr + i, |
| read_size); |
| if (ret != EC_SUCCESS) |
| break; |
| msleep(1); |
| } |
| return ret; |
| } |
| |
| /** |
| * Erase a block of SPI flash. |
| * |
| * @param offset Flash offset to start erasing |
| * @param block Block size in kb (4 or 32) |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| static int spi_flash_erase_block(unsigned int offset, unsigned int block) |
| { |
| uint8_t cmd[4]; |
| int rv = EC_SUCCESS; |
| |
| /* Invalid block size */ |
| if (block != 4 && block != 32) |
| return EC_ERROR_INVAL; |
| |
| /* Not block aligned */ |
| if ((offset % (block * 1024)) != 0) |
| return EC_ERROR_INVAL; |
| |
| /* Enable writing to SPI flash */ |
| rv = spi_flash_write_enable(); |
| if (rv) |
| return rv; |
| |
| /* Compose instruction */ |
| cmd[0] = (block == 4) ? SPI_FLASH_ERASE_4KB : SPI_FLASH_ERASE_32KB; |
| cmd[1] = (offset >> 16) & 0xFF; |
| cmd[2] = (offset >> 8) & 0xFF; |
| cmd[3] = offset & 0xFF; |
| |
| rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 4, NULL, 0); |
| if (rv) |
| return rv; |
| |
| /* Wait for previous operation to complete */ |
| return spi_flash_wait(); |
| } |
| |
| /** |
| * Erase SPI flash. |
| * |
| * @param offset Flash offset to start erasing |
| * @param bytes Number of bytes to erase |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| int spi_flash_erase(unsigned int offset, unsigned int bytes) |
| { |
| int rv = EC_SUCCESS; |
| |
| /* Invalid input */ |
| if (offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Not aligned to sector (4kb) */ |
| if (offset % 4096 || bytes % 4096) |
| return EC_ERROR_INVAL; |
| |
| /* Largest unit is block (32kb) */ |
| if (offset % (32 * 1024) == 0) { |
| while (bytes != (bytes % (32 * 1024))) { |
| rv = spi_flash_erase_block(offset, 32); |
| if (rv) |
| return rv; |
| |
| bytes -= 32 * 1024; |
| offset += 32 * 1024; |
| /* |
| * Refresh watchdog since we may be erasing a large |
| * number of blocks. |
| */ |
| watchdog_reload(); |
| } |
| } |
| |
| /* Largest unit is sector (4kb) */ |
| while (bytes != (bytes % (4 * 1024))) { |
| rv = spi_flash_erase_block(offset, 4); |
| if (rv) |
| return rv; |
| |
| bytes -= 4 * 1024; |
| offset += 4 * 1024; |
| } |
| |
| return rv; |
| } |
| |
| /** |
| * Write to SPI flash. Assumes already erased. |
| * Limited to SPI_FLASH_MAX_WRITE_SIZE by chip. |
| * |
| * @param offset Flash offset to write |
| * @param bytes Number of bytes to write |
| * @param data Data to write to flash |
| * |
| * @return EC_SUCCESS, or non-zero if any error. |
| */ |
| int spi_flash_write(unsigned int offset, unsigned int bytes, |
| const uint8_t *data) |
| { |
| int rv, write_size; |
| |
| /* Invalid input */ |
| if (!data || offset + bytes > CONFIG_FLASH_SIZE || |
| bytes > SPI_FLASH_MAX_WRITE_SIZE) |
| return EC_ERROR_INVAL; |
| |
| while (bytes > 0) { |
| watchdog_reload(); |
| /* Write length can not go beyond the end of the flash page */ |
| write_size = MIN(bytes, SPI_FLASH_MAX_WRITE_SIZE - |
| (offset & (SPI_FLASH_MAX_WRITE_SIZE - 1))); |
| |
| /* Wait for previous operation to complete */ |
| rv = spi_flash_wait(); |
| if (rv) |
| return rv; |
| |
| /* Enable writing to SPI flash */ |
| rv = spi_flash_write_enable(); |
| if (rv) |
| return rv; |
| |
| /* Copy data to send buffer; buffers may overlap */ |
| memmove(buf + 4, data, write_size); |
| |
| /* Compose instruction */ |
| buf[0] = SPI_FLASH_PAGE_PRGRM; |
| buf[1] = (offset) >> 16; |
| buf[2] = (offset) >> 8; |
| buf[3] = offset; |
| |
| rv = spi_transaction(SPI_FLASH_DEVICE, |
| buf, 4 + write_size, NULL, 0); |
| if (rv) |
| return rv; |
| |
| data += write_size; |
| offset += write_size; |
| bytes -= write_size; |
| } |
| |
| /* Wait for previous operation to complete */ |
| return spi_flash_wait(); |
| } |
| |
| /** |
| * Gets the SPI flash JEDEC ID (manufacturer ID, memory type, and capacity) |
| * |
| * @param dest Destination buffer; must be 3 bytes long |
| * @return EC_SUCCESS or non-zero on error |
| */ |
| int spi_flash_get_jedec_id(uint8_t *dest) |
| { |
| uint8_t cmd = SPI_FLASH_JEDEC_ID; |
| |
| return spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, dest, 3); |
| } |
| |
| /** |
| * Gets the SPI flash manufacturer and device ID |
| * |
| * @param dest Destination buffer; must be 2 bytes long |
| * @return EC_SUCCESS or non-zero on error |
| */ |
| int spi_flash_get_mfr_dev_id(uint8_t *dest) |
| { |
| uint8_t cmd[4] = {SPI_FLASH_MFR_DEV_ID, 0, 0, 0}; |
| |
| return spi_transaction(SPI_FLASH_DEVICE, cmd, sizeof(cmd), dest, 2); |
| } |
| |
| /** |
| * Gets the SPI flash unique ID (serial) |
| * |
| * @param dest Destination buffer; must be 8 bytes long |
| * @return EC_SUCCESS or non-zero on error |
| */ |
| int spi_flash_get_unique_id(uint8_t *dest) |
| { |
| uint8_t cmd[5] = {SPI_FLASH_UNIQUE_ID, 0, 0, 0, 0}; |
| |
| return spi_transaction(SPI_FLASH_DEVICE, cmd, sizeof(cmd), dest, 8); |
| } |
| |
| /** |
| * Check for SPI flash status register write protection |
| * Cannot sample WP pin, so caller should sample it if necessary, if |
| * SPI_WP_HARDWARE is returned. |
| * |
| * @return enum spi_flash_wp status based on protection |
| */ |
| enum spi_flash_wp spi_flash_check_wp(void) |
| { |
| int sr1_prot = spi_flash_get_status1() & SPI_FLASH_SR1_SRP0; |
| int sr2_prot = spi_flash_get_status2() & SPI_FLASH_SR2_SRP1; |
| |
| if (sr2_prot) |
| return sr1_prot ? SPI_WP_PERMANENT : SPI_WP_POWER_CYCLE; |
| else if (sr1_prot) |
| return SPI_WP_HARDWARE; |
| |
| return SPI_WP_NONE; |
| } |
| |
| /** |
| * Set SPI flash status register write protection |
| * |
| * @param wp Status register write protection mode |
| * |
| * @return EC_SUCCESS for no protection, or non-zero if error. |
| */ |
| int spi_flash_set_wp(enum spi_flash_wp w) |
| { |
| int sr1 = spi_flash_get_status1(); |
| int sr2 = spi_flash_get_status2(); |
| |
| switch (w) { |
| case SPI_WP_NONE: |
| sr1 &= ~SPI_FLASH_SR1_SRP0; |
| sr2 &= ~SPI_FLASH_SR2_SRP1; |
| break; |
| case SPI_WP_HARDWARE: |
| sr1 |= SPI_FLASH_SR1_SRP0; |
| sr2 &= ~SPI_FLASH_SR2_SRP1; |
| break; |
| case SPI_WP_POWER_CYCLE: |
| sr1 &= ~SPI_FLASH_SR1_SRP0; |
| sr2 |= SPI_FLASH_SR2_SRP1; |
| break; |
| case SPI_WP_PERMANENT: |
| sr1 |= SPI_FLASH_SR1_SRP0; |
| sr2 |= SPI_FLASH_SR2_SRP1; |
| break; |
| default: |
| return EC_ERROR_INVAL; |
| } |
| |
| return spi_flash_set_status(sr1, sr2); |
| } |
| |
| /** |
| * Check for SPI flash block write protection |
| * |
| * @param offset Flash block offset to check |
| * @param bytes Flash block length to check |
| * |
| * @return EC_SUCCESS for no protection, or non-zero if error. |
| */ |
| int spi_flash_check_protect(unsigned int offset, unsigned int bytes) |
| { |
| uint8_t sr1 = spi_flash_get_status1(); |
| uint8_t sr2 = spi_flash_get_status2(); |
| unsigned int start; |
| unsigned int len; |
| int rv = EC_SUCCESS; |
| |
| /* Invalid value */ |
| if (sr1 == 0xff || sr2 == 0xff || offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Compute current protect range */ |
| rv = spi_flash_reg_to_protect(sr1, sr2, &start, &len); |
| if (rv) |
| return rv; |
| |
| /* Check if ranges overlap */ |
| if (MAX(start, offset) < MIN(start + len, offset + bytes)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Set SPI flash block write protection |
| * If offset == bytes == 0, remove protection. |
| * |
| * @param offset Flash block offset to protect |
| * @param bytes Flash block length to protect |
| * |
| * @return EC_SUCCESS, or non-zero if error. |
| */ |
| int spi_flash_set_protect(unsigned int offset, unsigned int bytes) |
| { |
| int rv; |
| uint8_t sr1 = spi_flash_get_status1(); |
| uint8_t sr2 = spi_flash_get_status2(); |
| |
| /* Invalid values */ |
| if (sr1 == 0xff || sr2 == 0xff || offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Compute desired protect range */ |
| rv = spi_flash_protect_to_reg(offset, bytes, &sr1, &sr2); |
| if (rv) |
| return rv; |
| |
| return spi_flash_set_status(sr1, sr2); |
| } |
| |
| static int command_spi_flashinfo(int argc, char **argv) |
| { |
| uint8_t jedec[3]; |
| uint8_t unique[8]; |
| int rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| /* Wait for previous operation to complete */ |
| rv = spi_flash_wait(); |
| if (rv) |
| return rv; |
| |
| spi_flash_get_jedec_id(jedec); |
| spi_flash_get_unique_id(unique); |
| |
| ccprintf("Manufacturer ID: %02x\nDevice ID: %02x %02x\n", |
| jedec[0], jedec[1], jedec[2]); |
| ccprintf("Unique ID: %02x %02x %02x %02x %02x %02x %02x %02x\n", |
| unique[0], unique[1], unique[2], unique[3], |
| unique[4], unique[5], unique[6], unique[7]); |
| ccprintf("Capacity: %4d kB\n", SPI_FLASH_SIZE(jedec[2]) / 1024); |
| |
| return rv; |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flashinfo, command_spi_flashinfo, |
| NULL, |
| "Print SPI flash info"); |
| |
| #ifdef CONFIG_HOSTCMD_FLASH_SPI_INFO |
| static enum ec_status flash_command_spi_info(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_flash_spi_info *r = args->response; |
| |
| spi_flash_get_jedec_id(r->jedec); |
| r->reserved0 = 0; |
| spi_flash_get_mfr_dev_id(r->mfr_dev_id); |
| r->sr1 = spi_flash_get_status1(); |
| r->sr2 = spi_flash_get_status2(); |
| |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_FLASH_SPI_INFO, |
| flash_command_spi_info, |
| EC_VER_MASK(0)); |
| #endif /* CONFIG_HOSTCMD_FLASH_SPI_INFO */ |
| |
| #ifdef CONFIG_CMD_SPI_FLASH |
| static int command_spi_flasherase(int argc, char **argv) |
| { |
| int offset = -1; |
| int bytes = 4096; |
| int rv = parse_offset_size(argc, argv, 1, &offset, &bytes); |
| |
| if (rv) |
| return rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| /* Chip has protection */ |
| if (spi_flash_check_protect(offset, bytes)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| ccprintf("Erasing %d bytes at 0x%x...\n", bytes, offset); |
| return spi_flash_erase(offset, bytes); |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flasherase, command_spi_flasherase, |
| "offset [bytes]", |
| "Erase flash"); |
| |
| static int command_spi_flashwrite(int argc, char **argv) |
| { |
| int offset = -1; |
| int bytes = SPI_FLASH_MAX_WRITE_SIZE; |
| int write_len; |
| int rv = EC_SUCCESS; |
| int i; |
| |
| rv = parse_offset_size(argc, argv, 1, &offset, &bytes); |
| if (rv) |
| return rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| /* Chip has protection */ |
| if (spi_flash_check_protect(offset, bytes)) |
| return EC_ERROR_ACCESS_DENIED; |
| |
| /* Fill the data buffer with a pattern */ |
| for (i = 0; i < SPI_FLASH_MAX_WRITE_SIZE; i++) |
| buf[i] = i; |
| |
| ccprintf("Writing %d bytes to 0x%x...\n", bytes, offset); |
| while (bytes > 0) { |
| /* First write multiples of 256, then (bytes % 256) last */ |
| write_len = ((bytes % SPI_FLASH_MAX_WRITE_SIZE) == bytes) ? |
| bytes : SPI_FLASH_MAX_WRITE_SIZE; |
| |
| /* Perform write */ |
| rv = spi_flash_write(offset, write_len, buf); |
| if (rv) |
| return rv; |
| |
| offset += write_len; |
| bytes -= write_len; |
| } |
| |
| ASSERT(bytes == 0); |
| |
| return rv; |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flashwrite, command_spi_flashwrite, |
| "offset [bytes]", |
| "Write pattern to flash"); |
| |
| static int command_spi_flashread(int argc, char **argv) |
| { |
| int i; |
| int offset = -1; |
| int bytes = -1; |
| int read_len; |
| int rv; |
| |
| rv = parse_offset_size(argc, argv, 1, &offset, &bytes); |
| if (rv) |
| return rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| /* Can't read past size of memory */ |
| if (offset + bytes > CONFIG_FLASH_SIZE) |
| return EC_ERROR_INVAL; |
| |
| /* Wait for previous operation to complete */ |
| rv = spi_flash_wait(); |
| if (rv) |
| return rv; |
| |
| ccprintf("Reading %d bytes from 0x%x...\n", bytes, offset); |
| /* Read <= 256 bytes to avoid allocating another buffer */ |
| while (bytes > 0) { |
| watchdog_reload(); |
| |
| /* First read (bytes % 256), then in multiples of 256 */ |
| read_len = (bytes % SPI_FLASH_MAX_READ_SIZE) ? |
| (bytes % SPI_FLASH_MAX_READ_SIZE) : |
| SPI_FLASH_MAX_READ_SIZE; |
| |
| rv = spi_flash_read(buf, offset, read_len); |
| if (rv) |
| return rv; |
| |
| for (i = 0; i < read_len; i++) { |
| if (i % 16 == 0) |
| ccprintf("%02x:", offset + i); |
| |
| ccprintf(" %02x", buf[i]); |
| |
| if (i % 16 == 15 || i == read_len - 1) |
| ccputs("\n"); |
| } |
| |
| offset += read_len; |
| bytes -= read_len; |
| } |
| |
| ASSERT(bytes == 0); |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flashread, command_spi_flashread, |
| "offset bytes", |
| "Read flash"); |
| |
| static int command_spi_flashread_sr(int argc, char **argv) |
| { |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| ccprintf("Status Register 1: 0x%02x\n", spi_flash_get_status1()); |
| ccprintf("Status Register 2: 0x%02x\n", spi_flash_get_status2()); |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flash_rsr, command_spi_flashread_sr, |
| NULL, |
| "Read status registers"); |
| |
| static int command_spi_flashwrite_sr(int argc, char **argv) |
| { |
| int val1 = 0; |
| int val2 = 0; |
| int rv = parse_offset_size(argc, argv, 1, &val1, &val2); |
| |
| if (rv) |
| return rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| ccprintf("Writing 0x%02x to status register 1, ", val1); |
| ccprintf("0x%02x to status register 2...\n", val2); |
| return spi_flash_set_status(val1, val2); |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flash_wsr, command_spi_flashwrite_sr, |
| "value1 value2", |
| "Write to status registers"); |
| |
| static int command_spi_flashprotect(int argc, char **argv) |
| { |
| int val1 = 0; |
| int val2 = 0; |
| int rv = parse_offset_size(argc, argv, 1, &val1, &val2); |
| |
| if (rv) |
| return rv; |
| |
| spi_enable(CONFIG_SPI_FLASH_PORT, 1); |
| |
| ccprintf("Setting protection for 0x%06x to 0x%06x\n", val1, val1+val2); |
| return spi_flash_set_protect(val1, val2); |
| } |
| DECLARE_CONSOLE_COMMAND(spi_flash_prot, command_spi_flashprotect, |
| "offset len", |
| "Set block protection"); |
| #endif |