QNX---IMX6UL I2C 驱动分析
I2C 是经常用到的一种总线协议,它只占用两个IO口资源,分别是SCL时钟信号线与SDA数据线,两根线就能将连接与总线上的设备实现数据通信,由于它的简便的构造设计,于是成为一种较为常用的通信方式。在QNX系统里,也提供了I2C驱动,它为我们提供了驱动模板,其驱动目录结构如下:
QNX I2C驱动提供了基本的硬件操作接口,其接口名称为i2c_master_funcs_t,这些接口是驱动里要求实现,包括读写、设置地址、总线速度、版本信息等等。每个接口对应于上面的一个文件.c,这样便于模块化。
typedef struct {
/* size of this structure */
size_t size;
/*
* Return version information
* Returns:
* 0 success
* -1 failure
*/
int (*version_info)(i2c_libversion_t *version);
/*
* Initialize master interface.
* Returns a handle that is passed to all other functions.
* Returns:
* !NULL success
* NULL failure
*/
void *(*init)(int argc, char *argv[]);
/*
* Clean up driver.
* Frees memory associated with "hdl".
*/
void (*fini)(void *hdl);
/*
* Master send.
* Parameters:
* (in) hdl Handle returned from init()
* (in) buf Buffer of data to send
* (in) len Length in bytes of buf
* (in) stop If !0, set stop condition when send completes
* Returns:
* bitmask of status bits
*/
i2c_status_t (*send)(void *hdl, void *buf, unsigned int len,
unsigned int stop);
/*
* Master receive.
* Parameters:
* (in) hdl Handle returned from init()
* (in) buf Buffer for received data
* (in) len Length in bytes of buf
* (in) stop If !0, set stop condition when recv completes
* Returns:
* bitmask of status bits
*/
i2c_status_t (*recv)(void *hdl, void *buf, unsigned int len,
unsigned int stop);
/*
* Force the master to free the bus.
* Returns when the stop condition has been sent.
* Returns:
* 0 success
* -1 failure
*/
int (*abort)(void *hdl, int rcvid);
/*
* Specify the target slave address.
* Returns:
* 0 success
* -1 failure
*/
int (*set_slave_addr)(void *hdl, unsigned int addr, i2c_addrfmt_t fmt);
/*
* Specify the bus speed.
* If an invalid bus speed is requested, this function should return
* failure and leave the bus speed unchanged.
* Parameters:
* (in) speed Bus speed. Units are implementation-defined.
* (out) ospeed Actual bus speed (if NULL, this is ignored)
* Returns:
* 0 success
* -1 failure
*/
int (*set_bus_speed)(void *hdl, unsigned int speed, unsigned int *ospeed);
/*
* Request info about the driver.
* Returns:
* 0 success
* -1 failure
*/
int (*driver_info)(void *hdl, i2c_driver_info_t *info);
/*
* Handle a driver-specific devctl().
* Parameters:
* (in) cmd Device command
* (i/o) msg Message buffer
* (in) msglen Length of message buffer in bytes
* (out) nbytes Bytes to return (<= msglen)
* (out) info Extra status information returned by devctl
* Returns:
* EOK success
* errno failure
*/
int (*ctl)(void *hdl, int cmd, void *msg, int msglen,
int *nbytes, int *info);
/*
* Reset i2c bus module.
* Returns:
* EOK success
* EIO failure
*/
int (*bus_reset)(void *hdl);
} i2c_master_funcs_t;
下面针对IMX6UL 上的I2C驱动每一个模块进行分析。
1、version.c 提供版本信息。就是对i2c_libversion_t 结构体进行填充,这个没有啥好分析的,无非是提供一个版本信息而已。
int
mx35_version_info(i2c_libversion_t *version)
{
version->major = I2CLIB_VERSION_MAJOR;
version->minor = I2CLIB_VERSION_MINOR;
version->revision = I2CLIB_REVISION;
return 0;
}
2、bus_speed.c ,设置总线速率。在mx35_set_bus_speed 传参中有一个很重要的参数为hdl,它是一个设备句柄,其与I2C驱动相关的参数通过这个参数传入进来。
int
mx35_set_bus_speed(void *hdl, unsigned int speed, unsigned int *ospeed)
{
mx35_dev_t *dev = hdl;
unsigned int i2c_div, i2c_freq_val;
if (speed > 400000) {
errno = EINVAL;
return -1;
}
if(speed != dev->speed){ //判断当前速度与设置速度是否一致,一致就不需要设置 了
i2c_div = dev->input_clk / speed;
i2c_freq_val = find_best_ic( i2c_div );//获取时钟参数值
out16( dev->regbase + MX35_I2C_FRQREG_OFF, i2c_freq_val );//设置I2C时钟速率
dev->speed = speed; //保存当前设置的速率
dev->i2c_freq_val = i2c_freq_val;
}
if (ospeed)
*ospeed = dev->input_clk / I2C_Divs[ dev->i2c_freq_val ];
return 0;
}
3、slave_addr.c 设置设备地址
int
mx35_set_slave_addr(void *hdl, unsigned int addr, i2c_addrfmt_t fmt)
{
mx35_dev_t *dev = hdl;
if ((fmt != I2C_ADDRFMT_7BIT) && (fmt != I2C_ADDRFMT_10BIT)) { //检测设备地址是否为这两种格式。
errno = EINVAL;
return -1;
}
dev->slave_addr = addr;
dev->slave_addr_fmt = fmt;
return 0;
}
4、send.c I2C发送数据
i2c_status_t
mx35_send(void *hdl, void *buf, unsigned int len, unsigned int stop)
{
mx35_dev_t *dev = hdl;
i2c_status_t status;
if (len <= 0)
return I2C_STATUS_DONE;
if(mx35_wait_bus_not_busy(dev))//检测当前I2C是否处于忙状态
return I2C_STATUS_ERROR;
if (dev->slave_addr_fmt == I2C_ADDRFMT_7BIT)//发送器件地址
status = mx35_sendaddr7(dev, dev->slave_addr, MX35_I2C_ADDR_WR, dev->restart);
else
status = mx35_sendaddr10(dev, dev->slave_addr, MX35_I2C_ADDR_WR, dev->restart);
if (status)
return status;
while (len > 0) {
status = mx35_sendbyte(dev, *(uint8_t *)buf);//发送I2C数据
if (status)
return status;
++buf; --len;
}
if (stop)
out16(dev->regbase + MX35_I2C_CTRREG_OFF, CTRREG_IEN);//是否停止发送
dev->restart = !stop;
return I2C_STATUS_DONE;
}
5、recv.c 接收处理 和发送流程一样。
i2c_status_t
mx35_recv(void *hdl, void *buf, unsigned int len, unsigned int stop){
mx35_dev_t *dev = hdl;
i2c_status_t status;
if (len <= 0)
return I2C_STATUS_DONE;
if(mx35_wait_bus_not_busy(dev))
return I2C_STATUS_ERROR;
/* send slave address */
if (dev->slave_addr_fmt == I2C_ADDRFMT_7BIT)
status = mx35_sendaddr7(dev, dev->slave_addr, MX35_I2C_ADDR_RD, dev->restart);
else
status = mx35_sendaddr10(dev, dev->slave_addr, MX35_I2C_ADDR_RD, dev->restart);
if (status)
return status;
if (len > 1)
out16(dev->regbase + MX35_I2C_CTRREG_OFF, CTRREG_IEN | CTRREG_IIEN | CTRREG_MSTA);
else
out16(dev->regbase + MX35_I2C_CTRREG_OFF, CTRREG_IEN | CTRREG_IIEN | CTRREG_MSTA | CTRREG_TXAK);
in16(dev->regbase + MX35_I2C_DATREG_OFF);
while (len > 0){
status = mx35_recvbyte(dev, buf, (len == 2), (len == 1) && stop);
if (status)
return status;
++buf; --len;
}
if (!stop)
mx35_wait_status(dev);
dev->restart = !stop;
return I2C_STATUS_DONE;
}
6、init.c 初始化相关参数,主要配置I2C 中断 基地址 时钟等参数
void *
mx35_init(int argc, char *argv[])
{
mx35_dev_t *dev;
if (-1 == ThreadCtl(_NTO_TCTL_IO, 0)) {
perror("ThreadCtl");
return NULL;
}
dev = malloc(sizeof(mx35_dev_t));
if (!dev)
return NULL;
if(strstr(argv[argc-1], "controller") !=NULL){
sscanf(argv[argc-1],"controller=%u",&dev->unit);
argc--;
}
else
dev->unit= -1;
if (-1 == mx35_options(dev, argc, argv)) //根据命令 配置I2C相关参数包括基地址 中断号
goto fail;
if(dev->physbase == 0 || dev->intr ==0){//如果没有基地址和中断号 则驱动加载失败
mx35_i2c_slogf(dev, _SLOG_ERROR, "i2c-mx35 error : Invalid I2C controller physics based address or IRQ value.");
mx35_i2c_slogf(dev, _SLOG_ERROR, "i2c-mx35 error : Please check the command line or Hwinfo default setting.");
goto fail;
}
dev->regbase = mmap_device_io(dev->reglen, dev->physbase);//地址映射
if (dev->regbase == (uintptr_t)MAP_FAILED) {
perror("mmap_device_io");
goto fail;
}
/* 启动I2C模块 */
out16(dev->regbase + MX35_I2C_CTRREG_OFF, 0);
out16(dev->regbase + MX35_I2C_STSREG_OFF, 0);
delay(1);
out16(dev->regbase + MX35_I2C_CTRREG_OFF, CTRREG_IEN);
/* 初始化中断处理 */
SIGEV_INTR_INIT(&dev->intrevent);
dev->iid = InterruptAttachEvent(dev->intr, &dev->intrevent,
_NTO_INTR_FLAGS_TRK_MSK);
if (dev->iid == -1) {
perror("InterruptAttachEvent");
goto fail;
}
/* 设置默认I2C速率*/
mx35_set_bus_speed(dev, 100000, NULL);
#if 0
/* Set Own Address */
out16(dev->regbase + MX35_I2C_ADRREG_OFF , (dev->own_addr << 1));
#endif
/* enable interrupts */
out16(dev->regbase + MX35_I2C_CTRREG_OFF, in16(dev->regbase + MX35_I2C_CTRREG_OFF) | CTRREG_IIEN );
return dev;
fail:
free(dev);
return NULL;
}
总结:
I2C 驱动 QNX只提供与硬件相关的操作,具体实现过程并没有,那QNX是怎么运行的呢。实质和下面流程一样。在初始化里设置一个中断事件,这个事件将发送消息告诉主循环接收和发送数据。
#include <hw/i2c.h>
i2c_master_funcs_t masterf;
i2c_libversion_t version;
i2c_status_t status; void *hdl;
i2c_master_getfuncs(&masterf, sizeof(masterf));//初始化硬件接口 masterf.version_info(&version);
if ((version.major != I2CLIB_VERSION_MAJOR) || (version.minor > I2CLIB_VERSION_MINOR)) { /* error */ ... }
hdl = masterf.init(...); //调用初始化函数
masterf.set_bus_speed(hdl, ...); //设置总线速度
while(1)
{
InterruptWait (NULL, NULL);
masterf.set_slave_addr(hdl, ...); //设置从地址
status = masterf.send(hdl, ...);//发送数据
if (status != I2C_STATUS_DONE) { /* error */
if (!(status & I2C_STATUS_DONE))
masterf.abort(hdl); }
status = masterf.recv(hdl, ...);//接收数据
if (status != I2C_STATUS_DONE)
{ /* error */ ... }
}
masterf.fini(hdl);//完成I2C传输后,清理驱动程序并释放与给定句柄关联的所有内存
InterruptUnmask(INTNUM, id);
}