linux/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c
Tomi Valkeinen 0d346d2a6f media: v4l2-subdev: add subdev-wide state struct
We have 'struct v4l2_subdev_pad_config' which contains configuration for
a single pad used for the TRY functionality, and an array of those
structs is passed to various v4l2_subdev_pad_ops.

I was working on subdev internal routing between pads, and realized that
there's no way to add TRY functionality for routes, which is not pad
specific configuration. Adding a separate struct for try-route config
wouldn't work either, as e.g. set-fmt needs to know the try-route
configuration to propagate the settings.

This patch adds a new struct, 'struct v4l2_subdev_state' (which at the
moment only contains the v4l2_subdev_pad_config array) and the new
struct is used in most of the places where v4l2_subdev_pad_config was
used. All v4l2_subdev_pad_ops functions taking v4l2_subdev_pad_config
are changed to instead take v4l2_subdev_state.

The changes to drivers/media/v4l2-core/v4l2-subdev.c and
include/media/v4l2-subdev.h were written by hand, and all the driver
changes were done with the semantic patch below. The spatch needs to be
applied to a select list of directories. I used the following shell
commands to apply the spatch:

dirs="drivers/media/i2c drivers/media/platform drivers/media/usb drivers/media/test-drivers/vimc drivers/media/pci drivers/staging/media"
for dir in $dirs; do spatch -j8 --dir --include-headers --no-show-diff --in-place --sp-file v4l2-subdev-state.cocci $dir; done

Note that Coccinelle chokes on a few drivers (gcc extensions?). With
minor changes we can make Coccinelle run fine, and these changes can be
reverted after spatch. The diff for these changes is:

For drivers/media/i2c/s5k5baf.c:

	@@ -1481,7 +1481,7 @@ static int s5k5baf_set_selection(struct v4l2_subdev *sd,
	 				&s5k5baf_cis_rect,
	 				v4l2_subdev_get_try_crop(sd, cfg, PAD_CIS),
	 				v4l2_subdev_get_try_compose(sd, cfg, PAD_CIS),
	-				v4l2_subdev_get_try_crop(sd, cfg, PAD_OUT)
	+				v4l2_subdev_get_try_crop(sd, cfg, PAD_OUT),
	 			};
	 		s5k5baf_set_rect_and_adjust(rects, rtype, &sel->r);
	 		return 0;

For drivers/media/platform/s3c-camif/camif-capture.c:

	@@ -1230,7 +1230,7 @@ static int s3c_camif_subdev_get_fmt(struct v4l2_subdev *sd,
	 		*mf = camif->mbus_fmt;
	 		break;

	-	case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P:
	+	case CAMIF_SD_PAD_SOURCE_C:
	 		/* crop rectangle at camera interface input */
	 		mf->width = camif->camif_crop.width;
	 		mf->height = camif->camif_crop.height;
	@@ -1332,7 +1332,7 @@ static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd,
	 		}
	 		break;

	-	case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P:
	+	case CAMIF_SD_PAD_SOURCE_C:
	 		/* Pixel format can be only changed on the sink pad. */
	 		mf->code = camif->mbus_fmt.code;
	 		mf->width = crop->width;

The semantic patch is:

// <smpl>

// Change function parameter

@@
identifier func;
identifier cfg;
@@

 func(...,
-   struct v4l2_subdev_pad_config *cfg
+   struct v4l2_subdev_state *sd_state
    , ...)
 {
 <...
- cfg
+ sd_state
 ...>
 }

// Change function declaration parameter

@@
identifier func;
identifier cfg;
type T;
@@
T func(...,
-   struct v4l2_subdev_pad_config *cfg
+   struct v4l2_subdev_state *sd_state
    , ...);

// Change function return value

@@
identifier func;
@@
- struct v4l2_subdev_pad_config
+ struct v4l2_subdev_state
 *func(...)
 {
    ...
 }

// Change function declaration return value

@@
identifier func;
@@
- struct v4l2_subdev_pad_config
+ struct v4l2_subdev_state
 *func(...);

// Some drivers pass a local pad_cfg for a single pad to a called function. Wrap it
// inside a pad_state.

@@
identifier func;
identifier pad_cfg;
@@
func(...)
{
    ...
    struct v4l2_subdev_pad_config pad_cfg;
+   struct v4l2_subdev_state pad_state = { .pads = &pad_cfg };

    <+...

(
    v4l2_subdev_call
|
    sensor_call
|
    isi_try_fse
|
    isc_try_fse
|
    saa_call_all
)
    (...,
-   &pad_cfg
+   &pad_state
    ,...)

    ...+>
}

