【分析笔记】展讯 RDA8810PL 平台 Camera 驱动分析和移植(Android 4.4 )
前言概述
因以下原因,导致不得不通过代码分析来学习如何在该平台下进行摄像头驱动移植
- 香橙派开发商(迅龙软件)仅提供能跑起来的源代码、固件,以及简单的编译文档,不提供其它技术支持
- baidu、google 所找到的修改方法与所拿到的源代码完全不符,因此无借鉴价值
- 借助搜索引擎以及一些论坛也没有找到可以参考的文档资料
笔记主要以了解移植驱动所需要做的工作为目标导向,通过分析相关源码的方式一步一步地抽丝剥茧而形成的记录。
记录目的,一来可以借助笔记的方式来总结归纳以备后用,二来也可以分享给需要的人少走弯路。
需求说明
在 Orange Pi 2G-IOT 平台(主控为RDA8810)移植 GC2145 摄像头驱动,使之可用。
移植条件
目标的驱动 gc2145,要能驱动摄像头需要具备以下几个条件:
1. 供电电压
DVDD:1.8V (Camera Sensor 的内核电压)
AVDD:2.8V
DOVDD:2.8V (要与主控引脚 IO 电压一致)
2. 上电掉电时序(ldo、gpio、mclk)
供电、down、reset、mclock 的开启和关闭顺序
3. gc2145 设备驱动
1. 访问芯片获取 chipid (这一步至关重要)
2. 初始化代码、分辨率切换等等寄存器配置
4. 注册 V4L2 接口
根据 v4l2 接口提供对应的配置实现,如修改分辨率等等。
这部分不一定在具体的摄像头驱动中实现,如高通平台和这颗展讯平台,会有专门的驱动实现。
因此会发现没有摄像头也会有 /dev/videoX 节点存在。
源码定位
通过内核日志来确认原使用的摄像头驱动
// 摄像头: gc0309
[ 8.502624] rda_sensor_adapt: camera id=0
[ 8.518554] rda_sensor_adapt: try gc0309, chip_id=0xa0
[ 8.722412] rda-i2c rda-i2c.0: hal_i2c_raw_send_byte, timeout2
[ 8.723022] rda-i2c rda-i2c.0: rda_i2c_send_bytes, send addr fail, addr = 0x21
[ 8.932434] rda-i2c rda-i2c.0: hal_i2c_raw_send_byte, timeout2
[ 8.932983] rda-i2c rda-i2c.0: rda_i2c_send_bytes, send addr fail, addr = 0x21
[ 9.142456] rda-i2c rda-i2c.0: hal_i2c_raw_send_byte, timeout2
[ 9.143005] rda-i2c rda-i2c.0: rda_i2c_send_bytes, send addr fail, addr = 0x21
[ 9.143859] sensor_i2c_read: i2c read error, reg: 0x0
[ 9.144592] rda_sensor_adapt: failed!
通过外设名字搜索来确认相关源码的位置
~/work/git/OrangePi_2G-IOT-Andorid_SDCard$ find . -name "*gc0309*"
./runyee/project/riot01/riot01_NollecA9V2V8810P_ry_smt/code/device/rda/driver/camera/inc/gc0309_config_front.h
./runyee/project/riot01/riot01_NollecA9V2V8810P_ry_smt/code/device/rda/driver/camera/inc/gc0309_config_back.h
./out/target/product/etau-NollecA9V2V8810P/obj/device/rda/driver/camera/inc/gc0309_config.h
./out/target/product/etau-NollecA9V2V8810P/obj/device/rda/driver/camera/inc/gc0309_config_front.h
./out/target/product/etau-NollecA9V2V8810P/obj/device/rda/driver/camera/inc/gc0309_config_back.h
./device/rda/driver/camera/inc/gc0309_config.h
./device/rda/driver/camera/inc/gc0309_config_front.h
./device/rda/driver/camera/inc/gc0309_config_back.h
源码分析
1. 通过日志搜索得知外设驱动源码路径:device\rda\driver
- 外设驱动源代码(触摸屏、摄像头、光感、Gsensor、GPS...),下方为各个型号摄像头部分的头文件
- 可以确认 rda_cam_sensor.c 是主源文件,其它的 xxx_xxx_config.h 为具体摄像头的配置驱动文件
device\rda\driver\camera\rda_cam_sensor.c
device\rda\driver\camera\inc\rda2201_config.h
device\rda\driver\camera\inc\sp0718_config.h
device\rda\driver\camera\inc\gc0313_csi_config.h
device\rda\driver\camera\inc\sp0a20_csi_config.h
device\rda\driver\camera\inc\gc0312_config.h
device\rda\driver\camera\inc\sp0a09_csi_config.h
device\rda\driver\camera\inc\gc2035_config.h
device\rda\driver\camera\inc\gc0409_csi_config.h
device\rda\driver\camera\inc\sp2508_csi_config.h
device\rda\driver\camera\inc\rda2201_csi_config.h
device\rda\driver\camera\inc\gc0308_config.h
device\rda\driver\camera\inc\gc0309_config.h
device\rda\driver\camera\inc\gc2355_csi_config.h
device\rda\driver\camera\inc\gc0309_config_front.h
device\rda\driver\camera\inc\gc2155_config.h
device\rda\driver\camera\inc\gc0328_config.h
device\rda\driver\camera\inc\gc0329_config.h
device\rda\driver\camera\inc\gc0310_csi_config.h
device\rda\driver\camera\inc\gc2145_config.h
device\rda\driver\camera\inc\gc2145_csi_config.h
device\rda\driver\camera\inc\gc2035_csi_config.h
device\rda\driver\camera\inc\gc0328c_config.h
device\rda\driver\camera\inc\gc0309_config_back.h
2. 通过查看驱动源文件确定配置项:device\rda\driver\camera\rda_cam_sensor.c
- 主要实现根据宏定义来将对应驱动包含并加入链表(意味着可以支持多个摄像头),再触发适配。
- 可以发现两个关键的文件: gc0309_config.h(系统原使用的gc0309驱动)、gc2145_config.h(我们需要用的gc2145驱动)
- 有两个值得关注的宏定义: RDA_CUSTOMER_DRV_CSB、GC0309_B、GC2145_B
- 一个非常关键的函数接口: rda_sensor_adapt(&rda_sensor_cb,&rda_sensor_b_list, 0);
- 一个非常关键的回调变量: rda_sensor_cb,由 rda_sensor_adapt() 赋予,该接口保存了一系列的控制接口
- [总结] 说明新增的摄像头驱动以 xxx_config.h 头文件的方式加入,并在此增加相对应的宏定义
// rda_sensor.h ---------------------------------------------------------
struct sensor_callback_ops {
void(*cb_pdn) (bool pdn, bool acth, int id);
void(*cb_rst) (bool rst, bool acth);
void(*cb_clk) (bool out, int mclk);
void(*cb_i2c_r) (const u16 addr, u8 *data, u8 bits);
void(*cb_i2c_w) (const u16 addr, const u8 data, u8 bits);
void(*cb_isp_reg_write) (const u32 addr_offset, const u8 data);
void(*cb_isp_set_exp) (int exp);
void(*cb_isp_set_awb) (int awb);
void(*cb_isp_set_af)(int af);
};
// rda_sensor.h ---------------------------------------------------------
static struct sensor_callback_ops rda_sensor_cb; // 注意这个变量,保存了一系列控制接口
void sensor_power_down(bool pdn, bool acth, int id)
{
rda_sensor_cb.cb_pdn(pdn, acth, id);
}
void sensor_reset(bool rst, bool acth)
{
rda_sensor_cb.cb_rst(rst, acth);
}
void sensor_clock(bool out, int mclk)
{
rda_sensor_cb.cb_clk(out, mclk);
}
void sensor_read(const u16 addr, u8 *data, u8 bits)
{
rda_sensor_cb.cb_i2c_r(addr, data, bits);
}
void sensor_write(const u16 addr, const u8 data, u8 bits)
{
rda_sensor_cb.cb_i2c_w(addr, data, bits);
}
#if defined(GC0309_B) || defined(GC0309_F)
#include "gc0309_config.h"
#endif
#if defined(GC2145_B) || defined(GC2145_F) // 目标
#include "gc2145_config.h" // GC2145 的驱动实现
#endif
#ifdef RDA_CUSTOMER_DRV_CSB
static LIST_HEAD(rda_sensor_b_list);
static void init_back_sensor_list(struct list_head *head)
{
......
#ifdef GC0309_B
INIT_LIST_HEAD(&gc0309_dev.list);
list_add_tail(&gc0309_dev.list, head); // 系统原本自带的
#endif
......
#ifdef GC2145_B // 目标
INIT_LIST_HEAD(&gc2145_dev.list);
list_add_tail(&gc2145_dev.list, head); // 将该驱动加入链表
#endif
......
}
#endif
static int __init cam_sensor_subsys_init(void)
{
......
#ifdef RDA_CUSTOMER_DRV_CSB
init_back_sensor_list(&rda_sensor_b_list);
// 开始进行适配, rda_sensor_cb 这个参数至关重要,gc2145 驱动最终会通过该参数操作上电时序, 这里是重点
ret = rda_sensor_adapt(&rda_sensor_cb,&rda_sensor_b_list, 0);
if (ret < 0) {
rda_dbg_camera("%s: back sensor adapt failed\n", __func__);
} else {
init_done = 0;
}
#endif
......
return init_done;
}
subsys_initcall(cam_sensor_subsys_init);
MODULE_DESCRIPTION("The sensor driver for RDA Linux");
MODULE_LICENSE("GPL");
3. 通过搜索相关的宏定义如 RDA_CUSTOMER_DRV_CSB 找到: device\rda\etau\NollecA9V2V8810P\customer.mk
- 这个mk文件,主要用于配置指定使用哪些硬件设备驱动(如触摸屏、摄像头、光感、Gsensor、GPS...)
- 修改配置文件,其实完成这一步修改,就已经完成了摄像头的驱动切换,有现成的还是比较简单
- [总结] 要新增摄像头的驱动文件,也需要修改这个配置项,原因见下文。
- [总结] 要调整或新增其它的外设,也都可以修改此文件
原来配置项: RDA_CUSTOMER_DRV_CSB := GC0309
修改配置项: RDA_CUSTOMER_DRV_CSB := GC2145
4. 通过搜索相关的宏定义如 RDA_CUSTOMER_DRV_CSB 找到 Makefile: device\rda\driver\camera\Makefile
- 这里是根据上述配置项按照 xxx_B 的格式定义 C 代码可以识别的宏定义:GC2145_B
- GC2145_B 即对应到了(步骤2)的 rda_cam_sensor.c 文件
...
ifneq ($(RDA_CUSTOMER_DRV_CSB),)
EXTRA_CFLAGS += -DRDA_CUSTOMER_DRV_CSB
EXTRA_CFLAGS += $(foreach n,$(RDA_CUSTOMER_DRV_CSB),-D$(n)_B) // 修改格式为 GC2145_B
endif
..
5. 结合驱动源文件(步骤2)找到确定对应具体的摄像头驱动文件:device\rda\driver\camera\inc\gc2145_config.h
- 通过分析代码,主要是实现和填充了static struct sensor_ops 回调操作接口
- 查看其它的 xxx_config.h 文件,可以发现所有的摄像头驱动都需要提供该回调接口的实现
- 说明展讯的平台把跟摄像头硬件相关的差异抽离出来了,对于新增驱动来说方便不少。(和高通类似,全志的要稍微繁琐一点)
- 调用上电时序和读写寄存器的接口,sensor_power_down()\sensor_reset()\sensor_clock()\sensor_read()\sensor_write()
- 这些接口调用的是 rda_cam_sensor.c 里面的 rda_sensor_cb 所指向的回调函数指针
- [总结] 因此需要新增新的摄像头驱动,就需要实现 static struct sensor_ops 里面的回调接口和配置
......
static struct sensor_info gc2145_info = {
.name = "gc2145",
.chip_id = 0x2145, // 指定了 Chipid 会与读取出来的做比较
.mclk = 26, // mclk 频率
.i2c_addr = 0x3C, // 指定了 I2C 地址
.exp_def = 0,
.awb_def = 1,
.rst_act_h = false, // reset 低复位
.pdn_act_h = true, // pwdn 高电平有效
.init = &gc2145_init, // 指向了一个数组,数组包含了各个寄存器的初始化数据
.win_cfg = &gc2145_win_cfg, // 指向了一个数组,包含了所支持的分辨率的各个寄存器的配置
.csi_cfg = &gc2145_csi_cfg
};
......
static struct sensor_ops gc2145_ops = {
.power = gc2145_power, // 上电时序
.get_chipid = gc2145_get_chipid, // 获取芯片ID
.get_lum = gc2145_get_lum,
.set_flip = gc2145_set_flip,
.set_exp = gc2145_set_exp,
.set_awb = gc2145_set_awb,
.start = NULL,
.stop = NULL
};
struct sensor_dev gc2145_dev = { // 重点
.info = &gc2145_info,
.ops = &gc2145_ops,
};
......
static u32 gc2145_get_chipid(void)
{
u16 chip_id = 0;
u8 tmp;
sensor_read(0xf0, &tmp, RESERVE); // 注意这点
chip_id = (tmp << 8) & 0xff00;
sensor_read(0xf1, &tmp, RESERVE); // 注意这点
chip_id |= (tmp & 0xff);
printk("%s chipid = 0x%4x\n",__func__,chip_id);
return chip_id;
}
static u32 gc2145_power(int id, int mclk, bool rst_h, bool pdn_h)
{
/* set state to power off */
sensor_power_down(true, pdn_h, 0); // 注意这点
mdelay(1);
sensor_power_down(true, pdn_h, 1); // 注意这点
mdelay(1);
sensor_reset(true, rst_h);
mdelay(1);
sensor_clock(false, mclk);
mdelay(5);
/* power on sequence */
sensor_clock(true, mclk);
mdelay(1);
sensor_power_down(false, pdn_h, id);
mdelay(1);
sensor_reset(false, rst_h);
mdelay(10);
return 0;
}
6. 搜索(步骤2)驱动调用的接口 rda_sensor_adapt() 找到:kernel\drivers\media\platform\rda\rda_sensor_base.c
- 是一个以 I2C驱动程序的基础上注册为 V4L2 设备驱动,通过搜索名字很容易找到平台设备实现文件:Devices.c
- 步骤2中的rda_cam_sensor.c 里面的 rda_sensor_cb 所指向的回调函数指针在这里被赋予
- 借此提供了具体的 pwdn\reset\mclk\I2C 读写操作,同时也触发具体摄像头驱动的上电时序和读取芯片ID
- [总结] pwdn\reset 有专用的引脚,如果需要改为普通GPIO 需定义 GPIO_CAM_PWDN0\GPIO_CAM_PWDN1\GPIO_CAM_RESET
- [总结] 如果需要改为通过 GPIO 来控制电源的话,可以关注 rda_sensor_power(&priv->subdev, 1); 的实现
/* Export Function for camera sensor module in vendor --------------*/
static int rda_sensor_power(struct v4l2_subdev *sd, int on);
int rda_sensor_adapt(struct sensor_callback_ops *callback, // 一个导出函数, 被 rda_cam_sensor.c 调用
struct list_head *sdl, u32 id)
{
struct rda_sensor_priv *priv;
struct sensor_dev *tmp = NULL;
struct i2c_client *client = rda_sensor_i2c_client[id];
bool adapted = false;
u32 cid;
u32 chipid1 = 0;
printk(KERN_ALERT "%s: camera id=%d\n", __func__, id);
if (!client || !sdl || list_empty(sdl)) {
printk(KERN_ERR "%s: failed i2c client %p sdl %p!\n",
__func__, client, sdl);
return -EINVAL;
}
// 提供了 I2C 读写以及对 pwdn、reset、mclk 的具体实现,由具体的摄像头驱动调用(gc2145_config.h)
callback->cb_pdn = rda_sensor_pdn; // Camera pwdn 引脚控制回调
callback->cb_rst = rda_sensor_rst; // Camera reset 引脚控制回调
callback->cb_clk = rda_sensor_clk; // mclk 引脚控制回调
callback->cb_i2c_r = SENSOR_READ(id); // I2C 读取回调
callback->cb_i2c_w = SENSOR_WRITE(id); // I2C 写入回调
mutex_lock(&rda_sensor_mutex);
priv = to_rda_sensor(client);
// 识别 Sensor ChipId
rda_sensor_power(&priv->subdev, 1); // 上电
// 遍历所有具体摄像头驱动的sensor_dev结构体,如 struct sensor_dev gc2145_dev 对应 tmp
list_for_each_entry(tmp, sdl, list) {
client->addr = tmp->info->i2c_addr; // 对应 gc2145_info->i2c_addr
cid = tmp->info->chip_id; // 对应 gc2145_info->chip_id
tmp->ops->power(id, // 开始调用具体摄像头的上电时序(里面控制的 pdwn\rst\clck 接口就上面提供的具体回调)
tmp->info->mclk, // 对应 static u32 gc2145_power(int id, int mclk, bool rst_h, bool pdn_h)
tmp->info->rst_act_h,
tmp->info->pdn_act_h);
printk(KERN_ALERT "%s: try %s, chip_id=0x%x\n",
__func__, tmp->info->name, cid);
if (cid == tmp->ops->get_chipid()) { // 对应 static u32 gc2145_get_chipid(void) 读取出来的 chipid 与 gc2145_info->chip_id 对比
rda_dbg_camera(KERN_ERR "%s: name=%s, success!\n",
__func__, tmp->info->name);
tmp->cb = callback; // 保存回调接口到 gc2145_dev 中便于后续后续使用
priv->cur_sdev = tmp;
priv->dev_id = id;
adapted = true; // 适配成功
break;
}
}
tmp = NULL;
rda_sensor_power(&priv->subdev, 0); // 掉电
mutex_unlock(&rda_sensor_mutex);
if (!adapted) {
printk(KERN_ERR "%s: failed!\n", __func__);
return -ENODEV;
}
return 0;
}
EXPORT_SYMBOL(rda_sensor_adapt);
// 控制 pwdn 的具体实现
static void rda_sensor_pdn(bool pdn, bool acth, int id)
{
if (id) {
#ifdef GPIO_CAM_PWDN1
...... // 如果有指定 GPIO 的话需要设定相应的宏
...... //gpio_request(GPIO_CAM_PWDN1, "camera pwdn1");
...... //gpio_direction_output(GPIO_CAM_PWDN1, x)
...... //gpio_free(GPIO_CAM_PWDN1)
#else
rcam_pdn(pdn, acth); // 默认使用主控专用的 GPIO
#endif
} else {
#ifdef GPIO_CAM_PWDN0
...... // 如果有指定 GPIO 的话需要设定相应的宏
...... //gpio_request(GPIO_CAM_PWDN0, "camera pwdn0");
...... //gpio_direction_output(GPIO_CAM_PWDN0, x)
...... //gpio_free(GPIO_CAM_PWDN0)
#else
rcam_pdn(pdn, acth);
#endif
}
}
// 控制 reset 的具体实现
static void rda_sensor_rst(bool rst, bool acth)
{
#ifdef GPIO_CAM_RESET
...... // 如果有指定 GPIO 的话需要设定相应的宏
...... //gpio_request(GPIO_CAM_RESET, "camera reset");
...... //gpio_direction_output(GPIO_CAM_RESET, x)
...... //gpio_free(GPIO_CAM_RESET)
#else
rcam_rst(rst, acth);
#endif
}
// 控制 mclk 的具体实现
static void rda_sensor_clk(bool out, int mclk)
{
rcam_clk(out, mclk);
}
......
static int rda_sensor_probe(struct i2c_client *client, const struct i2c_device_id *did)
{
......
v4l2_i2c_subdev_init(&priv->subdev, client, &rda_sensor_subdev_ops); // 注册了 V4L2 所需的回调
printk(KERN_ERR "%s: sd->grp_id %x\n", __func__, priv->subdev.grp_id);
v4l2_ctrl_handler_init(&priv->hdl, num);
......
ret = rda_sensor_video_probe(client); // 里面调用了 v4l2_ctrl_handler_setup();
......
return 0;
}
static const struct i2c_device_id rda_sensor_id[] = {
{ RDA_SENSOR_DRV_NAME, 0 }, // #define RDA_SENSOR_DRV_NAME "rda-sensor" 对应 Devices.c
{ RDA_SENSOR_DRV_NAME, 1 },
};
MODULE_DEVICE_TABLE(i2c, rda_sensor_id);
static struct i2c_driver rda_sensor_i2c_driver = {
.driver = {
.name = RDA_SENSOR_DRV_NAME, // #define RDA_SENSOR_DRV_NAME "rda-sensor" 对应 Devices.c
},
.probe = rda_sensor_probe,
.remove = rda_sensor_remove,
.id_table = rda_sensor_id,
};
module_i2c_driver(rda_sensor_i2c_driver);
7. 通过平台驱动的名称 "rda-sensor" 搜索平台设备代码,找到:arch\arm\mach-rda\devices.c
- 平台设备驱动,包含很多其他的芯片控制器资源和配置信息以及 LDO 选择,如下面的 Camera 控制器资源和时钟极性配置等
- 注意该平台设备触发了三个驱动:rda_camera_8810.c、Soc_camera.c、rda_sensor_base.c
- 指定的 LDO 为 “v_cam”
- [总结] 如果需要调整控制器输出的时钟极性来适应新摄像头,可以通过修改 rda_camera_data 实现
- [总结] 如果不使用主控专用的 ldo ,也可以通过修改 rda_sensor_regulator 指定新的 ldo
- [总结] 展讯平台将Camera驱动分为三类:控制器驱动、V4L2驱动、具体Camera外设驱动
- [总结] 如果需要调整其它主控内部的控制器,如 SPI ,也可以修改此文件实现
- 主控 Camera 控制器驱动: rda_camera_8810.c(CSI/SPI)
- V4L2 设备驱动: Soc_camera.c (注册 V4L2, 实现 V4L2 接口, 创建 /dev/vdieoX,承上启下)
- 具体的外设驱动:rda_sensor_base.c (具体的 Camera 外设驱动,如 GC2145\GC0309)
/*
* Camera Controller
*/
// 主控控制器的寄存器资源(这里只有一份,说明只有一个 Camera 控制器(CSI/SPI),查阅了 datasheet 也确认只有一个)
static struct resource rda_camera_resource[] = {
[0] = {
.start = RDA_CAMERA_PHYS,
.end = RDA_CAMERA_PHYS + RDA_CAMERA_SIZE - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = RDA_IRQ_CAMERA,
.end = RDA_IRQ_CAMERA,
.flags = IORESOURCE_IRQ,
},
};
// 主控控制器的时钟极性输出配置
static struct rda_camera_device_data rda_camera_data[] = {
{
.hsync_act_low = 0, // hsync 时钟极性配置
.vsync_act_low = 0, // vsync 时钟极性配置
.pclk_act_falling = 0, // pclk 时钟极性配置
},
};
static u64 rda_camera_dmamask = DMA_BIT_MASK(32);
// 主控控制器的平台设备资源
static struct platform_device rda_camera = {
.name = RDA_CAMERA_DRV_NAME, // "rda-camera" 对应 rda_camera_8810.c(由此可见,该驱动是主控控制器驱动实现)
.id = 0,
.resource = rda_camera_resource, // Camera 硬件控制器资源
.num_resources = ARRAY_SIZE(rda_camera_resource),
.dev = {
.dma_mask = &rda_camera_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = rda_camera_data, // 时钟极性配置
},
};
/*
* Camera Sensor
*/
static struct i2c_board_info i2c_dev_camera[] = {
{ I2C_BOARD_INFO(RDA_SENSOR_DRV_NAME, (0x78 >> 1)) }, // "rda-sensor" 对应 rda_sensor_base.c (由此可见该驱动是外设驱动实现)
{ I2C_BOARD_INFO(RDA_SENSOR_DRV_NAME, (0x62 >> 1)) },
};
static struct regulator_bulk_data rda_sensor_regulator = {
.supply = LDO_CAM, // #define LDO_CAM "v_cam", 指定 LDO 为 v_cam,重点
};
static struct soc_camera_desc sdesc_sensor[] = {
{
.subdev_desc = {
.flags = 0,
.drv_priv = NULL,
.regulators = &rda_sensor_regulator, // 指定 LDO
.num_regulators = 1,
.power = NULL,
.reset = NULL,
},
.host_desc = {
.bus_id = 0,
.i2c_adapter_id = _TGT_AP_I2C_BUS_ID_CAM,
.board_info = &i2c_dev_camera[0],
},
},
{
.subdev_desc = {
.flags = 0,
.drv_priv = NULL,
.regulators = &rda_sensor_regulator,
.num_regulators = 1,
.power = NULL,
.reset = NULL,
},
.host_desc = {
.bus_id = 0,
.i2c_adapter_id = _TGT_AP_I2C_BUS_ID_CAM,
.board_info = &i2c_dev_camera[1],
},
},
};
static struct platform_device rda_camera_sensor[] = {
{
.name = "soc-camera-pdrv", // 对应了 Soc_camera.c
.id = 0,
.dev = {
.platform_data = &sdesc_sensor[0],
},
},
{
.name = "soc-camera-pdrv",
.id = 1,
.dev = {
.platform_data = &sdesc_sensor[1],
},
},
};
static struct platform_device *devices[] __initdata = {
......
&rda_camera_sensor[0],
// &rda_camera_sensor[1],
&rda_camera,
&rda_gouda,
......
};
......
void __init rda_init_devices(void)
{
......
// 这里将上面的 devices 数组里面所有的设备资源都加入到平台中
platform_add_devices(devices, ARRAY_SIZE(devices));
}
8. 通过步骤七发现关联的驱动之一:kernel\drivers\media\platform\rda\rda_camera_8810.c
- 如上述步骤所描述,是一个主控的 Camera 控制器驱动
- 当与平台设备匹配后触发 rda_camera_probe() 最后调用 soc_camera_host_register(),来创建一个 /dev/videoX 节点
- [总结] 主控有多少个控制器,意味着就会有多少个 /dev/videoX 节点
- [扩展] 如果想通过其他方式新增一个 /dev/vdieoX 应该可以参考此驱动(如SDIO接口的摄像头)
9. 通过步骤七发现关联的驱动之二:soc_camera.c
这里面主要是 V4L2 设备驱动部分的代码,就不继续分析下去了。
10. 通过步骤七确定供电是由 LDO_CAM ,搜索得到:arch/arm/mach-rda/regulator.c
- 这个驱动实现了所有需要控制 LDO 的具体操作接口,同时注册了以名字命名的 LDO
- 创建的 /sys/class/regulator/regulator.X 与注册顺序有关,可以通过查看里面的 name 来与 ldo 对应
- 需要参考 regulator-devices.c 来阅读会容易理解些
- static int rda_regulator_probe(struct platform_device *pdev) [regulator.c]
- -> regulator_register(&rda_regs_desc[pdev->id],&config) [regulator.c]
- -> regulator_init(rdev->reg_data) [core.c] 这里也创建了相应的设备节点 /sys/class/regulator/regulator.X/*
- -> int rda_regulator_do_init(void *driver_data) [core.c] 当有初始化数据和初始化接口才会被调用-这被指定为初始化接口)
- -> rda_regulator_send_cmd(rda_reg, param) [regulator.c] 真正让默认配置生效的地
static struct regulator_ops rda_regulator_ops = {
.enable = rda_regulator_enable,
.disable = rda_regulator_disable,
.is_enabled = rda_regulator_is_enabled,
/* If we have used set_voltage_sel, we cann't use set_voltage. If so core of regulator will report warning. */
.set_voltage_sel = rda_regulator_set_voltage_sel,
/* If we have used get_voltage_sel, we cann't use get_voltage. If so core of regulator will report warning. */
.get_voltage_sel = rda_regulator_get_voltage_sel,
.list_voltage = rda_regulator_list_voltage,
.set_mode = rda_regulator_set_mode,
.get_mode = rda_regulator_get_mode,
.set_current_limit = rda_regulator_set_current_limit,
.get_current_limit = rda_regulator_get_current_limit,
};
/* standard regulator_desc define*/
#define RDA_REGULATOR_DESC(_id_, _name_, _type_) \
[(_id_)] = { \
.name = (_name_), \
.id = (_id_), \
.ops = &rda_regulator_ops, \
.type = _type_, \
.owner = THIS_MODULE, \
}
static struct regulator_desc rda_regs_desc[] = {
RDA_REGULATOR_DESC (rda_ldo_cam, LDO_CAM, REGULATOR_VOLTAGE),
......
};
static int rda_regulator_probe(struct platform_device *pdev)
{
struct rda_reg_config *rda_cfg = (struct rda_reg_config *)pdev->dev.platform_data;
struct regulator_config config = { };
struct regulator_init_data *init_data = rda_cfg->init_data;
struct rda_regulator *rda_reg = NULL;
int ret = 0;
......
rda_reg->config = rda_cfg;
rda_reg->private = (void *)pdev;
......
config.dev = &pdev->dev;
config.driver_data = rda_reg;
config.init_data = init_data;
rda_reg->rdev = regulator_register(&rda_regs_desc[pdev->id],&config);
......
platform_set_drvdata(pdev, rda_reg);
return 0;
}
static struct platform_driver rda_regulator_driver = {
.driver = {
.name = "regulator-rda", // 对应的平台设备资源驱动:regulator-devices.c
.owner = THIS_MODULE,
},
.probe = rda_regulator_probe,
.remove = rda_regulator_remove,
};
11. 通过步骤10 的平台驱动名称 “regulator-rda”,搜索得到:arch/arm/mach-rda/regulator-devices.c
- 该驱动基本涵盖了所有外设需要使用的 ldo 默认配置
- 该驱动指定了 ldo 名称与实际芯片内部的 ldo 的对应关系(rda_reg_config.pm_id)
- [总结] 如果需要调整摄像头的供电电压可以通过这里调整 cam_default.def_val 即可
- [总结] 其它外设使用了芯片内部的 ldo 都可以在这里进行调整
#define REGULATOR_DEV(_id_, _data_) \
{ \
.name = "regulator-rda", \
.id = (_id_), \
.dev = { \
.platform_data = &(_data_), \
}, \
}
static struct platform_device regulator_devices[] = {
REGULATOR_DEV(rda_ldo_cam, cam_config),
......
};
void __init regulator_add_devices(void)
{
int i = 0;
int j;
struct rda_reg_config *pconfig;
int *table;
for (i = 0 ; i < ARRAY_SIZE(regulator_devices); i++) {
......
platform_device_register(&(regulator_devices[i])); // 注册平台设备
}
......
}
static struct regulator_consumer_supply cam_consumers[] = {
CONSUMERS_POWER_CAM,
};
static struct rda_reg_def cam_default = {
//.def_val = 2800000, // 指定默认电源(重点) 需要修改为 1800000 ,GC2145 DVDD 要求1.8V
.def_val = 1800000,
};
static struct regulator_init_data cam_data = {
.constraints = {
.min_uV = 1800000, // 指定最电压范围
.max_uV = 2800000,
.valid_modes_mask = REGULATOR_MODE_NORMAL
| REGULATOR_MODE_STANDBY,
.valid_ops_mask = REGULATOR_CHANGE_STATUS
| REGULATOR_CHANGE_VOLTAGE
| REGULATOR_CHANGE_MODE,
},
.num_consumer_supplies = ARRAY_SIZE(cam_consumers),
.consumer_supplies = cam_consumers,
.regulator_init = rda_regulator_do_init, // 指定了初始化接口
.driver_data = &cam_default,
};
static int cam_vtable[] = {
1800000, 2800000 // 指定可选的电源表
};
static struct rda_reg_config cam_config = {
.init_data = &cam_data, // 指定初始化的配置
.pm_id = 1, // 指定芯片内的对应的 ldo (猜测, 没有文档明确)
.msys_cmd = SYS_PM_CMD_EN, // 命令为使能该 ldo
.table = (void *)cam_vtable, // 指定可选电压表
.tsize = ARRAY_SIZE(cam_vtable),
};
摄像头移植总结
以该平台推荐的接法(即使用主控平台专用的 GPIO 和 ldo)
- 修改所涉及到的源文件
device\rda\driver\camera\inc\rda_cam_sensor.c
device\rda\etau\NollecA9V2V8810P\customer.mk
kernel\arch\arm\mach-rda\devices.c
kernel\arch\arm\mach-rda\regulator-devices.c
- 先查看一下路径是否有现成的驱动,如果没有取其中接口一样的驱动参考编写(寄存器配置相关可找供应商提供)
device\rda\driver\camera\inc\xxx_config.h
- 如果没有现成的驱动则需要添加相应的 xxx_config.h 文件,并修改 rda_cam_sensor.c 文件
- 修改配置项,指定摄像头驱动
RDA_CUSTOMER_DRV_CSB := xxxxx
- 根据需要调整时钟极性,修改以下文件
kernel\arch\arm\mach-rda\devices.c
// 主控控制器的时钟极性输出配置
static struct rda_camera_device_data rda_camera_data[] = {
{
.hsync_act_low = 0, // hsync 时钟极性配置
.vsync_act_low = 0, // vsync 时钟极性配置
.pclk_act_falling = 0, // pclk 时钟极性配置
},
};
- 根据需要调整电压,修改以下文件
kernel\arch\arm\mach-rda\regulator-devices.c
static struct rda_reg_def cam_default = {
.def_val = 2800000, // 指定默认电源(重点)
};
成像效果
效果优化部分还需要继续调整,这个已经不在本文范围内。