diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-07 13:17:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-07 13:17:52 +0000 |
commit | 3afb00d3f86d3d924f88b56fa8285d4e9db85852 (patch) | |
tree | 95a985d3019522cea546b7d8df621369bc44fc6c /drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | |
parent | Adding debian version 6.9.12-1. (diff) | |
download | linux-3afb00d3f86d3d924f88b56fa8285d4e9db85852.tar.xz linux-3afb00d3f86d3d924f88b56fa8285d4e9db85852.zip |
Merging upstream version 6.10.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/pci/intel/ipu6/ipu6-isys-queue.c')
-rw-r--r-- | drivers/media/pci/intel/ipu6/ipu6-isys-queue.c | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c new file mode 100644 index 0000000000..4bd4e324ab --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013--2024 Intel Corporation + */ +#include <linux/atomic.h> +#include <linux/bug.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <media/media-entity.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-dma-contig.h> +#include <media/videobuf2-v4l2.h> + +#include "ipu6-bus.h" +#include "ipu6-fw-isys.h" +#include "ipu6-isys.h" +#include "ipu6-isys-video.h" + +static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 size = ipu6_isys_get_data_size(av); + + /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ + if (!*num_planes) { + sizes[0] = size; + } else if (sizes[0] < size) { + dev_dbg(dev, "%s: queue setup: size %u < %u\n", + av->vdev.name, sizes[0], size); + return -EINVAL; + } + + *num_planes = 1; + + return 0; +} + +static int ipu6_isys_buf_prepare(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 bytesperline = ipu6_isys_get_bytes_per_line(av); + u32 height = ipu6_isys_get_frame_height(av); + u32 size = ipu6_isys_get_data_size(av); + + dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", + av->vdev.name, size, vb2_plane_size(vb, 0)); + + if (size > vb2_plane_size(vb, 0)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, bytesperline * height); + + return 0; +} + +/* + * Queue a buffer list back to incoming or active queues. The buffers + * are removed from the buffer list. + */ +void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state) +{ + struct ipu6_isys_buffer *ib, *ib_safe; + unsigned long flags; + bool first = true; + + if (!bl) + return; + + WARN_ON_ONCE(!bl->nbufs); + WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE && + op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING); + + list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { + struct ipu6_isys_video *av; + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct ipu6_isys_queue *aq = + vb2_queue_to_isys_queue(vb->vb2_queue); + struct device *dev; + + av = ipu6_isys_queue_to_video(aq); + dev = &av->isys->adev->auxdev.dev; + spin_lock_irqsave(&aq->lock, flags); + list_del(&ib->head); + if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE) + list_add(&ib->head, &aq->active); + else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING) + list_add_tail(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE) + vb2_buffer_done(vb, state); + + if (first) { + dev_dbg(dev, + "queue buf list %p flags %lx, s %d, %d bufs\n", + bl, op_flags, state, bl->nbufs); + first = false; + } + + bl->nbufs--; + } + + WARN_ON(bl->nbufs); +} + +/* + * flush_firmware_streamon_fail() - Flush in cases where requests may + * have been queued to firmware and the *firmware streamon fails for a + * reason or another. + */ +static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + + lockdep_assert_held(&stream->mutex); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib, *ib_safe; + + spin_lock_irqsave(&aq->lock, flags); + list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { + struct vb2_buffer *vb = + ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + if (av->streaming) { + dev_dbg(dev, + "%s: queue buffer %u back to incoming\n", + av->vdev.name, vb->index); + /* Queue already streaming, return to driver. */ + list_add(&ib->head, &aq->incoming); + continue; + } + /* Queue not yet streaming, return to user. */ + dev_dbg(dev, "%s: return %u back to videobuf2\n", + av->vdev.name, vb->index); + vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib), + VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&aq->lock, flags); + } +} + +/* + * Attempt obtaining a buffer list from the incoming queues, a list of buffers + * that contains one entry from each video buffer queue. If a buffer can't be + * obtained from every queue, the buffers are returned back to the queue. + */ +static int buffer_list_get(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; + + bl->nbufs = 0; + INIT_LIST_HEAD(&bl->head); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_buffer *ib; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->incoming)) { + spin_unlock_irqrestore(&aq->lock, flags); + if (!list_empty(&bl->head)) + ipu6_isys_buffer_list_queue(bl, buf_flag, 0); + return -ENODATA; + } + + ib = list_last_entry(&aq->incoming, + struct ipu6_isys_buffer, head); + + dev_dbg(dev, "buffer: %s: buffer %u\n", + ipu6_isys_queue_to_video(aq)->vdev.name, + ipu6_isys_buffer_to_vb2_buffer(ib)->index); + list_del(&ib->head); + list_add(&ib->head, &bl->head); + spin_unlock_irqrestore(&aq->lock, flags); + + bl->nbufs++; + } + + dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); + + return 0; +} + +static void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + + set->output_pins[aq->fw_output].addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) +void +ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + set->send_irq_sof = 1; + set->send_resp_sof = 1; + set->send_irq_eof = 0; + set->send_resp_eof = 0; + + if (stream->streaming) + set->send_irq_capture_ack = 0; + else + set->send_irq_capture_ack = 1; + set->send_irq_capture_done = 0; + + set->send_resp_capture_ack = 1; + set->send_resp_capture_done = 1; + if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { + set->send_resp_capture_ack = 0; + set->send_resp_capture_done = 0; + } + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); + } +} + +/* Start streaming for real. The buffer list must be available. */ +static int ipu6_isys_stream_start(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl, bool error) +{ + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_buffer_list __bl; + int ret; + + mutex_lock(&stream->isys->stream_mutex); + ret = ipu6_isys_video_set_streaming(av, 1, bl); + mutex_unlock(&stream->isys->stream_mutex); + if (ret) + goto out_requeue; + + stream->streaming = 1; + + bl = &__bl; + + do { + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct isys_fw_msgs *msg; + u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; + + ret = buffer_list_get(stream, bl); + if (ret < 0) + break; + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + ipu6_fw_isys_dump_frame_buff_set(dev, buf, + stream->nr_output_pins); + ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, + 0); + ret = ipu6_fw_isys_complex_cmd(stream->isys, + stream->stream_handle, buf, + msg->dma_addr, sizeof(*buf), + send_type); + } while (!WARN_ON(ret)); + + return 0; + +out_requeue: + if (bl && bl->nbufs) + ipu6_isys_buffer_list_queue(bl, + IPU6_ISYS_BUFFER_LIST_FL_INCOMING | + (error ? + IPU6_ISYS_BUFFER_LIST_FL_SET_STATE : + 0), error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + flush_firmware_streamon_fail(stream); + + return ret; +} + +static void buf_queue(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_video_buffer *ivb = + vb2_buffer_to_ipu6_isys_video_buffer(vvb); + struct ipu6_isys_buffer *ib = &ivb->ib; + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pipeline *media_pipe = + media_entity_pipeline(&av->vdev.entity); + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_buffer_list bl; + struct isys_fw_msgs *msg; + unsigned long flags; + dma_addr_t dma; + int ret; + + dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); + + dma = vb2_dma_contig_plane_dma_addr(vb, 0); + dev_dbg(dev, "iova: iova %pad\n", &dma); + + spin_lock_irqsave(&aq->lock, flags); + list_add(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (!media_pipe || !vb->vb2_queue->start_streaming_called) { + dev_dbg(dev, "media pipeline is not ready for %s\n", + av->vdev.name); + return; + } + + mutex_lock(&stream->mutex); + + if (stream->nr_streaming != stream->nr_queues) { + dev_dbg(dev, "not streaming yet, adding to incoming\n"); + goto out; + } + + /* + * We just put one buffer to the incoming list of this queue + * (above). Let's see whether all queues in the pipeline would + * have a buffer. + */ + ret = buffer_list_get(stream, &bl); + if (ret < 0) { + dev_dbg(dev, "No buffers available\n"); + goto out; + } + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out; + } + + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); + ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + + if (!stream->streaming) { + ret = ipu6_isys_stream_start(av, &bl, true); + if (ret) + dev_err(dev, "stream start failed.\n"); + goto out; + } + + /* + * We must queue the buffers in the buffer list to the + * appropriate video buffer queues BEFORE passing them to the + * firmware since we could get a buffer event back before we + * have queued them ourselves to the active queue. + */ + ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + + ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, + buf, msg->dma_addr, sizeof(*buf), + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + if (ret < 0) + dev_err(dev, "send stream capture failed\n"); + +out: + mutex_unlock(&stream->mutex); +} + +static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) +{ + struct v4l2_mbus_framefmt format; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *remote_pad = + media_pad_remote_pad_first(av->vdev.entity.pads); + struct v4l2_subdev *sd; + u32 r_stream, code; + int ret; + + if (!remote_pad) + return -ENOTCONN; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); + + ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, + &format); + + if (ret) { + dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", + sd->entity.name, remote_pad->index, r_stream); + return ret; + } + + if (format.width != ipu6_isys_get_frame_width(av) || + format.height != ipu6_isys_get_frame_height(av)) { + dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", + ipu6_isys_get_frame_width(av), + ipu6_isys_get_frame_height(av), format.width, + format.height); + return -EINVAL; + } + + code = ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0)->code; + if (format.code != code) { + dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", + code, format.code); + return -EINVAL; + } + + return 0; +} + +static void return_buffers(struct ipu6_isys_queue *aq, + enum vb2_buffer_state state) +{ + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib; + bool need_reset = false; + unsigned long flags; + + spin_lock_irqsave(&aq->lock, flags); + while (!list_empty(&aq->incoming)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + } + + /* + * Something went wrong (FW crash / HW hang / not all buffers + * returned from isys) if there are still buffers queued in active + * queue. We have to clean up places a bit. + */ + while (!list_empty(&aq->active)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->active, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + need_reset = true; + } + + spin_unlock_irqrestore(&aq->lock, flags); + + if (need_reset) { + mutex_lock(&av->isys->mutex); + av->isys->need_reset = true; + mutex_unlock(&av->isys->mutex); + } +} + +static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) +{ + video_device_pipeline_stop(&av->vdev); + ipu6_isys_put_stream(av->stream); + av->stream = NULL; +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_isys_format(ipu6_isys_get_format(av), 0); + struct ipu6_isys_buffer_list __bl, *bl = NULL; + struct ipu6_isys_stream *stream; + struct media_entity *source_entity = NULL; + int nr_queues, ret; + + dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", + av->vdev.name, ipu6_isys_get_frame_width(av), + ipu6_isys_get_frame_height(av), pfmt->css_pixelformat); + + ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); + if (ret < 0) { + dev_dbg(dev, "failed to setup video\n"); + goto out_return_buffers; + } + + ret = ipu6_isys_link_fmt_validate(aq); + if (ret) { + dev_dbg(dev, + "%s: link format validation failed (%d)\n", + av->vdev.name, ret); + goto out_pipeline_stop; + } + + ret = ipu6_isys_fw_open(av->isys); + if (ret) + goto out_pipeline_stop; + + stream = av->stream; + mutex_lock(&stream->mutex); + if (!stream->nr_streaming) { + ret = ipu6_isys_video_prepare_stream(av, source_entity, + nr_queues); + if (ret) + goto out_fw_close; + } + + stream->nr_streaming++; + dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, + stream->nr_queues); + + list_add(&aq->node, &stream->queues); + ipu6_isys_set_csi2_streams_status(av, true); + ipu6_isys_configure_stream_watermark(av, true); + ipu6_isys_update_stream_watermark(av, true); + + if (stream->nr_streaming != stream->nr_queues) + goto out; + + bl = &__bl; + ret = buffer_list_get(stream, bl); + if (ret < 0) { + dev_warn(dev, "no buffer available, DRIVER BUG?\n"); + goto out; + } + + ret = ipu6_isys_stream_start(av, bl, false); + if (ret) + goto out_stream_start; + +out: + mutex_unlock(&stream->mutex); + + return 0; + +out_stream_start: + ipu6_isys_update_stream_watermark(av, false); + list_del(&aq->node); + stream->nr_streaming--; + +out_fw_close: + mutex_unlock(&stream->mutex); + ipu6_isys_fw_close(av->isys); + +out_pipeline_stop: + ipu6_isys_stream_cleanup(av); + +out_return_buffers: + return_buffers(aq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_stream *stream = av->stream; + + ipu6_isys_set_csi2_streams_status(av, false); + + mutex_lock(&stream->mutex); + + ipu6_isys_update_stream_watermark(av, false); + + mutex_lock(&av->isys->stream_mutex); + if (stream->nr_streaming == stream->nr_queues && stream->streaming) + ipu6_isys_video_set_streaming(av, 0, NULL); + mutex_unlock(&av->isys->stream_mutex); + + stream->nr_streaming--; + list_del(&aq->node); + stream->streaming = 0; + mutex_unlock(&stream->mutex); + + ipu6_isys_stream_cleanup(av); + + return_buffers(aq, VB2_BUF_STATE_ERROR); + + ipu6_isys_fw_close(av->isys); +} + +static unsigned int +get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info) +{ + u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + + /* + * The timestamp is invalid as no TSC in some FPGA platform, + * so get the sequence from pipeline directly in this case. + */ + if (time == 0) + return atomic_read(&stream->sequence) - 1; + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + if (time == stream->seq[i].timestamp) { + dev_dbg(dev, "sof: using seq nr %u for ts %llu\n", + stream->seq[i].sequence, time); + return stream->seq[i].sequence; + } + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n", + stream->seq[i].sequence, stream->seq[i].timestamp); + + return 0; +} + +static u64 get_sof_ns_delta(struct ipu6_isys_video *av, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct ipu6_bus_device *adev = av->isys->adev; + struct ipu6_device *isp = adev->isp; + u64 delta, tsc_now; + + ipu6_buttress_tsc_read(isp, &tsc_now); + if (!tsc_now) + return 0; + + delta = tsc_now - ((u64)info->timestamp[1] << 32 | info->timestamp[0]); + + return ipu6_buttress_tsc_ticks_to_ns(delta, isp); +} + +void ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + u64 ns; + u32 sequence; + + ns = ktime_get_ns() - get_sof_ns_delta(av, info); + sequence = get_sof_sequence_by_timestamp(stream, info); + + vbuf->vb2_buf.timestamp = ns; + vbuf->sequence = sequence; + + dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", + av->vdev.name, ktime_get_ns(), sequence); + dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, + vbuf->vb2_buf.timestamp); +} + +void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + if (atomic_read(&ib->str2mmio_flag)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + /* + * Operation on buffer is ended with error and will be reported + * to the userspace when it is de-queued + */ + atomic_set(&ib->str2mmio_flag, 0); + } else { + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } +} + +void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct ipu6_isys_queue *aq = stream->output_pins[info->pin_id].aq; + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_isys_buffer *ib; + struct vb2_buffer *vb; + unsigned long flags; + bool first = true; + struct vb2_v4l2_buffer *buf; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->active)) { + spin_unlock_irqrestore(&aq->lock, flags); + dev_err(dev, "active queue empty\n"); + return; + } + + list_for_each_entry_reverse(ib, &aq->active, head) { + dma_addr_t addr; + + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (info->pin.addr != addr) { + if (first) + dev_err(dev, "Unexpected buffer address %pad\n", + &addr); + first = false; + continue; + } + + if (info->error_info.error == + IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { + /* + * Check for error message: + * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' + */ + atomic_set(&ib->str2mmio_flag, 1); + } + dev_dbg(dev, "buffer: found buffer %pad\n", &addr); + + buf = to_vb2_v4l2_buffer(vb); + buf->field = V4L2_FIELD_NONE; + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + ipu6_isys_buf_calc_sequence_time(ib, info); + + ipu6_isys_queue_buf_done(ib); + + return; + } + + dev_err(dev, "Failed to find a matching video buffer"); + + spin_unlock_irqrestore(&aq->lock, flags); +} + +static const struct vb2_ops ipu6_isys_queue_ops = { + .queue_setup = queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = ipu6_isys_buf_prepare, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .buf_queue = buf_queue, +}; + +int ipu6_isys_queue_init(struct ipu6_isys_queue *aq) +{ + struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + int ret; + + /* no support for userptr */ + if (!aq->vbq.io_modes) + aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; + + aq->vbq.drv_priv = aq; + aq->vbq.ops = &ipu6_isys_queue_ops; + aq->vbq.lock = &av->mutex; + aq->vbq.mem_ops = &vb2_dma_contig_memops; + aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + aq->vbq.min_queued_buffers = 1; + aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(&aq->vbq); + if (ret) + return ret; + + aq->dev = &isys->adev->auxdev.dev; + aq->vbq.dev = &isys->adev->auxdev.dev; + spin_lock_init(&aq->lock); + INIT_LIST_HEAD(&aq->active); + INIT_LIST_HEAD(&aq->incoming); + + return 0; +} |