aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/staging/media/tegra-video/vi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/media/tegra-video/vi.c')
-rw-r--r--drivers/staging/media/tegra-video/vi.c1074
1 files changed, 1074 insertions, 0 deletions
diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
new file mode 100644
index 000000000000..1b5e660155f5
--- /dev/null
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -0,0 +1,1074 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/host1x.h>
+#include <linux/lcm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include <soc/tegra/pmc.h>
+
+#include "vi.h"
+#include "video.h"
+
+#define SURFACE_ALIGN_BYTES 64
+#define MAX_CID_CONTROLS 1
+
+static const struct tegra_video_format tegra_default_format = {
+ .img_dt = TEGRA_IMAGE_DT_RAW10,
+ .bit_width = 10,
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .bpp = 2,
+ .img_fmt = TEGRA_IMAGE_FORMAT_DEF,
+ .fourcc = V4L2_PIX_FMT_SRGGB10,
+};
+
+static inline struct tegra_vi *
+host1x_client_to_vi(struct host1x_client *client)
+{
+ return container_of(client, struct tegra_vi, client);
+}
+
+static inline struct tegra_channel_buffer *
+to_tegra_channel_buffer(struct vb2_v4l2_buffer *vb)
+{
+ return container_of(vb, struct tegra_channel_buffer, buf);
+}
+
+static int tegra_get_format_idx_by_code(struct tegra_vi *vi,
+ unsigned int code)
+{
+ unsigned int i;
+
+ for (i = 0; i < vi->soc->nformats; ++i) {
+ if (vi->soc->video_formats[i].code == code)
+ return i;
+ }
+
+ return -1;
+}
+
+static u32 tegra_get_format_fourcc_by_idx(struct tegra_vi *vi,
+ unsigned int index)
+{
+ if (index >= vi->soc->nformats)
+ return -EINVAL;
+
+ return vi->soc->video_formats[index].fourcc;
+}
+
+static const struct tegra_video_format *
+tegra_get_format_by_fourcc(struct tegra_vi *vi, u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < vi->soc->nformats; ++i) {
+ if (vi->soc->video_formats[i].fourcc == fourcc)
+ return &vi->soc->video_formats[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * videobuf2 queue operations
+ */
+static int tegra_channel_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
+
+ if (*nplanes)
+ return sizes[0] < chan->format.sizeimage ? -EINVAL : 0;
+
+ *nplanes = 1;
+ sizes[0] = chan->format.sizeimage;
+ alloc_devs[0] = chan->vi->dev;
+
+ return 0;
+}
+
+static int tegra_channel_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct tegra_channel_buffer *buf = to_tegra_channel_buffer(vbuf);
+ unsigned long size = chan->format.sizeimage;
+
+ if (vb2_plane_size(vb, 0) < size) {
+ v4l2_err(chan->video.v4l2_dev,
+ "buffer too small (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, size);
+ buf->chan = chan;
+ buf->addr = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ return 0;
+}
+
+static void tegra_channel_buffer_queue(struct vb2_buffer *vb)
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct tegra_channel_buffer *buf = to_tegra_channel_buffer(vbuf);
+
+ /* put buffer into the capture queue */
+ spin_lock(&chan->start_lock);
+ list_add_tail(&buf->queue, &chan->capture);
+ spin_unlock(&chan->start_lock);
+
+ /* wait up kthread for capture */
+ wake_up_interruptible(&chan->start_wait);
+}
+
+struct v4l2_subdev *
+tegra_channel_get_remote_subdev(struct tegra_vi_channel *chan)
+{
+ struct media_pad *pad;
+ struct v4l2_subdev *subdev;
+ struct media_entity *entity;
+
+ pad = media_entity_remote_pad(&chan->pad);
+ entity = pad->entity;
+ subdev = media_entity_to_v4l2_subdev(entity);
+
+ return subdev;
+}
+
+int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on)
+{
+ struct v4l2_subdev *subdev;
+ int ret;
+
+ /* stream CSI */
+ subdev = tegra_channel_get_remote_subdev(chan);
+ ret = v4l2_subdev_call(subdev, video, s_stream, on);
+ if (on && ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ return 0;
+}
+
+void tegra_channel_release_buffers(struct tegra_vi_channel *chan,
+ enum vb2_buffer_state state)
+{
+ struct tegra_channel_buffer *buf, *nbuf;
+
+ spin_lock(&chan->start_lock);
+ list_for_each_entry_safe(buf, nbuf, &chan->capture, queue) {
+ vb2_buffer_done(&buf->buf.vb2_buf, state);
+ list_del(&buf->queue);
+ }
+ spin_unlock(&chan->start_lock);
+
+ spin_lock(&chan->done_lock);
+ list_for_each_entry_safe(buf, nbuf, &chan->done, queue) {
+ vb2_buffer_done(&buf->buf.vb2_buf, state);
+ list_del(&buf->queue);
+ }
+ spin_unlock(&chan->done_lock);
+}
+
+static int tegra_channel_start_streaming(struct vb2_queue *vq, u32 count)
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
+ int ret;
+
+ ret = pm_runtime_get_sync(chan->vi->dev);
+ if (ret < 0) {
+ dev_err(chan->vi->dev, "failed to get runtime PM: %d\n", ret);
+ pm_runtime_put_noidle(chan->vi->dev);
+ return ret;
+ }
+
+ ret = chan->vi->ops->vi_start_streaming(vq, count);
+ if (ret < 0)
+ pm_runtime_put(chan->vi->dev);
+
+ return ret;
+}
+
+static void tegra_channel_stop_streaming(struct vb2_queue *vq)
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
+
+ chan->vi->ops->vi_stop_streaming(vq);
+ pm_runtime_put(chan->vi->dev);
+}
+
+static const struct vb2_ops tegra_channel_queue_qops = {
+ .queue_setup = tegra_channel_queue_setup,
+ .buf_prepare = tegra_channel_buffer_prepare,
+ .buf_queue = tegra_channel_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = tegra_channel_start_streaming,
+ .stop_streaming = tegra_channel_stop_streaming,
+};
+
+/*
+ * V4L2 ioctl operations
+ */
+static int tegra_channel_querycap(struct file *file, void *fh,
+ struct v4l2_capability *cap)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+
+ strscpy(cap->driver, "tegra-video", sizeof(cap->driver));
+ strscpy(cap->card, chan->video.name, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ dev_name(chan->vi->dev));
+
+ return 0;
+}
+
+static int tegra_channel_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+
+ subdev = tegra_channel_get_remote_subdev(chan);
+ return v4l2_g_parm_cap(&chan->video, subdev, a);
+}
+
+static int tegra_channel_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+
+ subdev = tegra_channel_get_remote_subdev(chan);
+ return v4l2_s_parm_cap(&chan->video, subdev, a);
+}
+
+static int tegra_channel_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *sizes)
+{
+ int ret;
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+ const struct tegra_video_format *fmtinfo;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = sizes->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ fmtinfo = tegra_get_format_by_fourcc(chan->vi, sizes->pixel_format);
+ if (!fmtinfo)
+ return -EINVAL;
+
+ fse.code = fmtinfo->code;
+
+ subdev = tegra_channel_get_remote_subdev(chan);
+ ret = v4l2_subdev_call(subdev, pad, enum_frame_size, NULL, &fse);
+ if (ret)
+ return ret;
+
+ sizes->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ sizes->discrete.width = fse.max_width;
+ sizes->discrete.height = fse.max_height;
+
+ return 0;
+}
+
+static int tegra_channel_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *ivals)
+{
+ int ret;
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+ const struct tegra_video_format *fmtinfo;
+ struct v4l2_subdev_frame_interval_enum fie = {
+ .index = ivals->index,
+ .width = ivals->width,
+ .height = ivals->height,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ fmtinfo = tegra_get_format_by_fourcc(chan->vi, ivals->pixel_format);
+ if (!fmtinfo)
+ return -EINVAL;
+
+ fie.code = fmtinfo->code;
+
+ subdev = tegra_channel_get_remote_subdev(chan);
+ ret = v4l2_subdev_call(subdev, pad, enum_frame_interval, NULL, &fie);
+ if (ret)
+ return ret;
+
+ ivals->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ ivals->discrete.numerator = fie.interval.numerator;
+ ivals->discrete.denominator = fie.interval.denominator;
+
+ return 0;
+}
+
+static int tegra_channel_enum_format(struct file *file, void *fh,
+ struct v4l2_fmtdesc *f)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ unsigned int index = 0, i;
+ unsigned long *fmts_bitmap = chan->tpg_fmts_bitmap;
+
+ if (f->index >= bitmap_weight(fmts_bitmap, MAX_FORMAT_NUM))
+ return -EINVAL;
+
+ for (i = 0; i < f->index + 1; i++, index++)
+ index = find_next_bit(fmts_bitmap, MAX_FORMAT_NUM, index);
+
+ f->pixelformat = tegra_get_format_fourcc_by_idx(chan->vi, index - 1);
+
+ return 0;
+}
+
+static int tegra_channel_get_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+
+ format->fmt.pix = chan->format;
+
+ return 0;
+}
+
+static void tegra_channel_fmt_align(struct tegra_vi_channel *chan,
+ struct v4l2_pix_format *pix,
+ unsigned int bpp)
+{
+ unsigned int align;
+ unsigned int min_width;
+ unsigned int max_width;
+ unsigned int width;
+ unsigned int min_bpl;
+ unsigned int max_bpl;
+ unsigned int bpl;
+
+ /*
+ * The transfer alignment requirements are expressed in bytes. Compute
+ * minimum and maximum values, clamp the requested width and convert
+ * it back to pixels. Use bytesperline to adjust the width.
+ */
+ align = lcm(SURFACE_ALIGN_BYTES, bpp);
+ min_width = roundup(TEGRA_MIN_WIDTH, align);
+ max_width = rounddown(TEGRA_MAX_WIDTH, align);
+ width = roundup(pix->width * bpp, align);
+
+ pix->width = clamp(width, min_width, max_width) / bpp;
+ pix->height = clamp(pix->height, TEGRA_MIN_HEIGHT, TEGRA_MAX_HEIGHT);
+
+ /* Clamp the requested bytes per line value. If the maximum bytes per
+ * line value is zero, the module doesn't support user configurable
+ * line sizes. Override the requested value with the minimum in that
+ * case.
+ */
+ min_bpl = pix->width * bpp;
+ max_bpl = rounddown(TEGRA_MAX_WIDTH, SURFACE_ALIGN_BYTES);
+ bpl = roundup(pix->bytesperline, SURFACE_ALIGN_BYTES);
+
+ pix->bytesperline = clamp(bpl, min_bpl, max_bpl);
+ pix->sizeimage = pix->bytesperline * pix->height;
+}
+
+static int __tegra_channel_try_format(struct tegra_vi_channel *chan,
+ struct v4l2_pix_format *pix)
+{
+ const struct tegra_video_format *fmtinfo;
+ struct v4l2_subdev *subdev;
+ struct v4l2_subdev_format fmt;
+ struct v4l2_subdev_pad_config *pad_cfg;
+
+ subdev = tegra_channel_get_remote_subdev(chan);
+ pad_cfg = v4l2_subdev_alloc_pad_config(subdev);
+ if (!pad_cfg)
+ return -ENOMEM;
+ /*
+ * Retrieve the format information and if requested format isn't
+ * supported, keep the current format.
+ */
+ fmtinfo = tegra_get_format_by_fourcc(chan->vi, pix->pixelformat);
+ if (!fmtinfo) {
+ pix->pixelformat = chan->format.pixelformat;
+ pix->colorspace = chan->format.colorspace;
+ fmtinfo = tegra_get_format_by_fourcc(chan->vi,
+ pix->pixelformat);
+ }
+
+ pix->field = V4L2_FIELD_NONE;
+ fmt.which = V4L2_SUBDEV_FORMAT_TRY;
+ fmt.pad = 0;
+ v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code);
+ v4l2_subdev_call(subdev, pad, set_fmt, pad_cfg, &fmt);
+ v4l2_fill_pix_format(pix, &fmt.format);
+ tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
+
+ v4l2_subdev_free_pad_config(pad_cfg);
+
+ return 0;
+}
+
+static int tegra_channel_try_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+
+ return __tegra_channel_try_format(chan, &format->fmt.pix);
+}
+
+static int tegra_channel_set_format(struct file *file, void *fh,
+ struct v4l2_format *format)
+{
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ const struct tegra_video_format *fmtinfo;
+ struct v4l2_subdev_format fmt;
+ struct v4l2_subdev *subdev;
+ struct v4l2_pix_format *pix = &format->fmt.pix;
+ int ret;
+
+ if (vb2_is_busy(&chan->queue))
+ return -EBUSY;
+
+ /* get supported format by try_fmt */
+ ret = __tegra_channel_try_format(chan, pix);
+ if (ret)
+ return ret;
+
+ fmtinfo = tegra_get_format_by_fourcc(chan->vi, pix->pixelformat);
+
+ fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ fmt.pad = 0;
+ v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code);
+ subdev = tegra_channel_get_remote_subdev(chan);
+ v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt);
+ v4l2_fill_pix_format(pix, &fmt.format);
+ tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
+
+ chan->format = *pix;
+ chan->fmtinfo = fmtinfo;
+
+ return 0;
+}
+
+static int tegra_channel_enum_input(struct file *file, void *fh,
+ struct v4l2_input *inp)
+{
+ /* currently driver supports internal TPG only */
+ if (inp->index)
+ return -EINVAL;
+
+ inp->type = V4L2_INPUT_TYPE_CAMERA;
+ strscpy(inp->name, "Tegra TPG", sizeof(inp->name));
+
+ return 0;
+}
+
+static int tegra_channel_g_input(struct file *file, void *priv,
+ unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int tegra_channel_s_input(struct file *file, void *priv,
+ unsigned int input)
+{
+ if (input > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = {
+ .vidioc_querycap = tegra_channel_querycap,
+ .vidioc_g_parm = tegra_channel_g_parm,
+ .vidioc_s_parm = tegra_channel_s_parm,
+ .vidioc_enum_framesizes = tegra_channel_enum_framesizes,
+ .vidioc_enum_frameintervals = tegra_channel_enum_frameintervals,
+ .vidioc_enum_fmt_vid_cap = tegra_channel_enum_format,
+ .vidioc_g_fmt_vid_cap = tegra_channel_get_format,
+ .vidioc_s_fmt_vid_cap = tegra_channel_set_format,
+ .vidioc_try_fmt_vid_cap = tegra_channel_try_format,
+ .vidioc_enum_input = tegra_channel_enum_input,
+ .vidioc_g_input = tegra_channel_g_input,
+ .vidioc_s_input = tegra_channel_s_input,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/*
+ * V4L2 file operations
+ */
+static const struct v4l2_file_operations tegra_channel_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = video_ioctl2,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .read = vb2_fop_read,
+ .poll = vb2_fop_poll,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * V4L2 control operations
+ */
+static int vi_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct tegra_vi_channel *chan = container_of(ctrl->handler,
+ struct tegra_vi_channel,
+ ctrl_handler);
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ /* pattern change takes effect on next stream */
+ chan->pg_mode = ctrl->val + 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops vi_ctrl_ops = {
+ .s_ctrl = vi_s_ctrl,
+};
+
+static const char *const vi_pattern_strings[] = {
+ "Black/White Direct Mode",
+ "Color Patch Mode",
+};
+
+static int tegra_channel_setup_ctrl_handler(struct tegra_vi_channel *chan)
+{
+ int ret;
+
+ /* add test pattern control handler to v4l2 device */
+ v4l2_ctrl_new_std_menu_items(&chan->ctrl_handler, &vi_ctrl_ops,
+ V4L2_CID_TEST_PATTERN,
+ ARRAY_SIZE(vi_pattern_strings) - 1,
+ 0, 0, vi_pattern_strings);
+ if (chan->ctrl_handler.error) {
+ dev_err(chan->vi->dev, "failed to add TPG ctrl handler: %d\n",
+ chan->ctrl_handler.error);
+ v4l2_ctrl_handler_free(&chan->ctrl_handler);
+ return chan->ctrl_handler.error;
+ }
+
+ /* setup the controls */
+ ret = v4l2_ctrl_handler_setup(&chan->ctrl_handler);
+ if (ret < 0) {
+ dev_err(chan->vi->dev,
+ "failed to setup v4l2 ctrl handler: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* VI only support 2 formats in TPG mode */
+static void vi_tpg_fmts_bitmap_init(struct tegra_vi_channel *chan)
+{
+ int index;
+
+ bitmap_zero(chan->tpg_fmts_bitmap, MAX_FORMAT_NUM);
+
+ index = tegra_get_format_idx_by_code(chan->vi,
+ MEDIA_BUS_FMT_SRGGB10_1X10);
+ bitmap_set(chan->tpg_fmts_bitmap, index, 1);
+
+ index = tegra_get_format_idx_by_code(chan->vi,
+ MEDIA_BUS_FMT_RGB888_1X32_PADHI);
+ bitmap_set(chan->tpg_fmts_bitmap, index, 1);
+}
+
+static void tegra_channel_cleanup(struct tegra_vi_channel *chan)
+{
+ v4l2_ctrl_handler_free(&chan->ctrl_handler);
+ media_entity_cleanup(&chan->video.entity);
+ host1x_syncpt_free(chan->mw_ack_sp);
+ host1x_syncpt_free(chan->frame_start_sp);
+ mutex_destroy(&chan->video_lock);
+}
+
+void tegra_channels_cleanup(struct tegra_vi *vi)
+{
+ struct tegra_vi_channel *chan, *tmp;
+
+ if (!vi)
+ return;
+
+ list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
+ tegra_channel_cleanup(chan);
+ list_del(&chan->list);
+ kfree(chan);
+ }
+}
+
+static int tegra_channel_init(struct tegra_vi_channel *chan)
+{
+ struct tegra_vi *vi = chan->vi;
+ struct tegra_video_device *vid = dev_get_drvdata(vi->client.host);
+ unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
+ int ret;
+
+ mutex_init(&chan->video_lock);
+ INIT_LIST_HEAD(&chan->capture);
+ INIT_LIST_HEAD(&chan->done);
+ spin_lock_init(&chan->start_lock);
+ spin_lock_init(&chan->done_lock);
+ spin_lock_init(&chan->sp_incr_lock);
+ init_waitqueue_head(&chan->start_wait);
+ init_waitqueue_head(&chan->done_wait);
+
+ /* initialize the video format */
+ chan->fmtinfo = &tegra_default_format;
+ chan->format.pixelformat = chan->fmtinfo->fourcc;
+ chan->format.colorspace = V4L2_COLORSPACE_SRGB;
+ chan->format.field = V4L2_FIELD_NONE;
+ chan->format.width = TEGRA_DEF_WIDTH;
+ chan->format.height = TEGRA_DEF_HEIGHT;
+ chan->format.bytesperline = TEGRA_DEF_WIDTH * chan->fmtinfo->bpp;
+ chan->format.sizeimage = chan->format.bytesperline * TEGRA_DEF_HEIGHT;
+ tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
+
+ chan->frame_start_sp = host1x_syncpt_request(&vi->client, flags);
+ if (!chan->frame_start_sp) {
+ dev_err(vi->dev, "failed to request frame start syncpoint\n");
+ return -ENOMEM;
+ }
+
+ chan->mw_ack_sp = host1x_syncpt_request(&vi->client, flags);
+ if (!chan->mw_ack_sp) {
+ dev_err(vi->dev, "failed to request memory ack syncpoint\n");
+ ret = -ENOMEM;
+ goto free_fs_syncpt;
+ }
+
+ /* initialize the media entity */
+ chan->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&chan->video.entity, 1, &chan->pad);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to initialize media entity: %d\n", ret);
+ goto free_mw_ack_syncpt;
+ }
+
+ ret = v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS);
+ if (chan->ctrl_handler.error) {
+ dev_err(vi->dev,
+ "failed to initialize v4l2 ctrl handler: %d\n", ret);
+ goto cleanup_media;
+ }
+
+ /* initialize the video_device */
+ chan->video.fops = &tegra_channel_fops;
+ chan->video.v4l2_dev = &vid->v4l2_dev;
+ chan->video.release = video_device_release_empty;
+ chan->video.queue = &chan->queue;
+ snprintf(chan->video.name, sizeof(chan->video.name), "%s-%s-%u",
+ dev_name(vi->dev), "output", chan->portno);
+ chan->video.vfl_type = VFL_TYPE_VIDEO;
+ chan->video.vfl_dir = VFL_DIR_RX;
+ chan->video.ioctl_ops = &tegra_channel_ioctl_ops;
+ chan->video.ctrl_handler = &chan->ctrl_handler;
+ chan->video.lock = &chan->video_lock;
+ chan->video.device_caps = V4L2_CAP_VIDEO_CAPTURE |
+ V4L2_CAP_STREAMING |
+ V4L2_CAP_READWRITE;
+ video_set_drvdata(&chan->video, chan);
+
+ chan->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ chan->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ;
+ chan->queue.lock = &chan->video_lock;
+ chan->queue.drv_priv = chan;
+ chan->queue.buf_struct_size = sizeof(struct tegra_channel_buffer);
+ chan->queue.ops = &tegra_channel_queue_qops;
+ chan->queue.mem_ops = &vb2_dma_contig_memops;
+ chan->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ chan->queue.min_buffers_needed = 2;
+ chan->queue.dev = vi->dev;
+ ret = vb2_queue_init(&chan->queue);
+ if (ret < 0) {
+ dev_err(vi->dev, "failed to initialize vb2 queue: %d\n", ret);
+ goto free_v4l2_ctrl_hdl;
+ }
+
+ return 0;
+
+free_v4l2_ctrl_hdl:
+ v4l2_ctrl_handler_free(&chan->ctrl_handler);
+cleanup_media:
+ media_entity_cleanup(&chan->video.entity);
+free_mw_ack_syncpt:
+ host1x_syncpt_free(chan->mw_ack_sp);
+free_fs_syncpt:
+ host1x_syncpt_free(chan->frame_start_sp);
+ return ret;
+}
+
+static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi)
+{
+ struct tegra_vi_channel *chan;
+ unsigned int port_num;
+ unsigned int nchannels = vi->soc->vi_max_channels;
+
+ for (port_num = 0; port_num < nchannels; port_num++) {
+ /*
+ * Do not use devm_kzalloc as memory is freed immediately
+ * when device instance is unbound but application might still
+ * be holding the device node open. Channel memory allocated
+ * with kzalloc is freed during video device release callback.
+ */
+ chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ chan->vi = vi;
+ chan->portno = port_num;
+ list_add_tail(&chan->list, &vi->vi_chans);
+ }
+
+ return 0;
+}
+
+static int tegra_vi_channels_init(struct tegra_vi *vi)
+{
+ struct tegra_vi_channel *chan;
+ int ret;
+
+ list_for_each_entry(chan, &vi->vi_chans, list) {
+ ret = tegra_channel_init(chan);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to initialize channel-%d: %d\n",
+ chan->portno, ret);
+ goto cleanup;
+ }
+ }
+
+ return 0;
+
+cleanup:
+ list_for_each_entry_continue_reverse(chan, &vi->vi_chans, list)
+ tegra_channel_cleanup(chan);
+
+ return ret;
+}
+
+void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid)
+{
+ struct tegra_vi *vi = vid->vi;
+ struct tegra_csi *csi = vid->csi;
+ struct tegra_csi_channel *csi_chan;
+ struct tegra_vi_channel *chan;
+
+ list_for_each_entry(chan, &vi->vi_chans, list) {
+ video_unregister_device(&chan->video);
+ mutex_lock(&chan->video_lock);
+ vb2_queue_release(&chan->queue);
+ mutex_unlock(&chan->video_lock);
+ }
+
+ list_for_each_entry(csi_chan, &csi->csi_chans, list)
+ v4l2_device_unregister_subdev(&csi_chan->subdev);
+}
+
+int tegra_v4l2_nodes_setup_tpg(struct tegra_video_device *vid)
+{
+ struct tegra_vi *vi = vid->vi;
+ struct tegra_csi *csi = vid->csi;
+ struct tegra_vi_channel *vi_chan;
+ struct tegra_csi_channel *csi_chan;
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ int ret;
+
+ if (!vi || !csi)
+ return -ENODEV;
+
+ csi_chan = list_first_entry(&csi->csi_chans,
+ struct tegra_csi_channel, list);
+
+ list_for_each_entry(vi_chan, &vi->vi_chans, list) {
+ struct media_entity *source = &csi_chan->subdev.entity;
+ struct media_entity *sink = &vi_chan->video.entity;
+ struct media_pad *source_pad = csi_chan->pads;
+ struct media_pad *sink_pad = &vi_chan->pad;
+
+ ret = v4l2_device_register_subdev(&vid->v4l2_dev,
+ &csi_chan->subdev);
+ if (ret) {
+ dev_err(vi->dev,
+ "failed to register subdev: %d\n", ret);
+ goto cleanup;
+ }
+
+ ret = video_register_device(&vi_chan->video,
+ VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to register video device: %d\n", ret);
+ goto cleanup;
+ }
+
+ dev_dbg(vi->dev, "creating %s:%u -> %s:%u link\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index);
+
+ ret = media_create_pad_link(source, source_pad->index,
+ sink, sink_pad->index,
+ link_flags);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to create %s:%u -> %s:%u link: %d\n",
+ source->name, source_pad->index,
+ sink->name, sink_pad->index, ret);
+ goto cleanup;
+ }
+
+ ret = tegra_channel_setup_ctrl_handler(vi_chan);
+ if (ret < 0)
+ goto cleanup;
+
+ v4l2_set_subdev_hostdata(&csi_chan->subdev, vi_chan);
+ vi_tpg_fmts_bitmap_init(vi_chan);
+ csi_chan = list_next_entry(csi_chan, list);
+ }
+
+ return 0;
+
+cleanup:
+ tegra_v4l2_nodes_cleanup_tpg(vid);
+ return ret;
+}
+
+static int __maybe_unused vi_runtime_resume(struct device *dev)
+{
+ struct tegra_vi *vi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_enable(vi->vdd);
+ if (ret) {
+ dev_err(dev, "failed to enable VDD supply: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_rate(vi->clk, vi->soc->vi_max_clk_hz);
+ if (ret) {
+ dev_err(dev, "failed to set vi clock rate: %d\n", ret);
+ goto disable_vdd;
+ }
+
+ ret = clk_prepare_enable(vi->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable vi clock: %d\n", ret);
+ goto disable_vdd;
+ }
+
+ return 0;
+
+disable_vdd:
+ regulator_disable(vi->vdd);
+ return ret;
+}
+
+static int __maybe_unused vi_runtime_suspend(struct device *dev)
+{
+ struct tegra_vi *vi = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(vi->clk);
+
+ regulator_disable(vi->vdd);
+
+ return 0;
+}
+
+static int tegra_vi_init(struct host1x_client *client)
+{
+ struct tegra_video_device *vid = dev_get_drvdata(client->host);
+ struct tegra_vi *vi = host1x_client_to_vi(client);
+ struct tegra_vi_channel *chan, *tmp;
+ int ret;
+
+ vid->media_dev.hw_revision = vi->soc->hw_revision;
+ snprintf(vid->media_dev.bus_info, sizeof(vid->media_dev.bus_info),
+ "platform:%s", dev_name(vi->dev));
+
+ INIT_LIST_HEAD(&vi->vi_chans);
+
+ ret = tegra_vi_tpg_channels_alloc(vi);
+ if (ret < 0) {
+ dev_err(vi->dev, "failed to allocate tpg channels: %d\n", ret);
+ goto free_chans;
+ }
+
+ ret = tegra_vi_channels_init(vi);
+ if (ret < 0)
+ goto free_chans;
+
+ vid->vi = vi;
+
+ return 0;
+
+free_chans:
+ list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
+ list_del(&chan->list);
+ kfree(chan);
+ }
+
+ return ret;
+}
+
+static int tegra_vi_exit(struct host1x_client *client)
+{
+ /*
+ * Do not cleanup the channels here as application might still be
+ * holding video device nodes. Channels cleanup will happen during
+ * v4l2_device release callback which gets called after all video
+ * device nodes are released.
+ */
+
+ return 0;
+}
+
+static const struct host1x_client_ops vi_client_ops = {
+ .init = tegra_vi_init,
+ .exit = tegra_vi_exit,
+};
+
+static int tegra_vi_probe(struct platform_device *pdev)
+{
+ struct tegra_vi *vi;
+ int ret;
+
+ vi = devm_kzalloc(&pdev->dev, sizeof(*vi), GFP_KERNEL);
+ if (!vi)
+ return -ENOMEM;
+
+ vi->iomem = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(vi->iomem))
+ return PTR_ERR(vi->iomem);
+
+ vi->soc = of_device_get_match_data(&pdev->dev);
+
+ vi->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(vi->clk)) {
+ ret = PTR_ERR(vi->clk);
+ dev_err(&pdev->dev, "failed to get vi clock: %d\n", ret);
+ return ret;
+ }
+
+ vi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
+ if (IS_ERR(vi->vdd)) {
+ ret = PTR_ERR(vi->vdd);
+ dev_err(&pdev->dev, "failed to get VDD supply: %d\n", ret);
+ return ret;
+ }
+
+ if (!pdev->dev.pm_domain) {
+ ret = -ENOENT;
+ dev_warn(&pdev->dev, "PM domain is not attached: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_of_platform_populate(&pdev->dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to populate vi child device: %d\n", ret);
+ return ret;
+ }
+
+ vi->dev = &pdev->dev;
+ vi->ops = vi->soc->ops;
+ platform_set_drvdata(pdev, vi);
+ pm_runtime_enable(&pdev->dev);
+
+ /* initialize host1x interface */
+ INIT_LIST_HEAD(&vi->client.list);
+ vi->client.ops = &vi_client_ops;
+ vi->client.dev = &pdev->dev;
+
+ ret = host1x_client_register(&vi->client);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "failed to register host1x client: %d\n", ret);
+ goto rpm_disable;
+ }
+
+ return 0;
+
+rpm_disable:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int tegra_vi_remove(struct platform_device *pdev)
+{
+ struct tegra_vi *vi = platform_get_drvdata(pdev);
+ int err;
+
+ err = host1x_client_unregister(&vi->client);
+ if (err < 0) {
+ dev_err(&pdev->dev,
+ "failed to unregister host1x client: %d\n", err);
+ return err;
+ }
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id tegra_vi_of_id_table[] = {
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+ { .compatible = "nvidia,tegra210-vi", .data = &tegra210_vi_soc },
+#endif
+ { }
+};
+MODULE_DEVICE_TABLE(of, tegra_vi_of_id_table);
+
+static const struct dev_pm_ops tegra_vi_pm_ops = {
+ SET_RUNTIME_PM_OPS(vi_runtime_suspend, vi_runtime_resume, NULL)
+};
+
+struct platform_driver tegra_vi_driver = {
+ .driver = {
+ .name = "tegra-vi",
+ .of_match_table = tegra_vi_of_id_table,
+ .pm = &tegra_vi_pm_ops,
+ },
+ .probe = tegra_vi_probe,
+ .remove = tegra_vi_remove,
+};