背景
在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。
我要赚赏金
