目录
Linux I2C子系统提供了几种操作I2C外设的API,主要是分为两大类,一类是SMBUS协议的方式,一类是基于I2C标准协议的方式,这两种的实现代码都是在下列路径:
{$PROJECT_PATH}/LINUX/android/kernel/msm-4.9/drivers/i2c/
1. i2c_transfer
咱们这里主要使用I2C标准协议的方式进行开发,当前高通平台支持DMA的方式,主要用的核心I2C API如下路径:
{$PROJECT_PATH}/LINUX/android/kernel/msm-3.18/drivers/i2c/i2c-core.c
该API需要参考其中的i2c_transfer函数,其函数原型如下(内核还封装了其它的API,个人比较喜欢用这个i2c_transfer)
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
int ret;
/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev,
"master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",
ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',
msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
if (in_atomic() || irqs_disabled()) {
ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
i2c_lock_bus(adap, I2C_LOCK_SEGMENT);
}
ret = __i2c_transfer(adap, msgs, num);
i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL(i2c_transfer);
它有三个参数
adap: I2C操作的句柄,这个是在probe驱动的时候由platform生成
msgs: I2C子系统为了实现这种通信方式,封装了i2c_msg结构体,每一个START信号,都对应一个i2c_msg对象。
num: i2c_msg对象的个数
1.1. i2s_msg类型如下
{$PROJECT_PATH}/LINUX/android/kernel/msm-4.9/include/uapi/linux/i2c.h
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
当i2c_msg结构体中的flags设定的值为I2C_M_RD,则代表当前为读取操作,默认值为WRITE操作。
1.2. i2c_adapter类型如下
{$PROJECT_PATH}/LINUX/android/kernel/msm-4.9/include/linux/i2c.h
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
该结构体中定义了一个i2c_algorithm结构体如下所示:
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
结构体中声明了几个函数指针,其中用到的是master_xfer,该函数将会在高通的i2c设备中进行定义,有兴趣的可以继续研究。
{$PROJECT_PATH}/LINUX/android/kernel/msm-4.9/drivers/i2c/busses/i2c-msm-v2.c
static const struct i2c_algorithm i2c_msm_frmwrk_algrtm = {
.master_xfer = i2c_msm_frmwrk_xfer,
.functionality = i2c_msm_frmwrk_func,
};
其函数定义如下所示,在往下追深就是设计到了实际物理读写的API,
static int
i2c_msm_frmwrk_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
int ret = 0;
struct i2c_msm_ctrl *ctrl = i2c_get_adapdata(adap);
struct i2c_msm_xfer *xfer = &ctrl->xfer;
if (IS_ERR_OR_NULL(msgs) || num < 1) {
dev_err(ctrl->dev,
"Error on msgs Accessing invalid message pointer or message buffer\n");
return -EINVAL;
}
/* if system is suspended just bail out */
if (ctrl->pwr_state == I2C_MSM_PM_SYS_SUSPENDED) {
dev_err(ctrl->dev,
"slave:0x%x is calling xfer when system is suspended\n",
msgs->addr);
return -EIO;
}
ret = i2c_msm_pm_xfer_start(ctrl);
if (ret)
return ret;
/* init xfer */
xfer->msgs = msgs;
xfer->msg_cnt = num;
xfer->mode_id = I2C_MSM_XFER_MODE_NONE;
xfer->err = 0;
xfer->rx_cnt = 0;
xfer->tx_cnt = 0;
xfer->rx_ovrhd_cnt = 0;
xfer->tx_ovrhd_cnt = 0;
atomic_set(&xfer->event_cnt, 0);
init_completion(&xfer->complete);
init_completion(&xfer->rx_complete);
xfer->cur_buf.is_init = false;
xfer->cur_buf.msg_idx = 0;
i2c_msm_prof_evnt_add(ctrl, MSM_PROF, I2C_MSM_XFER_BEG, num,
msgs->addr, 0);
i2c_msm_xfer_scan(ctrl);
i2c_msm_xfer_calc_timeout(ctrl);
xfer->mode_id = i2c_msm_qup_choose_mode(ctrl);
dev_dbg(ctrl->dev, "xfer() mode:%d msg_cnt:%d rx_cbt:%zu tx_cnt:%zu\n",
xfer->mode_id, xfer->msg_cnt, xfer->rx_cnt, xfer->tx_cnt);
switch (xfer->mode_id) {
case I2C_MSM_XFER_MODE_FIFO:
ret = i2c_msm_fifo_xfer(ctrl);
break;
case I2C_MSM_XFER_MODE_BLOCK:
ret = i2c_msm_blk_xfer(ctrl);
break;
case I2C_MSM_XFER_MODE_DMA:
ret = i2c_msm_dma_xfer(ctrl);
break;
default:
ret = -EINTR;
};
i2c_msm_prof_evnt_add(ctrl, MSM_PROF, I2C_MSM_SCAN_SUM,
((xfer->rx_cnt & 0xff) | ((xfer->rx_ovrhd_cnt & 0xff) << 16)),
((xfer->tx_cnt & 0xff) | ((xfer->tx_ovrhd_cnt & 0xff) << 16)),
((ctrl->xfer.timeout & 0xfff) | ((xfer->mode_id & 0xf) << 24)));
ret = i2c_msm_qup_post_xfer(ctrl, ret);
/* on success, return number of messages sent (which is index + 1)*/
if (!ret)
ret = xfer->cur_buf.msg_idx + 1;
i2c_msm_prof_evnt_add(ctrl, MSM_PROF, I2C_MSM_XFER_END, ret, xfer->err,
xfer->cur_buf.msg_idx + 1);
/* process and dump profiling data */
if (xfer->err || (ctrl->dbgfs.dbg_lvl >= MSM_PROF))
i2c_msm_prof_evnt_dump(ctrl);
i2c_msm_pm_xfer_end(ctrl);
return ret;
}
1.3. i2c_transfer调用关系
i2c_transfer实际调用的是__i2c_transfer,其中会调用的上文提到的master_xfer
/**
* __i2c_transfer - unlocked flavor of i2c_transfer
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Adapter lock must be held when calling this function. No debug logging
* takes place. adap->algo->master_xfer existence isn't checked.
*/
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
unsigned long orig_jiffies;
int ret, try;
if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
return -EOPNOTSUPP;
/* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets
* enabled. This is an efficient way of keeping the for-loop from
* being executed when not needed.
*/
if (static_key_false(&i2c_trace_msg)) {
int i;
for (i = 0; i < num; i++)
if (msgs[i].flags & I2C_M_RD)
trace_i2c_read(adap, &msgs[i], i);
else
trace_i2c_write(adap, &msgs[i], i);
}
/* Retry automatically on arbitration loss */
orig_jiffies = jiffies;
for (ret = 0, try = 0; try <= adap->retries; try++) {
ret = adap->algo->master_xfer(adap, msgs, num);
if (ret != -EAGAIN)
break;
if (time_after(jiffies, orig_jiffies + adap->timeout))
break;
}
if (static_key_false(&i2c_trace_msg)) {
int i;
for (i = 0; i < ret; i++)
if (msgs[i].flags & I2C_M_RD)
trace_i2c_reply(adap, &msgs[i], i);
trace_i2c_result(adap, i, ret);
}
return ret;
}
EXPORT_SYMBOL(__i2c_transfer);
2. 实例参考
2.1. 写入数据到IIC外设
写入IIC外设一般操作比较简单,时许也比较简单,一般只要直接写入地址+数据流就行,其测试的IIC时序如下
代码:
static int test_i2c_write(struct mimxrt685_i2c *pdata, uint16_t write_len, uint8_t *write_buf)
{
int res = 0;
struct i2c_msg msg;
if (pdata == NULL)
{
pr_err("%s, pdata is NULL\n", __func__);
return -1;
}
msg.addr = pdata->client->addr,
msg.flags = pdata->client->flags,
msg.buf = write_buf,
msg.len = write_len,
res = i2c_transfer(pdata->client->adapter, &msg, sizeof(msg) / sizeof(struct i2c_msg));
if (res < 0)
{
dev_err(&pdata->client->dev, "%s [ERROR] i2c_master_send - errno: %x\n", __func__, res);
return -2;
}
return res;
}
实际的波形如下:
2.2. 从IIC外设读取数据
从IIC外设读取操作,不同的芯片外设时许存在不同的情况。
- 如一些简单的芯片,直接通过读取寄存器地址读取数据,直接读取地址即可,如下图所示:
- 如稍微繁琐的一些芯片,读取信息,需要先往里面写一些数据,然后再读取,这期间是是会存在两次Start信号,如下图所示:
本例使用的第二种方式,代码如下:
static int test_i2c_read(struct mimxrt685_i2c* pdata, uint8_t write_len, uint8_t* write_buf, uint16_t read_len, uint8_t* read_buf)
{
int res = 0;
int i = 0;
struct i2c_msg msg[2];
if (pdata == NULL)
{
pr_err("%s, pdata is NULL\n", __func__);
return -1;
}
for (i = 0; i < write_len; i ++)
{
pr_info("%s, write_buf[%d] = 0x%02x", __func__, i, write_buf[i]);
}
msg[0].addr = pdata->client->addr,
msg[0].flags = 0,
msg[0].buf = write_buf,
msg[0].len = write_len,
msg[1].addr = pdata->client->addr,
msg[1].flags = I2C_M_RD,
msg[1].buf = read_buf,
msg[1].len = read_len,
res = i2c_transfer(pdata->client->adapter, msg, sizeof(msg) / sizeof(struct i2c_msg));
if (res < 0)
{
dev_err(&pdata->client->dev, "%s [ERROR] i2c_master_receive - error: %x\n", __func__, res);
return -2;
}
return res;
}
对应的实际波形如下:
2.3. 使用i2c-tool
在开始验证可以通过i2c_tool工具进行遍历i2c总线的设备,进行寻址,读,写测试等等