blob: ea7ece7d59a3c53e6309c969a07cb0e8cbf8a7e0 [file] [log] [blame]
/* 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;
}