linux/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2016 NextThing Co
   4 * Copyright (C) 2016-2019 Bootlin
   5 *
   6 * Author: Maxime Ripard <maxime.ripard@bootlin.com>
   7 */
   8
   9#include <linux/device.h>
  10#include <linux/pm_runtime.h>
  11
  12#include <media/v4l2-ioctl.h>
  13#include <media/v4l2-mc.h>
  14#include <media/videobuf2-v4l2.h>
  15
  16#include "sun4i_csi.h"
  17
  18#define CSI_DEFAULT_WIDTH       640
  19#define CSI_DEFAULT_HEIGHT      480
  20
  21static const struct sun4i_csi_format sun4i_csi_formats[] = {
  22        /* YUV422 inputs */
  23        {
  24                .mbus           = MEDIA_BUS_FMT_YUYV8_2X8,
  25                .fourcc         = V4L2_PIX_FMT_YUV420M,
  26                .input          = CSI_INPUT_YUV,
  27                .output         = CSI_OUTPUT_YUV_420_PLANAR,
  28                .num_planes     = 3,
  29                .bpp            = { 8, 8, 8 },
  30                .hsub           = 2,
  31                .vsub           = 2,
  32        },
  33};
  34
  35const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc,
  36                                                     const u32 *mbus)
  37{
  38        unsigned int i;
  39
  40        for (i = 0; i < ARRAY_SIZE(sun4i_csi_formats); i++) {
  41                if (fourcc && *fourcc != sun4i_csi_formats[i].fourcc)
  42                        continue;
  43
  44                if (mbus && *mbus != sun4i_csi_formats[i].mbus)
  45                        continue;
  46
  47                return &sun4i_csi_formats[i];
  48        }
  49
  50        return NULL;
  51}
  52
  53static int sun4i_csi_querycap(struct file *file, void *priv,
  54                              struct v4l2_capability *cap)
  55{
  56        struct sun4i_csi *csi = video_drvdata(file);
  57
  58        strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
  59        strscpy(cap->card, "sun4i-csi", sizeof(cap->card));
  60        snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
  61                 dev_name(csi->dev));
  62
  63        return 0;
  64}
  65
  66static int sun4i_csi_enum_input(struct file *file, void *priv,
  67                                struct v4l2_input *inp)
  68{
  69        if (inp->index != 0)
  70                return -EINVAL;
  71
  72        inp->type = V4L2_INPUT_TYPE_CAMERA;
  73        strscpy(inp->name, "Camera", sizeof(inp->name));
  74
  75        return 0;
  76}
  77
  78static int sun4i_csi_g_input(struct file *file, void *fh,
  79                             unsigned int *i)
  80{
  81        *i = 0;
  82
  83        return 0;
  84}
  85
  86static int sun4i_csi_s_input(struct file *file, void *fh,
  87                             unsigned int i)
  88{
  89        if (i != 0)
  90                return -EINVAL;
  91
  92        return 0;
  93}
  94
  95static void _sun4i_csi_try_fmt(struct sun4i_csi *csi,
  96                               struct v4l2_pix_format_mplane *pix)
  97{
  98        const struct sun4i_csi_format *_fmt;
  99        unsigned int height, width;
 100        unsigned int i;
 101
 102        _fmt = sun4i_csi_find_format(&pix->pixelformat, NULL);
 103        if (!_fmt)
 104                _fmt = &sun4i_csi_formats[0];
 105
 106        pix->field = V4L2_FIELD_NONE;
 107        pix->colorspace = V4L2_COLORSPACE_SRGB;
 108        pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace);
 109        pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
 110        pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace,
 111                                                          pix->ycbcr_enc);
 112
 113        pix->num_planes = _fmt->num_planes;
 114        pix->pixelformat = _fmt->fourcc;
 115
 116        /* Align the width and height on the subsampling */
 117        width = ALIGN(pix->width, _fmt->hsub);
 118        height = ALIGN(pix->height, _fmt->vsub);
 119
 120        /* Clamp the width and height to our capabilities */
 121        pix->width = clamp(width, _fmt->hsub, CSI_MAX_WIDTH);
 122        pix->height = clamp(height, _fmt->vsub, CSI_MAX_HEIGHT);
 123
 124        for (i = 0; i < _fmt->num_planes; i++) {
 125                unsigned int hsub = i > 0 ? _fmt->hsub : 1;
 126                unsigned int vsub = i > 0 ? _fmt->vsub : 1;
 127                unsigned int bpl;
 128
 129                bpl = pix->width / hsub * _fmt->bpp[i] / 8;
 130                pix->plane_fmt[i].bytesperline = bpl;
 131                pix->plane_fmt[i].sizeimage = bpl * pix->height / vsub;
 132        }
 133}
 134
 135static int sun4i_csi_try_fmt_vid_cap(struct file *file, void *priv,
 136                                     struct v4l2_format *f)
 137{
 138        struct sun4i_csi *csi = video_drvdata(file);
 139
 140        _sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
 141
 142        return 0;
 143}
 144
 145static int sun4i_csi_s_fmt_vid_cap(struct file *file, void *priv,
 146                                   struct v4l2_format *f)
 147{
 148        struct sun4i_csi *csi = video_drvdata(file);
 149
 150        _sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
 151        csi->fmt = f->fmt.pix_mp;
 152
 153        return 0;
 154}
 155
 156static int sun4i_csi_g_fmt_vid_cap(struct file *file, void *priv,
 157                                   struct v4l2_format *f)
 158{
 159        struct sun4i_csi *csi = video_drvdata(file);
 160
 161        f->fmt.pix_mp = csi->fmt;
 162
 163        return 0;
 164}
 165
 166static int sun4i_csi_enum_fmt_vid_cap(struct file *file, void *priv,
 167                                      struct v4l2_fmtdesc *f)
 168{
 169        if (f->index >= ARRAY_SIZE(sun4i_csi_formats))
 170                return -EINVAL;
 171
 172        f->pixelformat = sun4i_csi_formats[f->index].fourcc;
 173
 174        return 0;
 175}
 176
 177static const struct v4l2_ioctl_ops sun4i_csi_ioctl_ops = {
 178        .vidioc_querycap                = sun4i_csi_querycap,
 179
 180        .vidioc_enum_fmt_vid_cap        = sun4i_csi_enum_fmt_vid_cap,
 181        .vidioc_g_fmt_vid_cap_mplane    = sun4i_csi_g_fmt_vid_cap,
 182        .vidioc_s_fmt_vid_cap_mplane    = sun4i_csi_s_fmt_vid_cap,
 183        .vidioc_try_fmt_vid_cap_mplane  = sun4i_csi_try_fmt_vid_cap,
 184
 185        .vidioc_enum_input              = sun4i_csi_enum_input,
 186        .vidioc_g_input                 = sun4i_csi_g_input,
 187        .vidioc_s_input                 = sun4i_csi_s_input,
 188
 189        .vidioc_reqbufs                 = vb2_ioctl_reqbufs,
 190        .vidioc_create_bufs             = vb2_ioctl_create_bufs,
 191        .vidioc_querybuf                = vb2_ioctl_querybuf,
 192        .vidioc_qbuf                    = vb2_ioctl_qbuf,
 193        .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
 194        .vidioc_expbuf                  = vb2_ioctl_expbuf,
 195        .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
 196        .vidioc_streamon                = vb2_ioctl_streamon,
 197        .vidioc_streamoff               = vb2_ioctl_streamoff,
 198};
 199
 200static int sun4i_csi_open(struct file *file)
 201{
 202        struct sun4i_csi *csi = video_drvdata(file);
 203        int ret;
 204
 205        ret = mutex_lock_interruptible(&csi->lock);
 206        if (ret)
 207                return ret;
 208
 209        ret = pm_runtime_resume_and_get(csi->dev);
 210        if (ret < 0)
 211                goto err_unlock;
 212
 213        ret = v4l2_pipeline_pm_get(&csi->vdev.entity);
 214        if (ret)
 215                goto err_pm_put;
 216
 217        ret = v4l2_fh_open(file);
 218        if (ret)
 219                goto err_pipeline_pm_put;
 220
 221        mutex_unlock(&csi->lock);
 222
 223        return 0;
 224
 225err_pipeline_pm_put:
 226        v4l2_pipeline_pm_put(&csi->vdev.entity);
 227
 228err_pm_put:
 229        pm_runtime_put(csi->dev);
 230
 231err_unlock:
 232        mutex_unlock(&csi->lock);
 233
 234        return ret;
 235}
 236
 237static int sun4i_csi_release(struct file *file)
 238{
 239        struct sun4i_csi *csi = video_drvdata(file);
 240
 241        mutex_lock(&csi->lock);
 242
 243        _vb2_fop_release(file, NULL);
 244
 245        v4l2_pipeline_pm_put(&csi->vdev.entity);
 246        pm_runtime_put(csi->dev);
 247
 248        mutex_unlock(&csi->lock);
 249
 250        return 0;
 251}
 252
 253static const struct v4l2_file_operations sun4i_csi_fops = {
 254        .owner          = THIS_MODULE,
 255        .open           = sun4i_csi_open,
 256        .release        = sun4i_csi_release,
 257        .unlocked_ioctl = video_ioctl2,
 258        .poll           = vb2_fop_poll,
 259        .mmap           = vb2_fop_mmap,
 260};
 261
 262static const struct v4l2_mbus_framefmt sun4i_csi_pad_fmt_default = {
 263        .width = CSI_DEFAULT_WIDTH,
 264        .height = CSI_DEFAULT_HEIGHT,
 265        .code = MEDIA_BUS_FMT_YUYV8_2X8,
 266        .field = V4L2_FIELD_NONE,
 267        .colorspace = V4L2_COLORSPACE_RAW,
 268        .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
 269        .quantization = V4L2_QUANTIZATION_DEFAULT,
 270        .xfer_func = V4L2_XFER_FUNC_DEFAULT,
 271};
 272
 273static int sun4i_csi_subdev_init_cfg(struct v4l2_subdev *subdev,
 274                                     struct v4l2_subdev_state *sd_state)
 275{
 276        struct v4l2_mbus_framefmt *fmt;
 277
 278        fmt = v4l2_subdev_get_try_format(subdev, sd_state, CSI_SUBDEV_SINK);
 279        *fmt = sun4i_csi_pad_fmt_default;
 280
 281        return 0;
 282}
 283
 284static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev,
 285                                    struct v4l2_subdev_state *sd_state,
 286                                    struct v4l2_subdev_format *fmt)
 287{
 288        struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
 289        struct v4l2_mbus_framefmt *subdev_fmt;
 290
 291        if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
 292                subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state,
 293                                                        fmt->pad);
 294        else
 295                subdev_fmt = &csi->subdev_fmt;
 296
 297        fmt->format = *subdev_fmt;
 298
 299        return 0;
 300}
 301
 302static int sun4i_csi_subdev_set_fmt(struct v4l2_subdev *subdev,
 303                                    struct v4l2_subdev_state *sd_state,
 304                                    struct v4l2_subdev_format *fmt)
 305{
 306        struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
 307        struct v4l2_mbus_framefmt *subdev_fmt;
 308
 309        if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
 310                subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state,
 311                                                        fmt->pad);
 312        else
 313                subdev_fmt = &csi->subdev_fmt;
 314
 315        /* We can only set the format on the sink pad */
 316        if (fmt->pad == CSI_SUBDEV_SINK) {
 317                /* It's the sink, only allow changing the frame size */
 318                subdev_fmt->width = fmt->format.width;
 319                subdev_fmt->height = fmt->format.height;
 320                subdev_fmt->code = fmt->format.code;
 321        }
 322
 323        fmt->format = *subdev_fmt;
 324
 325        return 0;
 326}
 327
 328static int
 329sun4i_csi_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
 330                                struct v4l2_subdev_state *sd_state,
 331                                struct v4l2_subdev_mbus_code_enum *mbus)
 332{
 333        if (mbus->index >= ARRAY_SIZE(sun4i_csi_formats))
 334                return -EINVAL;
 335
 336        mbus->code = sun4i_csi_formats[mbus->index].mbus;
 337
 338        return 0;
 339}
 340
 341static const struct v4l2_subdev_pad_ops sun4i_csi_subdev_pad_ops = {
 342        .link_validate  = v4l2_subdev_link_validate_default,
 343        .init_cfg       = sun4i_csi_subdev_init_cfg,
 344        .get_fmt        = sun4i_csi_subdev_get_fmt,
 345        .set_fmt        = sun4i_csi_subdev_set_fmt,
 346        .enum_mbus_code = sun4i_csi_subdev_enum_mbus_code,
 347};
 348
 349const struct v4l2_subdev_ops sun4i_csi_subdev_ops = {
 350        .pad = &sun4i_csi_subdev_pad_ops,
 351};
 352
 353int sun4i_csi_v4l2_register(struct sun4i_csi *csi)
 354{
 355        struct video_device *vdev = &csi->vdev;
 356        int ret;
 357
 358        vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
 359        vdev->v4l2_dev = &csi->v4l;
 360        vdev->queue = &csi->queue;
 361        strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
 362        vdev->release = video_device_release_empty;
 363        vdev->lock = &csi->lock;
 364
 365        /* Set a default format */
 366        csi->fmt.pixelformat = sun4i_csi_formats[0].fourcc;
 367        csi->fmt.width = CSI_DEFAULT_WIDTH;
 368        csi->fmt.height = CSI_DEFAULT_HEIGHT;
 369        _sun4i_csi_try_fmt(csi, &csi->fmt);
 370        csi->subdev_fmt = sun4i_csi_pad_fmt_default;
 371
 372        vdev->fops = &sun4i_csi_fops;
 373        vdev->ioctl_ops = &sun4i_csi_ioctl_ops;
 374        video_set_drvdata(vdev, csi);
 375
 376        ret = video_register_device(&csi->vdev, VFL_TYPE_VIDEO, -1);
 377        if (ret)
 378                return ret;
 379
 380        dev_info(csi->dev, "Device registered as %s\n",
 381                 video_device_node_name(vdev));
 382
 383        return 0;
 384}
 385