背景
在rtthread环境中使用过瑞萨的RA系列芯片的人都会发现,瑞萨其实已经标准化了大部分驱动层的接口,也就是说,适配一块新的板卡,其实并不需要再去像其他家那样,每块板卡都去单独编写驱动文件。
虽然不用重新写对应模块的驱动文件,但是我们可以通过分析驱动文件代码,获取出一个通用的驱动适配模板。
分析思路
驱动注册入口
通过前面分析硬件I2C框架中,其实我们就已经知道了硬件I2C的注册入口,即:
rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus, const char *bus_name);
而在瑞萨适配该驱动的位置,可以发现存在这么一段代码:
static const struct rt_i2c_bus_device_ops ra_i2c_ops = { .master_xfer = ra_i2c_mst_xfer, .slave_xfer = RT_NULL, .i2c_bus_control = RT_NULL }; int ra_hw_i2c_init(void) { fsp_err_t err = FSP_SUCCESS; for (rt_uint32_t i = 0; i < sizeof(ra_i2cs) / sizeof(ra_i2cs[0]); i++) { ra_i2cs[i].bus.ops = &ra_i2c_ops; ra_i2cs[i].bus.priv = 0; if (RT_EOK != rt_event_init(&ra_i2cs[i].event, ra_i2cs[i].bus_name, RT_IPC_FLAG_FIFO)) { LOG_E("Init event failed"); continue; } /* opening IIC master module */ err = R_IIC_MASTER_Open(ra_i2cs[i].i2c_ctrl, ra_i2cs[i].i2c_cfg); if (FSP_SUCCESS != err) { LOG_E("R_I2C_MASTER_Open API failed,%d", err); continue; } err = R_IIC_MASTER_CallbackSet(ra_i2cs[i].i2c_ctrl, i2c_master_callback, &ra_i2cs[i], RT_NULL); /* handle error */ if (FSP_SUCCESS != err) { LOG_E("R_I2C_CallbackSet API failed,%d", err); continue; } rt_i2c_bus_device_register(&ra_i2cs[i].bus, ra_i2cs[i].bus_name); } return 0; } INIT_DEVICE_EXPORT(ra_hw_i2c_init);
从这里,我们可以看出,除去作用不明的rt_event_init,以及瑞萨独有的硬件配置接口,其实在注册时,驱动所需要干的事情是构造一个结构体struct rt_i2c_bus_device_ops,其中需要实现master_xfer这个入口,至于另外两个入口为何不实现,个人理解为其实很少有人真的用I2C做从模式,因此也就不去适配I2C从模式了,另外i2c_bus_control这个入口,在驱动没有专有接口的情况下,其实也不需要去实现
master_xfer
要知道rt_event_init的作用,以及I2C master模式下的读写入口,就不得不分析结构体中的master_xfer实现了。而在驱动代码中,我们能看到的实现如下:
#define RT_I2C_WR 0x0000 #define RT_I2C_RD (1u << 0) #define RT_I2C_ADDR_10BIT (1u << 2) /* this is a ten bit chip address */ #define RT_I2C_NO_START (1u << 4) #define RT_I2C_IGNORE_NACK (1u << 5) #define RT_I2C_NO_READ_ACK (1u << 6) /* when I2C reading, we do not ACK */ #define RT_I2C_NO_STOP (1u << 7) struct rt_i2c_msg { rt_uint16_t addr; rt_uint16_t flags; rt_uint16_t len; rt_uint8_t *buf; }; static rt_ssize_t ra_i2c_mst_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num) { rt_size_t i; struct rt_i2c_msg *msg = msgs; RT_ASSERT(bus != RT_NULL); fsp_err_t err = FSP_SUCCESS; bool restart = false; struct ra_i2c_handle *ra_i2c = rt_container_of(bus, struct ra_i2c_handle, bus); i2c_master_ctrl_t *master_ctrl = ra_i2c->i2c_ctrl; for (i = 0; i < num; i++) { if (msg[i].flags & RT_I2C_NO_START) { restart = true; } if (msg[i].flags & RT_I2C_ADDR_10BIT) { R_IIC_MASTER_SlaveAddressSet(master_ctrl, msg[i].addr, I2C_MASTER_ADDR_MODE_10BIT); } else { R_IIC_MASTER_SlaveAddressSet(master_ctrl, msg[i].addr, I2C_MASTER_ADDR_MODE_7BIT); } if (msg[i].flags & RT_I2C_RD) { err = R_IIC_MASTER_Read(master_ctrl, msg[i].buf, msg[i].len, restart); if (FSP_SUCCESS == err) { if (RT_EOK != validate_i2c_event(ra_i2c)) { LOG_E("POWER_CTL reg I2C read failed"); break; } } /* handle error */ else { /* Write API returns itself is not successful */ LOG_E("R_I2C_MASTER_Write API failed"); break; } } else { err = R_IIC_MASTER_Write(master_ctrl, msg[i].buf, msg[i].len, restart); if (FSP_SUCCESS == err) { if (RT_EOK != validate_i2c_event(ra_i2c)) { LOG_E("POWER_CTL reg I2C write failed"); break; } } /* handle error */ else { /* Write API returns itself is not successful */ LOG_E("R_I2C_MASTER_Write API failed"); break; } } } return (rt_ssize_t)i; }
从这里面,我们可以发现,实际上master_xfer函数的实现是基于struct rt_i2c_msg中的变量flags来维护的,这个标记决定了当前的msg是读还是写,地址位长度是十位还是八位(这里的长度设置,个人觉得可能是个bug,因为没必要在此设置,硬件I2C框架中的control接口,就有设置总线地址长度的入口,没必要在每次写数据时再去重新指定一遍地址位长度)。
另外,前面存在疑惑的点,rt_event_init貌似在这里面没体现,但是进一步观察代码validate_i2c_event,就会发现其中的蹊跷。
// 这个函数在注册函数时是以回调的形式给到瑞萨的接口上的,个人理解为瑞萨的中断回调 void i2c_master_callback(i2c_master_callback_args_t *p_args) { rt_interrupt_enter(); if (NULL != p_args) { /* capture callback event for validating the i2c transfer event*/ struct ra_i2c_handle *obj = (struct ra_i2c_handle *)p_args->p_context; uint32_t event = 0; RT_ASSERT(obj != RT_NULL); switch (p_args->event) { case I2C_MASTER_EVENT_ABORTED: event |= RA_SCI_EVENT_ABORTED; break; case I2C_MASTER_EVENT_RX_COMPLETE: event |= RA_SCI_EVENT_RX_COMPLETE; break; case I2C_MASTER_EVENT_TX_COMPLETE: event |= RA_SCI_EVENT_TX_COMPLETE; break; } rt_event_send(&obj->event, event); } rt_interrupt_leave(); } static rt_err_t validate_i2c_event(struct ra_i2c_handle *handle) { rt_uint32_t event = 0; if (RT_EOK != rt_event_recv(&handle->event, RA_SCI_EVENT_ALL, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, (int32_t)rt_tick_from_millisecond(100), &event)) { return -RT_ETIMEOUT; } if ((event & (RA_SCI_EVENT_ABORTED | RA_SCI_EVENT_ERROR)) == 0) { return RT_EOK; } return -RT_ERROR; }
从这里,基本上就可以看出问题了,实际上加这个逻辑,仅仅是为了实现等待硬件执行完成罢了。但疑问是,这个使用完成量会不会更好?
总结
分析至此,其实我们已经可以获取到硬件I2C的注册模板了,其注册模板如下:
#include <rtdevice.h> #include <rtthread.h> #include "board.h" #include <stdlib.h> #ifdef BSP_USING_HW_I2C #define DRV_DEBUG #define LOG_TAG "drv.hwi2c" #include <drv_log.h> // 芯片接口头文件 #include <xxxx.h> // 资源结构体 struct xx_xx_i2c_handle { struct rt_i2c_bus_device bus; char bus_name[RT_NAME_MAX]; const i2c_master_cfg_t *i2c_cfg; void *i2c_ctrl; }; 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; struct rt_i2c_msg *msg = msgs; RT_ASSERT(bus != RT_NULL); // 获取I2C设备 struct ra_i2c_handle *ra_i2c = rt_container_of(bus, struct ra_i2c_handle, bus); for (i = 0; i < num; i++) { if (msg[i].flags & RT_I2C_RD) { // 硬件I2C主模式读实现 } else { // 硬件I2C主模式写实现 } } return (rt_ssize_t)i; // 返回实际读写数据量 } static const struct rt_i2c_bus_device_ops i2c_ops = { .master_xfer = i2c_mst_xfer, .slave_xfer = RT_NULL, .i2c_bus_control = RT_NULL }; int hw_i2c_init(void) { struct ra_i2c_handle *ra_i2cs; ra_i2cs = (struct ra_i2c_handle *)rt_malloc(sizeof(struct ra_i2c_handle)); ra_i2cs->bus.ops = &i2c_ops; ra_i2cs->bus.priv = 0; rt_memcpy(ra_i2cs->bus_name, "I2C0", sizeof("I2C0")); //硬件I2C资源初始化 //注册硬件I2C rt_i2c_bus_device_register(&ra_i2cs->bus, ra_i2cs->bus_name); return 0; } INIT_DEVICE_EXPORT(hw_i2c_init); #endif /* BSP_USING_I2C */
有了这套模板,其实后面做硬件I2C适配时,直接往里面填功能便可快速适配硬件I2C。