Nikolai Artemiev has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/47641 )
Change subject: WIP: wpce775x.c: import file from cros flashrom ......................................................................
WIP: wpce775x.c: import file from cros flashrom
Change-Id: I10ef0ae65a8f2bf5e3abc4f80be31dd70c7d31b4 Signed-off-by: Nikolai Artemiev nartemiev@google.com --- M Makefile M programmer.h A wpce775x.c 3 files changed, 1,009 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/41/47641/1
diff --git a/Makefile b/Makefile index 0498624..d2d8152 100644 --- a/Makefile +++ b/Makefile @@ -648,7 +648,7 @@ CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o edi.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o + spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o wpce775x.o
############################################################################### # Library code. diff --git a/programmer.h b/programmer.h index 1bd0d37..9916605 100644 --- a/programmer.h +++ b/programmer.h @@ -787,6 +787,9 @@ int serialport_read(unsigned char *buf, unsigned int readcnt); int serialport_read_nonblock(unsigned char *c, unsigned int readcnt, unsigned int timeout, unsigned int *really_read);
+/* wpce775x.c */ +int wpce775x_probe_spi_flash(const char *name); + /* Serial port/pin mapping:
1 CD <- diff --git a/wpce775x.c b/wpce775x.c new file mode 100644 index 0000000..7f3b56e --- /dev/null +++ b/wpce775x.c @@ -0,0 +1,1005 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of Nuvoton Technology Corporation. or the names of + * contributors or licensors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. + * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, + * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. + * NUVOTON TECHNOLOGY CORPORATION. ("NUVOTON") AND ITS LICENSORS SHALL NOT BE LIABLE + * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING + * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL + * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, + * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR + * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF + * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * This is an UNOFFICIAL patch for the Nuvoton WPCE775x/NPCE781x. It was tested + * for a specific hardware and firmware configuration and should be considered + * unreliable. Please see the following URL for Nuvoton's authoritative, + * officially supported flash update utility: + * http://sourceforge.net/projects/nuvflashupdate/ + */ + +#if defined(__i386__) || defined(__x86_64__) +#include <assert.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include "flash.h" +#include "chipdrivers.h" +#include "flashchips.h" +#include "programmer.h" +#include "spi.h" +#include "writeprotect.h" + +/** + * Definition of WPCE775X WCB (Write Command Buffer), as known as Shared Access + * Window 2. + * + * The document name is "WPCE775X Software User Guide Revision 1.2". + * + * Assume the host is little endian. + */ +struct __attribute__((packed)) wpce775x_wcb { + /* Byte 0: semaphore byte */ + uint8_t exe:1; /* Bit0-RW- set by host. means wcb is ready to execute. + should be cleared by host after RDY=1. */ + uint8_t resv0_41:4; + uint8_t pcp:1; /* Bit5-RO- set by EPCE775x. means preparation operations for + flash update process is complete. */ + uint8_t err:1; /* Bit6-RO- set by EPCE775x. means an error occurs. */ + uint8_t rdy:1; /* Bit7-RO- set by EPCE775x. means operation is completed. */ + + /* Byte 1-2: reserved */ + uint8_t byte1; + uint8_t byte2; + + /* Byte 3: command code */ + uint8_t code; + + /* Byte 4-15: command field */ + uint8_t field[12]; +}; + +/* The physical address of WCB -- Shared Access Window 2. */ +static chipaddr wcb_physical_address; + +/* The virtual address of WCB -- Shared Access Window 2. */ +static volatile struct wpce775x_wcb *volatile wcb; + +/* count of entering flash update mode */ +static int in_flash_update_mode; + +static int firmware_changed; + +/* + * Bytes 0x4-0xf of init_flash command. These represent opcodes and various + * parameters the WPCE775x will use when communicating with the SPI flash + * device. DO NOT RE-ORDER THIS STRUCTURE. + */ +struct wpce775x_initflash_cfg { + uint8_t read_device_id; /* Byte 0x04. Ex: JEDEC_RDID */ + uint8_t write_status_enable; /* Byte 0x05. Ex: JEDEC_EWSR */ + uint8_t write_enable; /* Byte 0x06. Ex: JEDEC_WREN */ + uint8_t read_status_register; /* Byte 0x07. Ex: JEDEC_RDSR */ + uint8_t write_status_register; /* Byte 0x08. Ex: JEDEC_WRSR */ + uint8_t flash_program; /* Byte 0x09. Ex: JEDEC_BYTE_PROGRAM */ + + /* Byte 0x0A. Ex: sector/block/chip erase opcode */ + uint8_t block_erase; + + uint8_t status_busy_mask; /* Byte B: bit position of BUSY bit */ + + /* Byte 0x0C: value to remove write protection */ + uint8_t status_reg_value; + + /* Byte 0x0D: Number of bytes to program in each write transaction. */ + uint8_t program_unit_size; + + uint8_t page_size; /* Byte 0x0E: 2^n bytes */ + + /* + * Byte 0x0F: Method to read device ID. 0x47 will cause ID bytes to be + * read immediately after read_device_id command is issued. Otherwise, + * 3 dummy address bytes are sent after the read_device_id code. + */ + uint8_t read_device_id_type; +} __attribute__((packed)); + +/* + * The WPCE775x can use init_flash multiple times during an update. We'll use + * this ability primarily for changing write protection bits. + */ +static struct wpce775x_initflash_cfg *initflash_cfg; + +/* SuperI/O related definitions and functions. */ +/* Strapping options */ +#define NUVOTON_SIO_PORT1 0x2e /* No pull-down resistor */ +#define NUVOTON_SIO_PORT2 0x164e /* Pull-down resistor on BADDR0 */ +/* Note: There's another funky state that we won't worry about right now */ + +/* SuperI/O Config */ +#define NUVOTON_SIOCFG_LDN 0x07 /* LDN Bank Selector */ +#define NUVOTON_SIOCFG_SID 0x20 /* SuperI/O ID */ +#define NUVOTON_SIOCFG_SRID 0x27 /* SuperI/O Revision ID */ +#define NUVOTON_LDN_SHM 0x0f /* LDN of SHM module */ + +/* WPCE775x shared memory config registers (LDN 0x0f) */ +#define WPCE775X_SHM_BASE_MSB 0x60 +#define WPCE775X_SHM_BASE_LSB 0x61 +#define WPCE775X_SHM_CFG 0xf0 +#define WPCE775X_SHM_CFG_BIOS_FWH_EN (1 << 3) +#define WPCE775X_SHM_CFG_FLASH_ACC_EN (1 << 2) +#define WPCE775X_SHM_CFG_BIOS_EXT_EN (1 << 1) +#define WPCE775X_SHM_CFG_BIOS_LPC_EN (1 << 0) +#define WPCE775X_WIN_CFG 0xf1 /* window config */ +#define WPCE775X_WIN_CFG_SHWIN_ACC (1 << 6) + +/* Shared access window 2 bar address registers */ +#define WPCE775X_SHAW2BA_0 0xf8 +#define WPCE775X_SHAW2BA_1 0xf9 +#define WPCE775X_SHAW2BA_2 0xfa +#define WPCE775X_SHAW2BA_3 0xfb + +/* Read/write buffer size */ +#define WPCE775X_MAX_WRITE_SIZE 8 +#define WPCE775X_MAX_READ_SIZE 12 + +/** probe for super i/o index + * @returns 0 to indicate success, <0 to indicate error + */ +static int nuvoton_get_sio_index(uint16_t *port) +{ + uint16_t ports[] = { NUVOTON_SIO_PORT2, + NUVOTON_SIO_PORT1, + }; + static uint16_t port_internal, port_found = 0; + + if (port_found) { + *port = port_internal; + return 0; + } + + if (rget_io_perms()) + return 1; + + for (size_t i = 0; i < ARRAY_SIZE(ports); i++) { + uint8_t sid = sio_read(ports[i], NUVOTON_SIOCFG_SID); + + if (sid == 0xfc) { /* Family ID */ + port_internal = ports[i]; + port_found = 1; + break; + } + } + + if (!port_found) { + msg_cdbg("\nfailed to obtain super i/o index\n"); + return -1; + } + + msg_cdbg("\nsuper i/o index = 0x%04x\n", port_internal); + *port = port_internal; + return 0; +} + +/** Call superio to get pre-configured WCB address. + * Read LDN 0x0f (SHM) idx:f8-fb (little-endian). + */ +static int get_shaw2ba(chipaddr *shaw2ba) +{ + uint16_t idx; + uint8_t org_ldn; + uint8_t win_cfg; + uint8_t shm_cfg; + + if (nuvoton_get_sio_index(&idx) < 0) + return -1; + + org_ldn = sio_read(idx, NUVOTON_SIOCFG_LDN); + sio_write(idx, NUVOTON_SIOCFG_LDN, NUVOTON_LDN_SHM); + + /* + * To obtain shared access window 2 base address, we must OR the base + * address bytes, where SHAW2BA_0 is least significant and SHAW2BA_3 + * most significant. + */ + *shaw2ba = sio_read(idx, WPCE775X_SHAW2BA_0) | + ((chipaddr)sio_read(idx, WPCE775X_SHAW2BA_1) << 8) | + ((chipaddr)sio_read(idx, WPCE775X_SHAW2BA_2) << 16) | + ((chipaddr)sio_read(idx, WPCE775X_SHAW2BA_3) << 24); + + /* + * If SHWIN_ACC is cleared, then we're using LPC memory access + * and SHAW2BA_3-0 indicate bits 31-0. If SHWIN_ACC is set, then + * bits 7-4 of SHAW2BA_3 are ignored and bits 31-28 are indicated + * by the idsel nibble. (See table 25 "supported host address ranges" + * for more details) + */ + win_cfg = sio_read(idx, WPCE775X_WIN_CFG); + if (win_cfg & WPCE775X_WIN_CFG_SHWIN_ACC) { + uint8_t idsel; + + /* Make sure shared BIOS memory is enabled */ + shm_cfg = sio_read(idx, WPCE775X_SHM_CFG); + if ((shm_cfg & WPCE775X_SHM_CFG_BIOS_FWH_EN)) + idsel = 0xf; + else { + msg_cdbg("Shared BIOS memory is diabled.\n"); + msg_cdbg("Please check SHM_CFG:BIOS_FWH_EN.\n"); + goto error; + } + + *shaw2ba &= 0x0fffffff; + *shaw2ba |= (chipaddr)idsel << 28; + } + + sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); + return 0; +error: + sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); + return -1; +} + +/* Call superio to get pre-configured fwh_id. + * Read LDN 0x0f (SHM) idx:f0. + */ +static int get_fwh_id(uint8_t *fwh_id) +{ + uint16_t idx; + uint8_t org_ldn; + + if (nuvoton_get_sio_index(&idx) < 0) + return -1; + + org_ldn = sio_read(idx, NUVOTON_SIOCFG_LDN); + sio_write(idx, NUVOTON_SIOCFG_LDN, NUVOTON_LDN_SHM); + *fwh_id = sio_read(idx, WPCE775X_SHM_CFG); + sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); + + return 0; +} + +/** helper function to make sure the exe bit is 0 (no one is using EC). + * @return 1 for error; 0 for success. + */ +static int assert_ec_is_free(void) +{ + if (wcb->exe) + msg_perr("ASSERT(wcb->exe==0), entering busy loop.\n"); + while(wcb->exe); + return 0; +} + +/** Trigger EXE bit, and block until operation completes. + * @return 1 for error; and 0 for success. + */ +static int blocked_exec(void) +{ + struct timeval begin, now; + int timeout; /* not zero if timeout occurs */ + int err; + + assert(wcb->rdy==0); + + /* raise EXE bit, and wait for operation complete or error occur. */ + wcb->exe = 1; + + timeout = 0; + gettimeofday(&begin, NULL); + while(wcb->rdy==0 && wcb->err==0) { + gettimeofday(&now, NULL); + /* According to Nuvoton's suggestion, few seconds is enough for + * longest flash operation, which is erase. + * Cutted from W25X16 datasheet, for max operation time + * Byte program tBP1 50us + * Page program tPP 3ms + * Sector Erase (4KB) tSE 200ms + * Block Erase (64KB) tBE 1s + * Chip Erase tCE 20s + * Since WPCE775x doesn't support chip erase, + * 3 secs is long enough for block erase. + */ + if ((now.tv_sec - begin.tv_sec) >= 4) { + timeout += 1; + break; + } + } + + /* keep ERR bit before clearing EXE bit. */ + err = wcb->err; + + /* Clear EXE bit, and wait for RDY back to 0. */ + wcb->exe = 0; + gettimeofday(&begin, NULL); + while(wcb->rdy) { + gettimeofday(&now, NULL); + /* 1 sec should be long enough for clearing rdy bit. */ + if (((now.tv_sec - begin.tv_sec)*1000*1000 + + (now.tv_usec - begin.tv_usec)) >= 1000*1000) { + timeout += 1; + break; + } + } + + if (err || timeout) { + msg_cdbg("err=%d timeout=%d\n", err, timeout); + return 1; + } + return 0; +} + +/** Initialize the EC parameters. + + * @return 1 for error; 0 for success. + */ +static int init_flash() +{ + if (!initflash_cfg) { + msg_perr("%s(): init_flash config is not defined\n", __func__); + return 1; + } + + assert_ec_is_free(); + /* Byte 3: command code: Init Flash */ + wcb->code = 0x5A; + msg_pdbg("%s(): init_flash bytes: ", __func__); + for (size_t i = 0; i < sizeof(struct wpce775x_initflash_cfg); i++) { + wcb->field[i] = *((uint8_t *)initflash_cfg + i); + msg_pdbg("%02x ", wcb->field[i]); + } + msg_pdbg("\n"); + + if (blocked_exec()) + return 1; + return 0; +} + +/* log2() could be used if we link with -lm */ +static int logbase2(int x) +{ + int log = 0; + + /* naive way */ + while (x) { + x >>= 1; + log++; + } + return log; +} + +/* initialize initflash_cfg struct */ +static int initflash_cfg_setup(const struct flashctx *flash) +{ + if (!initflash_cfg) + initflash_cfg = malloc(sizeof(*initflash_cfg)); + + /* Set "sane" defaults. If the flash chip is known, then use parameters + from it. */ + initflash_cfg->read_device_id = JEDEC_RDID; + if (flash && (flash->chip->feature_bits | FEATURE_WRSR_WREN)) + initflash_cfg->write_status_enable = JEDEC_WREN; + else if (flash && (flash->chip->feature_bits | FEATURE_WRSR_EWSR)) + initflash_cfg->write_status_enable = JEDEC_EWSR; + else + initflash_cfg->write_status_enable = JEDEC_WREN; + initflash_cfg->write_enable = JEDEC_WREN; + initflash_cfg->read_status_register = JEDEC_RDSR; + initflash_cfg->write_status_register = JEDEC_WRSR; + initflash_cfg->flash_program = JEDEC_BYTE_PROGRAM; + + /* note: these members are likely to be overridden later */ + initflash_cfg->block_erase = JEDEC_SE; + initflash_cfg->status_busy_mask = 0x01; + initflash_cfg->status_reg_value = 0x00; + + /* back to "sane" defaults... */ + initflash_cfg->program_unit_size = 0x01; + if (flash) + initflash_cfg->page_size = logbase2(flash->chip->page_size); + else + initflash_cfg->page_size = 0x08; + + initflash_cfg->read_device_id_type = 0x00; + + return 0; +} + +/** Read flash vendor/device IDs through EC. + * @param id0, id1, id2, id3 Pointers to store detected IDs. NULL will be ignored. + * @return 1 for error; 0 for success. + */ +static int read_id(const struct flashctx *flash, + uint8_t* id0, uint8_t* id1, + uint8_t* id2, uint8_t* id3) +{ + if (!initflash_cfg) { + initflash_cfg_setup(flash); + init_flash(); + } + + assert_ec_is_free(); + + wcb->code = 0xC0; /* Byte 3: command code: Read ID */ + if (blocked_exec()) + return 1; + + msg_cdbg("id0: 0x%2x, id1: 0x%2x, id2: 0x%2x, id3: 0x%2x\n", + wcb->field[0], wcb->field[1], wcb->field[2], wcb->field[3]); + if (id0) { + *id0 = wcb->field[0]; + } + if (id1) { + *id1 = wcb->field[1]; + } + if (id2) { + *id2 = wcb->field[2]; + } + if (id3) { + *id3 = wcb->field[3]; + } + + return 0; +} + +/** Tell EC to "enter flash update" mode. */ +static int enter_flash_update() +{ + if (in_flash_update_mode) { + /* already in update mode */ + msg_pdbg("%s: in_flash_update_mode: %d\n", + __func__, in_flash_update_mode); + return 0; + } + assert_ec_is_free(); + + wcb->code = 0x10; /* Enter Flash Update */ + wcb->field[0] = 0x55; /* required pattern by EC */ + wcb->field[1] = 0xAA; /* required pattern by EC */ + wcb->field[2] = 0xCD; /* required pattern by EC */ + wcb->field[3] = 0xBE; /* required pattern by EC */ + if (blocked_exec()) { + return 1; + } else { + in_flash_update_mode = 1; + return 0; + } +} + +/** Tell EC to "exit flash update" mode. + * Without calling this function, the EC stays in busy-loop and will not + * response further request from host, which means system will halt. + */ +static int exit_flash_update(uint8_t exit_code) +{ + /* + * Note: exit_flash_update must be called before shutting down the + * machine, otherwise the EC will be stuck in update mode, leaving + * the machine in a "wedged" state until power cycled. + */ + if (!in_flash_update_mode) { + msg_cdbg("Not in flash update mode yet.\n"); + return 1; + } + + wcb->code = exit_code; /* Exit Flash Update */ + if (blocked_exec()) { + return 1; + } + + in_flash_update_mode = 0; + return 0; +} + +/* + * Note: The EC firmware this patch has been tested with uses the following + * codes to indicate flash update status: + * 0x20 is used for EC F/W no change, but BIOS changed (in Share mode) + * 0x21 is used for EC F/W changed. Goto EC F/W, wait system reboot. + * 0x22 is used for EC F/W changed, Goto EC Watchdog reset. */ +static int exit_flash_update_firmware_no_change(void) { + return exit_flash_update(0x20); +} + +static int exit_flash_update_firmware_changed(void) { + return exit_flash_update(0x21); +} + +static int wpce775x_read(int addr, uint8_t *buf, unsigned int nbytes) +{ + unsigned int bytes_read = 0; + + assert_ec_is_free(); + msg_pspew("%s: reading %d bytes at 0x%06x\n", __func__, nbytes, addr); + + /* Set initial address; WPCE775x auto-increments address for successive + read and write operations. */ + wcb->code = 0xA0; + wcb->field[0] = addr & 0xff; + wcb->field[1] = (addr >> 8) & 0xff; + wcb->field[2] = (addr >> 16) & 0xff; + wcb->field[3] = (addr >> 24) & 0xff; + if (blocked_exec()) { + return 1; + } + + for (unsigned int offset = 0; offset < nbytes; offset += bytes_read) { + unsigned int bytes_left; + + bytes_left = nbytes - offset; + if (bytes_left > 0 && bytes_left < WPCE775X_MAX_READ_SIZE) + bytes_read = bytes_left; + else + bytes_read = WPCE775X_MAX_READ_SIZE; + wcb->code = 0xD0 | bytes_read; + if (blocked_exec()) { + return 1; + } + + for (size_t i = 0; i < bytes_read; i++) + buf[offset + i] = wcb->field[i]; + } + + return 0; +} + +static int wpce775x_erase_new(const struct flashctx *flash, int blockaddr, uint8_t opcode) { + unsigned int blocksize; + int ret = 0; + + assert_ec_is_free(); + + /* + * FIXME: In the long-run we should examine block_erasers within the + * flash struct to ensure the proper blocksize is used. This is because + * some chips implement commands differently. For now, we'll support + * only a few "safe" block erase commands with predictable block size. + * + * Looking thru the list of flashchips, it seems JEDEC_BE_52 and + * JEDEC_BE_D8 are not uniformly implemented. Thus, we cannot safely + * assume a blocksize. + * + * Also, I was unable to test chip erase (due to equipment and time + * constraints), but they might work. + */ + switch(opcode) { + case JEDEC_SE: + case JEDEC_BE_D7: + blocksize = 4 * 1024; + break; + case JEDEC_BE_52: + case JEDEC_BE_D8: + case JEDEC_CE_60: + case JEDEC_CE_C7: + default: + msg_perr("%s(): erase opcode=0x%02x not supported\n", + __func__, opcode); + return 1; + } + + msg_pspew("%s(): blockaddr=%d, blocksize=%d, opcode=0x%02x\n", + __func__, blockaddr, blocksize, opcode); + + if (!initflash_cfg) + initflash_cfg_setup(flash); + initflash_cfg->block_erase = opcode; + init_flash(); + + /* Set Write Window on flash chip (optional). + * You may limit the window to partial flash for experimental. */ + wcb->code = 0xC5; /* Set Write Window */ + wcb->field[0] = 0x00; /* window base: little-endian */ + wcb->field[1] = 0x00; + wcb->field[2] = 0x00; + wcb->field[3] = 0x00; + wcb->field[4] = 0x00; /* window length: little-endian */ + wcb->field[5] = 0x00; + wcb->field[6] = 0x20; + wcb->field[7] = 0x00; + if (blocked_exec()) + return 1; + + msg_pspew("Erasing ... 0x%08x 0x%08x\n", blockaddr, blocksize); + + for (unsigned int current = 0; + current < blocksize; + current += blocksize) { + wcb->code = 0x80; /* Sector/block erase */ + + /* WARNING: assume the block address for EC is always little-endian. */ + unsigned int addr = blockaddr + current; + wcb->field[0] = addr & 0xff; + wcb->field[1] = (addr >> 8) & 0xff; + wcb->field[2] = (addr >> 16) & 0xff; + wcb->field[3] = (addr >> 24) & 0xff; + if (blocked_exec()) { + ret = 1; + goto wpce775x_erase_new_exit; + } + } + +wpce775x_erase_new_exit: + firmware_changed = 1; + return ret; +} + +static int wpce775x_nbyte_program(int addr, const uint8_t *buf, + unsigned int nbytes) +{ + int ret = 0; + unsigned int written = 0; + + assert_ec_is_free(); + msg_pspew("%s: writing %d bytes to 0x%06x\n", __func__, nbytes, addr); + + /* Set initial address; WPCE775x auto-increments address for successive + read and write operations. */ + wcb->code = 0xA0; + wcb->field[0] = addr & 0xff; + wcb->field[1] = (addr >> 8) & 0xff; + wcb->field[2] = (addr >> 16) & 0xff; + wcb->field[3] = (addr >> 24) & 0xff; + if (blocked_exec()) { + return 1; + } + + for (unsigned int offset = 0; offset < nbytes; offset += written) { + unsigned int bytes_left; + + bytes_left = nbytes - offset; + if (bytes_left > 0 && bytes_left < WPCE775X_MAX_WRITE_SIZE) + written = bytes_left; + else + written = WPCE775X_MAX_WRITE_SIZE; + wcb->code = 0xB0 | written; + + for (size_t i = 0; i < written; i++) + wcb->field[i] = buf[offset + i]; + if (blocked_exec()) { + ret = 1; + goto wpce775x_nbyte_program_exit; + } + } + +wpce775x_nbyte_program_exit: + firmware_changed = 1; + return ret; +} + +static int wpce775x_spi_read(struct flashctx *flash, uint8_t * buf, + unsigned int start, unsigned int len) +{ + if (!initflash_cfg) { + initflash_cfg_setup(flash); + init_flash(); + } + return spi_read_chunked(flash, buf, start, len, + flash->chip->page_size); +} + +static int wpce775x_spi_write_256(struct flashctx *flash, const uint8_t *buf, + unsigned int start, unsigned int len) +{ + if (!initflash_cfg) { + initflash_cfg_setup(flash); + init_flash(); + } + return spi_write_chunked(flash, buf, start, len, + flash->chip->page_size); +} + +static int wpce775x_spi_read_status_register(unsigned int readcnt, uint8_t *readarr) +{ + uint8_t before[2]; + int ret = 0; + + assert_ec_is_free(); + msg_pdbg("%s(): reading status register.\n", __func__); + + /* We need to detect if EC firmware support this 0x30 command. + * 1. write a non-sense value to field[0] and field[1]. + * 2. execute command 0x30. + * 3. if field[0] is NOT changed, that means 0x30 is not supported, + * use initflash_cfg->status_reg_value instead. + * else, 0x30 works and returns field[]. + */ + wcb->field[0] = ~initflash_cfg->status_reg_value; + wcb->field[1] = 0xfc; /* set reserved bits to 1s */ + /* save original values */ + for (size_t i = 0; i < ARRAY_SIZE(before); i++) + before[i] = wcb->field[i]; + + wcb->code = 0x30; /* JEDEC_RDSR */ + if (blocked_exec()) { + ret = 1; + msg_perr("%s(): blocked_exec() returns error.\n", __func__); + } + msg_pdbg("%s(): WCB code=0x%02x field[]= ", __func__, wcb->code); + for (size_t i = 0; i < readcnt; ++i) { + readarr[i] = wcb->field[i]; + msg_pdbg("%02x ", wcb->field[i]); + } + msg_pdbg("\n"); + + /* FIXME: not sure EC returns 1 or 2 bytes for command 0x30, + * now we check field[0] only. + * Shall check field[1] if EC always returns 2 bytes. */ + if (wcb->field[0] == before[0]) { + /* field is not changed, 0x30 command doesn't work. */ + readarr[0] = initflash_cfg->status_reg_value; + readarr[1] = 0; /* TODO: if second status register exists */ + msg_pdbg("%s(): command 0x30 seems NOT working.\n", __func__); + } else { + /* 0x30 command seems working! */ + initflash_cfg->status_reg_value = readarr[0]; + msg_pdbg("%s(): command 0x30 seems working.\n", __func__); + } + + return ret; +} + +static int wpce775x_spi_write_status_register(const struct flashctx *flash, uint8_t val) +{ + assert_ec_is_free(); + msg_pdbg("%s(): writing 0x%02x to status register\n", __func__, val); + + if (!initflash_cfg) + initflash_cfg_setup(flash); + + initflash_cfg->status_reg_value = val; + if (in_flash_update_mode) { + exit_flash_update_firmware_no_change(); + in_flash_update_mode = 0; + } + if (init_flash()) + return 1; + if (enter_flash_update()) + return 1; + return 0; +} + +/* + * WPCE775x does not allow direct access to SPI chip from host. This function + * will translate SPI commands to valid WPCE775x WCB commands. + */ +static int wpce775x_spi_send_command(const struct flashctx *flash, + unsigned int writecnt, + unsigned int readcnt, + const uint8_t *writearr, + uint8_t *readarr) +{ + int rc = 0; + uint8_t opcode = writearr[0]; + + switch(opcode){ + case JEDEC_RDID:{ + uint8_t dummy = 0; + if (readcnt == 3) + read_id(flash, &readarr[0], &readarr[1], &readarr[2], &dummy); + else if (readcnt == 4) + read_id(flash, &readarr[0], &readarr[1], &readarr[2], &readarr[3]); + break; + } + case JEDEC_RDSR: + rc = wpce775x_spi_read_status_register(readcnt, readarr); + break; + case JEDEC_READ:{ + int blockaddr = ((int)writearr[1] << 16) | + ((int)writearr[2] << 8) | + writearr[3]; + rc = wpce775x_read(blockaddr, readarr, readcnt); + break; + } + case JEDEC_WRSR: + wpce775x_spi_write_status_register(flash, writearr[1]); + rc = 0; + break; + case JEDEC_WREN: + case JEDEC_EWSR: + /* Handled by init_flash() */ + rc = 0; + break; + case JEDEC_SE: + case JEDEC_BE_52: + case JEDEC_BE_D7: + case JEDEC_BE_D8: + case JEDEC_CE_60: + case JEDEC_CE_C7:{ + int blockaddr = ((int)writearr[1] << 16) | + ((int)writearr[2] << 8) | + writearr[3]; + + rc = wpce775x_erase_new(flash, blockaddr, opcode); + break; + } + case JEDEC_BYTE_PROGRAM:{ + int blockaddr = ((int)writearr[1] << 16) | + ((int)writearr[2] << 8) | + writearr[3]; + int nbytes = writecnt - 4; + + rc = wpce775x_nbyte_program(blockaddr, &writearr[4], nbytes); + break; + } + case JEDEC_REMS: + case JEDEC_RES: + case JEDEC_WRDI: + case JEDEC_AAI_WORD_PROGRAM: + default: + /* unsupported opcodes */ + msg_pdbg("unsupported SPI opcode: %02x\n", opcode); + rc = 1; + break; + } + + msg_pdbg("%s: opcode: 0x%02x\n", __func__, opcode); + return rc; +} + +static int wpce775x_shutdown(void *data) +{ + msg_pdbg("%s(): firmware %s\n", __func__, + firmware_changed ? "changed" : "not changed"); + + msg_pdbg("%s: in_flash_update_mode: %d\n", __func__, in_flash_update_mode); + if (in_flash_update_mode) { + if (firmware_changed) + exit_flash_update_firmware_changed(); + else + exit_flash_update_firmware_no_change(); + + in_flash_update_mode = 0; + } + + if (initflash_cfg) + free(initflash_cfg); + else + msg_perr("%s(): No initflash_cfg to free?!?\n", __func__); + + return 0; +} + +static const struct spi_master spi_master_wpce775x = { + .max_data_read = 256, /* FIXME: should be MAX_DATA_READ_UNLIMITED? */ + .max_data_write = 256, /* FIXME: should be MAX_DATA_WRITE_UNLIMITED? */ + .command = wpce775x_spi_send_command, + .multicommand = default_spi_send_multicommand, + .read = wpce775x_spi_read, + .write_256 = wpce775x_spi_write_256, +}; + +static int wpce775x_spi_common_init(void) +{ + uint8_t fwh_id; + + msg_pdbg("%s(): entered\n", __func__); + + /* + * FIXME: This is necessary to ensure that access to the shared access + * window region is sent on the LPC bus. The old CLI syntax + * (-p internal:bus=lpc) would cause the chipset enable code to set the + * target bus appropriately before this function gets run, but the new + * syntax ("-p ec") does not cause that to happen. + */ + // WIP: need to change this! + // target_bus = BUS_LPC; + // msg_pdbg("%s: forcing target bus: 0x%08x\n", __func__, target_bus); + chipset_flash_enable(); + + /* get the address of Shadow Window 2. */ + if (get_shaw2ba(&wcb_physical_address) < 0) { + msg_pdbg("Cannot get the address of Shadow Window 2"); + return 1; + } + msg_pdbg("Get the address of WCB(SHA WIN2) at 0x%08lx\n", + wcb_physical_address); + wcb = (struct wpce775x_wcb *) + programmer_map_flash_region("WPCE775X WCB", + wcb_physical_address, + getpagesize() /* min page size */); + msg_pdbg("mapped wcb address: %p for physical addr: 0x%08lx\n", wcb, wcb_physical_address); + if (!wcb) { + msg_perr("FATAL! Cannot map memory area for wcb physical address.\n"); + return 1; + } + memset((void*)wcb, 0, sizeof(*wcb)); + + if (get_fwh_id(&fwh_id) < 0) { + msg_pdbg("Cannot get fwh_id value.\n"); + return 1; + } + msg_pdbg("get fwh_id: 0x%02x\n", fwh_id); + + /* TODO: set fwh_idsel of chipset. + Currently, we employ "-p internal:fwh_idsel=0x0000223e". */ + + if (register_shutdown(wpce775x_shutdown, NULL)) + return 1; + + /* Enter flash update mode unconditionally. This is required even + for reading. */ + if (enter_flash_update()) return 1; + + /* Add FWH | LPC to list of buses supported if they are not + * both there already. */ + // WIP: need to change this! + // buses_supported |= BUS_FWH | BUS_LPC; + register_spi_master(&spi_master_wpce775x); + msg_pdbg("%s(): successfully initialized wpce775x\n", __func__); + return 0; +} + +static int wpce775x_probe_superio() +{ + uint16_t sio_port; + uint8_t srid; + + /* detect if wpce775x exists */ + if (nuvoton_get_sio_index(&sio_port) < 0) { + msg_pdbg("No Nuvoton chip is found.\n"); + return 1; + } + srid = sio_read(sio_port, NUVOTON_SIOCFG_SRID); + if ((srid & 0xE0) == 0xA0) { + msg_pdbg("Found EC: WPCE775x " + "(Vendor:0x%02x,ID:0x%02x,Rev:0x%02x) on sio_port:0x%x.\n", + sio_read(sio_port, NUVOTON_SIOCFG_SID), + srid >> 5, srid & 0x1f, sio_port); + } else { + msg_pdbg("Found EC: Nuvoton " + "(Vendor:0x%02x,ID:0x%02x,Rev:0x%02x) on sio_port:0x%x.\n", + sio_read(sio_port, NUVOTON_SIOCFG_SID), + srid >> 5, srid & 0x1f, sio_port); + } + + return 0; +} + +/* Called by internal_init() */ +int wpce775x_probe_spi_flash(const char *name) +{ + int ret = 0; + char *p = NULL; + + p = extract_programmer_param("type"); + if (p && strcmp(p, "ec")) { + msg_pdbg("mec1308 only supports "ec" type devices\n"); + ret = 1; + goto wpce775x_probe_spi_flash_exit; + } + + if (wpce775x_probe_superio()) { + ret = 1; + goto wpce775x_probe_spi_flash_exit; + } + + if (wpce775x_spi_common_init()) { + ret = 1; + goto wpce775x_probe_spi_flash_exit; + } + +wpce775x_probe_spi_flash_exit: + free(p); + return ret; +} +#endif /* __i386__ || __x86_64__ */
Nikolai Artemiev has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/47641 )
Change subject: WIP: wpce775x.c: import file from cros flashrom ......................................................................
Patch Set 1:
(2 comments)
https://review.coreboot.org/c/flashrom/+/47641/1/wpce775x.c File wpce775x.c:
https://review.coreboot.org/c/flashrom/+/47641/1/wpce775x.c@904 PS1, Line 904: // WIP: need to change this! : // target_bus = BUS_LPC; : // msg_pdbg("%s: forcing target bus: 0x%08x\n", __func__, target_bus); WIP
https://review.coreboot.org/c/flashrom/+/47641/1/wpce775x.c@945 PS1, Line 945: // WIP: need to change this! : // buses_supported |= BUS_FWH | BUS_LPC; WIP
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/47641 )
Change subject: WIP: wpce775x.c: import file from cros flashrom ......................................................................
Patch Set 1: Code-Review-2
(1 comment)
https://review.coreboot.org/c/flashrom/+/47641/1/programmer.h File programmer.h:
https://review.coreboot.org/c/flashrom/+/47641/1/programmer.h@790 PS1, Line 790: /* wpce775x.c */ : int wpce775x_probe_spi_flash(const char *name); needs guards. needs adding to the dispatch table in flashrom.c and also it should be added to meson as well as the Makefile. All the global state in wpce775x needs to go into a struct and put inside the spi master opaque void * data field. This all needs to be cleaned up in our tree before we wast upstreams limited resources and time. A couple of WIP patches is fine but not too many.
Honestly wpce775x needs plenty of clean ups in our tree before throwing our tech debt at upstream. This code is just too bad to send upstream in its current form.
Please continue to drive landing s25f work as it looks like it is almost there, perhaps some unit-tests would be great and seem very possible with s25f.
Edward O'Callaghan has posted comments on this change. ( https://review.coreboot.org/c/flashrom/+/47641 )
Change subject: WIP: wpce775x.c: import file from cros flashrom ......................................................................
Patch Set 1:
(1 comment)
https://review.coreboot.org/c/flashrom/+/47641/1/wpce775x.c File wpce775x.c:
https://review.coreboot.org/c/flashrom/+/47641/1/wpce775x.c@904 PS1, Line 904: // WIP: need to change this! : // target_bus = BUS_LPC; : // msg_pdbg("%s: forcing target bus: 0x%08x\n", __func__, target_bus);
WIP
repo sync more frequently and observe commit trends. This tech debt has already been removed.
Nikolai Artemiev has abandoned this change. ( https://review.coreboot.org/c/flashrom/+/47641 )
Change subject: WIP: wpce775x.c: import file from cros flashrom ......................................................................
Abandoned