| /* |
| * This file is part of the flashrom project. |
| * |
| * Copyright (C) 2010-2020, Google Inc. |
| * All rights reserved. |
| * |
| * 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 Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Alternatively, this software may be distributed under the terms of the |
| * GNU General Public License ("GPL") version 2 as published by the Free |
| * Software Foundation. |
| */ |
| |
| #if defined(__i386__) || defined(__x86_64__) |
| #include <inttypes.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "flash.h" |
| #include "hwaccess.h" |
| #include "chipdrivers.h" |
| #include "programmer.h" |
| #include "spi.h" |
| |
| #define MEC1308_SIO_PORT1 0x2e |
| #define MEC1308_SIO_PORT2 0x4e |
| #define MEC1308_SIO_ENTRY_KEY 0x55 |
| #define MEC1308_SIO_EXIT_KEY 0xaa |
| |
| #define MEC1308_SIOCFG_LDN 0x07 /* LDN Bank Selector */ |
| #define MEC1308_DEVICE_ID_REG 0x20 /* Device ID Register */ |
| #define MEC1308_DEVICE_ID_VAL 0x4d /* Device ID Value for MEC1308 */ |
| #define MEC1310_DEVICE_ID_VAL 0x04 /* Device ID Value for MEC1310 */ |
| #define MEC1308_DEVICE_REV 0x21 /* Device Revision ID Register */ |
| |
| #define MEC1308_MBX_CMD 0x82 /* mailbox command register offset */ |
| #define MEC1308_MBX_EXT_CMD 0x83 /* mailbox ext. command reg offset */ |
| #define MEC1308_MBX_DATA_START 0x84 /* first mailbox data register offset */ |
| #define MEC1308_MBX_DATA_END 0x91 /* last mailbox data register offset */ |
| |
| static unsigned int mbx_data; /* Mailbox register interface data address*/ |
| |
| /* |
| * These command codes depend on EC firmware. The ones listed below are input |
| * using the mailbox interface, though others may be input using the ACPI |
| * interface. Some commands also have an output value (ie pass/failure code) |
| * which EC writes to the mailbox command register after completion. |
| */ |
| #define MEC1308_CMD_SMI_ENABLE 0x84 |
| #define MEC1308_CMD_SMI_DISABLE 0x85 |
| #define MEC1308_CMD_ACPI_ENABLE 0x86 |
| #define MEC1308_CMD_ACPI_DISABLE 0x87 |
| |
| /* |
| * Passthru commands are also input using the mailbox interface. Passthru mode |
| * enter/start/end commands are special since they require a command word to |
| * be written to the data registers. Other passthru commands are performed |
| * after passthru mode has been started. |
| * |
| * Multiple passthru mode commands may be issued before ending passthru mode. |
| * You do not need to enter, start, and end passthru mode for each SPI |
| * command. However, other mailbox commands might not work when passthru mode |
| * is enabled. For example, you may read all SPI chip content while in passthru |
| * mode, but you should exit passthru mode before performing other EC commands |
| * such as reading fan speed. |
| */ |
| #define MEC1308_CMD_PASSTHRU 0x55 /* force EC to process word */ |
| #define MEC1308_CMD_PASSTHRU_SUCCESS 0xaa /* success code for passthru */ |
| #define MEC1308_CMD_PASSTHRU_FAIL 0xfe /* failure code for passthru */ |
| #define MEC1308_CMD_PASSTHRU_ENTER "PathThruMode" /* not a typo... */ |
| #define MEC1308_CMD_PASSTHRU_START "Start" |
| #define MEC1308_CMD_PASSTHRU_EXIT "End_Mode" |
| #define MEC1308_CMD_PASSTHRU_CS_EN 0xf0 /* chip-select enable */ |
| #define MEC1308_CMD_PASSTHRU_CS_DIS 0xf1 /* chip-select disable */ |
| #define MEC1308_CMD_PASSTHRU_SEND 0xf2 /* send byte from data0 */ |
| #define MEC1308_CMD_PASSTHRU_READ 0xf3 /* read byte, place in data0 */ |
| |
| typedef struct |
| { |
| unsigned int in_sio_cfgmode; |
| unsigned int mbx_idx; /* Mailbox register interface index address */ |
| unsigned int mbx_data; /* Mailbox register interface data address*/ |
| } mec1308_data_t; |
| |
| static void mec1308_sio_enter(mec1308_data_t *ctx_data, uint16_t port) |
| { |
| if (ctx_data->in_sio_cfgmode) |
| return; |
| |
| OUTB(MEC1308_SIO_ENTRY_KEY, port); |
| ctx_data->in_sio_cfgmode = 1; |
| } |
| |
| static void mec1308_sio_exit(mec1308_data_t *ctx_data, uint16_t port) |
| { |
| if (!ctx_data->in_sio_cfgmode) |
| return; |
| |
| OUTB(MEC1308_SIO_EXIT_KEY, port); |
| ctx_data->in_sio_cfgmode = 0; |
| } |
| |
| /** probe for super i/o index |
| * @port: allocated buffer to store port |
| * |
| * returns 0 to indicate success, <0 to indicate error |
| */ |
| static int mec1308_get_sio_index(mec1308_data_t *ctx_data, uint16_t *port) |
| { |
| uint16_t ports[] = { MEC1308_SIO_PORT1, |
| MEC1308_SIO_PORT2, |
| }; |
| size_t 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 tmp8; |
| |
| /* |
| * Only after config mode has been successfully entered will the |
| * index port will read back the last value written to it. |
| * So we will attempt to enter config mode, set the index |
| * register, and see if the index register retains the value. |
| * |
| * Note: It seems to work "best" when using a device ID register |
| * as the index and reading from the data port before reading |
| * the index port. |
| */ |
| mec1308_sio_enter(ctx_data, ports[i]); |
| OUTB(MEC1308_DEVICE_ID_REG, ports[i]); |
| tmp8 = INB(ports[i] + 1); |
| tmp8 = INB(ports[i]); |
| if ((tmp8 != MEC1308_DEVICE_ID_REG)) { |
| ctx_data->in_sio_cfgmode = 0; |
| continue; |
| } |
| |
| 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; |
| } |
| |
| static uint8_t mbx_read(mec1308_data_t *ctx_data, uint8_t idx) |
| { |
| OUTB(idx, ctx_data->mbx_idx); |
| return INB(mbx_data); |
| } |
| |
| static int mbx_wait(mec1308_data_t *ctx_data) |
| { |
| int i; |
| int max_attempts = 10000; |
| int rc = 0; |
| |
| for (i = 0; mbx_read(ctx_data, MEC1308_MBX_CMD); i++) { |
| if (i == max_attempts) { |
| rc = 1; |
| break; |
| } |
| /* FIXME: This delay adds determinism to the delay period. It |
| was chosen arbitrarily thru some experiments. */ |
| programmer_delay(2); |
| } |
| |
| return rc; |
| } |
| |
| static int mbx_write(mec1308_data_t *ctx_data, uint8_t idx, uint8_t data) |
| { |
| int rc = 0; |
| |
| if (idx == MEC1308_MBX_CMD && mbx_wait(ctx_data)) { |
| msg_perr("%s: command register not clear\n", __func__); |
| return 1; |
| } |
| |
| OUTB(idx, ctx_data->mbx_idx); |
| OUTB(data, mbx_data); |
| |
| if (idx == MEC1308_MBX_CMD) |
| rc = mbx_wait(ctx_data); |
| |
| return rc; |
| } |
| |
| static void mbx_clear(mec1308_data_t *ctx_data) |
| { |
| int reg; |
| |
| for (reg = MEC1308_MBX_DATA_START; reg < MEC1308_MBX_DATA_END; reg++) |
| mbx_write(ctx_data, reg, 0x00); |
| mbx_write(ctx_data, MEC1308_MBX_CMD, 0x00); |
| } |
| |
| static int mec1308_exit_passthru_mode(mec1308_data_t *ctx_data) |
| { |
| uint8_t tmp8; |
| size_t i; |
| |
| /* exit passthru mode */ |
| for (i = 0; i < strlen(MEC1308_CMD_PASSTHRU_EXIT); i++) { |
| mbx_write(ctx_data, MEC1308_MBX_DATA_START + i, |
| MEC1308_CMD_PASSTHRU_EXIT[i]); |
| } |
| |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU)) { |
| msg_pdbg("%s(): exit passthru command timed out\n", __func__); |
| return 1; |
| } |
| |
| tmp8 = mbx_read(ctx_data, MEC1308_MBX_DATA_START); |
| msg_pdbg("%s: result: 0x%02x ", __func__, tmp8); |
| if (tmp8 == MEC1308_CMD_PASSTHRU_SUCCESS) { |
| msg_pdbg("(exited passthru mode)\n"); |
| } else if (tmp8 == MEC1308_CMD_PASSTHRU_FAIL) { |
| msg_pdbg("(failed to exit passthru mode)\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int enter_passthru_mode(mec1308_data_t *ctx_data) |
| { |
| uint8_t tmp8; |
| size_t i; |
| |
| /* |
| * Enter passthru mode. If the EC does not successfully enter passthru |
| * mode the first time, we'll clear the mailbox and issue the "exit |
| * passthru mode" command sequence up to 3 times or until it arrives in |
| * a known state. |
| * |
| * Note: This workaround was developed experimentally. |
| */ |
| for (i = 0; i < 3; i++) { |
| size_t j; |
| |
| msg_pdbg("%s(): entering passthru mode, attempt %d out of 3\n", |
| __func__, (int)(i + 1)); |
| for (j = 0; j < strlen(MEC1308_CMD_PASSTHRU_ENTER); j++) { |
| mbx_write(ctx_data, MEC1308_MBX_DATA_START + j, |
| MEC1308_CMD_PASSTHRU_ENTER[j]); |
| } |
| |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU)) |
| msg_pdbg("%s(): enter passthru command timed out\n", |
| __func__); |
| |
| tmp8 = mbx_read(ctx_data, MEC1308_MBX_DATA_START); |
| if (tmp8 == MEC1308_CMD_PASSTHRU_SUCCESS) |
| break; |
| |
| msg_pdbg("%s(): command failed, clearing data registers and " |
| "issuing full exit passthru command...\n", __func__); |
| mbx_clear(ctx_data); |
| mec1308_exit_passthru_mode(ctx_data); |
| } |
| |
| if (tmp8 != MEC1308_CMD_PASSTHRU_SUCCESS) { |
| msg_perr("%s(): failed to enter passthru mode, result=0x%02x\n", |
| __func__, tmp8); |
| return 1; |
| } |
| |
| msg_pdbg("%s(): enter passthru mode return code: 0x%02x\n", |
| __func__, tmp8); |
| |
| /* start passthru mode */ |
| for (i = 0; i < strlen(MEC1308_CMD_PASSTHRU_START); i++) |
| mbx_write(ctx_data, MEC1308_MBX_DATA_START + i, |
| MEC1308_CMD_PASSTHRU_START[i]); |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU)) { |
| msg_pdbg("%s(): start passthru command timed out\n", __func__); |
| return 1; |
| } |
| tmp8 = mbx_read(ctx_data, MEC1308_MBX_DATA_START); |
| if (tmp8 != MEC1308_CMD_PASSTHRU_SUCCESS) { |
| msg_perr("%s(): failed to enter passthru mode, result=%02x\n", |
| __func__, tmp8); |
| return 1; |
| } |
| msg_pdbg("%s(): start passthru mode return code: 0x%02x\n", |
| __func__, tmp8); |
| |
| return 0; |
| } |
| |
| static int mec1308_shutdown(void *data) |
| { |
| mec1308_data_t *ctx_data = (mec1308_data_t *)data; |
| |
| /* Exit passthru mode before performing commands which do not affect |
| the SPI ROM */ |
| mec1308_exit_passthru_mode(ctx_data); |
| |
| /* Re-enable SMI and ACPI. |
| FIXME: is there an ordering dependency? */ |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_SMI_ENABLE)) |
| msg_pdbg("%s: unable to re-enable SMI\n", __func__); |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_ACPI_ENABLE)) |
| msg_pdbg("%s: unable to re-enable ACPI\n", __func__); |
| |
| free(data); |
| return 0; |
| } |
| |
| static int mec1308_chip_select(mec1308_data_t *ctx_data) |
| { |
| return mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU_CS_EN); |
| } |
| |
| static int mec1308_chip_deselect(mec1308_data_t *ctx_data) |
| { |
| return mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU_CS_DIS); |
| } |
| |
| /* |
| * MEC1308 will not allow direct access to SPI chip from host if EC is |
| * connected to LPC bus. This function will forward commands issued thru |
| * mailbox interface to the SPI flash chip. |
| */ |
| static int mec1308_spi_send_command(const struct flashctx *flash, unsigned int writecnt, |
| unsigned int readcnt, |
| const unsigned char *writearr, |
| unsigned char *readarr) |
| { |
| int rc = 0; |
| size_t i; |
| mec1308_data_t *ctx_data = (mec1308_data_t *)flash->mst->spi.data; |
| |
| if (mec1308_chip_select(ctx_data)) |
| return 1; |
| |
| for (i = 0; i < writecnt; i++) { |
| if (mbx_write(ctx_data, MEC1308_MBX_DATA_START, writearr[i]) || |
| mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU_SEND)) { |
| msg_pdbg("%s: failed to issue send command\n",__func__); |
| rc = 1; |
| goto mec1308_spi_send_command_exit; |
| } |
| } |
| |
| for (i = 0; i < readcnt; i++) { |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_PASSTHRU_READ)) { |
| msg_pdbg("%s: failed to issue read command\n",__func__); |
| rc = 1; |
| goto mec1308_spi_send_command_exit; |
| } |
| readarr[i] = mbx_read(ctx_data, MEC1308_MBX_DATA_START); |
| } |
| |
| mec1308_spi_send_command_exit: |
| rc |= mec1308_chip_deselect(ctx_data); |
| return rc; |
| } |
| |
| static struct spi_master spi_master_mec1308 = { |
| .max_data_read = 256, /* FIXME: should be MAX_DATA_READ_UNLIMITED? */ |
| .max_data_write = 256, /* FIXME: should be MAX_DATA_WRITE_UNLIMITED? */ |
| .command = mec1308_spi_send_command, |
| .multicommand = default_spi_send_multicommand, |
| .read = default_spi_read, |
| .write_256 = default_spi_write_256, |
| }; |
| |
| int mec1308_init(void) |
| { |
| uint16_t sio_port; |
| uint8_t device_id; |
| uint8_t tmp8; |
| int ret = 0; |
| char *p = NULL; |
| mec1308_data_t *ctx_data = NULL; |
| |
| msg_pdbg("%s(): entered\n", __func__); |
| |
| ctx_data = calloc(1, sizeof(mec1308_data_t)); |
| if (!ctx_data) { |
| msg_perr("Unable to allocate space for extra context data.\n"); |
| return 1; |
| } |
| |
| p = extract_programmer_param("type"); |
| if (p && strcmp(p, "ec")) { |
| msg_pdbg("mec1308 only supports \"ec\" type devices\n"); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| if (mec1308_get_sio_index(ctx_data, &sio_port) < 0) { |
| msg_pdbg("MEC1308 not found (probe failed).\n"); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| device_id = sio_read(sio_port, MEC1308_DEVICE_ID_REG); |
| switch(device_id) { |
| case MEC1308_DEVICE_ID_VAL: |
| msg_pdbg("Found EC: MEC1308 (ID:0x%02x,Rev:0x%02x) on " |
| "sio_port:0x%x.\n", device_id, |
| sio_read(sio_port, MEC1308_DEVICE_REV), sio_port); |
| break; |
| case MEC1310_DEVICE_ID_VAL: |
| msg_pdbg("Found EC: MEC1310 (ID:0x%02x,Rev:0x%02x) on " |
| "sio_port:0x%x.\n", device_id, |
| sio_read(sio_port, MEC1308_DEVICE_REV), sio_port); |
| break; |
| default: |
| msg_pdbg("MEC1308 not found\n"); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| /* |
| * setup mailbox interface at LDN 9 |
| */ |
| sio_write(sio_port, MEC1308_SIOCFG_LDN, 0x09); |
| tmp8 = sio_read(sio_port, 0x30); |
| tmp8 |= 1; |
| sio_write(sio_port, 0x30, tmp8); /* activate logical device */ |
| |
| ctx_data->mbx_idx = (unsigned int)sio_read(sio_port, 0x60) << 8 | |
| sio_read(sio_port, 0x61); |
| mbx_data = ctx_data->mbx_idx + 1; |
| msg_pdbg("%s: mbx_idx: 0x%04x, mbx_data: 0x%04x\n", |
| __func__, ctx_data->mbx_idx, mbx_data); |
| |
| /* Exit Super I/O config mode */ |
| mec1308_sio_exit(ctx_data, sio_port); |
| |
| /* Now that we can read the mailbox, we will wait for any remaining |
| * command to finish.*/ |
| if (mbx_wait(ctx_data) != 0) { |
| msg_perr("%s: mailbox is not available\n", __func__); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| /* Further setup -- disable SMI and ACPI. |
| FIXME: is there an ordering dependency? */ |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_ACPI_DISABLE)) { |
| msg_pdbg("%s: unable to disable ACPI\n", __func__); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| if (mbx_write(ctx_data, MEC1308_MBX_CMD, MEC1308_CMD_SMI_DISABLE)) { |
| msg_pdbg("%s: unable to disable SMI\n", __func__); |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| if (register_shutdown(mec1308_shutdown, ctx_data)) { |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| /* |
| * Enter SPI Pass-Thru Mode after commands which do not require access |
| * to SPI ROM are complete. We'll start by doing the exit_passthru_mode |
| * sequence, which is benign if the EC is already in passthru mode. |
| */ |
| mec1308_exit_passthru_mode(ctx_data); |
| |
| if (enter_passthru_mode(ctx_data)) { |
| ret = 1; |
| goto mec1308_init_exit; |
| } |
| |
| internal_buses_supported |= BUS_LPC; /* for LPC <--> SPI bridging */ |
| spi_master_mec1308.data = ctx_data; |
| register_spi_master(&spi_master_mec1308); |
| msg_pdbg("%s(): successfully initialized mec1308\n", __func__); |
| |
| mec1308_init_exit: |
| free(p); |
| if (ret) |
| free(ctx_data); |
| return ret; |
| } |
| #endif |