MTK平台闪光灯驱动分析

时间:2021-01-19 20:42:46

MTK平台闪光灯驱动分析

  以前没写过博客,总想写着来着,把之前学到的做过的东西都记录下来,但是一直没有时间也没那么大的决心。这次趁着刚换工作,正在学习熟悉平台不是太忙的机会,把自己总结的文档写下来,算是给自己一个全新的开始吧。
  这次我分析的事闪光灯控制器的驱动代码,使用的芯片是TI的LM3644双闪光灯控制芯片,由于是第一次写,就只分析这个驱动好了,该驱动的相关技术知识在接下来的博客中一一分析,不过现在先记录一下,免得之后忘了。
  写过这篇博客之后,要对接下来的知识进行分析。
    1. Linux文件系统挂载流程
    2. devtmpfs的挂载流程及运行原理
    3. platform总线初始化及原理
    4. I2C总线初始化及原理分析
  在第3,4目标中要掺杂Linux设备模型及sysfs文件系统框架的介绍。好了,给自己定好目标了,接下来开始今天的正片吧。

一、芯片工作原理

  该芯片通过一个I2C接口与AP通信。可为闪光灯模式的LED提供93mA到1.5A的电流,可为手电筒模式的LED提供48.4mA到375mA的电流。我们可以通过I2C设置该芯片内部的寄存器,继而控制哪个闪光灯开启/关闭、进入闪光灯/手电筒模式、闪光灯模式时的电流大小、手电筒模式的电流大小、闪光灯开启/结束时间等。该芯片的I2C地址是0x63,挂在AP的I2C Bus 2。I2C地址是芯片手册上可以看到的,挂在I2C的哪条总线上这个你要和你的HW工程师沟通或者自己看原理图。

二、硬件连接图

  由于信息安全问题,我不能把公司项目的硬件原理图贴出来,但是可以贴出datasheet上的参考电路,大致是一样的。
  MTK平台闪光灯驱动分析
  我们只使用上图的SDA,SCL引脚用于I2C通信。

三、代码分析

3.1、驱动初始化

  在驱动文件中如下所示的代码是该内核模块的入口和出口。

module_init(flashlight_init);
module_exit(flashlight_exit);

很明显其初始化函数是flashlight_init()函数。接下来我们开看该函数是怎么实现的。

static int __init flashlight_init(void)
{
int ret = 0;
logI("[flashlight_probe] start ~");

ret = platform_device_register (&flashlight_platform_device);//注册platform设备
if (ret) {
logE("[flashlight_probe] platform_device_register fail ~");
return ret;
}

ret = platform_driver_register(&flashlight_platform_driver);//注册platform驱动
if(ret){
logE("[flashlight_probe] platform_driver_register fail ~");
return ret;
}

register_low_battery_notify(&Lbat_protection_powerlimit_flash, LOW_BATTERY_PRIO_FLASHLIGHT);//添加低电量notify函数
register_battery_percent_notify(&bat_per_protection_powerlimit_flashlight, BATTERY_PERCENT_PRIO_FLASHLIGHT);
//@@ register_battery_oc_notify(&bat_oc_protection_powerlimit, BATTERY_OC_PRIO_FLASHLIGHT);

logI("[flashlight_probe] done! ~");
return ret;
}

在此函数中有两个重要的函数,platform_device_register( ),platform_driver_register( )。他们分别是platform设备,和platform设备驱动注册。这遵循linux设备模型原理,分别把device,和driver注册进platform bus中。同时由于platform bus中的match函数platform_match实现如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

最终会通过strcmp(pdev->name,drv->name)来匹配device和driver。及匹配其name字段。

static struct platform_driver flashlight_platform_driver =
{
.probe = flashlight_probe,
.remove = flashlight_remove,
.shutdown = flashlight_shutdown,
.driver = {
.name = FLASHLIGHT_DEVNAME,
.owner = THIS_MODULE,
},
};

