技术标签: camera
一直接触驱动,但没有系统的去学过驱动,对设备驱动的大体架构不是很了解,借着eeprom驱动,大致学习下linux下设备驱动以及i2c驱动。
android/kernel-4.19/drivers/misc/mediatek/cam_cal
在bringup阶段,会在
android/kernel-4.19/arch/arm64/configs/{project}_debug_defconfig与
android/kernel-4.19/arch/arm64/configs/{project}_defconfig配置
CONFIG_MTK_CAM_CAL=y
最下面mk与Kconfig为
这里相当于指定了obj-y,将生成的目标代码连接到内核。将src/目录列入向下迭代的目录中。
obj-$(CONFIG_MTK_CAM_CAL) += src/
Kconfig
config MTK_CAM_CAL
bool "MTK camera cal driver"
help
CONFIG_MTK_CAM_CAL define if the camera cal driver should be
built in or not. If you want to use EEPROM for sensor. You have
enable CONFIG_MTK_CAM_CAL on ProjectConfig. The kernel space for
EEPROM will be build. It may be like as "/dev/CAM_CAL_DRV".
Use space code will control the driver by open and ioctrl this one.
在上一层的android/kernel-4.19/drivers/misc/mediatek/Kconfig中
引用了此Kconfig
source "drivers/misc/mediatek/cam_cal/Kconfig"
makefile中
obj-$(CONFIG_MTK_CAM_CAL) += cam_cal/
接下来看驱动里面的makefile
android/kernel-4.19/drivers/misc/mediatek/cam_cal/src/Makefile
MTK_PLATFORM := $(subst ",,$(CONFIG_MTK_PLATFORM))
obj-y += $(MTK_PLATFORM)/
适配不同平台
:= 直接赋值 mk :=含义
subst 字符串替换,subst
进入到平台下:这里看mt6765/
MTK_CUSTOM_PATH := $(srctree)/drivers/misc/mediatek
MTK_PLATFORM := $(subst ",,$(CONFIG_MTK_PLATFORM))
CAM_CAL_SRC_PATH := $(MTK_CUSTOM_PATH)/cam_cal/src
CAM_CAL_PLATFORM_SRC_PATH := $(CAM_CAL_SRC_PATH)/$(MTK_PLATFORM)
ARCH_MTK_PROJECT := $(subst ",,$(CONFIG_ARCH_MTK_PROJECT))
COMMON_VERSION := v1
subdir-ccflags-y += -I$(MTK_CUSTOM_PATH)/imgsensor/inc \
-I$(MTK_CUSTOM_PATH)/imgsensor/src/$(CONFIG_MTK_PLATFORM)/camera_hw \
-I$(MTK_CUSTOM_PATH)/cam_cal/inc \
-I$(CAM_CAL_PLATFORM_SRC_PATH) \
-I$(CAM_CAL_SRC_PATH)/common/$(COMMON_VERSION)
subdir-ccflags-y += -I$(srctree)/drivers/i2c/busses/
# Common code
obj-y += ../common/$(COMMON_VERSION)/eeprom_driver.o
obj-y += ../common/$(COMMON_VERSION)/eeprom_i2c_common_driver.o
# Platform code
obj-y += eeprom_i2c_custom_driver.o
define INC_CUSTOM_SRC
ifeq ($(wildcard $(CAM_CAL_PLATFORM_SRC_PATH)/camera_project/$(ARCH_MTK_PROJECT)/$(1).c),)
obj-y += $(1).o
else
obj-y += camera_project/$(ARCH_MTK_PROJECT)/$(1).o
endif
endef
# Project or platform code
CUSTOM_SRC_LIST := cam_cal_list eeprom_i2c_dev
$(foreach src,$(CUSTOM_SRC_LIST),$(eval $(call INC_CUSTOM_SRC,$(src))))
subdir-ccflags-y += -I
意思是ccflags-y,编译时的参数,-I寻找头文件的目录
参考:编译选项
-I选项
此处没有针对项目客制化,camera_project/就不存在了。
此处关注
# Common code
obj-y += ../common/$(COMMON_VERSION)/eeprom_driver.o
obj-y += ../common/$(COMMON_VERSION)/eeprom_i2c_common_driver.o
# Platform code
obj-y += eeprom_i2c_custom_driver.o
这三个对应的.c文件
参考字符驱动架构
android/kernel-4.19/drivers/misc/mediatek/cam_cal/src/common/v1/eeprom_driver.c
首先,模块加载方法与卸载方法
static int __init EEPROM_drv_init(void)
{
pr_debug("%s Start!\n", __func__);
if (platform_driver_register(&g_stEEPROM_HW_Driver)) {
pr_debug("failed to register EEPROM driver i2C main\n");
return -ENODEV;
}
if (platform_device_register(&g_platDev)) {
pr_debug("failed to register EEPROM device");
return -ENODEV;
}
EEPROM_chrdev_register();
pr_debug("%s End!\n", __func__);
return 0;
}
static void __exit EEPROM_drv_exit(void)
{
platform_device_unregister(&g_platDev);
platform_driver_unregister(&g_stEEPROM_HW_Driver);
EEPROM_chrdev_unregister();
}
module_init(EEPROM_drv_init);
module_exit(EEPROM_drv_exit);
platform_driver_register可参考
其中:g_stEEPROM_HW_Driver 对应
static struct platform_driver g_stEEPROM_HW_Driver = {
.probe = EEPROM_HW_probe,
.remove = EEPROM_HW_remove,
.driver = {
.name = CAM_CAL_DRV_NAME,
.owner = THIS_MODULE,
}
};
EEPROM_chrdev_register()进行分配设备号等操作:
关键是g_charDrv,是一个cdev类型的结构体参考:cdev
static struct cdev *g_charDrv;
#define CAM_CAL_DYNAMIC_ALLOCATE_DEVNO 1
static inline int EEPROM_chrdev_register(void)
{
struct device *device = NULL;
pr_debug("%s Start\n", __func__);
#if CAM_CAL_DYNAMIC_ALLOCATE_DEVNO
if (alloc_chrdev_region(&g_devNum, 0, 1, CAM_CAL_DRV_NAME)) {
//动态分配主次设备号
pr_debug("Allocate device no failed\n");
return -EAGAIN;
}
#else
if (register_chrdev_region(g_devNum, 1, CAM_CAL_DRV_NAME)) {
pr_debug("Register device no failed\n");
return -EAGAIN;
}
#endif
g_charDrv = cdev_alloc();//空间的申请
if (g_charDrv == NULL) {
unregister_chrdev_region(g_devNum, 1);
pr_debug("Allocate mem for kobject failed\n");
return -ENOMEM;
}
cdev_init(g_charDrv, &g_stCAM_CAL_fops1);//对空间清零并赋值ops
g_charDrv->owner = THIS_MODULE;
if (cdev_add(g_charDrv, g_devNum, 1)) {
//注册字符设备对象cdev到内核
pr_debug("Attatch file operation failed\n");
unregister_chrdev_region(g_devNum, 1);
return -EAGAIN;
}
g_drvClass = class_create(THIS_MODULE, "CAM_CALdrv1");
// 以CLASS_NAME创建一个class结构,这个动作将会在/sys/class目录创建一个名为CLASS_NAME的目录
if (IS_ERR(g_drvClass)) {
int ret = PTR_ERR(g_drvClass);
pr_debug("Unable to create class, err = %d\n", ret);
return ret;
}
device = device_create(g_drvClass, NULL, g_devNum, NULL,
CAM_CAL_DRV_NAME);
//以DEVICE_NAME为名,参考/sys/class/CLASS_NAME在/dev目录下创建一个设备:/dev/DEVICE_NAME
pr_debug("%s End\n", __func__);
return 0;
}
alloc_chrdev_region(&g_devNum, 0, 1, CAM_CAL_DRV_NAME)// 为字符设备动态申请第一个设备号
CAM_CAL_DRV_NAME定义为,也就是设备节点,在/dev下面可以看到
#define CAM_CAL_DRV_NAME "CAM_CAL_DRV"
alloc_chrdev_region动态分配主次设备号,详细参考
&g_devNum为
#define CAM_CAL_DEV_MAJOR_NUMBER 226
static dev_t g_devNum = MKDEV(CAM_CAL_DEV_MAJOR_NUMBER, 0);
MKDEV是将主设备号和次设备号转换成dev_t类型的一个内核函数。参考
cdev_alloc与cdev_init参考
cdev_add 注册字符设备对象cdev到内核,
这里的g_stCAM_CAL_fops1,可参考
对应:
static const struct file_operations g_stCAM_CAL_fops1 = {
.owner = THIS_MODULE,
.open = EEPROM_drv_open,
.release = EEPROM_drv_release,
/*.ioctl = CAM_CAL_Ioctl */
#ifdef CONFIG_COMPAT
.compat_ioctl = EEPROM_drv_compat_ioctl,
#endif
.unlocked_ioctl = EEPROM_drv_ioctl
};
CONFIG_COMPAT是用来兼容64位与32位的,
接下来看ioctl实现,先看两个重要的机构体
stCAM_CAL_INFO_STRUCT
struct stCAM_CAL_INFO_STRUCT {
u32 u4Offset;
u32 u4Length;
u32 sensorID;
/*
* MAIN = 0x01,
* SUB = 0x02,
* MAIN_2 = 0x04,
* SUB_2 = 0x08,
* MAIN_3 = 0x10,
*/
u32 deviceID;
u8 *pu1Params;
};
stCAM_CAL_CMD_INFO_STRUCT
struct stCAM_CAL_CMD_INFO_STRUCT {
unsigned int sensorID;
unsigned int deviceID;
unsigned int i2cAddr;
struct i2c_client *client;
cam_cal_cmd_func readCMDFunc;
cam_cal_cmd_func writeCMDFunc;
unsigned int maxEepromSize;
};
ioctl实现
static long EEPROM_drv_ioctl(struct file *file,
unsigned int a_u4Command, unsigned long a_u4Param)
{
int i4RetValue = 0;
u8 *pBuff = NULL;
u8 *pu1Params = NULL;
/*u8 *tempP = NULL; */
struct stCAM_CAL_INFO_STRUCT *ptempbuf = NULL;
struct stCAM_CAL_CMD_INFO_STRUCT *pcmdInf = NULL;//初始化
if (_IOC_DIR(a_u4Command) != _IOC_NONE) {
//判断传送到设备驱动程序的 cmd 命令的读写状态
pBuff = kmalloc(sizeof(struct stCAM_CAL_INFO_STRUCT),
GFP_KERNEL);//分配内存,GFP_KERNEL是内核内存分配时最常用的,无内存可用时可引起休眠
if (pBuff == NULL) {
pr_debug("ioctl allocate pBuff mem failed\n");
return -ENOMEM;
}
if (copy_from_user
((u8 *) pBuff, (u8 *) a_u4Param,
sizeof(struct stCAM_CAL_INFO_STRUCT))) {
//将用户空间a_u4Param拷贝到pBuff
/*get input structure address */
kfree(pBuff);
pr_debug("ioctl copy from user failed\n");
return -EFAULT;
}
ptempbuf = (struct stCAM_CAL_INFO_STRUCT *)pBuff;
if ((ptempbuf->u4Length <= 0) ||
(ptempbuf->u4Length > CAM_CAL_MAX_BUF_SIZE)) {
//防止上层传下来的参数过大或过小
kfree(pBuff);
pr_debug("Buffer Length Error!\n");
return -EFAULT;
}
pu1Params = kmalloc(ptempbuf->u4Length, GFP_KERNEL);//为u4Length分配空间
if (pu1Params == NULL) {
kfree(pBuff);
pr_debug("ioctl allocate pu1Params mem failed\n");
return -ENOMEM;
}
if (copy_from_user
((u8 *) pu1Params, (u8 *) ptempbuf->pu1Params,
ptempbuf->u4Length)) {
//拿到参数
kfree(pBuff);
kfree(pu1Params);
pr_debug("ioctl copy from user failed\n");
return -EFAULT;
}
}
if (ptempbuf == NULL) {
/*It have to add */
pr_debug("ptempbuf is Null !!!");
return -EFAULT;
}
switch (a_u4Command) {
case CAM_CALIOC_S_WRITE: /*Note: Write Command is Unverified! */
pr_debug("CAM_CALIOC_S_WRITE start!\n");
pcmdInf = EEPROM_get_cmd_info_ex(ptempbuf->sensorID,
ptempbuf->deviceID);//根据id,拿到i2c地址,读写函数
/* Check the max size if specified */
if (pcmdInf != NULL &&
(pcmdInf->maxEepromSize != 0) &&
(pcmdInf->maxEepromSize <
(ptempbuf->u4Offset + ptempbuf->u4Length))) {
pr_debug("Error!! not support address >= 0x%x!!\n",
pcmdInf->maxEepromSize);
kfree(pBuff);
kfree(pu1Params);
return -EFAULT;
}
if (pcmdInf != NULL && g_lastDevID != ptempbuf->deviceID) {
if (EEPROM_set_i2c_bus(ptempbuf->deviceID,//设置i2c
pcmdInf) != 0) {
pr_debug("deviceID Error!\n");
kfree(pBuff);
kfree(pu1Params);
return -EFAULT;
}
g_lastDevID = ptempbuf->deviceID;
}
if (pcmdInf != NULL) {
if (pcmdInf->writeCMDFunc != NULL) {
i4RetValue = pcmdInf->writeCMDFunc(//执行写函数
pcmdInf->client,
ptempbuf->u4Offset, pu1Params,
ptempbuf->u4Length);
} else
pr_debug("pcmdInf->writeCMDFunc == NULL\n");
} else
pr_debug("pcmdInf == NULL\n");
pr_debug("CAM_CALIOC_S_WRITE End!\n");
break;
case CAM_CALIOC_G_READ:
pr_debug("CAM_CALIOC_G_READ start! offset=%d, length=%d\n",
ptempbuf->u4Offset, ptempbuf->u4Length);
pr_debug("SensorID=%x DeviceID=%x\n",
ptempbuf->sensorID, ptempbuf->deviceID);
pcmdInf = EEPROM_get_cmd_info_ex(
ptempbuf->sensorID,
ptempbuf->deviceID);
/* Check the max size if specified */
if (pcmdInf != NULL &&
(pcmdInf->maxEepromSize != 0) &&
(pcmdInf->maxEepromSize <
(ptempbuf->u4Offset + ptempbuf->u4Length))) {
pr_debug("Error!! not support address >= 0x%x!!\n",
pcmdInf->maxEepromSize);
kfree(pBuff);
kfree(pu1Params);
return -EFAULT;
}
if (pcmdInf != NULL && g_lastDevID != ptempbuf->deviceID) {
if (EEPROM_set_i2c_bus(ptempbuf->deviceID,
pcmdInf) != 0) {
pr_debug("deviceID Error!\n");
kfree(pBuff);
kfree(pu1Params);
return -EFAULT;
}
g_lastDevID = ptempbuf->deviceID;
}
if (pcmdInf != NULL) {
if (pcmdInf->readCMDFunc != NULL)
i4RetValue =
pcmdInf->readCMDFunc(pcmdInf->client,
ptempbuf->u4Offset,
pu1Params,
ptempbuf->u4Length);
else {
pr_debug("pcmdInf->readCMDFunc == NULL\n");
kfree(pBuff);
kfree(pu1Params);
return -EFAULT;
}
}
break;
default:
pr_debug("No CMD\n");
i4RetValue = -EPERM;
break;
}
if (_IOC_READ & _IOC_DIR(a_u4Command)) {
if (copy_to_user
((u8 __user *) ptempbuf->pu1Params, (u8 *) pu1Params,
ptempbuf->u4Length)) {
kfree(pBuff);
kfree(pu1Params);
pr_debug("ioctl copy to user failed\n");
return -EFAULT;
}
}
kfree(pBuff);
kfree(pu1Params);
return i4RetValue;
}
_IO, _IOR, _IOW, _IOWR 宏的用法与解析参考
CAM_CALIOC_G_READ定义
hal定义在/vendor/mediatek/proprietary/custom/mt6765/kernel/cam_cal/inc/cam_cal.h
kernel定义在/kernel-4.9/drivers/misc/mediatek/cam_cal/inc/
/*CAM_CAL write*/
#define CAM_CALIOC_S_WRITE _IOW(CAM_CALAGIC, 0, struct stCAM_CAL_INFO_STRUCT)
/*CAM_CAL read*/
#define CAM_CALIOC_G_READ _IOWR(CAM_CALAGIC, 5, struct stCAM_CAL_INFO_STRUCT)
ioctol函数重点关注三个部分:
EEPROM_get_cmd_info
EEPROM_set_i2c_bus
pcmdInf->readCMDFunc
这三个函数
EEPROM_get_cmd_info_ex会去校验配置的读写函数:
static struct stCAM_CAL_CMD_INFO_STRUCT *EEPROM_get_cmd_info_ex
(unsigned int sensorID, unsigned int deviceID)
{
int i = 0;
/* To check device ID */
for (i = 0; i < IMGSENSOR_SENSOR_IDX_MAX_NUM; i++) {
if (g_camCalDrvInfo[i].deviceID == deviceID)
break;
}
/* To check cmd from Sensor ID */
if (i == IMGSENSOR_SENSOR_IDX_MAX_NUM) {
for (i = 0; i < IMGSENSOR_SENSOR_IDX_MAX_NUM; i++) {
/* To Set Client */
if (g_camCalDrvInfo[i].sensorID == 0) {
pr_debug("Start get_cmd_info!\n");
EEPROM_get_cmd_info(sensorID,
&g_camCalDrvInfo[i]);
if (g_camCalDrvInfo[i].readCMDFunc != NULL) {
g_camCalDrvInfo[i].sensorID = sensorID;
g_camCalDrvInfo[i].deviceID = deviceID;
pr_debug("deviceID=%d, SensorID=%x\n",
deviceID, sensorID);
}
break;
}
}
}
if (i == IMGSENSOR_SENSOR_IDX_MAX_NUM) {
/*g_camCalDrvInfo is full */
return NULL;
} else {
return &g_camCalDrvInfo[i];
}
}
接着调用到EEPROM_get_cmd_info,根据sensor_id去拿到相应的配置
static int EEPROM_get_cmd_info(unsigned int sensorID,
struct stCAM_CAL_CMD_INFO_STRUCT *cmdInfo)
{
struct stCAM_CAL_LIST_STRUCT *pCamCalList = NULL;
int i = 0;
cam_cal_get_sensor_list(&pCamCalList);
if (pCamCalList != NULL) {
pr_debug("pCamCalList!=NULL && pCamCalFunc!= NULL\n");
for (i = 0; pCamCalList[i].sensorID != 0; i++) {
if (pCamCalList[i].sensorID == sensorID) {
pr_debug("pCamCalList[%d].sensorID==%x\n", i,
pCamCalList[i].sensorID);
cmdInfo->i2cAddr = pCamCalList[i].slaveID >> 1;
cmdInfo->readCMDFunc =
pCamCalList[i].readCamCalData;
//add for dual camera write cal data begin
cmdInfo->writeCMDFunc = pCamCalList[i].writeCamCalData;
//add for dual camera write cal data end
cmdInfo->maxEepromSize =
pCamCalList[i].maxEepromSize;
/*
* Default 8K for Common_read_region driver
* 0 for others
*/
if (cmdInfo->readCMDFunc == Common_read_region
&& cmdInfo->maxEepromSize == 0) {
cmdInfo->maxEepromSize =
DEFAULT_MAX_EEPROM_SIZE_8K;
}
return 1;
}
}
}
return 0;
}
cam_cal_get_sensor_list(&pCamCalList);会去拿到
struct stCAM_CAL_LIST_STRUCT g_camCalList[] = {
{
S5K3L6_SUNWIN_SENSOR_ID, 0xA0, Common_read_region,Common_write_region},
{
S5K3L6_TSP_SENSOR_ID, 0xA0, Common_read_region,Common_write_region},
/* ADD before this line */
{
0, 0, 0} /*end of list */
};
unsigned int cam_cal_get_sensor_list(
struct stCAM_CAL_LIST_STRUCT **ppCamcalList)
{
if (ppCamcalList == NULL)
return 1;
*ppCamcalList = &g_camCalList[0];
return 0;
}
之前,在init时,当检测到匹配的device - driver,调用probe()函数
static struct platform_driver g_stEEPROM_HW_Driver = {
.probe = EEPROM_HW_probe,
.remove = EEPROM_HW_remove,
.driver = {
.name = CAM_CAL_DRV_NAME,
.owner = THIS_MODULE,
}
};
即:i2c_add_driver参考:i2c设备驱动
static int EEPROM_HW_probe(struct platform_device *pdev)
{
i2c_add_driver(&EEPROM_HW_i2c_driver2);
i2c_add_driver(&EEPROM_HW_i2c_driver3);
return i2c_add_driver(&EEPROM_HW_i2c_driver);
}
EEPROM_HW_i2c_driver就对应i2c_driver结构体
struct i2c_driver EEPROM_HW_i2c_driver = {
.probe = EEPROM_HW_i2c_probe,
.remove = EEPROM_HW_i2c_remove,
.driver = {
.name = CAM_CAL_DRV_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = EEPROM_HW_i2c_of_ids,//对应设备树节点:{.compatible = "mediatek,camera_main_eeprom",},
#endif
},
.id_table = EEPROM_HW_i2c_id,//对应宏CAM_CAL_DRV_NAME,存储driver名称,作为bus匹配时的识别
};
设备树节点,此处会匹配camera_main_eeprom,老平台,需在dws中去配置。
mtk 新的5g平台,直接在cust_${Project}_camera.dtsi中配置。
EEPROM_HW_i2c_probe为: 此处对client,addr等信息进行赋值
static int EEPROM_HW_i2c_probe
(struct i2c_client *client, const struct i2c_device_id *id)
{
/* get sensor i2c client */
spin_lock(&g_spinLock);
g_pstI2Cclients[I2C_DEV_IDX_1] = client;
/* set I2C clock rate */
#ifdef CONFIG_MTK_I2C_EXTENSION
g_pstI2Cclients[I2C_DEV_IDX_1]->timing = gi2c_dev_timing[I2C_DEV_IDX_1];
g_pstI2Cclients[I2C_DEV_IDX_1]->ext_flag &= ~I2C_POLLING_FLAG;
#endif
/* Default EEPROM Slave Address Main= 0xa0 */
g_pstI2Cclients[I2C_DEV_IDX_1]->addr = 0x50;
spin_unlock(&g_spinLock);
return 0;
}
接下来回到ioctol中,在ioctol中调用
if (EEPROM_set_i2c_bus(ptempbuf->deviceID,
pcmdInf) != 0)
来到:设置client与addr,addr就是从cmdInfo,即配置的结构体中拿到的
将client放到cmdInfo这个结构体中
static int EEPROM_set_i2c_bus(unsigned int deviceID,
struct stCAM_CAL_CMD_INFO_STRUCT *cmdInfo)
{
enum IMGSENSOR_SENSOR_IDX idx;
enum EEPROM_I2C_DEV_IDX i2c_idx;
struct i2c_client *client;
idx = IMGSENSOR_SENSOR_IDX_MAP(deviceID);
i2c_idx = get_i2c_dev_sel(idx);
if (idx == IMGSENSOR_SENSOR_IDX_NONE)
return -EFAULT;
if (i2c_idx < I2C_DEV_IDX_1 || i2c_idx >= I2C_DEV_IDX_MAX)
return -EFAULT;
client = g_pstI2Cclients[i2c_idx];
pr_debug("%s end! deviceID=%d index=%u client=%p\n",
__func__, deviceID, idx, client);
if (client == NULL) {
pr_err("i2c client is NULL");
return -EFAULT;
}
if (cmdInfo != NULL) {
client->addr = cmdInfo->i2cAddr;
cmdInfo->client = client;
}
return 0;
}
接下来看ioctol read中的
pcmdInf->readCMDFunc(pcmdInf->client,
ptempbuf->u4Offset,
pu1Params,
ptempbuf->u4Length);
即为:
unsigned int Common_read_region(struct i2c_client *client, unsigned int addr,
unsigned char *data, unsigned int size)
{
g_pstI2CclientG = client;
if (iReadData_CAM_CAL(addr, size, data) == 0)
return size;
else
return 0;
}
再调用到
int iReadData_CAM_CAL(unsigned int ui4_offset,
unsigned int ui4_length, unsigned char *pinputdata)
{
int i4RetValue = 0;
int i4ResidueDataLength;
u32 u4IncOffset = 0;
u32 u4CurrentOffset;
u8 *pBuff;
i4ResidueDataLength = (int)ui4_length;
u4CurrentOffset = ui4_offset;
pBuff = pinputdata;
do {
if (i4ResidueDataLength >= EEPROM_I2C_READ_MSG_LENGTH_MAX) {
i4RetValue = Read_I2C_CAM_CAL(
(u16) u4CurrentOffset,
EEPROM_I2C_READ_MSG_LENGTH_MAX, pBuff);
if (i4RetValue != 0) {
pr_debug("I2C iReadData failed!!\n");
return -1;
}
u4IncOffset += EEPROM_I2C_READ_MSG_LENGTH_MAX;
i4ResidueDataLength -= EEPROM_I2C_READ_MSG_LENGTH_MAX;
u4CurrentOffset = ui4_offset + u4IncOffset;
pBuff = pinputdata + u4IncOffset;
} else {
i4RetValue =
Read_I2C_CAM_CAL(
(u16) u4CurrentOffset, i4ResidueDataLength, pBuff);
if (i4RetValue != 0) {
pr_debug("I2C iReadData failed!!\n");
return -1;
}
u4IncOffset += i4ResidueDataLength;
i4ResidueDataLength = 0;
u4CurrentOffset = ui4_offset + u4IncOffset;
pBuff = pinputdata + u4IncOffset;
/* break; */
}
} while (i4ResidueDataLength > 0);
return 0;
}
EEPROM_I2C_READ_MSG_LENGTH_MAX
一次读多少,如果没设置,默认为一次读32次。
接下来Read_I2C_CAM_CAL,就是构建read函数:
static int Read_I2C_CAM_CAL(u16 a_u2Addr, u32 ui4_length, u8 *a_puBuff)
{
int i4RetValue = 0;
char puReadCmd[2] = {
(char)(a_u2Addr >> 8), (char)(a_u2Addr & 0xFF) };
struct i2c_msg msg[EEPROM_I2C_MSG_SIZE_READ];
if (ui4_length > EEPROM_I2C_READ_MSG_LENGTH_MAX) {
pr_debug("exceed one transition %d bytes limitation\n",
EEPROM_I2C_READ_MSG_LENGTH_MAX);
return -1;
}
spin_lock(&g_spinLock);
g_pstI2CclientG->addr =
g_pstI2CclientG->addr & (I2C_MASK_FLAG | I2C_WR_FLAG);
spin_unlock(&g_spinLock);
msg[0].addr = g_pstI2CclientG->addr;
msg[0].flags = g_pstI2CclientG->flags & I2C_M_TEN;
msg[0].len = 2;
msg[0].buf = puReadCmd;
msg[1].addr = g_pstI2CclientG->addr;
msg[1].flags = g_pstI2CclientG->flags & I2C_M_TEN;
msg[1].flags |= I2C_M_RD;
msg[1].len = ui4_length;
msg[1].buf = a_puBuff;
i4RetValue = i2c_transfer(g_pstI2CclientG->adapter,
msg,
EEPROM_I2C_MSG_SIZE_READ);
spin_lock(&g_spinLock);
g_pstI2CclientG->addr = g_pstI2CclientG->addr & I2C_MASK_FLAG;
spin_unlock(&g_spinLock);
if (i4RetValue != EEPROM_I2C_MSG_SIZE_READ) {
pr_debug("I2C read data failed!!\n");
return -1;
}
return 0;
}
文章浏览阅读936次,点赞22次,收藏26次。React核心基础
文章浏览阅读2k次。linux系统查看磁盘空间的命令是【df -hl】,该命令可以查看磁盘剩余空间大小。如果要查看每个根路径的分区大小,可以使用【df -h】命令。df命令以磁盘分区为单位查看文件系统。本文操作环境:red hat enterprise linux 6.1系统、thinkpad t480电脑。(学习视频分享:linux视频教程)Linux 查看磁盘空间可以使用 df 和 du 命令。df命令df 以磁..._df -hl
文章浏览阅读923次。uses ComObj;var ExcelApp: OleVariant;implementationprocedure TForm1.Button1Click(Sender: TObject);const // SheetType xlChart = -4109; xlWorksheet = -4167; // WBATemplate xlWBATWorksheet = -4167_range[char(96 + acolumn) + inttostr(65536)].end[xlup]
文章浏览阅读2.3k次。上图为任务代码,在任务具体执行的方法中使用,一定要写在方法内使用SpringContextUtil.getBean()方法实例化Spring service类下边是ruoyi-quartz模块中util/SpringContextUtil.java(已改写)import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.s..._ruoyi-quartz无法引入ruoyi-admin的service
文章浏览阅读2w次,点赞10次,收藏77次。yum,全称“Yellow dog Updater, Modified”,是一个专门为了解决包的依赖关系而存在的软件包管理器。可以这么说,yum 是改进型的 RPM 软件管理器,它很好的解决了 RPM 所面临的软件包依赖问题。yum 在服务器端存有所有的 RPM 包,并将各个包之间的依赖关系记录在文件中,当管理员使用 yum 安装 RPM 包时,yum 会先从服务器端下载包的依赖性文件,通过分析此文件从服务器端一次性下载所有相关的 RPM 包并进行安装。_centos7配置yum源
文章浏览阅读828次,点赞21次,收藏8次。今天学长向大家分享一个毕业设计项目毕业设计 基于深度学习的抽烟行为检测算法实现(源码分享)毕业设计 深度学习的抽烟行为检测算法实现通过目前应用比较广泛的 Web 开发平台,将模型训练完成的算法模型部署,部署于 Web 平台。并且利用目前流行的前后端技术在该平台进行整合实现运营车辆驾驶员吸烟行为检测系统,方便用户使用。本系统是一种运营车辆驾驶员吸烟行为检测系统,为了降低误检率,对驾驶员视频中的吸烟烟雾和香烟目标分别进行检测,若同时检测到则判定该驾驶员存在吸烟行为。进行流程化处理,以满足用户的需要。
文章浏览阅读3.7k次,点赞3次,收藏14次。多个定时器同步触发启动是一种比较实用的功能,这里将对此做个示例说明。_stm32 定时器同步
文章浏览阅读348次。出处 : http://www.cnblogs.com/mythou/p/3187881.html本来想分析AppsCustomizePagedView类,不过今天突然接到一个临时任务。客户反馈说机器界面的图标很难点击启动程序,经常点击了没有反应,Boss说要优先解决这问题。没办法,只能看看是怎么回事。今天分析一下Launcher启动APP的过程。从用户点击到程序启动的流程,下面针对WorkSpa..._回调bubbletextview
文章浏览阅读6.2k次。Ubuntu 12 最快的两个源 个人感觉 163与cn99最快 ubuntu下包过慢 1、首先备份Ubuntu 12.04源列表 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup (备份下当前的源列表,有备无患嘛) 2、修改更新源 sudo gedit /etc/apt/sources.list (打开Ubuntu 12_un.12.cc
文章浏览阅读5.8k次,点赞6次,收藏86次。1.思路(1)动态添加路由肯定用的是addRouter,在哪用?(2)vuex当中获取到菜单,怎样展示到界面2.不管其他先试一下addRouter找到router/index.js文件,内容如下,这是我自己先配置的登录路由现在先不管请求到的菜单是什么样,先写一个固定的菜单通过addRouter添加添加以前注意:addRoutes()添加的是数组在export defult router的上一行图中17行写下以下代码var addRoute=[ { path:"/", name:"_vue动态路由权限
文章浏览阅读8.9k次。 关键词: JSTL 之变量赋值标签 /* * Author Yachun Miao * Created 11-Dec-06 */关于JSP核心库的set标签赋值变量,有两种方式: 1.日期" />2. 有种需求要把ApplicationResources_zh_CN.prope
文章浏览阅读3.1k次,点赞3次,收藏2次。1.1ZY5621概述ZY5621是VGA音频到HDMI转换器芯片,它符合HDMI1.4 DV1.0规范。ZY5621也是一款先进的高速转换器,集成了MCU和VGA EDID芯片。它还包含VGA输入指示和仅音频到HDMI功能。进一步降低系统制造成本,简化系统板上的布线。ZY5621方案设计简单,且可以完美还原输入端口的信号,此方案设计广泛应用于投影仪、教育多媒体、视频会议、视频展台、工业级主板显示、手持便携设备、转换盒、转换线材等产品设计上面。1.2 ZY5621 特性内置MCU嵌入式VGA_vga转hdmi带音频转换器,转接头拆解