基于RV1126移植Sony imx585

查看RV1126SDK内的sensor驱动,发现可以直接用的型号并不多,在实际项目实现的过程中,还是需要调试新的sensor,因此记录一下调试过程,之后的Sony系列都可以套用这个过程来实现。

首先确保硬件供电正常,另外i2c地址、时钟、上拉电阻等都要检查好,确保硬件没问题

在SDK/kernel/driver/media/i2c/ 下有相机sensor的驱动,都是在Linux框架下的,所以可以随便找一个索尼的驱动,在此基础上改。

在此之前要先修改设备树,设备树里主要定义了sensor的i2c地址,时钟频率,供电,复位的gpio、相机模组名、port节点的链接关系、date-lanes等

这里要按照自己原理图的定义去修改,另外ucam0_out节点链接到了mipi_in_ucam0,又链接到了rkcif->isp,因为对camera流的链接要求各不相同,此处不详细写出

&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    imx585: imx585@37 {
        compatible = "sony,imx585";
        reg = <0x37>;
        clocks = <&cru 103>;
        clock-names = "xvclk";
        power-domains = <&power 9>;
        pinctrl-names = "rockchip,camera_default";
        pinctrl-0 = <&mipicsi_clk0>;
        avdd-supply = <&vcc3v3_sys>;
        dovdd-supply = <&vcc_1v8>;
        dvdd-supply = <&vcc_dvdd>;
        reset-gpios = <&gpio1 28 1>;
        rockchip,camera-module-index = <1>;
        rockchip,camera-module-facing = "front";
        rockchip,camera-module-name = "YT10092";
        rockchip,camera-module-lens-name = "IR0147-60IRC-8M-F20";
        ir-cut = <&cam_ircut0>;
        port {
            ucam_out0: endpoint {
                remote-endpoint = <&mipi_in_ucam0>;
                data-lanes = <1 2 3 4>;
            };
        };
    };

};

在~/workspace/RV1126SDK/kernel/drivers/media/i2c下编辑Makefile,添加编译输出

obj-$(CONFIG_VIDEO_IMX585) += imx585.o

在~/workspace/RV1126SDK/kernel/drivers/media/i2c下编辑Kconfig,添加编译配置

config VIDEO_IMX585
        tristate "Sony IMX585 sensor support"
        depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
        depends on MEDIA_CAMERA_SUPPORT
        help
          This is a Video4Linux2 sensor driver for the Sony
          IMX585 camera.

          To compile this driver as a module, choose M here: the
          module will be called imx585.

编辑~/workspace/RV1126SDK/kernel/arch/arm/configs/rv1126_defconfig,打开IMX585编译选项

CONFIG_VIDEO_IMX585=y

以上配置完成后,连接SoC与sensor,i2c应该是通的,但是用i2c工具测试,设备并没有挂载上,重复多次检查硬件确保硬件正常

1、分析驱动源码

首先进入probe函数

通过of_property_read_u32获取设备树节点信息,以下代码为获取设备树内的相机模组名、hdr参数等

ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
               &imx585->module_index);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
                   &imx585->module_facing);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
                   &imx585->module_name);
ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
                   &imx585->len_name);
ret = of_property_read_u32(node, OF_CAMERA_HDR_MODE, &hdr_mode);

获取imx585的工作模式,寄存器值可以通过Sony的手册获取,保密问题此处省略

imx585->client = client;
imx585->cfg_num = ARRAY_SIZE(supported_modes);
for (i = 0; i < imx585->cfg_num; i++) {
    if (hdr_mode == supported_modes[i].hdr_mode) {
        imx585->cur_mode = &supported_modes[i];
        break;
    }
}
    
在非HDR模式时,默认匹配为supported_modes[0]
{
    .bus_fmt = MEDIA_BUS_FMT_SGBRG10_1X10,
    .width = 3864,
    .height = 2192,
    .max_fps = {
        .numerator = 10000,
        .denominator = 300000,
    },
    .exp_def = 0x08ca - 0x08,
    .hts_def = 0x044c * IMX585_4LANES * 2,
    .vts_def = 0x08ca,
    .global_reg_list = imx585_global_10bit_3864x2192_regs,
    .reg_list = imx585_linear_10bit_3864x2192_891M_regs,
    .hdr_mode = NO_HDR,
    .mipi_freq_idx = 1,
    .bpp = 10,
}

因此需要设置imx585_global_10bit_3864x2192_regs和imx585_linear_10bit_3864x2192_891M_regs这两个寄存器
寄存器值可以通过Sony寄存器手册获取,参照寄存器表,以设置不同的工作模式

static __maybe_unused const struct regval imx585_global_10bit_3864x2192_regs[] = {
    ***************
    {REG_NULL, 0x00},
};

static __maybe_unused const struct regval imx585_linear_10bit_3864x2192_891M_regs[] = {
    **************
    {REG_NULL, 0x00},
};

获取设备树内reset、power、pinctrl信息

    imx585->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_ASIS);
    if (IS_ERR(imx585->reset_gpio))
        dev_warn(dev, "Failed to get reset-gpios\n");
    imx585->power_gpio = devm_gpiod_get(dev, "power", GPIOD_ASIS);
    if (IS_ERR(imx585->power_gpio))
        dev_warn(dev, "Failed to get power-gpios\n");
    imx585->pinctrl = devm_pinctrl_get(dev);

获取power信息

