| /* 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; |
| |
| 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); |
| 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; |
| } |