static struct platform_device flashlight_platform_device = {
.name = FLASHLIGHT_DEVNAME,
.id = 0,
.dev = {
}
};

可以看出flashlight_platform_driver和flashlight_platform_device的name都是FLASHLIGHT_DEVNAME宏,即kd_camera_flashlight。所以会调用到该驱动的probe函数,即flashlight_probe函数。

3.2、注册真正的驱动

在probe函数中,主要的工作是注册了一个字符驱动。

    ret = alloc_chrdev_region(&flashlight_devno, 0, 1, FLASHLIGHT_DEVNAME);
if (ret) {
logE("[flashlight_probe] alloc_chrdev_region fail: %d ~", ret);
goto flashlight_probe_error;
} else {
logI("[flashlight_probe] major: %d, minor: %d ~", MAJOR(flashlight_devno), MINOR(flashlight_devno));
}
cdev_init(&flashlight_cdev, &flashlight_fops);
flashlight_cdev.owner = THIS_MODULE;
err = cdev_add(&flashlight_cdev, flashlight_devno, 1);
if (err) {
logE("[flashlight_probe] cdev_add fail: %d ~", err);
goto flashlight_probe_error;
}

  通过alloc_chrdev_region来获得字符驱动设备号,通过cdev_init初始化flashlight_cdev结构体,通过cdev_add来注册一个字符驱动。同时调用class_create,device_create在sysfs中创建class和device,来自动创建设备节点。

    flashlight_class = class_create(THIS_MODULE, "flashlightdrv");
if (IS_ERR(flashlight_class)) {
logE("[flashlight_probe] Unable to create class, err = %d ~", (int)PTR_ERR(flashlight_class));
goto flashlight_probe_error;
}

flashlight_device = device_create(flashlight_class, NULL, flashlight_devno, NULL, FLASHLIGHT_DEVNAME);
if(NULL == flashlight_device){
logE("[flashlight_probe] device_create fail ~");
goto flashlight_probe_error;
}

  在这个地方有一个需要注意的地方,在2.6的时候内核舍弃了devfs文件系统,转而使用udev来创建设备节点。udev可以通过内核中的kobject_uevent函数在用户空间接受内核空间发送的uevent事件。kobject_uevent函数是通过netlink来实现的。但是在现在的内核中又使用了devtmpfs使内核自己创建设备节点。即在device_create函数中调用device_add函数,device_add中调用devtmpfs_create_node(dev);来在devtmpfs上创建设备节点。具体为什么使用devtmpfs,我也没有找到比较官方的说法,比较为我接受的说法是,内核为了更快速的启动,所以在用户文件系统没有完全加载的情况下,先使用devtmpfs来创建设备文件,而不是等待用户空间的udev启动后再创建设备节点。关于devtmpfs文件系统的分析我们放在之后的文章中分析,敬请期待。

3.3、控制函数- -ioctl函数分析

字符驱动注册成功,同时设备文件创建成功后,就可以操作/dev/kd_camera_flashlight来控制闪光灯了。最主要的函数即是ioctl函数了,即my_ioctl_compat。

static struct file_operations flashlight_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = flashlight_ioctl,
.open = flashlight_open,
.release = flashlight_release,
#ifdef CONFIG_COMPAT
.compat_ioctl = my_ioctl_compat,
#endif
};

  对于使用了compat_sys_ioctl的linux系统中,ioct的调用顺序是先compat_ioctl,如果没有就调用unlocked_ioctl,再没有就调用vfs_ioctl。
在my_ioctl_compat函数中主要是调用了flashlight_ioctl_core函数。该函数主要接受如下几个命令FLASHLIGHTIOC_X_SET_DRIVER、FLASH_IOC_SET_TIME_OUT_TIME_MS、FLASH_IOC_SET_ONOFF、FLASH_IOC_SET_DUTY等。即来设置使用的驱动代码,设置开和关,设置延时时间,设置闪光等级。

