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

Commit da673e60 authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Mauro Carvalho Chehab
Browse files

[media] mt9m111: properly implement .s_crop and .s_fmt(), reset on STREAMON



mt9m111 camera sensors support cropping and scaling. The current
implementation is broken. For example, .s_crop() sets output frame sizes
instead of the input cropping window. This patch adds a proper implementation
of these methods. Besides it adds a sensor-disable and -enable operations
on first open() and last close() respectively, to save power while closed and
to return the camera to the default power-on state.

Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent a650bf1e
Loading
Loading
Loading
Loading
+113 −113
Original line number Original line Diff line number Diff line
@@ -185,19 +185,6 @@ struct mt9m111_datafmt {
	enum v4l2_colorspace		colorspace;
	enum v4l2_colorspace		colorspace;
};
};


/* Find a data format by a pixel code in an array */
static const struct mt9m111_datafmt *mt9m111_find_datafmt(
	enum v4l2_mbus_pixelcode code, const struct mt9m111_datafmt *fmt,
	int n)
{
	int i;
	for (i = 0; i < n; i++)
		if (fmt[i].code == code)
			return fmt + i;

	return NULL;
}

static const struct mt9m111_datafmt mt9m111_colour_fmts[] = {
static const struct mt9m111_datafmt mt9m111_colour_fmts[] = {
	{V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG},
	{V4L2_MBUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG},
	{V4L2_MBUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG},
	{V4L2_MBUS_FMT_YVYU8_2X8, V4L2_COLORSPACE_JPEG},
@@ -220,7 +207,9 @@ struct mt9m111 {
	int model;	/* V4L2_IDENT_MT9M111 or V4L2_IDENT_MT9M112 code
	int model;	/* V4L2_IDENT_MT9M111 or V4L2_IDENT_MT9M112 code
			 * from v4l2-chip-ident.h */
			 * from v4l2-chip-ident.h */
	struct mt9m111_context *ctx;
	struct mt9m111_context *ctx;
	struct v4l2_rect rect;
	struct v4l2_rect rect;	/* cropping rectangle */
	int width;		/* output */
	int height;		/* sizes */
	struct mutex power_lock; /* lock to protect power_count */
	struct mutex power_lock; /* lock to protect power_count */
	int power_count;
	int power_count;
	const struct mt9m111_datafmt *fmt;
	const struct mt9m111_datafmt *fmt;
@@ -228,6 +217,18 @@ struct mt9m111 {
	unsigned char datawidth;
	unsigned char datawidth;
};
};


/* Find a data format by a pixel code */
static const struct mt9m111_datafmt *mt9m111_find_datafmt(struct mt9m111 *mt9m111,
						enum v4l2_mbus_pixelcode code)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(mt9m111_colour_fmts); i++)
		if (mt9m111_colour_fmts[i].code == code)
			return mt9m111_colour_fmts + i;

	return mt9m111->fmt;
}

static struct mt9m111 *to_mt9m111(const struct i2c_client *client)
static struct mt9m111 *to_mt9m111(const struct i2c_client *client)
{
{
	return container_of(i2c_get_clientdata(client), struct mt9m111, subdev);
	return container_of(i2c_get_clientdata(client), struct mt9m111, subdev);
@@ -316,43 +317,49 @@ static int mt9m111_set_context(struct mt9m111 *mt9m111,
}
}


static int mt9m111_setup_rect_ctx(struct mt9m111 *mt9m111,
static int mt9m111_setup_rect_ctx(struct mt9m111 *mt9m111,
			struct v4l2_rect *rect, struct mt9m111_context *ctx)
			struct mt9m111_context *ctx, struct v4l2_rect *rect,
			unsigned int width, unsigned int height)
{
{
	struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev);
	struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev);
	int ret = mt9m111_reg_write(client, ctx->reducer_xzoom, MT9M111_MAX_WIDTH);
	int ret = mt9m111_reg_write(client, ctx->reducer_xzoom, rect->width);
	if (!ret)
	if (!ret)
		ret = mt9m111_reg_write(client, ctx->reducer_yzoom, MT9M111_MAX_HEIGHT);
		ret = mt9m111_reg_write(client, ctx->reducer_yzoom, rect->height);
	if (!ret)
	if (!ret)
		ret = mt9m111_reg_write(client, ctx->reducer_xsize, rect->width);
		ret = mt9m111_reg_write(client, ctx->reducer_xsize, width);
	if (!ret)
	if (!ret)
		ret = mt9m111_reg_write(client, ctx->reducer_ysize, rect->height);
		ret = mt9m111_reg_write(client, ctx->reducer_ysize, height);
	return ret;
	return ret;
}
}


