// SPDX-License-Identifier: GPL-2.0-only /* SPDX-FileCopyrightText: Copyright (c) 2015-2025 NVIDIA CORPORATION & AFFILIATES. * All rights reserved. * * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ /* * NVIDIA Tegra Video Input Device */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mipical/mipi_cal.h" #include #include "camera/csi/deskew.h" #define TPG_CSI_GROUP_ID 10 #define HDMI_IN_RATE 550000000 /* number of lanes per brick */ #define NUM_LANES_PER_BRICK 4 static s64 queue_init_ts; static bool tegra_channel_verify_focuser(struct tegra_channel *chan) { char *focuser; /* * WAR - to avoid power on/off during open/close for sensor * nodes but not focuser nodes. * add an array when more focusers are available, this logic is * not needed once the focuser is bound to sensor channel */ focuser = strnstr(chan->video->name, "lc898212", sizeof(chan->video->name)); return (focuser != NULL); } static void gang_buffer_offsets(struct tegra_channel *chan) { int i; u32 offset = 0; for (i = 0; i < chan->total_ports; i++) { switch (chan->gang_mode) { case CAMERA_NO_GANG_MODE: case CAMERA_GANG_L_R: case CAMERA_GANG_R_L: offset = chan->gang_bytesperline; break; case CAMERA_GANG_T_B: case CAMERA_GANG_B_T: offset = chan->gang_sizeimage; break; default: offset = 0; } offset = ((offset + TEGRA_SURFACE_ALIGNMENT - 1) & ~(TEGRA_SURFACE_ALIGNMENT - 1)); chan->buffer_offset[i] = i * offset; } spec_bar(); } static u32 gang_mode_width(enum camera_gang_mode gang_mode, unsigned int width) { if ((gang_mode == CAMERA_GANG_L_R) || (gang_mode == CAMERA_GANG_R_L)) return width >> 1; else return width; } static u32 gang_mode_height(enum camera_gang_mode gang_mode, unsigned int height) { if ((gang_mode == CAMERA_GANG_T_B) || (gang_mode == CAMERA_GANG_B_T)) return height >> 1; else return height; } static void update_gang_mode_params(struct tegra_channel *chan) { u32 numerator_product = 0; chan->gang_width = gang_mode_width(chan->gang_mode, chan->format.width); chan->gang_height = gang_mode_height(chan->gang_mode, chan->format.height); if (__builtin_umul_overflow(chan->gang_width, chan->fmtinfo->bpp.numerator, &numerator_product)) { dev_err(chan->vi->dev, "%s: calculate the width product failed due to an overflow\n", __func__); return; } chan->gang_bytesperline = (numerator_product / chan->fmtinfo->bpp.denominator); if (__builtin_umul_overflow(chan->gang_bytesperline, chan->format.height, &chan->gang_sizeimage)) { dev_err(chan->vi->dev, "%s: update gang size image failed due to an overflow\n", __func__); return; } gang_buffer_offsets(chan); } static void update_gang_mode(struct tegra_channel *chan) { int width = chan->format.width; int height = chan->format.height; /* * At present only 720p, 1080p and 4k resolutions * are supported and only 4K requires gang mode * Update this code with CID for future extensions * Also, validate width and height of images based * on gang mode and surface stride alignment */ if ((width > 1920) && (height > 1080)) { chan->gang_mode = CAMERA_GANG_L_R; chan->valid_ports = chan->total_ports; } else { chan->gang_mode = CAMERA_NO_GANG_MODE; chan->valid_ports = 1; } update_gang_mode_params(chan); } static u32 get_aligned_buffer_size(struct tegra_channel *chan, u32 bytesperline, u32 height) { u32 height_aligned; u32 temp_size, size; height_aligned = roundup(height, chan->height_align); if (__builtin_umul_overflow(bytesperline, height_aligned, &temp_size)) { dev_err(&chan->video->dev, "%s: calculate the buffer size failed due to an overflow\n", __func__); return 0; } size = roundup(temp_size, chan->size_align); return size; } static void tegra_channel_fmt_align(struct tegra_channel *chan, const struct tegra_video_format *vfmt, u32 *width, u32 *height, u32 *bytesperline) { unsigned int min_bpl; unsigned int max_bpl; unsigned int align, fmt_align; unsigned int temp_bpl; unsigned int bpl; unsigned int numerator, denominator; const struct tegra_frac *bpp = &vfmt->bpp; /* Init, if un-init */ if (!*width || !*height) { *width = chan->format.width; *height = chan->format.height; } denominator = (!bpp->denominator) ? 1 : bpp->denominator; numerator = (!bpp->numerator) ? 1 : bpp->numerator; /* The transfer alignment requirements are expressed in bytes. Compute * the minimum and maximum values, clamp the requested width and convert * it back to pixels. * use denominator for base width alignment when >1. * use bytesperline to adjust width for applicaton related requriements. */ fmt_align = (denominator == 1) ? numerator : 1; align = lcm(chan->width_align, fmt_align); align = align > 0 ? align : 1; bpl = tegra_core_bytes_per_line(*width, align, vfmt); /* Align stride */ if (chan->vi->fops->vi_stride_align) chan->vi->fops->vi_stride_align(&bpl); if (!*bytesperline) *bytesperline = bpl; /* Don't clamp the width based on bpl as stride and width can be * different. Aligned width also may force a sensor mode change other * than the requested one */ *height = clamp(*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 = bpl; max_bpl = rounddown(TEGRA_MAX_WIDTH, chan->stride_align); temp_bpl = roundup(*bytesperline, chan->stride_align); *bytesperline = clamp(temp_bpl, min_bpl, max_bpl); } /* Check if sensor mode is interlaced and the type of interlaced mode */ static void tegra_channel_set_interlace_mode(struct tegra_channel *chan) { struct v4l2_subdev *sd = NULL; struct camera_common_data *s_data = NULL; struct device_node *node = NULL; struct sensor_mode_properties *s_mode = NULL; if (chan->subdev_on_csi) { sd = chan->subdev_on_csi; s_data = to_camera_common_data(sd->dev); node = sd->dev->of_node; } if (s_data != NULL && node != NULL) { int idx = s_data->mode_prop_idx; if (idx < s_data->sensor_props.num_modes) { s_mode = &s_data->sensor_props.sensor_modes[idx]; chan->is_interlaced = s_mode->control_properties.is_interlaced; if (chan->is_interlaced) { if (s_mode->control_properties.interlace_type) chan->interlace_type = Interleaved; else chan->interlace_type = Top_Bottom; } } } } static void tegra_channel_update_format(struct tegra_channel *chan, u32 width, u32 height, u32 fourcc, const struct tegra_frac *bpp, u32 preferred_stride) { u32 denominator = (!bpp->denominator) ? 1 : bpp->denominator; u32 numerator = (!bpp->numerator) ? 1 : bpp->numerator; u32 scaled_width = 0; u32 bytesperline = 0; if (!chan) return; if (!chan->video) { dev_dbg(chan->vi->dev, "%s: chan->video NULL pointer\n", __func__); return; } if (__builtin_umul_overflow(width, numerator, &scaled_width)) { dev_err(&chan->video->dev, "%s: update format failed due to an overflow\n", __func__); return; } bytesperline = (scaled_width / denominator); /* Align stride */ if (chan->vi->fops->vi_stride_align) chan->vi->fops->vi_stride_align(&bytesperline); chan->format.width = width; chan->format.height = height; chan->format.pixelformat = fourcc; chan->format.bytesperline = preferred_stride ?: bytesperline; chan->buffer_offset[0] = 0; chan->interlace_bplfactor = 1; dev_dbg(&chan->video->dev, "%s: Resolution= %dx%d bytesperline=%d\n", __func__, width, height, chan->format.bytesperline); tegra_channel_fmt_align(chan, chan->fmtinfo, &chan->format.width, &chan->format.height, &chan->format.bytesperline); /* Calculate the sizeimage per plane */ chan->format.sizeimage = get_aligned_buffer_size(chan, chan->format.bytesperline, chan->format.height); tegra_channel_set_interlace_mode(chan); /* Double the size of allocated buffer for interlaced sensor modes */ if (chan->is_interlaced) chan->format.sizeimage *= 2; if (fourcc == V4L2_PIX_FMT_NV16) chan->format.sizeimage *= 2; } static void tegra_channel_fmts_bitmap_init(struct tegra_channel *chan) { int ret, pixel_format_index = 0, init_code = 0; struct v4l2_subdev *subdev = chan->subdev_on_csi; struct v4l2_subdev_format fmt = { .pad = 0, .which = V4L2_SUBDEV_FORMAT_ACTIVE, }; struct v4l2_subdev_mbus_code_enum code = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, }; struct v4l2_subdev_pad_config pad_cfg; struct v4l2_subdev_state cfg = {.pads = &pad_cfg}; bitmap_zero(chan->fmts_bitmap, MAX_FORMAT_NUM); /* * Initialize all the formats available from * the sub-device and extract the corresponding * index from the pre-defined video formats and initialize * the channel default format with the active code * Index zero as the only sub-device is sensor */ while (1) { ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, &cfg, &code); if (ret < 0) /* no more formats */ break; pixel_format_index = tegra_core_get_idx_by_code(chan, code.code, 0); while (pixel_format_index >= 0) { bitmap_set(chan->fmts_bitmap, pixel_format_index, 1); /* Set init_code to the first matched format */ if (!init_code) init_code = code.code; /* Look for other formats with the same mbus code */ pixel_format_index = tegra_core_get_idx_by_code(chan, code.code, pixel_format_index + 1); } code.index++; } if (!init_code) { pixel_format_index = tegra_core_get_idx_by_code(chan, TEGRA_VF_DEF, 0); if (pixel_format_index >= 0) { bitmap_set(chan->fmts_bitmap, pixel_format_index, 1); init_code = TEGRA_VF_DEF; } } /* Get the format based on active code of the sub-device */ ret = v4l2_subdev_call(subdev, pad, get_fmt, &cfg, &fmt); if (ret) return; /* Initiate the channel format to the first matched format */ chan->fmtinfo = tegra_core_get_format_by_code(chan, fmt.format.code, 0); if (!chan->fmtinfo) return; v4l2_fill_pix_format(&chan->format, &fmt.format); tegra_channel_update_format(chan, chan->format.width, chan->format.height, chan->fmtinfo->fourcc, &chan->fmtinfo->bpp, chan->preferred_stride); if (chan->total_ports > 1) update_gang_mode(chan); } /* * ----------------------------------------------------------------------------- * Tegra channel frame setup and capture operations * ----------------------------------------------------------------------------- */ /* * Update the timestamp of the buffer */ void set_timestamp(struct tegra_channel_buffer *buf, const struct timespec64 *ts) { buf->buf.vb2_buf.timestamp = (u64)timespec64_to_ns(ts); } EXPORT_SYMBOL(set_timestamp); void release_buffer(struct tegra_channel *chan, struct tegra_channel_buffer *buf) { struct vb2_v4l2_buffer *vbuf = &buf->buf; s64 frame_arrived_ts = 0; unsigned long flags; /* release one frame */ vbuf->sequence = chan->sequence; if (__builtin_uadd_overflow(chan->sequence, 1, &chan->sequence)) { dev_err(&chan->video->dev, "%s: release buffer failed due to an overflow\n", __func__); return; } vbuf->field = V4L2_FIELD_NONE; vb2_set_plane_payload(&vbuf->vb2_buf, 0, chan->format.sizeimage); spin_lock_irqsave(&chan->capture_state_lock, flags); /* * WAR to force buffer state if capture state is not good * WAR - After sync point timeout or error frame capture * the second buffer is intermittently frame of zeros * with no error status or padding. */ if (chan->capture_state != CAPTURE_GOOD || vbuf->sequence < 2) buf->state = VB2_BUF_STATE_ERROR; spin_unlock_irqrestore(&chan->capture_state_lock, flags); if (chan->sequence == 1) { /* * Evaluate the initial capture latency between videobuf2 queue * and first captured frame release to user-space. */ frame_arrived_ts = ktime_to_ms(ktime_get()); dev_dbg(&chan->video->dev, "%s: capture init latency is %lld ms\n", __func__, (frame_arrived_ts - queue_init_ts)); } dev_dbg(&chan->video->dev, "%s: release buf[%p] frame[%d] to user-space\n", __func__, buf, chan->sequence); vb2_buffer_done(&vbuf->vb2_buf, buf->state); } EXPORT_SYMBOL(release_buffer); /* * `buf` has been successfully setup to receive a frame and is * "in flight" through the VI hardware. We are currently waiting * on it to be filled. Moves the pointer into the `release` list * for the release thread to wait on. */ void enqueue_inflight(struct tegra_channel *chan, struct tegra_channel_buffer *buf) { /* Put buffer into the release queue */ spin_lock(&chan->release_lock); list_add_tail(&buf->queue, &chan->release); spin_unlock(&chan->release_lock); /* Wake up kthread for release */ wake_up_interruptible(&chan->release_wait); } EXPORT_SYMBOL(enqueue_inflight); struct tegra_channel_buffer *dequeue_inflight(struct tegra_channel *chan) { struct tegra_channel_buffer *buf = NULL; spin_lock(&chan->release_lock); if (list_empty(&chan->release)) { spin_unlock(&chan->release_lock); return NULL; } buf = list_entry(chan->release.next, struct tegra_channel_buffer, queue); if (buf) list_del_init(&buf->queue); spin_unlock(&chan->release_lock); return buf; } EXPORT_SYMBOL(dequeue_inflight); void tegra_channel_init_ring_buffer(struct tegra_channel *chan) { chan->released_bufs = 0; chan->num_buffers = 0; chan->save_index = 0; spin_lock(&chan->buffer_lock); chan->free_index = 0; spin_unlock(&chan->buffer_lock); chan->bfirst_fstart = false; chan->capture_descr_index = 0; chan->capture_descr_sequence = 0; chan->queue_error = false; } EXPORT_SYMBOL(tegra_channel_init_ring_buffer); void free_ring_buffers(struct tegra_channel *chan, int frames) { struct vb2_v4l2_buffer *vbuf; s64 frame_arrived_ts = 0; unsigned long flags; spin_lock(&chan->buffer_lock); if (frames == 0) frames = chan->num_buffers; while (frames > 0) { vbuf = chan->buffers[chan->free_index]; /* Skip updating the buffer sequence with channel sequence * for interlaced captures and this instead will be updated * with frame id received from CSI with capture complete */ if (!chan->is_interlaced) vbuf->sequence = chan->sequence++; else chan->sequence++; /* release one frame */ vbuf->field = V4L2_FIELD_NONE; vb2_set_plane_payload(&vbuf->vb2_buf, 0, chan->format.sizeimage); spin_lock_irqsave(&chan->capture_state_lock, flags); /* * WAR to force buffer state if capture state is not good * WAR - After sync point timeout or error frame capture * the second buffer is intermittently frame of zeros * with no error status or padding. */ /* This will drop the first two frames. Disable for now. */ if (chan->capture_state != CAPTURE_GOOD || chan->released_bufs < 2) chan->buffer_state[chan->free_index] = VB2_BUF_STATE_ERROR; spin_unlock_irqrestore(&chan->capture_state_lock, flags); if (chan->sequence == 1) { /* * Evaluate the initial capture latency * between videobuf2 queue and first captured * frame release to user-space. */ frame_arrived_ts = ktime_to_ms(ktime_get()); dev_dbg(&chan->video->dev, "%s: capture init latency is %lld ms\n", __func__, (frame_arrived_ts - queue_init_ts)); } vb2_buffer_done(&vbuf->vb2_buf, chan->buffer_state[chan->free_index++]); if (chan->free_index >= chan->capture_queue_depth) chan->free_index = 0; chan->num_buffers--; chan->released_bufs++; frames--; } spin_unlock(&chan->buffer_lock); } EXPORT_SYMBOL(free_ring_buffers); static void add_buffer_to_ring(struct tegra_channel *chan, struct vb2_v4l2_buffer *vb) { /* save the buffer to the ring first */ /* Mark buffer state as error before start */ spin_lock(&chan->buffer_lock); chan->buffer_state[chan->save_index] = VB2_BUF_STATE_ERROR; chan->buffers[chan->save_index] = vb; if (__builtin_uadd_overflow(chan->save_index, 1, &chan->save_index)) { dev_err(&chan->video->dev, "%s: save the buffer to the ring failed due to an overflow\n", __func__); return; } if (chan->save_index >= chan->capture_queue_depth) chan->save_index = 0; chan->num_buffers++; spin_unlock(&chan->buffer_lock); } static void update_state_to_buffer(struct tegra_channel *chan, int state) { int save_index = ((int)chan->save_index - PREVIOUS_BUFFER_DEC_INDEX); unsigned long flags; /* save index decrements by 2 as 3 bufs are added in ring buffer */ if (save_index < 0) save_index += chan->capture_queue_depth; /* update state for the previous buffer */ chan->buffer_state[save_index] = state; spin_lock_irqsave(&chan->capture_state_lock, flags); /* for timeout/error case update the current buffer state as well */ if (chan->capture_state != CAPTURE_GOOD) chan->buffer_state[chan->save_index] = state; spin_unlock_irqrestore(&chan->capture_state_lock, flags); } void tegra_channel_ring_buffer(struct tegra_channel *chan, struct vb2_v4l2_buffer *vb, struct timespec64 *ts, int state) { unsigned long flags; if (!chan->bfirst_fstart) chan->bfirst_fstart = true; else update_state_to_buffer(chan, state); spin_lock_irqsave(&chan->capture_state_lock, flags); /* Capture state is not GOOD, release all buffers and re-init state */ if (chan->capture_state != CAPTURE_GOOD) { free_ring_buffers(chan, chan->num_buffers); tegra_channel_init_ring_buffer(chan); spin_unlock_irqrestore(&chan->capture_state_lock, flags); return; } else { /* TODO: granular time code information */ vb->timecode.seconds = ts->tv_sec; } spin_unlock_irqrestore(&chan->capture_state_lock, flags); /* release buffer N at N+2 frame start event */ if (chan->num_buffers >= (chan->capture_queue_depth - 1)) free_ring_buffers(chan, 1); } EXPORT_SYMBOL(tegra_channel_ring_buffer); void tegra_channel_ec_close(struct tegra_mc_vi *vi) { struct tegra_channel *chan; /* clear all channles sync point fifo context */ list_for_each_entry(chan, &vi->vi_chans, list) { memset(chan->syncpoint_fifo, 0, sizeof(chan->syncpoint_fifo)); } } EXPORT_SYMBOL(tegra_channel_ec_close); struct tegra_channel_buffer *dequeue_buffer(struct tegra_channel *chan, bool requeue) { struct tegra_channel_buffer *buf = NULL; spin_lock(&chan->start_lock); if (list_empty(&chan->capture)) goto done; buf = list_entry(chan->capture.next, struct tegra_channel_buffer, queue); list_del_init(&buf->queue); if (requeue) { /* add dequeued buffer to the ring buffer */ add_buffer_to_ring(chan, &buf->buf); } done: spin_unlock(&chan->start_lock); return buf; } EXPORT_SYMBOL(dequeue_buffer); struct tegra_channel_buffer *dequeue_dequeue_buffer(struct tegra_channel *chan) { struct tegra_channel_buffer *buf = NULL; spin_lock(&chan->dequeue_lock); if (list_empty(&chan->dequeue)) goto done; buf = list_entry(chan->dequeue.next, struct tegra_channel_buffer, queue); list_del_init(&buf->queue); done: spin_unlock(&chan->dequeue_lock); return buf; } EXPORT_SYMBOL(dequeue_dequeue_buffer); int tegra_channel_error_recover(struct tegra_channel *chan, bool queue_error) { struct tegra_mc_vi *vi = chan->vi; int err = 0; if (!(vi->fops && vi->fops->vi_error_recover)) { err = -EIO; goto done; } dev_warn(vi->dev, "err_rec: attempting to reset the capture channel\n"); err = vi->fops->vi_error_recover(chan, queue_error); if (!err) dev_warn(vi->dev, "err_rec: successfully reset the capture channel\n"); done: return err; } EXPORT_SYMBOL(tegra_channel_error_recover); static struct device *tegra_channel_get_vi_unit(struct tegra_channel *chan) { struct tegra_mc_vi *vi = chan->vi; struct device *vi_unit_dev; if (vi->fops->vi_unit_get_device_handle) vi->fops->vi_unit_get_device_handle(vi->ndev, chan->port[0], &vi_unit_dev); else vi_unit_dev = vi->dev; return vi_unit_dev; } /* * ----------------------------------------------------------------------------- * 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_channel *chan = vb2_get_drv_priv(vq); struct tegra_mc_vi *vi = chan->vi; int ret = 0; /* In some cases, if nplanes is valid * and the requested image size is less than the * actual image size, we need to return EINVAL. * Previously, we were just updating sizes[0] irrespective * of the requested image size. Although this did not harm the * flow, according to "v4l2-compliance", we need to check if * the requested size is invalid. * Printing this error as info, to avoid kernel error/warning failure. */ if (*nplanes) { if (sizes[0] < chan->format.sizeimage) { dev_info(chan->vi->dev, "sizes[0] = %d chan->format.sizeimage = %d, for num_planes = %d ...\n" , sizes[0], chan->format.sizeimage, *nplanes); return -EINVAL; } } else { sizes[0] = chan->format.sizeimage; } *nplanes = 1; alloc_devs[0] = tegra_channel_get_vi_unit(chan); if (vi->fops && vi->fops->vi_setup_queue) return vi->fops->vi_setup_queue(chan, nbuffers); return ret; } int tegra_channel_alloc_buffer_queue(struct tegra_channel *chan, unsigned int num_buffers) { struct device *vi_unit_dev = tegra_channel_get_vi_unit(chan); chan->buffer_state = devm_kzalloc(vi_unit_dev, (num_buffers * sizeof(*chan->buffer_state)), GFP_KERNEL); if (!chan->buffer_state) goto alloc_error; chan->buffers = devm_kzalloc(vi_unit_dev, (num_buffers * sizeof(*chan->buffers)), GFP_KERNEL); if (!chan->buffers) goto alloc_error; chan->capture_queue_depth = num_buffers; return 0; alloc_error: dev_err(chan->vi->dev, "error: could not allocate memory for %u size buffer queue\n", num_buffers); tegra_channel_dealloc_buffer_queue(chan); return -ENOMEM; } EXPORT_SYMBOL(tegra_channel_alloc_buffer_queue); void tegra_channel_dealloc_buffer_queue(struct tegra_channel *chan) { struct device *vi_unit_dev = tegra_channel_get_vi_unit(chan); if (chan->buffer_state) devm_kfree(vi_unit_dev, chan->buffer_state); if (chan->buffers) devm_kfree(vi_unit_dev, chan->buffers); } static int tegra_channel_buffer_prepare(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct tegra_channel *chan = vb2_get_drv_priv(vb->vb2_queue); struct tegra_channel_buffer *buf = to_tegra_channel_buffer(vbuf); buf->chan = chan; vb2_set_plane_payload(&vbuf->vb2_buf, 0, chan->format.sizeimage); #if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) buf->addr = vb2_dma_contig_plane_dma_addr(vb, 0); #endif return 0; } static void tegra_channel_buffer_queue(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); struct tegra_channel *chan = vb2_get_drv_priv(vb->vb2_queue); struct tegra_channel_buffer *buf = to_tegra_channel_buffer(vbuf); /* for bypass mode - do nothing */ if (chan->bypass) return; if (!queue_init_ts) { /* * Record videobuf2 queue initial timestamp. * Note: latency is accurate when streaming is already turned ON */ queue_init_ts = ktime_to_ms(ktime_get()); } /* Put buffer into the capture queue */ spin_lock(&chan->start_lock); list_add_tail(&buf->queue, &chan->capture); spin_unlock(&chan->start_lock); /* Wake up kthread for capture */ wake_up_interruptible(&chan->start_wait); } static void tegra_channel_queued_buf_done_single_thread( struct tegra_channel *chan, enum vb2_buffer_state state) { struct tegra_channel_buffer *buf, *nbuf; /* delete capture list */ 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); /* delete dequeue list */ spin_lock(&chan->dequeue_lock); list_for_each_entry_safe(buf, nbuf, &chan->dequeue, queue) { vb2_buffer_done(&buf->buf.vb2_buf, state); list_del(&buf->queue); } spin_unlock(&chan->dequeue_lock); } static void tegra_channel_queued_buf_done_multi_thread( struct tegra_channel *chan, enum vb2_buffer_state state) { struct tegra_channel_buffer *buf, *nbuf; spinlock_t *lock = &chan->start_lock; spinlock_t *release_lock = &chan->release_lock; struct list_head *q = &chan->capture; struct list_head *rel_q = NULL; spin_lock(lock); list_for_each_entry_safe(buf, nbuf, q, queue) { vb2_buffer_done(&buf->buf.vb2_buf, state); list_del(&buf->queue); } spin_unlock(lock); /* delete release list */ spin_lock(release_lock); rel_q = &chan->release; list_for_each_entry_safe(buf, nbuf, rel_q, queue) { vb2_buffer_done(&buf->buf.vb2_buf, state); list_del(&buf->queue); } spin_unlock(release_lock); } /* Return all queued buffers back to videobuf2 */ void tegra_channel_queued_buf_done(struct tegra_channel *chan, enum vb2_buffer_state state, bool multi_queue) { if (multi_queue) tegra_channel_queued_buf_done_multi_thread(chan, state); else tegra_channel_queued_buf_done_single_thread(chan, state); } EXPORT_SYMBOL(tegra_channel_queued_buf_done); /* * ----------------------------------------------------------------------------- * subdevice set/unset operations * ----------------------------------------------------------------------------- */ int tegra_channel_write_blobs(struct tegra_channel *chan) { struct v4l2_subdev *sd = NULL; struct camera_common_data *s_data = NULL; /* for TPG, do nothing */ if (chan->pg_mode) return 0; sd = chan->subdev_on_csi; if (!sd) return -EINVAL; s_data = to_camera_common_data(sd->dev); if (!s_data) return 0; if (!is_tvcf_supported(s_data->version)) return 0; return tegracam_write_blobs(s_data->tegracam_ctrl_hdl); } EXPORT_SYMBOL(tegra_channel_write_blobs); int tegra_channel_set_stream(struct tegra_channel *chan, bool on) { int num_sd; int ret = 0; int err = 0; int max_deskew_attempts = 5; int deskew_attempts = 0; struct v4l2_subdev *sd; if (atomic_read(&chan->is_streaming) == on) return 0; trace_tegra_channel_set_stream("enable", on); if (on) { tegra_camera_update_clknbw(chan, true); /* Enable CSI before sensor. Reason is as follows: * CSI is able to catch the very first clk transition. */ while (deskew_attempts < max_deskew_attempts) { for (num_sd = 0; num_sd < chan->num_subdevs; num_sd++) { sd = chan->subdev[num_sd]; trace_tegra_channel_set_stream(sd->name, on); err = v4l2_subdev_call(sd, video, s_stream, on); if (!ret && err < 0 && err != -ENOIOCTLCMD) ret = err; } if (!chan->bypass && !chan->pg_mode && chan->deskew_ctx->deskew_lanes) { err = nvcsi_deskew_apply_check( chan->deskew_ctx); ++deskew_attempts; if (err && deskew_attempts < max_deskew_attempts) { for (num_sd = 0; num_sd < chan->num_subdevs; num_sd++) { sd = chan->subdev[num_sd]; trace_tegra_channel_set_stream( sd->name, false); err = v4l2_subdev_call(sd, video, s_stream, false); } } else break; } else break; } } else { for (num_sd = chan->num_subdevs - 1; num_sd >= 0; num_sd--) { sd = chan->subdev[num_sd]; trace_tegra_channel_set_stream(sd->name, on); err = v4l2_subdev_call(sd, video, s_stream, on); if (!ret && err < 0 && err != -ENOIOCTLCMD) ret = err; } spec_bar(); tegra_camera_update_clknbw(chan, false); } if (ret == 0) atomic_set(&chan->is_streaming, on); return ret; } EXPORT_SYMBOL(tegra_channel_set_stream); int tegra_channel_set_power(struct tegra_channel *chan, bool on) { int num_sd; int ret = 0; int err = 0; struct v4l2_subdev *sd; /* First power on and last power off will turn on/off the subdevices */ if (on) { if (atomic_add_return(1, &chan->power_on_refcnt) != 1) return 0; } else { if (!atomic_dec_and_test(&chan->power_on_refcnt)) return 0; } /* Power on CSI at the last to complete calibration of mipi lanes */ if ((chan->num_subdevs <= 0) || (chan->num_subdevs > MAX_SUBDEVICES)) { dev_err(chan->vi->dev, "%s: set power failed due to an invalid num_subdevs value\n", __func__); return -EINVAL; } for (num_sd = chan->num_subdevs - 1; num_sd >= 0; num_sd--) { sd = chan->subdev[num_sd]; trace_tegra_channel_set_power(sd->name, on); err = v4l2_subdev_call(sd, core, s_power, on); if (!ret && err < 0 && err != -ENOIOCTLCMD) ret = err; } return ret; } EXPORT_SYMBOL(tegra_channel_set_power); static int tegra_channel_start_streaming(struct vb2_queue *vq, u32 count) { struct tegra_channel *chan = vb2_get_drv_priv(vq); struct tegra_mc_vi *vi = chan->vi; if (vi->fops) { int ret = 0; /* power on hw at the start of streaming */ ret = vi->fops->vi_power_on(chan); if (ret < 0) return ret; return vi->fops->vi_start_streaming(vq, count); } return 0; } static void tegra_channel_stop_streaming(struct vb2_queue *vq) { struct tegra_channel *chan = vb2_get_drv_priv(vq); struct tegra_mc_vi *vi = chan->vi; if (vi->fops) { vi->fops->vi_stop_streaming(vq); vi->fops->vi_power_off(chan); } /* Clean-up recorded videobuf2 queue initial timestamp */ queue_init_ts = 0; } 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 ioctls */ static int tegra_channel_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { struct tegra_channel *chan = video_drvdata(file); ssize_t len; int ret = 0; cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; cap->device_caps |= V4L2_CAP_EXT_PIX_FORMAT; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; len = strscpy(cap->driver, "tegra-video", sizeof(cap->driver)); if (len < 0) return -ENAMETOOLONG; len = strscpy(cap->card, chan->video->name, sizeof(cap->card)); if (len < 0) return -ENAMETOOLONG; ret = snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s:%u", dev_name(chan->vi->dev), chan->port[0]); if (ret < 0) return -EINVAL; return 0; } static int tegra_channel_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *sizes) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; struct v4l2_subdev_frame_size_enum fse; struct v4l2_subdev_pad_config pad_cfg; struct v4l2_subdev_state cfg = {.pads = &pad_cfg}; int ret = 0; /* Convert v4l2 pixel format (fourcc) into media bus format code */ fse.code = tegra_core_get_code_by_fourcc(chan, sizes->pixel_format, 0); if (fse.code < 0) return -EINVAL; fse.index = sizes->index; fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; fse.pad = 0; ret = v4l2_subdev_call(sd, pad, enum_frame_size, &cfg, &fse); if (!ret) { sizes->type = V4L2_FRMSIZE_TYPE_DISCRETE; sizes->discrete.width = fse.max_width; sizes->discrete.height = fse.max_height; } return ret; } static int tegra_channel_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *intervals) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; struct v4l2_subdev_frame_interval_enum fie; struct v4l2_subdev_pad_config pad_cfg; struct v4l2_subdev_state cfg = {.pads = &pad_cfg}; int ret = 0; /* Convert v4l2 pixel format (fourcc) into media bus format code */ fie.code = tegra_core_get_code_by_fourcc( chan, intervals->pixel_format, 0); if (fie.code < 0) return -EINVAL; fie.index = intervals->index; fie.width = intervals->width; fie.height = intervals->height; fie.pad = 0; fie.which = V4L2_SUBDEV_FORMAT_TRY; ret = v4l2_subdev_call(sd, pad, enum_frame_interval, &cfg, &fie); if (!ret) { intervals->type = V4L2_FRMIVAL_TYPE_DISCRETE; intervals->discrete.numerator = fie.interval.numerator; intervals->discrete.denominator = fie.interval.denominator; } return ret; } static int tegra_channel_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct tegra_channel *chan = video_drvdata(file); unsigned int index = 0, i; unsigned long *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); if (index >= MAX_FORMAT_NUM) break; } if (__builtin_usub_overflow(index, 1, &index)) { dev_err(chan->vi->dev, "%s: update pixel format failed due to an overflow\n", __func__); return -EOVERFLOW; } f->pixelformat = tegra_core_get_fourcc_by_idx(chan, index); return 0; } static int tegra_channel_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; if (!v4l2_subdev_has_op(sd, pad, get_edid)) return -ENOTTY; return v4l2_subdev_call(sd, pad, get_edid, edid); } static int tegra_channel_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; if (!v4l2_subdev_has_op(sd, pad, set_edid)) return -ENOTTY; return v4l2_subdev_call(sd, pad, set_edid, edid); } static int tegra_channel_g_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings) { struct tegra_channel *chan = video_drvdata(file); #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ if (!v4l2_subdev_has_op(chan->subdev_on_csi, pad, g_dv_timings)) return -ENOTTY; return v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, pad, g_dv_timings, 0, timings); #else if (!v4l2_subdev_has_op(chan->subdev_on_csi, video, g_dv_timings)) return -ENOTTY; return v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, video, g_dv_timings, timings); #endif } static int tegra_channel_s_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_bt_timings *bt = &timings->bt; struct v4l2_dv_timings curr_timings; int ret; #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ if (!v4l2_subdev_has_op(chan->subdev_on_csi, pad, s_dv_timings)) #else if (!v4l2_subdev_has_op(chan->subdev_on_csi, video, s_dv_timings)) #endif return -ENOTTY; ret = tegra_channel_g_dv_timings(file, fh, &curr_timings); if (ret) return ret; if (tegra_v4l2_match_dv_timings(timings, &curr_timings, 0, false)) return 0; if (vb2_is_busy(&chan->queue)) return -EBUSY; #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ ret = v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, pad, s_dv_timings, 0, timings); #else ret = v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, video, s_dv_timings, timings); #endif if (!ret) tegra_channel_update_format(chan, bt->width, bt->height, chan->fmtinfo->fourcc, &chan->fmtinfo->bpp, chan->preferred_stride); if (chan->total_ports > 1) update_gang_mode(chan); return ret; } static int tegra_channel_query_dv_timings(struct file *file, void *fh, struct v4l2_dv_timings *timings) { struct tegra_channel *chan = video_drvdata(file); #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ if (!v4l2_subdev_has_op(chan->subdev_on_csi, pad, query_dv_timings)) return -ENOTTY; return v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, pad, query_dv_timings, 0, timings); #else if (!v4l2_subdev_has_op(chan->subdev_on_csi, video, query_dv_timings)) return -ENOTTY; return v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, video, query_dv_timings, timings); #endif } static int tegra_channel_enum_dv_timings(struct file *file, void *fh, struct v4l2_enum_dv_timings *timings) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; if (!v4l2_subdev_has_op(sd, pad, enum_dv_timings)) return -ENOTTY; return v4l2_subdev_call(sd, 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_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; if (!v4l2_subdev_has_op(sd, pad, dv_timings_cap)) return -ENOTTY; return v4l2_subdev_call(sd, pad, dv_timings_cap, cap); } int tegra_channel_s_ctrl(struct v4l2_ctrl *ctrl) { struct tegra_channel *chan; int err = 0; if (!ctrl) return -EINVAL; chan = container_of(ctrl->handler, struct tegra_channel, ctrl_handler); if (!chan) return -EINVAL; switch (ctrl->id) { case TEGRA_CAMERA_CID_GAIN_TPG: { if (chan->vi->csi != NULL && chan->vi->csi->tpg_gain_ctrl) { struct v4l2_subdev *sd = chan->subdev_on_csi; err = tegra_csi_tpg_set_gain(sd, &(ctrl->val)); } } break; case TEGRA_CAMERA_CID_VI_BYPASS_MODE: /* Prevent changing the bypass mode while the device is still streaming */ if (vb2_is_busy(&chan->queue)) return -EBUSY; if ((ctrl->val < 0) || (ctrl->val >= ARRAY_SIZE(switch_ctrl_qmenu))) { dev_err(&chan->video->dev, "%s: update bypass failed due to an invalid value\n", __func__); return -EINVAL; } if (switch_ctrl_qmenu[ctrl->val] == SWITCH_ON) chan->bypass = true; else if (chan->vi->bypass) { dev_dbg(&chan->video->dev, "can't disable bypass mode\n"); dev_dbg(&chan->video->dev, "because the VI/CSI is in bypass mode\n"); chan->bypass = true; } else chan->bypass = false; break; case TEGRA_CAMERA_CID_OVERRIDE_ENABLE: { struct v4l2_subdev *sd = chan->subdev_on_csi; struct camera_common_data *s_data = to_camera_common_data(sd->dev); if (!s_data) break; if ((ctrl->val < 0) || (ctrl->val >= ARRAY_SIZE(switch_ctrl_qmenu))) { dev_err(&chan->video->dev, "%s: update enable override failed due to an invalid value\n", __func__); return -EINVAL; } if (switch_ctrl_qmenu[ctrl->val] == SWITCH_ON) { s_data->override_enable = true; dev_dbg(&chan->video->dev, "enable override control\n"); } else { s_data->override_enable = false; dev_dbg(&chan->video->dev, "disable override control\n"); } } break; case TEGRA_CAMERA_CID_VI_HEIGHT_ALIGN: chan->height_align = ctrl->val; tegra_channel_update_format(chan, chan->format.width, chan->format.height, chan->format.pixelformat, &chan->fmtinfo->bpp, 0); break; case TEGRA_CAMERA_CID_VI_SIZE_ALIGN: if ((ctrl->val < 0) || (ctrl->val >= ARRAY_SIZE(size_align_ctrl_qmenu))) { dev_err(&chan->video->dev, "%s: update size alignment failed due to an invalid value\n", __func__); return -EINVAL; } chan->size_align = size_align_ctrl_qmenu[ctrl->val]; tegra_channel_update_format(chan, chan->format.width, chan->format.height, chan->format.pixelformat, &chan->fmtinfo->bpp, 0); break; case TEGRA_CAMERA_CID_LOW_LATENCY: chan->low_latency = ctrl->val; break; case TEGRA_CAMERA_CID_VI_PREFERRED_STRIDE: chan->preferred_stride = ctrl->val; tegra_channel_update_format(chan, chan->format.width, chan->format.height, chan->format.pixelformat, &chan->fmtinfo->bpp, chan->preferred_stride); break; default: dev_err(&chan->video->dev, "%s: Invalid ctrl %u\n", __func__, ctrl->id); err = -EINVAL; } return err; } static const struct v4l2_ctrl_ops channel_ctrl_ops = { .s_ctrl = tegra_channel_s_ctrl, }; static const struct v4l2_ctrl_config common_custom_ctrls[] = { { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_GAIN_TPG, .name = "TPG Gain Ctrl", .type = V4L2_CTRL_TYPE_INTEGER, .min = 1, .max = 64, .step = 1, .def = 1, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_GAIN_TPG_EMB_DATA_CFG, .name = "TPG embedded data config", .type = V4L2_CTRL_TYPE_BOOLEAN, .flags = V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = 1, .step = 1, .def = 0, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_VI_BYPASS_MODE, .name = "Bypass Mode", .type = V4L2_CTRL_TYPE_INTEGER_MENU, .def = 0, .min = 0, .max = ARRAY_SIZE(switch_ctrl_qmenu) - 1, .menu_skip_mask = 0, .qmenu_int = switch_ctrl_qmenu, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_OVERRIDE_ENABLE, .name = "Override Enable", .type = V4L2_CTRL_TYPE_INTEGER_MENU, .def = 0, .min = 0, .max = ARRAY_SIZE(switch_ctrl_qmenu) - 1, .menu_skip_mask = 0, .qmenu_int = switch_ctrl_qmenu, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_VI_HEIGHT_ALIGN, .name = "Height Align", .type = V4L2_CTRL_TYPE_INTEGER, .min = 1, .max = 16, .step = 1, .def = 1, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_VI_SIZE_ALIGN, .name = "Size Align", .type = V4L2_CTRL_TYPE_INTEGER_MENU, .def = TEGRA_SIZE_ALIGNMENT, .min = 0, .max = ARRAY_SIZE(size_align_ctrl_qmenu) - 1, .menu_skip_mask = 0, .qmenu_int = size_align_ctrl_qmenu, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_SENSOR_MODES, .name = "Sensor Modes", .type = V4L2_CTRL_TYPE_INTEGER, .flags = V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = MAX_NUM_SENSOR_MODES, .def = MAX_NUM_SENSOR_MODES, .step = 1, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_SENSOR_SIGNAL_PROPERTIES, .name = "Sensor Signal Properties", .type = V4L2_CTRL_TYPE_U32, .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD | V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = 0xFFFFFFFF, .step = 1, .def = 0, .dims = { MAX_NUM_SENSOR_MODES, SENSOR_SIGNAL_PROPERTIES_CID_SIZE }, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_SENSOR_IMAGE_PROPERTIES, .name = "Sensor Image Properties", .type = V4L2_CTRL_TYPE_U32, .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD | V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = 0xFFFFFFFF, .step = 1, .def = 0, .dims = { MAX_NUM_SENSOR_MODES, SENSOR_IMAGE_PROPERTIES_CID_SIZE }, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_SENSOR_CONTROL_PROPERTIES, .name = "Sensor Control Properties", .type = V4L2_CTRL_TYPE_U32, .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD | V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = 0xFFFFFFFF, .step = 1, .def = 0, .dims = { MAX_NUM_SENSOR_MODES, SENSOR_CONTROL_PROPERTIES_CID_SIZE }, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_SENSOR_DV_TIMINGS, .name = "Sensor DV Timings", .type = V4L2_CTRL_TYPE_U32, .flags = V4L2_CTRL_FLAG_HAS_PAYLOAD | V4L2_CTRL_FLAG_READ_ONLY, .min = 0, .max = 0xFFFFFFFF, .step = 1, .def = 0, .dims = { MAX_NUM_SENSOR_MODES, SENSOR_DV_TIMINGS_CID_SIZE }, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_LOW_LATENCY, .name = "Low Latency Mode", .type = V4L2_CTRL_TYPE_BOOLEAN, .def = 0, .min = 0, .max = 1, .step = 1, }, { .ops = &channel_ctrl_ops, .id = TEGRA_CAMERA_CID_VI_PREFERRED_STRIDE, .name = "Preferred Stride", .type = V4L2_CTRL_TYPE_INTEGER, .min = 0, .max = 65535, .step = 1, .def = 0, }, }; #define GET_TEGRA_CAMERA_CTRL(id, c) \ do { \ c = v4l2_ctrl_find(&chan->ctrl_handler, TEGRA_CAMERA_CID_##id); \ if (!c) { \ dev_err(chan->vi->dev, "%s: could not find ctrl %s\n", \ __func__, "##id"); \ return -EINVAL; \ } \ } while (0) static int tegra_channel_sensorprops_setup(struct tegra_channel *chan) { const struct v4l2_subdev *sd = chan->subdev_on_csi; const struct camera_common_data *s_data = to_camera_common_data(sd->dev); const struct sensor_mode_properties *modes; struct v4l2_ctrl *ctrl_modes; struct v4l2_ctrl *ctrl_signalprops; struct v4l2_ctrl *ctrl_imageprops; struct v4l2_ctrl *ctrl_controlprops; struct v4l2_ctrl *ctrl_dvtimings; u32 i; if (!s_data) return 0; GET_TEGRA_CAMERA_CTRL(SENSOR_MODES, ctrl_modes); GET_TEGRA_CAMERA_CTRL(SENSOR_SIGNAL_PROPERTIES, ctrl_signalprops); GET_TEGRA_CAMERA_CTRL(SENSOR_IMAGE_PROPERTIES, ctrl_imageprops); GET_TEGRA_CAMERA_CTRL(SENSOR_CONTROL_PROPERTIES, ctrl_controlprops); GET_TEGRA_CAMERA_CTRL(SENSOR_DV_TIMINGS, ctrl_dvtimings); ctrl_modes->val = s_data->sensor_props.num_modes; ctrl_modes->cur.val = s_data->sensor_props.num_modes; /* Update the control sizes * * Note that the structs have size elems * sizeof(u32) * So to get the number of structs (elems * sizeof(u32)) / num_modes */ ctrl_signalprops->elems = s_data->sensor_props.num_modes * SENSOR_SIGNAL_PROPERTIES_CID_SIZE; ctrl_imageprops->elems = s_data->sensor_props.num_modes * SENSOR_IMAGE_PROPERTIES_CID_SIZE; ctrl_controlprops->elems = s_data->sensor_props.num_modes * SENSOR_CONTROL_PROPERTIES_CID_SIZE; ctrl_dvtimings->elems = s_data->sensor_props.num_modes * SENSOR_DV_TIMINGS_CID_SIZE; modes = s_data->sensor_props.sensor_modes; for (i = 0; i < s_data->sensor_props.num_modes; i++) { void *ptr = NULL; u32 size; size = sizeof(struct sensor_signal_properties); ptr = ctrl_signalprops->p_new.p + (i * size); memcpy(ptr, &modes[i].signal_properties, size); size = sizeof(struct sensor_image_properties); ptr = ctrl_imageprops->p_new.p + (i * size); memcpy(ptr, &modes[i].image_properties, size); size = sizeof(struct sensor_control_properties); ptr = ctrl_controlprops->p_new.p + (i * size); memcpy(ptr, &modes[i].control_properties, size); size = sizeof(struct sensor_dv_timings); ptr = ctrl_dvtimings->p_new.p + (i * size); memcpy(ptr, &modes[i].dv_timings, size); } spec_bar(); /* Do not copy memory into p_cur block, reuse p_new */ ctrl_signalprops->p_cur.p = ctrl_signalprops->p_new.p; ctrl_imageprops->p_cur.p = ctrl_imageprops->p_new.p; ctrl_controlprops->p_cur.p = ctrl_controlprops->p_new.p; ctrl_dvtimings->p_cur.p = ctrl_dvtimings->p_new.p; return 0; } static int tegra_channel_setup_controls(struct tegra_channel *chan) { int num_sd = 0; struct v4l2_subdev *sd = NULL; struct tegra_mc_vi *vi = chan->vi; struct v4l2_ctrl *ctrl; int i; int ret = 0; /* Clear and reinit control handler - Bug 1956853 */ v4l2_ctrl_handler_free(&chan->ctrl_handler); v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS); /* Initialize the subdev and controls here at first open */ sd = chan->subdev[num_sd]; while ((sd = chan->subdev[num_sd++]) && (num_sd <= chan->num_subdevs)) { /* Add control handler for the subdevice */ ret = v4l2_ctrl_add_handler(&chan->ctrl_handler, sd->ctrl_handler, NULL, false); if (ret || chan->ctrl_handler.error) dev_err(chan->vi->dev, "Failed to add sub-device controls\n"); } /* Add new custom controls */ for (i = 0; i < ARRAY_SIZE(common_custom_ctrls); i++) { switch (common_custom_ctrls[i].id) { case TEGRA_CAMERA_CID_OVERRIDE_ENABLE: /* don't create override control for pg mode */ if (chan->pg_mode) continue; break; case TEGRA_CAMERA_CID_GAIN_TPG: /* Skip the custom control for sensor and * for TPG which doesn't support gain control */ if ((vi->csi == NULL) || (chan->pg_mode && !vi->csi->tpg_gain_ctrl)) continue; break; case TEGRA_CAMERA_CID_GAIN_TPG_EMB_DATA_CFG: /* Skip the custom control for sensor and * for TPG which doesn't support embedded * data with TPG config data. */ if ((vi->csi == NULL) || (chan->pg_mode && !vi->csi->tpg_emb_data_config)) continue; break; default: break; } ctrl = v4l2_ctrl_new_custom(&chan->ctrl_handler, &common_custom_ctrls[i], NULL); if (!ctrl) { dev_err(chan->vi->dev, "Failed to add %s ctrl\n", common_custom_ctrls[i].name); return chan->ctrl_handler.error; } /* Initialize the sensor arrays to have zero elements * This should keep accesses to only the modes * later defined in the DT */ if (ctrl->is_array) ctrl->elems = 0; } vi->fops->vi_add_ctrls(chan); if (chan->pg_mode) { ret = v4l2_ctrl_add_handler(&chan->ctrl_handler, &chan->vi->ctrl_handler, NULL, false); if (ret || chan->ctrl_handler.error) dev_err(chan->vi->dev, "Failed to add VI controls\n"); } /* setup the controls */ ret = v4l2_ctrl_handler_setup(&chan->ctrl_handler); if (ret < 0) goto error; return 0; error: v4l2_ctrl_handler_free(&chan->ctrl_handler); return ret; } static void tegra_channel_free_sensor_properties( const struct v4l2_subdev *sensor_sd) { struct camera_common_data *s_data; struct tegra_csi_device *csi = tegra_get_mc_csi(); struct tegra_csi_channel *chan; if (sensor_sd == NULL) return; s_data = to_camera_common_data(sensor_sd->dev); if (s_data == NULL) return; if (s_data->sensor_props.sensor_modes) devm_kfree(s_data->dev, s_data->sensor_props.sensor_modes); s_data->sensor_props.sensor_modes = NULL; /* remove reference to s_data */ list_for_each_entry(chan, &csi->csi_chans, list) { if (chan->sensor_sd == sensor_sd) chan->s_data = NULL; } } static int tegra_channel_connect_sensor( struct tegra_channel *chan, struct v4l2_subdev *sensor_sd) { struct device *sensor_dev; struct device_node *sensor_of_node; struct tegra_csi_device *csi_device; struct device_node *ep_node; if (!chan) return -EINVAL; if (!sensor_sd) return -EINVAL; sensor_dev = sensor_sd->dev; if (!sensor_dev) return -EINVAL; sensor_of_node = sensor_dev->of_node; if (!sensor_of_node) return -EINVAL; csi_device = tegra_get_mc_csi(); WARN_ON(!csi_device); if (!csi_device) return -ENODEV; for_each_endpoint_of_node(sensor_of_node, ep_node) { struct device_node *csi_chan_of_node; struct tegra_csi_channel *csi_chan; csi_chan_of_node = of_graph_get_remote_port_parent(ep_node); list_for_each_entry(csi_chan, &csi_device->csi_chans, list) { if (csi_chan->of_node == csi_chan_of_node) { csi_chan->s_data = to_camera_common_data(chan->subdev_on_csi->dev); csi_chan->sensor_sd = chan->subdev_on_csi; break; } } of_node_put(csi_chan_of_node); } return 0; } static int map_to_sensor_type(u32 phy_mode) { switch (phy_mode) { case CSI_PHY_MODE_DPHY: return SENSORTYPE_DPHY; case CSI_PHY_MODE_CPHY: return SENSORTYPE_CPHY; case SLVS_EC: return SENSORTYPE_SLVSEC; default: return SENSORTYPE_OTHER; } } static void tegra_channel_get_sensor_peak_vals(struct tegra_channel *chan, u64 *pixelclock, u32 *num_lanes) { int i = 0; u64 val = 0; struct v4l2_subdev *sd = chan->subdev_on_csi; struct camera_common_data *s_data = to_camera_common_data(sd->dev); struct sensor_mode_properties *sensor_mode; if (!s_data) return; for (i = 0; i < s_data->sensor_props.num_modes; i++) { sensor_mode = &s_data->sensor_props.sensor_modes[i]; if (sensor_mode->signal_properties.serdes_pixel_clock.val != 0ULL) val = sensor_mode->signal_properties.serdes_pixel_clock.val; else val = sensor_mode->signal_properties.pixel_clock.val; /* Select the value from the mode with largest pixel rate and lane numbers */ if (*pixelclock < val) *pixelclock = val; if (*num_lanes < sensor_mode->signal_properties.num_lanes) *num_lanes = sensor_mode->signal_properties.num_lanes; } spec_bar(); } static u32 tegra_channel_get_num_lanes(struct tegra_channel *chan) { u32 num_lanes = 0; struct v4l2_subdev *sd = chan->subdev_on_csi; struct camera_common_data *s_data = to_camera_common_data(sd->dev); struct sensor_mode_properties *sensor_mode; if (!s_data) return 0; sensor_mode = &s_data->sensor_props.sensor_modes[0]; num_lanes = sensor_mode->signal_properties.num_lanes; return num_lanes; } static u32 tegra_channel_get_sensor_type(struct tegra_channel *chan) { u32 phy_mode = 0, sensor_type = 0; struct v4l2_subdev *sd = chan->subdev_on_csi; struct camera_common_data *s_data = to_camera_common_data(sd->dev); struct sensor_mode_properties *sensor_mode; if (!s_data) return 0; /* Select phy mode based on the first mode */ sensor_mode = &s_data->sensor_props.sensor_modes[0]; phy_mode = sensor_mode->signal_properties.phy_mode; sensor_type = map_to_sensor_type(phy_mode); return sensor_type; } static u64 tegra_channel_get_max_source_rate(void) { /* WAR: bug 2095503 */ /* TODO very large hard-coded rate based on 4k@60 fps */ /* implement proper functionality here. */ u64 pixelrate = HDMI_IN_RATE; return pixelrate; } static void tegra_channel_populate_dev_info(struct tegra_camera_dev_info *cdev, struct tegra_channel *chan) { u64 pixelclock = 0; u32 max_num_lanes = 0; struct camera_common_data *s_data = to_camera_common_data(chan->subdev_on_csi->dev); if (s_data != NULL) { /* camera sensors */ cdev->sensor_type = tegra_channel_get_sensor_type(chan); tegra_channel_get_sensor_peak_vals(chan, &pixelclock, &max_num_lanes); /* Multiply by CPHY symbols to pixels factor. */ if (cdev->sensor_type == SENSORTYPE_CPHY) pixelclock *= 16/7; cdev->lane_num = tegra_channel_get_num_lanes(chan); } else { if (chan->pg_mode) { /* TPG mode */ cdev->sensor_type = SENSORTYPE_VIRTUAL; #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ } else if (v4l2_subdev_has_op(chan->subdev_on_csi, pad, g_dv_timings)) { #else } else if (v4l2_subdev_has_op(chan->subdev_on_csi, video, g_dv_timings)) { #endif /* HDMI-IN */ cdev->sensor_type = SENSORTYPE_OTHER; pixelclock = tegra_channel_get_max_source_rate(); } else { /* Focusers, no pixel clk and ISO BW, just bail out */ return; } } /* * VI clk scaling for gang mode usecase where 2 CSI bricks * stream through a single VI channel. */ if (max_num_lanes > NUM_LANES_PER_BRICK) cdev->pixel_rate = pixelclock * (max_num_lanes / NUM_LANES_PER_BRICK); else cdev->pixel_rate = pixelclock; cdev->pixel_bit_depth = chan->fmtinfo->width; cdev->bpp = chan->fmtinfo->bpp.numerator; /* BW in kBps */ cdev->bw = cdev->pixel_rate * cdev->bpp / 1024; cdev->bw /= 8; } void tegra_channel_remove_subdevices(struct tegra_channel *chan) { tegra_channel_free_sensor_properties(chan->subdev_on_csi); video_unregister_device(chan->video); chan->video = NULL; chan->num_subdevs = 0; chan->subdev_on_csi = NULL; } EXPORT_SYMBOL(tegra_channel_remove_subdevices); int tegra_channel_init_subdevices(struct tegra_channel *chan) { int ret = 0; struct media_entity *entity; struct media_pad *pad; struct v4l2_subdev *sd; int index = 0; u8 num_sd = 0; struct tegra_camera_dev_info camdev_info; int grp_id = chan->pg_mode ? (TPG_CSI_GROUP_ID + chan->port[0] + 1) : chan->port[0] + 1; int len = 0; /* set_stream of CSI */ #if defined(NV_MEDIA_ENTITY_REMOTE_PAD_PRESENT) /* Linux 6.0 */ pad = media_entity_remote_pad(&chan->pad); #else pad = media_pad_remote_pad_first(&chan->pad); #endif if (!pad) return -ENODEV; entity = pad->entity; sd = media_entity_to_v4l2_subdev(entity); v4l2_set_subdev_hostdata(sd, chan); chan->subdev[num_sd++] = sd; /* verify if the immediate subdevice is slvsec */ chan->is_slvsec = (strstr(sd->name, "slvs") != NULL) ? 1 : 0; /* Add subdev name to this video dev name with vi-output tag*/ len = snprintf(chan->video->name, sizeof(chan->video->name), "%s, %s", "vi-output", sd->name); if (len < 0) return -EINVAL; sd->grp_id = grp_id; chan->grp_id = grp_id; index = pad->index - 1; while (index >= 0) { pad = &entity->pads[index]; if (!(pad->flags & MEDIA_PAD_FL_SINK)) break; #if defined(NV_MEDIA_ENTITY_REMOTE_PAD_PRESENT) /* Linux 6.0 */ pad = media_entity_remote_pad(pad); #else pad = media_pad_remote_pad_first(pad); #endif if (pad == NULL || !tegra_is_v4l2_subdev(pad->entity)) break; if (num_sd >= MAX_SUBDEVICES) break; entity = pad->entity; sd = media_entity_to_v4l2_subdev(entity); v4l2_set_subdev_hostdata(sd, chan); sd->grp_id = grp_id; chan->subdev[num_sd++] = sd; /* Add subdev name to this video dev name with vi-output tag*/ len = snprintf(chan->video->name, sizeof(chan->video->name), "%s, %s", "vi-output", sd->name); if (len < 0) return -EINVAL; index = pad->index - 1; } spec_bar(); /** for num_sd < MAX_SUBDEVICES */ chan->num_subdevs = num_sd; /* * Each CSI channel has only one final remote source, * Mark that subdev as subdev_on_csi */ chan->subdev_on_csi = sd; /* initialize the available formats */ if (chan->num_subdevs) tegra_channel_fmts_bitmap_init(chan); ret = tegra_channel_setup_controls(chan); if (ret < 0) { dev_err(chan->vi->dev, "%s: failed to setup controls\n", __func__); goto fail; } memset(&camdev_info, 0, sizeof(camdev_info)); /* * If subdev on csi is csi or channel is in pg mode * then don't look for sensor props */ if (strstr(chan->subdev_on_csi->name, "nvcsi") != NULL || chan->pg_mode) { tegra_channel_populate_dev_info(&camdev_info, chan); ret = tegra_camera_device_register(&camdev_info, chan); return ret; } ret = tegra_channel_sensorprops_setup(chan); if (ret < 0) { dev_err(chan->vi->dev, "%s: failed to setup sensor props\n", __func__); goto fail; } /* Add a link for the camera_common_data in the tegra_csi_channel. */ ret = tegra_channel_connect_sensor(chan, chan->subdev_on_csi); if (ret < 0) { dev_err(chan->vi->dev, "%s: failed to connect sensor to channel\n", __func__); goto fail; } tegra_channel_populate_dev_info(&camdev_info, chan); ret = tegra_camera_device_register(&camdev_info, chan); return ret; fail: tegra_channel_free_sensor_properties(chan->subdev_on_csi); return ret; } EXPORT_SYMBOL(tegra_channel_init_subdevices); struct v4l2_subdev *tegra_channel_find_linked_csi_subdev( struct tegra_channel *chan) { struct tegra_csi_device *csi = tegra_get_mc_csi(); struct tegra_csi_channel *csi_it; int i = 0; if (!csi) return NULL; list_for_each_entry(csi_it, &csi->csi_chans, list) { for (i = 0; i < chan->num_subdevs; i++) if (chan->subdev[i] == &csi_it->subdev) return chan->subdev[i]; } return NULL; } EXPORT_SYMBOL(tegra_channel_find_linked_csi_subdev); static int tegra_channel_get_format(struct file *file, void *fh, struct v4l2_format *format) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_pix_format *pix = &format->fmt.pix; *pix = chan->format; return 0; } static int __tegra_channel_try_format(struct tegra_channel *chan, struct v4l2_pix_format *pix) { const struct tegra_video_format *vfmt; struct v4l2_subdev_format fmt; struct v4l2_subdev *sd = chan->subdev_on_csi; struct v4l2_subdev_pad_config pad_cfg; struct v4l2_subdev_state cfg = {.pads = &pad_cfg}; int ret = 0; /* Use the channel format if pixformat is not supported */ vfmt = tegra_core_get_format_by_fourcc(chan, pix->pixelformat); if (!vfmt) { pix->pixelformat = chan->format.pixelformat; vfmt = tegra_core_get_format_by_fourcc(chan, pix->pixelformat); if (!vfmt) return -EINVAL; } fmt.which = V4L2_SUBDEV_FORMAT_TRY; fmt.pad = 0; v4l2_fill_mbus_format(&fmt.format, pix, vfmt->code); ret = v4l2_subdev_call(sd, pad, set_fmt, &cfg, &fmt); if (ret == -ENOIOCTLCMD) return -ENOTTY; v4l2_fill_pix_format(pix, &fmt.format); tegra_channel_fmt_align(chan, vfmt, &pix->width, &pix->height, &pix->bytesperline); pix->sizeimage = get_aligned_buffer_size(chan, pix->bytesperline, pix->height); if (chan->fmtinfo->fourcc == V4L2_PIX_FMT_NV16) { if (__builtin_umul_overflow(pix->sizeimage, 2, &pix->sizeimage)) { dev_err(chan->vi->dev, "%s: update size image failed due to an overflow\n", __func__); return -EOVERFLOW; } } return ret; } static int tegra_channel_try_format(struct file *file, void *fh, struct v4l2_format *format) { struct tegra_channel *chan = video_drvdata(file); return __tegra_channel_try_format(chan, &format->fmt.pix); } static int __tegra_channel_set_format(struct tegra_channel *chan, struct v4l2_pix_format *pix) { const struct tegra_video_format *vfmt; struct v4l2_subdev_format fmt; struct v4l2_subdev *sd = chan->subdev_on_csi; struct v4l2_subdev_pad_config pad_cfg; struct v4l2_subdev_state cfg = {.pads = &pad_cfg}; int ret = 0; vfmt = tegra_core_get_format_by_fourcc(chan, pix->pixelformat); if (!vfmt) return -EINVAL; fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; fmt.pad = 0; v4l2_fill_mbus_format(&fmt.format, pix, vfmt->code); ret = v4l2_subdev_call(sd, pad, set_fmt, &cfg, &fmt); if (ret == -ENOIOCTLCMD) return -ENOTTY; v4l2_fill_pix_format(pix, &fmt.format); if (!ret) { chan->format = *pix; chan->fmtinfo = vfmt; if (chan->preferred_stride) pix->bytesperline = chan->preferred_stride; tegra_channel_update_format(chan, pix->width, pix->height, vfmt->fourcc, &vfmt->bpp, pix->bytesperline); *pix = chan->format; if (chan->total_ports > 1) update_gang_mode(chan); } return ret; } static int tegra_channel_set_format(struct file *file, void *fh, struct v4l2_format *format) { struct tegra_channel *chan = video_drvdata(file); int ret = 0; /* get the suppod format by try_fmt */ ret = __tegra_channel_try_format(chan, &format->fmt.pix); if (ret) return ret; if (vb2_is_busy(&chan->queue)) return -EBUSY; return __tegra_channel_set_format(chan, &format->fmt.pix); } 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_enum_input(struct file *file, void *fh, struct v4l2_input *inp) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd_on_csi = chan->subdev_on_csi; int ret, len; if (inp->index) return -EINVAL; ret = v4l2_device_call_until_err(chan->video->v4l2_dev, chan->grp_id, video, g_input_status, &inp->status); if (ret == -ENODEV || sd_on_csi == NULL) return -ENODEV; inp->type = V4L2_INPUT_TYPE_CAMERA; #if defined(NV_V4L2_SUBDEV_PAD_OPS_STRUCT_HAS_DV_TIMINGS) /* Linux v6.10 */ if (v4l2_subdev_has_op(sd_on_csi, pad, s_dv_timings)) { #else if (v4l2_subdev_has_op(sd_on_csi, video, s_dv_timings)) { #endif inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; len = snprintf(inp->name, sizeof(inp->name), "HDMI %u", chan->port[0]); if (len < 0) return -EINVAL; } else { len = snprintf(inp->name, sizeof(inp->name), "Camera %u", chan->port[0]); if (len < 0) return -EINVAL; } return ret; } 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 i) { if (i > 0) return -EINVAL; return 0; } static int tegra_channel_log_status(struct file *file, void *priv) { struct tegra_channel *chan = video_drvdata(file); v4l2_device_call_all(chan->video->v4l2_dev, chan->grp_id, core, log_status); return 0; } static long tegra_channel_default_ioctl(struct file *file, void *fh, bool use_prio, unsigned int cmd, void *arg) { struct tegra_channel *chan = video_drvdata(file); struct tegra_mc_vi *vi = chan->vi; long ret = -ENOTTY; if (vi->fops && vi->fops->vi_default_ioctl) ret = vi->fops->vi_default_ioctl(file, fh, use_prio, cmd, arg); return ret; } /* * Implemented vidioc_s_parm and vidioc_g_parm ioctl to support multiple frame * rates. */ static int tegra_channel_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; return v4l2_s_parm_cap(chan->video, sd, a); } static int tegra_channel_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) { struct tegra_channel *chan = video_drvdata(file); struct v4l2_subdev *sd = chan->subdev_on_csi; return v4l2_g_parm_cap(chan->video, sd, a); } static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = { .vidioc_querycap = tegra_channel_querycap, .vidioc_enum_framesizes = tegra_channel_enum_framesizes, .vidioc_enum_frameintervals = tegra_channel_enum_frameintervals, .vidioc_s_parm = tegra_channel_s_parm, .vidioc_g_parm = tegra_channel_g_parm, .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_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_g_edid = tegra_channel_g_edid, .vidioc_s_edid = tegra_channel_s_edid, .vidioc_s_dv_timings = tegra_channel_s_dv_timings, .vidioc_g_dv_timings = tegra_channel_g_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_subscribe_event = tegra_channel_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_enum_input = tegra_channel_enum_input, .vidioc_g_input = tegra_channel_g_input, .vidioc_s_input = tegra_channel_s_input, .vidioc_log_status = tegra_channel_log_status, .vidioc_default = tegra_channel_default_ioctl, }; static int tegra_channel_close(struct file *fp); static int tegra_channel_open(struct file *fp) { int ret; struct video_device *vdev = video_devdata(fp); struct tegra_channel *chan = video_drvdata(fp); struct tegra_mc_vi *vi; struct tegra_csi_device *csi; trace_tegra_channel_open(vdev->name); mutex_lock(&chan->video_lock); ret = v4l2_fh_open(fp); if (ret || !v4l2_fh_is_singular_file(fp)) { mutex_unlock(&chan->video_lock); return ret; } if (chan->subdev[0] == NULL) { ret = -ENODEV; goto fail; } vi = chan->vi; csi = vi->csi; chan->fh = (struct v4l2_fh *)fp->private_data; if (tegra_channel_verify_focuser(chan)) { if ((chan->num_subdevs <= 0) || (chan->num_subdevs > MAX_SUBDEVICES)) { dev_err(chan->vi->dev, "%s: set power failed due to an invalid num_subdevs value\n", __func__); return -EINVAL; } ret = tegra_channel_set_power(chan, true); if (ret < 0) return ret; } mutex_unlock(&chan->video_lock); return 0; fail: _vb2_fop_release(fp, NULL); mutex_unlock(&chan->video_lock); return ret; } static int tegra_channel_close(struct file *fp) { int ret = 0; struct video_device *vdev = video_devdata(fp); struct tegra_channel *chan = video_drvdata(fp); struct tegra_mc_vi *vi = chan->vi; bool is_singular; trace_tegra_channel_close(vdev->name); mutex_lock(&chan->video_lock); is_singular = v4l2_fh_is_singular_file(fp); ret = _vb2_fop_release(fp, NULL); if (!is_singular) { mutex_unlock(&chan->video_lock); return ret; } if (tegra_channel_verify_focuser(chan)) { ret = tegra_channel_set_power(chan, false); if (ret < 0) dev_err(vi->dev, "Failed to power off subdevices\n"); } mutex_unlock(&chan->video_lock); return ret; } /* ----------------------------------------------------------------------------- * V4L2 file operations */ static const struct v4l2_file_operations tegra_channel_fops = { .owner = THIS_MODULE, .unlocked_ioctl = video_ioctl2, #ifdef CONFIG_COMPAT .compat_ioctl32 = video_ioctl2, #endif .open = tegra_channel_open, .release = tegra_channel_close, .read = vb2_fop_read, .poll = vb2_fop_poll, .mmap = vb2_fop_mmap, }; int tegra_vi_get_port_info(struct tegra_channel *chan, struct device_node *node, unsigned int index) { struct device_node *ep = NULL; struct device_node *ports; struct device_node *port; int value = 0xFFFF; int ret = 0; u32 i = 0; ports = of_get_child_by_name(node, "ports"); if (ports == NULL) ports = node; for_each_child_of_node(ports, port) { if (!port->name || of_node_cmp(port->name, "port")) continue; ret = of_property_read_u32(port, "reg", &value); if (ret < 0) continue; if (value != index) continue; for_each_child_of_node(port, ep) { if (!ep->name || of_node_cmp(ep->name, "endpoint")) continue; /* Get virtual channel id */ ret = of_property_read_u32(ep, "vc-id", &value); /* vc-id is optional, default is 0 */ chan->virtual_channel = (ret < 0) ? 0 : value; /* Consider max simultaneous sensor streams to be 16 */ if (value > 16) { dev_err(chan->vi->dev, "vc id >16!\n"); return -EINVAL; } /* Get CSI port */ ret = of_property_read_u32(ep, "port-index", &value); if (ret < 0) dev_err(chan->vi->dev, "port index error\n"); chan->port[0] = value; if (value > NVCSI_PORT_H) { dev_err(chan->vi->dev, "port index >%d!\n", NVCSI_PORT_H); return -EINVAL; } /* Get number of data lanes for the endpoint */ ret = of_property_read_u32(ep, "bus-width", &value); if (ret < 0) dev_err(chan->vi->dev, "num lanes error\n"); chan->numlanes = value; if (value > 12) { dev_err(chan->vi->dev, "num lanes >12!\n"); return -EINVAL; } /* * for numlanes greater than 4 multiple CSI bricks * are needed to capture the image, the logic below * checks for numlanes > 4 and add a new CSI brick * as a valid port. Loops around the three CSI * bricks to add as many ports necessary. */ value -= 4; for (i = 1; value > 0 && i < TEGRA_CSI_BLOCKS; i++, value -= 4) { int next_port = chan->port[i-1] + 2; next_port = (next_port % (NVCSI_PORT_H + 1)); chan->port[i] = next_port; } } } return ret; } static int tegra_channel_csi_init(struct tegra_channel *chan) { int idx = 0; struct tegra_mc_vi *vi = chan->vi; int ret = 0; unsigned int portnum = 0; chan->gang_mode = CAMERA_NO_GANG_MODE; chan->total_ports = 0; memset(&chan->port[0], INVALID_CSI_PORT, TEGRA_CSI_BLOCKS); memset(chan->syncpoint_fifo, 0, sizeof(chan->syncpoint_fifo)); if (chan->pg_mode) { /* If VI has 4 existing channels, chan->id will start * from 4 for the first TPG channel, which uses PORT_A(0). * To get the correct PORT number, subtract existing number of * channels from chan->id. */ if (__builtin_usub_overflow(chan->id, vi->num_channels, &portnum)) { dev_err(vi->dev, "%s: csi init failed due to an overflow\n", __func__); return -EOVERFLOW; } chan->port[0] = portnum % NUM_TPG_INSTANCE; chan->virtual_channel = portnum / NUM_TPG_INSTANCE; WARN_ON(chan->port[0] > vi->csi->num_tpg_channels); chan->numlanes = 2; } else { ret = tegra_vi_get_port_info(chan, vi->dev->of_node, chan->id); if (ret) { dev_err(vi->dev, "%s:Fail to parse port info\n", __func__); return ret; } } for (idx = 0; idx < TEGRA_CSI_BLOCKS && csi_port_is_valid(chan->port[idx]); idx++) { chan->total_ports++; /* maximum of 4 lanes are present per CSI block */ chan->csibase[idx] = vi->iomem + TEGRA_VI_CSI_BASE(chan->port[idx]); } /* based on gang mode valid ports will be updated - set default to 1 */ chan->valid_ports = chan->total_ports ? 1 : 0; return ret; } int tegra_channel_init_video(struct tegra_channel *chan) { struct tegra_mc_vi *vi = chan->vi; int ret = 0, len = 0; unsigned int portnum = 0; if (chan->video) { dev_err(&chan->video->dev, "video device already allocated\n"); return 0; } chan->video = video_device_alloc(); if (chan->video == NULL) { dev_err(vi->dev, "%s: video device alloc error\n", __func__); return -EINVAL; } /* Initialize the media entity... */ chan->pad.flags = MEDIA_PAD_FL_SINK; ret = tegra_media_entity_init(&chan->video->entity, 1, &chan->pad, false, false); if (ret < 0) { video_device_release(chan->video); dev_err(&chan->video->dev, "failed to init video entity\n"); return ret; } /* init control handler */ ret = v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS); if (chan->ctrl_handler.error) { dev_err(&chan->video->dev, "failed to init control handler\n"); goto ctrl_init_error; } /* init video node... */ chan->video->fops = &tegra_channel_fops; chan->video->v4l2_dev = &vi->v4l2_dev; chan->video->queue = &chan->queue; if (chan->pg_mode) { if (__builtin_usub_overflow(chan->id, vi->num_channels, &portnum)) { dev_err(&chan->video->dev, "%s: video device init failed due to an overflow\n", __func__); ret = -EOVERFLOW; goto ctrl_init_error; } } else { portnum = chan->port[0]; } len = snprintf(chan->video->name, sizeof(chan->video->name), "%s-%s-%u", dev_name(vi->dev), chan->pg_mode ? "tpg" : "output", portnum); if (len < 0) { ret = -EINVAL; goto ctrl_init_error; } chan->video->vfl_type = VFL_TYPE_VIDEO; chan->video->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; chan->video->device_caps |= V4L2_CAP_EXT_PIX_FORMAT; chan->video->vfl_dir = VFL_DIR_RX; chan->video->release = video_device_release_empty; chan->video->ioctl_ops = &tegra_channel_ioctl_ops; chan->video->ctrl_handler = &chan->ctrl_handler; chan->video->lock = &chan->video_lock; video_set_drvdata(chan->video, chan); return ret; ctrl_init_error: media_entity_cleanup(&chan->video->entity); v4l2_ctrl_handler_free(&chan->ctrl_handler); video_device_release(chan->video); return ret; } EXPORT_SYMBOL(tegra_channel_init_video); int tegra_channel_init(struct tegra_channel *chan) { int ret; struct tegra_mc_vi *vi = chan->vi; struct device *vi_unit_dev; ret = tegra_channel_csi_init(chan); if (ret) return ret; /* * The VI device instance has to be retrieved after CSI channel * has been initialized. This will make sure the TPG ports are * setup correctly */ vi_unit_dev = tegra_channel_get_vi_unit(chan); chan->width_align = TEGRA_WIDTH_ALIGNMENT; chan->stride_align = TEGRA_STRIDE_ALIGNMENT; chan->height_align = TEGRA_HEIGHT_ALIGNMENT; chan->size_align = size_align_ctrl_qmenu[TEGRA_SIZE_ALIGNMENT]; chan->num_subdevs = 0; mutex_init(&chan->video_lock); chan->capture_descr_index = 0; chan->capture_descr_sequence = 0; INIT_LIST_HEAD(&chan->capture); INIT_LIST_HEAD(&chan->release); INIT_LIST_HEAD(&chan->entities); init_waitqueue_head(&chan->start_wait); init_waitqueue_head(&chan->release_wait); atomic_set(&chan->restart_version, 1); chan->capture_version = 0; spin_lock_init(&chan->start_lock); spin_lock_init(&chan->release_lock); INIT_LIST_HEAD(&chan->dequeue); init_waitqueue_head(&chan->dequeue_wait); spin_lock_init(&chan->dequeue_lock); mutex_init(&chan->stop_kthread_lock); init_rwsem(&chan->reset_lock); atomic_set(&chan->is_streaming, DISABLE); spin_lock_init(&chan->capture_state_lock); spin_lock_init(&chan->buffer_lock); /* Init video format */ vi->fops->vi_init_video_formats(chan); chan->fmtinfo = tegra_core_get_default_format(); tegra_channel_update_format(chan, TEGRA_DEF_WIDTH, TEGRA_DEF_HEIGHT, chan->fmtinfo->fourcc, &chan->fmtinfo->bpp, chan->preferred_stride); chan->buffer_offset[0] = 0; /* Init bpl factor to 1, will be overidden based on interlace_type */ chan->interlace_bplfactor = 1; #if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) /* get the buffers queue... */ ret = tegra_vb2_dma_init(vi_unit_dev, &chan->alloc_ctx, SZ_64K, &vi->vb2_dma_alloc_refcnt); if (ret < 0) goto vb2_init_error; #endif chan->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; chan->queue.io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ | VB2_USERPTR; 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; #if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) chan->queue.mem_ops = &vb2_dma_contig_memops; #endif chan->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC | V4L2_BUF_FLAG_TSTAMP_SRC_EOF; ret = vb2_queue_init(&chan->queue); if (ret < 0) { dev_err(chan->vi->dev, "failed to initialize VB2 queue\n"); goto vb2_queue_error; } chan->deskew_ctx = devm_kzalloc(vi->dev, sizeof(struct nvcsi_deskew_context), GFP_KERNEL); if (!chan->deskew_ctx) { ret = -ENOMEM; goto deskew_ctx_err; } chan->init_done = true; return 0; deskew_ctx_err: devm_kfree(vi->dev, chan->deskew_ctx); vb2_queue_error: #if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) tegra_vb2_dma_cleanup(vi_unit_dev, chan->alloc_ctx, &vi->vb2_dma_alloc_refcnt); vb2_init_error: #endif v4l2_ctrl_handler_free(&chan->ctrl_handler); return ret; } EXPORT_SYMBOL(tegra_channel_init); int tegra_channel_cleanup_video(struct tegra_channel *chan) { v4l2_ctrl_handler_free(&chan->ctrl_handler); media_entity_cleanup(&chan->video->entity); video_device_release(chan->video); return 0; } EXPORT_SYMBOL(tegra_channel_cleanup_video); int tegra_channel_cleanup(struct tegra_channel *chan) { struct device *vi_unit_dev = tegra_channel_get_vi_unit(chan); /* release embedded data buffer */ if (chan->emb_buf_size > 0) { dma_free_coherent(vi_unit_dev, chan->emb_buf_size, chan->emb_buf_addr, chan->emb_buf); chan->emb_buf_size = 0; } tegra_channel_dealloc_buffer_queue(chan); v4l2_ctrl_handler_free(&chan->ctrl_handler); mutex_lock(&chan->video_lock); vb2_queue_release(&chan->queue); #if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) tegra_vb2_dma_cleanup(vi_unit_dev, chan->alloc_ctx, &chan->vi->vb2_dma_alloc_refcnt); #endif mutex_unlock(&chan->video_lock); tegra_camera_device_unregister(chan); return 0; } EXPORT_SYMBOL(tegra_channel_cleanup); void tegra_vi_channels_unregister(struct tegra_mc_vi *vi) { struct tegra_channel *it; list_for_each_entry(it, &vi->vi_chans, list) { if (it->video->cdev != NULL) video_unregister_device(it->video); } } EXPORT_SYMBOL(tegra_vi_channels_unregister); int tegra_vi_mfi_work(struct tegra_mc_vi *vi, int channel) { if (vi->fops) return vi->fops->vi_mfi_work(vi, channel); return 0; } EXPORT_SYMBOL(tegra_vi_mfi_work); int tegra_vi_channels_init(struct tegra_mc_vi *vi) { int ret = 0; struct tegra_channel *it; int count = 0; list_for_each_entry(it, &vi->vi_chans, list) { it->vi = vi; ret = tegra_channel_init(it); if (ret < 0) { dev_err(vi->dev, "channel init failed\n"); continue; } count++; } if (count == 0) { dev_err(vi->dev, "all channel init failed\n"); return ret; } return 0; } EXPORT_SYMBOL(tegra_vi_channels_init); int tegra_vi_channels_cleanup(struct tegra_mc_vi *vi) { int ret = 0, err = 0; struct tegra_channel *it; list_for_each_entry(it, &vi->vi_chans, list) { if (!it->init_done) continue; err = tegra_channel_cleanup(it); if (err < 0) { ret = err; dev_err(vi->dev, "channel cleanup failed, err %d\n", err); } } return ret; } EXPORT_SYMBOL(tegra_vi_channels_cleanup);