David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the flashrom project. |
| 3 | * |
| 4 | * Copyright (C) 2010 Google, Inc. |
| 5 | * |
| 6 | * Redistribution and use in source and binary forms, with or without |
| 7 | * modification, are permitted provided that the following conditions |
| 8 | * are met: |
| 9 | * |
| 10 | * Redistributions of source code must retain the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer. |
| 12 | * |
| 13 | * Redistributions in binary form must reproduce the above copyright |
| 14 | * notice, this list of conditions and the following disclaimer in the |
| 15 | * documentation and/or other materials provided with the distribution. |
| 16 | * |
| 17 | * Neither the name of Nuvoton Technology Corporation. or the names of |
| 18 | * contributors or licensors may be used to endorse or promote products derived |
| 19 | * from this software without specific prior written permission. |
| 20 | * |
| 21 | * This software is provided "AS IS," without a warranty of any kind. |
| 22 | * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, |
| 23 | * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A |
| 24 | * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. |
| 25 | * NUVOTON TECHNOLOGY CORPORATION. ("NUVOTON") AND ITS LICENSORS SHALL NOT BE LIABLE |
| 26 | * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING |
| 27 | * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL |
| 28 | * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, |
| 29 | * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR |
| 30 | * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF |
| 31 | * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, |
| 32 | * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
| 33 | * |
| 34 | * This is an UNOFFICIAL patch for the Nuvoton WPCE775x/NPCE781x. It was tested |
| 35 | * for a specific hardware and firmware configuration and should be considered |
| 36 | * unreliable. Please see the following URL for Nuvoton's authoritative, |
| 37 | * officially supported flash update utility: |
| 38 | * http://sourceforge.net/projects/nuvflashupdate/ |
| 39 | */ |
| 40 | |
David Hendricks | cb2cec3 | 2011-03-24 18:44:49 -0700 | [diff] [blame] | 41 | #if defined(__i386__) || defined(__x86_64__) |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 42 | #include <assert.h> |
| 43 | #include <string.h> |
| 44 | #include <sys/time.h> |
| 45 | #include <time.h> |
| 46 | #include <unistd.h> |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 47 | #include <stdlib.h> |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 48 | #include "flash.h" |
| 49 | #include "chipdrivers.h" |
| 50 | #include "flashchips.h" |
| 51 | #include "programmer.h" |
| 52 | #include "spi.h" |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 53 | #include "writeprotect.h" |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 54 | |
| 55 | /** |
| 56 | * Definition of WPCE775X WCB (Write Command Buffer), as known as Shared Access |
| 57 | * Window 2. |
| 58 | * |
| 59 | * The document name is "WPCE775X Software User Guide Revision 1.2". |
| 60 | * |
| 61 | * Assume the host is little endian. |
| 62 | */ |
| 63 | __attribute__((packed)) |
| 64 | struct wpce775x_wcb { |
| 65 | /* Byte 0: semaphore byte */ |
| 66 | unsigned char exe:1; /* Bit0-RW- set by host. means wcb is ready to execute. |
| 67 | should be cleared by host after RDY=1. */ |
| 68 | unsigned char resv0_41:4; |
| 69 | unsigned char pcp:1; /* Bit5-RO- set by EPCE775x. means preparation operations for |
| 70 | flash update process is complete. */ |
| 71 | unsigned char err:1; /* Bit6-RO- set by EPCE775x. means an error occurs. */ |
| 72 | unsigned char rdy:1; /* Bit7-RO- set by EPCE775x. means operation is completed. */ |
| 73 | |
| 74 | /* Byte 1-2: reserved */ |
| 75 | unsigned char byte1; |
| 76 | unsigned char byte2; |
| 77 | |
| 78 | /* Byte 3: command code */ |
| 79 | unsigned char code; |
| 80 | |
| 81 | /* Byte 4-15: command field */ |
| 82 | unsigned char field[12]; |
| 83 | }; |
| 84 | |
| 85 | /* The physical address of WCB -- Shared Access Window 2. */ |
| 86 | static chipaddr wcb_physical_address; |
| 87 | |
| 88 | /* The virtual address of WCB -- Shared Access Window 2. */ |
| 89 | static volatile struct wpce775x_wcb *volatile wcb; |
| 90 | |
| 91 | /* count of entering flash update mode */ |
| 92 | static int in_flash_update_mode; |
| 93 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 94 | static int firmware_changed; |
| 95 | |
| 96 | /* |
| 97 | * Bytes 0x4-0xf of InitFlash command. These represent opcodes and various |
| 98 | * parameters the WPCE775x will use when communicating with the SPI flash |
| 99 | * device. DO NOT RE-ORDER THIS STRUCTURE. |
| 100 | */ |
| 101 | struct wpce775x_initflash_cfg { |
| 102 | uint8_t read_device_id; /* Byte 0x04. Ex: JEDEC_RDID */ |
| 103 | uint8_t write_status_enable; /* Byte 0x05. Ex: JEDEC_EWSR */ |
| 104 | uint8_t write_enable; /* Byte 0x06. Ex: JEDEC_WREN */ |
| 105 | uint8_t read_status_register; /* Byte 0x07. Ex: JEDEC_RDSR */ |
| 106 | uint8_t write_status_register; /* Byte 0x08. Ex: JEDEC_WRSR */ |
| 107 | uint8_t flash_program; /* Byte 0x09. Ex: JEDEC_BYTE_PROGRAM */ |
| 108 | |
| 109 | /* Byte 0x0A. Ex: sector/block/chip erase opcode */ |
| 110 | uint8_t block_erase; |
| 111 | |
| 112 | uint8_t status_busy_mask; /* Byte B: bit position of BUSY bit */ |
| 113 | |
| 114 | /* Byte 0x0C: value to remove write protection */ |
| 115 | uint8_t status_reg_value; |
| 116 | |
| 117 | /* Byte 0x0D: Number of bytes to program in each write transaction. */ |
| 118 | uint8_t program_unit_size; |
| 119 | |
| 120 | uint8_t page_size; /* Byte 0x0E: 2^n bytes */ |
| 121 | |
| 122 | /* |
| 123 | * Byte 0x0F: Method to read device ID. 0x47 will cause ID bytes to be |
| 124 | * read immediately after read_device_id command is issued. Otherwise, |
| 125 | * 3 dummy address bytes are sent after the read_device_id code. |
| 126 | */ |
| 127 | uint8_t read_device_id_type; |
| 128 | } __attribute__((packed)); |
| 129 | |
| 130 | /* |
| 131 | * The WPCE775x can use InitFlash multiple times during an update. We'll use |
| 132 | * this ability primarily for changing write protection bits. |
| 133 | */ |
| 134 | static struct wpce775x_initflash_cfg *initflash_cfg; |
| 135 | |
| 136 | static struct flashchip *flash_internal; |
| 137 | |
| 138 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 139 | /* Indicate the flash chip attached to the WPCE7xxx chip. |
| 140 | * This variable should be set in probe_wpce775x(). |
| 141 | * 0 means we haven't or cannot detect the chip type. */ |
| 142 | struct flashchip *scan = 0; |
| 143 | |
| 144 | /* SuperI/O related definitions and functions. */ |
| 145 | /* Strapping options */ |
| 146 | #define NUVOTON_SIO_PORT1 0x2e /* No pull-down resistor */ |
| 147 | #define NUVOTON_SIO_PORT2 0x164e /* Pull-down resistor on BADDR0 */ |
| 148 | /* Note: There's another funky state that we won't worry about right now */ |
| 149 | |
| 150 | /* SuperI/O Config */ |
| 151 | #define NUVOTON_SIOCFG_LDN 0x07 /* LDN Bank Selector */ |
| 152 | #define NUVOTON_SIOCFG_SID 0x20 /* SuperI/O ID */ |
| 153 | #define NUVOTON_SIOCFG_SRID 0x27 /* SuperI/O Revision ID */ |
| 154 | #define NUVOTON_LDN_SHM 0x0f /* LDN of SHM module */ |
| 155 | |
| 156 | /* WPCE775x shared memory config registers (LDN 0x0f) */ |
| 157 | #define WPCE775X_SHM_BASE_MSB 0x60 |
| 158 | #define WPCE775X_SHM_BASE_LSB 0x61 |
| 159 | #define WPCE775X_SHM_CFG 0xf0 |
| 160 | #define WPCE775X_SHM_CFG_BIOS_FWH_EN (1 << 3) |
| 161 | #define WPCE775X_SHM_CFG_FLASH_ACC_EN (1 << 2) |
| 162 | #define WPCE775X_SHM_CFG_BIOS_EXT_EN (1 << 1) |
| 163 | #define WPCE775X_SHM_CFG_BIOS_LPC_EN (1 << 0) |
| 164 | #define WPCE775X_WIN_CFG 0xf1 /* window config */ |
| 165 | #define WPCE775X_WIN_CFG_SHWIN_ACC (1 << 6) |
| 166 | |
| 167 | /* Shared access window 2 bar address registers */ |
| 168 | #define WPCE775X_SHAW2BA_0 0xf8 |
| 169 | #define WPCE775X_SHAW2BA_1 0xf9 |
| 170 | #define WPCE775X_SHAW2BA_2 0xfa |
| 171 | #define WPCE775X_SHAW2BA_3 0xfb |
| 172 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 173 | /* Read/write buffer size */ |
| 174 | #define WPCE775X_MAX_WRITE_SIZE 8 |
| 175 | #define WPCE775X_MAX_READ_SIZE 12 |
| 176 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 177 | /** probe for super i/o index |
| 178 | * @returns 0 to indicate success, <0 to indicate error |
| 179 | */ |
| 180 | static int nuvoton_get_sio_index(uint16_t *port) |
| 181 | { |
| 182 | uint16_t ports[] = { NUVOTON_SIO_PORT2, |
| 183 | NUVOTON_SIO_PORT1, |
| 184 | }; |
| 185 | int i; |
| 186 | static uint16_t port_internal, port_found = 0; |
| 187 | |
| 188 | if (port_found) { |
| 189 | *port = port_internal; |
| 190 | return 0; |
| 191 | } |
| 192 | |
| 193 | get_io_perms(); |
| 194 | |
| 195 | for (i = 0; i < ARRAY_SIZE(ports); i++) { |
| 196 | uint8_t sid = sio_read(ports[i], NUVOTON_SIOCFG_SID); |
| 197 | |
| 198 | if (sid == 0xfc) { /* Family ID */ |
| 199 | port_internal = ports[i]; |
| 200 | port_found = 1; |
| 201 | break; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | if (!port_found) { |
| 206 | msg_cdbg("\nfailed to obtain super i/o index"); |
| 207 | return -1; |
| 208 | } |
| 209 | |
| 210 | msg_cdbg("\nsuper i/o index = 0x%04x\n", port_internal); |
| 211 | *port = port_internal; |
| 212 | return 0; |
| 213 | } |
| 214 | |
| 215 | /** Call superio to get pre-configured WCB address. |
| 216 | * Read LDN 0x0f (SHM) idx:f8-fb (little-endian). |
| 217 | */ |
| 218 | static int get_shaw2ba(chipaddr *shaw2ba) |
| 219 | { |
| 220 | uint16_t idx; |
| 221 | uint8_t org_ldn; |
| 222 | uint8_t win_cfg; |
| 223 | uint8_t shm_cfg; |
| 224 | |
| 225 | if (nuvoton_get_sio_index(&idx) < 0) |
| 226 | return -1; |
| 227 | |
| 228 | org_ldn = sio_read(idx, NUVOTON_SIOCFG_LDN); |
| 229 | sio_write(idx, NUVOTON_SIOCFG_LDN, NUVOTON_LDN_SHM); |
| 230 | |
| 231 | /* |
| 232 | * To obtain shared access window 2 base address, we must OR the base |
| 233 | * address bytes, where SHAW2BA_0 is least significant and SHAW2BA_3 |
| 234 | * most significant. |
| 235 | */ |
| 236 | *shaw2ba = sio_read(idx, WPCE775X_SHAW2BA_0) | |
| 237 | (sio_read(idx, WPCE775X_SHAW2BA_1) << 8) | |
| 238 | (sio_read(idx, WPCE775X_SHAW2BA_2) << 16) | |
| 239 | (sio_read(idx, WPCE775X_SHAW2BA_3) << 24); |
| 240 | |
| 241 | /* |
| 242 | * If SHWIN_ACC is cleared, then we're using LPC memory access |
| 243 | * and SHAW2BA_3-0 indicate bits 31-0. If SHWIN_ACC is set, then |
| 244 | * bits 7-4 of SHAW2BA_3 are ignored and bits 31-28 are indicated |
| 245 | * by the idsel nibble. (See table 25 "supported host address ranges" |
| 246 | * for more details) |
| 247 | */ |
| 248 | win_cfg = sio_read(idx, WPCE775X_WIN_CFG); |
| 249 | if (win_cfg & WPCE775X_WIN_CFG_SHWIN_ACC) { |
| 250 | uint8_t idsel; |
| 251 | |
| 252 | /* Make sure shared BIOS memory is enabled */ |
| 253 | shm_cfg = sio_read(idx, WPCE775X_SHM_CFG); |
| 254 | if ((shm_cfg & WPCE775X_SHM_CFG_BIOS_FWH_EN)) |
| 255 | idsel = 0xf; |
| 256 | else { |
| 257 | msg_cdbg("Shared BIOS memory is diabled.\n"); |
| 258 | msg_cdbg("Please check SHM_CFG:BIOS_FWH_EN.\n"); |
| 259 | goto error; |
| 260 | } |
| 261 | |
| 262 | *shaw2ba &= 0x0fffffff; |
| 263 | *shaw2ba |= idsel << 28; |
| 264 | } |
| 265 | |
| 266 | sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); |
| 267 | return 0; |
| 268 | error: |
| 269 | sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); |
| 270 | return -1; |
| 271 | } |
| 272 | |
| 273 | /* Call superio to get pre-configured fwh_id. |
| 274 | * Read LDN 0x0f (SHM) idx:f0. |
| 275 | */ |
| 276 | static int get_fwh_id(uint8_t *fwh_id) |
| 277 | { |
| 278 | uint16_t idx; |
| 279 | uint8_t org_ldn; |
| 280 | |
| 281 | if (nuvoton_get_sio_index(&idx) < 0) |
| 282 | return -1; |
| 283 | |
| 284 | org_ldn = sio_read(idx, NUVOTON_SIOCFG_LDN); |
| 285 | sio_write(idx, NUVOTON_SIOCFG_LDN, NUVOTON_LDN_SHM); |
| 286 | *fwh_id = sio_read(idx, WPCE775X_SHM_CFG); |
| 287 | sio_write(idx, NUVOTON_SIOCFG_LDN, org_ldn); |
| 288 | |
| 289 | return 0; |
| 290 | } |
| 291 | |
| 292 | /** helper function to make sure the exe bit is 0 (no one is using EC). |
| 293 | * @return 1 for error; 0 for success. |
| 294 | */ |
| 295 | static int assert_ec_is_free(void) |
| 296 | { |
| 297 | if (wcb->exe) |
| 298 | msg_perr("ASSERT(wcb->exe==0), entering busy loop.\n"); |
| 299 | while(wcb->exe); |
| 300 | return 0; |
| 301 | } |
| 302 | |
| 303 | /** Trigger EXE bit, and block until operation completes. |
| 304 | * @return 1 for error; and 0 for success. |
| 305 | */ |
| 306 | static int blocked_exec(void) |
| 307 | { |
| 308 | struct timeval begin, now; |
| 309 | int timeout; /* not zero if timeout occurs */ |
| 310 | int err; |
| 311 | |
| 312 | assert(wcb->rdy==0); |
| 313 | |
| 314 | /* raise EXE bit, and wait for operation complete or error occur. */ |
| 315 | wcb->exe = 1; |
| 316 | |
| 317 | timeout = 0; |
| 318 | gettimeofday(&begin, NULL); |
| 319 | while(wcb->rdy==0 && wcb->err==0) { |
| 320 | gettimeofday(&now, NULL); |
| 321 | /* According to Nuvoton's suggestion, few seconds is enough for |
| 322 | * longest flash operation, which is erase. |
| 323 | * Cutted from W25X16 datasheet, for max operation time |
| 324 | * Byte program tBP1 50us |
| 325 | * Page program tPP 3ms |
| 326 | * Sector Erase (4KB) tSE 200ms |
| 327 | * Block Erase (64KB) tBE 1s |
| 328 | * Chip Erase tCE 20s |
| 329 | * Since WPCE775x doesn't support chip erase, |
| 330 | * 3 secs is long enough for block erase. |
| 331 | */ |
| 332 | if ((now.tv_sec - begin.tv_sec) >= 4) { |
| 333 | timeout += 1; |
| 334 | break; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | /* keep ERR bit before clearing EXE bit. */ |
| 339 | err = wcb->err; |
| 340 | |
| 341 | /* Clear EXE bit, and wait for RDY back to 0. */ |
| 342 | wcb->exe = 0; |
| 343 | gettimeofday(&begin, NULL); |
| 344 | while(wcb->rdy) { |
| 345 | gettimeofday(&now, NULL); |
| 346 | /* 1 sec should be long enough for clearing rdy bit. */ |
| 347 | if (((now.tv_sec - begin.tv_sec)*1000*1000 + |
| 348 | (now.tv_usec - begin.tv_usec)) >= 1000*1000) { |
| 349 | timeout += 1; |
| 350 | break; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | if (err || timeout) { |
| 355 | msg_cdbg("err=%d timeout=%d\n", err, timeout); |
| 356 | return 1; |
| 357 | } |
| 358 | return 0; |
| 359 | } |
| 360 | |
| 361 | /** Initialize the EC parameters. |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 362 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 363 | * @return 1 for error; 0 for success. |
| 364 | */ |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 365 | static int InitFlash() |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 366 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 367 | int i; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 368 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 369 | if (!initflash_cfg) { |
| 370 | msg_perr("%s(): InitFlash config is not defined\n", __func__); |
| 371 | return 1; |
| 372 | } |
| 373 | |
| 374 | assert_ec_is_free(); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 375 | /* Byte 3: command code: Init Flash */ |
| 376 | wcb->code = 0x5A; |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 377 | msg_pdbg("%s(): InitFlash bytes: ", __func__); |
| 378 | for (i = 0; i < sizeof(struct wpce775x_initflash_cfg); i++) { |
| 379 | wcb->field[i] = *((uint8_t *)initflash_cfg + i); |
| 380 | msg_pdbg("%02x ", wcb->field[i]); |
| 381 | } |
| 382 | msg_pdbg("\n"); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 383 | |
| 384 | if (blocked_exec()) |
| 385 | return 1; |
| 386 | return 0; |
| 387 | } |
| 388 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 389 | /* log2() could be used if we link with -lm */ |
| 390 | static int logbase2(int x) |
| 391 | { |
| 392 | int log = 0; |
| 393 | |
| 394 | /* naive way */ |
| 395 | while (x) { |
| 396 | x >>= 1; |
| 397 | log++; |
| 398 | } |
| 399 | return log; |
| 400 | } |
| 401 | |
| 402 | /* initialize initflash_cfg struct */ |
| 403 | int initflash_cfg_setup(struct flashchip *flash) |
| 404 | { |
| 405 | if (!initflash_cfg) |
| 406 | initflash_cfg = malloc(sizeof(*initflash_cfg)); |
| 407 | |
| 408 | /* Copy flash struct pointer so that raw SPI commands that do not get |
| 409 | it passed in (e.g. called by spi_send_command) can access it. */ |
| 410 | if (flash) |
| 411 | flash_internal = flash; |
| 412 | |
| 413 | /* Set "sane" defaults. If the flash chip is known, then use parameters |
| 414 | from it. */ |
| 415 | initflash_cfg->read_device_id = JEDEC_RDID; |
| 416 | if (flash && (flash->feature_bits | FEATURE_WRSR_WREN)) |
| 417 | initflash_cfg->write_status_enable = JEDEC_WREN; |
| 418 | else if (flash && (flash->feature_bits | FEATURE_WRSR_EWSR)) |
| 419 | initflash_cfg->write_status_enable = JEDEC_EWSR; |
| 420 | else |
| 421 | initflash_cfg->write_status_enable = JEDEC_WREN; |
| 422 | initflash_cfg->write_enable = JEDEC_WREN; |
| 423 | initflash_cfg->read_status_register = JEDEC_RDSR; |
| 424 | initflash_cfg->write_status_register = JEDEC_WRSR; |
| 425 | initflash_cfg->flash_program = JEDEC_BYTE_PROGRAM; |
| 426 | |
| 427 | /* note: these members are likely to be overridden later */ |
| 428 | initflash_cfg->block_erase = JEDEC_SE; |
| 429 | initflash_cfg->status_busy_mask = 0x01; |
| 430 | initflash_cfg->status_reg_value = 0x00; |
| 431 | |
| 432 | /* back to "sane" defaults... */ |
| 433 | initflash_cfg->program_unit_size = 0x01; |
| 434 | if (flash) |
| 435 | initflash_cfg->page_size = logbase2(flash->page_size); |
| 436 | else |
| 437 | initflash_cfg->page_size = 0x08; |
| 438 | |
| 439 | initflash_cfg->read_device_id_type = 0x00; |
| 440 | |
| 441 | return 0; |
| 442 | } |
| 443 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 444 | /** Read flash vendor/device IDs through EC. |
| 445 | * @param id0, id1, id2, id3 Pointers to store detected IDs. NULL will be ignored. |
| 446 | * @return 1 for error; 0 for success. |
| 447 | */ |
| 448 | static int ReadId(unsigned char* id0, unsigned char* id1, |
| 449 | unsigned char* id2, unsigned char* id3) |
| 450 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 451 | if (!initflash_cfg) { |
| 452 | initflash_cfg_setup(NULL); |
| 453 | InitFlash(); |
| 454 | } |
| 455 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 456 | assert_ec_is_free(); |
| 457 | |
| 458 | wcb->code = 0xC0; /* Byte 3: command code: Read ID */ |
| 459 | if (blocked_exec()) |
| 460 | return 1; |
| 461 | |
| 462 | msg_cdbg("id0: 0x%2x, id1: 0x%2x, id2: 0x%2x, id3: 0x%2x\n", |
| 463 | wcb->field[0], wcb->field[1], wcb->field[2], wcb->field[3]); |
| 464 | if (id0) { |
| 465 | *id0 = wcb->field[0]; |
| 466 | } |
| 467 | if (id1) { |
| 468 | *id1 = wcb->field[1]; |
| 469 | } |
| 470 | if (id2) { |
| 471 | *id2 = wcb->field[2]; |
| 472 | } |
| 473 | if (id3) { |
| 474 | *id3 = wcb->field[3]; |
| 475 | } |
| 476 | |
| 477 | return 0; |
| 478 | } |
| 479 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 480 | /** Tell EC to "enter flash update" mode. */ |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 481 | int EnterFlashUpdate() |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 482 | { |
| 483 | if (in_flash_update_mode) { |
| 484 | /* already in update mode */ |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 485 | msg_pdbg("%s: in_flash_update_mode: %d\n", |
| 486 | __func__, in_flash_update_mode); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 487 | return 0; |
| 488 | } |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 489 | assert_ec_is_free(); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 490 | |
| 491 | wcb->code = 0x10; /* Enter Flash Update */ |
| 492 | wcb->field[0] = 0x55; /* required pattern by EC */ |
| 493 | wcb->field[1] = 0xAA; /* required pattern by EC */ |
| 494 | wcb->field[2] = 0xCD; /* required pattern by EC */ |
| 495 | wcb->field[3] = 0xBE; /* required pattern by EC */ |
| 496 | if (blocked_exec()) { |
| 497 | return 1; |
| 498 | } else { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 499 | in_flash_update_mode = 1; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 500 | return 0; |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | /** Tell EC to "exit flash update" mode. |
| 505 | * Without calling this function, the EC stays in busy-loop and will not |
| 506 | * response further request from host, which means system will halt. |
| 507 | */ |
| 508 | int ExitFlashUpdate(unsigned char exit_code) |
| 509 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 510 | /* |
| 511 | * Note: ExitFlashUpdate must be called before shutting down the |
| 512 | * machine, otherwise the EC will be stuck in update mode, leaving |
| 513 | * the machine in a "wedged" state until power cycled. |
| 514 | */ |
| 515 | if (!in_flash_update_mode) { |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 516 | msg_cdbg("Not in flash update mode yet.\n"); |
| 517 | return 1; |
| 518 | } |
| 519 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 520 | wcb->code = exit_code; /* Exit Flash Update */ |
| 521 | if (blocked_exec()) { |
| 522 | return 1; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 523 | } |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 524 | |
| 525 | in_flash_update_mode = 0; |
| 526 | return 0; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 527 | } |
| 528 | |
| 529 | /* |
| 530 | * Note: The EC firmware this patch has been tested with uses the following |
| 531 | * codes to indicate flash update status: |
| 532 | * 0x20 is used for EC F/W no change, but BIOS changed (in Share mode) |
| 533 | * 0x21 is used for EC F/W changed. Goto EC F/W, wait system reboot. |
| 534 | * 0x22 is used for EC F/W changed, Goto EC Watchdog reset. */ |
| 535 | int ExitFlashUpdateFirmwareNoChange(void) { |
| 536 | return ExitFlashUpdate(0x20); |
| 537 | } |
| 538 | |
| 539 | int ExitFlashUpdateFirmwareChanged(void) { |
| 540 | return ExitFlashUpdate(0x21); |
| 541 | } |
| 542 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 543 | int wpce775x_spi_common_init(void) |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 544 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 545 | uint16_t sio_port; |
| 546 | uint8_t srid; |
| 547 | uint8_t fwh_id; |
| 548 | |
| 549 | msg_pdbg("%s(): entered\n", __func__); |
| 550 | |
| 551 | /* detect if wpce775x exists */ |
| 552 | if (nuvoton_get_sio_index(&sio_port) < 0) { |
| 553 | msg_pdbg("No Nuvoton chip is found.\n"); |
| 554 | return 0; |
| 555 | } |
| 556 | srid = sio_read(sio_port, NUVOTON_SIOCFG_SRID); |
| 557 | if ((srid & 0xE0) == 0xA0) { |
| 558 | msg_pdbg("Found EC: WPCE775x (Vendor:0x%02x,ID:0x%02x,Rev:0x%02x) on sio_port:0x%x.\n", |
| 559 | sio_read(sio_port, NUVOTON_SIOCFG_SID), |
| 560 | srid >> 5, srid & 0x1f, sio_port); |
| 561 | |
| 562 | } else { |
| 563 | msg_pdbg("Found EC: Nuvoton (Vendor:0x%02x,ID:0x%02x,Rev:0x%02x) on sio_port:0x%x.\n", |
| 564 | sio_read(sio_port, NUVOTON_SIOCFG_SID), |
| 565 | srid >> 5, srid & 0x1f, sio_port); |
| 566 | } |
| 567 | |
| 568 | /* get the address of Shadow Window 2. */ |
| 569 | if (get_shaw2ba(&wcb_physical_address) < 0) { |
| 570 | msg_pdbg("Cannot get the address of Shadow Window 2"); |
| 571 | return 0; |
| 572 | } |
| 573 | msg_pdbg("Get the address of WCB(SHA WIN2) at 0x%08x\n", |
| 574 | (uint32_t)wcb_physical_address); |
| 575 | wcb = (struct wpce775x_wcb *) |
| 576 | programmer_map_flash_region("WPCE775X WCB", |
| 577 | wcb_physical_address, |
| 578 | getpagesize() /* min page size */); |
| 579 | msg_pdbg("mapped wcb address: %p for physical addr: 0x%08lx\n", wcb, wcb_physical_address); |
| 580 | if (!wcb) { |
| 581 | msg_perr("FATAL! Cannot map memory area for wcb physical address.\n"); |
| 582 | return 0; |
| 583 | } |
| 584 | memset((void*)wcb, 0, sizeof(*wcb)); |
| 585 | |
| 586 | if (get_fwh_id(&fwh_id) < 0) { |
| 587 | msg_pdbg("Cannot get fwh_id value.\n"); |
| 588 | return 0; |
| 589 | } |
| 590 | msg_pdbg("get fwh_id: 0x%02x\n", fwh_id); |
| 591 | |
| 592 | /* TODO: set fwh_idsel of chipset. |
| 593 | Currently, we employ "-p internal:fwh_idsel=0x0000223e". */ |
| 594 | |
| 595 | /* Enter flash update mode unconditionally. This is required even |
| 596 | for reading. */ |
| 597 | if (EnterFlashUpdate()) return 1; |
| 598 | |
| 599 | spi_controller = SPI_CONTROLLER_WPCE775X; |
| 600 | msg_pdbg("%s(): successfully initialized wpce775x\n", __func__); |
| 601 | return 0; |
| 602 | |
| 603 | } |
| 604 | |
| 605 | int wpce775x_shutdown(void) |
| 606 | { |
| 607 | if (spi_controller != SPI_CONTROLLER_WPCE775X) |
| 608 | return 0; |
| 609 | |
| 610 | msg_pdbg("%s(): firmware %s\n", __func__, |
| 611 | firmware_changed ? "changed" : "not changed"); |
| 612 | |
| 613 | msg_pdbg("%s: in_flash_update_mode: %d\n", __func__, in_flash_update_mode); |
| 614 | if (in_flash_update_mode) { |
| 615 | if (firmware_changed) |
| 616 | ExitFlashUpdateFirmwareChanged(); |
| 617 | else |
| 618 | ExitFlashUpdateFirmwareNoChange(); |
| 619 | |
| 620 | in_flash_update_mode = 0; |
| 621 | } |
| 622 | |
| 623 | if (initflash_cfg) |
| 624 | free(initflash_cfg); |
| 625 | else |
| 626 | msg_perr("%s(): No initflash_cfg to free?!?\n", __func__); |
| 627 | |
| 628 | return 0; |
| 629 | } |
| 630 | |
| 631 | /* Called by internal_init() */ |
| 632 | int wpce775x_probe_spi_flash(const char *name) |
| 633 | { |
| 634 | int ret; |
| 635 | |
| 636 | if (!(buses_supported & CHIP_BUSTYPE_FWH)) { |
| 637 | msg_pdbg("%s():%d buses not support FWH\n", __func__, __LINE__); |
| 638 | return 1; |
| 639 | } |
| 640 | ret = wpce775x_spi_common_init(); |
| 641 | msg_pdbg("FWH: %s():%d ret=%d\n", __func__, __LINE__, ret); |
| 642 | if (!ret) { |
| 643 | msg_pdbg("%s():%d buses_supported=0x%x\n", __func__, __LINE__, |
| 644 | buses_supported); |
| 645 | if (buses_supported & CHIP_BUSTYPE_FWH) |
| 646 | msg_pdbg("Overriding chipset SPI with WPCE775x FWH|SPI.\n"); |
| 647 | buses_supported |= CHIP_BUSTYPE_FWH | CHIP_BUSTYPE_SPI; |
| 648 | } |
| 649 | return ret; |
| 650 | } |
| 651 | |
| 652 | int wpce775x_read(int addr, unsigned char *buf, unsigned int nbytes) |
| 653 | { |
| 654 | int offset; |
| 655 | unsigned int bytes_read = 0; |
| 656 | |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 657 | assert_ec_is_free(); |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 658 | msg_pspew("%s: reading %d bytes at 0x%06x\n", __func__, nbytes, addr); |
| 659 | |
| 660 | /* Set initial address; WPCE775x auto-increments address for successive |
| 661 | read and write operations. */ |
| 662 | wcb->code = 0xA0; |
| 663 | wcb->field[0] = addr & 0xff; |
| 664 | wcb->field[1] = (addr >> 8) & 0xff; |
| 665 | wcb->field[2] = (addr >> 16) & 0xff; |
| 666 | wcb->field[3] = (addr >> 24) & 0xff; |
| 667 | if (blocked_exec()) { |
| 668 | return 1; |
| 669 | } |
| 670 | |
| 671 | for (offset = 0; |
| 672 | offset < nbytes; |
| 673 | offset += bytes_read) { |
| 674 | int i; |
| 675 | unsigned int bytes_left; |
| 676 | |
| 677 | bytes_left = nbytes - offset; |
| 678 | if (bytes_left > 0 && bytes_left < WPCE775X_MAX_READ_SIZE) |
| 679 | bytes_read = bytes_left; |
| 680 | else |
| 681 | bytes_read = WPCE775X_MAX_READ_SIZE; |
| 682 | wcb->code = 0xD0 | bytes_read; |
| 683 | if (blocked_exec()) { |
| 684 | return 1; |
| 685 | } |
| 686 | |
| 687 | for (i = 0; i < bytes_read; i++) |
| 688 | buf[offset + i] = wcb->field[i]; |
| 689 | } |
| 690 | |
| 691 | return 0; |
| 692 | } |
| 693 | |
| 694 | int wpce775x_erase_new(int blockaddr, uint8_t opcode) { |
| 695 | unsigned int current; |
| 696 | int blocksize; |
| 697 | int ret = 0; |
| 698 | |
| 699 | assert_ec_is_free(); |
| 700 | |
| 701 | /* |
| 702 | * FIXME: In the long-run we should examine block_erasers within the |
| 703 | * flash struct to ensure the proper blocksize is used. This is because |
| 704 | * some chips implement commands differently. For now, we'll support |
| 705 | * only a few "safe" block erase commands with predictable block size. |
| 706 | * |
| 707 | * Looking thru the list of flashchips, it seems JEDEC_BE_52 and |
| 708 | * JEDEC_BE_D8 are not uniformly implemented. Thus, we cannot safely |
| 709 | * assume a blocksize. |
| 710 | * |
| 711 | * Also, I was unable to test chip erase (due to equipment and time |
| 712 | * constraints), but they might work. |
| 713 | */ |
| 714 | switch(opcode) { |
| 715 | case JEDEC_SE: |
| 716 | case JEDEC_BE_D7: |
| 717 | blocksize = 4 * 1024; |
| 718 | break; |
| 719 | case JEDEC_BE_52: |
| 720 | case JEDEC_BE_D8: |
| 721 | case JEDEC_CE_60: |
| 722 | case JEDEC_CE_C7: |
| 723 | default: |
| 724 | msg_perr("%s(): erase opcode=0x%02x not supported\n", |
| 725 | __func__, opcode); |
| 726 | return 1; |
| 727 | } |
| 728 | |
| 729 | msg_pspew("%s(): blockaddr=%d, blocksize=%d, opcode=0x%02x\n", |
| 730 | __func__, blockaddr, blocksize, opcode); |
| 731 | |
| 732 | if (!initflash_cfg) |
| 733 | initflash_cfg_setup(flash_internal); |
| 734 | initflash_cfg->block_erase = opcode; |
| 735 | InitFlash(); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 736 | |
| 737 | /* Set Write Window on flash chip (optional). |
| 738 | * You may limit the window to partial flash for experimental. */ |
| 739 | wcb->code = 0xC5; /* Set Write Window */ |
| 740 | wcb->field[0] = 0x00; /* window base: little-endian */ |
| 741 | wcb->field[1] = 0x00; |
| 742 | wcb->field[2] = 0x00; |
| 743 | wcb->field[3] = 0x00; |
| 744 | wcb->field[4] = 0x00; /* window length: little-endian */ |
| 745 | wcb->field[5] = 0x00; |
| 746 | wcb->field[6] = 0x20; |
| 747 | wcb->field[7] = 0x00; |
| 748 | if (blocked_exec()) |
| 749 | return 1; |
| 750 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 751 | msg_pspew("Erasing ... 0x%08x 0x%08x\n", blockaddr, blocksize); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 752 | |
| 753 | for (current = 0; |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 754 | current < blocksize; |
| 755 | current += blocksize) { |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 756 | wcb->code = 0x80; /* Sector/block erase */ |
| 757 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 758 | /* WARNING: assume the block address for EC is always little-endian. */ |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 759 | unsigned int addr = blockaddr + current; |
| 760 | wcb->field[0] = addr & 0xff; |
| 761 | wcb->field[1] = (addr >> 8) & 0xff; |
| 762 | wcb->field[2] = (addr >> 16) & 0xff; |
| 763 | wcb->field[3] = (addr >> 24) & 0xff; |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 764 | if (blocked_exec()) { |
| 765 | ret = 1; |
| 766 | goto wpce775x_erase_new_exit; |
| 767 | } |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 768 | } |
| 769 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 770 | wpce775x_erase_new_exit: |
| 771 | firmware_changed = 1; |
| 772 | return ret; |
| 773 | } |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 774 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 775 | int wpce775x_nbyte_program(int addr, const unsigned char *buf, |
| 776 | unsigned int nbytes) |
| 777 | { |
| 778 | int offset, ret = 0; |
| 779 | unsigned int written = 0; |
| 780 | |
| 781 | assert_ec_is_free(); |
| 782 | msg_pspew("%s: writing %d bytes to 0x%06x\n", __func__, nbytes, addr); |
| 783 | |
| 784 | /* Set initial address; WPCE775x auto-increments address for successive |
| 785 | read and write operations. */ |
| 786 | wcb->code = 0xA0; |
| 787 | wcb->field[0] = addr & 0xff; |
| 788 | wcb->field[1] = (addr >> 8) & 0xff; |
| 789 | wcb->field[2] = (addr >> 16) & 0xff; |
| 790 | wcb->field[3] = (addr >> 24) & 0xff; |
| 791 | if (blocked_exec()) { |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 792 | return 1; |
| 793 | } |
| 794 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 795 | for (offset = 0; |
| 796 | offset < nbytes; |
| 797 | offset += written) { |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 798 | int i; |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 799 | unsigned int bytes_left; |
| 800 | |
| 801 | bytes_left = nbytes - offset; |
| 802 | if (bytes_left > 0 && bytes_left < WPCE775X_MAX_WRITE_SIZE) |
| 803 | written = bytes_left; |
| 804 | else |
| 805 | written = WPCE775X_MAX_WRITE_SIZE; |
| 806 | wcb->code = 0xB0 | written; |
| 807 | |
| 808 | for (i = 0; i < written; i++) |
| 809 | wcb->field[i] = buf[offset + i]; |
| 810 | if (blocked_exec()) { |
| 811 | ret = 1; |
| 812 | goto wpce775x_nbyte_program_exit; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 813 | } |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 814 | } |
| 815 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 816 | wpce775x_nbyte_program_exit: |
| 817 | firmware_changed = 1; |
| 818 | return ret; |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 819 | } |
| 820 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 821 | int wpce775x_spi_read(struct flashchip *flash, uint8_t * buf, int start, int len) |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 822 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 823 | if (!initflash_cfg) { |
| 824 | initflash_cfg_setup(flash); |
| 825 | InitFlash(); |
| 826 | } |
| 827 | return spi_read_chunked(flash, buf, start, len, flash->page_size); |
David Hendricks | babd1b5 | 2010-08-19 13:16:19 -0700 | [diff] [blame] | 828 | } |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 829 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 830 | int wpce775x_spi_write_256(struct flashchip *flash, uint8_t *buf, int start, int len) |
| 831 | { |
| 832 | if (!initflash_cfg) { |
| 833 | initflash_cfg_setup(flash); |
| 834 | InitFlash(); |
| 835 | } |
| 836 | return spi_write_chunked(flash, buf, start, len, flash->page_size); |
| 837 | } |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 838 | |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 839 | int wpce775x_spi_read_status_register(unsigned int readcnt, uint8_t *readarr) |
| 840 | { |
Louis Yung-Chieh Lo | f6f03c4 | 2011-03-30 17:24:49 +0800 | [diff] [blame] | 841 | uint8_t before[2]; |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 842 | int ret = 0; |
| 843 | int i; |
| 844 | |
| 845 | assert_ec_is_free(); |
| 846 | msg_pdbg("%s(): reading status register.\n", __func__); |
| 847 | |
Louis Yung-Chieh Lo | f6f03c4 | 2011-03-30 17:24:49 +0800 | [diff] [blame] | 848 | /* We need to detect if EC firmware support this 0x30 command. |
| 849 | * 1. write a non-sense value to field[0] and field[1]. |
| 850 | * 2. execute command 0x30. |
| 851 | * 3. if field[0] is NOT changed, that means 0x30 is not supported, |
| 852 | * use initflash_cfg->status_reg_value instead. |
| 853 | * else, 0x30 works and returns field[]. |
| 854 | */ |
| 855 | wcb->field[0] = ~initflash_cfg->status_reg_value; |
| 856 | wcb->field[1] = 0xfc; /* set reserved bits to 1s */ |
| 857 | /* save original values */ |
| 858 | for (i = 0; i < ARRAY_SIZE(before); i++) |
| 859 | before[i] = wcb->field[i]; |
| 860 | |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 861 | wcb->code = 0x30; /* JEDEC_RDSR */ |
| 862 | if (blocked_exec()) { |
| 863 | ret = 1; |
Louis Yung-Chieh Lo | f6f03c4 | 2011-03-30 17:24:49 +0800 | [diff] [blame] | 864 | msg_perr("%s(): blocked_exec() returns error.\n", __func__); |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 865 | } |
| 866 | msg_pdbg("%s(): WCB code=0x%02x field[]= ", __func__, wcb->code); |
| 867 | for (i = 0; i < readcnt; ++i) { |
| 868 | readarr[i] = wcb->field[i]; |
| 869 | msg_pdbg("%02x ", wcb->field[i]); |
| 870 | } |
| 871 | msg_pdbg("\n"); |
Louis Yung-Chieh Lo | f6f03c4 | 2011-03-30 17:24:49 +0800 | [diff] [blame] | 872 | |
| 873 | /* FIXME: not sure EC returns 1 or 2 bytes for command 0x30, |
| 874 | * now we check field[0] only. |
| 875 | * Shall check field[1] if EC always returns 2 bytes. */ |
| 876 | if (wcb->field[0] == before[0]) { |
| 877 | /* field is not changed, 0x30 command doesn't work. */ |
| 878 | readarr[0] = initflash_cfg->status_reg_value; |
| 879 | readarr[1] = 0; /* TODO: if second status register exists */ |
| 880 | msg_pdbg("%s(): command 0x30 seems NOT working.\n", __func__); |
| 881 | } else { |
| 882 | /* 0x30 command seems working! */ |
| 883 | initflash_cfg->status_reg_value = readarr[0]; |
| 884 | msg_pdbg("%s(): command 0x30 seems working.\n", __func__); |
| 885 | } |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 886 | |
| 887 | return ret; |
| 888 | } |
| 889 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 890 | int wpce775x_spi_write_status_register(uint8_t val) |
| 891 | { |
| 892 | assert_ec_is_free(); |
| 893 | msg_pdbg("%s(): writing 0x%02x to status register\n", __func__, val); |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 894 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 895 | if (!initflash_cfg) |
| 896 | initflash_cfg_setup(flash_internal); |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 897 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 898 | initflash_cfg->status_reg_value = val; |
| 899 | if (in_flash_update_mode) { |
| 900 | ExitFlashUpdateFirmwareNoChange(); |
| 901 | in_flash_update_mode = 0; |
| 902 | } |
| 903 | if (InitFlash()) |
| 904 | return 1; |
Louis Yung-Chieh Lo | 623809c | 2010-11-30 15:43:18 +0800 | [diff] [blame] | 905 | if (EnterFlashUpdate()) |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 906 | return 1; |
Louis Yung-Chieh Lo | 623809c | 2010-11-30 15:43:18 +0800 | [diff] [blame] | 907 | ExitFlashUpdateFirmwareNoChange(); |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 908 | return 0; |
| 909 | } |
| 910 | |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 911 | /* |
| 912 | * WPCE775x does not allow direct access to SPI chip from host. This function |
| 913 | * will translate SPI commands to valid WPCE775x WCB commands. |
Louis Yung-Chieh Lo | 623809c | 2010-11-30 15:43:18 +0800 | [diff] [blame] | 914 | */ |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 915 | int wpce775x_spi_send_command(unsigned int writecnt, unsigned int readcnt, |
| 916 | const unsigned char *writearr, unsigned char *readarr) |
David Hendricks | 21e0630 | 2010-09-03 14:51:46 -0700 | [diff] [blame] | 917 | { |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 918 | int rc = 0; |
| 919 | uint8_t opcode = writearr[0]; |
| 920 | |
| 921 | switch(opcode){ |
| 922 | case JEDEC_RDID:{ |
| 923 | unsigned char dummy = 0; |
| 924 | if (readcnt == 3) |
| 925 | ReadId(&readarr[0], &readarr[1], &readarr[2], &dummy); |
| 926 | else if (readcnt == 4) |
| 927 | ReadId(&readarr[0], &readarr[1], &readarr[2], &readarr[3]); |
| 928 | break; |
| 929 | } |
| 930 | case JEDEC_RDSR: |
Louis Yung-Chieh Lo | a915bb1 | 2011-03-30 09:47:40 +0800 | [diff] [blame] | 931 | rc = wpce775x_spi_read_status_register(readcnt, readarr); |
David Hendricks | c801adb | 2010-12-09 16:58:56 -0800 | [diff] [blame] | 932 | break; |
| 933 | case JEDEC_READ:{ |
| 934 | int blockaddr = (writearr[1] << 16) | |
| 935 | (writearr[2] << 8) | |
| 936 | writearr[3]; |
| 937 | rc = wpce775x_read(blockaddr, readarr, readcnt); |
| 938 | break; |
| 939 | } |
| 940 | case JEDEC_WRSR: |
| 941 | wpce775x_spi_write_status_register(writearr[1]); |
| 942 | rc = 0; |
| 943 | break; |
| 944 | case JEDEC_WREN: |
| 945 | case JEDEC_EWSR: |
| 946 | /* Handled by InitFlash() */ |
| 947 | rc = 0; |
| 948 | break; |
| 949 | case JEDEC_SE: |
| 950 | case JEDEC_BE_52: |
| 951 | case JEDEC_BE_D7: |
| 952 | case JEDEC_BE_D8: |
| 953 | case JEDEC_CE_60: |
| 954 | case JEDEC_CE_C7:{ |
| 955 | int blockaddr = (writearr[1] << 16) | |
| 956 | (writearr[2] << 8) | |
| 957 | writearr[3]; |
| 958 | |
| 959 | rc = wpce775x_erase_new(blockaddr, opcode); |
| 960 | break; |
| 961 | } |
| 962 | case JEDEC_BYTE_PROGRAM:{ |
| 963 | int blockaddr = (writearr[1] << 16) | |
| 964 | (writearr[2] << 8) | |
| 965 | writearr[3]; |
| 966 | int nbytes = writecnt - 4; |
| 967 | |
| 968 | rc = wpce775x_nbyte_program(blockaddr, &writearr[4], nbytes); |
| 969 | break; |
| 970 | } |
| 971 | case JEDEC_REMS: |
| 972 | case JEDEC_RES: |
| 973 | case JEDEC_WRDI: |
| 974 | case JEDEC_AAI_WORD_PROGRAM: |
| 975 | default: |
| 976 | /* unsupported opcodes */ |
| 977 | msg_pdbg("unsupported SPI opcode: %02x\n", opcode); |
| 978 | rc = 1; |
| 979 | break; |
| 980 | } |
| 981 | |
| 982 | msg_pdbg("%s: opcode: 0x%02x\n", __func__, opcode); |
| 983 | return rc; |
Louis Yung-Chieh Lo | 623809c | 2010-11-30 15:43:18 +0800 | [diff] [blame] | 984 | } |
David Hendricks | cb2cec3 | 2011-03-24 18:44:49 -0700 | [diff] [blame] | 985 | #endif |