Victor Ding has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/44580 )
Change subject: Add ENE LPC programmer ......................................................................
Add ENE LPC programmer
Initial support of ENE LPC interface keyboard controller.
BUG=b:156140422 BRANCH=none
Signed-off-by: Victor Ding victording@google.com Change-Id: I970afd8c1bd92c159c60e09f22e2f18c0433729d --- M Makefile A ene_lpc.c M flashrom.c M meson.build M meson_options.txt M programmer.h 6 files changed, 642 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/80/44580/1
diff --git a/Makefile b/Makefile index 803529f..85b4850 100644 --- a/Makefile +++ b/Makefile @@ -170,6 +170,11 @@ else override CONFIG_DEVELOPERBOX_SPI = no endif +ifeq ($(CONFIG_ENE_LPC), yes) +UNSUPPORTED_FEATURES += CONFIG_ENE_LPC=yes +else +override CONFIG_ENE_LPC = no +endif ifeq ($(CONFIG_FT2232_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes else @@ -271,6 +276,11 @@ else override CONFIG_ATAPROMISE = no endif +ifeq ($(CONFIG_ENE_LPC), yes) +UNSUPPORTED_FEATURES += CONFIG_ENE_LPC=yes +else +override CONFIG_ENE_LPC = no +endif ifeq ($(CONFIG_IT8212), yes) UNSUPPORTED_FEATURES += CONFIG_IT8212=yes else @@ -381,6 +391,11 @@ else override CONFIG_DEVELOPERBOX_SPI = no endif +ifeq ($(CONFIG_ENE_LPC), yes) +UNSUPPORTED_FEATURES += CONFIG_ENE_LPC=yes +else +override CONFIG_ENE_LPC = no +endif ifeq ($(CONFIG_FT2232_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes else @@ -671,6 +686,9 @@ # Promise ATA controller support. CONFIG_ATAPROMISE ?= no
+# ENE LPC interface keyboard controller +CONFIG_ENE_LPC ?= yes + # Always enable FT2232 SPI dongles for now. CONFIG_FT2232_SPI ?= yes
@@ -855,6 +873,11 @@ NEED_LIBPCI += CONFIG_INTERNAL endif
+ifeq ($(CONFIG_ENE_LPC), yes) +FEATURE_CFLAGS += -D'CONFIG_ENE_LPC=1' +PROGRAMMER_OBJS += ene_lpc.o +endif + ifeq ($(CONFIG_SERPROG), yes) FEATURE_CFLAGS += -D'CONFIG_SERPROG=1' PROGRAMMER_OBJS += serprog.o diff --git a/ene_lpc.c b/ene_lpc.c new file mode 100644 index 0000000..701e84e --- /dev/null +++ b/ene_lpc.c @@ -0,0 +1,593 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2012 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 Google 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. + * GOOGLE INC 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 + * GOOGLE 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 GOOGLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +#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 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, +}; + +int ene_lpc_init() +{ + uint8_t hwver, ediid, i; + int ret = 0; + char *p = NULL; + 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; + + p = extract_programmer_param("type"); + if (p && strcmp(p, "ec")) { + msg_pdbg("ene_lpc only supports "ec" type devices\n"); + ret = 1; + goto ene_probe_spi_flash_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"); + ret = 1; + goto ene_probe_spi_flash_exit; + } + + /* TODO: probe the EC stop protocol + * + * Compal - ec_command(0x41, 0xa1) returns 43 4f 4d 50 41 4c 9c + */ + + + if (register_shutdown(ene_leave_flash_mode, ctx_data)) { + ret = 1; + goto ene_probe_spi_flash_exit; + } + + ene_enter_flash_mode(ctx_data); + + internal_buses_supported |= BUS_LPC; + spi_master_ene.data = ctx_data; + register_spi_master(&spi_master_ene); + msg_pdbg("%s: successfully initialized ene\n", __func__); + +ene_probe_spi_flash_exit: + free(p); + if (ret) + free(ctx_data); + return ret; +} + +#endif /* __i386__ || __x86_64__ */ diff --git a/flashrom.c b/flashrom.c index 4a30986..35ac6f5 100644 --- a/flashrom.c +++ b/flashrom.c @@ -279,6 +279,18 @@ }, #endif
+#if CONFIG_ENE_LPC == 1 + { + .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 + #if CONFIG_RAYER_SPI == 1 { .name = "rayer_spi", diff --git a/meson.build b/meson.build index 04176f3..180b5dd 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,7 @@ config_digilent_spi = get_option('config_digilent_spi') config_drkaiser = get_option('config_drkaiser') config_dummy = get_option('config_dummy') +config_ene_lpc = get_option('config_ene_lpc') config_ft2232_spi = get_option('config_ft2232_spi') config_gfxnvidia = get_option('config_gfxnvidia') config_raiden = get_option('config_raiden') @@ -202,6 +203,10 @@ cargs += '-DCONFIG_INTERNAL_DMI=1' endif endif +if config_ene_lpc + srcs += 'ene_lpc.c' + cargs += '-DCONFIG_ENE_LPC=1' +endif if config_it8212 srcs += 'it8212.c' cargs += '-DCONFIG_IT8212=1' diff --git a/meson_options.txt b/meson_options.txt index ac48e4e..ba5130b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -11,6 +11,7 @@ option('config_digilent_spi', type : 'boolean', value : true, description : 'Digilent Development board JTAG') option('config_drkaiser', type : 'boolean', value : true, description : 'Dr. Kaiser') option('config_dummy', type : 'boolean', value : true, description : 'dummy tracing') +option('config_ene_lpc', type : 'boolean', value : true, description : 'ENE LPC interface keyboard controller') option('config_ft2232_spi', type : 'boolean', value : true, description : 'FT2232 SPI dongles') option('config_gfxnvidia', type : 'boolean', value : true, description : 'NVIDIA graphics cards') option('config_raiden', type : 'boolean', value : true, description : 'ChromiumOS Servo DUT debug board') diff --git a/programmer.h b/programmer.h index c5cab18..b3bc700 100644 --- a/programmer.h +++ b/programmer.h @@ -61,6 +61,9 @@ #if CONFIG_ATAPROMISE == 1 PROGRAMMER_ATAPROMISE, #endif +#if CONFIG_ENE_LPC == 1 + PROGRAMMER_ENE_LPC, +#endif #if CONFIG_IT8212 == 1 PROGRAMMER_IT8212, #endif @@ -578,6 +581,11 @@ extern const struct dev_entry devs_digilent_spi[]; #endif
+/* ene_lpc.c */ +#if CONFIG_ENE_LPC == 1 +int ene_lpc_init(void); +#endif + /* jlink_spi.c */ #if CONFIG_JLINK_SPI == 1 int jlink_spi_init(void);