前言概述

因以下原因,导致不得不通过代码分析来学习如何在该平台下进行摄像头驱动移植

  1. 香橙派开发商(迅龙软件)仅提供能跑起来的源代码、固件,以及简单的编译文档,不提供其它技术支持
  2. baidu、google 所找到的修改方法与所拿到的源代码完全不符,因此无借鉴价值
  3. 借助搜索引擎以及一些论坛也没有找到可以参考的文档资料

笔记主要以了解移植驱动所需要做的工作为目标导向,通过分析相关源码的方式一步一步地抽丝剥茧而形成的记录。

记录目的,一来可以借助笔记的方式来总结归纳以备后用,二来也可以分享给需要的人少走弯路。

需求说明

在 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

  1. 外设驱动源代码(触摸屏、摄像头、光感、Gsensor、GPS...),下方为各个型号摄像头部分的头文件
  2. 可以确认 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

  1. 主要实现根据宏定义来将对应驱动包含并加入链表(意味着可以支持多个摄像头),再触发适配。
  2. 可以发现两个关键的文件: gc0309_config.h(系统原使用的gc0309驱动)、gc2145_config.h(我们需要用的gc2145驱动)
  3. 有两个值得关注的宏定义: RDA_CUSTOMER_DRV_CSB、GC0309_B、GC2145_B
  4. 一个非常关键的函数接口: rda_sensor_adapt(&rda_sensor_cb,&rda_sensor_b_list, 0);
  5. 一个非常关键的回调变量: rda_sensor_cb,由 rda_sensor_adapt() 赋予,该接口保存了一系列的控制接口
  6. [总结] 说明新增的摄像头驱动以 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

  1. 这个mk文件,主要用于配置指定使用哪些硬件设备驱动(如触摸屏、摄像头、光感、Gsensor、GPS...)
  2. 修改配置文件,其实完成这一步修改,就已经完成了摄像头的驱动切换,有现成的还是比较简单
  3. [总结] 要新增摄像头的驱动文件,也需要修改这个配置项,原因见下文。
  4. [总结] 要调整或新增其它的外设,也都可以修改此文件
原来配置项: RDA_CUSTOMER_DRV_CSB := GC0309
修改配置项: RDA_CUSTOMER_DRV_CSB := GC2145

4. 通过搜索相关的宏定义如 RDA_CUSTOMER_DRV_CSB 找到 Makefile: device\rda\driver\camera\Makefile

  1. 这里是根据上述配置项按照 xxx_B 的格式定义 C 代码可以识别的宏定义:GC2145_B
  2. 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

  1. 通过分析代码,主要是实现和填充了static struct sensor_ops 回调操作接口
  2. 查看其它的 xxx_config.h 文件,可以发现所有的摄像头驱动都需要提供该回调接口的实现
  3. 说明展讯的平台把跟摄像头硬件相关的差异抽离出来了,对于新增驱动来说方便不少。(和高通类似,全志的要稍微繁琐一点)
  4. 调用上电时序和读写寄存器的接口,sensor_power_down()\sensor_reset()\sensor_clock()\sensor_read()\sensor_write()
  5. 这些接口调用的是 rda_cam_sensor.c 里面的 rda_sensor_cb 所指向的回调函数指针
  6. [总结] 因此需要新增新的摄像头驱动,就需要实现 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

  1. 是一个以 I2C驱动程序的基础上注册为 V4L2 设备驱动,通过搜索名字很容易找到平台设备实现文件:Devices.c
  2. 步骤2中的rda_cam_sensor.c 里面的 rda_sensor_cb 所指向的回调函数指针在这里被赋予
  3. 借此提供了具体的 pwdn\reset\mclk\I2C 读写操作,同时也触发具体摄像头驱动的上电时序和读取芯片ID
  4. [总结] pwdn\reset 有专用的引脚,如果需要改为普通GPIO 需定义 GPIO_CAM_PWDN0\GPIO_CAM_PWDN1\GPIO_CAM_RESET
  5. [总结] 如果需要改为通过 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

  1. 平台设备驱动,包含很多其他的芯片控制器资源和配置信息以及 LDO 选择,如下面的 Camera 控制器资源和时钟极性配置等
  2. 注意该平台设备触发了三个驱动:rda_camera_8810.c、Soc_camera.c、rda_sensor_base.c
  3. 指定的 LDO 为 “v_cam”
  4. [总结] 如果需要调整控制器输出的时钟极性来适应新摄像头,可以通过修改 rda_camera_data 实现
  5. [总结] 如果不使用主控专用的 ldo ,也可以通过修改 rda_sensor_regulator 指定新的 ldo
  6. [总结] 展讯平台将Camera驱动分为三类:控制器驱动、V4L2驱动、具体Camera外设驱动
  7. [总结] 如果需要调整其它主控内部的控制器,如 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

  1. 如上述步骤所描述,是一个主控的 Camera 控制器驱动
  2. 当与平台设备匹配后触发 rda_camera_probe() 最后调用 soc_camera_host_register(),来创建一个 /dev/videoX 节点
  3. [总结] 主控有多少个控制器,意味着就会有多少个 /dev/videoX 节点
  4. [扩展] 如果想通过其他方式新增一个 /dev/vdieoX 应该可以参考此驱动(如SDIO接口的摄像头)

9. 通过步骤七发现关联的驱动之二:soc_camera.c

这里面主要是 V4L2 设备驱动部分的代码,就不继续分析下去了。

