Nikolai Artemiev has uploaded this change for review.

View Change

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__ */

To view, visit change 47641. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: flashrom
Gerrit-Branch: master
Gerrit-Change-Id: I10ef0ae65a8f2bf5e3abc4f80be31dd70c7d31b4
Gerrit-Change-Number: 47641
Gerrit-PatchSet: 1
Gerrit-Owner: Nikolai Artemiev <nartemiev@google.com>
Gerrit-MessageType: newchange