static int mt9m111_setup_rect(struct mt9m111 *mt9m111,
static int mt9m111_setup_geometry(struct mt9m111 *mt9m111, struct v4l2_rect *rect,
			      struct v4l2_rect *rect)
			int width, int height, enum v4l2_mbus_pixelcode code)
{
{
	struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev);
	struct i2c_client *client = v4l2_get_subdevdata(&mt9m111->subdev);
	int ret;
	int ret;
	bool is_raw_format = mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
		mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE;


	ret = reg_write(COLUMN_START, rect->left);
	ret = reg_write(COLUMN_START, rect->left);
	if (!ret)
	if (!ret)
		ret = reg_write(ROW_START, rect->top);
		ret = reg_write(ROW_START, rect->top);


	if (is_raw_format) {
	if (!ret)
	if (!ret)
		ret = reg_write(WINDOW_WIDTH, rect->width);
		ret = reg_write(WINDOW_WIDTH, rect->width);
	if (!ret)
	if (!ret)
		ret = reg_write(WINDOW_HEIGHT, rect->height);
		ret = reg_write(WINDOW_HEIGHT, rect->height);
	} else {

	if (code != V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) {
		/* IFP in use, down-scaling possible */
		if (!ret)
		if (!ret)
			ret = mt9m111_setup_rect_ctx(mt9m111, rect, &context_b);
			ret = mt9m111_setup_rect_ctx(mt9m111, &context_b,
						     rect, width, height);
		if (!ret)
		if (!ret)
			ret = mt9m111_setup_rect_ctx(mt9m111, rect, &context_a);
			ret = mt9m111_setup_rect_ctx(mt9m111, &context_a,
						     rect, width, height);
	}
	}


	dev_dbg(&client->dev, "%s(%x): %ux%u@%u:%u -> %ux%u = %d\n",
		__func__, code, rect->width, rect->height, rect->left, rect->top,
		width, height, ret);

	return ret;
	return ret;
}
}


@@ -377,43 +384,41 @@ static int mt9m111_reset(struct mt9m111 *mt9m111)
	return ret;
	return ret;
}
}