// If the function uses fields from pad_config, access via state->pads

@@
identifier func;
identifier state;
@@
 func(...,
    struct v4l2_subdev_state *state
    , ...)
 {
    <...
(
-   state->try_fmt
+   state->pads->try_fmt
|
-   state->try_crop
+   state->pads->try_crop
|
-   state->try_compose
+   state->pads->try_compose
)
    ...>
}

// If the function accesses the filehandle, use fh->state instead

@@
struct v4l2_subdev_fh *fh;
@@
-    fh->pad
+    fh->state

@@
struct v4l2_subdev_fh fh;
@@
-    fh.pad
+    fh.state

// Start of vsp1 specific

@@
@@
struct vsp1_entity {
    ...
-    struct v4l2_subdev_pad_config *config;
+    struct v4l2_subdev_state *config;
    ...
};

@@
symbol entity;
@@
vsp1_entity_init(...)
{
    ...
    entity->config =
-    v4l2_subdev_alloc_pad_config
+    v4l2_subdev_alloc_state
    (&entity->subdev);
    ...
}

@@
symbol entity;
@@
vsp1_entity_destroy(...)
{
    ...
-   v4l2_subdev_free_pad_config
+   v4l2_subdev_free_state
    (entity->config);
    ...
}

@exists@
identifier func =~ "(^vsp1.*)|(hsit_set_format)|(sru_enum_frame_size)|(sru_set_format)|(uif_get_selection)|(uif_set_selection)|(uds_enum_frame_size)|(uds_set_format)|(brx_set_format)|(brx_get_selection)|(histo_get_selection)|(histo_set_selection)|(brx_set_selection)";
symbol config;
@@
func(...) {
    ...
-    struct v4l2_subdev_pad_config *config;
+    struct v4l2_subdev_state *config;
    ...
}

// End of vsp1 specific

// Start of rcar specific

@@
identifier sd;
identifier pad_cfg;
@@
 rvin_try_format(...)
 {
    ...
-   struct v4l2_subdev_pad_config *pad_cfg;
+   struct v4l2_subdev_state *sd_state;
    ...
-   pad_cfg = v4l2_subdev_alloc_pad_config(sd);
+   sd_state = v4l2_subdev_alloc_state(sd);
    <...
-   pad_cfg
+   sd_state
    ...>
-   v4l2_subdev_free_pad_config(pad_cfg);
+   v4l2_subdev_free_state(sd_state);
    ...
 }

// End of rcar specific

// Start of rockchip specific

@@
identifier func =~ "(rkisp1_rsz_get_pad_fmt)|(rkisp1_rsz_get_pad_crop)|(rkisp1_rsz_register)";
symbol rsz;
symbol pad_cfg;
@@

 func(...)
 {
+   struct v4l2_subdev_state state = { .pads = rsz->pad_cfg };
    ...
-   rsz->pad_cfg
+   &state
    ...
 }

@@
identifier func =~ "(rkisp1_isp_get_pad_fmt)|(rkisp1_isp_get_pad_crop)";
symbol isp;
symbol pad_cfg;
@@

 func(...)
 {
+   struct v4l2_subdev_state state = { .pads = isp->pad_cfg };
    ...
-   isp->pad_cfg
+   &state
    ...
 }

@@
symbol rkisp1;
symbol isp;
symbol pad_cfg;
@@

 rkisp1_isp_register(...)
 {
+   struct v4l2_subdev_state state = { .pads = rkisp1->isp.pad_cfg };
    ...
-   rkisp1->isp.pad_cfg
+   &state
    ...
 }

// End of rockchip specific

// Start of tegra-video specific

@@
identifier sd;
identifier pad_cfg;
@@
 __tegra_channel_try_format(...)
 {
    ...
-   struct v4l2_subdev_pad_config *pad_cfg;
+   struct v4l2_subdev_state *sd_state;
    ...
-   pad_cfg = v4l2_subdev_alloc_pad_config(sd);
+   sd_state = v4l2_subdev_alloc_state(sd);
    <...
-   pad_cfg
+   sd_state
    ...>
-   v4l2_subdev_free_pad_config(pad_cfg);
+   v4l2_subdev_free_state(sd_state);
    ...
 }

@@
identifier sd_state;
@@
 __tegra_channel_try_format(...)
 {
    ...
    struct v4l2_subdev_state *sd_state;
    <...
-   sd_state->try_crop
+   sd_state->pads->try_crop
    ...>
 }

// End of tegra-video specific

// </smpl>

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
2021-06-17 10:01:27 +02:00

