RK3568 VCM 驱动

VCM 设备注册(DTS)

RK VCM 驱动私有参数说明:

名称 定义
启动电流 VCM 刚好能推动模组镜头从模组镜头可移动行程最近端(模组远焦)移动,此时 VCM driver ic 的输出电流值定义为启动电流
额定电流 VCM 刚好推动模组镜头至模组镜头可移动行程的最远端(模组近焦),此时 VCM driver ic 的输出电流值定义为额定电流
VCM 电流输出模式 VCM 移动过程中会产生振荡,VCM driver ic 电流输出变化需要考虑vcm 的振荡周期,以便最大程度减小振荡,输出模式决定了输出电流改变至目标值的时间

例子:

vm149c: vm149c@0c { // vcm 驱动配置,支持 AF 时需要有这个设置
	compatible = "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!\n");
		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\n");

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 私有控制命令,涉及:

RK_VIDIOC_VCM_TIMEINFO camera_engine通过该命令获取此次镜头移动所需时间, 据此来判断镜头何时停止以及 CIS 帧曝光时间段是否与镜头移动时间段有重叠;镜头移动时间与镜头移动距离、 VCM driver ic 电流输出模式相关。

2、struct v4l2_ctrl_ops

v4l2_ctrl_ops 主要实现,g_volatile_ctrl和.s_ctrl回调函数。
以标准的 v4l2 control 实现了以下命令:

V4L2_CID_FOCUS_ABSOLUTE camera_engine 通过该命令来设置和获取镜头的绝对位置, RK AF 算法中将镜头整个可移动行程的位置参数定义为[0,64]。

数据类型简要说明

1、struct v4l2_subdev_core_ops

Define core ops callbacks for subdevs.自定义 ioctl 的实现函数,主要包含获取马达移动的时间信息,

成员名称 描述
.ioctl called at the end of ioctl() syscall handler at the V4L2core.used to provide support for private ioctls used on the driver.
.compat_ioctl32 called when a 32 bits application uses a 64 bits Kernel, in order to fix data passed from/to userspace.in order to fix data passed from/to userspace.
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.

成员名称 描述
.g_volatile_ctrl Get a new value for this control. Generally only relevant for volatile (and usually read-only) controls such as a control that returns the current signal strength which changes continuously.
.s_ctrl Actually set the new control value. s_ctrl is compulsory.The ctrl->handler->lock is held when these ops are called,so no one else can access controls owned by that handler.
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\n",
				__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\n",
				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\n",
			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\n", cmd);
		return -EINVAL;
	}

	return ret;
}

你可能感兴趣的:(RK356X,android)