linux/drivers/media/platform/vivid/vivid-kthread-out.c
Hans Verkuil 3f682ffcf9 [media] vivid: add the kthread code that controls the video rate
Add the kthread handlers for video/vbi capture and video/vbi output.
These carefully control the rate at which frames are generated (video
capture) and accepted (video output). While the short-term jitter is
around the order of a jiffie, in the long term the rate matches the
configured framerate exactly.

The capture thread handler also takes care of the video looping and
of capture and overlay support. This is probably the most complex part
of this driver due to the many combinations of crop, compose and scaling
on the input and output, and the blending that has to be done if
overlay support is enabled as well.

Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <m.chehab@samsung.com>
2014-09-02 17:41:54 -03:00

305 lines
9.1 KiB
C

/*
* vivid-kthread-out.h - video/vbi output thread support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*
* This program is free software; you may redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/font.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/random.h>
#include <linux/v4l2-dv-timings.h>
#include <asm/div64.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include "vivid-core.h"
#include "vivid-vid-common.h"
#include "vivid-vid-cap.h"
#include "vivid-vid-out.h"
#include "vivid-radio-common.h"
#include "vivid-radio-rx.h"
#include "vivid-radio-tx.h"
#include "vivid-sdr-cap.h"
#include "vivid-vbi-cap.h"
#include "vivid-vbi-out.h"
#include "vivid-osd.h"
#include "vivid-ctrls.h"
static void vivid_thread_vid_out_tick(struct vivid_dev *dev)
{
struct vivid_buffer *vid_out_buf = NULL;
struct vivid_buffer *vbi_out_buf = NULL;
dprintk(dev, 1, "Video Output Thread Tick\n");
/* Drop a certain percentage of buffers. */
if (dev->perc_dropped_buffers &&
prandom_u32_max(100) < dev->perc_dropped_buffers)
return;
spin_lock(&dev->slock);
/*
* Only dequeue buffer if there is at least one more pending.
* This makes video loopback possible.
*/
if (!list_empty(&dev->vid_out_active) &&
!list_is_singular(&dev->vid_out_active)) {
vid_out_buf = list_entry(dev->vid_out_active.next,
struct vivid_buffer, list);
list_del(&vid_out_buf->list);
}
if (!list_empty(&dev->vbi_out_active) &&
(dev->field_out != V4L2_FIELD_ALTERNATE ||
(dev->vbi_out_seq_count & 1))) {
vbi_out_buf = list_entry(dev->vbi_out_active.next,
struct vivid_buffer, list);
list_del(&vbi_out_buf->list);
}
spin_unlock(&dev->slock);
if (!vid_out_buf && !vbi_out_buf)
return;
if (vid_out_buf) {
vid_out_buf->vb.v4l2_buf.sequence = dev->vid_out_seq_count;
if (dev->field_out == V4L2_FIELD_ALTERNATE) {
/*
* The sequence counter counts frames, not fields. So divide
* by two.
*/
vid_out_buf->vb.v4l2_buf.sequence /= 2;
}
v4l2_get_timestamp(&vid_out_buf->vb.v4l2_buf.timestamp);
vid_out_buf->vb.v4l2_buf.timestamp.tv_sec += dev->time_wrap_offset;
vb2_buffer_done(&vid_out_buf->vb, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vid_out buffer %d done\n",
vid_out_buf->vb.v4l2_buf.index);
}
if (vbi_out_buf) {
if (dev->stream_sliced_vbi_out)
vivid_sliced_vbi_out_process(dev, vbi_out_buf);
vbi_out_buf->vb.v4l2_buf.sequence = dev->vbi_out_seq_count;
v4l2_get_timestamp(&vbi_out_buf->vb.v4l2_buf.timestamp);
vbi_out_buf->vb.v4l2_buf.timestamp.tv_sec += dev->time_wrap_offset;
vb2_buffer_done(&vbi_out_buf->vb, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vbi_out buffer %d done\n",
vbi_out_buf->vb.v4l2_buf.index);
}
dev->dqbuf_error = false;
}
static int vivid_thread_vid_out(void *data)
{
struct vivid_dev *dev = data;
u64 numerators_since_start;
u64 buffers_since_start;
u64 next_jiffies_since_start;
unsigned long jiffies_since_start;
unsigned long cur_jiffies;
unsigned wait_jiffies;
unsigned numerator;
unsigned denominator;
dprintk(dev, 1, "Video Output Thread Start\n");
set_freezable();
/* Resets frame counters */
dev->out_seq_offset = 0;
if (dev->seq_wrap)
dev->out_seq_count = 0xffffff80U;
dev->jiffies_vid_out = jiffies;
dev->vid_out_seq_start = dev->vbi_out_seq_start = 0;
dev->out_seq_resync = false;
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
mutex_lock(&dev->mutex);
cur_jiffies = jiffies;
if (dev->out_seq_resync) {
dev->jiffies_vid_out = cur_jiffies;
dev->out_seq_offset = dev->out_seq_count + 1;
dev->out_seq_count = 0;
dev->out_seq_resync = false;
}
numerator = dev->timeperframe_vid_out.numerator;
denominator = dev->timeperframe_vid_out.denominator;
if (dev->field_out == V4L2_FIELD_ALTERNATE)
denominator *= 2;
/* Calculate the number of jiffies since we started streaming */
jiffies_since_start = cur_jiffies - dev->jiffies_vid_out;
/* Get the number of buffers streamed since the start */
buffers_since_start = (u64)jiffies_since_start * denominator +
(HZ * numerator) / 2;
do_div(buffers_since_start, HZ * numerator);
/*
* After more than 0xf0000000 (rounded down to a multiple of
* 'jiffies-per-day' to ease jiffies_to_msecs calculation)
* jiffies have passed since we started streaming reset the
* counters and keep track of the sequence offset.
*/
if (jiffies_since_start > JIFFIES_RESYNC) {
dev->jiffies_vid_out = cur_jiffies;
dev->out_seq_offset = buffers_since_start;
buffers_since_start = 0;
}
dev->out_seq_count = buffers_since_start + dev->out_seq_offset;
dev->vid_out_seq_count = dev->out_seq_count - dev->vid_out_seq_start;
dev->vbi_out_seq_count = dev->out_seq_count - dev->vbi_out_seq_start;
vivid_thread_vid_out_tick(dev);
mutex_unlock(&dev->mutex);
/*
* Calculate the number of 'numerators' streamed since we started,
* not including the current buffer.
*/
numerators_since_start = buffers_since_start * numerator;
/* And the number of jiffies since we started */
jiffies_since_start = jiffies - dev->jiffies_vid_out;
/* Increase by the 'numerator' of one buffer */
numerators_since_start += numerator;
/*
* Calculate when that next buffer is supposed to start
* in jiffies since we started streaming.
*/
next_jiffies_since_start = numerators_since_start * HZ +
denominator / 2;
do_div(next_jiffies_since_start, denominator);
/* If it is in the past, then just schedule asap */
if (next_jiffies_since_start < jiffies_since_start)
next_jiffies_since_start = jiffies_since_start;
wait_jiffies = next_jiffies_since_start - jiffies_since_start;
schedule_timeout_interruptible(wait_jiffies ? wait_jiffies : 1);
}
dprintk(dev, 1, "Video Output Thread End\n");
return 0;
}
static void vivid_grab_controls(struct vivid_dev *dev, bool grab)
{
v4l2_ctrl_grab(dev->ctrl_has_crop_out, grab);
v4l2_ctrl_grab(dev->ctrl_has_compose_out, grab);
v4l2_ctrl_grab(dev->ctrl_has_scaler_out, grab);
v4l2_ctrl_grab(dev->ctrl_tx_mode, grab);
v4l2_ctrl_grab(dev->ctrl_tx_rgb_range, grab);
}
int vivid_start_generating_vid_out(struct vivid_dev *dev, bool *pstreaming)
{
dprintk(dev, 1, "%s\n", __func__);
if (dev->kthread_vid_out) {
u32 seq_count = dev->out_seq_count + dev->seq_wrap * 128;
if (pstreaming == &dev->vid_out_streaming)
dev->vid_out_seq_start = seq_count;
else
dev->vbi_out_seq_start = seq_count;
*pstreaming = true;
return 0;
}
/* Resets frame counters */
dev->jiffies_vid_out = jiffies;
dev->vid_out_seq_start = dev->seq_wrap * 128;
dev->vbi_out_seq_start = dev->seq_wrap * 128;
dev->kthread_vid_out = kthread_run(vivid_thread_vid_out, dev,
"%s-vid-out", dev->v4l2_dev.name);
if (IS_ERR(dev->kthread_vid_out)) {
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return PTR_ERR(dev->kthread_vid_out);
}
*pstreaming = true;
vivid_grab_controls(dev, true);
dprintk(dev, 1, "returning from %s\n", __func__);
return 0;
}
void vivid_stop_generating_vid_out(struct vivid_dev *dev, bool *pstreaming)
{
dprintk(dev, 1, "%s\n", __func__);
if (dev->kthread_vid_out == NULL)
return;
*pstreaming = false;
if (pstreaming == &dev->vid_out_streaming) {
/* Release all active buffers */
while (!list_empty(&dev->vid_out_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->vid_out_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vid_out buffer %d done\n",
buf->vb.v4l2_buf.index);
}
}
if (pstreaming == &dev->vbi_out_streaming) {
while (!list_empty(&dev->vbi_out_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->vbi_out_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vbi_out buffer %d done\n",
buf->vb.v4l2_buf.index);
}
}
if (dev->vid_out_streaming || dev->vbi_out_streaming)
return;
/* shutdown control thread */
vivid_grab_controls(dev, false);
mutex_unlock(&dev->mutex);
kthread_stop(dev->kthread_vid_out);
dev->kthread_vid_out = NULL;
mutex_lock(&dev->mutex);
}