diff options
author | Sheng-Liang Song <ssl@chromium.org> | 2014-08-26 09:00:15 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-09-04 10:41:33 +0000 |
commit | 740b3e6107c2e849c6c76d43e429450d022cde26 (patch) | |
tree | 7aa1deaa5650bbae391601ded8e68bd8f5644907 | |
parent | d2503de95800d71b6e1be7b4d34a321c0bbb33dd (diff) | |
download | chrome-ec-740b3e6107c2e849c6c76d43e429450d022cde26.tar.gz |
EC: Add smbus interface read & write APIs
Ref: http://smbus.org/specs/smbus20.pdf
- Support software CRC8 generation and checking.
- Support read/write word (2-bytes)
- Support read/write blocks (up to 32 bytes)
Ported from ToT CL:209747
BUG=chrome-os-partner:24741
BRANCH=ToT,glimmer
TEST=Verified with smart battery firmware update application on glimmer.
Passed LGC & Simplo Battery.
Signed-off-by: Sheng-Liang Song <ssl@chromium.org>
Change-Id: Id1718d689e5081677b759b41f015629f6ab6b7c4
Reviewed-on: https://chromium-review.googlesource.com/214247
Reviewed-by: Shawn Nematbakhsh <shawnn@chromium.org>
-rw-r--r-- | chip/host/i2c.c | 21 | ||||
-rw-r--r-- | chip/lm4/i2c.c | 5 | ||||
-rw-r--r-- | common/build.mk | 2 | ||||
-rw-r--r-- | common/crc8.c | 23 | ||||
-rw-r--r-- | common/smbus.c | 236 | ||||
-rw-r--r-- | include/common.h | 2 | ||||
-rw-r--r-- | include/config.h | 5 | ||||
-rw-r--r-- | include/crc8.h | 21 | ||||
-rw-r--r-- | include/i2c.h | 5 | ||||
-rw-r--r-- | include/smbus.h | 158 |
10 files changed, 478 insertions, 0 deletions
diff --git a/chip/host/i2c.c b/chip/host/i2c.c index 3f8a8b8241..1ce7da9340 100644 --- a/chip/host/i2c.c +++ b/chip/host/i2c.c @@ -150,3 +150,24 @@ int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data, } return EC_ERROR_UNKNOWN; } + +int smbus_write_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t d16) +{ + return i2c_write16(i2c_port, slave_addr, smbus_cmd, d16); +} + +int smbus_read_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t *p16) +{ + int rv, d16 = 0; + rv = i2c_read16(i2c_port, slave_addr, smbus_cmd, &d16); + *p16 = d16; + return rv; +} + +int smbus_read_string(int i2c_port, uint8_t slave_addr, uint8_t smbus_cmd, + uint8_t *data, int len) +{ + return i2c_read_string(i2c_port, slave_addr, smbus_cmd, data, len); +} diff --git a/chip/lm4/i2c.c b/chip/lm4/i2c.c index 2c6bbab8c4..8b7bd182ca 100644 --- a/chip/lm4/i2c.c +++ b/chip/lm4/i2c.c @@ -70,6 +70,11 @@ struct i2c_port_data { }; static struct i2c_port_data pdata[I2C_PORT_COUNT]; +int i2c_is_busy(int port) +{ + return LM4_I2C_MCS(port) & LM4_I2C_MCS_BUSBSY; +} + /** * I2C transfer engine. * diff --git a/common/build.mk b/common/build.mk index 62f6750933..50bd82c94a 100644 --- a/common/build.mk +++ b/common/build.mk @@ -29,6 +29,7 @@ common-$(CONFIG_CHARGER_TPS65090)+=pmu_tps65090_charger.o common-$(CONFIG_CMD_I2CWEDGE)+=i2c_wedge.o common-$(CONFIG_COMMON_PANIC_OUTPUT)+=panic_output.o common-$(CONFIG_COMMON_TIMER)+=timer.o +common-$(CONFIG_CRC8)+= crc8.o common-$(CONFIG_PMU_POWERINFO)+=pmu_tps65090_powerinfo.o common-$(CONFIG_PMU_TPS65090)+=pmu_tps65090.o common-$(CONFIG_EOPTION)+=eoption.o @@ -55,6 +56,7 @@ common-$(CONFIG_POWER_BUTTON_X86)+=power_button_x86.o common-$(CONFIG_PSTORE)+=pstore_commands.o common-$(CONFIG_PWM)+=pwm.o common-$(CONFIG_PWM_KBLIGHT)+=pwm_kblight.o +common-$(CONFIG_SMBUS)+= smbus.o common-$(CONFIG_SWITCH)+=switch.o common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o thermal.o common-$(CONFIG_USB_PORT_POWER_DUMB)+=usb_port_power_dumb.o diff --git a/common/crc8.c b/common/crc8.c new file mode 100644 index 0000000000..b752698d14 --- /dev/null +++ b/common/crc8.c @@ -0,0 +1,23 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "common.h" +#include "crc8.h" + +uint8_t crc8(const uint8_t *data, int len) +{ + unsigned crc = 0; + int i, j; + + for (j = len; j; j--, data++) { + crc ^= (*data << 8); + for (i = 8; i; i--) { + if (crc & 0x8000) + crc ^= (0x1070 << 3); + crc <<= 1; + } + } + + return (uint8_t)(crc >> 8); +} diff --git a/common/smbus.c b/common/smbus.c new file mode 100644 index 0000000000..aea915f54e --- /dev/null +++ b/common/smbus.c @@ -0,0 +1,236 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * smbus cross-platform code for Chrome EC + * ref: http://smbus.org/specs/smbus20.pdf + */ + +#include "common.h" +#include "console.h" +#include "util.h" +#include "i2c.h" +#include "smbus.h" +#include "crc8.h" +#include "shared_mem.h" + +/** + * @brief smbus write common interface + * [S][slave_addr][A][smbus_cmd][A]...[P] + */ +struct smbus_wr_if { + uint8_t slave_addr;/**< i2c_addr << 1 */ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t data[0]; /**< smbus data */ +} __packed; + +/** + * @brief smbus read common interface + * [S][slave_addr][A][smbus_cmd][A][slave_addr_rd][A]...[P] + */ +struct smbus_rd_if { + uint8_t slave_addr; /**< (i2c_addr << 1)*/ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t slave_addr_rd;/**< (i2c_addr << 1) | 0x1 */ + uint8_t data[0]; /**< smbus data */ +} __packed; + + +#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) + +/* + * smbus interface write n bytes + * case 1: n-1 byte data, 1 byte PEC + * [S][i2c Address][Wr=0][A][cmd][A] ...[Di][Ai]... [PEC][A][P] + * + * case 2: 1 byte data-size, n -2 byte data, 1 byte PEC + * [S][i2c Address][Wr=0][A][cmd][A][size][A] ...[Di][Ai]... [PEC][A][P] + */ +static int smbus_if_write(int i2c_port, struct smbus_wr_if *intf, + uint8_t size_n, uint8_t data_n, uint8_t pec_n) +{ + int rv; + uint8_t n; + data_n = MIN(data_n, SMBUS_MAX_BLOCK_SIZE); + n = size_n + data_n + pec_n; + if (pec_n) + intf->data[n-1] = crc8((const uint8_t *)intf, + n - 1 + sizeof(struct smbus_wr_if)); + i2c_lock(i2c_port, 1); + rv = i2c_is_busy(i2c_port); + if (!rv) + rv = i2c_xfer(i2c_port, intf->slave_addr, + &intf->smbus_cmd, n + 1, NULL, 0, I2C_XFER_SINGLE); + else + rv = EC_ERROR_BUSY; + i2c_lock(i2c_port, 0); + if (rv) + CPRINTF("smbus wr i2c_xfer error:%d cmd:%02X n:%d\n", + rv, intf->smbus_cmd, n); + return rv; +} + +/* + * smbus interface read n bytes + * tx 8-bit smbus cmd, and read n bytes + * + * case 1: n-1 byte data, 1 byte PEC + * [S][i2c addr][Wr=0][A][cmd][A] + * [S][i2c addr][Rd=1][A]...[Di][Ai]...[PEC][A][P] + * + * case 2: 1 byte data-size, n - 2 byte data, 1 byte PEC + * [S][i2c addr][Wr=0][A][cmd][A] + * [S][i2c addr][Rd=1][A][size][A]...[Di][Ai]...[PEC][A][P] + */ +static int smbus_if_read(int i2c_port, struct smbus_rd_if *intf, + uint8_t size_n, uint8_t *pdata_n, uint8_t pec_n) +{ + int rv; + uint8_t pec, n, data_n; + + data_n = MIN(*pdata_n, SMBUS_MAX_BLOCK_SIZE); + n = size_n + data_n + pec_n; + + i2c_lock(i2c_port, 1); + + /* Check if smbus is busy */ + rv = i2c_is_busy(i2c_port); + if (rv) { + rv = EC_ERROR_BUSY; + CPRINTF("smbus_cmd:%02X bus busy error:%d\n", + intf->smbus_cmd, rv); + i2c_lock(i2c_port, 0); + return rv; + } + + rv = i2c_xfer(i2c_port, intf->slave_addr, + &(intf->smbus_cmd), 1, intf->data, n, I2C_XFER_SINGLE); + + i2c_lock(i2c_port, 0); + + if (rv) + return rv; + + if (pec_n == 0) + return EC_SUCCESS; + + /* + * Compute and Check Packet Error Code (crc8) + */ + intf->slave_addr_rd = intf->slave_addr | 0x01; + if (size_n) { + data_n = MIN(data_n, intf->data[0]); + data_n = MIN(data_n, SMBUS_MAX_BLOCK_SIZE); + } + + if (*pdata_n != data_n) { + CPRINTF("smbus read[%02X] size %02X != %02X\n", + intf->smbus_cmd, *pdata_n, data_n); + return EC_ERROR_INVAL; + } + + n = size_n + data_n + pec_n; + pec = crc8((const uint8_t *)intf, n - 1 + sizeof(struct smbus_rd_if)); + if (pec != intf->data[n-1]) { + CPRINTF("smbus read[%02X] PEC %02X != %02X\n", + intf->smbus_cmd, intf->data[n-1], pec); + return EC_ERROR_CRC; + } + return EC_SUCCESS; +} + +int smbus_write_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t d16) +{ + int rv; + struct smbus_wr_word *s; + rv = shared_mem_acquire(sizeof(struct smbus_wr_word), (char **)&s); + if (rv) { + CPRINTF("smbus write wd[%02X] mem error\n", smbus_cmd); + return rv; + } + s->slave_addr = slave_addr, + s->smbus_cmd = smbus_cmd; + s->data[0] = d16 & 0xFF; + s->data[1] = (d16 >> 8) & 0xFF; + rv = smbus_if_write(i2c_port, (struct smbus_wr_if *)s, 0, 2, 1); + shared_mem_release(s); + return rv; +} + +int smbus_write_block(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint8_t *data, uint8_t len) +{ + int rv; + struct smbus_wr_block *s; + rv = shared_mem_acquire(sizeof(struct smbus_wr_block), (char **)&s); + if (rv) { + CPRINTF("smbus write block[%02X] mem error\n", + smbus_cmd); + return rv; + } + s->slave_addr = slave_addr, + s->smbus_cmd = smbus_cmd; + s->size = MIN(len, SMBUS_MAX_BLOCK_SIZE); + memmove(s->data, data, s->size); + rv = smbus_if_write(i2c_port, (struct smbus_wr_if *)s, 1, s->size, 1); + shared_mem_release(s); + return rv; +} + +int smbus_read_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t *p16) +{ + int rv; + uint8_t data_n = 2; + struct smbus_rd_word s; + s.slave_addr = slave_addr; + s.smbus_cmd = smbus_cmd; + rv = smbus_if_read(i2c_port, (struct smbus_rd_if *)&s, 0, &data_n, 1); + if (rv == EC_SUCCESS) + *p16 = (s.data[1] << 8) | s.data[0]; + else + *p16 = 0; + return rv; +} + +int smbus_read_block(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint8_t *data, uint8_t *plen) +{ + int rv; + struct smbus_rd_block *s; + uint8_t len = *plen; + rv = shared_mem_acquire(sizeof(struct smbus_rd_block), (char **)&s); + + if (rv) { + CPRINTF("smbus read block[%02X] mem error\n", + smbus_cmd); + return rv; + } + s->slave_addr = slave_addr, + s->smbus_cmd = smbus_cmd; + s->size = MIN(len, SMBUS_MAX_BLOCK_SIZE); + + rv = smbus_if_read(i2c_port, (struct smbus_rd_if *)s, 1, &s->size, 1); + s->size = MIN(s->size, SMBUS_MAX_BLOCK_SIZE); + s->size = MIN(s->size, len); + *plen = s->size; + if (rv == EC_SUCCESS) + memmove(data, s->data, s->size); + else + memset(data, 0x0, s->size); + + shared_mem_release(s); + return rv; +} + +int smbus_read_string(int i2c_port, uint8_t slave_addr, uint8_t smbus_cmd, + uint8_t *data, uint8_t len) +{ + int rv; + len -= 1; + rv = smbus_read_block(i2c_port, slave_addr, + smbus_cmd, data, &len); + data[len] = '\0'; + return rv; +} diff --git a/include/common.h b/include/common.h index 5f236a1f35..113b165108 100644 --- a/include/common.h +++ b/include/common.h @@ -79,6 +79,8 @@ enum ec_error_list { EC_ERROR_NOT_POWERED = 8, /* Failed because component is not calibrated */ EC_ERROR_NOT_CALIBRATED = 9, + /* Failed because CRC error */ + EC_ERROR_CRC = 10, /* Invalid console command param (PARAMn means parameter n is bad) */ EC_ERROR_PARAM1 = 11, EC_ERROR_PARAM2 = 12, diff --git a/include/config.h b/include/config.h index e61cd5002d..34b9518d67 100644 --- a/include/config.h +++ b/include/config.h @@ -310,6 +310,9 @@ */ #undef CONFIG_CONSOLE_RESTRICTED_INPUT +/* Include crc8 utility functions */ +#undef CONFIG_CRC8 + /*****************************************************************************/ /* * Debugging config @@ -677,6 +680,8 @@ /* Allow the board to use a GPIO for the SCI# signal. */ #undef CONFIG_SCI_GPIO +/* Support smbus interface */ +#undef CONFIG_SMBUS /* Support SPI interfaces */ #undef CONFIG_SPI diff --git a/include/crc8.h b/include/crc8.h new file mode 100644 index 0000000000..94980effc5 --- /dev/null +++ b/include/crc8.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Very simple 8-bit CRC function. + */ +#ifndef __EC_CRC8_H__ +#define __EC_CRC8_H__ + +/** + * crc8 + * Return CRC-8 of the data, using x^8 + x^2 + x + 1 polynomial. A table-based + * algorithm would be faster, but for only a few bytes it isn't worth the code + * size. + * @param data uint8_t *, input, a pointer to input data + * @param len int, input, size of iput data in byte + * @return the crc-8 of the input data. + */ +uint8_t crc8(const uint8_t *data, int len); + +#endif /* __EC_CRC8_H__ */ diff --git a/include/i2c.h b/include/i2c.h index ac572960df..87fe4cc3d0 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -143,6 +143,11 @@ int i2c_read8(int port, int slave_addr, int offset, int *data); int i2c_write8(int port, int slave_addr, int offset, int data); /** + * @return none-zero if i2c bus is busy + */ +int i2c_is_busy(int port); + +/** * Attempt to unwedge an I2C bus. * * @param port I2C port diff --git a/include/smbus.h b/include/smbus.h new file mode 100644 index 0000000000..8f1bfeb9c9 --- /dev/null +++ b/include/smbus.h @@ -0,0 +1,158 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * @file smbus.h + * @brief smbus interface APIs + * @see http://smbus.org/specs/smbus20.pdf + */ +#ifndef __EC_SMBUS_H__ +#define __EC_SMBUS_H__ + +/** Maximum transfer of a SMBUS block transfer */ +#define SMBUS_MAX_BLOCK_SIZE 32 + +/** + * smbus write word + * write 2 byte data + 1 byte pec + */ +struct smbus_wr_word { + uint8_t slave_addr;/**< i2c_addr << 1 */ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t data[3]; /**< smbus data */ +} __packed; + +/** + * smbus write block data + * smbus write 1 byte size + 32 byte data + 1 byte pec + */ +struct smbus_wr_block { + uint8_t slave_addr;/**< i2c_addr << 1 */ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t size; /**< write size */ + uint8_t data[SMBUS_MAX_BLOCK_SIZE+1]; +} __packed; + +/** + * smbus read word + * smbus read 2 byte + 1 pec + */ +struct smbus_rd_word { + uint8_t slave_addr; /**< i2c_addr << 1 */ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t slave_addr_rd;/**< (i2c_addr << 1) | 0x1 */ + uint8_t data[3]; /**< smbus data */ +} __packed; + +/** + * smbus read block data + * smbus read 1 byte size + 32 byte data + 1 byte pec + */ +struct smbus_rd_block { + uint8_t slave_addr; /**< i2c_addr << 1 */ + uint8_t smbus_cmd; /**< smbus cmd */ + uint8_t slave_addr_rd;/**< (i2c_addr << 1) | 0x1 */ + uint8_t size; /**< read block size */ + uint8_t data[SMBUS_MAX_BLOCK_SIZE+1]; /**< smbus data */ +} __packed; + +/** + * smbus_write_word + * smbus write 2 bytes + * @param i2c_port uint8_t, i2c port address + * @param slave_addr uint8_t, i2c slave address:= (i2c_addr << 1) + * @param smbus_cmd uint8_t, smbus command + * @param d16 uint16_t, 2-bytes data + * @return error_code + * EC_SUCCESS if success; none-zero if fail + * EC_ERROR_BUSY if interface is bussy + * none zero error code if fail + */ +int smbus_write_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t d16); + +/** + * smbus_write_block + * smbus write upto 32 bytes + * case 1: n-1 byte data, 1 byte PEC + * [S][i2c Address][Wr=0][A][cmd][A] ...[Di][Ai]... [PEC][A][P] + * + * case 2: 1 byte data-size, n -2 byte data, 1 byte PEC + * [S][i2c Address][Wr=0][A][cmd][A][size][A] ...[Di][Ai]... [PEC][A][P] + * + * @param i2c_port uint8_t, i2c port address + * @param slave_addr uint8_t, i2c slave address:= (i2c_addr << 1) + * @param smbus_cmd uint8_t, smbus command + * @param data uint8_t *, n-bytes data + * @param len uint8_t, data length + * @return error_code + * EC_SUCCESS if success; none-zero if fail + * EC_ERROR_BUSY if interface is bussy + * none zero error code if fail + */ +int smbus_write_block(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint8_t *data, uint8_t len); + +/** + * smbus_read_word + * smbus read 2 bytes + * @param i2c_port uint8_t, i2c port address + * @param slave_addr uint8_t, i2c slave address:= (i2c_addr << 1) + * @param smbus_cmd uint8_t, smbus command + * @param p16 uint16_t *, a pointer to 2-bytes data + * @return error_code + * EC_SUCCESS if success; none-zero if fail + * EC_ERROR_BUSY if interface is bussy + * none zero error code if fail + */ +int smbus_read_word(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint16_t *p16); + +/** + * smbus_read_block + * smbus read upto 32 bytes + * case 1: n-1 byte data, 1 byte PEC + * [S][i2c addr][Wr=0][A][cmd][A] + * [S][i2c addr][Rd=1][A]...[Di][Ai]...[PEC][A][P] + * + * case 2: 1 byte data-size, n - 2 byte data, 1 byte PEC + * [S][i2c addr][Wr=0][A][cmd][A] + * [S][i2c addr][Rd=1][A][size][A]...[Di][Ai]...[PEC][A][P] + * + * @param i2c_port uint8_t, i2c port address + * @param slave_addr uint8_t, i2c slave address:= (i2c_addr << 1) + * @param smbus_cmd uint8_t, smbus command + * @param data uint8_t *, n-bytes data + * @param plen uint8_t *, a pointer data length + * @return error_code + * EC_SUCCESS if success; none-zero if fail + * EC_ERROR_BUSY if interface is bussy + * none zero error code if fail + */ +int smbus_read_block(uint8_t i2c_port, uint8_t slave_addr, + uint8_t smbus_cmd, uint8_t *data, uint8_t *plen); + +/** + * smbus_read_string + * smbus read ascii string (upto 32-byte data + 1-byte NULL) + * Read bytestream from <slaveaddr>:<smbus_cmd> with format: + * [length_N] [byte_0] [byte_1] ... [byte_N-1][byte_N='\0'] + * + * <len> : the max length of receving buffer. to read N bytes + * ascii, len should be at least N+1 to include the + * terminating 0 (NULL). + * + * @param i2c_port uint8_t, i2c port address + * @param slave_addr uint8_t, i2c slave address:= (i2c_addr << 1) + * @param smbus_cmd uint8_t, smbus command + * @param data uint8_t *, n-bytes data + * @param len uint8_t, data length + * @return error_code + * EC_SUCCESS if success; none-zero if fail + * EC_ERROR_BUSY if interface is bussy + * none zero error code if fail + */ +int smbus_read_string(int i2c_port, uint8_t slave_addr, uint8_t smbus_cmd, + uint8_t *data, uint8_t len); + +#endif /* __EC_SMBUS_H__ */ |