VCM 设备注册(DTS)
RK VCM 驱动私有参数说明:
例子:
vm149c: vm149c@0c { // vcm 驱动配置,支持 AF 时需要有这个设置
compa
tible = "silicon touch,vm149c";
status = "okay";
reg = <0x0c>;
rockchip,vcm-start-current = <0>; // 马达的启动电流
rockchip,vcm-rated-current = <100>; // 马达的额定电流
rockchip,vcm-step-mode = <4>; // 马达驱动 ic 的电流输出模式
rockchip,camera-module-index = <0>; // 模组编号
rockchip,camera-module-facing = "back"; // 模组朝向,有"back"和"front"
};
gc8034: gc8034@37 {
......
lens-focus = <&vm149c>; // vcm 驱动设置,支持 AF 时需要有这个设置
......
};
VCM 驱动说明
驱动移植步骤
1.实现标准的 i2c 子设备驱动部分
根据 struct i2c_driver 描述,主要实现以下几部分:
struct driver.name
struct driver.pm
struct driver. of_match_table
probe 函数
remove 函数
probe 函数实现细节描述
VCM 设备资源获取,主要获取 DTS 资源
1、RK 私有资源定义, 命名方式如 rockchip,camera-module-xxx,主要是提供设备参数和 Camera 设备进行匹配。
2、VCM 参数定义,命名方式如 rockchip,vcm-xxx, 主要涉及硬件参数启动电流、额定电流、移动模式, 参数跟马达移动的范围和速度相关。
ret = of_property_read_u32(np, RKMODULE_CAMERA_MODULE_INDEX,
&vm149c_dev->module_index);
ret |= of_property_read_string(np, RKMODULE_CAMERA_MODULE_FACING,
&vm149c_dev->module_facing);
if (ret) {
dev_err(&client->dev,
"could not get module information!
");
return -EINVAL;
}
...
memset(facing, 0, sizeof(facing));
if (strcmp(vm149c_dev->module_facing, "back") == 0)
facing[0] = 'b';
else
facing[0] = 'f';
snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
vm149c_dev->module_index, facing,
VM149C_NAME, dev_name(sd->dev));
ret = v4l2_async_register_subdev(sd);
if (ret)
dev_err(&client->dev, "v4l2 async register subdev failed
");
VCM v4l2 设备以及 media 实体的初始化
v4l2 子设备: v4l2_i2c_subdev_init, RK VCM 驱动要求 subdev 拥有自己的设备节点供用户态 camera_engine 访问,通过该设备节点实现调焦控制;
v4l2_i2c_subdev_init(&vm149c_dev->sd, client, &vm149c_ops);
vm149c_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
vm149c_dev->sd.internal_ops = &vm149c_int_ops;
ret = vm149c_init_controls(vm149c_dev);
if (ret)
goto err_cleanup;
ret = media_entity_
pads_init(&vm149c_dev->sd.entity, 0, NULL);
if (ret < 0)
goto err_cleanup;
sd = &vm149c_dev->sd;
sd->entity.function = MEDIA_ENT_F_LENS;
AF 算法
RK AF 算法将模组镜头整个可移动行程的位置参数定义为[0,64],模组镜头整个可移动行程在 VCM 驱动电流上对应的变化范围为[启动电流,额定电流],该函数中建议实现这 2 者间的映射换算关系;
实现 v4l2 子设备驱动,主要实现以下 2 个成员:
1、struct v4l2_subdev_core_ops
v4l2_subdev_core_ops主要实现.ioctl(.compat_ioctl32)回调函数。
该回调主要实现 RK 私有控制命令,涉及:
2、struct v4l2_ctrl_ops
v4l2_ctrl_ops 主要实现,g_volatile_ctrl和.s_ctrl回调函数。
以标准的 v4l2 control 实现了以下命令:
数据类型简要说明
1、struct v4l2_subdev_core_ops
Define core ops callbacks for subdevs.自定义 ioctl 的实现函数,主要包含获取马达移动的时间信息,
static const struct v4l2_subdev_core_ops vm149c_core_ops = {
.ioctl = vm149c_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = vm149c_compat_ioctl32
#endif
};
2、struct v4l2_ctrl_ops
The control operations that the driver has to provide.
static const struct v4l2_ctrl_ops vm149c_vcm_ctrl_ops = {
.g_volatile_ctrl = vm149c_get_ctrl,
.s_ctrl = vm149c_set_ctrl,
};
API 简要说明
1、xxxx_get_ctrl :获取马达的移动位置。
static int vm149c_get_ctrl(struct v4l2_ctrl *ctrl)
{
struct vm149c_device *dev_vcm = to_vm149c_vcm(ctrl);
if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE)
return vm149c_get_pos(dev_vcm, &ctrl->val);
return -EINVAL;
}
2、xxxx_set_ctrl : 设置马达的移动位置。
static int vm149c_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct vm149c_device *dev_vcm = to_vm149c_vcm(ctrl);
struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);
unsigned int dest_pos = ctrl->val;
int move_pos;
long int mv_us;
int ret = 0;
if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {
if (dest_pos > VCMDRV_MAX_LOG) {
dev_info(&client->dev,
"%s dest_pos is error. %d > %d
",
__func__, dest_pos, VCMDRV_MAX_LOG);
return -EINVAL;
}
/* calculate move time */
move_pos = dev_vcm->current_related_pos - dest_pos;
if (move_pos < 0)
move_pos = -move_pos;
ret = vm149c_set_pos(dev_vcm, dest_pos);
dev_vcm->move_ms =
((dev_vcm->vcm_movefull_t *
(uint32_t)move_pos) /
VCMDRV_MAX_LOG);
dev_dbg(&client->dev, "dest_pos %d, move_ms %ld
",
dest_pos, dev_vcm->move_ms);
dev_vcm->start_move_tv = ns_to_timeval(ktime_get_ns());
mv_us = dev_vcm->start_move_tv.tv_usec +
dev_vcm->move_ms * 1000;
if (mv_us >= 1000000) {
dev_vcm->end_move_tv.tv_sec =
dev_vcm->start_move_tv.tv_sec + 1;
dev_vcm->end_move_tv.tv_usec = mv_us - 1000000;
} else {
dev_vcm->end_move_tv.tv_sec =
dev_vcm->start_move_tv.tv_sec;
dev_vcm->end_move_tv.tv_usec = mv_us;
}
}
return ret;
}
3、xxxx_ioctl/xxxx_compat_ioctl32
自定义 ioctl 的实现函数,主要包含获取马达移动的时间信息,
实现了自定义 RK_VIDIOC_COMPAT_VCM_TIMEINFO。
static long vm149c_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct vm149c_device *vm149c_dev = sd_to_vm149c_vcm(sd);
struct rk_cam_vcm_tim *vcm_tim;
struct rk_cam_vcm_cfg *vcm_cfg;
int ret = 0;
if (cmd == RK_VIDIOC_VCM_TIMEINFO) {
vcm_tim = (struct rk_cam_vcm_tim *)arg;
vcm_tim->vcm_start_t.tv_sec = vm149c_dev->start_move_tv.tv_sec;
vcm_tim->vcm_start_t.tv_usec = vm149c_dev->start_move_tv.tv_usec;
vcm_tim->vcm_end_t.tv_sec = vm149c_dev->end_move_tv.tv_sec;
vcm_tim->vcm_end_t.tv_usec = vm149c_dev->end_move_tv.tv_usec;
dev_dbg(&client->dev, "vm149c_get_move_res 0x%lx, 0x%lx, 0x%lx, 0x%lx
",
vcm_tim->vcm_start_t.tv_sec, vcm_tim->vcm_start_t.tv_usec,
vcm_tim->vcm_end_t.tv_sec, vcm_tim->vcm_end_t.tv_usec);
} else if (cmd == RK_VIDIOC_GET_VCM_CFG) {
vcm_cfg = (struct rk_cam_vcm_cfg *)arg;
vcm_cfg->start_ma = vm149c_dev->vcm_cfg.start_ma;
vcm_cfg->rated_ma = vm149c_dev->vcm_cfg.rated_ma;
vcm_cfg->step_mode = vm149c_dev->vcm_cfg.step_mode;
} else if (cmd == RK_VIDIOC_SET_VCM_CFG) {
vcm_cfg = (struct rk_cam_vcm_cfg *)arg;
vm149c_dev->vcm_cfg.start_ma = vcm_cfg->start_ma;
vm149c_dev->vcm_cfg.rated_ma = vcm_cfg->rated_ma;
vm149c_dev->vcm_cfg.step_mode = vcm_cfg->step_mode;
vm149c_update_vcm_cfg(vm149c_dev);
} else {
dev_err(&client->dev,
"cmd 0x%x not supported
", cmd);
return -EINVAL;
}
return ret;
}
原作者:悲伤的小强