385 lines
9.1 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2016 NextThing Co
* Copyright (C) 2016-2019 Bootlin
*
* Author: Maxime Ripard <maxime.ripard@bootlin.com>
*/
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/videobuf2-v4l2.h>
#include "sun4i_csi.h"
#define CSI_DEFAULT_WIDTH 640
#define CSI_DEFAULT_HEIGHT 480
static const struct sun4i_csi_format sun4i_csi_formats[] = {
/* YUV422 inputs */
{
.mbus = MEDIA_BUS_FMT_YUYV8_2X8,
.fourcc = V4L2_PIX_FMT_YUV420M,
.input = CSI_INPUT_YUV,
.output = CSI_OUTPUT_YUV_420_PLANAR,
.num_planes = 3,
.bpp = { 8, 8, 8 },
.hsub = 2,
.vsub = 2,
},
};
const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc,
const u32 *mbus)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(sun4i_csi_formats); i++) {
if (fourcc && *fourcc != sun4i_csi_formats[i].fourcc)
continue;
if (mbus && *mbus != sun4i_csi_formats[i].mbus)
continue;
return &sun4i_csi_formats[i];
}
return NULL;
}
static int sun4i_csi_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct sun4i_csi *csi = video_drvdata(file);
strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
strscpy(cap->card, "sun4i-csi", sizeof(cap->card));
snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
dev_name(csi->dev));
return 0;
}
static int sun4i_csi_enum_input(struct file *file, void *priv,
struct v4l2_input *inp)
{
if (inp->index != 0)
return -EINVAL;
inp->type = V4L2_INPUT_TYPE_CAMERA;
strscpy(inp->name, "Camera", sizeof(inp->name));
return 0;
}
static int sun4i_csi_g_input(struct file *file, void *fh,
unsigned int *i)
{
*i = 0;
return 0;
}
static int sun4i_csi_s_input(struct file *file, void *fh,
unsigned int i)
{
if (i != 0)
return -EINVAL;
return 0;
}
static void _sun4i_csi_try_fmt(struct sun4i_csi *csi,
struct v4l2_pix_format_mplane *pix)
{
const struct sun4i_csi_format *_fmt;
unsigned int height, width;
unsigned int i;
_fmt = sun4i_csi_find_format(&pix->pixelformat, NULL);
if (!_fmt)
_fmt = &sun4i_csi_formats[0];
pix->field = V4L2_FIELD_NONE;
pix->colorspace = V4L2_COLORSPACE_SRGB;
pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace);
pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace,
pix->ycbcr_enc);
pix->num_planes = _fmt->num_planes;
pix->pixelformat = _fmt->fourcc;
/* Align the width and height on the subsampling */
width = ALIGN(pix->width, _fmt->hsub);
height = ALIGN(pix->height, _fmt->vsub);
/* Clamp the width and height to our capabilities */
pix->width = clamp(width, _fmt->hsub, CSI_MAX_WIDTH);
pix->height = clamp(height, _fmt->vsub, CSI_MAX_HEIGHT);
for (i = 0; i < _fmt->num_planes; i++) {
unsigned int hsub = i > 0 ? _fmt->hsub : 1;
unsigned int vsub = i > 0 ? _fmt->vsub : 1;
unsigned int bpl;
bpl = pix->width / hsub * _fmt->bpp[i] / 8;
pix->plane_fmt[i].bytesperline = bpl;
pix->plane_fmt[i].sizeimage = bpl * pix->height / vsub;
}
}
static int sun4i_csi_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
_sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
return 0;
}
static int sun4i_csi_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
_sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
csi->fmt = f->fmt.pix_mp;
return 0;
}
static int sun4i_csi_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
f->fmt.pix_mp = csi->fmt;
return 0;
}
static int sun4i_csi_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(sun4i_csi_formats))
return -EINVAL;
f->pixelformat = sun4i_csi_formats[f->index].fourcc;
return 0;
}
static const struct v4l2_ioctl_ops sun4i_csi_ioctl_ops = {
.vidioc_querycap = sun4i_csi_querycap,
.vidioc_enum_fmt_vid_cap = sun4i_csi_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap_mplane = sun4i_csi_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap_mplane = sun4i_csi_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap_mplane = sun4i_csi_try_fmt_vid_cap,
.vidioc_enum_input = sun4i_csi_enum_input,
.vidioc_g_input = sun4i_csi_g_input,
.vidioc_s_input = sun4i_csi_s_input,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
static int sun4i_csi_open(struct file *file)
{
struct sun4i_csi *csi = video_drvdata(file);
int ret;
ret = mutex_lock_interruptible(&csi->lock);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(csi->dev);
if (ret < 0)
goto err_unlock;
ret = v4l2_pipeline_pm_get(&csi->vdev.entity);
if (ret)
goto err_pm_put;
ret = v4l2_fh_open(file);
if (ret)
goto err_pipeline_pm_put;
mutex_unlock(&csi->lock);
return 0;
err_pipeline_pm_put:
v4l2_pipeline_pm_put(&csi->vdev.entity);
err_pm_put:
pm_runtime_put(csi->dev);
err_unlock:
mutex_unlock(&csi->lock);
return ret;
}
static int sun4i_csi_release(struct file *file)
{
struct sun4i_csi *csi = video_drvdata(file);
mutex_lock(&csi->lock);
_vb2_fop_release(file, NULL);
v4l2_pipeline_pm_put(&csi->vdev.entity);
pm_runtime_put(csi->dev);
mutex_unlock(&csi->lock);
return 0;
}
static const struct v4l2_file_operations sun4i_csi_fops = {
.owner = THIS_MODULE,
.open = sun4i_csi_open,
.release = sun4i_csi_release,
.unlocked_ioctl = video_ioctl2,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
};
static const struct v4l2_mbus_framefmt sun4i_csi_pad_fmt_default = {
.width = CSI_DEFAULT_WIDTH,
.height = CSI_DEFAULT_HEIGHT,
.code = MEDIA_BUS_FMT_YUYV8_2X8,
.field = V4L2_FIELD_NONE,
.colorspace = V4L2_COLORSPACE_RAW,
.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
.quantization = V4L2_QUANTIZATION_DEFAULT,
.xfer_func = V4L2_XFER_FUNC_DEFAULT,
};
static int sun4i_csi_subdev_init_cfg(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state)
{
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_get_try_format(subdev, sd_state, CSI_SUBDEV_SINK);
*fmt = sun4i_csi_pad_fmt_default;
return 0;
}
static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
struct v4l2_mbus_framefmt *subdev_fmt;
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state,
fmt->pad);
else
subdev_fmt = &csi->subdev_fmt;
fmt->format = *subdev_fmt;
return 0;
}
static int sun4i_csi_subdev_set_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_format *fmt)
{
struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
struct v4l2_mbus_framefmt *subdev_fmt;
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state,
fmt->pad);
else
subdev_fmt = &csi->subdev_fmt;
/* We can only set the format on the sink pad */
if (fmt->pad == CSI_SUBDEV_SINK) {
/* It's the sink, only allow changing the frame size */
subdev_fmt->width = fmt->format.width;
subdev_fmt->height = fmt->format.height;
subdev_fmt->code = fmt->format.code;
}
fmt->format = *subdev_fmt;
return 0;
}
static int
sun4i_csi_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *mbus)
{
if (mbus->index >= ARRAY_SIZE(sun4i_csi_formats))
return -EINVAL;
mbus->code = sun4i_csi_formats[mbus->index].mbus;
return 0;
}
static const struct v4l2_subdev_pad_ops sun4i_csi_subdev_pad_ops = {
.link_validate = v4l2_subdev_link_validate_default,
.init_cfg = sun4i_csi_subdev_init_cfg,
.get_fmt = sun4i_csi_subdev_get_fmt,
.set_fmt = sun4i_csi_subdev_set_fmt,
.enum_mbus_code = sun4i_csi_subdev_enum_mbus_code,
};
const struct v4l2_subdev_ops sun4i_csi_subdev_ops = {
.pad = &sun4i_csi_subdev_pad_ops,
};
int sun4i_csi_v4l2_register(struct sun4i_csi *csi)
{
struct video_device *vdev = &csi->vdev;
int ret;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
vdev->v4l2_dev = &csi->v4l;
vdev->queue = &csi->queue;
strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->lock = &csi->lock;
/* Set a default format */
csi->fmt.pixelformat = sun4i_csi_formats[0].fourcc;
csi->fmt.width = CSI_DEFAULT_WIDTH;
csi->fmt.height = CSI_DEFAULT_HEIGHT;
_sun4i_csi_try_fmt(csi, &csi->fmt);
csi->subdev_fmt = sun4i_csi_pad_fmt_default;
vdev->fops = &sun4i_csi_fops;
vdev->ioctl_ops = &sun4i_csi_ioctl_ops;
video_set_drvdata(vdev, csi);
ret = video_register_device(&csi->vdev, VFL_TYPE_VIDEO, -1);
if (ret)
return ret;
dev_info(csi->dev, "Device registered as %s\n",
video_device_node_name(vdev));
return 0;
}