Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0-only
config VIDEO_VIVID
tristate "Virtual Video Test Driver"
depends on VIDEO_DEV && !SPARC32 && !SPARC64
depends on HAS_DMA
select FONT_SUPPORT
select FONT_8x16
select VIDEOBUF2_VMALLOC
select VIDEOBUF2_DMA_CONTIG
select VIDEO_V4L2_TPG
select MEDIA_CONTROLLER
help
Enables a virtual video driver. This driver emulates a webcam,
TV, S-Video and HDMI capture hardware, including VBI support for
the SDTV inputs. Also video output, VBI output, radio receivers,
transmitters and software defined radio capture is emulated.
It is highly configurable and is ideal for testing applications.
Error injection is supported to test rare errors that are hard
to reproduce in real hardware.
Say Y here if you want to test video apps or debug V4L devices.
When in doubt, say N.
config VIDEO_VIVID_CEC
bool "Enable CEC emulation support"
depends on VIDEO_VIVID
select CEC_CORE
help
When selected the vivid module will emulate the optional
HDMI CEC feature.
config VIDEO_VIVID_OSD
bool "Enable Framebuffer for testing Output Overlay"
depends on VIDEO_VIVID && FB_CORE
depends on VIDEO_VIVID=m || FB_CORE=y
default y
select FB_IOMEM_HELPERS
help
When selected the vivid module will emulate a Framebuffer for
testing Output Overlay.
config VIDEO_VIVID_MAX_DEVS
int "Maximum number of devices"
depends on VIDEO_VIVID
default "64"
help
This allows you to specify the maximum number of devices supported
by the vivid driver.
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
vivid-objs := vivid-core.o vivid-ctrls.o vivid-vid-common.o vivid-vbi-gen.o \
vivid-vid-cap.o vivid-vid-out.o vivid-kthread-cap.o vivid-kthread-out.o \
vivid-radio-rx.o vivid-radio-tx.o vivid-radio-common.o \
vivid-rds-gen.o vivid-sdr-cap.o vivid-vbi-cap.o vivid-vbi-out.o \
vivid-meta-cap.o vivid-meta-out.o \
vivid-kthread-touch.o vivid-touch-cap.o
ifeq ($(CONFIG_VIDEO_VIVID_CEC),y)
vivid-objs += vivid-cec.o
endif
ifeq ($(CONFIG_VIDEO_VIVID_OSD),y)
vivid-objs += vivid-osd.o
endif
obj-$(CONFIG_VIDEO_VIVID) += vivid.o
@@ -0,0 +1,415 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-cec.c - A Virtual Video Test Driver, cec emulation
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/delay.h>
#include <media/cec.h>
#include "vivid-core.h"
#include "vivid-cec.h"
#define CEC_START_BIT_US 4500
#define CEC_DATA_BIT_US 2400
#define CEC_MARGIN_US 350
struct xfer_on_bus {
struct cec_adapter *adap;
u8 status;
};
static bool find_dest_adap(struct vivid_dev *dev,
struct cec_adapter *adap, u8 dest)
{
unsigned int i, j;
if (dest >= 0xf)
return false;
if (adap != dev->cec_rx_adap && dev->cec_rx_adap &&
dev->cec_rx_adap->is_configured &&
cec_has_log_addr(dev->cec_rx_adap, dest))
return true;
for (i = 0, j = 0; i < dev->num_inputs; i++) {
unsigned int menu_idx =
dev->input_is_connected_to_output[i];
if (dev->input_type[i] != HDMI)
continue;
j++;
if (menu_idx < FIXED_MENU_ITEMS)
continue;
struct vivid_dev *dev_tx = vivid_ctrl_hdmi_to_output_instance[menu_idx];
unsigned int output = vivid_ctrl_hdmi_to_output_index[menu_idx];
if (!dev_tx)
continue;
unsigned int hdmi_output = dev_tx->output_to_iface_index[output];
if (adap == dev_tx->cec_tx_adap[hdmi_output])
continue;
if (!dev_tx->cec_tx_adap[hdmi_output]->is_configured)
continue;
if (cec_has_log_addr(dev_tx->cec_tx_adap[hdmi_output], dest))
return true;
}
return false;
}
static bool xfer_ready(struct vivid_dev *dev)
{
unsigned int i;
bool ready = false;
spin_lock(&dev->cec_xfers_slock);
for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {
if (dev->xfers[i].sft &&
dev->xfers[i].sft <= dev->cec_sft) {
ready = true;
break;
}
}
spin_unlock(&dev->cec_xfers_slock);
return ready;
}
/*
* If an adapter tries to send successive messages, it must wait for the
* longest signal-free time between its transmissions. But, if another
* adapter sends a message in the interim, then the wait can be reduced
* because the messages are no longer successive. Make these adjustments
* if necessary. Should be called holding cec_xfers_slock.
*/
static void adjust_sfts(struct vivid_dev *dev)
{
unsigned int i;
u8 initiator;
for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {
if (dev->xfers[i].sft <= CEC_SIGNAL_FREE_TIME_RETRY)
continue;
initiator = dev->xfers[i].msg[0] >> 4;
if (initiator == dev->last_initiator)
dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;
else
dev->xfers[i].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR;
}
}
/*
* The main emulation of the bus on which CEC adapters attempt to send
* messages to each other. The bus keeps track of how long it has been
* signal-free and accepts a pending transmission only if the state of
* the bus matches the transmission's signal-free requirements. It calls
* cec_transmit_attempt_done() for all transmits that enter the bus and
* cec_received_msg() for successful transmits.
*/
int vivid_cec_bus_thread(void *_dev)
{
u32 last_sft;
unsigned int i, j;
unsigned int dest;
ktime_t start, end;
s64 delta_us, retry_us;
struct vivid_dev *dev = _dev;
dev->cec_sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;
for (;;) {
bool first = true;
int wait_xfer_us = 0;
bool valid_dest = false;
int wait_arb_lost_us = 0;
unsigned int first_idx = 0;
unsigned int first_status = 0;
struct cec_msg first_msg = {};
struct xfer_on_bus xfers_on_bus[MAX_OUTPUTS] = {};
wait_event_interruptible(dev->kthread_waitq_cec, xfer_ready(dev) ||
kthread_should_stop());
if (kthread_should_stop())
break;
last_sft = dev->cec_sft;
dev->cec_sft = 0;
/*
* Move the messages that are ready onto the bus. The adapter with
* the most leading zeros will win control of the bus and any other
* adapters will lose arbitration.
*/
spin_lock(&dev->cec_xfers_slock);
for (i = 0; i < ARRAY_SIZE(dev->xfers); i++) {
if (!dev->xfers[i].sft || dev->xfers[i].sft > last_sft)
continue;
if (first) {
first = false;
first_idx = i;
xfers_on_bus[first_idx].adap = dev->xfers[i].adap;
memcpy(first_msg.msg, dev->xfers[i].msg, dev->xfers[i].len);
first_msg.len = dev->xfers[i].len;
} else {
xfers_on_bus[i].adap = dev->xfers[i].adap;
xfers_on_bus[i].status = CEC_TX_STATUS_ARB_LOST;
/*
* For simplicity wait for all 4 bits of the initiator's
* address even though HDMI specification uses bit-level
* precision.
*/
wait_arb_lost_us = 4 * CEC_DATA_BIT_US + CEC_START_BIT_US;
}
dev->xfers[i].sft = 0;
}
dev->last_initiator = cec_msg_initiator(&first_msg);
adjust_sfts(dev);
spin_unlock(&dev->cec_xfers_slock);
dest = cec_msg_destination(&first_msg);
valid_dest = cec_msg_is_broadcast(&first_msg);
if (!valid_dest)
valid_dest = find_dest_adap(dev, xfers_on_bus[first_idx].adap, dest);
if (valid_dest) {
first_status = CEC_TX_STATUS_OK;
/*
* Message length is in bytes, but each byte is transmitted in
* a block of 10 bits.
*/
wait_xfer_us = first_msg.len * 10 * CEC_DATA_BIT_US;
} else {
first_status = CEC_TX_STATUS_NACK;
/*
* A message that is not acknowledged stops transmitting after
* the header block of 10 bits.
*/
wait_xfer_us = 10 * CEC_DATA_BIT_US;
}
wait_xfer_us += CEC_START_BIT_US;
xfers_on_bus[first_idx].status = first_status;
/* Sleep as if sending messages on a real hardware bus. */
start = ktime_get();
if (wait_arb_lost_us) {
usleep_range(wait_arb_lost_us - CEC_MARGIN_US, wait_arb_lost_us);
for (i = 0; i < ARRAY_SIZE(xfers_on_bus); i++) {
if (xfers_on_bus[i].status != CEC_TX_STATUS_ARB_LOST)
continue;
cec_transmit_attempt_done(xfers_on_bus[i].adap,
CEC_TX_STATUS_ARB_LOST);
}
if (kthread_should_stop())
break;
}
wait_xfer_us -= wait_arb_lost_us;
usleep_range(wait_xfer_us - CEC_MARGIN_US, wait_xfer_us);
cec_transmit_attempt_done(xfers_on_bus[first_idx].adap, first_status);
if (kthread_should_stop())
break;
if (first_status == CEC_TX_STATUS_OK) {
if (xfers_on_bus[first_idx].adap != dev->cec_rx_adap)
cec_received_msg(dev->cec_rx_adap, &first_msg);
for (i = 0, j = 0; i < dev->num_inputs; i++) {
unsigned int menu_idx =
dev->input_is_connected_to_output[i];
if (dev->input_type[i] != HDMI)
continue;
j++;
if (menu_idx < FIXED_MENU_ITEMS)
continue;
struct vivid_dev *dev_tx = vivid_ctrl_hdmi_to_output_instance[menu_idx];
unsigned int output = vivid_ctrl_hdmi_to_output_index[menu_idx];
if (!dev_tx)
continue;
unsigned int hdmi_output = dev_tx->output_to_iface_index[output];
if (xfers_on_bus[first_idx].adap != dev_tx->cec_tx_adap[hdmi_output])
cec_received_msg(dev_tx->cec_tx_adap[hdmi_output], &first_msg);
}
}
end = ktime_get();
/*
* If the emulated transfer took more or less time than it should
* have, then compensate by adjusting the wait time needed for the
* bus to be signal-free for 3 bit periods (the retry time).
*/
delta_us = div_s64(end - start, 1000);
delta_us -= wait_xfer_us + wait_arb_lost_us;
retry_us = CEC_SIGNAL_FREE_TIME_RETRY * CEC_DATA_BIT_US - delta_us;
if (retry_us > CEC_MARGIN_US)
usleep_range(retry_us - CEC_MARGIN_US, retry_us);
dev->cec_sft = CEC_SIGNAL_FREE_TIME_RETRY;
/*
* If there are no messages that need to be retried, check if any
* adapters that did not just transmit a message are ready to
* transmit. If none of these adapters are ready, then increase
* the signal-free time so that the bus is available to all
* adapters and go back to waiting for a transmission.
*/
while (dev->cec_sft >= CEC_SIGNAL_FREE_TIME_RETRY &&
dev->cec_sft < CEC_SIGNAL_FREE_TIME_NEXT_XFER &&
!xfer_ready(dev) && !kthread_should_stop()) {
usleep_range(2 * CEC_DATA_BIT_US - CEC_MARGIN_US,
2 * CEC_DATA_BIT_US);
dev->cec_sft += 2;
}
}
return 0;
}
static int vivid_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
adap->cec_pin_is_high = true;
return 0;
}
static int vivid_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
{
return 0;
}
static int vivid_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct vivid_dev *dev = cec_get_drvdata(adap);
struct vivid_dev *dev_rx = dev;
u8 idx = cec_msg_initiator(msg);
u8 output = 0;
if (dev->cec_rx_adap != adap) {
int i;
for (i = 0; i < dev->num_hdmi_outputs; i++)
if (dev->cec_tx_adap[i] == adap)
break;
if (i == dev->num_hdmi_outputs)
return -ENONET;
output = dev->hdmi_index_to_output_index[i];
dev_rx = dev->output_to_input_instance[output];
if (!dev_rx)
return -ENONET;
}
spin_lock(&dev_rx->cec_xfers_slock);
dev_rx->xfers[idx].adap = adap;
memcpy(dev_rx->xfers[idx].msg, msg->msg, CEC_MAX_MSG_SIZE);
dev_rx->xfers[idx].len = msg->len;
dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_RETRY;
if (signal_free_time > CEC_SIGNAL_FREE_TIME_RETRY) {
if (idx == dev_rx->last_initiator)
dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEXT_XFER;
else
dev_rx->xfers[idx].sft = CEC_SIGNAL_FREE_TIME_NEW_INITIATOR;
}
spin_unlock(&dev_rx->cec_xfers_slock);
wake_up_interruptible(&dev_rx->kthread_waitq_cec);
return 0;
}
static int vivid_received(struct cec_adapter *adap, struct cec_msg *msg)
{
struct vivid_dev *dev = cec_get_drvdata(adap);
struct cec_msg reply;
u8 dest = cec_msg_destination(msg);
if (cec_msg_is_broadcast(msg))
dest = adap->log_addrs.log_addr[0];
cec_msg_init(&reply, dest, cec_msg_initiator(msg));
switch (cec_msg_opcode(msg)) {
case CEC_MSG_SET_OSD_STRING: {
u8 disp_ctl;
char osd[14];
if (!cec_is_sink(adap))
break;
cec_ops_set_osd_string(msg, &disp_ctl, osd);
switch (disp_ctl) {
case CEC_OP_DISP_CTL_DEFAULT:
strscpy(dev->osd, osd, sizeof(dev->osd));
dev->osd_jiffies = jiffies;
break;
case CEC_OP_DISP_CTL_UNTIL_CLEARED:
strscpy(dev->osd, osd, sizeof(dev->osd));
dev->osd_jiffies = 0;
break;
case CEC_OP_DISP_CTL_CLEAR:
dev->osd[0] = 0;
dev->osd_jiffies = 0;
break;
default:
cec_msg_feature_abort(&reply, cec_msg_opcode(msg),
CEC_OP_ABORT_INVALID_OP);
cec_transmit_msg(adap, &reply, false);
break;
}
return 0;
}
case CEC_MSG_VENDOR_COMMAND_WITH_ID: {
u32 vendor_id;
u8 size;
const u8 *vendor_cmd;
/*
* If we receive <Vendor Command With ID> with our vendor ID
* and with a payload of size 1, and the payload value is odd,
* then we reply with the same message, but with the payload
* byte incremented by 1.
*
* If the size is 1 and the payload value is even, then we
* ignore the message.
*
* The reason we reply to odd instead of even payload values
* is that it allows for testing of the corner case where the
* reply value is 0 (0xff + 1 % 256).
*
* For other sizes we Feature Abort.
*
* This is added for the specific purpose of testing the
* CEC_MSG_FL_REPLY_VENDOR_ID flag using vivid.
*/
cec_ops_vendor_command_with_id(msg, &vendor_id, &size, &vendor_cmd);
if (vendor_id != adap->log_addrs.vendor_id)
break;
if (size == 1) {
// Ignore even op values
if (!(vendor_cmd[0] & 1))
return 0;
reply.len = msg->len;
memcpy(reply.msg + 1, msg->msg + 1, msg->len - 1);
reply.msg[msg->len - 1]++;
} else {
cec_msg_feature_abort(&reply, cec_msg_opcode(msg),
CEC_OP_ABORT_INVALID_OP);
}
cec_transmit_msg(adap, &reply, false);
return 0;
}
}
return -ENOMSG;
}
static const struct cec_adap_ops vivid_cec_adap_ops = {
.adap_enable = vivid_cec_adap_enable,
.adap_log_addr = vivid_cec_adap_log_addr,
.adap_transmit = vivid_cec_adap_transmit,
.received = vivid_received,
};
struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev,
unsigned int idx,
bool is_source)
{
u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;
char name[32];
snprintf(name, sizeof(name), "vivid-%03d-vid-%s%d",
dev->inst, is_source ? "out" : "cap", idx);
return cec_allocate_adapter(&vivid_cec_adap_ops, dev,
name, caps, CEC_MAX_LOG_ADDRS);
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-cec.h - A Virtual Video Test Driver, cec emulation
*
* Copyright 2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifdef CONFIG_VIDEO_VIVID_CEC
struct cec_adapter *vivid_cec_alloc_adap(struct vivid_dev *dev,
unsigned int idx,
bool is_source);
int vivid_cec_bus_thread(void *_dev);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,706 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-core.h - core datastructures
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_CORE_H_
#define _VIVID_CORE_H_
#include <linux/fb.h>
#include <linux/workqueue.h>
#include <media/cec.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ctrls.h>
#include <media/tpg/v4l2-tpg.h>
#include "vivid-rds-gen.h"
#include "vivid-vbi-gen.h"
#define dprintk(dev, level, fmt, arg...) \
v4l2_dbg(level, vivid_debug, &dev->v4l2_dev, fmt, ## arg)
/* The maximum number of inputs */
#define MAX_INPUTS 16
/* The maximum number of outputs */
#define MAX_OUTPUTS 16
/* The maximum number of video capture buffers */
#define MAX_VID_CAP_BUFFERS 64
/* The maximum up or down scaling factor is 4 */
#define MAX_ZOOM 4
/* The maximum image width/height are set to 4K DMT */
#define MAX_WIDTH 4096
#define MAX_HEIGHT 2160
/* The minimum image width/height */
#define MIN_WIDTH 16
#define MIN_HEIGHT MIN_WIDTH
/* Pixel Array control divider */
#define PIXEL_ARRAY_DIV MIN_WIDTH
/* The data_offset of plane 0 for the multiplanar formats */
#define PLANE0_DATA_OFFSET 128
/* The supported TV frequency range in MHz */
#define MIN_TV_FREQ (44U * 16U)
#define MAX_TV_FREQ (958U * 16U)
/* The number of samples returned in every SDR buffer */
#define SDR_CAP_SAMPLES_PER_BUF 0x4000
/* used by the threads to know when to resync internal counters */
#define JIFFIES_PER_DAY (3600U * 24U * HZ)
#define JIFFIES_RESYNC (JIFFIES_PER_DAY * (0xf0000000U / JIFFIES_PER_DAY))
/*
* Maximum number of HDMI inputs allowed by vivid, due to limitations
* of the Physical Address in the EDID and used by CEC we stop at 15
* inputs and outputs.
*/
#define MAX_HDMI_INPUTS 15
#define MAX_HDMI_OUTPUTS 15
/* Maximum number of S-Video inputs allowed by vivid */
#define MAX_SVID_INPUTS 16
/* The maximum number of items in a menu control */
#define MAX_MENU_ITEMS BITS_PER_LONG_LONG
/* Number of fixed menu items in the 'Connected To' menu controls */
#define FIXED_MENU_ITEMS 2
/* The maximum number of vivid devices */
#define VIVID_MAX_DEVS CONFIG_VIDEO_VIVID_MAX_DEVS
extern const struct v4l2_rect vivid_min_rect;
extern const struct v4l2_rect vivid_max_rect;
extern unsigned vivid_debug;
/*
* NULL-terminated string array for the HDMI 'Connected To' menu controls
* with the list of possible HDMI outputs.
*
* The first two items are fixed ("TPG" and "None").
*/
extern char *vivid_ctrl_hdmi_to_output_strings[1 + MAX_MENU_ITEMS];
/* Menu control skip mask of all HDMI outputs that are in use */
extern u64 hdmi_to_output_menu_skip_mask;
/*
* Bitmask of which vivid instances need to update any connected
* HDMI outputs.
*/
extern u64 hdmi_input_update_outputs_mask;
/*
* Spinlock for access to hdmi_to_output_menu_skip_mask and
* hdmi_input_update_outputs_mask.
*/
extern spinlock_t hdmi_output_skip_mask_lock;
/*
* Workqueue that updates the menu controls whenever the HDMI menu skip mask
* changes.
*/
extern struct workqueue_struct *update_hdmi_ctrls_workqueue;
/*
* The HDMI menu control value (index in the menu list) maps to an HDMI
* output that is part of the given vivid_dev instance and has the given
* output index (as returned by VIDIOC_G_OUTPUT).
*
* NULL/0 if not available.
*/
extern struct vivid_dev *vivid_ctrl_hdmi_to_output_instance[MAX_MENU_ITEMS];
extern unsigned int vivid_ctrl_hdmi_to_output_index[MAX_MENU_ITEMS];
/*
* NULL-terminated string array for the S-Video 'Connected To' menu controls
* with the list of possible S-Video outputs.
*
* The first two items are fixed ("TPG" and "None").
*/
extern char *vivid_ctrl_svid_to_output_strings[1 + MAX_MENU_ITEMS];
/* Menu control skip mask of all S-Video outputs that are in use */
extern u64 svid_to_output_menu_skip_mask;
/* Spinlock for access to svid_to_output_menu_skip_mask */
extern spinlock_t svid_output_skip_mask_lock;
/*
* Workqueue that updates the menu controls whenever the S-Video menu skip mask
* changes.
*/
extern struct workqueue_struct *update_svid_ctrls_workqueue;
/*
* The S-Video menu control value (index in the menu list) maps to an S-Video
* output that is part of the given vivid_dev instance and has the given
* output index (as returned by VIDIOC_G_OUTPUT).
*
* NULL/0 if not available.
*/
extern struct vivid_dev *vivid_ctrl_svid_to_output_instance[MAX_MENU_ITEMS];
extern unsigned int vivid_ctrl_svid_to_output_index[MAX_MENU_ITEMS];
extern struct vivid_dev *vivid_devs[VIVID_MAX_DEVS];
extern unsigned int n_devs;
struct vivid_fmt {
u32 fourcc; /* v4l2 format id */
enum tgp_color_enc color_enc;
bool can_do_overlay;
u8 vdownsampling[TPG_MAX_PLANES];
u32 alpha_mask;
u8 planes;
u8 buffers;
u32 data_offset[TPG_MAX_PLANES];
u32 bit_depth[TPG_MAX_PLANES];
};
extern struct vivid_fmt vivid_formats[];
/* buffer for one video frame */
struct vivid_buffer {
/* common v4l buffer stuff -- must be first */
struct vb2_v4l2_buffer vb;
struct list_head list;
};
enum vivid_input {
WEBCAM,
TV,
SVID,
HDMI,
};
enum vivid_signal_mode {
CURRENT_DV_TIMINGS,
CURRENT_STD = CURRENT_DV_TIMINGS,
NO_SIGNAL,
NO_LOCK,
OUT_OF_RANGE,
SELECTED_DV_TIMINGS,
SELECTED_STD = SELECTED_DV_TIMINGS,
CYCLE_DV_TIMINGS,
CYCLE_STD = CYCLE_DV_TIMINGS,
CUSTOM_DV_TIMINGS,
};
enum vivid_colorspace {
VIVID_CS_170M,
VIVID_CS_709,
VIVID_CS_SRGB,
VIVID_CS_OPRGB,
VIVID_CS_2020,
VIVID_CS_DCI_P3,
VIVID_CS_240M,
VIVID_CS_SYS_M,
VIVID_CS_SYS_BG,
};
#define VIVID_INVALID_SIGNAL(mode) \
((mode) == NO_SIGNAL || (mode) == NO_LOCK || (mode) == OUT_OF_RANGE)
struct vivid_cec_xfer {
struct cec_adapter *adap;
u8 msg[CEC_MAX_MSG_SIZE];
u32 len;
u32 sft;
};
struct vivid_dev {
u8 inst;
struct v4l2_device v4l2_dev;
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device mdev;
struct media_pad vid_cap_pad;
struct media_pad vid_out_pad;
struct media_pad vbi_cap_pad;
struct media_pad vbi_out_pad;
struct media_pad sdr_cap_pad;
struct media_pad meta_cap_pad;
struct media_pad meta_out_pad;
struct media_pad touch_cap_pad;
#endif
struct v4l2_ctrl_handler ctrl_hdl_user_gen;
struct v4l2_ctrl_handler ctrl_hdl_user_vid;
struct v4l2_ctrl_handler ctrl_hdl_user_aud;
struct v4l2_ctrl_handler ctrl_hdl_streaming;
struct v4l2_ctrl_handler ctrl_hdl_sdtv_cap;
struct v4l2_ctrl_handler ctrl_hdl_loop_cap;
struct v4l2_ctrl_handler ctrl_hdl_fb;
struct video_device vid_cap_dev;
struct v4l2_ctrl_handler ctrl_hdl_vid_cap;
struct video_device vid_out_dev;
struct v4l2_ctrl_handler ctrl_hdl_vid_out;
struct video_device vbi_cap_dev;
struct v4l2_ctrl_handler ctrl_hdl_vbi_cap;
struct video_device vbi_out_dev;
struct v4l2_ctrl_handler ctrl_hdl_vbi_out;
struct video_device radio_rx_dev;
struct v4l2_ctrl_handler ctrl_hdl_radio_rx;
struct video_device radio_tx_dev;
struct v4l2_ctrl_handler ctrl_hdl_radio_tx;
struct video_device sdr_cap_dev;
struct v4l2_ctrl_handler ctrl_hdl_sdr_cap;
struct video_device meta_cap_dev;
struct v4l2_ctrl_handler ctrl_hdl_meta_cap;
struct video_device meta_out_dev;
struct v4l2_ctrl_handler ctrl_hdl_meta_out;
struct video_device touch_cap_dev;
struct v4l2_ctrl_handler ctrl_hdl_touch_cap;
spinlock_t slock;
struct mutex mutex;
struct work_struct update_hdmi_ctrl_work;
struct work_struct update_svid_ctrl_work;
/* capabilities */
u32 vid_cap_caps;
u32 vid_out_caps;
u32 vbi_cap_caps;
u32 vbi_out_caps;
u32 sdr_cap_caps;
u32 radio_rx_caps;
u32 radio_tx_caps;
u32 meta_cap_caps;
u32 meta_out_caps;
u32 touch_cap_caps;
/* supported features */
bool multiplanar;
u8 num_inputs;
u8 num_hdmi_inputs;
u8 num_svid_inputs;
u8 input_type[MAX_INPUTS];
u8 input_name_counter[MAX_INPUTS];
u8 num_outputs;
u8 num_hdmi_outputs;
u8 output_type[MAX_OUTPUTS];
u8 output_name_counter[MAX_OUTPUTS];
bool has_audio_inputs;
bool has_audio_outputs;
bool has_vid_cap;
bool has_vid_out;
bool has_vbi_cap;
bool has_raw_vbi_cap;
bool has_sliced_vbi_cap;
bool has_vbi_out;
bool has_raw_vbi_out;
bool has_sliced_vbi_out;
bool has_radio_rx;
bool has_radio_tx;
bool has_sdr_cap;
bool has_fb;
bool has_meta_cap;
bool has_meta_out;
bool has_tv_tuner;
bool has_touch_cap;
/* Output index (0-MAX_OUTPUTS) to vivid instance of connected input */
struct vivid_dev *output_to_input_instance[MAX_OUTPUTS];
/* Output index (0-MAX_OUTPUTS) to input index (0-MAX_INPUTS) of connected input */
u8 output_to_input_index[MAX_OUTPUTS];
/* Output index (0-MAX_OUTPUTS) to HDMI or S-Video output index (0-MAX_HDMI/SVID_OUTPUTS) */
u8 output_to_iface_index[MAX_OUTPUTS];
/* ctrl_hdmi_to_output or ctrl_svid_to_output control value for each input */
s32 input_is_connected_to_output[MAX_INPUTS];
/* HDMI index (0-MAX_HDMI_OUTPUTS) to output index (0-MAX_OUTPUTS) */
u8 hdmi_index_to_output_index[MAX_HDMI_OUTPUTS];
/* HDMI index (0-MAX_HDMI_INPUTS) to input index (0-MAX_INPUTS) */
u8 hdmi_index_to_input_index[MAX_HDMI_INPUTS];
/* S-Video index (0-MAX_SVID_INPUTS) to input index (0-MAX_INPUTS) */
u8 svid_index_to_input_index[MAX_SVID_INPUTS];
/* controls */
struct v4l2_ctrl *brightness;
struct v4l2_ctrl *contrast;
struct v4l2_ctrl *saturation;
struct v4l2_ctrl *hue;
struct {
/* autogain/gain cluster */
struct v4l2_ctrl *autogain;
struct v4l2_ctrl *gain;
};
struct v4l2_ctrl *volume;
struct v4l2_ctrl *mute;
struct v4l2_ctrl *alpha;
struct v4l2_ctrl *button;
struct v4l2_ctrl *boolean;
struct v4l2_ctrl *int32;
struct v4l2_ctrl *int64;
struct v4l2_ctrl *menu;
struct v4l2_ctrl *string;
struct v4l2_ctrl *bitmask;
struct v4l2_ctrl *int_menu;
struct v4l2_ctrl *ro_int32;
struct v4l2_ctrl *pixel_array;
struct v4l2_ctrl *test_pattern;
struct v4l2_ctrl *colorspace;
struct v4l2_ctrl *rgb_range_cap;
struct v4l2_ctrl *real_rgb_range_cap;
struct {
/* std_signal_mode/standard cluster */
struct v4l2_ctrl *ctrl_std_signal_mode;
struct v4l2_ctrl *ctrl_standard;
};
struct {
/* dv_timings_signal_mode/timings cluster */
struct v4l2_ctrl *ctrl_dv_timings_signal_mode;
struct v4l2_ctrl *ctrl_dv_timings;
};
struct v4l2_ctrl *ctrl_has_crop_cap;
struct v4l2_ctrl *ctrl_has_compose_cap;
struct v4l2_ctrl *ctrl_has_scaler_cap;
struct v4l2_ctrl *ctrl_has_crop_out;
struct v4l2_ctrl *ctrl_has_compose_out;
struct v4l2_ctrl *ctrl_has_scaler_out;
struct v4l2_ctrl *ctrl_tx_mode;
struct v4l2_ctrl *ctrl_tx_rgb_range;
struct v4l2_ctrl *ctrl_tx_edid_present;
struct v4l2_ctrl *ctrl_tx_hotplug;
struct v4l2_ctrl *ctrl_tx_rxsense;
struct v4l2_ctrl *ctrl_rx_power_present;
struct v4l2_ctrl *radio_tx_rds_pi;
struct v4l2_ctrl *radio_tx_rds_pty;
struct v4l2_ctrl *radio_tx_rds_mono_stereo;
struct v4l2_ctrl *radio_tx_rds_art_head;
struct v4l2_ctrl *radio_tx_rds_compressed;
struct v4l2_ctrl *radio_tx_rds_dyn_pty;
struct v4l2_ctrl *radio_tx_rds_ta;
struct v4l2_ctrl *radio_tx_rds_tp;
struct v4l2_ctrl *radio_tx_rds_ms;
struct v4l2_ctrl *radio_tx_rds_psname;
struct v4l2_ctrl *radio_tx_rds_radiotext;
struct v4l2_ctrl *radio_rx_rds_pty;
struct v4l2_ctrl *radio_rx_rds_ta;
struct v4l2_ctrl *radio_rx_rds_tp;
struct v4l2_ctrl *radio_rx_rds_ms;
struct v4l2_ctrl *radio_rx_rds_psname;
struct v4l2_ctrl *radio_rx_rds_radiotext;
struct v4l2_ctrl *ctrl_hdmi_to_output[MAX_HDMI_INPUTS];
char ctrl_hdmi_to_output_names[MAX_HDMI_INPUTS][32];
struct v4l2_ctrl *ctrl_svid_to_output[MAX_SVID_INPUTS];
char ctrl_svid_to_output_names[MAX_SVID_INPUTS][32];
unsigned input_brightness[MAX_INPUTS];
unsigned osd_mode;
unsigned button_pressed;
bool sensor_hflip;
bool sensor_vflip;
bool hflip;
bool vflip;
bool vbi_cap_interlaced;
bool loop_video;
bool reduced_fps;
/* Framebuffer */
unsigned long video_pbase;
void *video_vbase;
u32 video_buffer_size;
int display_width;
int display_height;
int display_byte_stride;
int bits_per_pixel;
int bytes_per_pixel;
#ifdef CONFIG_VIDEO_VIVID_OSD
struct fb_info fb_info;
struct fb_var_screeninfo fb_defined;
struct fb_fix_screeninfo fb_fix;
#endif
/* Error injection */
bool disconnect_error;
bool queue_setup_error;
bool buf_prepare_error;
bool start_streaming_error;
bool dqbuf_error;
bool req_validate_error;
bool seq_wrap;
u64 time_wrap;
u64 time_wrap_offset;
unsigned perc_dropped_buffers;
enum vivid_signal_mode std_signal_mode[MAX_INPUTS];
unsigned int query_std_last[MAX_INPUTS];
v4l2_std_id query_std[MAX_INPUTS];
enum tpg_video_aspect std_aspect_ratio[MAX_INPUTS];
enum vivid_signal_mode dv_timings_signal_mode[MAX_INPUTS];
char **query_dv_timings_qmenu;
char *query_dv_timings_qmenu_strings;
unsigned query_dv_timings_size;
unsigned int query_dv_timings_last[MAX_INPUTS];
unsigned int query_dv_timings[MAX_INPUTS];
enum tpg_video_aspect dv_timings_aspect_ratio[MAX_INPUTS];
/* Input */
unsigned input;
v4l2_std_id std_cap[MAX_INPUTS];
struct v4l2_dv_timings dv_timings_cap[MAX_INPUTS];
int dv_timings_cap_sel[MAX_INPUTS];
u32 service_set_cap;
struct vivid_vbi_gen_data vbi_gen;
u8 *edid;
unsigned edid_blocks;
unsigned edid_max_blocks;
unsigned webcam_size_idx;
unsigned webcam_ival_idx;
unsigned tv_freq;
unsigned tv_audmode;
unsigned tv_field_cap;
unsigned tv_audio_input;
u32 power_present;
/* Output */
unsigned output;
v4l2_std_id std_out;
struct v4l2_dv_timings dv_timings_out;
u32 colorspace_out;
u32 ycbcr_enc_out;
u32 hsv_enc_out;
u32 quantization_out;
u32 xfer_func_out;
u32 service_set_out;
unsigned bytesperline_out[TPG_MAX_PLANES];
unsigned tv_field_out;
unsigned tv_audio_output;
bool vbi_out_have_wss;
u8 vbi_out_wss[2];
bool vbi_out_have_cc[2];
u8 vbi_out_cc[2][2];
bool dvi_d_out;
u8 *scaled_line;
u8 *blended_line;
unsigned cur_scaled_line;
/* Output Overlay */
void *fb_vbase_out;
bool overlay_out_enabled;
int overlay_out_top, overlay_out_left;
unsigned fbuf_out_flags;
u32 chromakey_out;
u8 global_alpha_out;
/* video capture */
struct tpg_data tpg;
unsigned ms_vid_cap;
bool must_blank[MAX_VID_CAP_BUFFERS];
const struct vivid_fmt *fmt_cap;
struct v4l2_fract timeperframe_vid_cap;
enum v4l2_field field_cap;
struct v4l2_rect src_rect;
struct v4l2_rect fmt_cap_rect;
struct v4l2_rect crop_cap;
struct v4l2_rect compose_cap;
struct v4l2_rect crop_bounds_cap;
struct vb2_queue vb_vid_cap_q;
struct list_head vid_cap_active;
struct vb2_queue vb_vbi_cap_q;
struct list_head vbi_cap_active;
struct vb2_queue vb_meta_cap_q;
struct list_head meta_cap_active;
struct vb2_queue vb_touch_cap_q;
struct list_head touch_cap_active;
/* thread for generating video capture stream */
struct task_struct *kthread_vid_cap;
unsigned long jiffies_vid_cap;
u64 cap_stream_start;
u64 cap_frame_period;
u64 cap_frame_eof_offset;
u32 cap_seq_offset;
u32 cap_seq_count;
bool cap_seq_resync;
u32 vid_cap_seq_start;
u32 vid_cap_seq_count;
bool vid_cap_streaming;
u32 vbi_cap_seq_start;
u32 vbi_cap_seq_count;
bool vbi_cap_streaming;
u32 meta_cap_seq_start;
u32 meta_cap_seq_count;
bool meta_cap_streaming;
/* Touch capture */
struct task_struct *kthread_touch_cap;
unsigned long jiffies_touch_cap;
u64 touch_cap_stream_start;
u32 touch_cap_seq_offset;
bool touch_cap_seq_resync;
u32 touch_cap_seq_start;
u32 touch_cap_seq_count;
u32 touch_cap_with_seq_wrap_count;
bool touch_cap_streaming;
struct v4l2_fract timeperframe_tch_cap;
struct v4l2_pix_format tch_format;
int tch_pat_random;
/* video output */
const struct vivid_fmt *fmt_out;
struct v4l2_fract timeperframe_vid_out;
enum v4l2_field field_out;
struct v4l2_rect sink_rect;
struct v4l2_rect fmt_out_rect;
struct v4l2_rect crop_out;
struct v4l2_rect compose_out;
struct v4l2_rect compose_bounds_out;
struct vb2_queue vb_vid_out_q;
struct list_head vid_out_active;
struct vb2_queue vb_vbi_out_q;
struct list_head vbi_out_active;
struct vb2_queue vb_meta_out_q;
struct list_head meta_out_active;
/* video loop precalculated rectangles */
/*
* Intersection between what the output side composes and the capture side
* crops. I.e., what actually needs to be copied from the output buffer to
* the capture buffer.
*/
struct v4l2_rect loop_vid_copy;
/* The part of the output buffer that (after scaling) corresponds to loop_vid_copy. */
struct v4l2_rect loop_vid_out;
/* The part of the capture buffer that (after scaling) corresponds to loop_vid_copy. */
struct v4l2_rect loop_vid_cap;
/*
* The intersection of the framebuffer, the overlay output window and
* loop_vid_copy. I.e., the part of the framebuffer that actually should be
* blended with the compose_out rectangle. This uses the framebuffer origin.
*/
struct v4l2_rect loop_fb_copy;
/* The same as loop_fb_copy but with compose_out origin. */
struct v4l2_rect loop_vid_overlay;
/*
* The part of the capture buffer that (after scaling) corresponds
* to loop_vid_overlay.
*/
struct v4l2_rect loop_vid_overlay_cap;
/* thread for generating video output stream */
struct task_struct *kthread_vid_out;
unsigned long jiffies_vid_out;
u32 out_seq_offset;
u32 out_seq_count;
bool out_seq_resync;
u32 vid_out_seq_start;
u32 vid_out_seq_count;
bool vid_out_streaming;
u32 vbi_out_seq_start;
u32 vbi_out_seq_count;
bool vbi_out_streaming;
bool stream_sliced_vbi_out;
u32 meta_out_seq_start;
u32 meta_out_seq_count;
bool meta_out_streaming;
/* SDR capture */
struct vb2_queue vb_sdr_cap_q;
struct list_head sdr_cap_active;
u32 sdr_pixelformat; /* v4l2 format id */
unsigned sdr_buffersize;
unsigned sdr_adc_freq;
unsigned sdr_fm_freq;
unsigned sdr_fm_deviation;
int sdr_fixp_src_phase;
int sdr_fixp_mod_phase;
bool tstamp_src_is_soe;
bool has_crop_cap;
bool has_compose_cap;
bool has_scaler_cap;
bool has_crop_out;
bool has_compose_out;
bool has_scaler_out;
/* thread for generating SDR stream */
struct task_struct *kthread_sdr_cap;
unsigned long jiffies_sdr_cap;
u32 sdr_cap_seq_offset;
u32 sdr_cap_seq_start;
u32 sdr_cap_seq_count;
u32 sdr_cap_with_seq_wrap_count;
bool sdr_cap_seq_resync;
/* RDS generator */
struct vivid_rds_gen rds_gen;
/* Radio receiver */
unsigned radio_rx_freq;
unsigned radio_rx_audmode;
int radio_rx_sig_qual;
unsigned radio_rx_hw_seek_mode;
bool radio_rx_hw_seek_prog_lim;
bool radio_rx_rds_controls;
bool radio_rx_rds_enabled;
unsigned radio_rx_rds_use_alternates;
unsigned radio_rx_rds_last_block;
struct v4l2_fh *radio_rx_rds_owner;
/* Radio transmitter */
unsigned radio_tx_freq;
unsigned radio_tx_subchans;
bool radio_tx_rds_controls;
unsigned radio_tx_rds_last_block;
struct v4l2_fh *radio_tx_rds_owner;
/* Shared between radio receiver and transmitter */
bool radio_rds_loop;
ktime_t radio_rds_init_time;
/* CEC */
struct cec_adapter *cec_rx_adap;
struct cec_adapter *cec_tx_adap[MAX_HDMI_OUTPUTS];
struct task_struct *kthread_cec;
wait_queue_head_t kthread_waitq_cec;
struct vivid_cec_xfer xfers[MAX_OUTPUTS];
spinlock_t cec_xfers_slock; /* read and write cec messages */
u32 cec_sft; /* bus signal free time, in bit periods */
u8 last_initiator;
/* CEC OSD String */
char osd[14];
unsigned long osd_jiffies;
bool meta_pts;
bool meta_scr;
};
static inline bool vivid_is_webcam(const struct vivid_dev *dev)
{
return dev->input_type[dev->input] == WEBCAM;
}
static inline bool vivid_is_tv_cap(const struct vivid_dev *dev)
{
return dev->input_type[dev->input] == TV;
}
static inline bool vivid_is_svid_cap(const struct vivid_dev *dev)
{
return dev->input_type[dev->input] == SVID;
}
static inline bool vivid_is_hdmi_cap(const struct vivid_dev *dev)
{
return dev->input_type[dev->input] == HDMI;
}
static inline bool vivid_is_sdtv_cap(const struct vivid_dev *dev)
{
return vivid_is_tv_cap(dev) || vivid_is_svid_cap(dev);
}
static inline bool vivid_is_svid_out(const struct vivid_dev *dev)
{
return dev->output_type[dev->output] == SVID;
}
static inline bool vivid_is_hdmi_out(const struct vivid_dev *dev)
{
return dev->output_type[dev->output] == HDMI;
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-ctrls.h - control support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_CTRLS_H_
#define _VIVID_CTRLS_H_
enum vivid_hw_seek_modes {
VIVID_HW_SEEK_BOUNDED,
VIVID_HW_SEEK_WRAP,
VIVID_HW_SEEK_BOTH,
};
int vivid_create_controls(struct vivid_dev *dev, bool show_ccs_cap,
bool show_ccs_out, bool no_error_inj,
bool has_sdtv, bool has_hdmi);
void vivid_free_controls(struct vivid_dev *dev);
#endif
@@ -0,0 +1,915 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-kthread-cap.h - video/vbi capture thread support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#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 <linux/jiffies.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 <media/v4l2-rect.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"
#include "vivid-kthread-cap.h"
#include "vivid-meta-cap.h"
static inline v4l2_std_id vivid_get_std_cap(const struct vivid_dev *dev)
{
if (vivid_is_sdtv_cap(dev))
return dev->std_cap[dev->input];
return 0;
}
static void copy_pix(struct vivid_dev *dev, int win_y, int win_x,
u16 *cap, const u16 *osd)
{
u16 out;
out = *cap;
*cap = *osd;
if ((dev->fbuf_out_flags & V4L2_FBUF_FLAG_CHROMAKEY) &&
*osd != dev->chromakey_out)
return;
if ((dev->fbuf_out_flags & V4L2_FBUF_FLAG_SRC_CHROMAKEY) &&
out == dev->chromakey_out)
return;
if (dev->fmt_cap->alpha_mask) {
if ((dev->fbuf_out_flags & V4L2_FBUF_FLAG_GLOBAL_ALPHA) &&
dev->global_alpha_out)
return;
if ((dev->fbuf_out_flags & V4L2_FBUF_FLAG_LOCAL_ALPHA) &&
*cap & dev->fmt_cap->alpha_mask)
return;
if ((dev->fbuf_out_flags & V4L2_FBUF_FLAG_LOCAL_INV_ALPHA) &&
!(*cap & dev->fmt_cap->alpha_mask))
return;
}
*cap = out;
}
static void blend_line(struct vivid_dev *dev, unsigned y_offset, unsigned x_offset,
u8 *vcapbuf, const u8 *vosdbuf,
unsigned width, unsigned pixsize)
{
unsigned x;
for (x = 0; x < width; x++, vcapbuf += pixsize, vosdbuf += pixsize) {
copy_pix(dev, y_offset, x_offset + x,
(u16 *)vcapbuf, (const u16 *)vosdbuf);
}
}
static void scale_line(const u8 *src, u8 *dst, unsigned srcw, unsigned dstw, unsigned twopixsize)
{
/* Coarse scaling with Bresenham */
unsigned int_part;
unsigned fract_part;
unsigned src_x = 0;
unsigned error = 0;
unsigned x;
/*
* We always combine two pixels to prevent color bleed in the packed
* yuv case.
*/
srcw /= 2;
dstw /= 2;
int_part = srcw / dstw;
fract_part = srcw % dstw;
for (x = 0; x < dstw; x++, dst += twopixsize) {
memcpy(dst, src + src_x * twopixsize, twopixsize);
src_x += int_part;
error += fract_part;
if (error >= dstw) {
error -= dstw;
src_x++;
}
}
}
/*
* Precalculate the rectangles needed to perform video looping:
*
* The nominal pipeline is that the video output buffer is cropped by
* crop_out, scaled to compose_out, overlaid with the output overlay,
* cropped on the capture side by crop_cap and scaled again to the video
* capture buffer using compose_cap.
*
* To keep things efficient we calculate the intersection of compose_out
* and crop_cap (since that's the only part of the video that will
* actually end up in the capture buffer), determine which part of the
* video output buffer that is and which part of the video capture buffer
* so we can scale the video straight from the output buffer to the capture
* buffer without any intermediate steps.
*
* If we need to deal with an output overlay, then there is no choice and
* that intermediate step still has to be taken. For the output overlay
* support we calculate the intersection of the framebuffer and the overlay
* window (which may be partially or wholly outside of the framebuffer
* itself) and the intersection of that with loop_vid_copy (i.e. the part of
* the actual looped video that will be overlaid). The result is calculated
* both in framebuffer coordinates (loop_fb_copy) and compose_out coordinates
* (loop_vid_overlay). Finally calculate the part of the capture buffer that
* will receive that overlaid video.
*/
static void vivid_precalc_copy_rects(struct vivid_dev *dev, struct vivid_dev *out_dev)
{
/* Framebuffer rectangle */
struct v4l2_rect r_fb = {
0, 0, dev->display_width, dev->display_height
};
/* Overlay window rectangle in framebuffer coordinates */
struct v4l2_rect r_overlay = {
out_dev->overlay_out_left, out_dev->overlay_out_top,
out_dev->compose_out.width, out_dev->compose_out.height
};
v4l2_rect_intersect(&dev->loop_vid_copy, &dev->crop_cap, &out_dev->compose_out);
dev->loop_vid_out = dev->loop_vid_copy;
v4l2_rect_scale(&dev->loop_vid_out, &out_dev->compose_out, &out_dev->crop_out);
dev->loop_vid_out.left += out_dev->crop_out.left;
dev->loop_vid_out.top += out_dev->crop_out.top;
dev->loop_vid_cap = dev->loop_vid_copy;
v4l2_rect_scale(&dev->loop_vid_cap, &dev->crop_cap, &dev->compose_cap);
dprintk(dev, 1,
"loop_vid_copy: (%d,%d)/%ux%u loop_vid_out: (%d,%d)/%ux%u loop_vid_cap: (%d,%d)/%ux%u\n",
dev->loop_vid_copy.left, dev->loop_vid_copy.top,
dev->loop_vid_copy.width, dev->loop_vid_copy.height,
dev->loop_vid_out.left, dev->loop_vid_out.top,
dev->loop_vid_out.width, dev->loop_vid_out.height,
dev->loop_vid_cap.left, dev->loop_vid_cap.top,
dev->loop_vid_cap.width, dev->loop_vid_cap.height);
v4l2_rect_intersect(&r_overlay, &r_fb, &r_overlay);
/* shift r_overlay to the same origin as compose_out */
r_overlay.left += out_dev->compose_out.left - out_dev->overlay_out_left;
r_overlay.top += out_dev->compose_out.top - out_dev->overlay_out_top;
v4l2_rect_intersect(&dev->loop_vid_overlay, &r_overlay, &dev->loop_vid_copy);
dev->loop_fb_copy = dev->loop_vid_overlay;
/* shift dev->loop_fb_copy back again to the fb origin */
dev->loop_fb_copy.left -= out_dev->compose_out.left - out_dev->overlay_out_left;
dev->loop_fb_copy.top -= out_dev->compose_out.top - out_dev->overlay_out_top;
dev->loop_vid_overlay_cap = dev->loop_vid_overlay;
v4l2_rect_scale(&dev->loop_vid_overlay_cap, &dev->crop_cap, &dev->compose_cap);
dprintk(dev, 1,
"loop_fb_copy: (%d,%d)/%ux%u loop_vid_overlay: (%d,%d)/%ux%u loop_vid_overlay_cap: (%d,%d)/%ux%u\n",
dev->loop_fb_copy.left, dev->loop_fb_copy.top,
dev->loop_fb_copy.width, dev->loop_fb_copy.height,
dev->loop_vid_overlay.left, dev->loop_vid_overlay.top,
dev->loop_vid_overlay.width, dev->loop_vid_overlay.height,
dev->loop_vid_overlay_cap.left, dev->loop_vid_overlay_cap.top,
dev->loop_vid_overlay_cap.width, dev->loop_vid_overlay_cap.height);
}
static void *plane_vaddr(struct tpg_data *tpg, struct vivid_buffer *buf,
unsigned p, unsigned bpl[TPG_MAX_PLANES], unsigned h)
{
unsigned i;
void *vbuf;
if (p == 0 || tpg_g_buffers(tpg) > 1)
return vb2_plane_vaddr(&buf->vb.vb2_buf, p);
vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
for (i = 0; i < p; i++)
vbuf += bpl[i] * h / tpg->vdownsampling[i];
return vbuf;
}
static noinline_for_stack int vivid_copy_buffer(struct vivid_dev *dev,
struct vivid_dev *out_dev, unsigned p,
u8 *vcapbuf, struct vivid_buffer *vid_cap_buf)
{
bool blank = dev->must_blank[vid_cap_buf->vb.vb2_buf.index];
struct tpg_data *tpg = &dev->tpg;
struct vivid_buffer *vid_out_buf = NULL;
unsigned vdiv = out_dev->fmt_out->vdownsampling[p];
unsigned twopixsize = tpg_g_twopixelsize(tpg, p);
unsigned img_width = tpg_hdiv(tpg, p, dev->compose_cap.width);
unsigned img_height = dev->compose_cap.height;
unsigned stride_cap = tpg->bytesperline[p];
unsigned stride_out = out_dev->bytesperline_out[p];
unsigned stride_osd = dev->display_byte_stride;
unsigned hmax = (img_height * tpg->perc_fill) / 100;
u8 *voutbuf;
u8 *vosdbuf = NULL;
unsigned y;
bool blend = out_dev->fbuf_out_flags;
/* Coarse scaling with Bresenham */
unsigned vid_out_int_part;
unsigned vid_out_fract_part;
unsigned vid_out_y = 0;
unsigned vid_out_error = 0;
unsigned vid_overlay_int_part = 0;
unsigned vid_overlay_fract_part = 0;
unsigned vid_overlay_y = 0;
unsigned vid_overlay_error = 0;
unsigned vid_cap_left = tpg_hdiv(tpg, p, dev->loop_vid_cap.left);
unsigned vid_cap_right;
bool quick;
vid_out_int_part = dev->loop_vid_out.height / dev->loop_vid_cap.height;
vid_out_fract_part = dev->loop_vid_out.height % dev->loop_vid_cap.height;
if (!list_empty(&out_dev->vid_out_active))
vid_out_buf = list_entry(out_dev->vid_out_active.next,
struct vivid_buffer, list);
if (vid_out_buf == NULL)
return -ENODATA;
vid_cap_buf->vb.field = vid_out_buf->vb.field;
voutbuf = plane_vaddr(tpg, vid_out_buf, p,
out_dev->bytesperline_out, out_dev->fmt_out_rect.height);
if (p < out_dev->fmt_out->buffers)
voutbuf += vid_out_buf->vb.vb2_buf.planes[p].data_offset;
voutbuf += tpg_hdiv(tpg, p, dev->loop_vid_out.left) +
(dev->loop_vid_out.top / vdiv) * stride_out;
vcapbuf += tpg_hdiv(tpg, p, dev->compose_cap.left) +
(dev->compose_cap.top / vdiv) * stride_cap;
if (dev->loop_vid_copy.width == 0 || dev->loop_vid_copy.height == 0) {
/*
* If there is nothing to copy, then just fill the capture window
* with black.
*/
for (y = 0; y < hmax / vdiv; y++, vcapbuf += stride_cap)
memcpy(vcapbuf, tpg->black_line[p], img_width);
return 0;
}
if (out_dev->overlay_out_enabled &&
dev->loop_vid_overlay.width && dev->loop_vid_overlay.height) {
vosdbuf = dev->video_vbase;
vosdbuf += (dev->loop_fb_copy.left * twopixsize) / 2 +
dev->loop_fb_copy.top * stride_osd;
vid_overlay_int_part = dev->loop_vid_overlay.height /
dev->loop_vid_overlay_cap.height;
vid_overlay_fract_part = dev->loop_vid_overlay.height %
dev->loop_vid_overlay_cap.height;
}
vid_cap_right = tpg_hdiv(tpg, p, dev->loop_vid_cap.left + dev->loop_vid_cap.width);
/* quick is true if no video scaling is needed */
quick = dev->loop_vid_out.width == dev->loop_vid_cap.width;
dev->cur_scaled_line = dev->loop_vid_out.height;
for (y = 0; y < hmax; y += vdiv, vcapbuf += stride_cap) {
/* osdline is true if this line requires overlay blending */
bool osdline = vosdbuf && y >= dev->loop_vid_overlay_cap.top &&
y < dev->loop_vid_overlay_cap.top + dev->loop_vid_overlay_cap.height;
/*
* If this line of the capture buffer doesn't get any video, then
* just fill with black.
*/
if (y < dev->loop_vid_cap.top ||
y >= dev->loop_vid_cap.top + dev->loop_vid_cap.height) {
memcpy(vcapbuf, tpg->black_line[p], img_width);
continue;
}
/* fill the left border with black */
if (dev->loop_vid_cap.left)
memcpy(vcapbuf, tpg->black_line[p], vid_cap_left);
/* fill the right border with black */
if (vid_cap_right < img_width)
memcpy(vcapbuf + vid_cap_right, tpg->black_line[p],
img_width - vid_cap_right);
if (quick && !osdline) {
memcpy(vcapbuf + vid_cap_left,
voutbuf + vid_out_y * stride_out,
tpg_hdiv(tpg, p, dev->loop_vid_cap.width));
goto update_vid_out_y;
}
if (dev->cur_scaled_line == vid_out_y) {
memcpy(vcapbuf + vid_cap_left, dev->scaled_line,
tpg_hdiv(tpg, p, dev->loop_vid_cap.width));
goto update_vid_out_y;
}
if (!osdline) {
scale_line(voutbuf + vid_out_y * stride_out, dev->scaled_line,
tpg_hdiv(tpg, p, dev->loop_vid_out.width),
tpg_hdiv(tpg, p, dev->loop_vid_cap.width),
tpg_g_twopixelsize(tpg, p));
} else {
/*
* Offset in bytes within loop_vid_copy to the start of the
* loop_vid_overlay rectangle.
*/
unsigned offset =
((dev->loop_vid_overlay.left - dev->loop_vid_copy.left) *
twopixsize) / 2;
u8 *osd = vosdbuf + vid_overlay_y * stride_osd;
scale_line(voutbuf + vid_out_y * stride_out, dev->blended_line,
dev->loop_vid_out.width, dev->loop_vid_copy.width,
tpg_g_twopixelsize(tpg, p));
if (blend)
blend_line(dev, vid_overlay_y + dev->loop_vid_overlay.top,
dev->loop_vid_overlay.left,
dev->blended_line + offset, osd,
dev->loop_vid_overlay.width, twopixsize / 2);
else
memcpy(dev->blended_line + offset,
osd, (dev->loop_vid_overlay.width * twopixsize) / 2);
scale_line(dev->blended_line, dev->scaled_line,
dev->loop_vid_copy.width, dev->loop_vid_cap.width,
tpg_g_twopixelsize(tpg, p));
}
dev->cur_scaled_line = vid_out_y;
memcpy(vcapbuf + vid_cap_left, dev->scaled_line,
tpg_hdiv(tpg, p, dev->loop_vid_cap.width));
update_vid_out_y:
if (osdline) {
vid_overlay_y += vid_overlay_int_part;
vid_overlay_error += vid_overlay_fract_part;
if (vid_overlay_error >= dev->loop_vid_overlay_cap.height) {
vid_overlay_error -= dev->loop_vid_overlay_cap.height;
vid_overlay_y++;
}
}
vid_out_y += vid_out_int_part;
vid_out_error += vid_out_fract_part;
if (vid_out_error >= dev->loop_vid_cap.height / vdiv) {
vid_out_error -= dev->loop_vid_cap.height / vdiv;
vid_out_y++;
}
}
if (!blank)
return 0;
for (; y < img_height; y += vdiv, vcapbuf += stride_cap)
memcpy(vcapbuf, tpg->contrast_line[p], img_width);
return 0;
}
static void vivid_fillbuff(struct vivid_dev *dev, struct vivid_buffer *buf)
{
struct vivid_dev *out_dev = NULL;
struct tpg_data *tpg = &dev->tpg;
unsigned factor = V4L2_FIELD_HAS_T_OR_B(dev->field_cap) ? 2 : 1;
unsigned line_height = 16 / factor;
bool is_tv = vivid_is_sdtv_cap(dev);
bool is_60hz = is_tv && (dev->std_cap[dev->input] & V4L2_STD_525_60);
unsigned p;
int line = 1;
u8 *basep[TPG_MAX_PLANES][2];
unsigned ms;
char str[100];
s32 gain;
buf->vb.sequence = dev->vid_cap_seq_count;
v4l2_ctrl_s_ctrl(dev->ro_int32, buf->vb.sequence & 0xff);
if (dev->field_cap == V4L2_FIELD_ALTERNATE) {
/*
* 60 Hz standards start with the bottom field, 50 Hz standards
* with the top field. So if the 0-based seq_count is even,
* then the field is TOP for 50 Hz and BOTTOM for 60 Hz
* standards.
*/
buf->vb.field = ((dev->vid_cap_seq_count & 1) ^ is_60hz) ?
V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;
/*
* The sequence counter counts frames, not fields. So divide
* by two.
*/
buf->vb.sequence /= 2;
} else {
buf->vb.field = dev->field_cap;
}
tpg_s_field(tpg, buf->vb.field,
dev->field_cap == V4L2_FIELD_ALTERNATE);
tpg_s_perc_fill_blank(tpg, dev->must_blank[buf->vb.vb2_buf.index]);
if (vivid_vid_can_loop(dev) &&
((vivid_is_svid_cap(dev) &&
!VIVID_INVALID_SIGNAL(dev->std_signal_mode[dev->input])) ||
(vivid_is_hdmi_cap(dev) &&
!VIVID_INVALID_SIGNAL(dev->dv_timings_signal_mode[dev->input])))) {
out_dev = vivid_input_is_connected_to(dev);
/*
* If the vivid instance of the output device is different
* from the vivid instance of this input device, then we
* must take care to properly serialize the output device to
* prevent that the buffer we are copying from is being freed.
*
* If the output device is part of the same instance, then the
* lock is already taken and there is no need to take the mutex.
*
* The problem with taking the mutex is that you can get
* deadlocked if instance A locks instance B and vice versa.
* It is not really worth trying to be very smart about this,
* so just try to take the lock, and if you can't, then just
* set out_dev to NULL and you will end up with a single frame
* of Noise (the default test pattern in this case).
*/
if (out_dev && dev != out_dev && !mutex_trylock(&out_dev->mutex))
out_dev = NULL;
}
if (out_dev)
vivid_precalc_copy_rects(dev, out_dev);
for (p = 0; p < tpg_g_planes(tpg); p++) {
void *vbuf = plane_vaddr(tpg, buf, p,
tpg->bytesperline, tpg->buf_height);
/*
* The first plane of a multiplanar format has a non-zero
* data_offset. This helps testing whether the application
* correctly supports non-zero data offsets.
*/
if (p < tpg_g_buffers(tpg) && dev->fmt_cap->data_offset[p]) {
memset(vbuf, dev->fmt_cap->data_offset[p] & 0xff,
dev->fmt_cap->data_offset[p]);
vbuf += dev->fmt_cap->data_offset[p];
}
tpg_calc_text_basep(tpg, basep, p, vbuf);
if (!out_dev || vivid_copy_buffer(dev, out_dev, p, vbuf, buf))
tpg_fill_plane_buffer(tpg, vivid_get_std_cap(dev),
p, vbuf);
}
if (out_dev && dev != out_dev)
mutex_unlock(&out_dev->mutex);
dev->must_blank[buf->vb.vb2_buf.index] = false;
/* Updates stream time, only update at the start of a new frame. */
if (dev->field_cap != V4L2_FIELD_ALTERNATE ||
(dev->vid_cap_seq_count & 1) == 0)
dev->ms_vid_cap =
jiffies_to_msecs(jiffies - dev->jiffies_vid_cap);
ms = dev->ms_vid_cap;
if (dev->osd_mode <= 1) {
snprintf(str, sizeof(str), " %02d:%02d:%02d:%03d %u%s",
(ms / (60 * 60 * 1000)) % 24,
(ms / (60 * 1000)) % 60,
(ms / 1000) % 60,
ms % 1000,
buf->vb.sequence,
(dev->field_cap == V4L2_FIELD_ALTERNATE) ?
(buf->vb.field == V4L2_FIELD_TOP ?
" top" : " bottom") : "");
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
}
if (dev->osd_mode == 0) {
snprintf(str, sizeof(str), " %dx%d, input %d ",
dev->src_rect.width, dev->src_rect.height, dev->input);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
gain = v4l2_ctrl_g_ctrl(dev->gain);
mutex_lock(dev->ctrl_hdl_user_vid.lock);
snprintf(str, sizeof(str),
" brightness %3d, contrast %3d, saturation %3d, hue %d ",
dev->brightness->cur.val,
dev->contrast->cur.val,
dev->saturation->cur.val,
dev->hue->cur.val);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
snprintf(str, sizeof(str),
" autogain %d, gain %3d, alpha 0x%02x ",
dev->autogain->cur.val, gain, dev->alpha->cur.val);
mutex_unlock(dev->ctrl_hdl_user_vid.lock);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
mutex_lock(dev->ctrl_hdl_user_aud.lock);
snprintf(str, sizeof(str),
" volume %3d, mute %d ",
dev->volume->cur.val, dev->mute->cur.val);
mutex_unlock(dev->ctrl_hdl_user_aud.lock);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
mutex_lock(dev->ctrl_hdl_user_gen.lock);
snprintf(str, sizeof(str), " int32 %d, ro_int32 %d, int64 %lld, bitmask %08x ",
dev->int32->cur.val,
dev->ro_int32->cur.val,
*dev->int64->p_cur.p_s64,
dev->bitmask->cur.val);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
snprintf(str, sizeof(str), " boolean %d, menu %s, string \"%s\" ",
dev->boolean->cur.val,
dev->menu->qmenu[dev->menu->cur.val],
dev->string->p_cur.p_char);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
snprintf(str, sizeof(str), " integer_menu %lld, value %d ",
dev->int_menu->qmenu_int[dev->int_menu->cur.val],
dev->int_menu->cur.val);
mutex_unlock(dev->ctrl_hdl_user_gen.lock);
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
if (dev->button_pressed) {
dev->button_pressed--;
snprintf(str, sizeof(str), " button pressed!");
tpg_gen_text(tpg, basep, line++ * line_height, 16, str);
}
if (dev->osd[0]) {
if (vivid_is_hdmi_cap(dev)) {
snprintf(str, sizeof(str),
" OSD \"%s\"", dev->osd);
tpg_gen_text(tpg, basep, line++ * line_height,
16, str);
}
if (dev->osd_jiffies &&
time_is_before_jiffies(dev->osd_jiffies + 5 * HZ)) {
dev->osd[0] = 0;
dev->osd_jiffies = 0;
}
}
}
}
static void vivid_cap_update_frame_period(struct vivid_dev *dev)
{
u64 f_period;
f_period = (u64)dev->timeperframe_vid_cap.numerator * 1000000000;
if (WARN_ON(dev->timeperframe_vid_cap.denominator == 0))
dev->timeperframe_vid_cap.denominator = 1;
do_div(f_period, dev->timeperframe_vid_cap.denominator);
if (dev->field_cap == V4L2_FIELD_ALTERNATE)
f_period >>= 1;
/*
* If "End of Frame", then offset the exposure time by 0.9
* of the frame period.
*/
dev->cap_frame_eof_offset = f_period * 9;
do_div(dev->cap_frame_eof_offset, 10);
dev->cap_frame_period = f_period;
}
static noinline_for_stack void vivid_thread_vid_cap_tick(struct vivid_dev *dev,
int dropped_bufs)
{
struct vivid_buffer *vid_cap_buf = NULL;
struct vivid_buffer *vbi_cap_buf = NULL;
struct vivid_buffer *meta_cap_buf = NULL;
u64 f_time = 0;
dprintk(dev, 1, "Video Capture Thread Tick\n");
while (dropped_bufs-- > 1)
tpg_update_mv_count(&dev->tpg,
dev->field_cap == V4L2_FIELD_NONE ||
dev->field_cap == V4L2_FIELD_ALTERNATE);
/* Drop a certain percentage of buffers. */
if (dev->perc_dropped_buffers &&
get_random_u32_below(100) < dev->perc_dropped_buffers)
goto update_mv;
spin_lock(&dev->slock);
if (!list_empty(&dev->vid_cap_active)) {
vid_cap_buf = list_entry(dev->vid_cap_active.next, struct vivid_buffer, list);
list_del(&vid_cap_buf->list);
}
if (!list_empty(&dev->vbi_cap_active)) {
if (dev->field_cap != V4L2_FIELD_ALTERNATE ||
(dev->vbi_cap_seq_count & 1)) {
vbi_cap_buf = list_entry(dev->vbi_cap_active.next,
struct vivid_buffer, list);
list_del(&vbi_cap_buf->list);
}
}
if (!list_empty(&dev->meta_cap_active)) {
meta_cap_buf = list_entry(dev->meta_cap_active.next,
struct vivid_buffer, list);
list_del(&meta_cap_buf->list);
}
spin_unlock(&dev->slock);
if (!vid_cap_buf && !vbi_cap_buf && !meta_cap_buf)
goto update_mv;
f_time = ktime_get_ns() + dev->time_wrap_offset;
if (vid_cap_buf) {
v4l2_ctrl_request_setup(vid_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_cap);
/* Fill buffer */
vivid_fillbuff(dev, vid_cap_buf);
dprintk(dev, 1, "filled buffer %d\n",
vid_cap_buf->vb.vb2_buf.index);
v4l2_ctrl_request_complete(vid_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_cap);
vb2_buffer_done(&vid_cap_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vid_cap buffer %d done\n",
vid_cap_buf->vb.vb2_buf.index);
vid_cap_buf->vb.vb2_buf.timestamp = f_time;
if (!dev->tstamp_src_is_soe)
vid_cap_buf->vb.vb2_buf.timestamp += dev->cap_frame_eof_offset;
}
if (vbi_cap_buf) {
u64 vbi_period;
v4l2_ctrl_request_setup(vbi_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_cap);
if (vbi_cap_buf->vb.vb2_buf.type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE)
vivid_sliced_vbi_cap_process(dev, vbi_cap_buf);
else
vivid_raw_vbi_cap_process(dev, vbi_cap_buf);
v4l2_ctrl_request_complete(vbi_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_cap);
vb2_buffer_done(&vbi_cap_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vbi_cap %d done\n",
vbi_cap_buf->vb.vb2_buf.index);
/* If capturing a VBI, offset by 0.05 */
vbi_period = dev->cap_frame_period * 5;
do_div(vbi_period, 100);
vbi_cap_buf->vb.vb2_buf.timestamp = f_time + dev->cap_frame_eof_offset + vbi_period;
}
if (meta_cap_buf) {
v4l2_ctrl_request_setup(meta_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_cap);
vivid_meta_cap_fillbuff(dev, meta_cap_buf, f_time);
v4l2_ctrl_request_complete(meta_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_cap);
vb2_buffer_done(&meta_cap_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "meta_cap %d done\n",
meta_cap_buf->vb.vb2_buf.index);
meta_cap_buf->vb.vb2_buf.timestamp = f_time + dev->cap_frame_eof_offset;
}
dev->dqbuf_error = false;
update_mv:
/* Update the test pattern movement counters */
tpg_update_mv_count(&dev->tpg, dev->field_cap == V4L2_FIELD_NONE ||
dev->field_cap == V4L2_FIELD_ALTERNATE);
}
static int vivid_thread_vid_cap(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;
int dropped_bufs;
dprintk(dev, 1, "Video Capture Thread Start\n");
set_freezable();
/* Resets frame counters */
dev->cap_seq_offset = 0;
dev->cap_seq_count = 0;
dev->cap_seq_resync = false;
dev->jiffies_vid_cap = jiffies;
dev->cap_stream_start = ktime_get_ns();
if (dev->time_wrap)
dev->time_wrap_offset = dev->time_wrap - dev->cap_stream_start;
else
dev->time_wrap_offset = 0;
vivid_cap_update_frame_period(dev);
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
if (!mutex_trylock(&dev->mutex)) {
schedule();
continue;
}
cur_jiffies = jiffies;
if (dev->cap_seq_resync) {
dev->jiffies_vid_cap = cur_jiffies;
dev->cap_seq_offset = dev->cap_seq_count + 1;
dev->cap_seq_count = 0;
dev->cap_stream_start += dev->cap_frame_period *
dev->cap_seq_offset;
vivid_cap_update_frame_period(dev);
dev->cap_seq_resync = false;
}
numerator = dev->timeperframe_vid_cap.numerator;
denominator = dev->timeperframe_vid_cap.denominator;
if (dev->field_cap == V4L2_FIELD_ALTERNATE)
denominator *= 2;
/* Calculate the number of jiffies since we started streaming */
jiffies_since_start = cur_jiffies - dev->jiffies_vid_cap;
/* 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_cap = cur_jiffies;
dev->cap_seq_offset = buffers_since_start;
buffers_since_start = 0;
}
dropped_bufs = buffers_since_start + dev->cap_seq_offset - dev->cap_seq_count;
dev->cap_seq_count = buffers_since_start + dev->cap_seq_offset;
dev->vid_cap_seq_count = dev->cap_seq_count - dev->vid_cap_seq_start;
dev->vbi_cap_seq_count = dev->cap_seq_count - dev->vbi_cap_seq_start;
dev->meta_cap_seq_count = dev->cap_seq_count - dev->meta_cap_seq_start;
vivid_thread_vid_cap_tick(dev, dropped_bufs);
/*
* Calculate the number of 'numerators' streamed since we started,
* 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_cap;
mutex_unlock(&dev->mutex);
/*
* 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;
if (!time_is_after_jiffies(cur_jiffies + wait_jiffies))
continue;
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait, kthread_should_stop(),
cur_jiffies + wait_jiffies - jiffies);
}
dprintk(dev, 1, "Video Capture Thread End\n");
return 0;
}
static void vivid_grab_controls(struct vivid_dev *dev, bool grab)
{
v4l2_ctrl_grab(dev->ctrl_has_crop_cap, grab);
v4l2_ctrl_grab(dev->ctrl_has_compose_cap, grab);
v4l2_ctrl_grab(dev->ctrl_has_scaler_cap, grab);
}
int vivid_start_generating_vid_cap(struct vivid_dev *dev, bool *pstreaming)
{
dprintk(dev, 1, "%s\n", __func__);
if (dev->kthread_vid_cap) {
u32 seq_count = dev->cap_seq_count + dev->seq_wrap * 128;
if (pstreaming == &dev->vid_cap_streaming)
dev->vid_cap_seq_start = seq_count;
else if (pstreaming == &dev->vbi_cap_streaming)
dev->vbi_cap_seq_start = seq_count;
else
dev->meta_cap_seq_start = seq_count;
*pstreaming = true;
return 0;
}
/* Resets frame counters */
tpg_init_mv_count(&dev->tpg);
dev->vid_cap_seq_start = dev->seq_wrap * 128;
dev->vbi_cap_seq_start = dev->seq_wrap * 128;
dev->meta_cap_seq_start = dev->seq_wrap * 128;
dev->kthread_vid_cap = kthread_run(vivid_thread_vid_cap, dev,
"%s-vid-cap", dev->v4l2_dev.name);
if (IS_ERR(dev->kthread_vid_cap)) {
int err = PTR_ERR(dev->kthread_vid_cap);
dev->kthread_vid_cap = NULL;
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return err;
}
*pstreaming = true;
vivid_grab_controls(dev, true);
dprintk(dev, 1, "returning from %s\n", __func__);
return 0;
}
void vivid_stop_generating_vid_cap(struct vivid_dev *dev, bool *pstreaming)
{
dprintk(dev, 1, "%s\n", __func__);
if (dev->kthread_vid_cap == NULL)
return;
*pstreaming = false;
if (pstreaming == &dev->vid_cap_streaming) {
/* Release all active buffers */
while (!list_empty(&dev->vid_cap_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->vid_cap_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_cap);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vid_cap buffer %d done\n",
buf->vb.vb2_buf.index);
}
}
if (pstreaming == &dev->vbi_cap_streaming) {
while (!list_empty(&dev->vbi_cap_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->vbi_cap_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_cap);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vbi_cap buffer %d done\n",
buf->vb.vb2_buf.index);
}
}
if (pstreaming == &dev->meta_cap_streaming) {
while (!list_empty(&dev->meta_cap_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->meta_cap_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_cap);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "meta_cap buffer %d done\n",
buf->vb.vb2_buf.index);
}
}
if (dev->vid_cap_streaming || dev->vbi_cap_streaming ||
dev->meta_cap_streaming)
return;
/* shutdown control thread */
vivid_grab_controls(dev, false);
kthread_stop(dev->kthread_vid_cap);
dev->kthread_vid_cap = NULL;
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-kthread-cap.h - video/vbi capture thread support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_KTHREAD_CAP_H_
#define _VIVID_KTHREAD_CAP_H_
int vivid_start_generating_vid_cap(struct vivid_dev *dev, bool *pstreaming);
void vivid_stop_generating_vid_cap(struct vivid_dev *dev, bool *pstreaming);
#endif
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-kthread-out.h - video/vbi output thread support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#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 <linux/jiffies.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"
#include "vivid-kthread-out.h"
#include "vivid-meta-out.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;
struct vivid_buffer *meta_out_buf = NULL;
dprintk(dev, 1, "Video Output Thread Tick\n");
/* Drop a certain percentage of buffers. */
if (dev->perc_dropped_buffers &&
get_random_u32_below(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);
}
if (!list_empty(&dev->meta_out_active)) {
meta_out_buf = list_entry(dev->meta_out_active.next,
struct vivid_buffer, list);
list_del(&meta_out_buf->list);
}
spin_unlock(&dev->slock);
if (!vid_out_buf && !vbi_out_buf && !meta_out_buf)
return;
if (vid_out_buf) {
v4l2_ctrl_request_setup(vid_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_out);
v4l2_ctrl_request_complete(vid_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_out);
vid_out_buf->vb.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.sequence /= 2;
}
vid_out_buf->vb.vb2_buf.timestamp =
ktime_get_ns() + dev->time_wrap_offset;
vb2_buffer_done(&vid_out_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vid_out buffer %d done\n",
vid_out_buf->vb.vb2_buf.index);
}
if (vbi_out_buf) {
v4l2_ctrl_request_setup(vbi_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_out);
v4l2_ctrl_request_complete(vbi_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_out);
if (dev->stream_sliced_vbi_out)
vivid_sliced_vbi_out_process(dev, vbi_out_buf);
vbi_out_buf->vb.sequence = dev->vbi_out_seq_count;
vbi_out_buf->vb.vb2_buf.timestamp =
ktime_get_ns() + dev->time_wrap_offset;
vb2_buffer_done(&vbi_out_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "vbi_out buffer %d done\n",
vbi_out_buf->vb.vb2_buf.index);
}
if (meta_out_buf) {
v4l2_ctrl_request_setup(meta_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_out);
v4l2_ctrl_request_complete(meta_out_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_out);
vivid_meta_out_process(dev, meta_out_buf);
meta_out_buf->vb.sequence = dev->meta_out_seq_count;
meta_out_buf->vb.vb2_buf.timestamp =
ktime_get_ns() + dev->time_wrap_offset;
vb2_buffer_done(&meta_out_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "meta_out buffer %d done\n",
meta_out_buf->vb.vb2_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;
dev->out_seq_count = 0;
dev->jiffies_vid_out = jiffies;
dev->out_seq_resync = false;
if (dev->time_wrap)
dev->time_wrap_offset = dev->time_wrap - ktime_get_ns();
else
dev->time_wrap_offset = 0;
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
if (!mutex_trylock(&dev->mutex)) {
schedule();
continue;
}
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;
dev->meta_out_seq_count = dev->out_seq_count - dev->meta_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;
if (!time_is_after_jiffies(cur_jiffies + wait_jiffies))
continue;
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait, kthread_should_stop(),
cur_jiffies + wait_jiffies - jiffies);
}
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 if (pstreaming == &dev->vbi_out_streaming)
dev->vbi_out_seq_start = seq_count;
else
dev->meta_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->meta_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)) {
int err = PTR_ERR(dev->kthread_vid_out);
dev->kthread_vid_out = NULL;
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return err;
}
*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);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vid_out);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vid_out buffer %d done\n",
buf->vb.vb2_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);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_vbi_out);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "vbi_out buffer %d done\n",
buf->vb.vb2_buf.index);
}
}
if (pstreaming == &dev->meta_out_streaming) {
while (!list_empty(&dev->meta_out_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->meta_out_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_meta_out);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "meta_out buffer %d done\n",
buf->vb.vb2_buf.index);
}
}
if (dev->vid_out_streaming || dev->vbi_out_streaming ||
dev->meta_out_streaming)
return;
/* shutdown control thread */
vivid_grab_controls(dev, false);
kthread_stop(dev->kthread_vid_out);
dev->kthread_vid_out = NULL;
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-kthread-out.h - video/vbi output thread support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_KTHREAD_OUT_H_
#define _VIVID_KTHREAD_OUT_H_
int vivid_start_generating_vid_out(struct vivid_dev *dev, bool *pstreaming);
void vivid_stop_generating_vid_out(struct vivid_dev *dev, bool *pstreaming);
#endif
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-kthread-touch.c - touch capture thread support functions.
*
*/
#include <linux/freezer.h>
#include <linux/jiffies.h>
#include "vivid-core.h"
#include "vivid-kthread-touch.h"
#include "vivid-touch-cap.h"
static noinline_for_stack void vivid_thread_tch_cap_tick(struct vivid_dev *dev,
int dropped_bufs)
{
struct vivid_buffer *tch_cap_buf = NULL;
spin_lock(&dev->slock);
if (!list_empty(&dev->touch_cap_active)) {
tch_cap_buf = list_entry(dev->touch_cap_active.next,
struct vivid_buffer, list);
list_del(&tch_cap_buf->list);
}
spin_unlock(&dev->slock);
if (tch_cap_buf) {
v4l2_ctrl_request_setup(tch_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_touch_cap);
vivid_fillbuff_tch(dev, tch_cap_buf);
v4l2_ctrl_request_complete(tch_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_touch_cap);
vb2_buffer_done(&tch_cap_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dprintk(dev, 2, "touch_cap buffer %d done\n",
tch_cap_buf->vb.vb2_buf.index);
tch_cap_buf->vb.vb2_buf.timestamp = ktime_get_ns() + dev->time_wrap_offset;
}
dev->dqbuf_error = false;
}
static int vivid_thread_touch_cap(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 int wait_jiffies;
unsigned int numerator;
unsigned int denominator;
int dropped_bufs;
dprintk(dev, 1, "Touch Capture Thread Start\n");
set_freezable();
/* Resets frame counters */
dev->touch_cap_seq_offset = 0;
dev->touch_cap_seq_count = 0;
dev->touch_cap_seq_resync = false;
dev->jiffies_touch_cap = jiffies;
if (dev->time_wrap)
dev->time_wrap_offset = dev->time_wrap - ktime_get_ns();
else
dev->time_wrap_offset = 0;
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
if (!mutex_trylock(&dev->mutex)) {
schedule();
continue;
}
cur_jiffies = jiffies;
if (dev->touch_cap_seq_resync) {
dev->jiffies_touch_cap = cur_jiffies;
dev->touch_cap_seq_offset = dev->touch_cap_seq_count + 1;
dev->touch_cap_seq_count = 0;
dev->cap_seq_resync = false;
}
denominator = dev->timeperframe_tch_cap.denominator;
numerator = dev->timeperframe_tch_cap.numerator;
/* Calculate the number of jiffies since we started streaming */
jiffies_since_start = cur_jiffies - dev->jiffies_touch_cap;
/* 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_touch_cap = cur_jiffies;
dev->cap_seq_offset = buffers_since_start;
buffers_since_start = 0;
}
dropped_bufs = buffers_since_start + dev->touch_cap_seq_offset - dev->touch_cap_seq_count;
dev->touch_cap_seq_count = buffers_since_start + dev->touch_cap_seq_offset;
dev->touch_cap_with_seq_wrap_count =
dev->touch_cap_seq_count - dev->touch_cap_seq_start;
vivid_thread_tch_cap_tick(dev, dropped_bufs);
/*
* Calculate the number of 'numerators' streamed
* since we started, 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_touch_cap;
mutex_unlock(&dev->mutex);
/*
* 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;
if (!time_is_after_jiffies(cur_jiffies + wait_jiffies))
continue;
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait, kthread_should_stop(),
cur_jiffies + wait_jiffies - jiffies);
}
dprintk(dev, 1, "Touch Capture Thread End\n");
return 0;
}
int vivid_start_generating_touch_cap(struct vivid_dev *dev)
{
if (dev->kthread_touch_cap) {
dev->touch_cap_streaming = true;
return 0;
}
dev->touch_cap_seq_start = dev->seq_wrap * 128;
dev->kthread_touch_cap = kthread_run(vivid_thread_touch_cap, dev,
"%s-tch-cap", dev->v4l2_dev.name);
if (IS_ERR(dev->kthread_touch_cap)) {
int err = PTR_ERR(dev->kthread_touch_cap);
dev->kthread_touch_cap = NULL;
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
return err;
}
dev->touch_cap_streaming = true;
dprintk(dev, 1, "returning from %s\n", __func__);
return 0;
}
void vivid_stop_generating_touch_cap(struct vivid_dev *dev)
{
if (!dev->kthread_touch_cap)
return;
dev->touch_cap_streaming = false;
while (!list_empty(&dev->touch_cap_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->touch_cap_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_touch_cap);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
dprintk(dev, 2, "touch_cap buffer %d done\n",
buf->vb.vb2_buf.index);
}
kthread_stop(dev->kthread_touch_cap);
dev->kthread_touch_cap = NULL;
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-kthread-cap.h - video/vbi capture thread support functions.
*
*/
#ifndef _VIVID_KTHREAD_CAP_H_
#define _VIVID_KTHREAD_CAP_H_
int vivid_start_generating_touch_cap(struct vivid_dev *dev);
void vivid_stop_generating_touch_cap(struct vivid_dev *dev);
#endif
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-meta-cap.c - meta capture support functions.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include <linux/usb/video.h>
#include "vivid-core.h"
#include "vivid-kthread-cap.h"
#include "vivid-meta-cap.h"
static int meta_cap_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
unsigned int size = sizeof(struct vivid_uvc_meta_buf);
if (!vivid_is_webcam(dev))
return -EINVAL;
if (*nplanes) {
if (sizes[0] < size)
return -EINVAL;
} else {
sizes[0] = size;
}
*nplanes = 1;
return 0;
}
static int meta_cap_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
unsigned int size = sizeof(struct vivid_uvc_meta_buf);
dprintk(dev, 1, "%s\n", __func__);
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void meta_cap_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
dprintk(dev, 1, "%s\n", __func__);
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->meta_cap_active);
spin_unlock(&dev->slock);
}
static int meta_cap_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err;
dprintk(dev, 1, "%s\n", __func__);
dev->meta_cap_seq_count = 0;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else {
err = vivid_start_generating_vid_cap(dev,
&dev->meta_cap_streaming);
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp,
&dev->meta_cap_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void meta_cap_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
dprintk(dev, 1, "%s\n", __func__);
vivid_stop_generating_vid_cap(dev, &dev->meta_cap_streaming);
}
static void meta_cap_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_meta_cap);
}
const struct vb2_ops vivid_meta_cap_qops = {
.queue_setup = meta_cap_queue_setup,
.buf_prepare = meta_cap_buf_prepare,
.buf_queue = meta_cap_buf_queue,
.start_streaming = meta_cap_start_streaming,
.stop_streaming = meta_cap_stop_streaming,
.buf_request_complete = meta_cap_buf_request_complete,
};
int vidioc_enum_fmt_meta_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
struct vivid_dev *dev = video_drvdata(file);
if (!vivid_is_webcam(dev))
return -EINVAL;
if (f->index > 0)
return -EINVAL;
f->type = V4L2_BUF_TYPE_META_CAPTURE;
f->pixelformat = V4L2_META_FMT_UVC;
return 0;
}
int vidioc_g_fmt_meta_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_meta_format *meta = &f->fmt.meta;
if (!vivid_is_webcam(dev) || !dev->has_meta_cap)
return -EINVAL;
meta->dataformat = V4L2_META_FMT_UVC;
meta->buffersize = sizeof(struct vivid_uvc_meta_buf);
return 0;
}
void vivid_meta_cap_fillbuff(struct vivid_dev *dev,
struct vivid_buffer *buf, u64 soe)
{
struct vivid_uvc_meta_buf *meta = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
int buf_off = 0;
buf->vb.sequence = dev->meta_cap_seq_count;
if (dev->field_cap == V4L2_FIELD_ALTERNATE)
buf->vb.sequence /= 2;
memset(meta, 1, vb2_plane_size(&buf->vb.vb2_buf, 0));
meta->ns = ktime_get_ns();
meta->sof = buf->vb.sequence * 30;
meta->length = sizeof(*meta) - offsetof(struct vivid_uvc_meta_buf, length);
meta->flags = UVC_STREAM_EOH | UVC_STREAM_EOF;
if ((buf->vb.sequence % 2) == 0)
meta->flags |= UVC_STREAM_FID;
dprintk(dev, 2, "%s ns:%llu sof:%4d len:%u flags: 0x%02x",
__func__, meta->ns, meta->sof, meta->length, meta->flags);
if (dev->meta_pts) {
meta->flags |= UVC_STREAM_PTS;
meta->buf[0] = div_u64(soe, VIVID_META_CLOCK_UNIT);
buf_off = 4;
dprintk(dev, 2, " pts: %u\n", *(__u32 *)(meta->buf));
}
if (dev->meta_scr) {
meta->flags |= UVC_STREAM_SCR;
meta->buf[buf_off] = div_u64((soe + dev->cap_frame_eof_offset),
VIVID_META_CLOCK_UNIT);
meta->buf[buf_off + 4] = (buf->vb.sequence * 30) % 1000;
dprintk(dev, 2, " stc: %u, sof counter: %u\n",
*(__u32 *)(meta->buf + buf_off),
*(__u16 *)(meta->buf + buf_off + 4));
}
dprintk(dev, 2, "\n");
}
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-meta-cap.h - meta capture support functions.
*/
#ifndef _VIVID_META_CAP_H_
#define _VIVID_META_CAP_H_
#define VIVID_META_CLOCK_UNIT 10 /* 100 MHz */
struct vivid_uvc_meta_buf {
__u64 ns;
__u16 sof;
__u8 length;
__u8 flags;
__u8 buf[10]; /* PTS(4)+STC(4)+SOF(2) */
} __packed;
void vivid_meta_cap_fillbuff(struct vivid_dev *dev,
struct vivid_buffer *buf, u64 soe);
int vidioc_enum_fmt_meta_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f);
int vidioc_g_fmt_meta_cap(struct file *file, void *priv,
struct v4l2_format *f);
extern const struct vb2_ops vivid_meta_cap_qops;
#endif
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-meta-out.c - meta output support functions.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include <linux/usb/video.h>
#include "vivid-core.h"
#include "vivid-kthread-out.h"
#include "vivid-meta-out.h"
static int meta_out_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
unsigned int size = sizeof(struct vivid_meta_out_buf);
if (!vivid_is_webcam(dev))
return -EINVAL;
if (*nplanes) {
if (sizes[0] < size)
return -EINVAL;
} else {
sizes[0] = size;
}
*nplanes = 1;
return 0;
}
static int meta_out_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
unsigned int size = sizeof(struct vivid_meta_out_buf);
dprintk(dev, 1, "%s\n", __func__);
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void meta_out_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
dprintk(dev, 1, "%s\n", __func__);
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->meta_out_active);
spin_unlock(&dev->slock);
}
static int meta_out_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err;
dprintk(dev, 1, "%s\n", __func__);
dev->meta_out_seq_count = 0;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else {
err = vivid_start_generating_vid_out(dev,
&dev->meta_out_streaming);
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp,
&dev->meta_out_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void meta_out_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
dprintk(dev, 1, "%s\n", __func__);
vivid_stop_generating_vid_out(dev, &dev->meta_out_streaming);
}
static void meta_out_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_meta_out);
}
const struct vb2_ops vivid_meta_out_qops = {
.queue_setup = meta_out_queue_setup,
.buf_prepare = meta_out_buf_prepare,
.buf_queue = meta_out_buf_queue,
.start_streaming = meta_out_start_streaming,
.stop_streaming = meta_out_stop_streaming,
.buf_request_complete = meta_out_buf_request_complete,
};
int vidioc_enum_fmt_meta_out(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
struct vivid_dev *dev = video_drvdata(file);
if (!vivid_is_webcam(dev))
return -EINVAL;
if (f->index > 0)
return -EINVAL;
f->type = V4L2_BUF_TYPE_META_OUTPUT;
f->pixelformat = V4L2_META_FMT_VIVID;
return 0;
}
int vidioc_g_fmt_meta_out(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_meta_format *meta = &f->fmt.meta;
if (!vivid_is_webcam(dev) || !dev->has_meta_out)
return -EINVAL;
meta->dataformat = V4L2_META_FMT_VIVID;
meta->buffersize = sizeof(struct vivid_meta_out_buf);
return 0;
}
void vivid_meta_out_process(struct vivid_dev *dev,
struct vivid_buffer *buf)
{
struct vivid_meta_out_buf *meta = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
v4l2_ctrl_s_ctrl(dev->brightness, meta->brightness);
v4l2_ctrl_s_ctrl(dev->contrast, meta->contrast);
v4l2_ctrl_s_ctrl(dev->saturation, meta->saturation);
v4l2_ctrl_s_ctrl(dev->hue, meta->hue);
dprintk(dev, 2, " %s brightness %u contrast %u saturation %u hue %d\n",
__func__, meta->brightness, meta->contrast,
meta->saturation, meta->hue);
}
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-meta-out.h - meta output support functions.
*/
#ifndef _VIVID_META_OUT_H_
#define _VIVID_META_OUT_H_
struct vivid_meta_out_buf {
u16 brightness;
u16 contrast;
u16 saturation;
s16 hue;
};
void vivid_meta_out_process(struct vivid_dev *dev, struct vivid_buffer *buf);
int vidioc_enum_fmt_meta_out(struct file *file, void *priv,
struct v4l2_fmtdesc *f);
int vidioc_g_fmt_meta_out(struct file *file, void *priv,
struct v4l2_format *f);
int vidioc_s_fmt_meta_out(struct file *file, void *priv,
struct v4l2_format *f);
extern const struct vb2_ops vivid_meta_out_qops;
#endif
@@ -0,0 +1,401 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-osd.c - osd support for testing overlays.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#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/fb.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>
#include "vivid-core.h"
#include "vivid-osd.h"
#define MAX_OSD_WIDTH 720
#define MAX_OSD_HEIGHT 576
/*
* Order: white, yellow, cyan, green, magenta, red, blue, black,
* and same again with the alpha bit set (if any)
*/
static const u16 rgb555[16] = {
0x7fff, 0x7fe0, 0x03ff, 0x03e0, 0x7c1f, 0x7c00, 0x001f, 0x0000,
0xffff, 0xffe0, 0x83ff, 0x83e0, 0xfc1f, 0xfc00, 0x801f, 0x8000
};
static const u16 rgb565[16] = {
0xffff, 0xffe0, 0x07ff, 0x07e0, 0xf81f, 0xf800, 0x001f, 0x0000,
0xffff, 0xffe0, 0x07ff, 0x07e0, 0xf81f, 0xf800, 0x001f, 0x0000
};
unsigned int vivid_fb_green_bits(struct vivid_dev *dev)
{
return dev->fb_defined.green.length;
}
void vivid_fb_clear(struct vivid_dev *dev)
{
void *p = dev->video_vbase;
const u16 *rgb = rgb555;
unsigned x, y;
if (vivid_fb_green_bits(dev) == 6)
rgb = rgb565;
for (y = 0; y < dev->display_height; y++) {
u16 *d = p;
for (x = 0; x < dev->display_width; x++)
d[x] = rgb[(y / 16 + x / 16) % 16];
p += dev->display_byte_stride;
}
}
/* --------------------------------------------------------------------- */
static int vivid_fb_ioctl(struct fb_info *info, unsigned cmd, unsigned long arg)
{
struct vivid_dev *dev = (struct vivid_dev *)info->par;
switch (cmd) {
case FBIOGET_VBLANK: {
struct fb_vblank vblank;
memset(&vblank, 0, sizeof(vblank));
vblank.flags = FB_VBLANK_HAVE_COUNT | FB_VBLANK_HAVE_VCOUNT |
FB_VBLANK_HAVE_VSYNC;
vblank.count = 0;
vblank.vcount = 0;
vblank.hcount = 0;
if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank)))
return -EFAULT;
return 0;
}
default:
dprintk(dev, 1, "Unknown ioctl %08x\n", cmd);
return -EINVAL;
}
return 0;
}
/* Framebuffer device handling */
static int vivid_fb_set_var(struct vivid_dev *dev, struct fb_var_screeninfo *var)
{
dprintk(dev, 1, "vivid_fb_set_var\n");
if (var->bits_per_pixel != 16) {
dprintk(dev, 1, "vivid_fb_set_var - Invalid bpp\n");
return -EINVAL;
}
dev->display_byte_stride = var->xres * dev->bytes_per_pixel;
return 0;
}
static int vivid_fb_get_fix(struct vivid_dev *dev, struct fb_fix_screeninfo *fix)
{
dprintk(dev, 1, "vivid_fb_get_fix\n");
memset(fix, 0, sizeof(struct fb_fix_screeninfo));
strscpy(fix->id, "vioverlay fb", sizeof(fix->id));
fix->smem_start = dev->video_pbase;
fix->smem_len = dev->video_buffer_size;
fix->type = FB_TYPE_PACKED_PIXELS;
fix->visual = FB_VISUAL_TRUECOLOR;
fix->xpanstep = 1;
fix->ypanstep = 1;
fix->ywrapstep = 0;
fix->line_length = dev->display_byte_stride;
fix->accel = FB_ACCEL_NONE;
return 0;
}
/* Check the requested display mode, returning -EINVAL if we can't
handle it. */
static int _vivid_fb_check_var(struct fb_var_screeninfo *var, struct vivid_dev *dev)
{
dprintk(dev, 1, "vivid_fb_check_var\n");
var->bits_per_pixel = 16;
if (var->green.length == 5) {
var->red.offset = 10;
var->red.length = 5;
var->green.offset = 5;
var->green.length = 5;
var->blue.offset = 0;
var->blue.length = 5;
var->transp.offset = 15;
var->transp.length = 1;
} else {
var->red.offset = 11;
var->red.length = 5;
var->green.offset = 5;
var->green.length = 6;
var->blue.offset = 0;
var->blue.length = 5;
var->transp.offset = 0;
var->transp.length = 0;
}
var->xoffset = var->yoffset = 0;
var->left_margin = var->upper_margin = 0;
var->nonstd = 0;
var->vmode &= ~FB_VMODE_MASK;
var->vmode |= FB_VMODE_NONINTERLACED;
/* Dummy values */
var->hsync_len = 24;
var->vsync_len = 2;
var->pixclock = 84316;
var->right_margin = 776;
var->lower_margin = 591;
return 0;
}
static int vivid_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct vivid_dev *dev = (struct vivid_dev *) info->par;
dprintk(dev, 1, "vivid_fb_check_var\n");
return _vivid_fb_check_var(var, dev);
}
static int vivid_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
{
return 0;
}
static int vivid_fb_set_par(struct fb_info *info)
{
int rc = 0;
struct vivid_dev *dev = (struct vivid_dev *) info->par;
dprintk(dev, 1, "vivid_fb_set_par\n");
rc = vivid_fb_set_var(dev, &info->var);
vivid_fb_get_fix(dev, &info->fix);
return rc;
}
static int vivid_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info)
{
u32 color, *palette;
if (regno >= info->cmap.len)
return -EINVAL;
color = ((transp & 0xFF00) << 16) | ((red & 0xFF00) << 8) |
(green & 0xFF00) | ((blue & 0xFF00) >> 8);
if (regno >= 16)
return -EINVAL;
palette = info->pseudo_palette;
if (info->var.bits_per_pixel == 16) {
switch (info->var.green.length) {
case 6:
color = (red & 0xf800) |
((green & 0xfc00) >> 5) |
((blue & 0xf800) >> 11);
break;
case 5:
color = ((red & 0xf800) >> 1) |
((green & 0xf800) >> 6) |
((blue & 0xf800) >> 11) |
(transp ? 0x8000 : 0);
break;
}
}
palette[regno] = color;
return 0;
}
/* We don't really support blanking. All this does is enable or
disable the OSD. */
static int vivid_fb_blank(int blank_mode, struct fb_info *info)
{
struct vivid_dev *dev = (struct vivid_dev *)info->par;
dprintk(dev, 1, "Set blanking mode : %d\n", blank_mode);
switch (blank_mode) {
case FB_BLANK_UNBLANK:
break;
case FB_BLANK_NORMAL:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
break;
}
return 0;
}
static const struct fb_ops vivid_fb_ops = {
.owner = THIS_MODULE,
FB_DEFAULT_IOMEM_OPS,
.fb_check_var = vivid_fb_check_var,
.fb_set_par = vivid_fb_set_par,
.fb_setcolreg = vivid_fb_setcolreg,
.fb_cursor = NULL,
.fb_ioctl = vivid_fb_ioctl,
.fb_pan_display = vivid_fb_pan_display,
.fb_blank = vivid_fb_blank,
};
/* Initialization */
/* Setup our initial video mode */
static int vivid_fb_init_vidmode(struct vivid_dev *dev)
{
struct v4l2_rect start_window;
/* Color mode */
dev->bits_per_pixel = 16;
dev->bytes_per_pixel = dev->bits_per_pixel / 8;
start_window.width = MAX_OSD_WIDTH;
start_window.left = 0;
dev->display_byte_stride = start_window.width * dev->bytes_per_pixel;
/* Vertical size & position */
start_window.height = MAX_OSD_HEIGHT;
start_window.top = 0;
dev->display_width = start_window.width;
dev->display_height = start_window.height;
/* Generate a valid fb_var_screeninfo */
dev->fb_defined.xres = dev->display_width;
dev->fb_defined.yres = dev->display_height;
dev->fb_defined.xres_virtual = dev->display_width;
dev->fb_defined.yres_virtual = dev->display_height;
dev->fb_defined.bits_per_pixel = dev->bits_per_pixel;
dev->fb_defined.vmode = FB_VMODE_NONINTERLACED;
dev->fb_defined.left_margin = start_window.left + 1;
dev->fb_defined.upper_margin = start_window.top + 1;
dev->fb_defined.accel_flags = FB_ACCEL_NONE;
dev->fb_defined.nonstd = 0;
/* set default to 1:5:5:5 */
dev->fb_defined.green.length = 5;
/* We've filled in the most data, let the usual mode check
routine fill in the rest. */
_vivid_fb_check_var(&dev->fb_defined, dev);
/* Generate valid fb_fix_screeninfo */
vivid_fb_get_fix(dev, &dev->fb_fix);
/* Generate valid fb_info */
dev->fb_info.node = -1;
dev->fb_info.par = dev;
dev->fb_info.var = dev->fb_defined;
dev->fb_info.fix = dev->fb_fix;
dev->fb_info.screen_base = (u8 __iomem *)dev->video_vbase;
dev->fb_info.fbops = &vivid_fb_ops;
/* Supply some monitor specs. Bogus values will do for now */
dev->fb_info.monspecs.hfmin = 8000;
dev->fb_info.monspecs.hfmax = 70000;
dev->fb_info.monspecs.vfmin = 10;
dev->fb_info.monspecs.vfmax = 100;
/* Allocate color map */
if (fb_alloc_cmap(&dev->fb_info.cmap, 256, 1)) {
pr_err("abort, unable to alloc cmap\n");
return -ENOMEM;
}
/* Allocate the pseudo palette */
dev->fb_info.pseudo_palette = kmalloc_array(16, sizeof(u32), GFP_KERNEL);
return dev->fb_info.pseudo_palette ? 0 : -ENOMEM;
}
/* Release any memory we've grabbed */
static void vivid_fb_release_buffers(struct vivid_dev *dev)
{
if (dev->video_vbase == NULL)
return;
/* Release cmap */
if (dev->fb_info.cmap.len)
fb_dealloc_cmap(&dev->fb_info.cmap);
/* Release pseudo palette */
kfree(dev->fb_info.pseudo_palette);
kfree(dev->video_vbase);
}
/* Initialize the specified card */
int vivid_fb_init(struct vivid_dev *dev)
{
int ret;
dev->video_buffer_size = MAX_OSD_HEIGHT * MAX_OSD_WIDTH * 2;
dev->video_vbase = kzalloc(dev->video_buffer_size, GFP_KERNEL);
if (dev->video_vbase == NULL)
return -ENOMEM;
dev->video_pbase = virt_to_phys(dev->video_vbase);
pr_info("Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n",
dev->video_pbase, dev->video_vbase,
dev->video_buffer_size / 1024);
/* Set the startup video mode information */
ret = vivid_fb_init_vidmode(dev);
if (ret) {
vivid_fb_release_buffers(dev);
return ret;
}
vivid_fb_clear(dev);
/* Register the framebuffer */
if (register_framebuffer(&dev->fb_info) < 0) {
vivid_fb_release_buffers(dev);
return -EINVAL;
}
/* Set the card to the requested mode */
vivid_fb_set_par(&dev->fb_info);
v4l2_info(&dev->v4l2_dev, "Framebuffer device registered as fb%d\n",
dev->fb_info.node);
return 0;
}
void vivid_fb_deinit(struct vivid_dev *dev)
{
v4l2_info(&dev->v4l2_dev, "unregistering fb%d\n", dev->fb_info.node);
unregister_framebuffer(&dev->fb_info);
vivid_fb_release_buffers(dev);
}
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-osd.h - output overlay support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_OSD_H_
#define _VIVID_OSD_H_
#ifdef CONFIG_VIDEO_VIVID_OSD
int vivid_fb_init(struct vivid_dev *dev);
void vivid_fb_deinit(struct vivid_dev *dev);
void vivid_fb_clear(struct vivid_dev *dev);
unsigned int vivid_fb_green_bits(struct vivid_dev *dev);
#else
static inline int vivid_fb_init(struct vivid_dev *dev)
{
return -ENODEV;
}
static inline void vivid_fb_deinit(struct vivid_dev *dev) {}
static inline void vivid_fb_clear(struct vivid_dev *dev) {}
static inline unsigned int vivid_fb_green_bits(struct vivid_dev *dev)
{
return 5;
}
#endif
#endif
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-radio-common.c - common radio rx/tx support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-rds-gen.h"
/*
* These functions are shared between the vivid receiver and transmitter
* since both use the same frequency bands.
*/
const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = {
/* Band FM */
{
.type = V4L2_TUNER_RADIO,
.index = 0,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = FM_FREQ_RANGE_LOW,
.rangehigh = FM_FREQ_RANGE_HIGH,
.modulation = V4L2_BAND_MODULATION_FM,
},
/* Band AM */
{
.type = V4L2_TUNER_RADIO,
.index = 1,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = AM_FREQ_RANGE_LOW,
.rangehigh = AM_FREQ_RANGE_HIGH,
.modulation = V4L2_BAND_MODULATION_AM,
},
/* Band SW */
{
.type = V4L2_TUNER_RADIO,
.index = 2,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = SW_FREQ_RANGE_LOW,
.rangehigh = SW_FREQ_RANGE_HIGH,
.modulation = V4L2_BAND_MODULATION_AM,
},
};
/*
* Initialize the RDS generator. If we can loop, then the RDS generator
* is set up with the values from the RDS TX controls, otherwise it
* will fill in standard values using one of two alternates.
*/
void vivid_radio_rds_init(struct vivid_dev *dev)
{
struct vivid_rds_gen *rds = &dev->rds_gen;
bool alt = dev->radio_rx_rds_use_alternates;
/* Do nothing, blocks will be filled by the transmitter */
if (dev->radio_rds_loop && !dev->radio_tx_rds_controls)
return;
if (dev->radio_rds_loop) {
v4l2_ctrl_lock(dev->radio_tx_rds_pi);
rds->picode = dev->radio_tx_rds_pi->cur.val;
rds->pty = dev->radio_tx_rds_pty->cur.val;
rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val;
rds->art_head = dev->radio_tx_rds_art_head->cur.val;
rds->compressed = dev->radio_tx_rds_compressed->cur.val;
rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val;
rds->ta = dev->radio_tx_rds_ta->cur.val;
rds->tp = dev->radio_tx_rds_tp->cur.val;
rds->ms = dev->radio_tx_rds_ms->cur.val;
strscpy(rds->psname,
dev->radio_tx_rds_psname->p_cur.p_char,
sizeof(rds->psname));
strscpy(rds->radiotext,
dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64,
sizeof(rds->radiotext));
v4l2_ctrl_unlock(dev->radio_tx_rds_pi);
} else {
vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt);
}
if (dev->radio_rx_rds_controls) {
v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty);
v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta);
v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp);
v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms);
v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname);
v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext);
if (!dev->radio_rds_loop)
dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates;
}
vivid_rds_generate(rds);
}
/*
* Calculate the emulated signal quality taking into account the frequency
* the transmitter is using.
*/
static void vivid_radio_calc_sig_qual(struct vivid_dev *dev)
{
int mod = 16000;
int delta = 800;
int sig_qual, sig_qual_tx = mod;
/*
* For SW and FM there is a channel every 1000 kHz, for AM there is one
* every 100 kHz.
*/
if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) {
mod /= 10;
delta /= 10;
}
sig_qual = (dev->radio_rx_freq + delta) % mod - delta;
if (dev->has_radio_tx)
sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq;
if (abs(sig_qual_tx) <= abs(sig_qual)) {
sig_qual = sig_qual_tx;
/*
* Zero the internal rds buffer if we are going to loop
* rds blocks.
*/
if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls)
memset(dev->rds_gen.data, 0,
sizeof(dev->rds_gen.data));
dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW;
} else {
dev->radio_rds_loop = false;
}
if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH)
sig_qual *= 10;
dev->radio_rx_sig_qual = sig_qual;
}
int vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf)
{
if (vf->tuner != 0)
return -EINVAL;
vf->frequency = *pfreq;
return 0;
}
int vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf)
{
struct vivid_dev *dev = video_drvdata(file);
unsigned freq;
unsigned band;
if (vf->tuner != 0)
return -EINVAL;
if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2)
band = BAND_FM;
else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2)
band = BAND_AM;
else
band = BAND_SW;
freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow,
vivid_radio_bands[band].rangehigh);
*pfreq = freq;
/*
* For both receiver and transmitter recalculate the signal quality
* (since that depends on both frequencies) and re-init the rds
* generator.
*/
vivid_radio_calc_sig_qual(dev);
vivid_radio_rds_init(dev);
return 0;
}
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-radio-common.h - common radio rx/tx support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_RADIO_COMMON_H_
#define _VIVID_RADIO_COMMON_H_
/* The supported radio frequency ranges in kHz */
#define FM_FREQ_RANGE_LOW (64000U * 16U)
#define FM_FREQ_RANGE_HIGH (108000U * 16U)
#define AM_FREQ_RANGE_LOW (520U * 16U)
#define AM_FREQ_RANGE_HIGH (1710U * 16U)
#define SW_FREQ_RANGE_LOW (2300U * 16U)
#define SW_FREQ_RANGE_HIGH (26100U * 16U)
enum { BAND_FM, BAND_AM, BAND_SW, TOT_BANDS };
extern const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS];
int vivid_radio_g_frequency(struct file *file, const unsigned *freq, struct v4l2_frequency *vf);
int vivid_radio_s_frequency(struct file *file, unsigned *freq, const struct v4l2_frequency *vf);
void vivid_radio_rds_init(struct vivid_dev *dev);
#endif
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-radio-rx.c - radio receiver support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <linux/sched/signal.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-dv-timings.h>
#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-rds-gen.h"
#include "vivid-radio-rx.h"
ssize_t vivid_radio_rx_read(struct file *file, char __user *buf,
size_t size, loff_t *offset)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_rds_data *data = dev->rds_gen.data;
bool use_alternates;
ktime_t timestamp;
unsigned blk;
int perc;
int i;
if (dev->radio_rx_rds_controls)
return -EINVAL;
if (size < sizeof(*data))
return 0;
size = sizeof(*data) * (size / sizeof(*data));
if (mutex_lock_interruptible(&dev->mutex))
return -ERESTARTSYS;
if (dev->radio_rx_rds_owner &&
file_to_v4l2_fh(file) != dev->radio_rx_rds_owner) {
mutex_unlock(&dev->mutex);
return -EBUSY;
}
if (dev->radio_rx_rds_owner == NULL) {
vivid_radio_rds_init(dev);
dev->radio_rx_rds_owner = file_to_v4l2_fh(file);
}
retry:
timestamp = ktime_sub(ktime_get(), dev->radio_rds_init_time);
blk = ktime_divns(timestamp, VIVID_RDS_NSEC_PER_BLK);
use_alternates = (blk % VIVID_RDS_GEN_BLOCKS) & 1;
if (dev->radio_rx_rds_last_block == 0 ||
dev->radio_rx_rds_use_alternates != use_alternates) {
dev->radio_rx_rds_use_alternates = use_alternates;
/* Re-init the RDS generator */
vivid_radio_rds_init(dev);
}
if (blk >= dev->radio_rx_rds_last_block + VIVID_RDS_GEN_BLOCKS)
dev->radio_rx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;
/*
* No data is available if there hasn't been time to get new data,
* or if the RDS receiver has been disabled, or if we use the data
* from the RDS transmitter and that RDS transmitter has been disabled,
* or if the signal quality is too weak.
*/
if (blk == dev->radio_rx_rds_last_block || !dev->radio_rx_rds_enabled ||
(dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) ||
abs(dev->radio_rx_sig_qual) > 200) {
mutex_unlock(&dev->mutex);
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
if (msleep_interruptible(20) && signal_pending(current))
return -EINTR;
if (mutex_lock_interruptible(&dev->mutex))
return -ERESTARTSYS;
goto retry;
}
/* abs(dev->radio_rx_sig_qual) <= 200, map that to a 0-50% range */
perc = abs(dev->radio_rx_sig_qual) / 4;
for (i = 0; i < size && blk > dev->radio_rx_rds_last_block;
dev->radio_rx_rds_last_block++) {
unsigned data_blk = dev->radio_rx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
struct v4l2_rds_data rds = data[data_blk];
if (data_blk == 0 && dev->radio_rds_loop)
vivid_radio_rds_init(dev);
if (perc && get_random_u32_below(100) < perc) {
switch (get_random_u32_below(4)) {
case 0:
rds.block |= V4L2_RDS_BLOCK_CORRECTED;
break;
case 1:
rds.block |= V4L2_RDS_BLOCK_INVALID;
break;
case 2:
rds.block |= V4L2_RDS_BLOCK_ERROR;
rds.lsb = get_random_u8();
rds.msb = get_random_u8();
break;
case 3: /* Skip block altogether */
if (i)
continue;
/*
* Must make sure at least one block is
* returned, otherwise the application
* might think that end-of-file occurred.
*/
break;
}
}
if (copy_to_user(buf + i, &rds, sizeof(rds))) {
i = -EFAULT;
break;
}
i += sizeof(rds);
}
mutex_unlock(&dev->mutex);
return i;
}
__poll_t vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait)
{
return EPOLLIN | EPOLLRDNORM | v4l2_ctrl_poll(file, wait);
}
int vivid_radio_rx_enum_freq_bands(struct file *file, void *priv, struct v4l2_frequency_band *band)
{
if (band->tuner != 0)
return -EINVAL;
if (band->index >= TOT_BANDS)
return -EINVAL;
*band = vivid_radio_bands[band->index];
return 0;
}
int vivid_radio_rx_s_hw_freq_seek(struct file *file, void *priv, const struct v4l2_hw_freq_seek *a)
{
struct vivid_dev *dev = video_drvdata(file);
unsigned low, high;
unsigned freq;
unsigned spacing;
unsigned band;
if (a->tuner)
return -EINVAL;
if (a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_BOUNDED)
return -EINVAL;
if (!a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_WRAP)
return -EINVAL;
if (!a->rangelow ^ !a->rangehigh)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
if (a->rangelow) {
for (band = 0; band < TOT_BANDS; band++)
if (a->rangelow >= vivid_radio_bands[band].rangelow &&
a->rangehigh <= vivid_radio_bands[band].rangehigh)
break;
if (band == TOT_BANDS)
return -EINVAL;
if (!dev->radio_rx_hw_seek_prog_lim &&
(a->rangelow != vivid_radio_bands[band].rangelow ||
a->rangehigh != vivid_radio_bands[band].rangehigh))
return -EINVAL;
low = a->rangelow;
high = a->rangehigh;
} else {
for (band = 0; band < TOT_BANDS; band++)
if (dev->radio_rx_freq >= vivid_radio_bands[band].rangelow &&
dev->radio_rx_freq <= vivid_radio_bands[band].rangehigh)
break;
if (band == TOT_BANDS)
return -EINVAL;
low = vivid_radio_bands[band].rangelow;
high = vivid_radio_bands[band].rangehigh;
}
spacing = band == BAND_AM ? 1600 : 16000;
freq = clamp(dev->radio_rx_freq, low, high);
if (a->seek_upward) {
freq = spacing * (freq / spacing) + spacing;
if (freq > high) {
if (!a->wrap_around)
return -ENODATA;
freq = spacing * (low / spacing) + spacing;
if (freq >= dev->radio_rx_freq)
return -ENODATA;
}
} else {
freq = spacing * ((freq + spacing - 1) / spacing) - spacing;
if (freq < low) {
if (!a->wrap_around)
return -ENODATA;
freq = spacing * ((high + spacing - 1) / spacing) - spacing;
if (freq <= dev->radio_rx_freq)
return -ENODATA;
}
}
return 0;
}
int vivid_radio_rx_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
{
struct vivid_dev *dev = video_drvdata(file);
int delta = 800;
int sig_qual;
if (vt->index > 0)
return -EINVAL;
strscpy(vt->name, "AM/FM/SW Receiver", sizeof(vt->name));
vt->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
(dev->radio_rx_rds_controls ?
V4L2_TUNER_CAP_RDS_CONTROLS :
V4L2_TUNER_CAP_RDS_BLOCK_IO) |
(dev->radio_rx_hw_seek_prog_lim ?
V4L2_TUNER_CAP_HWSEEK_PROG_LIM : 0);
switch (dev->radio_rx_hw_seek_mode) {
case VIVID_HW_SEEK_BOUNDED:
vt->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
break;
case VIVID_HW_SEEK_WRAP:
vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP;
break;
case VIVID_HW_SEEK_BOTH:
vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP |
V4L2_TUNER_CAP_HWSEEK_BOUNDED;
break;
}
vt->rangelow = AM_FREQ_RANGE_LOW;
vt->rangehigh = FM_FREQ_RANGE_HIGH;
sig_qual = dev->radio_rx_sig_qual;
vt->signal = abs(sig_qual) > delta ? 0 :
0xffff - ((unsigned)abs(sig_qual) * 0xffff) / delta;
vt->afc = sig_qual > delta ? 0 : sig_qual;
if (abs(sig_qual) > delta)
vt->rxsubchans = 0;
else if (dev->radio_rx_freq < FM_FREQ_RANGE_LOW || vt->signal < 0x8000)
vt->rxsubchans = V4L2_TUNER_SUB_MONO;
else if (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_STEREO))
vt->rxsubchans = V4L2_TUNER_SUB_MONO;
else
vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
if (dev->radio_rx_rds_enabled &&
(!dev->radio_rds_loop || (dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) &&
dev->radio_rx_freq >= FM_FREQ_RANGE_LOW && vt->signal >= 0xc000)
vt->rxsubchans |= V4L2_TUNER_SUB_RDS;
if (dev->radio_rx_rds_controls)
vivid_radio_rds_init(dev);
vt->audmode = dev->radio_rx_audmode;
return 0;
}
int vivid_radio_rx_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt)
{
struct vivid_dev *dev = video_drvdata(file);
if (vt->index)
return -EINVAL;
dev->radio_rx_audmode = vt->audmode >= V4L2_TUNER_MODE_STEREO;
return 0;
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-radio-rx.h - radio receiver support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_RADIO_RX_H_
#define _VIVID_RADIO_RX_H_
ssize_t vivid_radio_rx_read(struct file *, char __user *, size_t, loff_t *);
__poll_t vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait);
int vivid_radio_rx_enum_freq_bands(struct file *file, void *priv, struct v4l2_frequency_band *band);
int vivid_radio_rx_s_hw_freq_seek(struct file *file, void *priv, const struct v4l2_hw_freq_seek *a);
int vivid_radio_rx_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt);
int vivid_radio_rx_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt);
#endif
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-radio-tx.c - radio transmitter support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/sched/signal.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-dv-timings.h>
#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-radio-tx.h"
ssize_t vivid_radio_tx_write(struct file *file, const char __user *buf,
size_t size, loff_t *offset)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_rds_data *data = dev->rds_gen.data;
ktime_t timestamp;
unsigned blk;
int i;
if (dev->radio_tx_rds_controls)
return -EINVAL;
if (size < sizeof(*data))
return -EINVAL;
size = sizeof(*data) * (size / sizeof(*data));
if (mutex_lock_interruptible(&dev->mutex))
return -ERESTARTSYS;
if (dev->radio_tx_rds_owner &&
file_to_v4l2_fh(file) != dev->radio_tx_rds_owner) {
mutex_unlock(&dev->mutex);
return -EBUSY;
}
dev->radio_tx_rds_owner = file_to_v4l2_fh(file);
retry:
timestamp = ktime_sub(ktime_get(), dev->radio_rds_init_time);
blk = ktime_divns(timestamp, VIVID_RDS_NSEC_PER_BLK);
if (blk - VIVID_RDS_GEN_BLOCKS >= dev->radio_tx_rds_last_block)
dev->radio_tx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;
/*
* No data is available if there hasn't been time to get new data,
* or if the RDS receiver has been disabled, or if we use the data
* from the RDS transmitter and that RDS transmitter has been disabled,
* or if the signal quality is too weak.
*/
if (blk == dev->radio_tx_rds_last_block ||
!(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) {
mutex_unlock(&dev->mutex);
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
if (msleep_interruptible(20) && signal_pending(current))
return -EINTR;
if (mutex_lock_interruptible(&dev->mutex))
return -ERESTARTSYS;
goto retry;
}
for (i = 0; i < size && blk > dev->radio_tx_rds_last_block;
dev->radio_tx_rds_last_block++) {
unsigned data_blk = dev->radio_tx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
struct v4l2_rds_data rds;
if (copy_from_user(&rds, buf + i, sizeof(rds))) {
i = -EFAULT;
break;
}
i += sizeof(rds);
if (!dev->radio_rds_loop)
continue;
if ((rds.block & V4L2_RDS_BLOCK_MSK) == V4L2_RDS_BLOCK_INVALID ||
(rds.block & V4L2_RDS_BLOCK_ERROR))
continue;
rds.block &= V4L2_RDS_BLOCK_MSK;
data[data_blk] = rds;
}
mutex_unlock(&dev->mutex);
return i;
}
__poll_t vivid_radio_tx_poll(struct file *file, struct poll_table_struct *wait)
{
return EPOLLOUT | EPOLLWRNORM | v4l2_ctrl_poll(file, wait);
}
int vidioc_g_modulator(struct file *file, void *priv, struct v4l2_modulator *a)
{
struct vivid_dev *dev = video_drvdata(file);
if (a->index > 0)
return -EINVAL;
strscpy(a->name, "AM/FM/SW Transmitter", sizeof(a->name));
a->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
(dev->radio_tx_rds_controls ?
V4L2_TUNER_CAP_RDS_CONTROLS :
V4L2_TUNER_CAP_RDS_BLOCK_IO);
a->rangelow = AM_FREQ_RANGE_LOW;
a->rangehigh = FM_FREQ_RANGE_HIGH;
a->txsubchans = dev->radio_tx_subchans;
return 0;
}
int vidioc_s_modulator(struct file *file, void *priv, const struct v4l2_modulator *a)
{
struct vivid_dev *dev = video_drvdata(file);
if (a->index)
return -EINVAL;
if (a->txsubchans & ~0x13)
return -EINVAL;
dev->radio_tx_subchans = a->txsubchans;
return 0;
}
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-radio-tx.h - radio transmitter support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_RADIO_TX_H_
#define _VIVID_RADIO_TX_H_
ssize_t vivid_radio_tx_write(struct file *, const char __user *, size_t, loff_t *);
__poll_t vivid_radio_tx_poll(struct file *file, struct poll_table_struct *wait);
int vidioc_g_modulator(struct file *file, void *priv, struct v4l2_modulator *a);
int vidioc_s_modulator(struct file *file, void *priv, const struct v4l2_modulator *a);
#endif
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-rds-gen.c - rds (radio data system) generator support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/string.h>
#include <linux/videodev2.h>
#include "vivid-rds-gen.h"
static u8 vivid_get_di(const struct vivid_rds_gen *rds, unsigned grp)
{
switch (grp) {
case 0:
return (rds->dyn_pty << 2) | (grp & 3);
case 1:
return (rds->compressed << 2) | (grp & 3);
case 2:
return (rds->art_head << 2) | (grp & 3);
case 3:
return (rds->mono_stereo << 2) | (grp & 3);
}
return 0;
}
/*
* This RDS generator creates 57 RDS groups (one group == four RDS blocks).
* Groups 0-3, 22-25 and 44-47 (spaced 22 groups apart) are filled with a
* standard 0B group containing the PI code and PS name.
*
* Groups 4-19 and 26-41 use group 2A for the radio text.
*
* Group 56 contains the time (group 4A).
*
* All remaining groups use a filler group 15B block that just repeats
* the PI and PTY codes.
*/
void vivid_rds_generate(struct vivid_rds_gen *rds)
{
struct v4l2_rds_data *data = rds->data;
unsigned grp;
unsigned idx;
struct tm tm;
unsigned date;
unsigned time;
int l;
for (grp = 0; grp < VIVID_RDS_GEN_GROUPS; grp++, data += VIVID_RDS_GEN_BLKS_PER_GRP) {
data[0].lsb = rds->picode & 0xff;
data[0].msb = rds->picode >> 8;
data[0].block = V4L2_RDS_BLOCK_A | (V4L2_RDS_BLOCK_A << 3);
data[1].lsb = rds->pty << 5;
data[1].msb = (rds->pty >> 3) | (rds->tp << 2);
data[1].block = V4L2_RDS_BLOCK_B | (V4L2_RDS_BLOCK_B << 3);
data[3].block = V4L2_RDS_BLOCK_D | (V4L2_RDS_BLOCK_D << 3);
switch (grp) {
case 0 ... 3:
case 22 ... 25:
case 44 ... 47: /* Group 0B */
idx = (grp % 22) % 4;
data[1].lsb |= (rds->ta << 4) | (rds->ms << 3);
data[1].lsb |= vivid_get_di(rds, idx);
data[1].msb |= 1 << 3;
data[2].lsb = rds->picode & 0xff;
data[2].msb = rds->picode >> 8;
data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3);
data[3].lsb = rds->psname[2 * idx + 1];
data[3].msb = rds->psname[2 * idx];
break;
case 4 ... 19:
case 26 ... 41: /* Group 2A */
idx = ((grp - 4) % 22) % 16;
data[1].lsb |= idx;
data[1].msb |= 4 << 3;
data[2].msb = rds->radiotext[4 * idx];
data[2].lsb = rds->radiotext[4 * idx + 1];
data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3);
data[3].msb = rds->radiotext[4 * idx + 2];
data[3].lsb = rds->radiotext[4 * idx + 3];
break;
case 56:
/*
* Group 4A
*
* Uses the algorithm from Annex G of the RDS standard
* EN 50067:1998 to convert a UTC date to an RDS Modified
* Julian Day.
*/
time64_to_tm(ktime_get_real_seconds(), 0, &tm);
l = tm.tm_mon <= 1;
date = 14956 + tm.tm_mday + ((tm.tm_year - l) * 1461) / 4 +
((tm.tm_mon + 2 + l * 12) * 306001) / 10000;
time = (tm.tm_hour << 12) |
(tm.tm_min << 6) |
(sys_tz.tz_minuteswest >= 0 ? 0x20 : 0) |
(abs(sys_tz.tz_minuteswest) / 30);
data[1].lsb &= ~3;
data[1].lsb |= date >> 15;
data[1].msb |= 8 << 3;
data[2].lsb = (date << 1) & 0xfe;
data[2].lsb |= (time >> 16) & 1;
data[2].msb = (date >> 7) & 0xff;
data[2].block = V4L2_RDS_BLOCK_C | (V4L2_RDS_BLOCK_C << 3);
data[3].lsb = time & 0xff;
data[3].msb = (time >> 8) & 0xff;
break;
default: /* Group 15B */
data[1].lsb |= (rds->ta << 4) | (rds->ms << 3);
data[1].lsb |= vivid_get_di(rds, grp % 22);
data[1].msb |= 0x1f << 3;
data[2].lsb = rds->picode & 0xff;
data[2].msb = rds->picode >> 8;
data[2].block = V4L2_RDS_BLOCK_C_ALT | (V4L2_RDS_BLOCK_C_ALT << 3);
data[3].lsb = rds->pty << 5;
data[3].lsb |= (rds->ta << 4) | (rds->ms << 3);
data[3].lsb |= vivid_get_di(rds, grp % 22);
data[3].msb |= rds->pty >> 3;
data[3].msb |= 0x1f << 3;
break;
}
}
}
void vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq,
bool alt)
{
/* Alternate PTY between Info and Weather */
if (rds->use_rbds) {
rds->picode = 0x2e75; /* 'KLNX' call sign */
rds->pty = alt ? 29 : 2;
} else {
rds->picode = 0x8088;
rds->pty = alt ? 16 : 3;
}
rds->mono_stereo = true;
rds->art_head = false;
rds->compressed = false;
rds->dyn_pty = false;
rds->tp = true;
rds->ta = alt;
rds->ms = true;
snprintf(rds->psname, sizeof(rds->psname), "%6d.%1d",
(freq / 16) % 1000000, (((freq & 0xf) * 10) / 16) % 10);
if (alt)
strscpy(rds->radiotext,
" The Radio Data System can switch between different Radio Texts ",
sizeof(rds->radiotext));
else
strscpy(rds->radiotext,
"An example of Radio Text as transmitted by the Radio Data System",
sizeof(rds->radiotext));
}
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-rds-gen.h - rds (radio data system) generator support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_RDS_GEN_H_
#define _VIVID_RDS_GEN_H_
/*
* It takes almost exactly 5 seconds to transmit 57 RDS groups.
* Each group has 4 blocks and each block has a payload of 16 bits + a
* block identification. The driver will generate the contents of these
* 57 groups only when necessary and it will just be played continuously.
*/
#define VIVID_RDS_GEN_GROUPS 57
#define VIVID_RDS_GEN_BLKS_PER_GRP 4
#define VIVID_RDS_GEN_BLOCKS (VIVID_RDS_GEN_BLKS_PER_GRP * VIVID_RDS_GEN_GROUPS)
#define VIVID_RDS_NSEC_PER_BLK (u32)(5ull * NSEC_PER_SEC / VIVID_RDS_GEN_BLOCKS)
struct vivid_rds_gen {
struct v4l2_rds_data data[VIVID_RDS_GEN_BLOCKS];
bool use_rbds;
u16 picode;
u8 pty;
bool mono_stereo;
bool art_head;
bool compressed;
bool dyn_pty;
bool ta;
bool tp;
bool ms;
char psname[8 + 1];
char radiotext[64 + 1];
};
void vivid_rds_gen_fill(struct vivid_rds_gen *rds, unsigned freq,
bool use_alternate);
void vivid_rds_generate(struct vivid_rds_gen *rds);
#endif
@@ -0,0 +1,582 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-sdr-cap.c - software defined radio support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <linux/math64.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-dv-timings.h>
#include <linux/fixp-arith.h>
#include <linux/jiffies.h>
#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-sdr-cap.h"
/* stream formats */
struct vivid_format {
u32 pixelformat;
u32 buffersize;
};
/* format descriptions for capture and preview */
static const struct vivid_format formats[] = {
{
.pixelformat = V4L2_SDR_FMT_CU8,
.buffersize = SDR_CAP_SAMPLES_PER_BUF * 2,
}, {
.pixelformat = V4L2_SDR_FMT_CS8,
.buffersize = SDR_CAP_SAMPLES_PER_BUF * 2,
},
};
static const struct v4l2_frequency_band bands_adc[] = {
{
.tuner = 0,
.type = V4L2_TUNER_ADC,
.index = 0,
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 300000,
.rangehigh = 300000,
},
{
.tuner = 0,
.type = V4L2_TUNER_ADC,
.index = 1,
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 900001,
.rangehigh = 2800000,
},
{
.tuner = 0,
.type = V4L2_TUNER_ADC,
.index = 2,
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 3200000,
.rangehigh = 3200000,
},
};
/* ADC band midpoints */
#define BAND_ADC_0 ((bands_adc[0].rangehigh + bands_adc[1].rangelow) / 2)
#define BAND_ADC_1 ((bands_adc[1].rangehigh + bands_adc[2].rangelow) / 2)
static const struct v4l2_frequency_band bands_fm[] = {
{
.tuner = 1,
.type = V4L2_TUNER_RF,
.index = 0,
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 50000000,
.rangehigh = 2000000000,
},
};
static void vivid_thread_sdr_cap_tick(struct vivid_dev *dev)
{
struct vivid_buffer *sdr_cap_buf = NULL;
dprintk(dev, 1, "SDR Capture Thread Tick\n");
/* Drop a certain percentage of buffers. */
if (dev->perc_dropped_buffers &&
get_random_u32_below(100) < dev->perc_dropped_buffers)
return;
spin_lock(&dev->slock);
if (!list_empty(&dev->sdr_cap_active)) {
sdr_cap_buf = list_entry(dev->sdr_cap_active.next,
struct vivid_buffer, list);
list_del(&sdr_cap_buf->list);
}
spin_unlock(&dev->slock);
if (sdr_cap_buf) {
sdr_cap_buf->vb.sequence = dev->sdr_cap_with_seq_wrap_count;
v4l2_ctrl_request_setup(sdr_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_sdr_cap);
v4l2_ctrl_request_complete(sdr_cap_buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_sdr_cap);
vivid_sdr_cap_process(dev, sdr_cap_buf);
sdr_cap_buf->vb.vb2_buf.timestamp =
ktime_get_ns() + dev->time_wrap_offset;
vb2_buffer_done(&sdr_cap_buf->vb.vb2_buf, dev->dqbuf_error ?
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
dev->dqbuf_error = false;
}
}
static int vivid_thread_sdr_cap(void *data)
{
struct vivid_dev *dev = data;
u64 samples_since_start;
u64 buffers_since_start;
u64 next_jiffies_since_start;
unsigned long jiffies_since_start;
unsigned long cur_jiffies;
unsigned wait_jiffies;
dprintk(dev, 1, "SDR Capture Thread Start\n");
set_freezable();
/* Resets frame counters */
dev->sdr_cap_seq_offset = 0;
dev->sdr_cap_seq_count = 0;
dev->jiffies_sdr_cap = jiffies;
dev->sdr_cap_seq_resync = false;
if (dev->time_wrap)
dev->time_wrap_offset = dev->time_wrap - ktime_get_ns();
else
dev->time_wrap_offset = 0;
for (;;) {
try_to_freeze();
if (kthread_should_stop())
break;
if (!mutex_trylock(&dev->mutex)) {
schedule();
continue;
}
cur_jiffies = jiffies;
if (dev->sdr_cap_seq_resync) {
dev->jiffies_sdr_cap = cur_jiffies;
dev->sdr_cap_seq_offset = dev->sdr_cap_seq_count + 1;
dev->sdr_cap_seq_count = 0;
dev->sdr_cap_seq_resync = false;
}
/* Calculate the number of jiffies since we started streaming */
jiffies_since_start = cur_jiffies - dev->jiffies_sdr_cap;
/* Get the number of buffers streamed since the start */
buffers_since_start =
(u64)jiffies_since_start * dev->sdr_adc_freq +
(HZ * SDR_CAP_SAMPLES_PER_BUF) / 2;
do_div(buffers_since_start, HZ * SDR_CAP_SAMPLES_PER_BUF);
/*
* 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_sdr_cap = cur_jiffies;
dev->sdr_cap_seq_offset = buffers_since_start;
buffers_since_start = 0;
}
dev->sdr_cap_seq_count =
buffers_since_start + dev->sdr_cap_seq_offset;
dev->sdr_cap_with_seq_wrap_count = dev->sdr_cap_seq_count - dev->sdr_cap_seq_start;
vivid_thread_sdr_cap_tick(dev);
mutex_unlock(&dev->mutex);
/*
* Calculate the number of samples streamed since we started,
* not including the current buffer.
*/
samples_since_start = buffers_since_start * SDR_CAP_SAMPLES_PER_BUF;
/* And the number of jiffies since we started */
jiffies_since_start = jiffies - dev->jiffies_sdr_cap;
/* Increase by the number of samples in one buffer */
samples_since_start += SDR_CAP_SAMPLES_PER_BUF;
/*
* Calculate when that next buffer is supposed to start
* in jiffies since we started streaming.
*/
next_jiffies_since_start = samples_since_start * HZ +
dev->sdr_adc_freq / 2;
do_div(next_jiffies_since_start, dev->sdr_adc_freq);
/* 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;
if (!time_is_after_jiffies(cur_jiffies + wait_jiffies))
continue;
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait, kthread_should_stop(),
cur_jiffies + wait_jiffies - jiffies);
}
dprintk(dev, 1, "SDR Capture Thread End\n");
return 0;
}
static int sdr_cap_queue_setup(struct vb2_queue *vq,
unsigned *nbuffers, unsigned *nplanes,
unsigned sizes[], struct device *alloc_devs[])
{
/* 2 = max 16-bit sample returned */
u32 size = SDR_CAP_SAMPLES_PER_BUF * 2;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
*nplanes = 1;
sizes[0] = size;
return 0;
}
static int sdr_cap_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
unsigned size = SDR_CAP_SAMPLES_PER_BUF * 2;
dprintk(dev, 1, "%s\n", __func__);
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void sdr_cap_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
dprintk(dev, 1, "%s\n", __func__);
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->sdr_cap_active);
spin_unlock(&dev->slock);
}
static int sdr_cap_start_streaming(struct vb2_queue *vq, unsigned count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err = 0;
dprintk(dev, 1, "%s\n", __func__);
dev->sdr_cap_seq_start = dev->seq_wrap * 128;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else if (dev->kthread_sdr_cap == NULL) {
dev->kthread_sdr_cap = kthread_run(vivid_thread_sdr_cap, dev,
"%s-sdr-cap", dev->v4l2_dev.name);
if (IS_ERR(dev->kthread_sdr_cap)) {
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
err = PTR_ERR(dev->kthread_sdr_cap);
dev->kthread_sdr_cap = NULL;
}
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp, &dev->sdr_cap_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void sdr_cap_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
if (dev->kthread_sdr_cap == NULL)
return;
while (!list_empty(&dev->sdr_cap_active)) {
struct vivid_buffer *buf;
buf = list_entry(dev->sdr_cap_active.next,
struct vivid_buffer, list);
list_del(&buf->list);
v4l2_ctrl_request_complete(buf->vb.vb2_buf.req_obj.req,
&dev->ctrl_hdl_sdr_cap);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
/* shutdown control thread */
kthread_stop(dev->kthread_sdr_cap);
dev->kthread_sdr_cap = NULL;
}
static void sdr_cap_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_sdr_cap);
}
const struct vb2_ops vivid_sdr_cap_qops = {
.queue_setup = sdr_cap_queue_setup,
.buf_prepare = sdr_cap_buf_prepare,
.buf_queue = sdr_cap_buf_queue,
.start_streaming = sdr_cap_start_streaming,
.stop_streaming = sdr_cap_stop_streaming,
.buf_request_complete = sdr_cap_buf_request_complete,
};
int vivid_sdr_enum_freq_bands(struct file *file, void *priv,
struct v4l2_frequency_band *band)
{
switch (band->tuner) {
case 0:
if (band->index >= ARRAY_SIZE(bands_adc))
return -EINVAL;
*band = bands_adc[band->index];
return 0;
case 1:
if (band->index >= ARRAY_SIZE(bands_fm))
return -EINVAL;
*band = bands_fm[band->index];
return 0;
default:
return -EINVAL;
}
}
int vivid_sdr_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *vf)
{
struct vivid_dev *dev = video_drvdata(file);
switch (vf->tuner) {
case 0:
vf->frequency = dev->sdr_adc_freq;
vf->type = V4L2_TUNER_ADC;
return 0;
case 1:
vf->frequency = dev->sdr_fm_freq;
vf->type = V4L2_TUNER_RF;
return 0;
default:
return -EINVAL;
}
}
int vivid_sdr_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *vf)
{
struct vivid_dev *dev = video_drvdata(file);
unsigned freq = vf->frequency;
unsigned band;
switch (vf->tuner) {
case 0:
if (vf->type != V4L2_TUNER_ADC)
return -EINVAL;
if (freq < BAND_ADC_0)
band = 0;
else if (freq < BAND_ADC_1)
band = 1;
else
band = 2;
freq = clamp_t(unsigned, freq,
bands_adc[band].rangelow,
bands_adc[band].rangehigh);
if (vb2_is_streaming(&dev->vb_sdr_cap_q) &&
freq != dev->sdr_adc_freq) {
/* resync the thread's timings */
dev->sdr_cap_seq_resync = true;
}
dev->sdr_adc_freq = freq;
return 0;
case 1:
if (vf->type != V4L2_TUNER_RF)
return -EINVAL;
dev->sdr_fm_freq = clamp_t(unsigned, freq,
bands_fm[0].rangelow,
bands_fm[0].rangehigh);
return 0;
default:
return -EINVAL;
}
}
int vivid_sdr_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
{
switch (vt->index) {
case 0:
strscpy(vt->name, "ADC", sizeof(vt->name));
vt->type = V4L2_TUNER_ADC;
vt->capability =
V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
vt->rangelow = bands_adc[0].rangelow;
vt->rangehigh = bands_adc[2].rangehigh;
return 0;
case 1:
strscpy(vt->name, "RF", sizeof(vt->name));
vt->type = V4L2_TUNER_RF;
vt->capability =
V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
vt->rangelow = bands_fm[0].rangelow;
vt->rangehigh = bands_fm[0].rangehigh;
return 0;
default:
return -EINVAL;
}
}
int vivid_sdr_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt)
{
if (vt->index > 1)
return -EINVAL;
return 0;
}
int vidioc_enum_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(formats))
return -EINVAL;
f->pixelformat = formats[f->index].pixelformat;
return 0;
}
int vidioc_g_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
f->fmt.sdr.pixelformat = dev->sdr_pixelformat;
f->fmt.sdr.buffersize = dev->sdr_buffersize;
return 0;
}
int vidioc_s_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct vb2_queue *q = &dev->vb_sdr_cap_q;
int i;
if (vb2_is_busy(q))
return -EBUSY;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
dev->sdr_pixelformat = formats[i].pixelformat;
dev->sdr_buffersize = formats[i].buffersize;
f->fmt.sdr.buffersize = formats[i].buffersize;
return 0;
}
}
dev->sdr_pixelformat = formats[0].pixelformat;
dev->sdr_buffersize = formats[0].buffersize;
f->fmt.sdr.pixelformat = formats[0].pixelformat;
f->fmt.sdr.buffersize = formats[0].buffersize;
return 0;
}
int vidioc_try_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f)
{
int i;
for (i = 0; i < ARRAY_SIZE(formats); i++) {
if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
f->fmt.sdr.buffersize = formats[i].buffersize;
return 0;
}
}
f->fmt.sdr.pixelformat = formats[0].pixelformat;
f->fmt.sdr.buffersize = formats[0].buffersize;
return 0;
}
#define FIXP_N (15)
#define FIXP_FRAC (1 << FIXP_N)
#define FIXP_2PI ((int)(2 * 3.141592653589 * FIXP_FRAC))
#define M_100000PI (3.14159 * 100000)
void vivid_sdr_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf)
{
u8 *vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
unsigned long i;
unsigned long plane_size = vb2_plane_size(&buf->vb.vb2_buf, 0);
s64 s64tmp;
s32 src_phase_step;
s32 mod_phase_step;
s32 fixp_i;
s32 fixp_q;
/* calculate phase step */
#define BEEP_FREQ 1000 /* 1kHz beep */
src_phase_step = DIV_ROUND_CLOSEST(FIXP_2PI * BEEP_FREQ,
dev->sdr_adc_freq);
for (i = 0; i < plane_size; i += 2) {
mod_phase_step = fixp_cos32_rad(dev->sdr_fixp_src_phase,
FIXP_2PI) >> (31 - FIXP_N);
dev->sdr_fixp_src_phase += src_phase_step;
s64tmp = (s64) mod_phase_step * dev->sdr_fm_deviation;
dev->sdr_fixp_mod_phase += div_s64(s64tmp, M_100000PI);
/*
* Transfer phase angle to [0, 2xPI] in order to avoid variable
* overflow and make it suitable for cosine implementation
* used, which does not support negative angles.
*/
dev->sdr_fixp_src_phase %= FIXP_2PI;
dev->sdr_fixp_mod_phase %= FIXP_2PI;
if (dev->sdr_fixp_mod_phase < 0)
dev->sdr_fixp_mod_phase += FIXP_2PI;
fixp_i = fixp_cos32_rad(dev->sdr_fixp_mod_phase, FIXP_2PI);
fixp_q = fixp_sin32_rad(dev->sdr_fixp_mod_phase, FIXP_2PI);
/* Normalize fraction values represented with 32 bit precision
* to fixed point representation with FIXP_N bits */
fixp_i >>= (31 - FIXP_N);
fixp_q >>= (31 - FIXP_N);
switch (dev->sdr_pixelformat) {
case V4L2_SDR_FMT_CU8:
/* convert 'fixp float' to u8 [0, +255] */
/* u8 = X * 127.5 + 127.5; X is float [-1.0, +1.0] */
fixp_i = fixp_i * 1275 + FIXP_FRAC * 1275;
fixp_q = fixp_q * 1275 + FIXP_FRAC * 1275;
*vbuf++ = DIV_ROUND_CLOSEST(fixp_i, FIXP_FRAC * 10);
*vbuf++ = DIV_ROUND_CLOSEST(fixp_q, FIXP_FRAC * 10);
break;
case V4L2_SDR_FMT_CS8:
/* convert 'fixp float' to s8 [-128, +127] */
/* s8 = X * 127.5 - 0.5; X is float [-1.0, +1.0] */
fixp_i = fixp_i * 1275 - FIXP_FRAC * 5;
fixp_q = fixp_q * 1275 - FIXP_FRAC * 5;
*vbuf++ = DIV_ROUND_CLOSEST(fixp_i, FIXP_FRAC * 10);
*vbuf++ = DIV_ROUND_CLOSEST(fixp_q, FIXP_FRAC * 10);
break;
default:
break;
}
}
}
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-sdr-cap.h - software defined radio support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_SDR_CAP_H_
#define _VIVID_SDR_CAP_H_
int vivid_sdr_enum_freq_bands(struct file *file, void *priv, struct v4l2_frequency_band *band);
int vivid_sdr_g_frequency(struct file *file, void *priv, struct v4l2_frequency *vf);
int vivid_sdr_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *vf);
int vivid_sdr_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt);
int vivid_sdr_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt);
int vidioc_enum_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f);
int vidioc_g_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_sdr_cap(struct file *file, void *priv, struct v4l2_format *f);
void vivid_sdr_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf);
extern const struct vb2_ops vivid_sdr_cap_qops;
#endif
@@ -0,0 +1,336 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-touch-cap.c - touch support functions.
*/
#include "vivid-core.h"
#include "vivid-kthread-touch.h"
#include "vivid-vid-common.h"
#include "vivid-touch-cap.h"
static int touch_cap_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
struct v4l2_pix_format *f = &dev->tch_format;
unsigned int size = f->sizeimage;
if (*nplanes) {
if (*nplanes != 1)
return -EINVAL;
return sizes[0] < size ? -EINVAL : 0;
}
*nplanes = 1;
sizes[0] = size;
return 0;
}
static int touch_cap_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct v4l2_pix_format *f = &dev->tch_format;
unsigned int size = f->sizeimage;
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void touch_cap_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
vbuf->field = V4L2_FIELD_NONE;
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->touch_cap_active);
spin_unlock(&dev->slock);
}
static int touch_cap_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err;
dev->touch_cap_seq_count = 0;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else {
err = vivid_start_generating_touch_cap(dev);
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp,
&dev->touch_cap_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void touch_cap_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
vivid_stop_generating_touch_cap(dev);
}
static void touch_cap_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_touch_cap);
}
const struct vb2_ops vivid_touch_cap_qops = {
.queue_setup = touch_cap_queue_setup,
.buf_prepare = touch_cap_buf_prepare,
.buf_queue = touch_cap_buf_queue,
.start_streaming = touch_cap_start_streaming,
.stop_streaming = touch_cap_stop_streaming,
.buf_request_complete = touch_cap_buf_request_complete,
};
int vivid_enum_fmt_tch(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
if (f->index)
return -EINVAL;
f->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
return 0;
}
int vivid_g_fmt_tch(struct file *file, void *priv, struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
if (dev->multiplanar)
return -ENOTTY;
f->fmt.pix = dev->tch_format;
return 0;
}
int vivid_g_fmt_tch_mplane(struct file *file, void *priv, struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_format sp_fmt;
if (!dev->multiplanar)
return -ENOTTY;
sp_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
sp_fmt.fmt.pix = dev->tch_format;
fmt_sp2mp(&sp_fmt, f);
return 0;
}
int vivid_g_parm_tch(struct file *file, void *priv,
struct v4l2_streamparm *parm)
{
struct vivid_dev *dev = video_drvdata(file);
if (parm->type != (dev->multiplanar ?
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
V4L2_BUF_TYPE_VIDEO_CAPTURE))
return -EINVAL;
parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
parm->parm.capture.timeperframe = dev->timeperframe_tch_cap;
parm->parm.capture.readbuffers = 1;
return 0;
}
int vivid_enum_input_tch(struct file *file, void *priv, struct v4l2_input *inp)
{
if (inp->index)
return -EINVAL;
inp->type = V4L2_INPUT_TYPE_TOUCH;
strscpy(inp->name, "Vivid Touch", sizeof(inp->name));
inp->capabilities = 0;
return 0;
}
int vivid_g_input_tch(struct file *file, void *priv, unsigned int *i)
{
*i = 0;
return 0;
}
int vivid_set_touch(struct vivid_dev *dev, unsigned int i)
{
struct v4l2_pix_format *f = &dev->tch_format;
if (i)
return -EINVAL;
f->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
f->width = VIVID_TCH_WIDTH;
f->height = VIVID_TCH_HEIGHT;
f->field = V4L2_FIELD_NONE;
f->colorspace = V4L2_COLORSPACE_RAW;
f->bytesperline = f->width * sizeof(s16);
f->sizeimage = f->width * f->height * sizeof(s16);
return 0;
}
int vivid_s_input_tch(struct file *file, void *priv, unsigned int i)
{
return vivid_set_touch(video_drvdata(file), i);
}
static void vivid_fill_buff_noise(__s16 *tch_buf, int size)
{
int i;
/* Fill 10% of the values within range -3 and 3, zero the others */
for (i = 0; i < size; i++) {
unsigned int rand = get_random_u32();
if (rand % 10)
tch_buf[i] = 0;
else
tch_buf[i] = (rand / 10) % 7 - 3;
}
}
static inline int get_random_pressure(void)
{
return get_random_u32_below(VIVID_PRESSURE_LIMIT);
}
static void vivid_tch_buf_set(struct v4l2_pix_format *f,
__s16 *tch_buf,
int index)
{
unsigned int x = index % f->width;
unsigned int y = index / f->width;
unsigned int offset = VIVID_MIN_PRESSURE;
tch_buf[index] = offset + get_random_pressure();
offset /= 2;
if (x)
tch_buf[index - 1] = offset + get_random_pressure();
if (x < f->width - 1)
tch_buf[index + 1] = offset + get_random_pressure();
if (y)
tch_buf[index - f->width] = offset + get_random_pressure();
if (y < f->height - 1)
tch_buf[index + f->width] = offset + get_random_pressure();
offset /= 2;
if (x && y)
tch_buf[index - 1 - f->width] = offset + get_random_pressure();
if (x < f->width - 1 && y)
tch_buf[index + 1 - f->width] = offset + get_random_pressure();
if (x && y < f->height - 1)
tch_buf[index - 1 + f->width] = offset + get_random_pressure();
if (x < f->width - 1 && y < f->height - 1)
tch_buf[index + 1 + f->width] = offset + get_random_pressure();
}
void vivid_fillbuff_tch(struct vivid_dev *dev, struct vivid_buffer *buf)
{
struct v4l2_pix_format *f = &dev->tch_format;
int size = f->width * f->height;
int x, y, xstart, ystart, offset_x, offset_y;
unsigned int test_pattern, test_pat_idx, rand;
__s16 *tch_buf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
buf->vb.sequence = dev->touch_cap_with_seq_wrap_count;
test_pattern = (buf->vb.sequence / TCH_SEQ_COUNT) % TEST_CASE_MAX;
test_pat_idx = buf->vb.sequence % TCH_SEQ_COUNT;
vivid_fill_buff_noise(tch_buf, size);
if (test_pat_idx >= TCH_PATTERN_COUNT)
return;
if (test_pat_idx == 0)
dev->tch_pat_random = get_random_u32();
rand = dev->tch_pat_random;
switch (test_pattern) {
case SINGLE_TAP:
if (test_pat_idx == 2)
vivid_tch_buf_set(f, tch_buf, rand % size);
break;
case DOUBLE_TAP:
if (test_pat_idx == 2 || test_pat_idx == 4)
vivid_tch_buf_set(f, tch_buf, rand % size);
break;
case TRIPLE_TAP:
if (test_pat_idx == 2 || test_pat_idx == 4 || test_pat_idx == 6)
vivid_tch_buf_set(f, tch_buf, rand % size);
break;
case MOVE_LEFT_TO_RIGHT:
vivid_tch_buf_set(f, tch_buf,
(rand % f->height) * f->width +
test_pat_idx *
(f->width / TCH_PATTERN_COUNT));
break;
case ZOOM_IN:
x = f->width / 2;
y = f->height / 2;
offset_x = ((TCH_PATTERN_COUNT - 1 - test_pat_idx) * x) /
TCH_PATTERN_COUNT;
offset_y = ((TCH_PATTERN_COUNT - 1 - test_pat_idx) * y) /
TCH_PATTERN_COUNT;
vivid_tch_buf_set(f, tch_buf,
(x - offset_x) + f->width * (y - offset_y));
vivid_tch_buf_set(f, tch_buf,
(x + offset_x) + f->width * (y + offset_y));
break;
case ZOOM_OUT:
x = f->width / 2;
y = f->height / 2;
offset_x = (test_pat_idx * x) / TCH_PATTERN_COUNT;
offset_y = (test_pat_idx * y) / TCH_PATTERN_COUNT;
vivid_tch_buf_set(f, tch_buf,
(x - offset_x) + f->width * (y - offset_y));
vivid_tch_buf_set(f, tch_buf,
(x + offset_x) + f->width * (y + offset_y));
break;
case PALM_PRESS:
for (x = 0; x < f->width; x++)
for (y = f->height / 2; y < f->height; y++)
tch_buf[x + f->width * y] = VIVID_MIN_PRESSURE +
get_random_pressure();
break;
case MULTIPLE_PRESS:
/* 16 pressure points */
for (y = 0; y < 4; y++) {
for (x = 0; x < 4; x++) {
ystart = (y * f->height) / 4 + f->height / 8;
xstart = (x * f->width) / 4 + f->width / 8;
vivid_tch_buf_set(f, tch_buf,
ystart * f->width + xstart);
}
}
break;
}
#ifdef __BIG_ENDIAN__
for (x = 0; x < size; x++)
tch_buf[x] = (__force s16)__cpu_to_le16((u16)tch_buf[x]);
#endif
}
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-touch-cap.h - touch support functions.
*/
#ifndef _VIVID_TOUCH_CAP_H_
#define _VIVID_TOUCH_CAP_H_
#define VIVID_TCH_HEIGHT 12
#define VIVID_TCH_WIDTH 21
#define VIVID_MIN_PRESSURE 180
#define VIVID_PRESSURE_LIMIT 40
#define TCH_SEQ_COUNT 16
#define TCH_PATTERN_COUNT 12
enum vivid_tch_test {
SINGLE_TAP,
DOUBLE_TAP,
TRIPLE_TAP,
MOVE_LEFT_TO_RIGHT,
ZOOM_IN,
ZOOM_OUT,
PALM_PRESS,
MULTIPLE_PRESS,
TEST_CASE_MAX
};
extern const struct vb2_ops vivid_touch_cap_qops;
int vivid_enum_fmt_tch(struct file *file, void *priv, struct v4l2_fmtdesc *f);
int vivid_g_fmt_tch(struct file *file, void *priv, struct v4l2_format *f);
int vivid_g_fmt_tch_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vivid_enum_input_tch(struct file *file, void *priv, struct v4l2_input *inp);
int vivid_g_input_tch(struct file *file, void *priv, unsigned int *i);
int vivid_s_input_tch(struct file *file, void *priv, unsigned int i);
void vivid_fillbuff_tch(struct vivid_dev *dev, struct vivid_buffer *buf);
int vivid_set_touch(struct vivid_dev *dev, unsigned int i);
int vivid_g_parm_tch(struct file *file, void *priv,
struct v4l2_streamparm *parm);
#endif
@@ -0,0 +1,359 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-vbi-cap.c - vbi capture support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include "vivid-core.h"
#include "vivid-kthread-cap.h"
#include "vivid-vbi-cap.h"
#include "vivid-vbi-gen.h"
#include "vivid-vid-common.h"
static void vivid_sliced_vbi_cap_fill(struct vivid_dev *dev, unsigned seqnr)
{
struct vivid_vbi_gen_data *vbi_gen = &dev->vbi_gen;
bool is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
vivid_vbi_gen_sliced(vbi_gen, is_60hz, seqnr);
if (!is_60hz) {
if (vivid_vid_can_loop(dev)) {
if (dev->vbi_out_have_wss) {
vbi_gen->data[12].data[0] = dev->vbi_out_wss[0];
vbi_gen->data[12].data[1] = dev->vbi_out_wss[1];
} else {
vbi_gen->data[12].id = 0;
}
} else {
switch (tpg_g_video_aspect(&dev->tpg)) {
case TPG_VIDEO_ASPECT_14X9_CENTRE:
vbi_gen->data[12].data[0] = 0x01;
break;
case TPG_VIDEO_ASPECT_16X9_CENTRE:
vbi_gen->data[12].data[0] = 0x0b;
break;
case TPG_VIDEO_ASPECT_16X9_ANAMORPHIC:
vbi_gen->data[12].data[0] = 0x07;
break;
case TPG_VIDEO_ASPECT_4X3:
default:
vbi_gen->data[12].data[0] = 0x08;
break;
}
}
} else if (vivid_vid_can_loop(dev) && is_60hz) {
if (dev->vbi_out_have_cc[0]) {
vbi_gen->data[0].data[0] = dev->vbi_out_cc[0][0];
vbi_gen->data[0].data[1] = dev->vbi_out_cc[0][1];
} else {
vbi_gen->data[0].id = 0;
}
if (dev->vbi_out_have_cc[1]) {
vbi_gen->data[1].data[0] = dev->vbi_out_cc[1][0];
vbi_gen->data[1].data[1] = dev->vbi_out_cc[1][1];
} else {
vbi_gen->data[1].id = 0;
}
}
}
static void vivid_g_fmt_vbi_cap(struct vivid_dev *dev, struct v4l2_vbi_format *vbi)
{
bool is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
vbi->sampling_rate = 27000000;
vbi->offset = 24;
vbi->samples_per_line = 1440;
vbi->sample_format = V4L2_PIX_FMT_GREY;
vbi->start[0] = is_60hz ? V4L2_VBI_ITU_525_F1_START + 9 : V4L2_VBI_ITU_625_F1_START + 5;
vbi->start[1] = is_60hz ? V4L2_VBI_ITU_525_F2_START + 9 : V4L2_VBI_ITU_625_F2_START + 5;
vbi->count[0] = vbi->count[1] = is_60hz ? 12 : 18;
vbi->flags = dev->vbi_cap_interlaced ? V4L2_VBI_INTERLACED : 0;
vbi->reserved[0] = 0;
vbi->reserved[1] = 0;
}
void vivid_raw_vbi_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf)
{
struct v4l2_vbi_format vbi;
u8 *vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
vivid_g_fmt_vbi_cap(dev, &vbi);
buf->vb.sequence = dev->vbi_cap_seq_count;
if (dev->field_cap == V4L2_FIELD_ALTERNATE)
buf->vb.sequence /= 2;
vivid_sliced_vbi_cap_fill(dev, buf->vb.sequence);
memset(vbuf, 0x10, vb2_plane_size(&buf->vb.vb2_buf, 0));
if (!VIVID_INVALID_SIGNAL(dev->std_signal_mode[dev->input]))
vivid_vbi_gen_raw(&dev->vbi_gen, &vbi, vbuf);
}
void vivid_sliced_vbi_cap_process(struct vivid_dev *dev,
struct vivid_buffer *buf)
{
struct v4l2_sliced_vbi_data *vbuf =
vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
buf->vb.sequence = dev->vbi_cap_seq_count;
if (dev->field_cap == V4L2_FIELD_ALTERNATE)
buf->vb.sequence /= 2;
vivid_sliced_vbi_cap_fill(dev, buf->vb.sequence);
memset(vbuf, 0, vb2_plane_size(&buf->vb.vb2_buf, 0));
if (!VIVID_INVALID_SIGNAL(dev->std_signal_mode[dev->input])) {
unsigned i;
for (i = 0; i < 25; i++)
vbuf[i] = dev->vbi_gen.data[i];
}
}
static int vbi_cap_queue_setup(struct vb2_queue *vq,
unsigned *nbuffers, unsigned *nplanes,
unsigned sizes[], struct device *alloc_devs[])
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
bool is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
unsigned size = vq->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE ?
36 * sizeof(struct v4l2_sliced_vbi_data) :
1440 * 2 * (is_60hz ? 12 : 18);
if (!vivid_is_sdtv_cap(dev))
return -EINVAL;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
sizes[0] = size;
*nplanes = 1;
return 0;
}
static int vbi_cap_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
bool is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
unsigned size = vb->vb2_queue->type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE ?
36 * sizeof(struct v4l2_sliced_vbi_data) :
1440 * 2 * (is_60hz ? 12 : 18);
dprintk(dev, 1, "%s\n", __func__);
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void vbi_cap_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
dprintk(dev, 1, "%s\n", __func__);
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->vbi_cap_active);
spin_unlock(&dev->slock);
}
static int vbi_cap_start_streaming(struct vb2_queue *vq, unsigned count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err;
dprintk(dev, 1, "%s\n", __func__);
dev->vbi_cap_seq_count = 0;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else {
err = vivid_start_generating_vid_cap(dev, &dev->vbi_cap_streaming);
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp, &dev->vbi_cap_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void vbi_cap_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
dprintk(dev, 1, "%s\n", __func__);
vivid_stop_generating_vid_cap(dev, &dev->vbi_cap_streaming);
}
static void vbi_cap_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_vbi_cap);
}
const struct vb2_ops vivid_vbi_cap_qops = {
.queue_setup = vbi_cap_queue_setup,
.buf_prepare = vbi_cap_buf_prepare,
.buf_queue = vbi_cap_buf_queue,
.start_streaming = vbi_cap_start_streaming,
.stop_streaming = vbi_cap_stop_streaming,
.buf_request_complete = vbi_cap_buf_request_complete,
};
int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_vbi_format *vbi = &f->fmt.vbi;
if (!vivid_is_sdtv_cap(dev) || !dev->has_raw_vbi_cap)
return -EINVAL;
vivid_g_fmt_vbi_cap(dev, vbi);
return 0;
}
int vidioc_s_fmt_vbi_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
int ret = vidioc_g_fmt_vbi_cap(file, priv, f);
if (ret)
return ret;
if (f->type != V4L2_BUF_TYPE_VBI_CAPTURE && vb2_is_busy(&dev->vb_vbi_cap_q))
return -EBUSY;
return 0;
}
void vivid_fill_service_lines(struct v4l2_sliced_vbi_format *vbi, u32 service_set)
{
vbi->io_size = sizeof(struct v4l2_sliced_vbi_data) * 36;
vbi->service_set = service_set;
memset(vbi->service_lines, 0, sizeof(vbi->service_lines));
memset(vbi->reserved, 0, sizeof(vbi->reserved));
if (vbi->service_set == 0)
return;
if (vbi->service_set & V4L2_SLICED_CAPTION_525) {
vbi->service_lines[0][21] = V4L2_SLICED_CAPTION_525;
vbi->service_lines[1][21] = V4L2_SLICED_CAPTION_525;
}
if (vbi->service_set & V4L2_SLICED_WSS_625) {
unsigned i;
for (i = 7; i <= 18; i++)
vbi->service_lines[0][i] =
vbi->service_lines[1][i] = V4L2_SLICED_TELETEXT_B;
vbi->service_lines[0][23] = V4L2_SLICED_WSS_625;
}
}
int vidioc_g_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
if (!vivid_is_sdtv_cap(dev) || !dev->has_sliced_vbi_cap)
return -EINVAL;
vivid_fill_service_lines(vbi, dev->service_set_cap);
return 0;
}
int vidioc_try_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
bool is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
u32 service_set = vbi->service_set;
if (!vivid_is_sdtv_cap(dev) || !dev->has_sliced_vbi_cap)
return -EINVAL;
service_set &= is_60hz ? V4L2_SLICED_CAPTION_525 :
V4L2_SLICED_WSS_625 | V4L2_SLICED_TELETEXT_B;
vivid_fill_service_lines(vbi, service_set);
return 0;
}
int vidioc_s_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
int ret = vidioc_try_fmt_sliced_vbi_cap(file, priv, fmt);
if (ret)
return ret;
if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE && vb2_is_busy(&dev->vb_vbi_cap_q))
return -EBUSY;
dev->service_set_cap = vbi->service_set;
return 0;
}
int vidioc_g_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_sliced_vbi_cap *cap)
{
struct vivid_dev *dev = video_drvdata(file);
struct video_device *vdev = video_devdata(file);
bool is_60hz;
if (vdev->vfl_dir == VFL_DIR_RX) {
is_60hz = dev->std_cap[dev->input] & V4L2_STD_525_60;
if (!vivid_is_sdtv_cap(dev) || !dev->has_sliced_vbi_cap ||
cap->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE)
return -EINVAL;
} else {
is_60hz = dev->std_out & V4L2_STD_525_60;
if (!vivid_is_svid_out(dev) || !dev->has_sliced_vbi_out ||
cap->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT)
return -EINVAL;
}
cap->service_set = is_60hz ? V4L2_SLICED_CAPTION_525 :
V4L2_SLICED_WSS_625 | V4L2_SLICED_TELETEXT_B;
if (is_60hz) {
cap->service_lines[0][21] = V4L2_SLICED_CAPTION_525;
cap->service_lines[1][21] = V4L2_SLICED_CAPTION_525;
} else {
unsigned i;
for (i = 7; i <= 18; i++)
cap->service_lines[0][i] =
cap->service_lines[1][i] = V4L2_SLICED_TELETEXT_B;
cap->service_lines[0][23] = V4L2_SLICED_WSS_625;
}
return 0;
}
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vbi-cap.h - vbi capture support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VBI_CAP_H_
#define _VIVID_VBI_CAP_H_
void vivid_fill_time_of_day_packet(u8 *packet);
void vivid_raw_vbi_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf);
void vivid_sliced_vbi_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf);
void vivid_sliced_vbi_out_process(struct vivid_dev *dev, struct vivid_buffer *buf);
int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
struct v4l2_format *f);
int vidioc_s_fmt_vbi_cap(struct file *file, void *priv,
struct v4l2_format *f);
int vidioc_g_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt);
int vidioc_try_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt);
int vidioc_s_fmt_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_format *fmt);
int vidioc_g_sliced_vbi_cap(struct file *file, void *priv, struct v4l2_sliced_vbi_cap *cap);
void vivid_fill_service_lines(struct v4l2_sliced_vbi_format *vbi, u32 service_set);
extern const struct vb2_ops vivid_vbi_cap_qops;
#endif
@@ -0,0 +1,306 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-vbi-gen.c - vbi generator support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/string.h>
#include <linux/videodev2.h>
#include "vivid-vbi-gen.h"
static void wss_insert(u8 *wss, u32 val, unsigned size)
{
while (size--)
*wss++ = (val & (1 << size)) ? 0xc0 : 0x10;
}
static void vivid_vbi_gen_wss_raw(const struct v4l2_sliced_vbi_data *data,
u8 *buf, unsigned sampling_rate)
{
const unsigned rate = 5000000; /* WSS has a 5 MHz transmission rate */
u8 wss[29 + 24 + 24 + 24 + 18 + 18] = { 0 };
const unsigned zero = 0x07;
const unsigned one = 0x38;
unsigned bit = 0;
u16 wss_data;
int i;
wss_insert(wss + bit, 0x1f1c71c7, 29); bit += 29;
wss_insert(wss + bit, 0x1e3c1f, 24); bit += 24;
wss_data = (data->data[1] << 8) | data->data[0];
for (i = 0; i <= 13; i++, bit += 6)
wss_insert(wss + bit, (wss_data & (1 << i)) ? one : zero, 6);
for (i = 0, bit = 0; bit < sizeof(wss); bit++) {
unsigned n = ((bit + 1) * sampling_rate) / rate;
while (i < n)
buf[i++] = wss[bit];
}
}
static void vivid_vbi_gen_teletext_raw(const struct v4l2_sliced_vbi_data *data,
u8 *buf, unsigned sampling_rate)
{
const unsigned rate = 6937500 / 10; /* Teletext has a 6.9375 MHz transmission rate */
u8 teletext[45] = { 0x55, 0x55, 0x27 };
unsigned bit = 0;
int i;
memcpy(teletext + 3, data->data, sizeof(teletext) - 3);
/* prevents 32 bit overflow */
sampling_rate /= 10;
for (i = 0, bit = 0; bit < sizeof(teletext) * 8; bit++) {
unsigned n = ((bit + 1) * sampling_rate) / rate;
u8 val = (teletext[bit / 8] & (1 << (bit & 7))) ? 0xc0 : 0x10;
while (i < n)
buf[i++] = val;
}
}
static void cc_insert(u8 *cc, u8 ch)
{
unsigned tot = 0;
unsigned i;
for (i = 0; i < 7; i++) {
cc[2 * i] = cc[2 * i + 1] = (ch & (1 << i)) ? 1 : 0;
tot += cc[2 * i];
}
cc[14] = cc[15] = !(tot & 1);
}
#define CC_PREAMBLE_BITS (14 + 4 + 2)
static void vivid_vbi_gen_cc_raw(const struct v4l2_sliced_vbi_data *data,
u8 *buf, unsigned sampling_rate)
{
const unsigned rate = 1000000; /* CC has a 1 MHz transmission rate */
u8 cc[CC_PREAMBLE_BITS + 2 * 16] = {
/* Clock run-in: 7 cycles */
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
/* 2 cycles of 0 */
0, 0, 0, 0,
/* Start bit of 1 (each bit is two cycles) */
1, 1
};
unsigned bit, i;
cc_insert(cc + CC_PREAMBLE_BITS, data->data[0]);
cc_insert(cc + CC_PREAMBLE_BITS + 16, data->data[1]);
for (i = 0, bit = 0; bit < sizeof(cc); bit++) {
unsigned n = ((bit + 1) * sampling_rate) / rate;
while (i < n)
buf[i++] = cc[bit] ? 0xc0 : 0x10;
}
}
void vivid_vbi_gen_raw(const struct vivid_vbi_gen_data *vbi,
const struct v4l2_vbi_format *vbi_fmt, u8 *buf)
{
unsigned idx;
for (idx = 0; idx < 25; idx++) {
const struct v4l2_sliced_vbi_data *data = vbi->data + idx;
unsigned start_2nd_field;
unsigned line = data->line;
u8 *linebuf = buf;
start_2nd_field = (data->id & V4L2_SLICED_VBI_525) ? 263 : 313;
if (data->field)
line += start_2nd_field;
line -= vbi_fmt->start[data->field];
if (vbi_fmt->flags & V4L2_VBI_INTERLACED)
linebuf += (line * 2 + data->field) *
vbi_fmt->samples_per_line;
else
linebuf += (line + data->field * vbi_fmt->count[0]) *
vbi_fmt->samples_per_line;
if (data->id == V4L2_SLICED_CAPTION_525)
vivid_vbi_gen_cc_raw(data, linebuf, vbi_fmt->sampling_rate);
else if (data->id == V4L2_SLICED_WSS_625)
vivid_vbi_gen_wss_raw(data, linebuf, vbi_fmt->sampling_rate);
else if (data->id == V4L2_SLICED_TELETEXT_B)
vivid_vbi_gen_teletext_raw(data, linebuf, vbi_fmt->sampling_rate);
}
}
static const u8 vivid_cc_sequence1[30] = {
0x14, 0x20, /* Resume Caption Loading */
'H', 'e',
'l', 'l',
'o', ' ',
'w', 'o',
'r', 'l',
'd', '!',
0x14, 0x2f, /* End of Caption */
};
static const u8 vivid_cc_sequence2[30] = {
0x14, 0x20, /* Resume Caption Loading */
'C', 'l',
'o', 's',
'e', 'd',
' ', 'c',
'a', 'p',
't', 'i',
'o', 'n',
's', ' ',
't', 'e',
's', 't',
0x14, 0x2f, /* End of Caption */
};
static u8 calc_parity(u8 val)
{
return val | (parity8(val) ? 0 : 0x80);
}
static void vivid_vbi_gen_set_time_of_day(u8 *packet)
{
struct tm tm;
u8 checksum, i;
time64_to_tm(ktime_get_real_seconds(), 0, &tm);
packet[0] = calc_parity(0x07);
packet[1] = calc_parity(0x01);
packet[2] = calc_parity(0x40 | tm.tm_min);
packet[3] = calc_parity(0x40 | tm.tm_hour);
packet[4] = calc_parity(0x40 | tm.tm_mday);
if (tm.tm_mday == 1 && tm.tm_mon == 2 &&
sys_tz.tz_minuteswest > tm.tm_min + tm.tm_hour * 60)
packet[4] = calc_parity(0x60 | tm.tm_mday);
packet[5] = calc_parity(0x40 | (1 + tm.tm_mon));
packet[6] = calc_parity(0x40 | (1 + tm.tm_wday));
packet[7] = calc_parity(0x40 | ((tm.tm_year - 90) & 0x3f));
packet[8] = calc_parity(0x0f);
for (checksum = i = 0; i <= 8; i++)
checksum += packet[i] & 0x7f;
packet[9] = calc_parity(0x100 - checksum);
packet[10] = calc_parity(0x07);
packet[11] = calc_parity(0x04);
if (sys_tz.tz_minuteswest >= 0)
packet[12] = calc_parity(0x40 | ((sys_tz.tz_minuteswest / 60) & 0x1f));
else
packet[12] = calc_parity(0x40 | ((24 + sys_tz.tz_minuteswest / 60) & 0x1f));
packet[13] = calc_parity(0);
packet[14] = calc_parity(0x0f);
for (checksum = 0, i = 10; i <= 14; i++)
checksum += packet[i] & 0x7f;
packet[15] = calc_parity(0x100 - checksum);
}
static const u8 hamming[16] = {
0x15, 0x02, 0x49, 0x5e, 0x64, 0x73, 0x38, 0x2f,
0xd0, 0xc7, 0x8c, 0x9b, 0xa1, 0xb6, 0xfd, 0xea
};
static void vivid_vbi_gen_teletext(u8 *packet, unsigned line, unsigned frame)
{
unsigned offset = 2;
unsigned i;
packet[0] = hamming[1 + ((line & 1) << 3)];
packet[1] = hamming[line >> 1];
memset(packet + 2, 0x20, 40);
if (line == 0) {
/* subcode */
packet[2] = hamming[frame % 10];
packet[3] = hamming[frame / 10];
packet[4] = hamming[0];
packet[5] = hamming[0];
packet[6] = hamming[0];
packet[7] = hamming[0];
packet[8] = hamming[0];
packet[9] = hamming[1];
offset = 10;
}
packet += offset;
memcpy(packet, "Page: 100 Row: 10", 17);
packet[7] = '0' + frame / 10;
packet[8] = '0' + frame % 10;
packet[15] = '0' + line / 10;
packet[16] = '0' + line % 10;
for (i = 0; i < 42 - offset; i++)
packet[i] = calc_parity(packet[i]);
}
void vivid_vbi_gen_sliced(struct vivid_vbi_gen_data *vbi,
bool is_60hz, unsigned seqnr)
{
struct v4l2_sliced_vbi_data *data0 = vbi->data;
struct v4l2_sliced_vbi_data *data1 = vbi->data + 1;
unsigned frame = seqnr % 60;
memset(vbi->data, 0, sizeof(vbi->data));
if (!is_60hz) {
unsigned i;
for (i = 0; i <= 11; i++) {
data0->id = V4L2_SLICED_TELETEXT_B;
data0->line = 7 + i;
vivid_vbi_gen_teletext(data0->data, i, frame);
data0++;
}
data0->id = V4L2_SLICED_WSS_625;
data0->line = 23;
/* 4x3 video aspect ratio */
data0->data[0] = 0x08;
data0++;
for (i = 0; i <= 11; i++) {
data0->id = V4L2_SLICED_TELETEXT_B;
data0->field = 1;
data0->line = 7 + i;
vivid_vbi_gen_teletext(data0->data, 12 + i, frame);
data0++;
}
return;
}
data0->id = V4L2_SLICED_CAPTION_525;
data0->line = 21;
data1->id = V4L2_SLICED_CAPTION_525;
data1->field = 1;
data1->line = 21;
if (frame < 15) {
data0->data[0] = calc_parity(vivid_cc_sequence1[2 * frame]);
data0->data[1] = calc_parity(vivid_cc_sequence1[2 * frame + 1]);
} else if (frame >= 30 && frame < 45) {
frame -= 30;
data0->data[0] = calc_parity(vivid_cc_sequence2[2 * frame]);
data0->data[1] = calc_parity(vivid_cc_sequence2[2 * frame + 1]);
} else {
data0->data[0] = calc_parity(0);
data0->data[1] = calc_parity(0);
}
frame = seqnr % (30 * 60);
switch (frame) {
case 0:
vivid_vbi_gen_set_time_of_day(vbi->time_of_day_packet);
fallthrough;
case 1 ... 7:
data1->data[0] = vbi->time_of_day_packet[frame * 2];
data1->data[1] = vbi->time_of_day_packet[frame * 2 + 1];
break;
default:
data1->data[0] = calc_parity(0);
data1->data[1] = calc_parity(0);
break;
}
}
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vbi-gen.h - vbi generator support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VBI_GEN_H_
#define _VIVID_VBI_GEN_H_
struct vivid_vbi_gen_data {
struct v4l2_sliced_vbi_data data[25];
u8 time_of_day_packet[16];
};
void vivid_vbi_gen_sliced(struct vivid_vbi_gen_data *vbi,
bool is_60hz, unsigned seqnr);
void vivid_vbi_gen_raw(const struct vivid_vbi_gen_data *vbi,
const struct v4l2_vbi_format *vbi_fmt, u8 *buf);
#endif
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vivid-vbi-out.c - vbi output support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/videodev2.h>
#include <media/v4l2-common.h>
#include "vivid-core.h"
#include "vivid-kthread-out.h"
#include "vivid-vbi-out.h"
#include "vivid-vbi-cap.h"
static int vbi_out_queue_setup(struct vb2_queue *vq,
unsigned *nbuffers, unsigned *nplanes,
unsigned sizes[], struct device *alloc_devs[])
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
bool is_60hz = dev->std_out & V4L2_STD_525_60;
unsigned size = vq->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT ?
36 * sizeof(struct v4l2_sliced_vbi_data) :
1440 * 2 * (is_60hz ? 12 : 18);
if (!vivid_is_svid_out(dev))
return -EINVAL;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
sizes[0] = size;
*nplanes = 1;
return 0;
}
static int vbi_out_buf_prepare(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
bool is_60hz = dev->std_out & V4L2_STD_525_60;
unsigned size = vb->vb2_queue->type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT ?
36 * sizeof(struct v4l2_sliced_vbi_data) :
1440 * 2 * (is_60hz ? 12 : 18);
dprintk(dev, 1, "%s\n", __func__);
if (dev->buf_prepare_error) {
/*
* Error injection: test what happens if buf_prepare() returns
* an error.
*/
dev->buf_prepare_error = false;
return -EINVAL;
}
if (vb2_plane_size(vb, 0) < size) {
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
__func__, vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
return 0;
}
static void vbi_out_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
struct vivid_buffer *buf = container_of(vbuf, struct vivid_buffer, vb);
dprintk(dev, 1, "%s\n", __func__);
spin_lock(&dev->slock);
list_add_tail(&buf->list, &dev->vbi_out_active);
spin_unlock(&dev->slock);
}
static int vbi_out_start_streaming(struct vb2_queue *vq, unsigned count)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
int err;
dprintk(dev, 1, "%s\n", __func__);
dev->vbi_out_seq_count = 0;
if (dev->start_streaming_error) {
dev->start_streaming_error = false;
err = -EINVAL;
} else {
err = vivid_start_generating_vid_out(dev, &dev->vbi_out_streaming);
}
if (err) {
struct vivid_buffer *buf, *tmp;
list_for_each_entry_safe(buf, tmp, &dev->vbi_out_active, list) {
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf,
VB2_BUF_STATE_QUEUED);
}
}
return err;
}
/* abort streaming and wait for last buffer */
static void vbi_out_stop_streaming(struct vb2_queue *vq)
{
struct vivid_dev *dev = vb2_get_drv_priv(vq);
dprintk(dev, 1, "%s\n", __func__);
vivid_stop_generating_vid_out(dev, &dev->vbi_out_streaming);
dev->vbi_out_have_wss = false;
dev->vbi_out_have_cc[0] = false;
dev->vbi_out_have_cc[1] = false;
}
static void vbi_out_buf_request_complete(struct vb2_buffer *vb)
{
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
v4l2_ctrl_request_complete(vb->req_obj.req, &dev->ctrl_hdl_vbi_out);
}
const struct vb2_ops vivid_vbi_out_qops = {
.queue_setup = vbi_out_queue_setup,
.buf_prepare = vbi_out_buf_prepare,
.buf_queue = vbi_out_buf_queue,
.start_streaming = vbi_out_start_streaming,
.stop_streaming = vbi_out_stop_streaming,
.buf_request_complete = vbi_out_buf_request_complete,
};
int vidioc_g_fmt_vbi_out(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_vbi_format *vbi = &f->fmt.vbi;
bool is_60hz = dev->std_out & V4L2_STD_525_60;
if (!vivid_is_svid_out(dev) || !dev->has_raw_vbi_out)
return -EINVAL;
vbi->sampling_rate = 25000000;
vbi->offset = 24;
vbi->samples_per_line = 1440;
vbi->sample_format = V4L2_PIX_FMT_GREY;
vbi->start[0] = is_60hz ? V4L2_VBI_ITU_525_F1_START + 9 : V4L2_VBI_ITU_625_F1_START + 5;
vbi->start[1] = is_60hz ? V4L2_VBI_ITU_525_F2_START + 9 : V4L2_VBI_ITU_625_F2_START + 5;
vbi->count[0] = vbi->count[1] = is_60hz ? 12 : 18;
vbi->flags = dev->vbi_cap_interlaced ? V4L2_VBI_INTERLACED : 0;
vbi->reserved[0] = 0;
vbi->reserved[1] = 0;
return 0;
}
int vidioc_s_fmt_vbi_out(struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivid_dev *dev = video_drvdata(file);
int ret = vidioc_g_fmt_vbi_out(file, priv, f);
if (ret)
return ret;
if (vb2_is_busy(&dev->vb_vbi_out_q))
return -EBUSY;
dev->stream_sliced_vbi_out = false;
dev->vbi_out_dev.queue->type = V4L2_BUF_TYPE_VBI_OUTPUT;
return 0;
}
int vidioc_g_fmt_sliced_vbi_out(struct file *file, void *priv, struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
if (!vivid_is_svid_out(dev) || !dev->has_sliced_vbi_out)
return -EINVAL;
vivid_fill_service_lines(vbi, dev->service_set_out);
return 0;
}
int vidioc_try_fmt_sliced_vbi_out(struct file *file, void *priv, struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
bool is_60hz = dev->std_out & V4L2_STD_525_60;
u32 service_set = vbi->service_set;
if (!vivid_is_svid_out(dev) || !dev->has_sliced_vbi_out)
return -EINVAL;
service_set &= is_60hz ? V4L2_SLICED_CAPTION_525 :
V4L2_SLICED_WSS_625 | V4L2_SLICED_TELETEXT_B;
vivid_fill_service_lines(vbi, service_set);
return 0;
}
int vidioc_s_fmt_sliced_vbi_out(struct file *file, void *priv,
struct v4l2_format *fmt)
{
struct vivid_dev *dev = video_drvdata(file);
struct v4l2_sliced_vbi_format *vbi = &fmt->fmt.sliced;
int ret = vidioc_try_fmt_sliced_vbi_out(file, priv, fmt);
if (ret)
return ret;
if (vb2_is_busy(&dev->vb_vbi_out_q))
return -EBUSY;
dev->service_set_out = vbi->service_set;
dev->stream_sliced_vbi_out = true;
dev->vbi_out_dev.queue->type = V4L2_BUF_TYPE_SLICED_VBI_OUTPUT;
return 0;
}
void vivid_sliced_vbi_out_process(struct vivid_dev *dev,
struct vivid_buffer *buf)
{
struct v4l2_sliced_vbi_data *vbi =
vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
unsigned elems =
vb2_get_plane_payload(&buf->vb.vb2_buf, 0) / sizeof(*vbi);
dev->vbi_out_have_cc[0] = false;
dev->vbi_out_have_cc[1] = false;
dev->vbi_out_have_wss = false;
while (elems--) {
switch (vbi->id) {
case V4L2_SLICED_CAPTION_525:
if ((dev->std_out & V4L2_STD_525_60) && vbi->line == 21) {
dev->vbi_out_have_cc[!!vbi->field] = true;
dev->vbi_out_cc[!!vbi->field][0] = vbi->data[0];
dev->vbi_out_cc[!!vbi->field][1] = vbi->data[1];
}
break;
case V4L2_SLICED_WSS_625:
if ((dev->std_out & V4L2_STD_625_50) &&
vbi->field == 0 && vbi->line == 23) {
dev->vbi_out_have_wss = true;
dev->vbi_out_wss[0] = vbi->data[0];
dev->vbi_out_wss[1] = vbi->data[1];
}
break;
}
vbi++;
}
}
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vbi-out.h - vbi output support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VBI_OUT_H_
#define _VIVID_VBI_OUT_H_
void vivid_sliced_vbi_out_process(struct vivid_dev *dev, struct vivid_buffer *buf);
int vidioc_g_fmt_vbi_out(struct file *file, void *priv,
struct v4l2_format *f);
int vidioc_s_fmt_vbi_out(struct file *file, void *priv,
struct v4l2_format *f);
int vidioc_g_fmt_sliced_vbi_out(struct file *file, void *priv, struct v4l2_format *fmt);
int vidioc_try_fmt_sliced_vbi_out(struct file *file, void *priv, struct v4l2_format *fmt);
int vidioc_s_fmt_sliced_vbi_out(struct file *file, void *priv, struct v4l2_format *fmt);
extern const struct vb2_ops vivid_vbi_out_qops;
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vid-cap.h - video capture support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VID_CAP_H_
#define _VIVID_VID_CAP_H_
void vivid_update_quality(struct vivid_dev *dev);
void vivid_update_reduced_fps(struct vivid_dev *dev);
void vivid_update_format_cap(struct vivid_dev *dev, bool keep_controls);
void vivid_update_outputs(struct vivid_dev *dev);
void vivid_update_connected_outputs(struct vivid_dev *dev);
enum tpg_video_aspect vivid_get_video_aspect(const struct vivid_dev *dev);
extern const v4l2_std_id vivid_standard[];
extern const char * const vivid_ctrl_standard_strings[];
extern const struct vb2_ops vivid_vid_cap_qops;
int vivid_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vivid_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vivid_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f);
int vivid_vid_cap_g_selection(struct file *file, void *priv, struct v4l2_selection *sel);
int vivid_vid_cap_s_selection(struct file *file, void *priv, struct v4l2_selection *s);
int vivid_vid_cap_g_pixelaspect(struct file *file, void *priv, int type, struct v4l2_fract *f);
int vidioc_enum_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_fmtdesc *f);
int vidioc_g_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_enum_input(struct file *file, void *priv, struct v4l2_input *inp);
int vidioc_g_input(struct file *file, void *priv, unsigned *i);
int vidioc_s_input(struct file *file, void *priv, unsigned i);
int vidioc_enumaudio(struct file *file, void *priv, struct v4l2_audio *vin);
int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *vin);
int vidioc_s_audio(struct file *file, void *priv, const struct v4l2_audio *vin);
int vivid_video_g_frequency(struct file *file, void *priv, struct v4l2_frequency *vf);
int vivid_video_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *vf);
int vivid_video_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt);
int vivid_video_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt);
int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *id);
int vivid_vid_cap_s_std(struct file *file, void *priv, v4l2_std_id id);
int vivid_vid_cap_s_dv_timings(struct file *file, void *priv, struct v4l2_dv_timings *timings);
int vidioc_query_dv_timings(struct file *file, void *priv, struct v4l2_dv_timings *timings);
int vidioc_s_edid(struct file *file, void *priv, struct v4l2_edid *edid);
int vidioc_enum_framesizes(struct file *file, void *priv, struct v4l2_frmsizeenum *fsize);
int vidioc_enum_frameintervals(struct file *file, void *priv, struct v4l2_frmivalenum *fival);
int vivid_vid_cap_g_parm(struct file *file, void *priv, struct v4l2_streamparm *parm);
int vivid_vid_cap_s_parm(struct file *file, void *priv, struct v4l2_streamparm *parm);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vid-common.h - common video support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VID_COMMON_H_
#define _VIVID_VID_COMMON_H_
typedef int (*fmtfunc)(struct file *file, void *priv, struct v4l2_format *f);
/*
* Conversion function that converts a single-planar format to a
* single-plane multiplanar format.
*/
void fmt_sp2mp(const struct v4l2_format *sp_fmt, struct v4l2_format *mp_fmt);
int fmt_sp2mp_func(struct file *file, void *priv,
struct v4l2_format *f, fmtfunc func);
extern const struct v4l2_dv_timings_cap vivid_dv_timings_cap;
const struct vivid_fmt *vivid_get_format(struct vivid_dev *dev, u32 pixelformat);
struct vivid_dev *vivid_input_is_connected_to(struct vivid_dev *dev);
struct vivid_dev *vivid_output_is_connected_to(struct vivid_dev *dev);
bool vivid_vid_can_loop(struct vivid_dev *dev);
void vivid_send_source_change(struct vivid_dev *dev, unsigned int type);
void vivid_send_input_source_change(struct vivid_dev *dev, unsigned int input_index);
int vivid_vid_adjust_sel(unsigned flags, struct v4l2_rect *r);
int vivid_enum_fmt_vid(struct file *file, void *priv, struct v4l2_fmtdesc *f);
int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id);
int vidioc_g_dv_timings(struct file *file, void *priv, struct v4l2_dv_timings *timings);
int vidioc_enum_dv_timings(struct file *file, void *priv, struct v4l2_enum_dv_timings *timings);
int vidioc_dv_timings_cap(struct file *file, void *priv, struct v4l2_dv_timings_cap *cap);
int vidioc_g_edid(struct file *file, void *priv, struct v4l2_edid *edid);
int vidioc_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* vivid-vid-out.h - video output support functions.
*
* Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef _VIVID_VID_OUT_H_
#define _VIVID_VID_OUT_H_
extern const struct vb2_ops vivid_vid_out_qops;
void vivid_update_format_out(struct vivid_dev *dev);
int vivid_g_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vivid_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vivid_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_g_fmt_vid_out_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_out_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_out_mplane(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_g_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_out(struct file *file, void *priv, struct v4l2_format *f);
int vivid_vid_out_g_selection(struct file *file, void *priv, struct v4l2_selection *sel);
int vivid_vid_out_s_selection(struct file *file, void *priv, struct v4l2_selection *s);
int vivid_vid_out_g_pixelaspect(struct file *file, void *priv, int type, struct v4l2_fract *f);
int vidioc_enum_fmt_vid_out_overlay(struct file *file, void *priv, struct v4l2_fmtdesc *f);
int vidioc_g_fmt_vid_out_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_try_fmt_vid_out_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vidioc_s_fmt_vid_out_overlay(struct file *file, void *priv, struct v4l2_format *f);
int vivid_vid_out_overlay(struct file *file, void *priv, unsigned i);
int vivid_vid_out_g_fbuf(struct file *file, void *priv, struct v4l2_framebuffer *a);
int vivid_vid_out_s_fbuf(struct file *file, void *priv, const struct v4l2_framebuffer *a);
int vidioc_enum_output(struct file *file, void *priv, struct v4l2_output *out);
int vidioc_g_output(struct file *file, void *priv, unsigned *i);
int vidioc_s_output(struct file *file, void *priv, unsigned i);
int vidioc_enumaudout(struct file *file, void *priv, struct v4l2_audioout *vout);
int vidioc_g_audout(struct file *file, void *priv, struct v4l2_audioout *vout);
int vidioc_s_audout(struct file *file, void *priv, const struct v4l2_audioout *vout);
int vivid_vid_out_s_std(struct file *file, void *priv, v4l2_std_id id);
int vivid_vid_out_s_dv_timings(struct file *file, void *priv, struct v4l2_dv_timings *timings);
int vivid_vid_out_g_parm(struct file *file, void *priv, struct v4l2_streamparm *parm);
#endif