diff options
Diffstat (limited to 'src/ec/google/chromeec')
-rw-r--r-- | src/ec/google/chromeec/ec_lpc.c | 169 |
1 files changed, 167 insertions, 2 deletions
diff --git a/src/ec/google/chromeec/ec_lpc.c b/src/ec/google/chromeec/ec_lpc.c index 5344716d47..4bd58ef792 100644 --- a/src/ec/google/chromeec/ec_lpc.c +++ b/src/ec/google/chromeec/ec_lpc.c @@ -29,6 +29,30 @@ #include "ec.h" #include "ec_commands.h" +static int google_chromeec_command_version(void) +{ + u8 id1, id2, flags; + + id1 = inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID); + id2 = inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID + 1); + flags = inb(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_HOST_CMD_FLAGS); + + if (id1 != 'E' || id2 != 'C') { + printk(BIOS_ERR, "Missing Chromium EC memory map.\n"); + return -1; + } + + if (flags & EC_HOST_CMD_FLAG_VERSION_3) { + return EC_HOST_CMD_FLAG_VERSION_3; + } else if (flags & EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED) { + return EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED; + } else { + printk(BIOS_ERR, + "Chromium EC command version unsupported\n"); + return -1; + } +} + static int google_chromeec_wait_ready(u16 port) { u8 ec_status = inb(port); @@ -51,7 +75,119 @@ static int google_chromeec_wait_ready(u16 port) return 0; } -int google_chromeec_command(struct chromeec_command *cec_command) +static int google_chromeec_command_v3(struct chromeec_command *cec_command) +{ + struct ec_host_request rq; + struct ec_host_response rs; + const u8 *d; + u8 *dout; + int csum = 0; + int i; + + if (cec_command->cmd_size_in + sizeof(rq) > EC_LPC_HOST_PACKET_SIZE) { + printk(BIOS_ERR, "EC cannot send %ld bytes\n", + cec_command->cmd_size_in + sizeof(rq)); + return -1; + } + + if (cec_command->cmd_size_out > EC_LPC_HOST_PACKET_SIZE) { + printk(BIOS_ERR, "EC cannot receive %d bytes\n", + cec_command->cmd_size_out); + return -1; + } + + if (google_chromeec_wait_ready(EC_LPC_ADDR_HOST_CMD)) { + printk(BIOS_ERR, "Timeout waiting for EC start command %d!\n", + cec_command->cmd_code); + return -1; + } + + /* Fill in request packet */ + rq.struct_version = EC_HOST_REQUEST_VERSION; + rq.checksum = 0; + rq.command = cec_command->cmd_code | + EC_CMD_PASSTHRU_OFFSET(cec_command->cmd_dev_index); + rq.command_version = cec_command->cmd_version; + rq.reserved = 0; + rq.data_len = cec_command->cmd_size_in; + + /* Copy data and start checksum */ + for (i = 0, d = (const u8 *)cec_command->cmd_data_in; + i < cec_command->cmd_size_in; i++, d++) { + outb(*d, EC_LPC_ADDR_HOST_PACKET + sizeof(rq) + i); + csum += *d; + } + + /* Finish checksum */ + for (i = 0, d = (const u8 *)&rq; i < sizeof(rq); i++, d++) + csum += *d; + + /* Write checksum field so the entire packet sums to 0 */ + rq.checksum = (u8)(-csum); + + /* Copy header */ + for (i = 0, d = (const uint8_t *)&rq; i < sizeof(rq); i++, d++) + outb(*d, EC_LPC_ADDR_HOST_PACKET + i); + + /* Start the command */ + outb(EC_COMMAND_PROTOCOL_3, EC_LPC_ADDR_HOST_CMD); + + if (google_chromeec_wait_ready(EC_LPC_ADDR_HOST_CMD)) { + printk(BIOS_ERR, "Timeout waiting for EC process command %d!\n", + cec_command->cmd_code); + return -1; + } + + /* Check result */ + cec_command->cmd_code = inb(EC_LPC_ADDR_HOST_DATA); + if (cec_command->cmd_code) { + printk(BIOS_ERR, "EC returned error result code %d\n", + cec_command->cmd_code); + return -i; + } + + /* Read back response header and start checksum */ + csum = 0; + for (i = 0, dout = (u8 *)&rs; i < sizeof(rs); i++, dout++) { + *dout = inb(EC_LPC_ADDR_HOST_PACKET + i); + csum += *dout; + } + + if (rs.struct_version != EC_HOST_RESPONSE_VERSION) { + printk(BIOS_ERR, "EC response version mismatch (%d != %d)\n", + rs.struct_version, EC_HOST_RESPONSE_VERSION); + return -1; + } + + if (rs.reserved) { + printk(BIOS_ERR, "EC response reserved is %d, should be 0\n", + rs.reserved); + return -1; + } + + if (rs.data_len > cec_command->cmd_size_out) { + printk(BIOS_ERR, "EC returned too much data (%d > %d)\n", + rs.data_len, cec_command->cmd_size_out); + return -1; + } + + /* Read back data and update checksum */ + for (i = 0, dout = (uint8_t *)cec_command->cmd_data_out; + i < rs.data_len; i++, dout++) { + *dout = inb(EC_LPC_ADDR_HOST_PACKET + sizeof(rs) + i); + csum += *dout; + } + + /* Verify checksum */ + if ((u8)csum) { + printk(BIOS_ERR, "EC response has invalid checksum\n"); + return -1; + } + + return 0; +} + +static int google_chromeec_command_v1(struct chromeec_command *cec_command) { struct ec_lpc_host_args args; const u8 *d; @@ -134,7 +270,36 @@ int google_chromeec_command(struct chromeec_command *cec_command) return 0; } -#ifndef __PRE_RAM__ +#ifdef __PRE_RAM__ + +int google_chromeec_command(struct chromeec_command *cec_command) +{ + switch (google_chromeec_command_version()) { + case EC_HOST_CMD_FLAG_VERSION_3: + return google_chromeec_command_v3(cec_command); + case EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED: + return google_chromeec_command_v1(cec_command); + } + return -1; +} + +#else /* !__PRE_RAM__ */ + +int google_chromeec_command(struct chromeec_command *cec_command) +{ + static int command_version = 0; + + if (command_version <= 0) + command_version = google_chromeec_command_version(); + + switch (command_version) { + case EC_HOST_CMD_FLAG_VERSION_3: + return google_chromeec_command_v3(cec_command); + case EC_HOST_CMD_FLAG_LPC_ARGS_SUPPORTED: + return google_chromeec_command_v1(cec_command); + } + return -1; +} #ifndef __SMM__ static void lpc_ec_init(struct device *dev) |