static int mt9m111_make_rect(struct mt9m111 *mt9m111,
static int mt9m111_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
			     struct v4l2_rect *rect)
{
{
	struct v4l2_rect rect = a->c;
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	int width, height;
	int ret;

	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
		return -EINVAL;

	if (mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
	if (mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
	    mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) {
	    mt9m111->fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) {
		/* Bayer format - even size lengths */
		/* Bayer format - even size lengths */
		rect->width	= ALIGN(rect->width, 2);
		rect.width	= ALIGN(rect.width, 2);
		rect->height	= ALIGN(rect->height, 2);
		rect.height	= ALIGN(rect.height, 2);
		/* Let the user play with the starting pixel */
		/* Let the user play with the starting pixel */
	}
	}


	/* FIXME: the datasheet doesn't specify minimum sizes */
	/* FIXME: the datasheet doesn't specify minimum sizes */
	soc_camera_limit_side(&rect->left, &rect->width,
	soc_camera_limit_side(&rect.left, &rect.width,
		     MT9M111_MIN_DARK_COLS, 2, MT9M111_MAX_WIDTH);
		     MT9M111_MIN_DARK_COLS, 2, MT9M111_MAX_WIDTH);


	soc_camera_limit_side(&rect->top, &rect->height,
	soc_camera_limit_side(&rect.top, &rect.height,
		     MT9M111_MIN_DARK_ROWS, 2, MT9M111_MAX_HEIGHT);
		     MT9M111_MIN_DARK_ROWS, 2, MT9M111_MAX_HEIGHT);


	return mt9m111_setup_rect(mt9m111, rect);
	width = min(mt9m111->width, rect.width);
}
	height = min(mt9m111->height, rect.height);

static int mt9m111_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a)
{
	struct v4l2_rect rect = a->c;
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	int ret;

	dev_dbg(&client->dev, "%s left=%d, top=%d, width=%d, height=%d\n",
		__func__, rect.left, rect.top, rect.width, rect.height);


	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
	ret = mt9m111_setup_geometry(mt9m111, &rect, width, height, mt9m111->fmt->code);
		return -EINVAL;
	if (!ret) {

	ret = mt9m111_make_rect(mt9m111, &rect);
	if (!ret)
		mt9m111->rect = rect;
		mt9m111->rect = rect;
		mt9m111->width = width;
		mt9m111->height = height;
	}

	return ret;
	return ret;
}
}


