mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-11 04:18:39 +08:00
media: intel/ipu6: input system video nodes and buffer queues
Register V4L2 video device and setup the VB2 queues to support video capture. Video streaming callback will trigger the input system driver to construct a input system stream configuration for firmware based on data type and stream ID and then queue buffers to firmware to do capture. Signed-off-by: Bingbu Cao <bingbu.cao@intel.com> Co-developed-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
This commit is contained in:
parent
1e7eeb3016
commit
3c1dfb5a69
809
drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
Normal file
809
drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
Normal file
@ -0,0 +1,809 @@
|
||||
// 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 = av->pix_fmt.sizeimage;
|
||||
|
||||
/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
|
||||
if (!*num_planes) {
|
||||
sizes[0] = size;
|
||||
} else if (sizes[0] < size) {
|
||||
dev_err(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;
|
||||
|
||||
dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
|
||||
av->vdev.name, av->pix_fmt.sizeimage,
|
||||
vb2_plane_size(vb, 0));
|
||||
|
||||
if (av->pix_fmt.sizeimage > vb2_plane_size(vb, 0))
|
||||
return -EINVAL;
|
||||
|
||||
vb2_set_plane_payload(vb, 0, av->pix_fmt.bytesperline *
|
||||
av->pix_fmt.height);
|
||||
vb->planes[0].data_offset = 0;
|
||||
|
||||
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;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name);
|
||||
|
||||
for (i = 0; i < vb->num_planes; i++) {
|
||||
dma = vb2_dma_contig_plane_dma_addr(vb, i);
|
||||
dev_dbg(dev, "iova: plane %u iova %pad\n", i, &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_warn(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;
|
||||
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 != av->pix_fmt.width ||
|
||||
format.height != av->pix_fmt.height) {
|
||||
dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
|
||||
av->pix_fmt.width, av->pix_fmt.height,
|
||||
format.width, format.height);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (format.code != av->pfmt->code) {
|
||||
dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n",
|
||||
av->pfmt->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;
|
||||
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, av->pix_fmt.width, av->pix_fmt.height,
|
||||
av->pfmt->css_pixelformat);
|
||||
|
||||
ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to setup video\n");
|
||||
goto out_return_buffers;
|
||||
}
|
||||
|
||||
ret = ipu6_isys_link_fmt_validate(aq);
|
||||
if (ret) {
|
||||
dev_err(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;
|
||||
}
|
78
drivers/media/pci/intel/ipu6/ipu6-isys-queue.h
Normal file
78
drivers/media/pci/intel/ipu6/ipu6-isys-queue.h
Normal file
@ -0,0 +1,78 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (C) 2013--2024 Intel Corporation */
|
||||
|
||||
#ifndef IPU6_ISYS_QUEUE_H
|
||||
#define IPU6_ISYS_QUEUE_H
|
||||
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock_types.h>
|
||||
|
||||
#include <media/videobuf2-v4l2.h>
|
||||
|
||||
#include "ipu6-fw-isys.h"
|
||||
#include "ipu6-isys-video.h"
|
||||
|
||||
struct ipu6_isys_stream;
|
||||
|
||||
struct ipu6_isys_queue {
|
||||
struct vb2_queue vbq;
|
||||
struct list_head node;
|
||||
struct device *dev;
|
||||
/*
|
||||
* @lock: serialise access to queued and pre_streamon_queued
|
||||
*/
|
||||
spinlock_t lock;
|
||||
struct list_head active;
|
||||
struct list_head incoming;
|
||||
unsigned int fw_output;
|
||||
};
|
||||
|
||||
struct ipu6_isys_buffer {
|
||||
struct list_head head;
|
||||
atomic_t str2mmio_flag;
|
||||
};
|
||||
|
||||
struct ipu6_isys_video_buffer {
|
||||
struct vb2_v4l2_buffer vb_v4l2;
|
||||
struct ipu6_isys_buffer ib;
|
||||
};
|
||||
|
||||
#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0)
|
||||
#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1)
|
||||
#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2)
|
||||
|
||||
struct ipu6_isys_buffer_list {
|
||||
struct list_head head;
|
||||
unsigned int nbufs;
|
||||
};
|
||||
|
||||
#define vb2_queue_to_isys_queue(__vb2) \
|
||||
container_of(__vb2, struct ipu6_isys_queue, vbq)
|
||||
|
||||
#define ipu6_isys_to_isys_video_buffer(__ib) \
|
||||
container_of(__ib, struct ipu6_isys_video_buffer, ib)
|
||||
|
||||
#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \
|
||||
container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2)
|
||||
|
||||
#define ipu6_isys_buffer_to_vb2_buffer(__ib) \
|
||||
(&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf)
|
||||
|
||||
void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
|
||||
unsigned long op_flags,
|
||||
enum vb2_buffer_state state);
|
||||
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);
|
||||
void
|
||||
ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib,
|
||||
struct ipu6_fw_isys_resp_info_abi *info);
|
||||
void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib);
|
||||
void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
|
||||
struct ipu6_fw_isys_resp_info_abi *info);
|
||||
int ipu6_isys_queue_init(struct ipu6_isys_queue *aq);
|
||||
#endif /* IPU6_ISYS_QUEUE_H */
|
1249
drivers/media/pci/intel/ipu6/ipu6-isys-video.c
Normal file
1249
drivers/media/pci/intel/ipu6/ipu6-isys-video.c
Normal file
File diff suppressed because it is too large
Load Diff
132
drivers/media/pci/intel/ipu6/ipu6-isys-video.h
Normal file
132
drivers/media/pci/intel/ipu6/ipu6-isys-video.h
Normal file
@ -0,0 +1,132 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (C) 2013--2024 Intel Corporation */
|
||||
|
||||
#ifndef IPU6_ISYS_VIDEO_H
|
||||
#define IPU6_ISYS_VIDEO_H
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include <media/media-entity.h>
|
||||
#include <media/v4l2-dev.h>
|
||||
|
||||
#include "ipu6-isys-queue.h"
|
||||
|
||||
#define IPU6_ISYS_OUTPUT_PINS 11
|
||||
#define IPU6_ISYS_MAX_PARALLEL_SOF 2
|
||||
|
||||
struct file;
|
||||
struct ipu6_isys;
|
||||
struct ipu6_isys_csi2;
|
||||
struct ipu6_isys_subdev;
|
||||
|
||||
struct ipu6_isys_pixelformat {
|
||||
u32 pixelformat;
|
||||
u32 bpp;
|
||||
u32 bpp_packed;
|
||||
u32 code;
|
||||
u32 css_pixelformat;
|
||||
};
|
||||
|
||||
struct sequence_info {
|
||||
unsigned int sequence;
|
||||
u64 timestamp;
|
||||
};
|
||||
|
||||
struct output_pin_data {
|
||||
void (*pin_ready)(struct ipu6_isys_stream *stream,
|
||||
struct ipu6_fw_isys_resp_info_abi *info);
|
||||
struct ipu6_isys_queue *aq;
|
||||
};
|
||||
|
||||
/*
|
||||
* Align with firmware stream. Each stream represents a CSI virtual channel.
|
||||
* May map to multiple video devices
|
||||
*/
|
||||
struct ipu6_isys_stream {
|
||||
struct mutex mutex;
|
||||
struct media_entity *source_entity;
|
||||
atomic_t sequence;
|
||||
unsigned int seq_index;
|
||||
struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF];
|
||||
int stream_source;
|
||||
int stream_handle;
|
||||
unsigned int nr_output_pins;
|
||||
struct ipu6_isys_subdev *asd;
|
||||
|
||||
int nr_queues; /* Number of capture queues */
|
||||
int nr_streaming;
|
||||
int streaming; /* Has streaming been really started? */
|
||||
struct list_head queues;
|
||||
struct completion stream_open_completion;
|
||||
struct completion stream_close_completion;
|
||||
struct completion stream_start_completion;
|
||||
struct completion stream_stop_completion;
|
||||
struct ipu6_isys *isys;
|
||||
|
||||
struct output_pin_data output_pins[IPU6_ISYS_OUTPUT_PINS];
|
||||
int error;
|
||||
u8 vc;
|
||||
};
|
||||
|
||||
struct video_stream_watermark {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 hblank;
|
||||
u32 frame_rate;
|
||||
u64 pixel_rate;
|
||||
u64 stream_data_rate;
|
||||
u16 sram_gran_shift;
|
||||
u16 sram_gran_size;
|
||||
struct list_head stream_node;
|
||||
};
|
||||
|
||||
struct ipu6_isys_video {
|
||||
struct ipu6_isys_queue aq;
|
||||
/* Serialise access to other fields in the struct. */
|
||||
struct mutex mutex;
|
||||
struct media_pad pad;
|
||||
struct video_device vdev;
|
||||
struct v4l2_pix_format pix_fmt;
|
||||
const struct ipu6_isys_pixelformat *pfmt;
|
||||
struct ipu6_isys *isys;
|
||||
struct ipu6_isys_csi2 *csi2;
|
||||
struct ipu6_isys_stream *stream;
|
||||
unsigned int streaming;
|
||||
struct video_stream_watermark watermark;
|
||||
u32 source_stream;
|
||||
u8 vc;
|
||||
u8 dt;
|
||||
};
|
||||
|
||||
#define ipu6_isys_queue_to_video(__aq) \
|
||||
container_of(__aq, struct ipu6_isys_video, aq)
|
||||
|
||||
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[];
|
||||
extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[];
|
||||
|
||||
int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av,
|
||||
struct media_entity *source_entity,
|
||||
int nr_queues);
|
||||
int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state,
|
||||
struct ipu6_isys_buffer_list *bl);
|
||||
int ipu6_isys_fw_open(struct ipu6_isys *isys);
|
||||
void ipu6_isys_fw_close(struct ipu6_isys *isys);
|
||||
int ipu6_isys_setup_video(struct ipu6_isys_video *av,
|
||||
struct media_entity **source_entity, int *nr_queues);
|
||||
int ipu6_isys_video_init(struct ipu6_isys_video *av);
|
||||
void ipu6_isys_video_cleanup(struct ipu6_isys_video *av);
|
||||
void ipu6_isys_put_stream(struct ipu6_isys_stream *stream);
|
||||
struct ipu6_isys_stream *
|
||||
ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle);
|
||||
struct ipu6_isys_stream *
|
||||
ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc);
|
||||
|
||||
void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
|
||||
bool state);
|
||||
void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state);
|
||||
|
||||
#endif /* IPU6_ISYS_VIDEO_H */
|
Loading…
Reference in New Issue
Block a user