3.4、设置真正的驱动控制程序

  在flashlight_ioctl_core函数中,通过setFlashDrv来设置驱动控制代码。即填充g_pFlashInitFunc结构体,其保存了具体闪光灯芯片的控制代码函数。FLASH_IOC_SET_ONOFF、FLASH_IOC_SET_TIME_OUT_TIME_MS、FLASH_IOC_SET_DUTY三个命令也是最终使用g_pFlashInitFunc结构体内的ioctl函数来执行的。
  现在我们来分析一下具体操作闪光灯控制芯片的代码。
  首先先注册了一个I2C设备,如下面代码所示通过i2c_register_board_info来注册一个i2c devices,使用i2c_add_driver注册一个i2c driver。同时由于driver的id_table中name字段和i2c device中的type字段相同,匹配成功后调用probe函数。probe函数只做了一件事,即把i2c_client结构体保存到全局变量中,为之后的i2c reg read/write使用。关于I2C总线的具体分析,我们也放在之后的文章中好好分析。

static struct i2c_board_info __initdata i2c_LM3644={I2C_BOARD_INFO(LM3644_NAME, STROBE_DEVICE_ID>>1)};
static int __init LM3644_init(void)
{
printk("LM3644_init\n");

i2c_register_board_info(2, &i2c_LM3644, 1);//注册I2C设备


return i2c_add_driver(&LM3644_i2c_driver);//注册I2C驱动
}

static void __exit LM3644_exit(void)
{
i2c_del_driver(&LM3644_i2c_driver);
}

module_init(LM3644_init);
module_exit(LM3644_exit);

之后对闪光灯的控制,即闪光等级,开关等都是通过i2c读写来操作的

static int readReg(u8 reg)
{
int val=0;

mutex_lock(&g_strobeLock);
val = i2c_smbus_read_byte_data(LM3644_i2c_client, reg);
mutex_unlock(&g_strobeLock);
return val;
}

static int writeReg(u8 reg, u8 data)
{
int ret=0;

mutex_lock(&g_strobeLock);
ret = i2c_smbus_write_byte_data(LM3644_i2c_client, reg, data);
mutex_unlock(&g_strobeLock);

if (ret < 0)
PK_ERR("failed writting at 0x%02x\n", reg);
return ret;
}

最后再贴出来一个真正操作芯片开关闪光灯的代码

int flashEnable_LM3644_2(void)
{
int err;
int enable_value = 0;

PK_DBG(" flashDisable_LM3644_2 S line=%d\n",__LINE__);

......

enable_value = readReg(0x01);
PK_DBG(" LED1&2_enable_value S =0x%x\n",enable_value);

if((LED1CloseFlag == 1) && (LED2CloseFlag == 1))
{
err = writeReg(0x01, (enable_value & 0xF0));
}
else if(LED1CloseFlag == 1)
{
if(isMovieMode[m_duty2] == 1)
err = writeReg(0x01, (enable_value & 0xF0) | 0x09);
else
err = writeReg(0x01, (enable_value & 0xF0) | 0x0d);
}
else if(LED2CloseFlag == 1)
{
if(isMovieMode[m_duty1] == 1)
err = writeReg(0x01, (enable_value & 0xF0) | 0x0a);
else
err = writeReg(0x01, (enable_value & 0xF0) | 0x0e);
}
else
{
if((isMovieMode[m_duty1] == 1) && (isMovieMode[m_duty2] == 1))
err = writeReg(0x01, (enable_value & 0xF0) | 0x0B);
else
err = writeReg(0x01, (enable_value & 0xF0) | 0x0F);
}

enable_value = readReg(0x01);
......
}

对应的芯片0x01寄存器的定义如下图
MTK平台闪光灯驱动分析
所以可以看出上面代码中的 writeReg(0x01, (enable_value & 0xF0) | 0x0F);类似的代码就是真正控制闪光灯开关的代码。

  第一次下博客,感觉比较简陋,而且也没什么技术含量,但是是自己的一个总结,希望自己能继续坚持下去,加油。