@@ -448,8 +453,8 @@ static int mt9m111_g_fmt(struct v4l2_subdev *sd,
{
{
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);


	mf->width	= mt9m111->rect.width;
	mf->width	= mt9m111->width;
	mf->height	= mt9m111->rect.height;
	mf->height	= mt9m111->height;
	mf->code	= mt9m111->fmt->code;
	mf->code	= mt9m111->fmt->code;
	mf->colorspace	= mt9m111->fmt->colorspace;
	mf->colorspace	= mt9m111->fmt->colorspace;
	mf->field	= V4L2_FIELD_NONE;
	mf->field	= V4L2_FIELD_NONE;
@@ -527,78 +532,72 @@ static int mt9m111_set_pixfmt(struct mt9m111 *mt9m111,
	return ret;
	return ret;
}
}


static int mt9m111_s_fmt(struct v4l2_subdev *sd,
static int mt9m111_try_fmt(struct v4l2_subdev *sd,
			   struct v4l2_mbus_framefmt *mf)
			   struct v4l2_mbus_framefmt *mf)
{
{
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	struct i2c_client *client = v4l2_get_subdevdata(sd);
	const struct mt9m111_datafmt *fmt;
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	struct v4l2_rect rect = {
	const struct mt9m111_datafmt *fmt;
		.left	= mt9m111->rect.left,
	struct v4l2_rect *rect = &mt9m111->rect;
		.top	= mt9m111->rect.top,
	bool bayer;
		.width	= mf->width,
		.height	= mf->height,
	};
	int ret;


	fmt = mt9m111_find_datafmt(mf->code, mt9m111_colour_fmts,
	fmt = mt9m111_find_datafmt(mt9m111, mf->code);
				   ARRAY_SIZE(mt9m111_colour_fmts));
	if (!fmt)
		return -EINVAL;


	dev_dbg(&client->dev,
	bayer = fmt->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
		"%s code=%x left=%d, top=%d, width=%d, height=%d\n", __func__,
		fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE;
		mf->code, rect.left, rect.top, rect.width, rect.height);


	ret = mt9m111_make_rect(mt9m111, &rect);
	/*
	if (!ret)
	 * With Bayer format enforce even side lengths, but let the user play
		ret = mt9m111_set_pixfmt(mt9m111, mf->code);
	 * with the starting pixel
	if (!ret) {
	 */
		mt9m111->rect	= rect;
	if (bayer) {
		mt9m111->fmt	= fmt;
		rect->width = ALIGN(rect->width, 2);
		mf->colorspace	= fmt->colorspace;
		rect->height = ALIGN(rect->height, 2);
	}
	}


	return ret;
	if (fmt->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE) {
		/* IFP bypass mode, no scaling */
		mf->width = rect->width;
		mf->height = rect->height;
	} else {
		/* No upscaling */
		if (mf->width > rect->width)
			mf->width = rect->width;
		if (mf->height > rect->height)
			mf->height = rect->height;
	}
	}


static int mt9m111_try_fmt(struct v4l2_subdev *sd,
	dev_dbg(&client->dev, "%s(): %ux%u, code=%x\n", __func__,
			   struct v4l2_mbus_framefmt *mf)
		mf->width, mf->height, fmt->code);
{
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
	const struct mt9m111_datafmt *fmt;
	bool bayer = mf->code == V4L2_MBUS_FMT_SBGGR8_1X8 ||
		mf->code == V4L2_MBUS_FMT_SBGGR10_2X8_PADHI_LE;


	fmt = mt9m111_find_datafmt(mf->code, mt9m111_colour_fmts,
				   ARRAY_SIZE(mt9m111_colour_fmts));
	if (!fmt) {
		fmt = mt9m111->fmt;
	mf->code = fmt->code;
	mf->code = fmt->code;
	}
	mf->colorspace = fmt->colorspace;


	/*
	return 0;
	 * With Bayer format enforce even side lengths, but let the user play
}
	 * with the starting pixel
	 */


	if (mf->height > MT9M111_MAX_HEIGHT)
static int mt9m111_s_fmt(struct v4l2_subdev *sd,
		mf->height = MT9M111_MAX_HEIGHT;
			 struct v4l2_mbus_framefmt *mf)
	else if (mf->height < 2)
{
		mf->height = 2;
	const struct mt9m111_datafmt *fmt;
	else if (bayer)
	struct mt9m111 *mt9m111 = container_of(sd, struct mt9m111, subdev);
		mf->height = ALIGN(mf->height, 2);
	struct v4l2_rect *rect = &mt9m111->rect;
	int ret;


	if (mf->width > MT9M111_MAX_WIDTH)
	mt9m111_try_fmt(sd, mf);
		mf->width = MT9M111_MAX_WIDTH;
	fmt = mt9m111_find_datafmt(mt9m111, mf->code);
	else if (mf->width < 2)
	/* try_fmt() guarantees fmt != NULL && fmt->code == mf->code */
		mf->width = 2;
	else if (bayer)
		mf->width = ALIGN(mf->width, 2);


	mf->colorspace = fmt->colorspace;
	ret = mt9m111_setup_geometry(mt9m111, rect, mf->width, mf->height, mf->code);
	if (!ret)
		ret = mt9m111_set_pixfmt(mt9m111, mf->code);
	if (!ret) {
		mt9m111->width	= mf->width;
		mt9m111->height	= mf->height;
		mt9m111->fmt	= fmt;
	}


	return 0;
	return ret;
}
}


static int mt9m111_g_chip_ident(struct v4l2_subdev *sd,
static int mt9m111_g_chip_ident(struct v4l2_subdev *sd,
@@ -765,7 +764,8 @@ static void mt9m111_restore_state(struct mt9m111 *mt9m111)
{
{
	mt9m111_set_context(mt9m111, mt9m111->ctx);
	mt9m111_set_context(mt9m111, mt9m111->ctx);
	mt9m111_set_pixfmt(mt9m111, mt9m111->fmt->code);
	mt9m111_set_pixfmt(mt9m111, mt9m111->fmt->code);
	mt9m111_setup_rect(mt9m111, &mt9m111->rect);
	mt9m111_setup_geometry(mt9m111, &mt9m111->rect,
			mt9m111->width, mt9m111->height, mt9m111->fmt->code);
	v4l2_ctrl_handler_setup(&mt9m111->hdl);
	v4l2_ctrl_handler_setup(&mt9m111->hdl);
}
}