mirror of
https://review.coreboot.org/flashrom.git
synced 2025-04-27 15:12:36 +02:00

This allows spi masters to register shutdown function in spi_master struct, which means there is no need to call register_shutdown in init function, since this call is now a part of register_spi_master. As a consequence of using new API, two things are happening here: 1) No resource leakage anymore in case register_shutdown() would fail, 2) Fixed propagation of register_spi_master() return values. Basic testing: when I comment out free(data) in linux_spi_shutdown, test fails with error ../linux_spi.c:235: note: block 0x55a4db276510 allocated here ERROR: linux_spi_init_and_shutdown_test_success leaked 1 block(s) Means, shutdown function is invoked. BUG=b:185191942 TEST= 1) builds and ninja test including CB:56911 2) On ARMv7 device flashrom -p linux_spi -V -> using linux_spi, chip found 3) On x86_64 AMD device flashrom -p internal -V -> this is actually using sb600spi, chip found Change-Id: Ib60300f9ddb295a255d5ef3f8da0e07064207140 Signed-off-by: Anastasia Klimchuk <aklm@chromium.org> Reviewed-on: https://review.coreboot.org/c/flashrom/+/56103 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Nico Huber <nico.h@gmx.de> Reviewed-by: Angel Pons <th3fanbus@gmail.com> Reviewed-by: Edward O'Callaghan <quasisec@chromium.org>
592 lines
14 KiB
C
592 lines
14 KiB
C
/*
|
|
* This file is part of the flashrom project.
|
|
*
|
|
* Copyright (C) 2012-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 <sys/time.h>
|
|
|
|
#include "chipdrivers.h"
|
|
#include "flash.h"
|
|
#include "programmer.h"
|
|
#include "hwaccess.h"
|
|
#include "spi.h"
|
|
|
|
/* MCU registers */
|
|
#define REG_EC_HWVER 0xff00
|
|
#define REG_EC_FWVER 0xff01
|
|
#define REG_EC_EDIID 0xff24
|
|
#define REG_8051_CTRL 0xff14
|
|
#define REG_EC_EXTCMD 0xff10
|
|
|
|
#define CPU_RESET 1
|
|
|
|
/* MCU SPI peripheral registers */
|
|
#define REG_SPI_DATA 0xfeab
|
|
#define REG_SPI_COMMAND 0xfeac
|
|
#define REG_SPI_CONFIG 0xfead
|
|
|
|
#define CFG_CSn_FORCE_LOW (1 << 4)
|
|
#define CFG_COMMAND_WRITE_ENABLE (1 << 3)
|
|
#define CFG_STATUS (1 << 1)
|
|
#define CFG_ENABLE_BUSY_STATUS_CHECK (1 << 0)
|
|
|
|
/* Timeout */
|
|
#define EC_COMMAND_TIMEOUT 4
|
|
#define EC_RESTART_TIMEOUT 10
|
|
#define ENE_SPI_DELAY_CYCLE 4
|
|
#define EC_PAUSE_TIMEOUT 12
|
|
#define EC_RESET_TRIES 3
|
|
|
|
#define ENE_KB94X_PAUSE_WAKEUP_PORT 0x64
|
|
|
|
#define MASK_INPUT_BUFFER_FULL 2
|
|
#define MASK_OUTPUT_BUFFER_FULL 1
|
|
|
|
const int port_ene_bank = 1;
|
|
const int port_ene_offset = 2;
|
|
const int port_ene_data = 3;
|
|
|
|
/* Supported ENE ECs, ENE_LAST should always be LAST member */
|
|
enum ene_chip_id {
|
|
ENE_KB932 = 0,
|
|
ENE_KB94X,
|
|
ENE_LAST
|
|
};
|
|
|
|
/* EC state */
|
|
enum ene_ec_state {
|
|
EC_STATE_NORMAL,
|
|
EC_STATE_IDLE,
|
|
EC_STATE_RESET,
|
|
EC_STATE_UNKNOWN
|
|
};
|
|
|
|
/* chip-specific parameters */
|
|
typedef struct {
|
|
enum ene_chip_id chip_id;
|
|
uint8_t hwver;
|
|
uint8_t ediid;
|
|
uint32_t port_bios;
|
|
uint32_t port_ec_command;
|
|
uint32_t port_ec_data;
|
|
uint8_t ec_reset_cmd;
|
|
uint8_t ec_reset_data;
|
|
uint8_t ec_restart_cmd;
|
|
uint8_t ec_restart_data;
|
|
uint8_t ec_pause_cmd;
|
|
uint8_t ec_pause_data;
|
|
uint16_t ec_status_buf;
|
|
uint8_t ec_is_stopping;
|
|
uint8_t ec_is_running;
|
|
uint8_t ec_is_pausing;
|
|
uint32_t port_io_base;
|
|
} ene_chip_t;
|
|
|
|
typedef struct
|
|
{
|
|
/* pointer to table entry of identified chip */
|
|
ene_chip_t *chip;
|
|
/* current ec state */
|
|
enum ene_ec_state ec_state;
|
|
struct timeval pause_begin;
|
|
} ene_lpc_data_t;
|
|
|
|
/* table of supported chips + parameters */
|
|
static ene_chip_t ene_chips[] = {
|
|
{
|
|
ENE_KB932, /* chip_id */
|
|
0xa2, 0x02, /* hwver + ediid */
|
|
0x66, /* port_bios */
|
|
0x6c, 0x68, /* port_ec_{command,data} */
|
|
0x59, 0xf2, /* ec_reset_{cmd,data} */
|
|
0x59, 0xf9, /* ec_restart_{cmd,data} */
|
|
0x59, 0xf1, /* ec_pause_{cmd,data} */
|
|
0xf554, /* ec_status_buf */
|
|
0xa5, 0x00, /* ec_is_{stopping,running} masks */
|
|
0x33, /* ec_is_pausing mask */
|
|
0xfd60 /* port_io_base */
|
|
},
|
|
{
|
|
ENE_KB94X, /* chip_id */
|
|
0xa3, 0x05, /* hwver + ediid */
|
|
0x66, /* port_bios */
|
|
0x66, 0x68, /* port_ec_{command,data} */
|
|
0x7d, 0x10, /* ec_reset_{cmd,data} */
|
|
0x7f, 0x10, /* ec_restart_{cmd,data} */
|
|
0x7e, 0x10, /* ec_pause_{cmd,data} */
|
|
0xf710, /* ec_status_buf */
|
|
0x02, 0x00, /* ec_is_{stopping,running} masks */
|
|
0x01, /* ec_is_pausing mask */
|
|
0x0380 /* port_io_base */
|
|
}
|
|
};
|
|
|
|
static void ec_command(const ene_chip_t *chip, uint8_t cmd, uint8_t data)
|
|
{
|
|
struct timeval begin, now;
|
|
|
|
/* Spin wait for EC input buffer empty */
|
|
gettimeofday(&begin, NULL);
|
|
while (INB(chip->port_ec_command) & MASK_INPUT_BUFFER_FULL) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: buf not empty\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Write command */
|
|
OUTB(cmd, chip->port_ec_command);
|
|
|
|
if (chip->chip_id == ENE_KB932) {
|
|
/* Spin wait for EC input buffer empty */
|
|
gettimeofday(&begin, NULL);
|
|
while (INB(chip->port_ec_command) & MASK_INPUT_BUFFER_FULL) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: buf not empty\n", __func__);
|
|
return;
|
|
}
|
|
}
|
|
/* Write data */
|
|
OUTB(data, chip->port_ec_data);
|
|
}
|
|
}
|
|
|
|
static uint8_t ene_read(const ene_chip_t *chip, uint16_t addr)
|
|
{
|
|
uint8_t bank;
|
|
uint8_t offset;
|
|
uint8_t data;
|
|
uint32_t port_io_base;
|
|
|
|
bank = addr >> 8;
|
|
offset = addr & 0xff;
|
|
port_io_base = chip->port_io_base;
|
|
|
|
OUTB(bank, port_io_base + port_ene_bank);
|
|
OUTB(offset, port_io_base + port_ene_offset);
|
|
data = INB(port_io_base + port_ene_data);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void ene_write(const ene_chip_t *chip, uint16_t addr, uint8_t data)
|
|
{
|
|
uint8_t bank;
|
|
uint8_t offset;
|
|
uint32_t port_io_base;
|
|
|
|
bank = addr >> 8;
|
|
offset = addr & 0xff;
|
|
port_io_base = chip->port_io_base;
|
|
|
|
OUTB(bank, port_io_base + port_ene_bank);
|
|
OUTB(offset, port_io_base + port_ene_offset);
|
|
|
|
OUTB(data, port_io_base + port_ene_data);
|
|
}
|
|
|
|
/**
|
|
* wait_cycles, wait for n LPC bus clock cycles
|
|
*
|
|
* @param n: number of LPC cycles to wait
|
|
* @return void
|
|
*/
|
|
static void wait_cycles(const ene_chip_t *chip,int n)
|
|
{
|
|
while (n--)
|
|
INB(chip->port_io_base + port_ene_bank);
|
|
}
|
|
|
|
static int is_spicmd_write(uint8_t cmd)
|
|
{
|
|
switch (cmd) {
|
|
case JEDEC_WREN:
|
|
/* Chip Write Enable */
|
|
case JEDEC_EWSR:
|
|
/* Write Status Enable */
|
|
case JEDEC_CE_60:
|
|
/* Chip Erase 0x60 */
|
|
case JEDEC_CE_C7:
|
|
/* Chip Erase 0xc7 */
|
|
case JEDEC_BE_52:
|
|
/* Block Erase 0x52 */
|
|
case JEDEC_BE_D8:
|
|
/* Block Erase 0xd8 */
|
|
case JEDEC_BE_D7:
|
|
/* Block Erase 0xd7 */
|
|
case JEDEC_SE:
|
|
/* Sector Erase */
|
|
case JEDEC_BYTE_PROGRAM:
|
|
/* Write memory byte */
|
|
case JEDEC_AAI_WORD_PROGRAM:
|
|
/* Write AAI word */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ene_spi_start(const ene_chip_t *chip)
|
|
{
|
|
int cfg;
|
|
|
|
cfg = ene_read(chip, REG_SPI_CONFIG);
|
|
cfg |= CFG_CSn_FORCE_LOW;
|
|
cfg |= CFG_COMMAND_WRITE_ENABLE;
|
|
ene_write(chip, REG_SPI_CONFIG, cfg);
|
|
|
|
wait_cycles(chip, ENE_SPI_DELAY_CYCLE);
|
|
}
|
|
|
|
static void ene_spi_end(const ene_chip_t *chip)
|
|
{
|
|
int cfg;
|
|
|
|
cfg = ene_read(chip, REG_SPI_CONFIG);
|
|
cfg &= ~CFG_CSn_FORCE_LOW;
|
|
cfg |= CFG_COMMAND_WRITE_ENABLE;
|
|
ene_write(chip, REG_SPI_CONFIG, cfg);
|
|
|
|
wait_cycles(chip, ENE_SPI_DELAY_CYCLE);
|
|
}
|
|
|
|
static int ene_spi_wait(const ene_chip_t *chip)
|
|
{
|
|
struct timeval begin, now;
|
|
|
|
gettimeofday(&begin, NULL);
|
|
while (ene_read(chip, REG_SPI_CONFIG) & CFG_STATUS) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: spi busy\n", __func__);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ene_pause_ec(ene_lpc_data_t *ctx_data)
|
|
{
|
|
struct timeval begin, now;
|
|
const ene_chip_t *chip = ctx_data->chip;
|
|
|
|
if (!chip->ec_pause_cmd)
|
|
return -1;
|
|
|
|
/* EC prepare pause */
|
|
ec_command(chip, chip->ec_pause_cmd, chip->ec_pause_data);
|
|
|
|
gettimeofday(&begin, NULL);
|
|
/* Spin wait for EC ready */
|
|
while (ene_read(chip, chip->ec_status_buf) != chip->ec_is_pausing) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: unable to pause ec\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
gettimeofday(&ctx_data->pause_begin, NULL);
|
|
ctx_data->ec_state = EC_STATE_IDLE;
|
|
return 0;
|
|
}
|
|
|
|
static int ene_resume_ec(ene_lpc_data_t *ctx_data)
|
|
{
|
|
struct timeval begin, now;
|
|
const ene_chip_t *chip = ctx_data->chip;
|
|
|
|
if (chip->chip_id == ENE_KB94X)
|
|
OUTB(0xff, ENE_KB94X_PAUSE_WAKEUP_PORT);
|
|
else
|
|
/* Trigger 8051 interrupt to resume */
|
|
ene_write(chip, REG_EC_EXTCMD, 0xff);
|
|
|
|
gettimeofday(&begin, NULL);
|
|
while (ene_read(chip, chip->ec_status_buf) != chip->ec_is_running) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: unable to resume ec\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ctx_data->ec_state = EC_STATE_NORMAL;
|
|
return 0;
|
|
}
|
|
|
|
static int ene_pause_timeout_check(ene_lpc_data_t *ctx_data)
|
|
{
|
|
struct timeval pause_now;
|
|
gettimeofday(&pause_now, NULL);
|
|
if (pause_now.tv_sec - ctx_data->pause_begin.tv_sec >= EC_PAUSE_TIMEOUT) {
|
|
if (ene_resume_ec(ctx_data) == 0)
|
|
ene_pause_ec(ctx_data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ene_reset_ec(ene_lpc_data_t *ctx_data)
|
|
{
|
|
uint8_t reg;
|
|
struct timeval begin, now;
|
|
const ene_chip_t *chip = ctx_data->chip;
|
|
|
|
gettimeofday(&begin, NULL);
|
|
|
|
/* EC prepare reset */
|
|
ec_command(chip, chip->ec_reset_cmd, chip->ec_reset_data);
|
|
|
|
/* Spin wait for EC ready */
|
|
while (ene_read(chip, chip->ec_status_buf) != chip->ec_is_stopping) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_COMMAND_TIMEOUT) {
|
|
msg_pdbg("%s: unable to reset ec\n", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Wait 1 second */
|
|
sleep(1);
|
|
|
|
/* Reset 8051 */
|
|
reg = ene_read(chip, REG_8051_CTRL);
|
|
reg |= CPU_RESET;
|
|
ene_write(chip, REG_8051_CTRL, reg);
|
|
|
|
ctx_data->ec_state = EC_STATE_RESET;
|
|
return 0;
|
|
}
|
|
|
|
static int ene_enter_flash_mode(ene_lpc_data_t *ctx_data)
|
|
{
|
|
if (ene_pause_ec(ctx_data))
|
|
return ene_reset_ec(ctx_data);
|
|
return 0;
|
|
}
|
|
|
|
static int ene_spi_send_command(const struct flashctx *flash,
|
|
unsigned int writecnt,
|
|
unsigned int readcnt,
|
|
const unsigned char *writearr,
|
|
unsigned char *readarr)
|
|
{
|
|
unsigned int i;
|
|
int tries = EC_RESET_TRIES;
|
|
ene_lpc_data_t *ctx_data = (ene_lpc_data_t *)flash->mst->spi.data;
|
|
const ene_chip_t *chip = ctx_data->chip;
|
|
|
|
if (ctx_data->ec_state == EC_STATE_IDLE && is_spicmd_write(writearr[0])) {
|
|
do {
|
|
/* Enter reset mode if we need to write/erase */
|
|
if (ene_resume_ec(ctx_data))
|
|
continue;
|
|
|
|
if (!ene_reset_ec(ctx_data))
|
|
break;
|
|
} while (--tries > 0);
|
|
|
|
if (!tries) {
|
|
msg_perr("%s: EC failed reset, skipping write\n", __func__);
|
|
ctx_data->ec_state = EC_STATE_IDLE;
|
|
return 1;
|
|
}
|
|
} else if (chip->chip_id == ENE_KB94X && ctx_data->ec_state == EC_STATE_IDLE) {
|
|
ene_pause_timeout_check(ctx_data);
|
|
}
|
|
|
|
ene_spi_start(chip);
|
|
|
|
for (i = 0; i < writecnt; i++) {
|
|
ene_write(chip, REG_SPI_COMMAND, writearr[i]);
|
|
if (ene_spi_wait(chip)) {
|
|
msg_pdbg("%s: write count %d\n", __func__, i);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < readcnt; i++) {
|
|
/* Push data by clock the serial bus */
|
|
ene_write(chip, REG_SPI_COMMAND, 0);
|
|
if (ene_spi_wait(chip)) {
|
|
msg_pdbg("%s: read count %d\n", __func__, i);
|
|
return 1;
|
|
}
|
|
readarr[i] = ene_read(chip, REG_SPI_DATA);
|
|
if (ene_spi_wait(chip)) {
|
|
msg_pdbg("%s: read count %d\n", __func__, i);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
ene_spi_end(chip);
|
|
return 0;
|
|
}
|
|
|
|
static int ene_leave_flash_mode(void *data)
|
|
{
|
|
ene_lpc_data_t *ctx_data = (ene_lpc_data_t *)data;
|
|
const ene_chip_t *chip = ctx_data->chip;
|
|
int rv = 0;
|
|
uint8_t reg;
|
|
struct timeval begin, now;
|
|
|
|
if (ctx_data->ec_state == EC_STATE_RESET) {
|
|
reg = ene_read(chip, REG_8051_CTRL);
|
|
reg &= ~CPU_RESET;
|
|
ene_write(chip, REG_8051_CTRL, reg);
|
|
|
|
gettimeofday(&begin, NULL);
|
|
/* EC restart */
|
|
while (ene_read(chip, chip->ec_status_buf) != chip->ec_is_running) {
|
|
gettimeofday(&now, NULL);
|
|
if (now.tv_sec - begin.tv_sec >= EC_RESTART_TIMEOUT) {
|
|
msg_pdbg("%s: ec restart busy\n", __func__);
|
|
rv = 1;
|
|
goto exit;
|
|
}
|
|
}
|
|
msg_pdbg("%s: send ec restart\n", __func__);
|
|
ec_command(chip, chip->ec_restart_cmd, chip->ec_restart_data);
|
|
|
|
ctx_data->ec_state = EC_STATE_NORMAL;
|
|
rv = 0;
|
|
goto exit;
|
|
}
|
|
|
|
rv = ene_resume_ec(ctx_data);
|
|
|
|
exit:
|
|
/*
|
|
* Trigger ec interrupt after pause/reset by sending 0x80
|
|
* to bios command port.
|
|
*/
|
|
OUTB(0x80, chip->port_bios);
|
|
free(data);
|
|
return rv;
|
|
}
|
|
|
|
static const struct spi_master spi_master_ene = {
|
|
.max_data_read = 256,
|
|
.max_data_write = 256,
|
|
.command = ene_spi_send_command,
|
|
.multicommand = default_spi_send_multicommand,
|
|
.read = default_spi_read,
|
|
.write_256 = default_spi_write_256,
|
|
.write_aai = default_spi_write_aai,
|
|
.shutdown = ene_leave_flash_mode,
|
|
};
|
|
|
|
static int check_params(void)
|
|
{
|
|
int ret = 0;
|
|
char *const p = extract_programmer_param("type");
|
|
if (p && strcmp(p, "ec")) {
|
|
msg_pdbg("ene_lpc only supports \"ec\" type devices\n");
|
|
ret = 1;
|
|
}
|
|
|
|
free(p);
|
|
return ret;
|
|
}
|
|
|
|
static int ene_lpc_init()
|
|
{
|
|
uint8_t hwver, ediid, i;
|
|
ene_lpc_data_t *ctx_data = NULL;
|
|
|
|
msg_pdbg("%s\n", __func__);
|
|
|
|
ctx_data = calloc(1, sizeof(ene_lpc_data_t));
|
|
if (!ctx_data) {
|
|
msg_perr("Unable to allocate space for extra context data.\n");
|
|
return 1;
|
|
}
|
|
ctx_data->ec_state = EC_STATE_NORMAL;
|
|
|
|
if (check_params())
|
|
goto init_err_exit;
|
|
|
|
for (i = 0; i < ENE_LAST; ++i) {
|
|
ctx_data->chip = &ene_chips[i];
|
|
|
|
hwver = ene_read(ctx_data->chip, REG_EC_HWVER);
|
|
ediid = ene_read(ctx_data->chip, REG_EC_EDIID);
|
|
|
|
if(hwver == ene_chips[i].hwver &&
|
|
ediid == ene_chips[i].ediid) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ENE_LAST) {
|
|
msg_pdbg("ENE EC not found (probe failed)\n");
|
|
goto init_err_exit;
|
|
}
|
|
|
|
/* TODO: probe the EC stop protocol
|
|
*
|
|
* Compal - ec_command(0x41, 0xa1) returns 43 4f 4d 50 41 4c 9c
|
|
*/
|
|
|
|
ene_enter_flash_mode(ctx_data);
|
|
|
|
internal_buses_supported |= BUS_LPC;
|
|
|
|
return register_spi_master(&spi_master_ene, ctx_data);
|
|
|
|
init_err_exit:
|
|
free(ctx_data);
|
|
return 1;
|
|
}
|
|
|
|
const struct programmer_entry programmer_ene_lpc = {
|
|
.name = "ene_lpc",
|
|
.type = OTHER,
|
|
.devs.note = "ENE LPC interface keyboard controller\n",
|
|
.init = ene_lpc_init,
|
|
.map_flash_region = fallback_map,
|
|
.unmap_flash_region = fallback_unmap,
|
|
.delay = internal_delay,
|
|
};
|
|
|
|
#endif /* __i386__ || __x86_64__ */
|