| /* |
| * 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 */ |
| unsigned char exe:1; /* Bit0-RW- set by host. means wcb is ready to execute. |
| should be cleared by host after RDY=1. */ |
| unsigned char resv0_41:4; |
| unsigned char pcp:1; /* Bit5-RO- set by EPCE775x. means preparation operations for |
| flash update process is complete. */ |
| unsigned char err:1; /* Bit6-RO- set by EPCE775x. means an error occurs. */ |
| unsigned char rdy:1; /* Bit7-RO- set by EPCE775x. means operation is completed. */ |
| |
| /* Byte 1-2: reserved */ |
| unsigned char byte1; |
| unsigned char byte2; |
| |
| /* Byte 3: command code */ |
| unsigned char code; |
| |
| /* Byte 4-15: command field */ |
| unsigned char 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 InitFlash 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 InitFlash multiple times during an update. We'll use |
| * this ability primarily for changing write protection bits. |
| */ |
| static struct wpce775x_initflash_cfg *initflash_cfg; |
| |
| static struct flashctx *flash_internal; |
| |
| |
| /* Indicate the flash chip attached to the WPCE7xxx chip. |
| * This variable should be set in probe_wpce775x(). |
| * 0 means we haven't or cannot detect the chip type. */ |
| struct flashchip *scan = 0; |
| |
| /* 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, |
| }; |
| int i; |
| static uint16_t port_internal, port_found = 0; |
| |
| if (port_found) { |
| *port = port_internal; |
| return 0; |
| } |
| |
| if (rget_io_perms()) |
| return 1; |
| |
| for (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 InitFlash() |
| { |
| int i; |
| |
| if (!initflash_cfg) { |
| msg_perr("%s(): InitFlash config is not defined\n", __func__); |
| return 1; |
| } |
| |
| assert_ec_is_free(); |
| /* Byte 3: command code: Init Flash */ |
| wcb->code = 0x5A; |
| msg_pdbg("%s(): InitFlash bytes: ", __func__); |
| for (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 */ |
| int initflash_cfg_setup(struct flashctx *flash) |
| { |
| if (!initflash_cfg) |
| initflash_cfg = malloc(sizeof(*initflash_cfg)); |
| |
| /* Copy flash struct pointer so that raw SPI commands that do not get |
| it passed in (e.g. called by spi_send_command) can access it. */ |
| if (flash) |
| flash_internal = flash; |
| |
| /* Set "sane" defaults. If the flash chip is known, then use parameters |
| from it. */ |
| initflash_cfg->read_device_id = JEDEC_RDID; |
| if (flash && (flash->feature_bits | FEATURE_WRSR_WREN)) |
| initflash_cfg->write_status_enable = JEDEC_WREN; |
| else if (flash && (flash->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->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 ReadId(unsigned char* id0, unsigned char* id1, |
| unsigned char* id2, unsigned char* id3) |
| { |
| if (!initflash_cfg) { |
| initflash_cfg_setup(NULL); |
| InitFlash(); |
| } |
| |
| 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. */ |
| int EnterFlashUpdate() |
| { |
| 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. |
| */ |
| int ExitFlashUpdate(unsigned char exit_code) |
| { |
| /* |
| * Note: ExitFlashUpdate 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. */ |
| int ExitFlashUpdateFirmwareNoChange(void) { |
| return ExitFlashUpdate(0x20); |
| } |
| |
| int ExitFlashUpdateFirmwareChanged(void) { |
| return ExitFlashUpdate(0x21); |
| } |
| |
| int wpce775x_read(int addr, unsigned char *buf, unsigned int nbytes) |
| { |
| int offset; |
| 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 (offset = 0; |
| offset < nbytes; |
| offset += bytes_read) { |
| int i; |
| 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 (i = 0; i < bytes_read; i++) |
| buf[offset + i] = wcb->field[i]; |
| } |
| |
| return 0; |
| } |
| |
| int wpce775x_erase_new(int blockaddr, uint8_t opcode) { |
| unsigned int current; |
| 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_internal); |
| initflash_cfg->block_erase = opcode; |
| InitFlash(); |
| |
| /* 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 (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; |
| } |
| |
| int wpce775x_nbyte_program(int addr, const unsigned char *buf, |
| unsigned int nbytes) |
| { |
| int offset, 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 (offset = 0; |
| offset < nbytes; |
| offset += written) { |
| int i; |
| 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 (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; |
| } |
| |
| int wpce775x_spi_read(struct flashctx *flash, uint8_t * buf, |
| unsigned int start, unsigned int len) |
| { |
| if (!initflash_cfg) { |
| initflash_cfg_setup(flash); |
| InitFlash(); |
| } |
| return spi_read_chunked(flash, buf, start, len, flash->page_size); |
| } |
| |
| 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); |
| InitFlash(); |
| } |
| return spi_write_chunked(flash, buf, start, len, flash->page_size); |
| } |
| |
| int wpce775x_spi_read_status_register(unsigned int readcnt, uint8_t *readarr) |
| { |
| uint8_t before[2]; |
| int ret = 0; |
| int i; |
| |
| 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 (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 (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; |
| } |
| |
| int wpce775x_spi_write_status_register(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_internal); |
| |
| initflash_cfg->status_reg_value = val; |
| if (in_flash_update_mode) { |
| ExitFlashUpdateFirmwareNoChange(); |
| in_flash_update_mode = 0; |
| } |
| if (InitFlash()) |
| return 1; |
| if (EnterFlashUpdate()) |
| 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. |
| */ |
| int wpce775x_spi_send_command(const struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, |
| const unsigned char *writearr, unsigned char *readarr) |
| { |
| int rc = 0; |
| uint8_t opcode = writearr[0]; |
| |
| switch(opcode){ |
| case JEDEC_RDID:{ |
| unsigned char dummy = 0; |
| if (readcnt == 3) |
| ReadId(&readarr[0], &readarr[1], &readarr[2], &dummy); |
| else if (readcnt == 4) |
| ReadId(&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(writearr[1]); |
| rc = 0; |
| break; |
| case JEDEC_WREN: |
| case JEDEC_EWSR: |
| /* Handled by InitFlash() */ |
| 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(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) |
| ExitFlashUpdateFirmwareChanged(); |
| else |
| ExitFlashUpdateFirmwareNoChange(); |
| |
| 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_programmer spi_programmer_wpce775x = { |
| .type = SPI_CONTROLLER_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, |
| }; |
| |
| 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. |
| */ |
| 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 (EnterFlashUpdate()) return 1; |
| |
| /* Add FWH | LPC to list of buses supported if they are not |
| * both there already. */ |
| buses_supported |= BUS_FWH | BUS_LPC; |
| register_spi_programmer(&spi_programmer_wpce775x); |
| msg_pdbg("%s(): successfully initialized wpce775x\n", __func__); |
| return 0; |
| } |
| |
| 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; |
| |
| if (alias && alias->type != ALIAS_EC) |
| return 1; |
| |
| 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 |