Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab7b50ae authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab
Browse files

[media] mt9v022: convert to the control framework



Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
[g.liakhovetski@gmx.de: simplified pointer arithmetic]
Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 25e965ad
Loading
Loading
Loading
Loading
+107 −158
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include <media/soc_mediabus.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-chip-ident.h>
#include <media/v4l2-ctrls.h>

/*
 * mt9v022 i2c address 0x48, 0x4c, 0x58, 0x5c
@@ -101,6 +102,17 @@ static const struct mt9v022_datafmt mt9v022_monochrome_fmts[] = {

struct mt9v022 {
	struct v4l2_subdev subdev;
	struct v4l2_ctrl_handler hdl;
	struct {
		/* exposure/auto-exposure cluster */
		struct v4l2_ctrl *autoexposure;
		struct v4l2_ctrl *exposure;
	};
	struct {
		/* gain/auto-gain cluster */
		struct v4l2_ctrl *autogain;
		struct v4l2_ctrl *gain;
	};
	struct v4l2_rect rect;	/* Sensor window */
	const struct mt9v022_datafmt *fmt;
	const struct mt9v022_datafmt *fmts;
@@ -179,6 +191,8 @@ static int mt9v022_init(struct i2c_client *client)
		ret = reg_clear(client, MT9V022_BLACK_LEVEL_CALIB_CTRL, 1);
	if (!ret)
		ret = reg_write(client, MT9V022_DIGITAL_TEST_PATTERN, 0);
	if (!ret)
		return v4l2_ctrl_handler_setup(&mt9v022->hdl);

	return ret;
}
@@ -431,189 +445,107 @@ static int mt9v022_s_register(struct v4l2_subdev *sd,
}
#endif

static const struct v4l2_queryctrl mt9v022_controls[] = {
	{
		.id		= V4L2_CID_VFLIP,
		.type		= V4L2_CTRL_TYPE_BOOLEAN,
		.name		= "Flip Vertically",
		.minimum	= 0,
		.maximum	= 1,
		.step		= 1,
		.default_value	= 0,
	}, {
		.id		= V4L2_CID_HFLIP,
		.type		= V4L2_CTRL_TYPE_BOOLEAN,
		.name		= "Flip Horizontally",
		.minimum	= 0,
		.maximum	= 1,
		.step		= 1,
		.default_value	= 0,
	}, {
		.id		= V4L2_CID_GAIN,
		.type		= V4L2_CTRL_TYPE_INTEGER,
		.name		= "Analog Gain",
		.minimum	= 64,
		.maximum	= 127,
		.step		= 1,
		.default_value	= 64,
		.flags		= V4L2_CTRL_FLAG_SLIDER,
	}, {
		.id		= V4L2_CID_EXPOSURE,
		.type		= V4L2_CTRL_TYPE_INTEGER,
		.name		= "Exposure",
		.minimum	= 1,
		.maximum	= 255,
		.step		= 1,
		.default_value	= 255,
		.flags		= V4L2_CTRL_FLAG_SLIDER,
	}, {
		.id		= V4L2_CID_AUTOGAIN,
		.type		= V4L2_CTRL_TYPE_BOOLEAN,
		.name		= "Automatic Gain",
		.minimum	= 0,
		.maximum	= 1,
		.step		= 1,
		.default_value	= 1,
	}, {
		.id		= V4L2_CID_EXPOSURE_AUTO,
		.type		= V4L2_CTRL_TYPE_BOOLEAN,
		.name		= "Automatic Exposure",
		.minimum	= 0,
		.maximum	= 1,
		.step		= 1,
		.default_value	= 1,
	}
};

static struct soc_camera_ops mt9v022_ops = {
	.controls		= mt9v022_controls,
	.num_controls		= ARRAY_SIZE(mt9v022_controls),
};

