| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2013--2024 Intel Corporation | |
| */ | |
| #include <linux/atomic.h> | |
| #include <linux/cleanup.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-sg.h> | |
| #include <media/videobuf2-v4l2.h> | |
| #include "ipu6-bus.h" | |
| #include "ipu6-dma.h" | |
| #include "ipu6-fw-isys.h" | |
| #include "ipu6-isys.h" | |
| #include "ipu6-isys-video.h" | |
| static int ipu6_isys_buf_init(struct vb2_buffer *vb) | |
| { | |
| struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue); | |
| struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); | |
| struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); | |
| struct ipu6_isys_video_buffer *ivb = | |
| vb2_buffer_to_ipu6_isys_video_buffer(vvb); | |
| int ret; | |
| ret = ipu6_dma_map_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); | |
| if (ret) | |
| return ret; | |
| ivb->dma_addr = sg_dma_address(sg->sgl); | |
| return 0; | |
| } | |
| static void ipu6_isys_buf_cleanup(struct vb2_buffer *vb) | |
| { | |
| struct ipu6_isys *isys = vb2_get_drv_priv(vb->vb2_queue); | |
| struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); | |
| struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); | |
| struct ipu6_isys_video_buffer *ivb = | |
| vb2_buffer_to_ipu6_isys_video_buffer(vvb); | |
| ivb->dma_addr = 0; | |
| ipu6_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); | |
| } | |
| static int ipu6_isys_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 (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; | |
| lockdep_assert_held(&stream->mutex); | |
| 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); | |
| struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); | |
| struct ipu6_isys_video_buffer *ivb = | |
| vb2_buffer_to_ipu6_isys_video_buffer(vvb); | |
| set->output_pins[aq->fw_output].addr = ivb->dma_addr; | |
| 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) | |
| { | |
| struct ipu6_isys_stream *stream = av->stream; | |
| struct device *dev = &stream->isys->adev->auxdev.dev; | |
| struct ipu6_isys_buffer_list __bl; | |
| int ret; | |
| guard(mutex)(&stream->isys->stream_mutex); | |
| ret = ipu6_isys_video_set_streaming(av, 1, bl); | |
| 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, | |
| 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 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 = ivb->dma_addr; | |
| 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 (!vb2_start_streaming_called(vb->vb2_queue)) { | |
| dev_dbg(dev, "start_streaming hasn't been called yet on %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); | |
| /* | |
| * 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, *__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; | |
| 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); | |
| struct v4l2_subdev_state *state = | |
| v4l2_subdev_lock_and_get_active_state(sd); | |
| __format = v4l2_subdev_state_get_format(state, remote_pad->index, | |
| r_stream); | |
| if (__format) | |
| format = *__format; | |
| v4l2_subdev_unlock_state(state); | |
| if (!__format) { | |
| dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", | |
| sd->entity.name, remote_pad->index, r_stream); | |
| return -EPIPE; | |
| } | |
| 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_pad *source_pad, *remote_pad; | |
| 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); | |
| remote_pad = media_pad_remote_pad_unique(&av->pad); | |
| if (IS_ERR(remote_pad)) { | |
| dev_dbg(dev, "failed to get remote pad\n"); | |
| ret = PTR_ERR(remote_pad); | |
| goto out_return_buffers; | |
| } | |
| source_pad = media_pad_remote_pad_unique(&remote_pad->entity->pads[0]); | |
| if (IS_ERR(source_pad)) { | |
| dev_dbg(dev, "No external source entity\n"); | |
| ret = PTR_ERR(source_pad); | |
| goto out_return_buffers; | |
| } | |
| ret = ipu6_isys_setup_video(av, remote_pad, source_pad, &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_pad->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_configure_stream_watermark(av, source_pad->entity); | |
| 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); | |
| 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; | |
| 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); | |
| list_del(&aq->node); | |
| mutex_unlock(&av->isys->stream_mutex); | |
| stream->nr_streaming--; | |
| 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, u64 time) | |
| { | |
| 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, u64 timestamp) | |
| { | |
| 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 - timestamp; | |
| return ipu6_buttress_tsc_ticks_to_ns(delta, isp); | |
| } | |
| static void | |
| ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, u64 time) | |
| { | |
| 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, time); | |
| sequence = get_sof_sequence_by_timestamp(stream, time); | |
| 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); | |
| } | |
| static 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); | |
| } | |
| } | |
| static void | |
| ipu6_stream_buf_ready(struct ipu6_isys_stream *stream, u8 pin_id, u32 pin_addr, | |
| u64 time, bool error_check) | |
| { | |
| struct ipu6_isys_queue *aq = stream->output_pins_queue[pin_id]; | |
| 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) { | |
| struct ipu6_isys_video_buffer *ivb; | |
| struct vb2_v4l2_buffer *vvb; | |
| dma_addr_t addr; | |
| vb = ipu6_isys_buffer_to_vb2_buffer(ib); | |
| vvb = to_vb2_v4l2_buffer(vb); | |
| ivb = vb2_buffer_to_ipu6_isys_video_buffer(vvb); | |
| addr = ivb->dma_addr; | |
| if (pin_addr != addr) { | |
| if (first) | |
| dev_err(dev, "Unexpected buffer address %pad\n", | |
| &addr); | |
| first = false; | |
| continue; | |
| } | |
| if (error_check) { | |
| /* | |
| * 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, time); | |
| ipu6_isys_queue_buf_done(ib); | |
| return; | |
| } | |
| dev_err(dev, "Failed to find a matching video buffer\n"); | |
| spin_unlock_irqrestore(&aq->lock, flags); | |
| } | |
| void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, | |
| struct ipu6_fw_isys_resp_info_abi *info) | |
| { | |
| u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; | |
| bool err = info->error_info.error == IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO; | |
| ipu6_stream_buf_ready(stream, info->pin_id, info->pin.addr, time, err); | |
| } | |
| static const struct vb2_ops ipu6_isys_queue_ops = { | |
| .queue_setup = ipu6_isys_queue_setup, | |
| .buf_init = ipu6_isys_buf_init, | |
| .buf_prepare = ipu6_isys_buf_prepare, | |
| .buf_cleanup = ipu6_isys_buf_cleanup, | |
| .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); | |
| struct ipu6_bus_device *adev = isys->adev; | |
| int ret; | |
| /* no support for userptr */ | |
| if (!aq->vbq.io_modes) | |
| aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; | |
| aq->vbq.drv_priv = isys; | |
| aq->vbq.ops = &ipu6_isys_queue_ops; | |
| aq->vbq.lock = &av->mutex; | |
| aq->vbq.mem_ops = &vb2_dma_sg_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->vbq.dev = &adev->isp->pdev->dev; | |
| spin_lock_init(&aq->lock); | |
| INIT_LIST_HEAD(&aq->active); | |
| INIT_LIST_HEAD(&aq->incoming); | |
| return 0; | |
| } |