Commit 56281021 authored by Akinobu Mita's avatar Akinobu Mita Committed by Mauro Carvalho Chehab
Browse files

media: video-i2c: support changing frame interval

AMG88xx has a register for setting frame rate 1 or 10 FPS.
This adds support changing frame interval.

Reference specifications:
https://docid81hrs3j1.cloudfront.net/medialibrary/2017/11/PANA-S-A0002141979-1.pdf



Acked-by: default avatarMatt Ranostay <matt.ranostay@konsulko.com>
Acked-by: default avatarSakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: default avatarAkinobu Mita <akinobu.mita@gmail.com>
Signed-off-by: default avatarHans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+samsung@kernel.org>
parent a714f6c7
Loading
Loading
Loading
Loading
+66 −12
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ struct video_i2c_data {

	struct task_struct *kthread_vid_cap;
	struct list_head vid_cap_active;

	struct v4l2_fract frame_interval;
};

static const struct v4l2_fmtdesc amg88xx_format = {
@@ -74,8 +76,9 @@ struct video_i2c_chip {
	const struct v4l2_fmtdesc *format;
	const struct v4l2_frmsize_discrete *size;

	/* max frames per second */
	unsigned int max_fps;
	/* available frame intervals */
	const struct v4l2_fract *frame_intervals;
	unsigned int num_frame_intervals;

	/* pixel buffer size */
	unsigned int buffer_size;
@@ -85,6 +88,9 @@ struct video_i2c_chip {

	const struct regmap_config *regmap_config;

	/* setup function */
	int (*setup)(struct video_i2c_data *data);

	/* xfer function */
	int (*xfer)(struct video_i2c_data *data, char *buf);

@@ -92,6 +98,10 @@ struct video_i2c_chip {
	int (*hwmon_init)(struct video_i2c_data *data);
};

/* Frame rate register */
#define AMG88XX_REG_FPSC	0x02
#define AMG88XX_FPSC_1FPS		BIT(0)

/* Thermistor register */
#define AMG88XX_REG_TTHL	0x0e

@@ -104,6 +114,19 @@ static int amg88xx_xfer(struct video_i2c_data *data, char *buf)
				data->chip->buffer_size);
}

static int amg88xx_setup(struct video_i2c_data *data)
{
	unsigned int mask = AMG88XX_FPSC_1FPS;
	unsigned int val;

	if (data->frame_interval.numerator == data->frame_interval.denominator)
		val = mask;
	else
		val = 0;

	return regmap_update_bits(data->regmap, AMG88XX_REG_FPSC, mask, val);
}

#if IS_ENABLED(CONFIG_HWMON)

static const u32 amg88xx_temp_config[] = {
@@ -178,14 +201,21 @@ static int amg88xx_hwmon_init(struct video_i2c_data *data)

#define AMG88XX		0

static const struct v4l2_fract amg88xx_frame_intervals[] = {
	{ 1, 10 },
	{ 1, 1 },
};

static const struct video_i2c_chip video_i2c_chip[] = {
	[AMG88XX] = {
		.size		= &amg88xx_size,
		.format		= &amg88xx_format,
		.max_fps	= 10,
		.frame_intervals	= amg88xx_frame_intervals,
		.num_frame_intervals	= ARRAY_SIZE(amg88xx_frame_intervals),
		.buffer_size	= 128,
		.bpp		= 16,
		.regmap_config	= &amg88xx_regmap_config,
		.setup		= &amg88xx_setup,
		.xfer		= &amg88xx_xfer,
		.hwmon_init	= amg88xx_hwmon_init,
	},
@@ -250,7 +280,8 @@ static void buffer_queue(struct vb2_buffer *vb)
static int video_i2c_thread_vid_cap(void *priv)
{
	struct video_i2c_data *data = priv;
	unsigned int delay = msecs_to_jiffies(1000 / data->chip->max_fps);
	unsigned int delay = mult_frac(HZ, data->frame_interval.numerator,
				       data->frame_interval.denominator);

	set_freezable();

@@ -312,19 +343,26 @@ static void video_i2c_del_list(struct vb2_queue *vq, enum vb2_buffer_state state
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
	struct video_i2c_data *data = vb2_get_drv_priv(vq);
	int ret;

	if (data->kthread_vid_cap)
		return 0;

	ret = data->chip->setup(data);
	if (ret)
		goto error_del_list;

	data->sequence = 0;
	data->kthread_vid_cap = kthread_run(video_i2c_thread_vid_cap, data,
					    "%s-vid-cap", data->v4l2_dev.name);
	if (!IS_ERR(data->kthread_vid_cap))
	ret = PTR_ERR_OR_ZERO(data->kthread_vid_cap);
	if (!ret)
		return 0;

error_del_list:
	video_i2c_del_list(vq, VB2_BUF_STATE_QUEUED);

	return PTR_ERR(data->kthread_vid_cap);
	return ret;
}

static void stop_streaming(struct vb2_queue *vq)
@@ -431,15 +469,14 @@ static int video_i2c_enum_frameintervals(struct file *file, void *priv,
	const struct video_i2c_data *data = video_drvdata(file);
	const struct v4l2_frmsize_discrete *size = data->chip->size;

	if (fe->index > 0)
	if (fe->index >= data->chip->num_frame_intervals)
		return -EINVAL;

	if (fe->width != size->width || fe->height != size->height)
		return -EINVAL;

	fe->type = V4L2_FRMIVAL_TYPE_DISCRETE;
	fe->discrete.numerator = 1;
	fe->discrete.denominator = data->chip->max_fps;
	fe->discrete = data->chip->frame_intervals[fe->index];

	return 0;
}
@@ -484,12 +521,27 @@ static int video_i2c_g_parm(struct file *filp, void *priv,

	parm->parm.capture.readbuffers = 1;
	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
	parm->parm.capture.timeperframe.numerator = 1;
	parm->parm.capture.timeperframe.denominator = data->chip->max_fps;
	parm->parm.capture.timeperframe = data->frame_interval;

	return 0;
}

static int video_i2c_s_parm(struct file *filp, void *priv,
			      struct v4l2_streamparm *parm)
{
	struct video_i2c_data *data = video_drvdata(filp);
	int i;

	for (i = 0; i < data->chip->num_frame_intervals - 1; i++) {
		if (V4L2_FRACT_COMPARE(parm->parm.capture.timeperframe, <=,
				       data->chip->frame_intervals[i]))
			break;
	}
	data->frame_interval = data->chip->frame_intervals[i];

	return video_i2c_g_parm(filp, priv, parm);
}

static const struct v4l2_ioctl_ops video_i2c_ioctl_ops = {
	.vidioc_querycap		= video_i2c_querycap,
	.vidioc_g_input			= video_i2c_g_input,
@@ -501,7 +553,7 @@ static const struct v4l2_ioctl_ops video_i2c_ioctl_ops = {
	.vidioc_g_fmt_vid_cap		= video_i2c_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap		= video_i2c_s_fmt_vid_cap,
	.vidioc_g_parm			= video_i2c_g_parm,
	.vidioc_s_parm			= video_i2c_g_parm,
	.vidioc_s_parm			= video_i2c_s_parm,
	.vidioc_try_fmt_vid_cap		= video_i2c_try_fmt_vid_cap,
	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
@@ -591,6 +643,8 @@ static int video_i2c_probe(struct i2c_client *client,
	spin_lock_init(&data->slock);
	INIT_LIST_HEAD(&data->vid_cap_active);

	data->frame_interval = data->chip->frame_intervals[0];

	video_set_drvdata(&data->vdev, data);
	i2c_set_clientdata(client, data);