10. 通过步骤七确定供电是由 LDO_CAM ,搜索得到:arch/arm/mach-rda/regulator.c

  1. 这个驱动实现了所有需要控制 LDO 的具体操作接口,同时注册了以名字命名的 LDO
  2. 创建的 /sys/class/regulator/regulator.X 与注册顺序有关,可以通过查看里面的 name 来与 ldo 对应
  3. 需要参考 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

  1. 该驱动基本涵盖了所有外设需要使用的 ldo 默认配置
  2. 该驱动指定了 ldo 名称与实际芯片内部的 ldo 的对应关系(rda_reg_config.pm_id)
  3. [总结] 如果需要调整摄像头的供电电压可以通过这里调整 cam_default.def_val 即可
  4. [总结] 其它外设使用了芯片内部的 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, // 指定默认电源(重点)
};

成像效果

效果优化部分还需要继续调整,这个已经不在本文范围内。

【分析笔记】展讯 RDA8810PL 平台 Camera 驱动分析和移植(Android 4.4 )的更多相关文章

  1. 展讯sprd_battery.c 充电驱动

    sprd_battery.c 是充电驱动,这个是充电功能的核心内容,电量显示策略.温度检测策略.充电保护机制等功能在这里实现,功能实现与硬件细节剥离,调用通用接口实现逻辑控制: 1 sprdbat_p ...

  2. Qcom平台RTC驱动分析

    相关文件list: pm8998.dtsi ---RTC dts配置 qpnp-rtc.c ---qcom RTC驱动 class.c ---RTC相关class interface.c ---相关R ...

  3. 高通 android平台LCD驱动分析

    目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(), ...

  4. apigw鉴权分析(1-2)腾讯开放平台 - 鉴权分析

    一.访问入口 http://wiki.open.qq.com/wiki/%E8%85%BE%E8%AE%AF%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0%E7%AC%AC% ...

  5. 高通Camera驱动分析【转】

    本文转载自:http://blog.csdn.net/liwei16611/article/details/53955711 1.Sensor slave配置 结构体msm_camera_sensor ...

  6. linux驱动基础系列--Linux下Spi接口Wifi驱动分析

    前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...

  7. Linux的LCD驱动分析及移植

    测试平台 宿主机平台:Ubuntu 12.04.4 LTS 目标机:Easy-ARM IMX283 目标机内核:Linux 2.6.35.3 LCD驱动分析 LCD屏的驱动总体上分成两块,一块是GUI ...

  8. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

  9. camera驱动框架分析(上)【转】

    转自:https://www.cnblogs.com/rongpmcu/p/7662738.html 前言 camera驱动框架涉及到的知识点比较多,特别是camera本身的接口就有很多,有些是直接连 ...

  10. camera驱动框架分析(上)

    前言 camera驱动框架涉及到的知识点比较多,特别是camera本身的接口就有很多,有些是直接连接到soc的camif口上的,有些是通过usb接口导出的,如usb camera.我这里主要讨论前者, ...

随机推荐

  1. Django更换数据库和迁移数据方案

    前言 双十一光顾着买东西都没怎么写文章,现在笔记里还有十几篇半成品文章没写完- 今天来分享一下 Django 项目切换数据库和迁移数据的方案,网络上找到的文章方法不一,且使用中容易遇到各类报错,本文根 ...

  2. 系统启动后bond配置不生效问题定位

    背景描述 为了适配新功能,裸金属服务的磁盘镜像中做了如下修改: dracut添加network, iscsi模块 grub添加rd.iscsi.firmware=1参数 删除网卡配置文件/etc/sy ...

  3. docker中php xdebug调试开发

    docker-compose环境来自:https://github.com/zhaojunlik...原文:http://blog.oeynet.com/post/9... 说明 在开发中,断点调试是 ...

  4. 【SQL进阶】【表默认值、自增、修改表列名、列顺序】Day02:表与索引操作

    一.表的创建.修改与删除 1.创建一张新表 [设置日期默认值.设置id自增] [注意有备注添加备注COMMENT] CREATE TABLE user_info_vip( id int(11) pri ...

  5. python爬取网易云音乐评论及相关信息

    python爬取网易云音乐评论及相关信息 urllib requests 正则表达式 爬取网易云音乐评论及相关信息 urllib了解 参考链接: https://www.liaoxuefeng.com ...

  6. python中函数教程

    函数的基本概念 1.什么是函数? 函数相当于一种工具,就是把一串代码装到一起,我们下次需要用的这个功能的时候可以直接使用 函数相当于是工具(具有一定功能) 不用函数 修理工需要修理器件要用锤子 原地打 ...

  7. 常用BOM操作 DOM操作 事件 jQuery类库

    目录 BOM操作 常用BOM操作 三种弹出框 alert confirm prompt 定时任务 setTimeout 循环定时 setInterval DOM操作 查找标签 直接查找 间接查找 操作 ...

  8. Chaos 测试下的若干 NebulaGraph Raft 问题分析

    Raft 是一种广泛使用的分布式共识算法.NebulaGraph 底层采用 Raft 算法实现 metad 和 storaged 的分布式功能.Raft 算法使 NebulaGraph 中的 meta ...

  9. JavaScript:操作符:空值合并运算符(??)

    这是一个新增的运算符,它的功能是: 对于表达式1 ?? 表达式2,如果表达式1的结果是null或者undefined时,返回表达式b的结果:否则返回表达式a的结果: 它与赋值运算符结合使用,即??=, ...

  10. vue3 递归组件 树形组件

    递归组件 第一种方式,直接自己调用自己 Tree.vue <template> <div class="tree"> <div v-for=" ...