blob: 87b98d22f1c9b66955c88bec51ef81e87827a98f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2013 - 2020 Intel Corporation
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/string.h>
#include <media/media-entity.h>
#include <media/videobuf2-dma-contig.h>
#include <media/v4l2-ioctl.h>
#include "ipu.h"
#include "ipu-bus.h"
#include "ipu-buttress.h"
#include "ipu-isys.h"
#include "ipu-isys-csi2.h"
#include "ipu-isys-video.h"
static bool wall_clock_ts_on;
module_param(wall_clock_ts_on, bool, 0660);
MODULE_PARM_DESC(wall_clock_ts_on, "Timestamp based on REALTIME clock");
static int queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
bool use_fmt = false;
unsigned int i;
/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
if (!*num_planes) {
use_fmt = true;
*num_planes = av->mpix.num_planes;
}
for (i = 0; i < *num_planes; i++) {
if (use_fmt)
sizes[i] = av->mpix.plane_fmt[i].sizeimage;
alloc_devs[i] = aq->dev;
dev_dbg(&av->isys->adev->dev,
"%s: queue setup: plane %d size %u\n",
av->vdev.name, i, sizes[i]);
}
return 0;
}
static void ipu_isys_queue_lock(struct vb2_queue *q)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev, "%s: queue lock\n", av->vdev.name);
mutex_lock(&av->mutex);
}
static void ipu_isys_queue_unlock(struct vb2_queue *q)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev, "%s: queue unlock\n", av->vdev.name);
mutex_unlock(&av->mutex);
}
static int buf_init(struct vb2_buffer *vb)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev, "buffer: %s: %s\n", av->vdev.name,
__func__);
if (aq->buf_init)
return aq->buf_init(vb);
return 0;
}
int ipu_isys_buf_prepare(struct vb2_buffer *vb)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev,
"buffer: %s: configured size %u, buffer size %lu\n",
av->vdev.name,
av->mpix.plane_fmt[0].sizeimage, vb2_plane_size(vb, 0));
if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0))
return -EINVAL;
vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline *
av->mpix.height);
vb->planes[0].data_offset = av->line_header_length / BITS_PER_BYTE;
return 0;
}
static int buf_prepare(struct vb2_buffer *vb)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
int rval;
if (av->isys->adev->isp->flr_done)
return -EIO;
rval = aq->buf_prepare(vb);
return rval;
}
static void buf_finish(struct vb2_buffer *vb)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev, "buffer: %s: %s\n", av->vdev.name,
__func__);
}
static void buf_cleanup(struct vb2_buffer *vb)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
dev_dbg(&av->isys->adev->dev, "buffer: %s: %s\n", av->vdev.name,
__func__);
if (aq->buf_cleanup)
return aq->buf_cleanup(vb);
}
/*
* Queue a buffer list back to incoming or active queues. The buffers
* are removed from the buffer list.
*/
void ipu_isys_buffer_list_queue(struct ipu_isys_buffer_list *bl,
unsigned long op_flags,
enum vb2_buffer_state state)
{
struct ipu_isys_buffer *ib, *ib_safe;
unsigned long flags;
bool first = true;
if (!bl)
return;
WARN_ON(!bl->nbufs);
WARN_ON(op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE &&
op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING);
list_for_each_entry_safe(ib, ib_safe, &bl->head, head) {
struct ipu_isys_video *av;
if (ib->type == IPU_ISYS_VIDEO_BUFFER) {
struct vb2_buffer *vb =
ipu_isys_buffer_to_vb2_buffer(ib);
struct ipu_isys_queue *aq =
vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
av = ipu_isys_queue_to_video(aq);
spin_lock_irqsave(&aq->lock, flags);
list_del(&ib->head);
if (op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE)
list_add(&ib->head, &aq->active);
else if (op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING)
list_add_tail(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (op_flags & IPU_ISYS_BUFFER_LIST_FL_SET_STATE)
vb2_buffer_done(vb, state);
} else if (ib->type == IPU_ISYS_SHORT_PACKET_BUFFER) {
struct ipu_isys_private_buffer *pb =
ipu_isys_buffer_to_private_buffer(ib);
struct ipu_isys_pipeline *ip = pb->ip;
av = container_of(ip, struct ipu_isys_video, ip);
spin_lock_irqsave(&ip->short_packet_queue_lock, flags);
list_del(&ib->head);
if (op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE)
list_add(&ib->head, &ip->short_packet_active);
else if (op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING)
list_add(&ib->head, &ip->short_packet_incoming);
spin_unlock_irqrestore(&ip->short_packet_queue_lock,
flags);
} else {
WARN_ON(1);
return;
}
if (first) {
dev_dbg(&av->isys->adev->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 ipu_isys_pipeline *ip)
{
struct ipu_isys_video *pipe_av =
container_of(ip, struct ipu_isys_video, ip);
struct ipu_isys_queue *aq;
unsigned long flags;
lockdep_assert_held(&pipe_av->mutex);
list_for_each_entry(aq, &ip->queues, node) {
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct ipu_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 =
ipu_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
if (av->streaming) {
dev_dbg(&av->isys->adev->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(&av->isys->adev->dev,
"%s: return %u back to videobuf2\n",
av->vdev.name,
vb->index);
vb2_buffer_done(ipu_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
* all queues have no buffers, the buffers that were already dequeued
* are returned to their queues.
*/
static int buffer_list_get(struct ipu_isys_pipeline *ip,
struct ipu_isys_buffer_list *bl)
{
struct ipu_isys_queue *aq;
struct ipu_isys_buffer *ib;
unsigned long flags;
int ret = 0;
bl->nbufs = 0;
INIT_LIST_HEAD(&bl->head);
list_for_each_entry(aq, &ip->queues, node) {
struct ipu_isys_buffer *ib;
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->incoming)) {
spin_unlock_irqrestore(&aq->lock, flags);
ret = -ENODATA;
goto error;
}
ib = list_last_entry(&aq->incoming,
struct ipu_isys_buffer, head);
if (ib->req) {
spin_unlock_irqrestore(&aq->lock, flags);
ret = -ENODATA;
goto error;
}
dev_dbg(&ip->isys->adev->dev, "buffer: %s: buffer %u\n",
ipu_isys_queue_to_video(aq)->vdev.name,
ipu_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++;
}
list_for_each_entry(ib, &bl->head, head) {
struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib);
aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
if (aq->prepare_frame_buff_set)
aq->prepare_frame_buff_set(vb);
}
/* Get short packet buffer. */
if (ip->interlaced && ip->isys->short_packet_source ==
IPU_ISYS_SHORT_PACKET_FROM_RECEIVER) {
ib = ipu_isys_csi2_get_short_packet_buffer(ip, bl);
if (!ib) {
ret = -ENODATA;
dev_err(&ip->isys->adev->dev,
"No more short packet buffers. Driver bug?");
WARN_ON(1);
goto error;
}
bl->nbufs++;
}
dev_dbg(&ip->isys->adev->dev, "get buffer list %p, %u buffers\n", bl,
bl->nbufs);
return ret;
error:
if (!list_empty(&bl->head))
ipu_isys_buffer_list_queue(bl,
IPU_ISYS_BUFFER_LIST_FL_INCOMING, 0);
return ret;
}
void
ipu_isys_buffer_to_fw_frame_buff_pin(struct vb2_buffer *vb,
struct ipu_fw_isys_frame_buff_set_abi *set)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = container_of(aq, struct ipu_isys_video, aq);
if (av->compression)
set->output_pins[aq->fw_output].compress = 1;
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 IPU_ISYS_FRAME_NUM_THRESHOLD (30)
void
ipu_isys_buffer_to_fw_frame_buff(struct ipu_fw_isys_frame_buff_set_abi *set,
struct ipu_isys_pipeline *ip,
struct ipu_isys_buffer_list *bl)
{
struct ipu_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 (ip->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 (!ip->interlaced &&
atomic_read(&ip->sequence) >= IPU_ISYS_FRAME_NUM_THRESHOLD) {
set->send_resp_capture_ack = 0;
set->send_resp_capture_done = 0;
}
list_for_each_entry(ib, &bl->head, head) {
if (ib->type == IPU_ISYS_VIDEO_BUFFER) {
struct vb2_buffer *vb =
ipu_isys_buffer_to_vb2_buffer(ib);
struct ipu_isys_queue *aq =
vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
if (aq->fill_frame_buff_set_pin)
aq->fill_frame_buff_set_pin(vb, set);
} else if (ib->type == IPU_ISYS_SHORT_PACKET_BUFFER) {
struct ipu_isys_private_buffer *pb =
ipu_isys_buffer_to_private_buffer(ib);
struct ipu_fw_isys_output_pin_payload_abi *output_pin =
&set->output_pins[ip->short_packet_output_pin];
output_pin->addr = pb->dma_addr;
output_pin->out_buf_id = pb->index + 1;
} else {
WARN_ON(1);
}
}
}
/* Start streaming for real. The buffer list must be available. */
static int ipu_isys_stream_start(struct ipu_isys_pipeline *ip,
struct ipu_isys_buffer_list *bl, bool error)
{
struct ipu_isys_video *pipe_av =
container_of(ip, struct ipu_isys_video, ip);
struct ipu_isys_buffer_list __bl;
int rval;
mutex_lock(&pipe_av->isys->stream_mutex);
rval = ipu_isys_video_set_streaming(pipe_av, 1, bl);
if (rval) {
mutex_unlock(&pipe_av->isys->stream_mutex);
goto out_requeue;
}
ip->streaming = 1;
mutex_unlock(&pipe_av->isys->stream_mutex);
bl = &__bl;
do {
struct ipu_fw_isys_frame_buff_set_abi *buf = NULL;
struct isys_fw_msgs *msg;
enum ipu_fw_isys_send_type send_type =
IPU_FW_ISYS_SEND_TYPE_STREAM_CAPTURE;
rval = buffer_list_get(ip, bl);
if (rval == -EINVAL)
goto out_requeue;
else if (rval < 0)
break;
msg = ipu_get_fw_msg_buf(ip);
if (!msg)
return -ENOMEM;
buf = to_frame_msg_buf(msg);
ipu_isys_buffer_to_fw_frame_buff(buf, ip, bl);
ipu_fw_isys_dump_frame_buff_set(&pipe_av->isys->adev->dev, buf,
ip->nr_output_pins);
ipu_isys_buffer_list_queue(bl,
IPU_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
rval = ipu_fw_isys_complex_cmd(pipe_av->isys,
ip->stream_handle,
buf, to_dma_addr(msg),
sizeof(*buf),
send_type);
} while (!WARN_ON(rval));
return 0;
out_requeue:
if (bl && bl->nbufs)
ipu_isys_buffer_list_queue(bl,
IPU_ISYS_BUFFER_LIST_FL_INCOMING |
(error ?
IPU_ISYS_BUFFER_LIST_FL_SET_STATE :
0),
error ? VB2_BUF_STATE_ERROR :
VB2_BUF_STATE_QUEUED);
flush_firmware_streamon_fail(ip);
return rval;
}
static void __buf_queue(struct vb2_buffer *vb, bool force)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct ipu_isys_buffer *ib = vb2_buffer_to_ipu_isys_buffer(vb);
struct ipu_isys_pipeline *ip =
to_ipu_isys_pipeline(av->vdev.entity.pipe);
struct ipu_isys_buffer_list bl;
struct ipu_fw_isys_frame_buff_set_abi *buf = NULL;
struct isys_fw_msgs *msg;
struct ipu_isys_video *pipe_av =
container_of(ip, struct ipu_isys_video, ip);
unsigned long flags;
unsigned int i;
int rval;
dev_dbg(&av->isys->adev->dev, "buffer: %s: buf_queue %u\n",
av->vdev.name,
vb->index
);
for (i = 0; i < vb->num_planes; i++)
dev_dbg(&av->isys->adev->dev, "iova: plane %u iova 0x%x\n", i,
(u32)vb2_dma_contig_plane_dma_addr(vb, i));
spin_lock_irqsave(&aq->lock, flags);
list_add(&ib->head, &aq->incoming);
spin_unlock_irqrestore(&aq->lock, flags);
if (ib->req)
return;
if (!pipe_av || !vb->vb2_queue->streaming) {
dev_dbg(&av->isys->adev->dev,
"not pipe_av set, adding to incoming\n");
return;
}
mutex_unlock(&av->mutex);
mutex_lock(&pipe_av->mutex);
if (!force && ip->nr_streaming != ip->nr_queues) {
dev_dbg(&av->isys->adev->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.
*/
rval = buffer_list_get(ip, &bl);
if (rval < 0) {
if (rval == -EINVAL) {
dev_err(&av->isys->adev->dev,
"error: should not happen\n");
WARN_ON(1);
} else {
dev_dbg(&av->isys->adev->dev,
"not enough buffers available\n");
}
goto out;
}
msg = ipu_get_fw_msg_buf(ip);
if (!msg) {
rval = -ENOMEM;
goto out;
}
buf = to_frame_msg_buf(msg);
ipu_isys_buffer_to_fw_frame_buff(buf, ip, &bl);
ipu_fw_isys_dump_frame_buff_set(&pipe_av->isys->adev->dev, buf,
ip->nr_output_pins);
if (!ip->streaming) {
dev_dbg(&av->isys->adev->dev,
"got a buffer to start streaming!\n");
rval = ipu_isys_stream_start(ip, &bl, true);
if (rval)
dev_err(&av->isys->adev->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.
*/
ipu_isys_buffer_list_queue(&bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
rval = ipu_fw_isys_complex_cmd(pipe_av->isys,
ip->stream_handle,
buf, to_dma_addr(msg),
sizeof(*buf),
IPU_FW_ISYS_SEND_TYPE_STREAM_CAPTURE);
if (!WARN_ON(rval < 0))
dev_dbg(&av->isys->adev->dev, "queued buffer\n");
out:
mutex_unlock(&pipe_av->mutex);
mutex_lock(&av->mutex);
}
static void buf_queue(struct vb2_buffer *vb)
{
__buf_queue(vb, false);
}
int ipu_isys_link_fmt_validate(struct ipu_isys_queue *aq)
{
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct v4l2_subdev_format fmt = { 0 };
struct media_pad *pad = media_entity_remote_pad(av->vdev.entity.pads);
struct v4l2_subdev *sd;
int rval;
if (!pad) {
dev_dbg(&av->isys->adev->dev,
"video node %s pad not connected\n", av->vdev.name);
return -ENOTCONN;
}
sd = media_entity_to_v4l2_subdev(pad->entity);
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.pad = pad->index;
rval = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
if (rval)
return rval;
if (fmt.format.width != av->mpix.width ||
fmt.format.height != av->mpix.height) {
dev_dbg(&av->isys->adev->dev,
"wrong width or height %ux%u (%ux%u expected)\n",
av->mpix.width, av->mpix.height,
fmt.format.width, fmt.format.height);
return -EINVAL;
}
if (fmt.format.field != av->mpix.field) {
dev_dbg(&av->isys->adev->dev,
"wrong field value 0x%8.8x (0x%8.8x expected)\n",
av->mpix.field, fmt.format.field);
return -EINVAL;
}
if (fmt.format.code != av->pfmt->code) {
dev_dbg(&av->isys->adev->dev,
"wrong media bus code 0x%8.8x (0x%8.8x expected)\n",
av->pfmt->code, fmt.format.code);
return -EINVAL;
}
return 0;
}
/* Return buffers back to videobuf2. */
static void return_buffers(struct ipu_isys_queue *aq,
enum vb2_buffer_state state)
{
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
int reset_needed = 0;
unsigned long flags;
spin_lock_irqsave(&aq->lock, flags);
while (!list_empty(&aq->incoming)) {
struct ipu_isys_buffer *ib = list_first_entry(&aq->incoming,
struct
ipu_isys_buffer,
head);
struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
dev_dbg(&av->isys->adev->dev,
"%s: stop_streaming incoming %u\n",
ipu_isys_queue_to_video(vb2_queue_to_ipu_isys_queue
(vb->vb2_queue))->vdev.name,
vb->index);
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 ipu_isys_buffer *ib = list_first_entry(&aq->active,
struct
ipu_isys_buffer,
head);
struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib);
list_del(&ib->head);
spin_unlock_irqrestore(&aq->lock, flags);
vb2_buffer_done(vb, state);
dev_warn(&av->isys->adev->dev, "%s: cleaning active queue %u\n",
ipu_isys_queue_to_video(vb2_queue_to_ipu_isys_queue
(vb->vb2_queue))->vdev.name,
vb->index);
spin_lock_irqsave(&aq->lock, flags);
reset_needed = 1;
}
spin_unlock_irqrestore(&aq->lock, flags);
if (reset_needed) {
mutex_lock(&av->isys->mutex);
av->isys->reset_needed = true;
mutex_unlock(&av->isys->mutex);
}
}
static int start_streaming(struct vb2_queue *q, unsigned int count)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct ipu_isys_video *pipe_av;
struct ipu_isys_pipeline *ip;
struct ipu_isys_buffer_list __bl, *bl = NULL;
bool first;
int rval;
dev_dbg(&av->isys->adev->dev,
"stream: %s: width %u, height %u, css pixelformat %u\n",
av->vdev.name, av->mpix.width, av->mpix.height,
av->pfmt->css_pixelformat);
mutex_lock(&av->isys->stream_mutex);
first = !av->vdev.entity.pipe;
if (first) {
rval = ipu_isys_video_prepare_streaming(av, 1);
if (rval)
goto out_return_buffers;
}
mutex_unlock(&av->isys->stream_mutex);
rval = aq->link_fmt_validate(aq);
if (rval) {
dev_dbg(&av->isys->adev->dev,
"%s: link format validation failed (%d)\n",
av->vdev.name, rval);
goto out_unprepare_streaming;
}
ip = to_ipu_isys_pipeline(av->vdev.entity.pipe);
pipe_av = container_of(ip, struct ipu_isys_video, ip);
mutex_unlock(&av->mutex);
mutex_lock(&pipe_av->mutex);
ip->nr_streaming++;
dev_dbg(&av->isys->adev->dev, "queue %u of %u\n", ip->nr_streaming,
ip->nr_queues);
list_add(&aq->node, &ip->queues);
if (ip->nr_streaming != ip->nr_queues)
goto out;
if (list_empty(&av->isys->requests)) {
bl = &__bl;
rval = buffer_list_get(ip, bl);
if (rval == -EINVAL) {
goto out_stream_start;
} else if (rval < 0) {
dev_dbg(&av->isys->adev->dev,
"no request available, postponing streamon\n");
goto out;
}
}
rval = ipu_isys_stream_start(ip, bl, false);
if (rval)
goto out_stream_start;
out:
mutex_unlock(&pipe_av->mutex);
mutex_lock(&av->mutex);
return 0;
out_stream_start:
list_del(&aq->node);
ip->nr_streaming--;
mutex_unlock(&pipe_av->mutex);
mutex_lock(&av->mutex);
out_unprepare_streaming:
mutex_lock(&av->isys->stream_mutex);
if (first)
ipu_isys_video_prepare_streaming(av, 0);
out_return_buffers:
mutex_unlock(&av->isys->stream_mutex);
return_buffers(aq, VB2_BUF_STATE_QUEUED);
return rval;
}
static void stop_streaming(struct vb2_queue *q)
{
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(q);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct ipu_isys_pipeline *ip =
to_ipu_isys_pipeline(av->vdev.entity.pipe);
struct ipu_isys_video *pipe_av =
container_of(ip, struct ipu_isys_video, ip);
if (pipe_av != av) {
mutex_unlock(&av->mutex);
mutex_lock(&pipe_av->mutex);
}
mutex_lock(&av->isys->stream_mutex);
if (ip->nr_streaming == ip->nr_queues && ip->streaming)
ipu_isys_video_set_streaming(av, 0, NULL);
if (ip->nr_streaming == 1)
ipu_isys_video_prepare_streaming(av, 0);
mutex_unlock(&av->isys->stream_mutex);
ip->nr_streaming--;
list_del(&aq->node);
ip->streaming = 0;
if (pipe_av != av) {
mutex_unlock(&pipe_av->mutex);
mutex_lock(&av->mutex);
}
return_buffers(aq, VB2_BUF_STATE_ERROR);
}
static unsigned int
get_sof_sequence_by_timestamp(struct ipu_isys_pipeline *ip,
struct ipu_fw_isys_resp_info_abi *info)
{
struct ipu_isys *isys =
container_of(ip, struct ipu_isys_video, ip)->isys;
u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0];
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(&ip->sequence) - 1;
for (i = 0; i < IPU_ISYS_MAX_PARALLEL_SOF; i++)
if (time == ip->seq[i].timestamp) {
dev_dbg(&isys->adev->dev,
"sof: using seq nr %u for ts 0x%16.16llx\n",
ip->seq[i].sequence, time);
return ip->seq[i].sequence;
}
dev_dbg(&isys->adev->dev, "SOF: looking for 0x%16.16llx\n", time);
for (i = 0; i < IPU_ISYS_MAX_PARALLEL_SOF; i++)
dev_dbg(&isys->adev->dev,
"SOF: sequence %u, timestamp value 0x%16.16llx\n",
ip->seq[i].sequence, ip->seq[i].timestamp);
dev_dbg(&isys->adev->dev, "SOF sequence number not found\n");
return 0;
}
static u64 get_sof_ns_delta(struct ipu_isys_video *av,
struct ipu_fw_isys_resp_info_abi *info)
{
struct ipu_bus_device *adev = to_ipu_bus_device(&av->isys->adev->dev);
struct ipu_device *isp = adev->isp;
u64 delta, tsc_now;
if (!ipu_buttress_tsc_read(isp, &tsc_now))
delta = tsc_now -
((u64)info->timestamp[1] << 32 | info->timestamp[0]);
else
delta = 0;
return ipu_buttress_tsc_ticks_to_ns(delta, isp);
}
void
ipu_isys_buf_calc_sequence_time(struct ipu_isys_buffer *ib,
struct ipu_fw_isys_resp_info_abi *info)
{
struct vb2_buffer *vb = ipu_isys_buffer_to_vb2_buffer(ib);
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct ipu_isys_queue *aq = vb2_queue_to_ipu_isys_queue(vb->vb2_queue);
struct ipu_isys_video *av = ipu_isys_queue_to_video(aq);
struct device *dev = &av->isys->adev->dev;
struct ipu_isys_pipeline *ip =
to_ipu_isys_pipeline(av->vdev.entity.pipe);
u64 ns;
u32 sequence;
if (ip->has_sof) {
ns = (wall_clock_ts_on) ? ktime_get_real_ns() : ktime_get_ns();
ns -= get_sof_ns_delta(av, info);
sequence = get_sof_sequence_by_timestamp(ip, info);
} else {
ns = ((wall_clock_ts_on) ? ktime_get_real_ns() :
ktime_get_ns());
sequence = (atomic_inc_return(&ip->sequence) - 1)
/ ip->nr_queues;
}
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, endl\n",
vb->index, vbuf->vb2_buf.timestamp);
}
void ipu_isys_queue_buf_done(struct ipu_isys_buffer *ib)
{
struct vb2_buffer *vb = ipu_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 ipu_isys_queue_buf_ready(struct ipu_isys_pipeline *ip,
struct ipu_fw_isys_resp_info_abi *info)
{
struct ipu_isys *isys =
container_of(ip, struct ipu_isys_video, ip)->isys;
struct ipu_isys_queue *aq = ip->output_pins[info->pin_id].aq;
struct ipu_isys_buffer *ib;
struct vb2_buffer *vb;
unsigned long flags;
bool first = true;
struct vb2_v4l2_buffer *buf;
dev_dbg(&isys->adev->dev, "buffer: %s: received buffer %8.8x\n",
ipu_isys_queue_to_video(aq)->vdev.name, info->pin.addr);
spin_lock_irqsave(&aq->lock, flags);
if (list_empty(&aq->active)) {
spin_unlock_irqrestore(&aq->lock, flags);
dev_err(&isys->adev->dev, "active queue empty\n");
return;
}
list_for_each_entry_reverse(ib, &aq->active, head) {
dma_addr_t addr;
vb = ipu_isys_buffer_to_vb2_buffer(ib);
addr = vb2_dma_contig_plane_dma_addr(vb, 0);
if (info->pin.addr != addr) {
if (first)
dev_err(&isys->adev->dev,
"WARN: buffer address %pad expected!\n",
&addr);
first = false;
continue;
}
if (info->error_info.error ==
IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) {
/*
* Check for error message:
* 'IPU_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO'
*/
atomic_set(&ib->str2mmio_flag, 1);
}
dev_dbg(&isys->adev->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);
ipu_isys_buf_calc_sequence_time(ib, info);
/*
* For interlaced buffers, the notification to user space
* is postponed to capture_done event since the field
* information is available only at that time.
*/
if (ip->interlaced) {
spin_lock_irqsave(&ip->short_packet_queue_lock, flags);
list_add(&ib->head, &ip->pending_interlaced_bufs);
spin_unlock_irqrestore(&ip->short_packet_queue_lock,
flags);
} else {
ipu_isys_queue_buf_done(ib);
}
return;
}
dev_err(&isys->adev->dev,
"WARNING: cannot find a matching video buffer!\n");
spin_unlock_irqrestore(&aq->lock, flags);
}
void
ipu_isys_queue_short_packet_ready(struct ipu_isys_pipeline *ip,
struct ipu_fw_isys_resp_info_abi *info)
{
struct ipu_isys *isys =
container_of(ip, struct ipu_isys_video, ip)->isys;
unsigned long flags;
dev_dbg(&isys->adev->dev, "receive short packet buffer %8.8x\n",
info->pin.addr);
spin_lock_irqsave(&ip->short_packet_queue_lock, flags);
ip->cur_field = ipu_isys_csi2_get_current_field(ip, info->timestamp);
spin_unlock_irqrestore(&ip->short_packet_queue_lock, flags);
}
struct vb2_ops ipu_isys_queue_ops = {
.queue_setup = queue_setup,
.wait_prepare = ipu_isys_queue_unlock,
.wait_finish = ipu_isys_queue_lock,
.buf_init = buf_init,
.buf_prepare = buf_prepare,
.buf_finish = buf_finish,
.buf_cleanup = buf_cleanup,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.buf_queue = buf_queue,
};
int ipu_isys_queue_init(struct ipu_isys_queue *aq)
{
struct ipu_isys *isys = ipu_isys_queue_to_video(aq)->isys;
int rval;
if (!aq->vbq.io_modes)
aq->vbq.io_modes = VB2_USERPTR | VB2_MMAP | VB2_DMABUF;
aq->vbq.drv_priv = aq;
aq->vbq.ops = &ipu_isys_queue_ops;
aq->vbq.mem_ops = &vb2_dma_contig_memops;
aq->vbq.timestamp_flags = (wall_clock_ts_on) ?
V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN : V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
rval = vb2_queue_init(&aq->vbq);
if (rval)
return rval;
aq->dev = &isys->adev->dev;
aq->vbq.dev = &isys->adev->dev;
spin_lock_init(&aq->lock);
INIT_LIST_HEAD(&aq->active);
INIT_LIST_HEAD(&aq->incoming);
return 0;
}
void ipu_isys_queue_cleanup(struct ipu_isys_queue *aq)
{
vb2_queue_release(&aq->vbq);
}