ret = imx585_configure_regulators(imx585);
if (ret) {
    dev_err(dev, "Failed to get power regulators\n");
    return ret;
}

查看imx585_configure_regulators函数最后可以定位到下面的结构体的power信息
static const char * const imx585_supply_names[] = {
    "dvdd",     /* Digital core power */
    "dovdd",    /* Digital I/O power */
    "avdd",     /* Analog power */
};

imx585_initialize_controls用于创建v4l2的控件,进入可以看到新建了exposure_max,、vblank_def、pixel_rate、h_blank等内容

__imx585_power_on用于使能设备,进入函数可以看到在里面设置了power_gpio和reset引脚的输出方向

imx585_check_sensor_id用于检测sensor,在此函数内进行了复位后寄存器值的检查,用于确认连接的sensor是否正确

    sd = &imx585->subdev;
    v4l2_i2c_subdev_init(sd, client, &imx585_subdev_ops);
    ret = imx585_initialize_controls(imx585);
    if (ret)
        goto err_destroy_mutex;

    ret = __imx585_power_on(imx585);
    if (ret)
        goto err_free_handler;

    ret = imx585_check_sensor_id(imx585, client);
    if (ret)
        goto err_power_off;

通过内核log,最后将问题确认到了imx585_check_sensor_id函数,在imx415和imx715中,检验的寄存器和寄存器值都是一样的,

改成imx585后,因为其与imx415、imx715的寄存器不一样,因此在check_sensor的时候就会报错,导致sensor不能启动,i2c就不通。

通过imx585_read_reg获取IMX585_REG_CHIP_ID寄存器的值,id即为读取的IMX585_REG_CHIP_ID寄存器内的值,使其与CHIP_ID比较,如果正确,则确定sensor是imx585,因此在选择此寄存器时,要选择寄存器表中最能体现为imx585的寄存器。

static int imx585_check_sensor_id(struct imx585 *imx585,
                  struct i2c_client *client)
{
    struct device *dev = &imx585->client->dev;
    u32 id = 0;
    int ret;

    if (imx585->is_thunderboot) {
        dev_info(dev, "Enable thunderboot mode, skip sensor id check\n");
        return 0;
    }

    ret = imx585_read_reg(client, IMX585_REG_CHIP_ID,
                  IMX585_REG_VALUE_08BIT, &id);
    if (id != CHIP_ID) {
        dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
        return -ENODEV;
    }

    dev_info(dev, "Detected imx585 id %06x\n", CHIP_ID);

    return 0;
}

i2c读寄存器时,要创建两个msg,第一个msg用于存要读的寄存器信息,第二个msg用于存获取的寄存器信息

cpu_to_be16将i2c地址由小端存储改为大端存储。

/* Read registers up to 4 at a time */
static int imx585_read_reg(struct i2c_client *client, u16 reg, unsigned int len,
               u32 *val)
{
    struct i2c_msg msgs[2];
    u8 *data_be_p;
    __be32 data_be = 0;
    __be16 reg_addr_be = cpu_to_be16(reg);
    int ret;

    if (len > 4 || !len)
        return -EINVAL;

    data_be_p = (u8 *)&data_be;
    /* Write register address */
    msgs[0].addr = client->addr;
    msgs[0].flags = 0;
    msgs[0].len = 2;
    msgs[0].buf = (u8 *)&reg_addr_be;

    /* Read data from register */
    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = len;
    msgs[1].buf = &data_be_p[4 - len];

    ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
    if (ret != ARRAY_SIZE(msgs))
        return -EIO;

    *val = be32_to_cpu(data_be);

    return 0;
}

设置的校验寄存器及复位后的初始化值

/* TODO: Get the real chip id from reg */
#define CHIP_ID             0x32
#define IMX585_REG_CHIP_ID      0x30DC

修改完后重新编译驱动烧录测试,使用i2c工具检测,可以看到设备0x37处为UU,设备成功挂载

修改宏定义部分寄存器值

#define IMX585_LF_GAIN_REG_H        0x306D
#define IMX585_LF_GAIN_REG_L        0x306C


#define IMX585_VTS_REG_L        0x3028
#define IMX585_VTS_REG_M        0x3029
#define IMX585_VTS_REG_H        0x302A

在驱动内修改INCKData Rate

#define IMX585_XVCLK_FREQ_74M       74250000

usleep_range(10 * 1000, 20 * 1000);
ret = clk_set_rate(imx585->xvclk, IMX585_XVCLK_FREQ_74M);


static const s64 link_freq_items[] = {
    MIPI_FREQ_297M,
    MIPI_FREQ_446M,
    MIPI_FREQ_743M,
    MIPI_FREQ_891M,
};

接镜头测试,发现画面整体偏红,但黑色区域是正常的,因此考虑应该是Bayer格式的RGB排列问题,改为SRGGB10--> .bus_fmt = MEDIA_BUS_FMT_SGBRG10_1X10,画面正常。

2、总结

因此移植一个新sensor可以总结为:

1)确认硬件连接,确认io口及供电正确

2)修改设备树,确认sensor的i2c地址,时钟频率,供电,复位的gpio、相机模组名、port节点的链接关系、date-lanes等

3)修改驱动内初始化寄存器值,check_id,INCK、Data Rate等,要与实际参数对应上

4)检查sensor的Bayer排列格式,不同的sensor排列方式不一样,此处设置不对画面会出现异常