static int mt9v022_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
static int mt9v022_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
	struct mt9v022 *mt9v022 = container_of(ctrl->handler,
					       struct mt9v022, hdl);
	struct v4l2_subdev *sd = &mt9v022->subdev;
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	const struct v4l2_queryctrl *qctrl;
	struct v4l2_ctrl *gain = mt9v022->gain;
	struct v4l2_ctrl *exp = mt9v022->exposure;
	unsigned long range;
	int data;

	qctrl = soc_camera_find_qctrl(&mt9v022_ops, ctrl->id);

	switch (ctrl->id) {
	case V4L2_CID_VFLIP:
		data = reg_read(client, MT9V022_READ_MODE);
		if (data < 0)
			return -EIO;
		ctrl->value = !!(data & 0x10);
		break;
	case V4L2_CID_HFLIP:
		data = reg_read(client, MT9V022_READ_MODE);
		if (data < 0)
			return -EIO;
		ctrl->value = !!(data & 0x20);
		break;
	case V4L2_CID_EXPOSURE_AUTO:
		data = reg_read(client, MT9V022_AEC_AGC_ENABLE);
		if (data < 0)
			return -EIO;
		ctrl->value = !!(data & 0x1);
		break;
	case V4L2_CID_AUTOGAIN:
		data = reg_read(client, MT9V022_AEC_AGC_ENABLE);
		if (data < 0)
			return -EIO;
		ctrl->value = !!(data & 0x2);
		break;
	case V4L2_CID_GAIN:
		data = reg_read(client, MT9V022_ANALOG_GAIN);
		if (data < 0)
			return -EIO;

		range = qctrl->maximum - qctrl->minimum;
		ctrl->value = ((data - 16) * range + 24) / 48 + qctrl->minimum;

		break;
	case V4L2_CID_EXPOSURE:
		range = gain->maximum - gain->minimum;
		gain->val = ((data - 16) * range + 24) / 48 + gain->minimum;
		return 0;
	case V4L2_CID_EXPOSURE_AUTO:
		data = reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH);
		if (data < 0)
			return -EIO;

		range = qctrl->maximum - qctrl->minimum;
		ctrl->value = ((data - 1) * range + 239) / 479 + qctrl->minimum;

		break;
	}
		range = exp->maximum - exp->minimum;
		exp->val = ((data - 1) * range + 239) / 479 + exp->minimum;
		return 0;
	}
	return -EINVAL;
}

static int mt9v022_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
static int mt9v022_s_ctrl(struct v4l2_ctrl *ctrl)
{
	int data;
	struct mt9v022 *mt9v022 = container_of(ctrl->handler,
					       struct mt9v022, hdl);
	struct v4l2_subdev *sd = &mt9v022->subdev;
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	const struct v4l2_queryctrl *qctrl;

	qctrl = soc_camera_find_qctrl(&mt9v022_ops, ctrl->id);
	if (!qctrl)
		return -EINVAL;
	int data;

	switch (ctrl->id) {
	case V4L2_CID_VFLIP:
		if (ctrl->value)
		if (ctrl->val)
			data = reg_set(client, MT9V022_READ_MODE, 0x10);
		else
			data = reg_clear(client, MT9V022_READ_MODE, 0x10);
		if (data < 0)
			return -EIO;
		break;
		return 0;
	case V4L2_CID_HFLIP:
		if (ctrl->value)
		if (ctrl->val)
			data = reg_set(client, MT9V022_READ_MODE, 0x20);
		else
			data = reg_clear(client, MT9V022_READ_MODE, 0x20);
		if (data < 0)
			return -EIO;
		break;
	case V4L2_CID_GAIN:
		return 0;
	case V4L2_CID_AUTOGAIN:
		if (ctrl->val) {
			if (reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0)
				return -EIO;
		} else {
			struct v4l2_ctrl *gain = mt9v022->gain;
			/* mt9v022 has minimum == default */
		if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum)
			return -EINVAL;
		else {
			unsigned long range = qctrl->maximum - qctrl->minimum;
			unsigned long range = gain->maximum - gain->minimum;
			/* Valid values 16 to 64, 32 to 64 must be even. */
			unsigned long gain = ((ctrl->value - qctrl->minimum) *
			unsigned long gain_val = ((gain->val - gain->minimum) *
					      48 + range / 2) / range + 16;
			if (gain >= 32)
				gain &= ~1;

			if (gain_val >= 32)
				gain_val &= ~1;

			/*
			 * The user wants to set gain manually, hope, she
			 * knows, what she's doing... Switch AGC off.
			 */

			if (reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x2) < 0)
				return -EIO;

			dev_dbg(&client->dev, "Setting gain from %d to %lu\n",
				reg_read(client, MT9V022_ANALOG_GAIN), gain);
			if (reg_write(client, MT9V022_ANALOG_GAIN, gain) < 0)
				reg_read(client, MT9V022_ANALOG_GAIN), gain_val);
			if (reg_write(client, MT9V022_ANALOG_GAIN, gain_val) < 0)
				return -EIO;
		}
		break;
	case V4L2_CID_EXPOSURE:
		/* mt9v022 has maximum == default */
		if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum)
			return -EINVAL;
		else {
			unsigned long range = qctrl->maximum - qctrl->minimum;
			unsigned long shutter = ((ctrl->value - qctrl->minimum) *
		return 0;
	case V4L2_CID_EXPOSURE_AUTO:
		if (ctrl->val == V4L2_EXPOSURE_AUTO) {
			data = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x1);
		} else {
			struct v4l2_ctrl *exp = mt9v022->exposure;
			unsigned long range = exp->maximum - exp->minimum;
			unsigned long shutter = ((exp->val - exp->minimum) *
					479 + range / 2) / range + 1;

			/*
			 * The user wants to set shutter width manually, hope,
			 * she knows, what she's doing... Switch AEC off.
			 */

			if (reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x1) < 0)
			data = reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x1);
			if (data < 0)
				return -EIO;

			dev_dbg(&client->dev, "Shutter width from %d to %lu\n",
					reg_read(client, MT9V022_TOTAL_SHUTTER_WIDTH),
					shutter);
