I2C驱动框架解析
代码入口
代码入口其实挺好找,基本上芯片对I2C驱动代码提供会以以下名称编写,drv_i2c.c(硬件I2C驱动)或drv_soft_i2c.c(软件I2C套在硬件I2C驱动接口上的实现,我个人的观点,如果不支持硬件I2C,其实根本没必要专门写一套软件I2C的驱动,原因是,软件I2C本质上就是gpio操作加延时处理,完全就是业务逻辑的事情,不应该放在驱动层实现)。这里我们主要看硬件I2C,因此直接查看硬件I2C驱动文件。在驱动文件中,我们找到么一个入口:
struct i2c_handle
{
struct rt_i2c_bus_device bus;
char bus_name[RT_NAME_MAX];
};
static struct i2c_handle ra_i2cs[] =
{
...
}
static const struct rt_i2c_bus_device_ops i2c_ops =
{
.master_xfer = ra_i2c_mst_xfer,
.slave_xfer = RT_NULL,
.i2c_bus_control = RT_NULL
};
int xxxx(void)
{
fsp_err_t err = FSP_SUCCESS;
for (rt_uint32_t i = 0; i < sizeof(ra_i2cs) / sizeof(ra_i2cs[0]); i++)
{
// I2C接口初始化
i2cs[i].bus.ops = &i2c_ops;
i2cs[i].bus.priv = 0;
// 注册I2C设备
rt_i2c_bus_device_register(&i2cs[i].bus, i2cs[i].bus_name);
}
return 0;
}
INIT_DEVICE_EXPORT(xxxx);其中:rt_i2c_bus_device_register即为我们所需要找到的I2C框架的入口位置。
I2C驱动注册函数解析
找到了设备级的I2C设备注册入口,我们便开始逐层分析注册接口。
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops i2c_ops =
{
RT_NULL,
RT_NULL,
RT_NULL,
i2c_bus_device_read,
i2c_bus_device_write,
i2c_bus_device_control
};
#endif
rt_err_t rt_i2c_bus_device_device_init(struct rt_i2c_bus_device *bus,
const char *name)
{
struct rt_device *device;
RT_ASSERT(bus != RT_NULL);
device = &bus->parent;
device->user_data = bus;
/* set device type */
device->type = RT_Device_Class_I2CBUS;
/* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
device->ops = &i2c_ops;
#else
device->init = RT_NULL;
device->open = RT_NULL;
device->close = RT_NULL;
device->read = i2c_bus_device_read;
device->write = i2c_bus_device_write;
device->control = i2c_bus_device_control;
#endif
/* register to device manager */
rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);
return RT_EOK;
}
rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
const char *bus_name)
{
rt_err_t res = RT_EOK;
// 注册设备锁,用于不同线程调用I2C设备时的互斥操作
rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_PRIO);
if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND;
// 注册I2C设备
res = rt_i2c_bus_device_device_init(bus, bus_name);
LOG_I("I2C bus [%s] registered", bus_name);
#ifdef RT_USING_DM
if (!res)
{
i2c_bus_scan_clients(bus);
}
#endif
return res;
}完整代码一贴出来,我们便可以发现,所谓的I2C总线设备,实际上只是在posix标准框架上标准化了一套I2C接口框架,这个框架没有init,open和close。只有read,write和control。而这个注册最核心的部分,恰恰是rtt设备框架中的驱动注册入口rt_device_register。
应用获取I2C入口
既然I2C设备是通过rt_device_register注册的,因此应用端寻找I2C设备,也只需要调用rt_device_find即可,若存在,则返回设备操作句柄,若不存在,则返回null。
但是呢,实际上,rtt还封装了一层接口。
struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name)
{
struct rt_i2c_bus_device *bus;
rt_device_t dev = rt_device_find(bus_name);
if (dev == RT_NULL || dev->type != RT_Device_Class_I2CBUS)
{
LOG_E("I2C bus %s not exist", bus_name);
return RT_NULL;
}
bus = (struct rt_i2c_bus_device *)dev->user_data;
return bus;
}从接口实现上看,其实可以看出来了,加了个判断,判断获取到的设备的设备类类型是否是I2C设备
I2C读写函数解析
由RTT官方的I2C总线设备使用手册可知,I2C读写入口可以分为I2C读写入口,I2C读入口和I2C写入口
I2C 读写入口
rt_ssize_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num)
{
rt_ssize_t ret;
rt_err_t err;
if (bus->ops->master_xfer)
{
#ifdef RT_I2C_DEBUG
for (ret = 0; ret < num; ret++)
{
LOG_D("msgs[%d] %c, addr=0x%02x, len=%d", ret,
(msgs[ret].flags & RT_I2C_RD) ? 'R' : 'W',
msgs[ret].addr, msgs[ret].len);
}
#endif
err = rt_mutex_take(&bus->lock, RT_WAITING_FOREVER);
if (err != RT_EOK)
{
return (rt_ssize_t)err;
}
ret = bus->ops->master_xfer(bus, msgs, num);
err = rt_mutex_release(&bus->lock);
if (err != RT_EOK)
{
return (rt_ssize_t)err;
}
return ret;
}
else
{
LOG_E("I2C bus operation not supported");
return -RT_EINVAL;
}
}看到这,我们可以大致知道这些信息,驱动需要实现xfer函数,而xfer函数的作用需要实现读写操作。因此我们拿一份简化驱动实现来看。
static rt_ssize_t i2c_mst_xfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num)
{
rt_size_t i;
RT_ASSERT(bus != RT_NULL);
for (i = 0; i < num; i++)
{
if (msg[i].flags & RT_I2C_NO_START)
{
// 设置是否需要起始位
}
if (msg[i].flags & RT_I2C_ADDR_10BIT)
{
// 设置成10bit地址工作模式
}
else
{
// 设置成8bit地址工作模式
}
if (msg[i].flags & RT_I2C_RD)
{
// 读操作,读失败跳出
}
else
{
// 写操作,写失败跳出
}
}
return (rt_ssize_t)i;
}实际上驱动的实现也是按照此思路实现的,根据flags的标记做对应功能的实现,根据传入的数据量决定读写多少数据。
I2C 写入口
rt_ssize_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
const rt_uint8_t *buf,
rt_uint32_t count)
{
rt_ssize_t ret;
struct rt_i2c_msg msg;
msg.addr = addr;
msg.flags = flags;
msg.len = count;
msg.buf = (rt_uint8_t *)buf;
ret = rt_i2c_transfer(bus, &msg, 1);
return (ret == 1) ? count : ret;
}从数据上结构上看,写入口仅仅是多开放了一种实现方式,将struct rt_i2c_msg放在函数内部实现,应用是现实不再需要维护这么个结构体,仅仅需要将读写参数以变量的形式传递至rt_i2c_master_send即可
I2C读入口
rt_ssize_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
rt_uint16_t addr,
rt_uint16_t flags,
rt_uint8_t *buf,
rt_uint32_t count)
{
rt_ssize_t ret;
struct rt_i2c_msg msg;
RT_ASSERT(bus != RT_NULL);
msg.addr = addr;
msg.flags = flags | RT_I2C_RD;
msg.len = count;
msg.buf = buf;
ret = rt_i2c_transfer(bus, &msg, 1);
return (ret == 1) ? count : ret;
}读入口,从功能上看,和写入口一致,仅仅是将struct rt_i2c_msg封装,另外,把读标记的操作方式固化下来。
看完这几个入口,其实我们已经可以看出,RTT的I2C框架,目前仅支持主模式工作的I2C,并不支持I2C从模式。另外,还有一个疑问,I2C设备不是注册了几个标准事件接口(i2c_bus_device_read, i2c_bus_device_write, i2c_bus_device_control)吗,怎么感觉完全没用上?实际上,i2c_bus_device_read 和 i2c_bus_device_write这两个接口,从使用上来说,就是调用rtt文档中暴露的 rt_i2c_master_recv 和 rt_i2c_master_send接口,其实现如下:
static rt_ssize_t i2c_bus_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t count)
{
rt_uint16_t addr;
rt_uint16_t flags;
struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(buffer != RT_NULL);
LOG_D("I2C bus dev [%s] reading %u bytes.", dev->parent.name, count);
addr = pos & 0xffff;
flags = (pos >> 16) & 0xffff;
return rt_i2c_master_recv(bus, addr, flags, (rt_uint8_t *)buffer, count);
}
static rt_ssize_t i2c_bus_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t count)
{
rt_uint16_t addr;
rt_uint16_t flags;
struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;
RT_ASSERT(bus != RT_NULL);
RT_ASSERT(buffer != RT_NULL);
LOG_D("I2C bus dev [%s] writing %u bytes.", dev->parent.name, count);
addr = pos & 0xffff;
flags = (pos >> 16) & 0xffff;
return rt_i2c_master_send(bus, addr, flags, (const rt_uint8_t *)buffer, count);
}I2C控制接口
至于i2c_bus_device_control接口,我们可以通过代码层级来看
rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus,
int cmd,
void *args)
{
rt_err_t ret;
if(bus->ops->i2c_bus_control)
{
ret = bus->ops->i2c_bus_control(bus, cmd, args);
return ret;
}
else
{
LOG_E("I2C bus operation not supported");
return -RT_EINVAL;
}
}
static rt_err_t i2c_bus_device_control(rt_device_t dev,
int cmd,
void *args)
{
rt_err_t ret;
struct rt_i2c_priv_data *priv_data;
struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;
RT_ASSERT(bus != RT_NULL);
switch (cmd)
{
/* set 10-bit addr mode */
case RT_I2C_DEV_CTRL_10BIT:
bus->flags |= RT_I2C_ADDR_10BIT;
break;
case RT_I2C_DEV_CTRL_TIMEOUT:
bus->timeout = *(rt_uint32_t *)args;
break;
case RT_I2C_DEV_CTRL_RW:
priv_data = (struct rt_i2c_priv_data *)args;
ret = rt_i2c_transfer(bus, priv_data->msgs, priv_data->number);
if (ret < 0)
{
return -RT_EIO;
}
break;
default:
return rt_i2c_control(bus, cmd, args);
}
return RT_EOK;
}可以看出,在i2c_dev.c这一层,标准化了I2C常用格式的控制逻辑,并将这部分数据在读写I2C时传递至驱动实现。另外,该接口也预留了驱动自定义实现接口的部分,方便各家在实际应用中自定义一些内部参数控制入口。
总结
至此,我们基本上了理清了RTT的硬件I2C框架结构,其设计上的分层逻辑如下:

我要赚赏金
