1. 描述
本文主要描述QNX I2C Drvier的相关内容,并以SA8155处理器为例讲解。
I2C 是经常用到的一种总线协议,它只占用两个IO口资源,分别是SCL时钟信号线与SDA数据线,
两根线就能将连接与总线上的设备实现数据通信,由于它的简便的构造设计,于是成为一种较为
常用的通信方式。在QNX系统里,也提供了I2C驱动框架,整体框架与SPI类似。
I2C总线数据传输和应答
2. 目录结构
2.1 QUB配置部分
TODO
请查阅 https://blog.csdn.net/liaochaoyun/article/details/127317225
2.2. 驱动部分:
qnx_ap/AMSS/platform/hwdrivers/wired_peripherals/i2c
├── common
│ ├── aarch64
│ │ ├── a-le
│ │ ├── Makefile
│ │ └── so-le
│ ├── arm
│ │ ├── a-le-v7
│ │ ├── Makefile
│ │ └── so-le-v7
│ ├── common.mk
│ ├── Makefile
│ └── src
│ ├── I2cDeviceQup.c
│ └── I2cSys.c
├── inc
│ ├── ddii2c.h
│ ├── I2cDevice.h
│ ├── I2cError.h
│ ├── I2cTransferCfg.h
│ ├── I2cTransfer.h
│ └── I2cTypes.h
├── Makefile
└── platsvc_i2c
├── aarch64
│ ├── a-le
│ ├── Makefile
│ └── so-le
├── arm
│ ├── a-le-v7
│ ├── Makefile
│ └── so-le-v7
├── common.mk
├── inc
│ ├── I2cLog.h
│ ├── I2cPlatBam.h
│ ├── I2cPlatBsp.h
│ ├── I2cPlatSvc.h
│ └── I2cSys.h
├── Makefile
└── src
├── I2cPlatBsp.c
└── I2cPlatSvc.c
2.3. 资源管理器Res部分
qnx_ap/AMSS/platform/resources/i2c_drv
├── aarch64
│ ├── a-le
│ │ └── Makefile
│ ├── Makefile
│ ├── so-le
│ │ └── Makefile
│ └── so-le-g
│ └── Makefile
├── arm
│ ├── a-le-v7
│ │ └── Makefile
│ ├── Makefile
│ └── so-le-v7
│ └── Makefile
├── common.mk
├── i2c_drv.c
├── Makefile
└── protected
└── i2c_devctls.h
2.4 I2C Service(Demo 进程)
qnx_ap/AMSS/platform/services/daemons/i2c_service
├── aarch64
│ ├── Makefile
│ └── o-le
│ └── Makefile
├── arm
│ ├── Makefile
│ └── o-le-v7
│ └── Makefile
├── common.mk
├── Makefile
├── src
│ └── i2cservice_main.c
└── usefile
2.5 I2c API(Client)
qnx_ap/AMSS/platform/qal/clients/i2c_client
├── aarch64
│ ├── Makefile
│ ├── so-le
│ │ └── Makefile
│ └── so-le-g
│ └── Makefile
├── arm
│ ├── Makefile
│ └── so-le-v7
│ └── Makefile
├── common.mk
├── i2c_client.c
├── Makefile
└── public
└── amss
└── i2c_client.h
3. I2C API
qnx_ap/AMSS/platform/qal/clients/i2c_client/public/amss/i2c_client.h
安装:qnx_ap/install/usr/include/amss/i2c_client.h
/*
* gain access to controller/bus driver
* Returns a handle that is passed to all other functions.
* Parameters:
* (in) devname Resource Manager connection(ex. "/dev/i2c1")
*
* Returns:
* >0 success
* -1 failure
*/
int i2c_open(void* devname);/*
* releases access to controller/bus driver
* Frees memory associated with "fd".
* Parameters:
* (in) fd Handle returned from init()
*
*/
void i2c_close(int fd);/*
* Specify the bus clock frequency
* If an invalid bus speed is requested, this function should return
* failure and leave the bus speed unchanged.
* Parameters:
* (in) addr The slave address
* (in) fmt The slave address format
* Returns:
* 0 success
* -1 failure
*/
int i2c_set_slave_addr(int fd, uint32_t addr, uint32_t fmt);/*
* Specify the bus clock frequency
* If an invalid bus speed is requested, this function should return
* failure and leave the bus speed unchanged.
* Parameters:
* (in) bus_clock_freq.
* Returns:
* 0 success
* -1 failure
*/
int i2c_set_bus_speed(int fd, uint32_t speed, uint32_t * ospeed);/*
* Lock this I2C controller/bus
* Parameters:
* (in) fd Handle returned from init()
* Returns:
* 0 success
* -1 failure
*/
int i2c_bus_lock(int fd);/*
* Unlock this I2C controller/bus
* Parameters:
* (in) fd Handle returned from init()
* Returns:
* 0 success
* -1 failure
*/
int i2c_bus_unlock(int fd);/*
* Write
* Parameters:
* (in) fd Handle returned from init()
* (in) buf Buffer of data to write
* (in) len Length in bytes of buf
* Returns:
* Number of bytes read
*/
int i2c_write(int fd, void *buf, uint32_t len);/*
* Read
* Parameters:
* (in) fd Handle returned from init()
* (in) buf Buffer for read data
* (in) len Length in bytes of buf
* Returns:
* >=0 Number of bytes read
* <0 Error # from errno.h
*/
int i2c_read(int fd, void *buf, uint32_t len);/*
* Combined Write Read
* Parameters:
* (in) fd Handle returned from init()
* (in) wbuf Buffer of data to send
* (in) wlen Length in bytes of wbuf
* (in) rbuff Buffer for received data
* (in) rlen Length in bytes of wbuf
* Returns:
* >=0 Number of bytes read
* <0 Error # from errno.h
*/
int i2c_combined_writeread(int fd, void * wbuff, uint32_t wlen, void* rbuff, uint32_t rlen );
/*
* Request info about the driver.
* Returns:
* 0 success
* -1 failure
*/
int i2c_driver_info(int fd, i2c_driver_info_t *info);
文件引用:#include <amss/i2c_client.h>
4. I2C 资源管理器设计
4.1 优点
资源管理器接口的主要优点是:
- 它为应用程序开发人员提供了一个清晰、易于理解的思路。
- 它作为一个中介,在多个应用程序对一个或多个从设备之间进行访问,强制不同I2C接口之间的一致性。
- 对于专用的i2c总线应用程序,硬件访问库更有效;硬件接口定义了这个库的接口,有助于维护和代码可移植性。
4.2 通用架构
通用架构如下如:
4.3 代码分析
4.3.1 服务进程
核心文件: qnx_ap/AMSS/platform/services/daemons/i2c_service/src/i2cservice_main.c
int i2c_service_base_init(void)
{
//调用i2c_drv 资源管理接口实现i2c资源管理器初始化
if (-1 == i2c_drv_init()) {
logger_log(QCLOG_AMSS_QNP_SERVICES_I2C_SERVICE, QCLOG_AMSS_I2C_SERVICE_MINOR,
QCLOG_ERROR, "i2c_drv_init Failed");
}
drop_abilities_i2c();
/* init sysctrl */
if(DAL_SUCCESS != sysctrl_init()) {
logger_log(QCLOG_AMSS_QNP_SERVICES_I2C_SERVICE, QCLOG_AMSS_I2C_SERVICE_MINOR,
QCLOG_ERROR, "sysctrl_init failed, errno: %s", strerror(errno));
return DAL_ERROR;
}
return DAL_SUCCESS;
}
int main(int argc, char *argv[]) {
int c;
...
/* QNX IO operation privilege */
if (ThreadCtl(_NTO_TCTL_IO, 0) == -1)
{
PROCESS_ERROR_CRITICAL("i2c_service: ThreadCtl Error");
return EXIT_FAILURE;
}
//信号SIGTERM处理
signal(SIGTERM,handle_sigterm);
// Check if process is running /dev/i2c_service
if( 0 < open(DEVICE_NAME, O_RDONLY))
{
PROCESS_ERROR_CRITICAL("Process [%s] already running.. exiting.", DEVICE_NAME);
return EXIT_FAILURE;
}
// Run process in background,进程后台运行
Resource_Daemonize();
//资源管理器实现接口
if(i2c_service_base_init() != DAL_SUCCESS)
{
PROCESS_ERROR_CRITICAL("i2c_service: i2c_service_core_init failed");
return EXIT_FAILURE;
}
//启动I2C资源管理器服务
/* the i2c resource manager main loop is going to run after here */
if(DAL_SUCCESS != sysctrl_start(DEVICE_NAME)) {
PROCESS_ERROR_CRITICAL("i2c_service: service_init failed");
return EXIT_FAILURE;
}
// Code should never reach here
PROCESS_ERROR_CRITICAL("Exiting i2c_service..");
return EXIT_SUCCESS;
}
查看系统进程:
# pidin | grep i2c
40982 1 bin/i2c_service 10r RECEIVE 8
40982 2 bin/i2c_service 15r RECEIVE 2
40982 3 bin/i2c_service 21r SEM fff808a056320f94
40982 4 bin/i2c_service 41r INTR
40982 5 bin/i2c_service 10r RECEIVE 3
40982 6 bin/i2c_service 41r INTR
40982 7 bin/i2c_service 10r RECEIVE 6
40982 8 bin/i2c_service 41r INTR
40982 9 bin/i2c_service 10r RECEIVE 11
4.3.2 资源管理器实现
核心文件:qnx_ap/AMSS/platform/resources/i2c_drv/i2c_drv.c
入口:
/******************************************************************************/
/***************************** startup and config ****************************/
/******************************************************************************/
int i2c_drv_init(void)
{
int i;
int idx = 0;
int ret = EOK;
uint64_t chip_id = 0;
const void *fdt_paddr = 0;
pthread_t threadID;
DALSYSPropertyVar PropVar;
DALSYS_PROPERTY_HANDLE_DECLARE(hDALProps);
DALSYS_InitMod(NULL);
DALSYS_RegisterMod(&gDALModDriverInfoList);
int policy;
struct sched_param param;
pthread_attr_t attr;
if (waitfor_attach(QCORE_SERVICE, 5000))
{
I2C_SLOGE("Timed out waiting for %s to be ready", QCORE_SERVICE);
return -1;
}
fdt_paddr = fdt_get_root();
if (!fdt_paddr) {
I2C_SLOGE("Failed to load device tree");
return -1;
}
ret = fdt_foreach_subnode_byname((void*) fdt_paddr , "/chip_info",
&get_chip_info, &chip_id);
if (ret) {
I2C_SLOGE("Failed to find dt chip_info");
return -1;
}
I2C_SLOGI("Found chip id=%d", (int)chip_id);
//线程配置
pthread_attr_init(&attr);
pthread_getschedparam(pthread_self(), &policy, ¶m);
param.sched_priority = 100;
pthread_attr_setschedparam(&attr, ¶m);
devs = calloc(MAX_NUM_I2C_DEVS, sizeof(i2c_dev_t));
if(devs == NULL)
return -1;
//创建设备
// create devices , if specified via command line
for(i = 0; i < MAX_NUM_I2C_DEVS; i++)
{
if(DALSYS_GetDALPropertyHandle(DeviceID[i], hDALProps) == DAL_SUCCESS)
{
//解析I2C配置,详情看QUP-I2C配置项
if (DAL_SUCCESS != DALSYS_GetPropertyValue(hDALProps, "I2C_ENABLED", 0, &PropVar)
|| PropVar.Val.dwVal == 0)
{
continue;
}
if (DAL_SUCCESS != DALSYS_GetPropertyValue(hDALProps, "CLOCK_SE_NAME", 0, &PropVar)
|| PropVar.Val.pszVal == 0)
{
I2C_SLOGE("Failed to get property CLOCK_SE_NAME");
return -1;
}
if (!strncmp(PropVar.Val.pszVal, "scc", 3)) {
devs[idx].is_ssc = true;
}
//设备编号,名称等
snprintf(devs[idx].devname, MAX_NUM_DEVNAME, "/dev/i2c%d", i+1);
devs[idx].DALDeviceID = DeviceID[i];
devs[idx].index = idx;
devs[idx].bus_active = 0;
devs[idx].timer_created = 0;
//QUP I2C设备初始化
if (DAL_SUCCESS != I2CDEV_Init(DeviceID[i], &devs[idx].ahI2cDev)) {
I2C_SLOGE("Failed to initialize I2C %s", devs[idx].devname);
continue;
}
devs[idx].initialized = 1;
//通过线程创建设备,资源管理器及接口。
ret = pthread_create(&threadID, NULL, (void *)&device_main,
(void *)&devs[idx]);
if (EOK == ret) {
pthread_setname_np(threadID, devs[idx].devname);
idx++;
} else {
I2C_SLOGE("Couldn't create RM thread for device-%d:"
"name-%s:ret-%d",
DeviceID[i], devs[idx].devname, ret);
}
}
}
if (ID_6155 == chip_id) {
if ((ret = i2c_register_ssr())) {
I2C_SLOGE("Failed to register for SSR ret=%x", ret);
return -1;
}
}
return 0;
}
资源管理器创建通用步骤:
- 建立一个上下文切换句柄dpp = dispatch_create();这个东东主要用在mainloop中产生一个block特性,可以让我们等待接受消息;
- iofunc初始化。这一步是将自己实现的函数与POSIX层函数进行接口,解析从read、write、devctl等函数传来的消息进行解析,以实现底层与应用层函数之间的交互,通过io_funcs.read = io_read,io_funcs.write = io_write,进行函数重载;
- 注册设备名,使设备在命名空间中产生相应的名称,这一点是整个过程的关键了,形如 pathID = resmgr_attach (dpp, &rattr, "/dev/Null",_FTYPE_ANY, 0, &connect_funcs,&io_funcs, &ioattr),这样不仅注册了一个设备名,还让系统知道了我们实习的IO函数对应关系;
- 为之前创建的上下文句柄分配空间,例如ctp = dispatch_context_alloc (dpp);为了第六步使用;
- 通过不断循环等待dispatch_block()来调用MsgReceive()使Resource manger处于receive block状态,以接收上层发送来的消息,通过dispatch_handler (ctp)去调用我们自己定义的IO函数
资源管理器创建实现:
int device_main(i2c_dev_t *dev)
{
resmgr_connect_funcs_t connect_funcs;
resmgr_io_funcs_t io_funcs;
resmgr_attr_t rattr;
iofunc_funcs_t ocb_funcs = { _IOFUNC_NFUNCS, _ocb_calloc, _ocb_free,
NULL, NULL, NULL };
iofunc_mount_t mount = { 0, IOFUNC_PC_ACL, 0, 0, &ocb_funcs };
I2CDEV_PowerStates power_state = 0;
pthread_condattr_t cond_attr = { 0 };
/* Set up contiguous buffers */
dev->fd_mmap = posix_typed_mem_open("/ram/dma",
O_RDWR,
POSIX_TYPED_MEM_ALLOCATE_CONTIG);
if (dev->fd_mmap == -1) {
I2C_SLOGE("%s: posix_typed_mem_open(/ram/dma) failed(%d-%s)",
__FUNCTION__, errno, strerror(errno));
exit(1);
}
/* Configure clocks, gpios at init-time*/
I2CDEV_GetPowerState(dev->ahI2cDev, &power_state);
if (POWER_STATE_2 != power_state) {
I2CDEV_SetPowerState(dev->ahI2cDev , POWER_STATE_2);
dev->bus_active = 1;
}
pthread_mutex_init(&dev->mutex, NULL);
pthread_condattr_init(&cond_attr);
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
pthread_cond_init(&dev->cond, &cond_attr);
//创建channel
dev->chid = ChannelCreate(_NTO_CHF_DISCONNECT | _NTO_CHF_UNBLOCK);
if(dev->chid == -1) {
I2C_SLOGE("ChannelCreate failed (%s)", strerror(errno));
exit (1);
}
//第一步
/*
* allocate and initialize a dispatch structure for use by our
* main loop
*/
I2C_SLOGI("initializing %s\n",dev->devname);
dev->dpp = dispatch_create_channel ( dev->chid, 0);
if (dev->dpp == NULL)
{
I2C_SLOGE("couldn't dispatch_create: %s\n",strerror(errno));
exit (1);
}
/*
* set up the resource manager attributes structure, we'll
* use this as a way of passing information to resmgr_attach().
* For now, we just use defaults.
*/
memset (&rattr, 0, sizeof (rattr)); /* using the defaults for rattr */
rattr.nparts_max = 2; /* Max iov used by client is 2 */
rattr.msg_max_size = MAX_NUM_I2C_WRITE;
//第二步 iofunc初始化
/*
* intialize the connect functions and I/O functions tables to
* their defaults by calling iofunc_func_init().
*
* connect_funcs, and io_funcs variables are already declared.
*
*/
iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_funcs,
_RESMGR_IO_NFUNCS, &io_funcs);
/* over-ride the connect_funcs handler for open with our io_open,
* and over-ride the io_funcs handlers for read and write with our
* io_read and io_write handlers
*/
connect_funcs.open = io_open;
io_funcs.devctl = io_devctl;
io_funcs.acl = io_acl;
io_funcs.close_ocb = io_close;
/* initialize our device description structure
*/
iofunc_attr_init (&dev->hdr, S_IFCHR | 0666, NULL, NULL);
dev->hdr.mount = &mount; // so we can alloc an OCB per open
iofunc_acl_init(&dev->hdr, iofunc_acl_posix_ctrl, 0);
//第三步 注册设备
/*
* call resmgr_attach to register our prefix with the
* process manager, and also to let it know about our connect
* and I/O functions.
*
* On error, returns -1 and errno is set.
*/
dev->pathID = resmgr_attach(dev->dpp,
&rattr,
dev->devname,
_FTYPE_ANY,
0,
&connect_funcs,
&io_funcs,
(RESMGR_HANDLE_T *)dev);
if (dev->pathID == -1)
{
I2C_SLOGE(" couldn't attach pathname: %s\n", strerror (errno));
exit (1);
}
//第四步 为dpp分配句柄空间
dispatch_context_t *ctp;
dev->ctp = dispatch_context_alloc(dev->dpp);
if (dev->ctp == NULL)
{
dispatch_destroy(dev->dpp);
return EXIT_FAILURE;
}
/* Notify bmetrics I2C device is ready */
int fd = open("/dev/bmetrics", O_WRONLY);
if (fd == -1) {
I2C_SLOGE("Failed to open /dev/bmetrics with error:%d\n", errno);
} else {
char buf[BM_KPI_BUF_SIZE];
snprintf(buf, BM_KPI_BUF_SIZE, "bootmarker DRIVER I2C Ready:%s\n",
dev->devname);
if (-1 == write(fd, buf, strlen(buf))) {
I2C_SLOGE("Failed to write /dev/bmetrics with error:%d\n", errno);
}
close(fd);
}
/* register LPM pulses */
if (EOK != i2c_register_pulse(dev)) {
I2C_SLOGE("pulse registration for %s failed", dev->devname);
exit(1);
}
/* register SSR pulses */
dev->ssr_coid = ConnectAttach(ND_LOCAL_NODE, 0 /* pid */,
dev->chid, _NTO_SIDE_CHANNEL, 0);
if (-1 == dev->ssr_coid) {
I2C_SLOGE("I2C_RM SSR ConnectAttach failed (%s)", strerror(errno));
exit(1);
}
I2C_SLOGE("Initialized %s", dev->devname);
//第五步 通过不断循环等待dispatch_block()来调用MsgReceive()使Resource manger处于receive block状态,以接收上层发送来的消息,通过dispatch_handler (ctp)去调用我们自己定义的IO函数
while(1) {
if ((ctp = dispatch_block(dev->ctp)) == NULL) {
I2C_SLOGE("dispatch_block failed");
exit(1);
}
dispatch_handler(dev->ctp);
}
return 0;
}
4.4 API与资源管理器之间的关联
4.4.1 标准框架如下:
4.4.2 具体实现
TODO
拿devctl为例:
5. 梳理
6. 案例
TODO
//伪代码如下
#include <amss/i2c_client.h>
#define DEV_NAME "/dev/i2c_1"
int fd = -1;
int slaveAddr = 0xAA;
main()
{
unsigned char writedata[3];
writedata[0] = 0x11;
writedata[1] = 0x22;
writedata[2] = 0x22;
fd = i2c_open(DEV_NAME );
i2c_bus_lock ( fd );
i2c_set_slave_addr(fd, slaveAddr, 0))
i2c_write(fd, writedata, sizeof(writedata));
}