@@ -621,26 +553,10 @@ static int mt9v022_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
						shutter) < 0)
				return -EIO;
		}
		break;
	case V4L2_CID_AUTOGAIN:
		if (ctrl->value)
			data = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x2);
		else
			data = reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x2);
		if (data < 0)
			return -EIO;
		break;
	case V4L2_CID_EXPOSURE_AUTO:
		if (ctrl->value)
			data = reg_set(client, MT9V022_AEC_AGC_ENABLE, 0x1);
		else
			data = reg_clear(client, MT9V022_AEC_AGC_ENABLE, 0x1);
		if (data < 0)
			return -EIO;
		break;
	}
		return 0;
	}
	return -EINVAL;
}

/*
 * Interface active, can use i2c. If it fails, it can indeed mean, that
@@ -752,9 +668,12 @@ static int mt9v022_g_skip_top_lines(struct v4l2_subdev *sd, u32 *lines)
	return 0;
}

static struct v4l2_subdev_core_ops mt9v022_subdev_core_ops = {
	.g_ctrl		= mt9v022_g_ctrl,
static const struct v4l2_ctrl_ops mt9v022_ctrl_ops = {
	.g_volatile_ctrl = mt9v022_g_volatile_ctrl,
	.s_ctrl = mt9v022_s_ctrl,
};

static struct v4l2_subdev_core_ops mt9v022_subdev_core_ops = {
	.g_chip_ident	= mt9v022_g_chip_ident,
#ifdef CONFIG_VIDEO_ADV_DEBUG
	.g_register	= mt9v022_g_register,
@@ -906,10 +825,39 @@ static int mt9v022_probe(struct i2c_client *client,
		return -ENOMEM;

	v4l2_i2c_subdev_init(&mt9v022->subdev, client, &mt9v022_subdev_ops);
	v4l2_ctrl_handler_init(&mt9v022->hdl, 6);
	v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
			V4L2_CID_VFLIP, 0, 1, 1, 0);
	v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
			V4L2_CID_HFLIP, 0, 1, 1, 0);
	mt9v022->autogain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
	mt9v022->gain = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
			V4L2_CID_GAIN, 0, 127, 1, 64);

	/*
	 * Simulated autoexposure. If enabled, we calculate shutter width
	 * ourselves in the driver based on vertical blanking and frame width
	 */
	mt9v022->autoexposure = v4l2_ctrl_new_std_menu(&mt9v022->hdl,
			&mt9v022_ctrl_ops, V4L2_CID_EXPOSURE_AUTO, 1, 0,
			V4L2_EXPOSURE_AUTO);
	mt9v022->exposure = v4l2_ctrl_new_std(&mt9v022->hdl, &mt9v022_ctrl_ops,
			V4L2_CID_EXPOSURE, 1, 255, 1, 255);

	mt9v022->subdev.ctrl_handler = &mt9v022->hdl;
	if (mt9v022->hdl.error) {
		int err = mt9v022->hdl.error;

		kfree(mt9v022);
		return err;
	}
	v4l2_ctrl_auto_cluster(2, &mt9v022->autoexposure,
				V4L2_EXPOSURE_MANUAL, true);
	v4l2_ctrl_auto_cluster(2, &mt9v022->autogain, 0, true);

	mt9v022->chip_control = MT9V022_CHIP_CONTROL_DEFAULT;

	icd->ops		= &mt9v022_ops;
	/*
	 * MT9V022 _really_ corrupts the first read out line.
	 * TODO: verify on i.MX31
@@ -922,7 +870,7 @@ static int mt9v022_probe(struct i2c_client *client,

	ret = mt9v022_video_probe(icd, client);
	if (ret) {
		icd->ops = NULL;
		v4l2_ctrl_handler_free(&mt9v022->hdl);
		kfree(mt9v022);
	}

@@ -934,8 +882,9 @@ static int mt9v022_remove(struct i2c_client *client)
	struct mt9v022 *mt9v022 = to_mt9v022(client);
	struct soc_camera_device *icd = client->dev.platform_data;

	icd->ops = NULL;
	v4l2_device_unregister_subdev(&mt9v022->subdev);
	mt9v022_video_remove(icd);
	v4l2_ctrl_handler_free(&mt9v022->hdl);
	kfree(mt9v022);

	return 0;