| // 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/of_graph.h> |
| #include <linux/platform_device.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/slab.h> |
| |
| #include <media/v4l2-dv-timings.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 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 inline struct tegra_vi_graph_entity * |
| to_tegra_vi_graph_entity(struct v4l2_async_subdev *asd) |
| { |
| return container_of(asd, struct tegra_vi_graph_entity, asd); |
| } |
| |
| static int tegra_get_format_idx_by_code(struct tegra_vi *vi, |
| unsigned int code, |
| unsigned int offset) |
| { |
| unsigned int i; |
| |
| for (i = offset; 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_csi_subdev(struct tegra_vi_channel *chan) |
| { |
| struct media_pad *pad; |
| |
| pad = media_entity_remote_pad(&chan->pad); |
| if (!pad) |
| return NULL; |
| |
| return media_entity_to_v4l2_subdev(pad->entity); |
| } |
| |
| struct v4l2_subdev * |
| tegra_channel_get_remote_source_subdev(struct tegra_vi_channel *chan) |
| { |
| struct media_pad *pad; |
| struct v4l2_subdev *subdev; |
| struct media_entity *entity; |
| |
| subdev = tegra_channel_get_remote_csi_subdev(chan); |
| if (!subdev) |
| return NULL; |
| |
| pad = &subdev->entity.pads[0]; |
| while (!(pad->flags & MEDIA_PAD_FL_SOURCE)) { |
| pad = media_entity_remote_pad(pad); |
| if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) |
| break; |
| entity = pad->entity; |
| pad = &entity->pads[0]; |
| subdev = media_entity_to_v4l2_subdev(entity); |
| } |
| |
| return subdev; |
| } |
| |
| static int tegra_channel_enable_stream(struct tegra_vi_channel *chan) |
| { |
| struct v4l2_subdev *csi_subdev, *src_subdev; |
| struct tegra_csi_channel *csi_chan; |
| int ret, err; |
| |
| /* |
| * Tegra CSI receiver can detect the first LP to HS transition. |
| * So, start the CSI stream-on prior to sensor stream-on and |
| * vice-versa for stream-off. |
| */ |
| csi_subdev = tegra_channel_get_remote_csi_subdev(chan); |
| ret = v4l2_subdev_call(csi_subdev, video, s_stream, true); |
| if (ret < 0 && ret != -ENOIOCTLCMD) |
| return ret; |
| |
| if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| return 0; |
| |
| csi_chan = v4l2_get_subdevdata(csi_subdev); |
| /* |
| * TRM has incorrectly documented to wait for done status from |
| * calibration logic after CSI interface power on. |
| * As per the design, calibration results are latched and applied |
| * to the pads only when the link is in LP11 state which will happen |
| * during the sensor stream-on. |
| * CSI subdev stream-on triggers start of MIPI pads calibration. |
| * Wait for calibration to finish here after sensor subdev stream-on. |
| */ |
| src_subdev = tegra_channel_get_remote_source_subdev(chan); |
| ret = v4l2_subdev_call(src_subdev, video, s_stream, true); |
| err = tegra_mipi_finish_calibration(csi_chan->mipi); |
| |
| if (ret < 0 && ret != -ENOIOCTLCMD) |
| goto err_disable_csi_stream; |
| |
| if (err < 0) |
| dev_warn(csi_chan->csi->dev, |
| "MIPI calibration failed: %d\n", err); |
| |
| return 0; |
| |
| err_disable_csi_stream: |
| v4l2_subdev_call(csi_subdev, video, s_stream, false); |
| return ret; |
| } |
| |
| static int tegra_channel_disable_stream(struct tegra_vi_channel *chan) |
| { |
| struct v4l2_subdev *subdev; |
| int ret; |
| |
| /* |
| * Stream-off subdevices in reverse order to stream-on. |
| * Remote source subdev in TPG mode is same as CSI subdev. |
| */ |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| ret = v4l2_subdev_call(subdev, video, s_stream, false); |
| if (ret < 0 && ret != -ENOIOCTLCMD) |
| return ret; |
| |
| if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| return 0; |
| |
| subdev = tegra_channel_get_remote_csi_subdev(chan); |
| ret = v4l2_subdev_call(subdev, video, s_stream, false); |
| if (ret < 0 && ret != -ENOIOCTLCMD) |
| return ret; |
| |
| return 0; |
| } |
| |
| int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on) |
| { |
| int ret; |
| |
| if (on) |
| ret = tegra_channel_enable_stream(chan); |
| else |
| ret = tegra_channel_disable_stream(chan); |
| |
| return ret; |
| } |
| |
| 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_resume_and_get(chan->vi->dev); |
| if (ret < 0) { |
| dev_err(chan->vi->dev, "failed to get runtime PM: %d\n", ret); |
| 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_source_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_source_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_source_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_source_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 (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| fmts_bitmap = chan->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 min_bpl; |
| unsigned int max_bpl; |
| unsigned int bpl; |
| |
| /* |
| * The transfer alignment requirements are expressed in bytes. |
| * Clamp the requested width and height to the limits. |
| */ |
| pix->width = clamp(pix->width, TEGRA_MIN_WIDTH, TEGRA_MAX_WIDTH); |
| 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; |
| if (pix->pixelformat == V4L2_PIX_FMT_NV16) |
| pix->sizeimage *= 2; |
| } |
| |
| 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_state *sd_state; |
| struct v4l2_subdev_frame_size_enum fse = { |
| .which = V4L2_SUBDEV_FORMAT_TRY, |
| }; |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = V4L2_SEL_TGT_CROP_BOUNDS, |
| }; |
| int ret; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!subdev) |
| return -ENODEV; |
| |
| sd_state = v4l2_subdev_alloc_state(subdev); |
| if (IS_ERR(sd_state)) |
| return PTR_ERR(sd_state); |
| /* |
| * 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); |
| |
| /* |
| * Attempt to obtain the format size from subdev. |
| * If not available, try to get crop boundary from subdev. |
| */ |
| fse.code = fmtinfo->code; |
| ret = v4l2_subdev_call(subdev, pad, enum_frame_size, sd_state, &fse); |
| if (ret) { |
| if (!v4l2_subdev_has_op(subdev, pad, get_selection)) { |
| sd_state->pads->try_crop.width = 0; |
| sd_state->pads->try_crop.height = 0; |
| } else { |
| ret = v4l2_subdev_call(subdev, pad, get_selection, |
| NULL, &sdsel); |
| if (ret) |
| return -EINVAL; |
| |
| sd_state->pads->try_crop.width = sdsel.r.width; |
| sd_state->pads->try_crop.height = sdsel.r.height; |
| } |
| } else { |
| sd_state->pads->try_crop.width = fse.max_width; |
| sd_state->pads->try_crop.height = fse.max_height; |
| } |
| |
| ret = v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &fmt); |
| if (ret < 0) |
| return ret; |
| |
| v4l2_fill_pix_format(pix, &fmt.format); |
| tegra_channel_fmt_align(chan, pix, fmtinfo->bpp); |
| |
| v4l2_subdev_free_state(sd_state); |
| |
| 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 void tegra_channel_update_gangports(struct tegra_vi_channel *chan) |
| { |
| if (chan->format.width <= 1920) |
| chan->numgangports = 1; |
| else |
| chan->numgangports = chan->totalports; |
| } |
| |
| 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_source_subdev(chan); |
| ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); |
| if (ret < 0) |
| return ret; |
| |
| v4l2_fill_pix_format(pix, &fmt.format); |
| tegra_channel_fmt_align(chan, pix, fmtinfo->bpp); |
| |
| chan->format = *pix; |
| chan->fmtinfo = fmtinfo; |
| tegra_channel_update_gangports(chan); |
| |
| return 0; |
| } |
| |
| static int tegra_channel_set_subdev_active_fmt(struct tegra_vi_channel *chan) |
| { |
| int ret, index; |
| struct v4l2_subdev *subdev; |
| struct v4l2_subdev_format fmt = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| }; |
| |
| /* |
| * Initialize channel format to the sub-device active format if there |
| * is corresponding match in the Tegra supported video formats. |
| */ |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); |
| if (ret) |
| return ret; |
| |
| index = tegra_get_format_idx_by_code(chan->vi, fmt.format.code, 0); |
| if (index < 0) |
| return -EINVAL; |
| |
| chan->fmtinfo = &chan->vi->soc->video_formats[index]; |
| v4l2_fill_pix_format(&chan->format, &fmt.format); |
| chan->format.pixelformat = chan->fmtinfo->fourcc; |
| chan->format.bytesperline = chan->format.width * chan->fmtinfo->bpp; |
| chan->format.sizeimage = chan->format.bytesperline * |
| chan->format.height; |
| tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp); |
| tegra_channel_update_gangports(chan); |
| |
| return 0; |
| } |
| |
| static int |
| tegra_channel_subscribe_event(struct v4l2_fh *fh, |
| const struct v4l2_event_subscription *sub) |
| { |
| switch (sub->type) { |
| case V4L2_EVENT_SOURCE_CHANGE: |
| return v4l2_event_subscribe(fh, sub, 4, NULL); |
| } |
| |
| return v4l2_ctrl_subscribe_event(fh, sub); |
| } |
| |
| static int tegra_channel_g_selection(struct file *file, void *priv, |
| struct v4l2_selection *sel) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| struct v4l2_subdev_format fmt = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| }; |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = sel->target, |
| }; |
| int ret; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, get_selection)) |
| return -ENOTTY; |
| |
| if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| /* |
| * Try the get selection operation and fallback to get format if not |
| * implemented. |
| */ |
| ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel); |
| if (!ret) |
| sel->r = sdsel.r; |
| if (ret != -ENOIOCTLCMD) |
| return ret; |
| |
| ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); |
| if (ret < 0) |
| return ret; |
| |
| sel->r.left = 0; |
| sel->r.top = 0; |
| sel->r.width = fmt.format.width; |
| sel->r.height = fmt.format.height; |
| |
| return 0; |
| } |
| |
| static int tegra_channel_s_selection(struct file *file, void *fh, |
| struct v4l2_selection *sel) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| int ret; |
| struct v4l2_subdev_selection sdsel = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| .target = sel->target, |
| .flags = sel->flags, |
| .r = sel->r, |
| }; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, set_selection)) |
| return -ENOTTY; |
| |
| if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| |
| if (vb2_is_busy(&chan->queue)) |
| return -EBUSY; |
| |
| ret = v4l2_subdev_call(subdev, pad, set_selection, NULL, &sdsel); |
| if (!ret) { |
| sel->r = sdsel.r; |
| /* |
| * Subdev active format resolution may have changed during |
| * set selection operation. So, update channel format to |
| * the sub-device active format. |
| */ |
| return tegra_channel_set_subdev_active_fmt(chan); |
| } |
| |
| return ret; |
| } |
| |
| static int tegra_channel_g_edid(struct file *file, void *fh, |
| struct v4l2_edid *edid) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, get_edid)) |
| return -ENOTTY; |
| |
| return v4l2_subdev_call(subdev, pad, get_edid, edid); |
| } |
| |
| static int tegra_channel_s_edid(struct file *file, void *fh, |
| struct v4l2_edid *edid) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, set_edid)) |
| return -ENOTTY; |
| |
| return v4l2_subdev_call(subdev, pad, set_edid, edid); |
| } |
| |
| static int tegra_channel_g_dv_timings(struct file *file, void *fh, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, video, g_dv_timings)) |
| return -ENOTTY; |
| |
| return v4l2_device_call_until_err(chan->video.v4l2_dev, 0, |
| video, g_dv_timings, timings); |
| } |
| |
| static int tegra_channel_s_dv_timings(struct file *file, void *fh, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| struct v4l2_bt_timings *bt = &timings->bt; |
| struct v4l2_dv_timings curr_timings; |
| int ret; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, video, s_dv_timings)) |
| return -ENOTTY; |
| |
| ret = tegra_channel_g_dv_timings(file, fh, &curr_timings); |
| if (ret) |
| return ret; |
| |
| if (v4l2_match_dv_timings(timings, &curr_timings, 0, false)) |
| return 0; |
| |
| if (vb2_is_busy(&chan->queue)) |
| return -EBUSY; |
| |
| ret = v4l2_device_call_until_err(chan->video.v4l2_dev, 0, |
| video, s_dv_timings, timings); |
| if (ret) |
| return ret; |
| |
| chan->format.width = bt->width; |
| chan->format.height = bt->height; |
| chan->format.bytesperline = bt->width * chan->fmtinfo->bpp; |
| chan->format.sizeimage = chan->format.bytesperline * bt->height; |
| tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp); |
| tegra_channel_update_gangports(chan); |
| |
| return 0; |
| } |
| |
| static int tegra_channel_query_dv_timings(struct file *file, void *fh, |
| struct v4l2_dv_timings *timings) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, video, query_dv_timings)) |
| return -ENOTTY; |
| |
| return v4l2_device_call_until_err(chan->video.v4l2_dev, 0, |
| video, query_dv_timings, timings); |
| } |
| |
| static int tegra_channel_enum_dv_timings(struct file *file, void *fh, |
| struct v4l2_enum_dv_timings *timings) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, enum_dv_timings)) |
| return -ENOTTY; |
| |
| return v4l2_subdev_call(subdev, pad, enum_dv_timings, timings); |
| } |
| |
| static int tegra_channel_dv_timings_cap(struct file *file, void *fh, |
| struct v4l2_dv_timings_cap *cap) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!v4l2_subdev_has_op(subdev, pad, dv_timings_cap)) |
| return -ENOTTY; |
| |
| return v4l2_subdev_call(subdev, pad, dv_timings_cap, cap); |
| } |
| |
| static int tegra_channel_log_status(struct file *file, void *fh) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| |
| v4l2_device_call_all(chan->video.v4l2_dev, 0, core, log_status); |
| |
| return 0; |
| } |
| |
| static int tegra_channel_enum_input(struct file *file, void *fh, |
| struct v4l2_input *inp) |
| { |
| struct tegra_vi_channel *chan = video_drvdata(file); |
| struct v4l2_subdev *subdev; |
| |
| if (inp->index) |
| return -EINVAL; |
| |
| inp->type = V4L2_INPUT_TYPE_CAMERA; |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| strscpy(inp->name, subdev->name, sizeof(inp->name)); |
| if (v4l2_subdev_has_op(subdev, pad, dv_timings_cap)) |
| inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; |
| |
| 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 = tegra_channel_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| .vidioc_g_selection = tegra_channel_g_selection, |
| .vidioc_s_selection = tegra_channel_s_selection, |
| .vidioc_g_edid = tegra_channel_g_edid, |
| .vidioc_s_edid = tegra_channel_s_edid, |
| .vidioc_g_dv_timings = tegra_channel_g_dv_timings, |
| .vidioc_s_dv_timings = tegra_channel_s_dv_timings, |
| .vidioc_query_dv_timings = tegra_channel_query_dv_timings, |
| .vidioc_enum_dv_timings = tegra_channel_enum_dv_timings, |
| .vidioc_dv_timings_cap = tegra_channel_dv_timings_cap, |
| .vidioc_log_status = tegra_channel_log_status, |
| }; |
| |
| /* |
| * 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; |
| case V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY: |
| chan->syncpt_timeout_retry = ctrl->val; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct v4l2_ctrl_ops vi_ctrl_ops = { |
| .s_ctrl = vi_s_ctrl, |
| }; |
| |
| #if IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG) |
| static const char *const vi_pattern_strings[] = { |
| "Black/White Direct Mode", |
| "Color Patch Mode", |
| }; |
| #else |
| static const struct v4l2_ctrl_config syncpt_timeout_ctrl = { |
| .ops = &vi_ctrl_ops, |
| .id = V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY, |
| .name = "Syncpt timeout retry", |
| .type = V4L2_CTRL_TYPE_INTEGER, |
| .min = 1, |
| .max = 10000, |
| .step = 1, |
| .def = 5, |
| }; |
| #endif |
| |
| static int tegra_channel_setup_ctrl_handler(struct tegra_vi_channel *chan) |
| { |
| int ret; |
| |
| #if IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG) |
| /* 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; |
| } |
| #else |
| struct v4l2_subdev *subdev; |
| |
| /* custom control */ |
| v4l2_ctrl_new_custom(&chan->ctrl_handler, &syncpt_timeout_ctrl, NULL); |
| if (chan->ctrl_handler.error) { |
| dev_err(chan->vi->dev, "failed to add %s ctrl handler: %d\n", |
| syncpt_timeout_ctrl.name, |
| chan->ctrl_handler.error); |
| v4l2_ctrl_handler_free(&chan->ctrl_handler); |
| return chan->ctrl_handler.error; |
| } |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| if (!subdev) |
| return -ENODEV; |
| |
| ret = v4l2_ctrl_add_handler(&chan->ctrl_handler, subdev->ctrl_handler, |
| NULL, true); |
| if (ret < 0) { |
| dev_err(chan->vi->dev, |
| "failed to add subdev %s ctrl handler: %d\n", |
| subdev->name, ret); |
| v4l2_ctrl_handler_free(&chan->ctrl_handler); |
| return ret; |
| } |
| #endif |
| |
| /* 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, 0); |
| bitmap_set(chan->tpg_fmts_bitmap, index, 1); |
| |
| index = tegra_get_format_idx_by_code(chan->vi, |
| MEDIA_BUS_FMT_RGB888_1X32_PADHI, |
| 0); |
| bitmap_set(chan->tpg_fmts_bitmap, index, 1); |
| } |
| |
| static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan) |
| { |
| int index, ret, match_code = 0; |
| struct v4l2_subdev *subdev; |
| struct v4l2_subdev_mbus_code_enum code = { |
| .which = V4L2_SUBDEV_FORMAT_ACTIVE, |
| }; |
| |
| bitmap_zero(chan->fmts_bitmap, MAX_FORMAT_NUM); |
| |
| /* |
| * Set the bitmap bits based on all the matched formats between the |
| * available media bus formats of sub-device and the pre-defined Tegra |
| * supported video formats. |
| */ |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| while (1) { |
| ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, |
| NULL, &code); |
| if (ret < 0) |
| break; |
| |
| index = tegra_get_format_idx_by_code(chan->vi, code.code, 0); |
| while (index >= 0) { |
| bitmap_set(chan->fmts_bitmap, index, 1); |
| if (!match_code) |
| match_code = code.code; |
| /* look for other formats with same mbus code */ |
| index = tegra_get_format_idx_by_code(chan->vi, |
| code.code, |
| index + 1); |
| } |
| |
| code.index++; |
| } |
| |
| /* |
| * Set the bitmap bit corresponding to default tegra video format if |
| * there are no matched formats. |
| */ |
| if (!match_code) { |
| match_code = tegra_default_format.code; |
| index = tegra_get_format_idx_by_code(chan->vi, match_code, 0); |
| if (WARN_ON(index < 0)) |
| return -EINVAL; |
| |
| bitmap_set(chan->fmts_bitmap, index, 1); |
| } |
| |
| /* initialize channel format to the sub-device active format */ |
| tegra_channel_set_subdev_active_fmt(chan); |
| |
| return 0; |
| } |
| |
| static void tegra_channel_host1x_syncpts_free(struct tegra_vi_channel *chan) |
| { |
| int i; |
| |
| for (i = 0; i < chan->numgangports; i++) { |
| host1x_syncpt_put(chan->mw_ack_sp[i]); |
| host1x_syncpt_put(chan->frame_start_sp[i]); |
| } |
| } |
| |
| static void tegra_channel_cleanup(struct tegra_vi_channel *chan) |
| { |
| v4l2_ctrl_handler_free(&chan->ctrl_handler); |
| media_entity_cleanup(&chan->video.entity); |
| tegra_channel_host1x_syncpts_free(chan); |
| 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_host1x_syncpt_init(struct tegra_vi_channel *chan) |
| { |
| struct tegra_vi *vi = chan->vi; |
| unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED; |
| struct host1x_syncpt *fs_sp; |
| struct host1x_syncpt *mw_sp; |
| int ret, i; |
| |
| for (i = 0; i < chan->numgangports; i++) { |
| fs_sp = host1x_syncpt_request(&vi->client, flags); |
| if (!fs_sp) { |
| dev_err(vi->dev, "failed to request frame start syncpoint\n"); |
| ret = -ENOMEM; |
| goto free_syncpts; |
| } |
| |
| mw_sp = host1x_syncpt_request(&vi->client, flags); |
| if (!mw_sp) { |
| dev_err(vi->dev, "failed to request memory ack syncpoint\n"); |
| host1x_syncpt_put(fs_sp); |
| ret = -ENOMEM; |
| goto free_syncpts; |
| } |
| |
| chan->frame_start_sp[i] = fs_sp; |
| chan->mw_ack_sp[i] = mw_sp; |
| spin_lock_init(&chan->sp_incr_lock[i]); |
| } |
| |
| return 0; |
| |
| free_syncpts: |
| tegra_channel_host1x_syncpts_free(chan); |
| return ret; |
| } |
| |
| 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); |
| 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); |
| 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); |
| |
| ret = tegra_channel_host1x_syncpt_init(chan); |
| if (ret) |
| return ret; |
| |
| /* 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_syncpts; |
| } |
| |
| 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->portnos[0]); |
| 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; |
| } |
| |
| if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| v4l2_async_nf_init(&chan->notifier); |
| |
| return 0; |
| |
| free_v4l2_ctrl_hdl: |
| v4l2_ctrl_handler_free(&chan->ctrl_handler); |
| cleanup_media: |
| media_entity_cleanup(&chan->video.entity); |
| free_syncpts: |
| tegra_channel_host1x_syncpts_free(chan); |
| return ret; |
| } |
| |
| static int tegra_vi_channel_alloc(struct tegra_vi *vi, unsigned int port_num, |
| struct device_node *node, unsigned int lanes) |
| { |
| struct tegra_vi_channel *chan; |
| unsigned int i; |
| |
| /* |
| * 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->portnos[0] = port_num; |
| /* |
| * For data lanes more than maximum csi lanes per brick, multiple of |
| * x4 ports are used simultaneously for capture. |
| */ |
| if (lanes <= CSI_LANES_PER_BRICK) |
| chan->totalports = 1; |
| else |
| chan->totalports = lanes / CSI_LANES_PER_BRICK; |
| chan->numgangports = chan->totalports; |
| |
| for (i = 1; i < chan->totalports; i++) |
| chan->portnos[i] = chan->portnos[0] + i * CSI_PORTS_PER_BRICK; |
| |
| chan->of_node = node; |
| list_add_tail(&chan->list, &vi->vi_chans); |
| |
| return 0; |
| } |
| |
| static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi) |
| { |
| unsigned int port_num; |
| unsigned int nchannels = vi->soc->vi_max_channels; |
| int ret; |
| |
| for (port_num = 0; port_num < nchannels; port_num++) { |
| ret = tegra_vi_channel_alloc(vi, port_num, |
| vi->dev->of_node, 2); |
| if (ret < 0) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int tegra_vi_channels_alloc(struct tegra_vi *vi) |
| { |
| struct device_node *node = vi->dev->of_node; |
| struct device_node *ep = NULL; |
| struct device_node *ports; |
| struct device_node *port; |
| unsigned int port_num; |
| struct device_node *parent; |
| struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; |
| unsigned int lanes; |
| int ret = 0; |
| |
| ports = of_get_child_by_name(node, "ports"); |
| if (!ports) |
| return -ENODEV; |
| |
| for_each_child_of_node(ports, port) { |
| if (!of_node_name_eq(port, "port")) |
| continue; |
| |
| ret = of_property_read_u32(port, "reg", &port_num); |
| if (ret < 0) |
| continue; |
| |
| if (port_num > vi->soc->vi_max_channels) { |
| dev_err(vi->dev, "invalid port num %d for %pOF\n", |
| port_num, port); |
| ret = -EINVAL; |
| of_node_put(port); |
| goto cleanup; |
| } |
| |
| ep = of_get_child_by_name(port, "endpoint"); |
| if (!ep) |
| continue; |
| |
| parent = of_graph_get_remote_port_parent(ep); |
| of_node_put(ep); |
| if (!parent) |
| continue; |
| |
| ep = of_graph_get_endpoint_by_regs(parent, 0, 0); |
| of_node_put(parent); |
| ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), |
| &v4l2_ep); |
| of_node_put(ep); |
| if (ret) |
| continue; |
| |
| lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; |
| ret = tegra_vi_channel_alloc(vi, port_num, port, lanes); |
| if (ret < 0) { |
| of_node_put(port); |
| goto cleanup; |
| } |
| } |
| |
| cleanup: |
| of_node_put(ports); |
| return ret; |
| } |
| |
| 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->portnos[0], 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) |
| vb2_video_unregister_device(&chan->video); |
| |
| 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; |
| } |
| |
| /* |
| * Graph Management |
| */ |
| static struct tegra_vi_graph_entity * |
| tegra_vi_graph_find_entity(struct tegra_vi_channel *chan, |
| const struct fwnode_handle *fwnode) |
| { |
| struct tegra_vi_graph_entity *entity; |
| struct v4l2_async_subdev *asd; |
| |
| list_for_each_entry(asd, &chan->notifier.asd_list, asd_list) { |
| entity = to_tegra_vi_graph_entity(asd); |
| if (entity->asd.match.fwnode == fwnode) |
| return entity; |
| } |
| |
| return NULL; |
| } |
| |
| static int tegra_vi_graph_build(struct tegra_vi_channel *chan, |
| struct tegra_vi_graph_entity *entity) |
| { |
| struct tegra_vi *vi = chan->vi; |
| struct tegra_vi_graph_entity *ent; |
| struct fwnode_handle *ep = NULL; |
| struct v4l2_fwnode_link link; |
| struct media_entity *local = entity->entity; |
| struct media_entity *remote; |
| struct media_pad *local_pad; |
| struct media_pad *remote_pad; |
| u32 link_flags = MEDIA_LNK_FL_ENABLED; |
| int ret = 0; |
| |
| dev_dbg(vi->dev, "creating links for entity %s\n", local->name); |
| |
| while (1) { |
| ep = fwnode_graph_get_next_endpoint(entity->asd.match.fwnode, |
| ep); |
| if (!ep) |
| break; |
| |
| ret = v4l2_fwnode_parse_link(ep, &link); |
| if (ret < 0) { |
| dev_err(vi->dev, "failed to parse link for %pOF: %d\n", |
| to_of_node(ep), ret); |
| continue; |
| } |
| |
| if (link.local_port >= local->num_pads) { |
| dev_err(vi->dev, "invalid port number %u on %pOF\n", |
| link.local_port, to_of_node(link.local_node)); |
| v4l2_fwnode_put_link(&link); |
| ret = -EINVAL; |
| break; |
| } |
| |
| local_pad = &local->pads[link.local_port]; |
| /* Remote node is vi node. So use channel video entity and pad |
| * as remote/sink. |
| */ |
| if (link.remote_node == of_fwnode_handle(vi->dev->of_node)) { |
| remote = &chan->video.entity; |
| remote_pad = &chan->pad; |
| goto create_link; |
| } |
| |
| /* |
| * Skip sink ports, they will be processed from the other end |
| * of the link. |
| */ |
| if (local_pad->flags & MEDIA_PAD_FL_SINK) { |
| dev_dbg(vi->dev, "skipping sink port %pOF:%u\n", |
| to_of_node(link.local_node), link.local_port); |
| v4l2_fwnode_put_link(&link); |
| continue; |
| } |
| |
| /* find the remote entity from notifier list */ |
| ent = tegra_vi_graph_find_entity(chan, link.remote_node); |
| if (!ent) { |
| dev_err(vi->dev, "no entity found for %pOF\n", |
| to_of_node(link.remote_node)); |
| v4l2_fwnode_put_link(&link); |
| ret = -ENODEV; |
| break; |
| } |
| |
| remote = ent->entity; |
| if (link.remote_port >= remote->num_pads) { |
| dev_err(vi->dev, "invalid port number %u on %pOF\n", |
| link.remote_port, |
| to_of_node(link.remote_node)); |
| v4l2_fwnode_put_link(&link); |
| ret = -EINVAL; |
| break; |
| } |
| |
| remote_pad = &remote->pads[link.remote_port]; |
| |
| create_link: |
| dev_dbg(vi->dev, "creating %s:%u -> %s:%u link\n", |
| local->name, local_pad->index, |
| remote->name, remote_pad->index); |
| |
| ret = media_create_pad_link(local, local_pad->index, |
| remote, remote_pad->index, |
| link_flags); |
| v4l2_fwnode_put_link(&link); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to create %s:%u -> %s:%u link: %d\n", |
| local->name, local_pad->index, |
| remote->name, remote_pad->index, ret); |
| break; |
| } |
| } |
| |
| fwnode_handle_put(ep); |
| return ret; |
| } |
| |
| static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier) |
| { |
| struct tegra_vi_graph_entity *entity; |
| struct v4l2_async_subdev *asd; |
| struct v4l2_subdev *subdev; |
| struct tegra_vi_channel *chan; |
| struct tegra_vi *vi; |
| int ret; |
| |
| chan = container_of(notifier, struct tegra_vi_channel, notifier); |
| vi = chan->vi; |
| |
| dev_dbg(vi->dev, "notify complete, all subdevs registered\n"); |
| |
| /* |
| * Video device node should be created at the end of all the device |
| * related initialization/setup. |
| * Current video_register_device() does both initialize and register |
| * video device in same API. |
| * |
| * TODO: Update v4l2-dev driver to split initialize and register into |
| * separate APIs and then update Tegra video driver to do video device |
| * initialize followed by all video device related setup and then |
| * register the video device. |
| */ |
| ret = video_register_device(&chan->video, VFL_TYPE_VIDEO, -1); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to register video device: %d\n", ret); |
| goto unregister_video; |
| } |
| |
| /* create links between the entities */ |
| list_for_each_entry(asd, &chan->notifier.asd_list, asd_list) { |
| entity = to_tegra_vi_graph_entity(asd); |
| ret = tegra_vi_graph_build(chan, entity); |
| if (ret < 0) |
| goto unregister_video; |
| } |
| |
| ret = tegra_channel_setup_ctrl_handler(chan); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to setup channel controls: %d\n", ret); |
| goto unregister_video; |
| } |
| |
| ret = vi_fmts_bitmap_init(chan); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to initialize formats bitmap: %d\n", ret); |
| goto unregister_video; |
| } |
| |
| subdev = tegra_channel_get_remote_csi_subdev(chan); |
| if (!subdev) { |
| ret = -ENODEV; |
| dev_err(vi->dev, |
| "failed to get remote csi subdev: %d\n", ret); |
| goto unregister_video; |
| } |
| |
| v4l2_set_subdev_hostdata(subdev, chan); |
| |
| subdev = tegra_channel_get_remote_source_subdev(chan); |
| v4l2_set_subdev_hostdata(subdev, chan); |
| |
| return 0; |
| |
| unregister_video: |
| vb2_video_unregister_device(&chan->video); |
| return ret; |
| } |
| |
| static int tegra_vi_graph_notify_bound(struct v4l2_async_notifier *notifier, |
| struct v4l2_subdev *subdev, |
| struct v4l2_async_subdev *asd) |
| { |
| struct tegra_vi_graph_entity *entity; |
| struct tegra_vi *vi; |
| struct tegra_vi_channel *chan; |
| |
| chan = container_of(notifier, struct tegra_vi_channel, notifier); |
| vi = chan->vi; |
| |
| /* |
| * Locate the entity corresponding to the bound subdev and store the |
| * subdev pointer. |
| */ |
| entity = tegra_vi_graph_find_entity(chan, subdev->fwnode); |
| if (!entity) { |
| dev_err(vi->dev, "no entity for subdev %s\n", subdev->name); |
| return -EINVAL; |
| } |
| |
| if (entity->subdev) { |
| dev_err(vi->dev, "duplicate subdev for node %pOF\n", |
| to_of_node(entity->asd.match.fwnode)); |
| return -EINVAL; |
| } |
| |
| dev_dbg(vi->dev, "subdev %s bound\n", subdev->name); |
| entity->entity = &subdev->entity; |
| entity->subdev = subdev; |
| |
| return 0; |
| } |
| |
| static const struct v4l2_async_notifier_operations tegra_vi_async_ops = { |
| .bound = tegra_vi_graph_notify_bound, |
| .complete = tegra_vi_graph_notify_complete, |
| }; |
| |
| static int tegra_vi_graph_parse_one(struct tegra_vi_channel *chan, |
| struct fwnode_handle *fwnode) |
| { |
| struct tegra_vi *vi = chan->vi; |
| struct fwnode_handle *ep = NULL; |
| struct fwnode_handle *remote = NULL; |
| struct tegra_vi_graph_entity *tvge; |
| struct device_node *node = NULL; |
| int ret; |
| |
| dev_dbg(vi->dev, "parsing node %pOF\n", to_of_node(fwnode)); |
| |
| /* parse all the remote entities and put them into the list */ |
| for_each_endpoint_of_node(to_of_node(fwnode), node) { |
| ep = of_fwnode_handle(node); |
| remote = fwnode_graph_get_remote_port_parent(ep); |
| if (!remote) { |
| dev_err(vi->dev, |
| "remote device at %pOF not found\n", node); |
| ret = -EINVAL; |
| goto cleanup; |
| } |
| |
| /* skip entities that are already processed */ |
| if (remote == dev_fwnode(vi->dev) || |
| tegra_vi_graph_find_entity(chan, remote)) { |
| fwnode_handle_put(remote); |
| continue; |
| } |
| |
| tvge = v4l2_async_nf_add_fwnode(&chan->notifier, remote, |
| struct tegra_vi_graph_entity); |
| if (IS_ERR(tvge)) { |
| ret = PTR_ERR(tvge); |
| dev_err(vi->dev, |
| "failed to add subdev to notifier: %d\n", ret); |
| fwnode_handle_put(remote); |
| goto cleanup; |
| } |
| |
| ret = tegra_vi_graph_parse_one(chan, remote); |
| if (ret < 0) { |
| fwnode_handle_put(remote); |
| goto cleanup; |
| } |
| |
| fwnode_handle_put(remote); |
| } |
| |
| return 0; |
| |
| cleanup: |
| dev_err(vi->dev, "failed parsing the graph: %d\n", ret); |
| v4l2_async_nf_cleanup(&chan->notifier); |
| of_node_put(node); |
| return ret; |
| } |
| |
| static int tegra_vi_graph_init(struct tegra_vi *vi) |
| { |
| struct tegra_video_device *vid = dev_get_drvdata(vi->client.host); |
| struct tegra_vi_channel *chan; |
| struct fwnode_handle *fwnode = dev_fwnode(vi->dev); |
| int ret; |
| struct fwnode_handle *remote = NULL; |
| |
| /* |
| * Walk the links to parse the full graph. Each channel will have |
| * one endpoint of the composite node. Start by parsing the |
| * composite node and parse the remote entities in turn. |
| * Each channel will register v4l2 async notifier to make the graph |
| * independent between the channels so we can the current channel |
| * in case of something wrong during graph parsing and continue with |
| * next channels. |
| */ |
| list_for_each_entry(chan, &vi->vi_chans, list) { |
| remote = fwnode_graph_get_remote_node(fwnode, chan->portnos[0], |
| 0); |
| if (!remote) |
| continue; |
| |
| ret = tegra_vi_graph_parse_one(chan, remote); |
| fwnode_handle_put(remote); |
| if (ret < 0 || list_empty(&chan->notifier.asd_list)) |
| continue; |
| |
| chan->notifier.ops = &tegra_vi_async_ops; |
| ret = v4l2_async_nf_register(&vid->v4l2_dev, &chan->notifier); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to register channel %d notifier: %d\n", |
| chan->portnos[0], ret); |
| v4l2_async_nf_cleanup(&chan->notifier); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void tegra_vi_graph_cleanup(struct tegra_vi *vi) |
| { |
| struct tegra_vi_channel *chan; |
| |
| list_for_each_entry(chan, &vi->vi_chans, list) { |
| vb2_video_unregister_device(&chan->video); |
| v4l2_async_nf_unregister(&chan->notifier); |
| v4l2_async_nf_cleanup(&chan->notifier); |
| } |
| } |
| |
| 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); |
| |
| if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| ret = tegra_vi_tpg_channels_alloc(vi); |
| else |
| ret = tegra_vi_channels_alloc(vi); |
| if (ret < 0) { |
| dev_err(vi->dev, |
| "failed to allocate vi channels: %d\n", ret); |
| goto free_chans; |
| } |
| |
| ret = tegra_vi_channels_init(vi); |
| if (ret < 0) |
| goto free_chans; |
| |
| vid->vi = vi; |
| |
| if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) { |
| ret = tegra_vi_graph_init(vi); |
| if (ret < 0) |
| goto cleanup_chans; |
| } |
| |
| return 0; |
| |
| cleanup_chans: |
| list_for_each_entry(chan, &vi->vi_chans, list) |
| tegra_channel_cleanup(chan); |
| 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) |
| { |
| struct tegra_vi *vi = host1x_client_to_vi(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. |
| */ |
| |
| if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| tegra_vi_graph_cleanup(vi); |
| |
| 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, |
| }; |