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