mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-26 04:25:27 +08:00
0a7790be18
The saa6588_ioctl() function expects to get called from other kernel
functions with a 'saa6588_command' pointer, but I found nothing stops it
from getting called from user space instead, which seems rather dangerous.
The same thing happens in the davinci vpbe driver with its VENC_GET_FLD
command.
As a quick fix, add a separate .command() callback pointer for this
driver and change the two callers over to that. This change can easily
get backported to stable kernels if necessary, but since there are only
two drivers, we may want to eventually replace this with a set of more
specialized callbacks in the long run.
Fixes: c3fda7f835
("V4L/DVB (10537): saa6588: convert to v4l2_subdev.")
Cc: stable@vger.kernel.org
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
1513 lines
40 KiB
C
1513 lines
40 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2010 Texas Instruments Incorporated - https://www.ti.com/
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/string.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/time.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/slab.h>
|
|
|
|
|
|
#include <media/v4l2-dev.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/davinci/vpbe_display.h>
|
|
#include <media/davinci/vpbe_types.h>
|
|
#include <media/davinci/vpbe.h>
|
|
#include <media/davinci/vpbe_venc.h>
|
|
#include <media/davinci/vpbe_osd.h>
|
|
#include "vpbe_venc_regs.h"
|
|
|
|
#define VPBE_DISPLAY_DRIVER "vpbe-v4l2"
|
|
|
|
static int debug;
|
|
|
|
#define VPBE_DEFAULT_NUM_BUFS 3
|
|
|
|
module_param(debug, int, 0644);
|
|
|
|
static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev,
|
|
struct vpbe_layer *layer);
|
|
|
|
static int venc_is_second_field(struct vpbe_display *disp_dev)
|
|
{
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
int ret, val;
|
|
|
|
ret = v4l2_subdev_call(vpbe_dev->venc,
|
|
core,
|
|
command,
|
|
VENC_GET_FLD,
|
|
&val);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in getting Field ID 0\n");
|
|
return 1;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void vpbe_isr_even_field(struct vpbe_display *disp_obj,
|
|
struct vpbe_layer *layer)
|
|
{
|
|
if (layer->cur_frm == layer->next_frm)
|
|
return;
|
|
|
|
layer->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns();
|
|
vb2_buffer_done(&layer->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
|
|
/* Make cur_frm pointing to next_frm */
|
|
layer->cur_frm = layer->next_frm;
|
|
}
|
|
|
|
static void vpbe_isr_odd_field(struct vpbe_display *disp_obj,
|
|
struct vpbe_layer *layer)
|
|
{
|
|
struct osd_state *osd_device = disp_obj->osd_device;
|
|
unsigned long addr;
|
|
|
|
spin_lock(&disp_obj->dma_queue_lock);
|
|
if (list_empty(&layer->dma_queue) ||
|
|
(layer->cur_frm != layer->next_frm)) {
|
|
spin_unlock(&disp_obj->dma_queue_lock);
|
|
return;
|
|
}
|
|
/*
|
|
* one field is displayed configure
|
|
* the next frame if it is available
|
|
* otherwise hold on current frame
|
|
* Get next from the buffer queue
|
|
*/
|
|
layer->next_frm = list_entry(layer->dma_queue.next,
|
|
struct vpbe_disp_buffer, list);
|
|
/* Remove that from the buffer queue */
|
|
list_del(&layer->next_frm->list);
|
|
spin_unlock(&disp_obj->dma_queue_lock);
|
|
/* Mark state of the frame to active */
|
|
layer->next_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
|
|
addr = vb2_dma_contig_plane_dma_addr(&layer->next_frm->vb.vb2_buf, 0);
|
|
osd_device->ops.start_layer(osd_device,
|
|
layer->layer_info.id,
|
|
addr,
|
|
disp_obj->cbcr_ofst);
|
|
}
|
|
|
|
/* interrupt service routine */
|
|
static irqreturn_t venc_isr(int irq, void *arg)
|
|
{
|
|
struct vpbe_display *disp_dev = (struct vpbe_display *)arg;
|
|
struct vpbe_layer *layer;
|
|
static unsigned last_event;
|
|
unsigned event = 0;
|
|
int fid;
|
|
int i;
|
|
|
|
if (!arg || !disp_dev->dev[0])
|
|
return IRQ_HANDLED;
|
|
|
|
if (venc_is_second_field(disp_dev))
|
|
event |= VENC_SECOND_FIELD;
|
|
else
|
|
event |= VENC_FIRST_FIELD;
|
|
|
|
if (event == (last_event & ~VENC_END_OF_FRAME)) {
|
|
/*
|
|
* If the display is non-interlaced, then we need to flag the
|
|
* end-of-frame event at every interrupt regardless of the
|
|
* value of the FIDST bit. We can conclude that the display is
|
|
* non-interlaced if the value of the FIDST bit is unchanged
|
|
* from the previous interrupt.
|
|
*/
|
|
event |= VENC_END_OF_FRAME;
|
|
} else if (event == VENC_SECOND_FIELD) {
|
|
/* end-of-frame for interlaced display */
|
|
event |= VENC_END_OF_FRAME;
|
|
}
|
|
last_event = event;
|
|
|
|
for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) {
|
|
layer = disp_dev->dev[i];
|
|
|
|
if (!vb2_start_streaming_called(&layer->buffer_queue))
|
|
continue;
|
|
|
|
if (layer->layer_first_int) {
|
|
layer->layer_first_int = 0;
|
|
continue;
|
|
}
|
|
/* Check the field format */
|
|
if ((V4L2_FIELD_NONE == layer->pix_fmt.field) &&
|
|
(event & VENC_END_OF_FRAME)) {
|
|
/* Progressive mode */
|
|
|
|
vpbe_isr_even_field(disp_dev, layer);
|
|
vpbe_isr_odd_field(disp_dev, layer);
|
|
} else {
|
|
/* Interlaced mode */
|
|
|
|
layer->field_id ^= 1;
|
|
if (event & VENC_FIRST_FIELD)
|
|
fid = 0;
|
|
else
|
|
fid = 1;
|
|
|
|
/*
|
|
* If field id does not match with store
|
|
* field id
|
|
*/
|
|
if (fid != layer->field_id) {
|
|
/* Make them in sync */
|
|
layer->field_id = fid;
|
|
continue;
|
|
}
|
|
/*
|
|
* device field id and local field id are
|
|
* in sync. If this is even field
|
|
*/
|
|
if (0 == fid)
|
|
vpbe_isr_even_field(disp_dev, layer);
|
|
else /* odd field */
|
|
vpbe_isr_odd_field(disp_dev, layer);
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* vpbe_buffer_prepare()
|
|
* This is the callback function called from vb2_qbuf() function
|
|
* the buffer is prepared and user space virtual address is converted into
|
|
* physical address
|
|
*/
|
|
static int vpbe_buffer_prepare(struct vb2_buffer *vb)
|
|
{
|
|
struct vb2_queue *q = vb->vb2_queue;
|
|
struct vpbe_layer *layer = vb2_get_drv_priv(q);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
unsigned long addr;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"vpbe_buffer_prepare\n");
|
|
|
|
vb2_set_plane_payload(vb, 0, layer->pix_fmt.sizeimage);
|
|
if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0))
|
|
return -EINVAL;
|
|
|
|
addr = vb2_dma_contig_plane_dma_addr(vb, 0);
|
|
if (!IS_ALIGNED(addr, 8)) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"buffer_prepare:offset is not aligned to 32 bytes\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_buffer_setup()
|
|
* This function allocates memory for the buffers
|
|
*/
|
|
static int
|
|
vpbe_buffer_queue_setup(struct vb2_queue *vq,
|
|
unsigned int *nbuffers, unsigned int *nplanes,
|
|
unsigned int sizes[], struct device *alloc_devs[])
|
|
|
|
{
|
|
/* Get the file handle object and layer object */
|
|
struct vpbe_layer *layer = vb2_get_drv_priv(vq);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_buffer_setup\n");
|
|
|
|
/* Store number of buffers allocated in numbuffer member */
|
|
if (vq->num_buffers + *nbuffers < VPBE_DEFAULT_NUM_BUFS)
|
|
*nbuffers = VPBE_DEFAULT_NUM_BUFS - vq->num_buffers;
|
|
|
|
if (*nplanes)
|
|
return sizes[0] < layer->pix_fmt.sizeimage ? -EINVAL : 0;
|
|
|
|
*nplanes = 1;
|
|
sizes[0] = layer->pix_fmt.sizeimage;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_buffer_queue()
|
|
* This function adds the buffer to DMA queue
|
|
*/
|
|
static void vpbe_buffer_queue(struct vb2_buffer *vb)
|
|
{
|
|
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
|
|
/* Get the file handle object and layer object */
|
|
struct vpbe_disp_buffer *buf = container_of(vbuf,
|
|
struct vpbe_disp_buffer, vb);
|
|
struct vpbe_layer *layer = vb2_get_drv_priv(vb->vb2_queue);
|
|
struct vpbe_display *disp = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
unsigned long flags;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"vpbe_buffer_queue\n");
|
|
|
|
/* add the buffer to the DMA queue */
|
|
spin_lock_irqsave(&disp->dma_queue_lock, flags);
|
|
list_add_tail(&buf->list, &layer->dma_queue);
|
|
spin_unlock_irqrestore(&disp->dma_queue_lock, flags);
|
|
}
|
|
|
|
static int vpbe_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
{
|
|
struct vpbe_layer *layer = vb2_get_drv_priv(vq);
|
|
struct osd_state *osd_device = layer->disp_dev->osd_device;
|
|
int ret;
|
|
|
|
osd_device->ops.disable_layer(osd_device, layer->layer_info.id);
|
|
|
|
/* Get the next frame from the buffer queue */
|
|
layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next,
|
|
struct vpbe_disp_buffer, list);
|
|
/* Remove buffer from the buffer queue */
|
|
list_del(&layer->cur_frm->list);
|
|
/* Mark state of the current frame to active */
|
|
layer->cur_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE;
|
|
/* Initialize field_id and started member */
|
|
layer->field_id = 0;
|
|
|
|
/* Set parameters in OSD and VENC */
|
|
ret = vpbe_set_osd_display_params(layer->disp_dev, layer);
|
|
if (ret < 0) {
|
|
struct vpbe_disp_buffer *buf, *tmp;
|
|
|
|
vb2_buffer_done(&layer->cur_frm->vb.vb2_buf,
|
|
VB2_BUF_STATE_QUEUED);
|
|
list_for_each_entry_safe(buf, tmp, &layer->dma_queue, list) {
|
|
list_del(&buf->list);
|
|
vb2_buffer_done(&buf->vb.vb2_buf,
|
|
VB2_BUF_STATE_QUEUED);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* if request format is yuv420 semiplanar, need to
|
|
* enable both video windows
|
|
*/
|
|
layer->layer_first_int = 1;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void vpbe_stop_streaming(struct vb2_queue *vq)
|
|
{
|
|
struct vpbe_layer *layer = vb2_get_drv_priv(vq);
|
|
struct osd_state *osd_device = layer->disp_dev->osd_device;
|
|
struct vpbe_display *disp = layer->disp_dev;
|
|
unsigned long flags;
|
|
|
|
if (!vb2_is_streaming(vq))
|
|
return;
|
|
|
|
osd_device->ops.disable_layer(osd_device, layer->layer_info.id);
|
|
|
|
/* release all active buffers */
|
|
spin_lock_irqsave(&disp->dma_queue_lock, flags);
|
|
if (layer->cur_frm == layer->next_frm) {
|
|
vb2_buffer_done(&layer->cur_frm->vb.vb2_buf,
|
|
VB2_BUF_STATE_ERROR);
|
|
} else {
|
|
if (layer->cur_frm)
|
|
vb2_buffer_done(&layer->cur_frm->vb.vb2_buf,
|
|
VB2_BUF_STATE_ERROR);
|
|
if (layer->next_frm)
|
|
vb2_buffer_done(&layer->next_frm->vb.vb2_buf,
|
|
VB2_BUF_STATE_ERROR);
|
|
}
|
|
|
|
while (!list_empty(&layer->dma_queue)) {
|
|
layer->next_frm = list_entry(layer->dma_queue.next,
|
|
struct vpbe_disp_buffer, list);
|
|
list_del(&layer->next_frm->list);
|
|
vb2_buffer_done(&layer->next_frm->vb.vb2_buf,
|
|
VB2_BUF_STATE_ERROR);
|
|
}
|
|
spin_unlock_irqrestore(&disp->dma_queue_lock, flags);
|
|
}
|
|
|
|
static const struct vb2_ops video_qops = {
|
|
.queue_setup = vpbe_buffer_queue_setup,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
.wait_finish = vb2_ops_wait_finish,
|
|
.buf_prepare = vpbe_buffer_prepare,
|
|
.start_streaming = vpbe_start_streaming,
|
|
.stop_streaming = vpbe_stop_streaming,
|
|
.buf_queue = vpbe_buffer_queue,
|
|
};
|
|
|
|
static
|
|
struct vpbe_layer*
|
|
_vpbe_display_get_other_win_layer(struct vpbe_display *disp_dev,
|
|
struct vpbe_layer *layer)
|
|
{
|
|
enum vpbe_display_device_id thiswin, otherwin;
|
|
thiswin = layer->device_id;
|
|
|
|
otherwin = (thiswin == VPBE_DISPLAY_DEVICE_0) ?
|
|
VPBE_DISPLAY_DEVICE_1 : VPBE_DISPLAY_DEVICE_0;
|
|
return disp_dev->dev[otherwin];
|
|
}
|
|
|
|
static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev,
|
|
struct vpbe_layer *layer)
|
|
{
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct osd_state *osd_device = disp_dev->osd_device;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
unsigned long addr;
|
|
int ret;
|
|
|
|
addr = vb2_dma_contig_plane_dma_addr(&layer->cur_frm->vb.vb2_buf, 0);
|
|
/* Set address in the display registers */
|
|
osd_device->ops.start_layer(osd_device,
|
|
layer->layer_info.id,
|
|
addr,
|
|
disp_dev->cbcr_ofst);
|
|
|
|
ret = osd_device->ops.enable_layer(osd_device,
|
|
layer->layer_info.id, 0);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in enabling osd window layer 0\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Enable the window */
|
|
layer->layer_info.enable = 1;
|
|
if (cfg->pixfmt == PIXFMT_NV12) {
|
|
struct vpbe_layer *otherlayer =
|
|
_vpbe_display_get_other_win_layer(disp_dev, layer);
|
|
|
|
ret = osd_device->ops.enable_layer(osd_device,
|
|
otherlayer->layer_info.id, 1);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in enabling osd window layer 1\n");
|
|
return -1;
|
|
}
|
|
otherlayer->layer_info.enable = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
vpbe_disp_calculate_scale_factor(struct vpbe_display *disp_dev,
|
|
struct vpbe_layer *layer,
|
|
int expected_xsize, int expected_ysize)
|
|
{
|
|
struct display_layer_info *layer_info = &layer->layer_info;
|
|
struct v4l2_pix_format *pixfmt = &layer->pix_fmt;
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
int calculated_xsize;
|
|
int h_exp = 0;
|
|
int v_exp = 0;
|
|
int h_scale;
|
|
int v_scale;
|
|
|
|
v4l2_std_id standard_id = vpbe_dev->current_timings.std_id;
|
|
|
|
/*
|
|
* Application initially set the image format. Current display
|
|
* size is obtained from the vpbe display controller. expected_xsize
|
|
* and expected_ysize are set through S_SELECTION ioctl. Based on this,
|
|
* driver will calculate the scale factors for vertical and
|
|
* horizontal direction so that the image is displayed scaled
|
|
* and expanded. Application uses expansion to display the image
|
|
* in a square pixel. Otherwise it is displayed using displays
|
|
* pixel aspect ratio.It is expected that application chooses
|
|
* the crop coordinates for cropped or scaled display. if crop
|
|
* size is less than the image size, it is displayed cropped or
|
|
* it is displayed scaled and/or expanded.
|
|
*
|
|
* to begin with, set the crop window same as expected. Later we
|
|
* will override with scaled window size
|
|
*/
|
|
|
|
cfg->xsize = pixfmt->width;
|
|
cfg->ysize = pixfmt->height;
|
|
layer_info->h_zoom = ZOOM_X1; /* no horizontal zoom */
|
|
layer_info->v_zoom = ZOOM_X1; /* no horizontal zoom */
|
|
layer_info->h_exp = H_EXP_OFF; /* no horizontal zoom */
|
|
layer_info->v_exp = V_EXP_OFF; /* no horizontal zoom */
|
|
|
|
if (pixfmt->width < expected_xsize) {
|
|
h_scale = vpbe_dev->current_timings.xres / pixfmt->width;
|
|
if (h_scale < 2)
|
|
h_scale = 1;
|
|
else if (h_scale >= 4)
|
|
h_scale = 4;
|
|
else
|
|
h_scale = 2;
|
|
cfg->xsize *= h_scale;
|
|
if (cfg->xsize < expected_xsize) {
|
|
if ((standard_id & V4L2_STD_525_60) ||
|
|
(standard_id & V4L2_STD_625_50)) {
|
|
calculated_xsize = (cfg->xsize *
|
|
VPBE_DISPLAY_H_EXP_RATIO_N) /
|
|
VPBE_DISPLAY_H_EXP_RATIO_D;
|
|
if (calculated_xsize <= expected_xsize) {
|
|
h_exp = 1;
|
|
cfg->xsize = calculated_xsize;
|
|
}
|
|
}
|
|
}
|
|
if (h_scale == 2)
|
|
layer_info->h_zoom = ZOOM_X2;
|
|
else if (h_scale == 4)
|
|
layer_info->h_zoom = ZOOM_X4;
|
|
if (h_exp)
|
|
layer_info->h_exp = H_EXP_9_OVER_8;
|
|
} else {
|
|
/* no scaling, only cropping. Set display area to crop area */
|
|
cfg->xsize = expected_xsize;
|
|
}
|
|
|
|
if (pixfmt->height < expected_ysize) {
|
|
v_scale = expected_ysize / pixfmt->height;
|
|
if (v_scale < 2)
|
|
v_scale = 1;
|
|
else if (v_scale >= 4)
|
|
v_scale = 4;
|
|
else
|
|
v_scale = 2;
|
|
cfg->ysize *= v_scale;
|
|
if (cfg->ysize < expected_ysize) {
|
|
if ((standard_id & V4L2_STD_625_50)) {
|
|
calculated_xsize = (cfg->ysize *
|
|
VPBE_DISPLAY_V_EXP_RATIO_N) /
|
|
VPBE_DISPLAY_V_EXP_RATIO_D;
|
|
if (calculated_xsize <= expected_ysize) {
|
|
v_exp = 1;
|
|
cfg->ysize = calculated_xsize;
|
|
}
|
|
}
|
|
}
|
|
if (v_scale == 2)
|
|
layer_info->v_zoom = ZOOM_X2;
|
|
else if (v_scale == 4)
|
|
layer_info->v_zoom = ZOOM_X4;
|
|
if (v_exp)
|
|
layer_info->v_exp = V_EXP_6_OVER_5;
|
|
} else {
|
|
/* no scaling, only cropping. Set display area to crop area */
|
|
cfg->ysize = expected_ysize;
|
|
}
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"crop display xsize = %d, ysize = %d\n",
|
|
cfg->xsize, cfg->ysize);
|
|
}
|
|
|
|
static void vpbe_disp_adj_position(struct vpbe_display *disp_dev,
|
|
struct vpbe_layer *layer,
|
|
int top, int left)
|
|
{
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
|
|
cfg->xpos = min((unsigned int)left,
|
|
vpbe_dev->current_timings.xres - cfg->xsize);
|
|
cfg->ypos = min((unsigned int)top,
|
|
vpbe_dev->current_timings.yres - cfg->ysize);
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"new xpos = %d, ypos = %d\n",
|
|
cfg->xpos, cfg->ypos);
|
|
}
|
|
|
|
static void vpbe_disp_check_window_params(struct vpbe_display *disp_dev,
|
|
struct v4l2_rect *c)
|
|
{
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
|
|
if ((c->width == 0) ||
|
|
((c->width + c->left) > vpbe_dev->current_timings.xres))
|
|
c->width = vpbe_dev->current_timings.xres - c->left;
|
|
|
|
if ((c->height == 0) || ((c->height + c->top) >
|
|
vpbe_dev->current_timings.yres))
|
|
c->height = vpbe_dev->current_timings.yres - c->top;
|
|
|
|
/* window height must be even for interlaced display */
|
|
if (vpbe_dev->current_timings.interlaced)
|
|
c->height &= (~0x01);
|
|
|
|
}
|
|
|
|
/*
|
|
* vpbe_try_format()
|
|
* If user application provides width and height, and have bytesperline set
|
|
* to zero, driver calculates bytesperline and sizeimage based on hardware
|
|
* limits.
|
|
*/
|
|
static int vpbe_try_format(struct vpbe_display *disp_dev,
|
|
struct v4l2_pix_format *pixfmt, int check)
|
|
{
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
int min_height = 1;
|
|
int min_width = 32;
|
|
int max_height;
|
|
int max_width;
|
|
int bpp;
|
|
|
|
if ((pixfmt->pixelformat != V4L2_PIX_FMT_UYVY) &&
|
|
(pixfmt->pixelformat != V4L2_PIX_FMT_NV12))
|
|
/* choose default as V4L2_PIX_FMT_UYVY */
|
|
pixfmt->pixelformat = V4L2_PIX_FMT_UYVY;
|
|
|
|
/* Check the field format */
|
|
if ((pixfmt->field != V4L2_FIELD_INTERLACED) &&
|
|
(pixfmt->field != V4L2_FIELD_NONE)) {
|
|
if (vpbe_dev->current_timings.interlaced)
|
|
pixfmt->field = V4L2_FIELD_INTERLACED;
|
|
else
|
|
pixfmt->field = V4L2_FIELD_NONE;
|
|
}
|
|
|
|
if (pixfmt->field == V4L2_FIELD_INTERLACED)
|
|
min_height = 2;
|
|
|
|
if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12)
|
|
bpp = 1;
|
|
else
|
|
bpp = 2;
|
|
|
|
max_width = vpbe_dev->current_timings.xres;
|
|
max_height = vpbe_dev->current_timings.yres;
|
|
|
|
min_width /= bpp;
|
|
|
|
if (!pixfmt->width || (pixfmt->width < min_width) ||
|
|
(pixfmt->width > max_width)) {
|
|
pixfmt->width = vpbe_dev->current_timings.xres;
|
|
}
|
|
|
|
if (!pixfmt->height || (pixfmt->height < min_height) ||
|
|
(pixfmt->height > max_height)) {
|
|
pixfmt->height = vpbe_dev->current_timings.yres;
|
|
}
|
|
|
|
if (pixfmt->bytesperline < (pixfmt->width * bpp))
|
|
pixfmt->bytesperline = pixfmt->width * bpp;
|
|
|
|
/* Make the bytesperline 32 byte aligned */
|
|
pixfmt->bytesperline = ((pixfmt->width * bpp + 31) & ~31);
|
|
|
|
if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12)
|
|
pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height +
|
|
(pixfmt->bytesperline * pixfmt->height >> 1);
|
|
else
|
|
pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
snprintf(cap->driver, sizeof(cap->driver), "%s",
|
|
dev_name(vpbe_dev->pdev));
|
|
snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
|
|
dev_name(vpbe_dev->pdev));
|
|
strscpy(cap->card, vpbe_dev->cfg->module_name, sizeof(cap->card));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_s_selection(struct file *file, void *priv,
|
|
struct v4l2_selection *sel)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_display *disp_dev = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct osd_state *osd_device = disp_dev->osd_device;
|
|
struct v4l2_rect rect = sel->r;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"VIDIOC_S_SELECTION, layer id = %d\n", layer->device_id);
|
|
|
|
if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT ||
|
|
sel->target != V4L2_SEL_TGT_CROP)
|
|
return -EINVAL;
|
|
|
|
if (rect.top < 0)
|
|
rect.top = 0;
|
|
if (rect.left < 0)
|
|
rect.left = 0;
|
|
|
|
vpbe_disp_check_window_params(disp_dev, &rect);
|
|
|
|
osd_device->ops.get_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
|
|
vpbe_disp_calculate_scale_factor(disp_dev, layer,
|
|
rect.width,
|
|
rect.height);
|
|
vpbe_disp_adj_position(disp_dev, layer, rect.top,
|
|
rect.left);
|
|
ret = osd_device->ops.set_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in set layer config:\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* apply zooming and h or v expansion */
|
|
osd_device->ops.set_zoom(osd_device,
|
|
layer->layer_info.id,
|
|
layer->layer_info.h_zoom,
|
|
layer->layer_info.v_zoom);
|
|
ret = osd_device->ops.set_vid_expansion(osd_device,
|
|
layer->layer_info.h_exp,
|
|
layer->layer_info.v_exp);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in set vid expansion:\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((layer->layer_info.h_zoom != ZOOM_X1) ||
|
|
(layer->layer_info.v_zoom != ZOOM_X1) ||
|
|
(layer->layer_info.h_exp != H_EXP_OFF) ||
|
|
(layer->layer_info.v_exp != V_EXP_OFF))
|
|
/* Enable expansion filter */
|
|
osd_device->ops.set_interpolation_filter(osd_device, 1);
|
|
else
|
|
osd_device->ops.set_interpolation_filter(osd_device, 0);
|
|
|
|
sel->r = rect;
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_g_selection(struct file *file, void *priv,
|
|
struct v4l2_selection *sel)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
struct osd_state *osd_device = layer->disp_dev->osd_device;
|
|
struct v4l2_rect *rect = &sel->r;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"VIDIOC_G_SELECTION, layer id = %d\n",
|
|
layer->device_id);
|
|
|
|
if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
switch (sel->target) {
|
|
case V4L2_SEL_TGT_CROP:
|
|
osd_device->ops.get_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
rect->top = cfg->ypos;
|
|
rect->left = cfg->xpos;
|
|
rect->width = cfg->xsize;
|
|
rect->height = cfg->ysize;
|
|
break;
|
|
case V4L2_SEL_TGT_CROP_DEFAULT:
|
|
case V4L2_SEL_TGT_CROP_BOUNDS:
|
|
rect->left = 0;
|
|
rect->top = 0;
|
|
rect->width = vpbe_dev->current_timings.xres;
|
|
rect->height = vpbe_dev->current_timings.yres;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_g_pixelaspect(struct file *file, void *priv,
|
|
int type, struct v4l2_fract *f)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_CROPCAP ioctl\n");
|
|
|
|
if (type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
*f = vpbe_dev->current_timings.aspect;
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_g_fmt(struct file *file, void *priv,
|
|
struct v4l2_format *fmt)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"VIDIOC_G_FMT, layer id = %d\n",
|
|
layer->device_id);
|
|
|
|
/* If buffer type is video output */
|
|
if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n");
|
|
return -EINVAL;
|
|
}
|
|
/* Fill in the information about format */
|
|
fmt->fmt.pix = layer->pix_fmt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_enum_fmt(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *fmt)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"VIDIOC_ENUM_FMT, layer id = %d\n",
|
|
layer->device_id);
|
|
if (fmt->index > 1) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev, "Invalid format index\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Fill in the information about format */
|
|
if (fmt->index == 0)
|
|
fmt->pixelformat = V4L2_PIX_FMT_UYVY;
|
|
else
|
|
fmt->pixelformat = V4L2_PIX_FMT_NV12;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_s_fmt(struct file *file, void *priv,
|
|
struct v4l2_format *fmt)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_display *disp_dev = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct v4l2_pix_format *pixfmt = &fmt->fmt.pix;
|
|
struct osd_state *osd_device = disp_dev->osd_device;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"VIDIOC_S_FMT, layer id = %d\n",
|
|
layer->device_id);
|
|
|
|
if (vb2_is_busy(&layer->buffer_queue))
|
|
return -EBUSY;
|
|
|
|
if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) {
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "invalid type\n");
|
|
return -EINVAL;
|
|
}
|
|
/* Check for valid pixel format */
|
|
ret = vpbe_try_format(disp_dev, pixfmt, 1);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* YUV420 is requested, check availability of the
|
|
other video window */
|
|
|
|
layer->pix_fmt = *pixfmt;
|
|
if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) {
|
|
struct vpbe_layer *otherlayer;
|
|
|
|
otherlayer = _vpbe_display_get_other_win_layer(disp_dev, layer);
|
|
/* if other layer is available, only
|
|
* claim it, do not configure it
|
|
*/
|
|
ret = osd_device->ops.request_layer(osd_device,
|
|
otherlayer->layer_info.id);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Display Manager failed to allocate layer\n");
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
/* Get osd layer config */
|
|
osd_device->ops.get_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
/* Store the pixel format in the layer object */
|
|
cfg->xsize = pixfmt->width;
|
|
cfg->ysize = pixfmt->height;
|
|
cfg->line_length = pixfmt->bytesperline;
|
|
cfg->ypos = 0;
|
|
cfg->xpos = 0;
|
|
cfg->interlaced = vpbe_dev->current_timings.interlaced;
|
|
|
|
if (V4L2_PIX_FMT_UYVY == pixfmt->pixelformat)
|
|
cfg->pixfmt = PIXFMT_YCBCRI;
|
|
|
|
/* Change of the default pixel format for both video windows */
|
|
if (V4L2_PIX_FMT_NV12 == pixfmt->pixelformat) {
|
|
struct vpbe_layer *otherlayer;
|
|
cfg->pixfmt = PIXFMT_NV12;
|
|
otherlayer = _vpbe_display_get_other_win_layer(disp_dev,
|
|
layer);
|
|
otherlayer->layer_info.config.pixfmt = PIXFMT_NV12;
|
|
}
|
|
|
|
/* Set the layer config in the osd window */
|
|
ret = osd_device->ops.set_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
if (ret < 0) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Error in S_FMT params:\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Readback and fill the local copy of current pix format */
|
|
osd_device->ops.get_layer_config(osd_device,
|
|
layer->layer_info.id, cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vpbe_display_try_fmt(struct file *file, void *priv,
|
|
struct v4l2_format *fmt)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_display *disp_dev = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
struct v4l2_pix_format *pixfmt = &fmt->fmt.pix;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_TRY_FMT\n");
|
|
|
|
if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check for valid field format */
|
|
return vpbe_try_format(disp_dev, pixfmt, 0);
|
|
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_s_std - Set the given standard in the encoder
|
|
*
|
|
* Sets the standard if supported by the current encoder. Return the status.
|
|
* 0 - success & -EINVAL on error
|
|
*/
|
|
static int vpbe_display_s_std(struct file *file, void *priv,
|
|
v4l2_std_id std_id)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_STD\n");
|
|
|
|
if (vb2_is_busy(&layer->buffer_queue))
|
|
return -EBUSY;
|
|
|
|
if (vpbe_dev->ops.s_std) {
|
|
ret = vpbe_dev->ops.s_std(vpbe_dev, std_id);
|
|
if (ret) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Failed to set standard for sub devices\n");
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_g_std - Get the standard in the current encoder
|
|
*
|
|
* Get the standard in the current encoder. Return the status. 0 - success
|
|
* -EINVAL on error
|
|
*/
|
|
static int vpbe_display_g_std(struct file *file, void *priv,
|
|
v4l2_std_id *std_id)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_STD\n");
|
|
|
|
/* Get the standard from the current encoder */
|
|
if (vpbe_dev->current_timings.timings_type & VPBE_ENC_STD) {
|
|
*std_id = vpbe_dev->current_timings.std_id;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_enum_output - enumerate outputs
|
|
*
|
|
* Enumerates the outputs available at the vpbe display
|
|
* returns the status, -EINVAL if end of output list
|
|
*/
|
|
static int vpbe_display_enum_output(struct file *file, void *priv,
|
|
struct v4l2_output *output)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_OUTPUT\n");
|
|
|
|
/* Enumerate outputs */
|
|
if (!vpbe_dev->ops.enum_outputs)
|
|
return -EINVAL;
|
|
|
|
ret = vpbe_dev->ops.enum_outputs(vpbe_dev, output);
|
|
if (ret) {
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"Failed to enumerate outputs\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_s_output - Set output to
|
|
* the output specified by the index
|
|
*/
|
|
static int vpbe_display_s_output(struct file *file, void *priv,
|
|
unsigned int i)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_OUTPUT\n");
|
|
|
|
if (vb2_is_busy(&layer->buffer_queue))
|
|
return -EBUSY;
|
|
|
|
if (!vpbe_dev->ops.set_output)
|
|
return -EINVAL;
|
|
|
|
ret = vpbe_dev->ops.set_output(vpbe_dev, i);
|
|
if (ret) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Failed to set output for sub devices\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_g_output - Get output from subdevice
|
|
* for a given by the index
|
|
*/
|
|
static int vpbe_display_g_output(struct file *file, void *priv,
|
|
unsigned int *i)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_OUTPUT\n");
|
|
/* Get the standard from the current encoder */
|
|
*i = vpbe_dev->current_out_index;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_enum_dv_timings - Enumerate the dv timings
|
|
*
|
|
* enum the timings in the current encoder. Return the status. 0 - success
|
|
* -EINVAL on error
|
|
*/
|
|
static int
|
|
vpbe_display_enum_dv_timings(struct file *file, void *priv,
|
|
struct v4l2_enum_dv_timings *timings)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_DV_TIMINGS\n");
|
|
|
|
/* Enumerate outputs */
|
|
if (!vpbe_dev->ops.enum_dv_timings)
|
|
return -EINVAL;
|
|
|
|
ret = vpbe_dev->ops.enum_dv_timings(vpbe_dev, timings);
|
|
if (ret) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Failed to enumerate dv timings info\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_s_dv_timings - Set the dv timings
|
|
*
|
|
* Set the timings in the current encoder. Return the status. 0 - success
|
|
* -EINVAL on error
|
|
*/
|
|
static int
|
|
vpbe_display_s_dv_timings(struct file *file, void *priv,
|
|
struct v4l2_dv_timings *timings)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
int ret;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_DV_TIMINGS\n");
|
|
|
|
if (vb2_is_busy(&layer->buffer_queue))
|
|
return -EBUSY;
|
|
|
|
/* Set the given standard in the encoder */
|
|
if (!vpbe_dev->ops.s_dv_timings)
|
|
return -EINVAL;
|
|
|
|
ret = vpbe_dev->ops.s_dv_timings(vpbe_dev, timings);
|
|
if (ret) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Failed to set the dv timings info\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_g_dv_timings - Set the dv timings
|
|
*
|
|
* Get the timings in the current encoder. Return the status. 0 - success
|
|
* -EINVAL on error
|
|
*/
|
|
static int
|
|
vpbe_display_g_dv_timings(struct file *file, void *priv,
|
|
struct v4l2_dv_timings *dv_timings)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_DV_TIMINGS\n");
|
|
|
|
/* Get the given standard in the encoder */
|
|
|
|
if (vpbe_dev->current_timings.timings_type &
|
|
VPBE_ENC_DV_TIMINGS) {
|
|
*dv_timings = vpbe_dev->current_timings.dv_timings;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_open()
|
|
* It creates object of file handle structure and stores it in private_data
|
|
* member of filepointer
|
|
*/
|
|
static int vpbe_display_open(struct file *file)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct vpbe_display *disp_dev = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
struct osd_state *osd_device = disp_dev->osd_device;
|
|
int err;
|
|
|
|
/* creating context for file descriptor */
|
|
err = v4l2_fh_open(file);
|
|
if (err) {
|
|
v4l2_err(&vpbe_dev->v4l2_dev, "v4l2_fh_open failed\n");
|
|
return err;
|
|
}
|
|
|
|
/* leaving if layer is already initialized */
|
|
if (!v4l2_fh_is_singular_file(file))
|
|
return err;
|
|
|
|
if (!layer->usrs) {
|
|
if (mutex_lock_interruptible(&layer->opslock))
|
|
return -ERESTARTSYS;
|
|
/* First claim the layer for this device */
|
|
err = osd_device->ops.request_layer(osd_device,
|
|
layer->layer_info.id);
|
|
mutex_unlock(&layer->opslock);
|
|
if (err < 0) {
|
|
/* Couldn't get layer */
|
|
v4l2_err(&vpbe_dev->v4l2_dev,
|
|
"Display Manager failed to allocate layer\n");
|
|
v4l2_fh_release(file);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
/* Increment layer usrs counter */
|
|
layer->usrs++;
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev,
|
|
"vpbe display device opened successfully\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_release()
|
|
* This function deletes buffer queue, frees the buffers and the davinci
|
|
* display file * handle
|
|
*/
|
|
static int vpbe_display_release(struct file *file)
|
|
{
|
|
struct vpbe_layer *layer = video_drvdata(file);
|
|
struct osd_layer_config *cfg = &layer->layer_info.config;
|
|
struct vpbe_display *disp_dev = layer->disp_dev;
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
struct osd_state *osd_device = disp_dev->osd_device;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_release\n");
|
|
|
|
mutex_lock(&layer->opslock);
|
|
|
|
osd_device->ops.disable_layer(osd_device,
|
|
layer->layer_info.id);
|
|
/* Decrement layer usrs counter */
|
|
layer->usrs--;
|
|
/* If this file handle has initialize encoder device, reset it */
|
|
if (!layer->usrs) {
|
|
if (cfg->pixfmt == PIXFMT_NV12) {
|
|
struct vpbe_layer *otherlayer;
|
|
otherlayer =
|
|
_vpbe_display_get_other_win_layer(disp_dev, layer);
|
|
osd_device->ops.disable_layer(osd_device,
|
|
otherlayer->layer_info.id);
|
|
osd_device->ops.release_layer(osd_device,
|
|
otherlayer->layer_info.id);
|
|
}
|
|
osd_device->ops.disable_layer(osd_device,
|
|
layer->layer_info.id);
|
|
osd_device->ops.release_layer(osd_device,
|
|
layer->layer_info.id);
|
|
}
|
|
|
|
_vb2_fop_release(file, NULL);
|
|
mutex_unlock(&layer->opslock);
|
|
|
|
disp_dev->cbcr_ofst = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* vpbe capture ioctl operations */
|
|
static const struct v4l2_ioctl_ops vpbe_ioctl_ops = {
|
|
.vidioc_querycap = vpbe_display_querycap,
|
|
.vidioc_g_fmt_vid_out = vpbe_display_g_fmt,
|
|
.vidioc_enum_fmt_vid_out = vpbe_display_enum_fmt,
|
|
.vidioc_s_fmt_vid_out = vpbe_display_s_fmt,
|
|
.vidioc_try_fmt_vid_out = vpbe_display_try_fmt,
|
|
|
|
.vidioc_reqbufs = vb2_ioctl_reqbufs,
|
|
.vidioc_create_bufs = vb2_ioctl_create_bufs,
|
|
.vidioc_querybuf = vb2_ioctl_querybuf,
|
|
.vidioc_qbuf = vb2_ioctl_qbuf,
|
|
.vidioc_dqbuf = vb2_ioctl_dqbuf,
|
|
.vidioc_streamon = vb2_ioctl_streamon,
|
|
.vidioc_streamoff = vb2_ioctl_streamoff,
|
|
.vidioc_expbuf = vb2_ioctl_expbuf,
|
|
|
|
.vidioc_g_pixelaspect = vpbe_display_g_pixelaspect,
|
|
.vidioc_g_selection = vpbe_display_g_selection,
|
|
.vidioc_s_selection = vpbe_display_s_selection,
|
|
|
|
.vidioc_s_std = vpbe_display_s_std,
|
|
.vidioc_g_std = vpbe_display_g_std,
|
|
|
|
.vidioc_enum_output = vpbe_display_enum_output,
|
|
.vidioc_s_output = vpbe_display_s_output,
|
|
.vidioc_g_output = vpbe_display_g_output,
|
|
|
|
.vidioc_s_dv_timings = vpbe_display_s_dv_timings,
|
|
.vidioc_g_dv_timings = vpbe_display_g_dv_timings,
|
|
.vidioc_enum_dv_timings = vpbe_display_enum_dv_timings,
|
|
};
|
|
|
|
static const struct v4l2_file_operations vpbe_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = vpbe_display_open,
|
|
.release = vpbe_display_release,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = vb2_fop_mmap,
|
|
.poll = vb2_fop_poll,
|
|
};
|
|
|
|
static int vpbe_device_get(struct device *dev, void *data)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct vpbe_display *vpbe_disp = data;
|
|
|
|
if (strcmp("vpbe_controller", pdev->name) == 0)
|
|
vpbe_disp->vpbe_dev = platform_get_drvdata(pdev);
|
|
|
|
if (strstr(pdev->name, "vpbe-osd"))
|
|
vpbe_disp->osd_device = platform_get_drvdata(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int init_vpbe_layer(int i, struct vpbe_display *disp_dev,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct vpbe_layer *vpbe_display_layer = NULL;
|
|
struct video_device *vbd = NULL;
|
|
|
|
/* Allocate memory for four plane display objects */
|
|
disp_dev->dev[i] = kzalloc(sizeof(*disp_dev->dev[i]), GFP_KERNEL);
|
|
if (!disp_dev->dev[i])
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&disp_dev->dev[i]->irqlock);
|
|
mutex_init(&disp_dev->dev[i]->opslock);
|
|
|
|
/* Get the pointer to the layer object */
|
|
vpbe_display_layer = disp_dev->dev[i];
|
|
vbd = &vpbe_display_layer->video_dev;
|
|
/* Initialize field of video device */
|
|
vbd->release = video_device_release_empty;
|
|
vbd->fops = &vpbe_fops;
|
|
vbd->ioctl_ops = &vpbe_ioctl_ops;
|
|
vbd->minor = -1;
|
|
vbd->v4l2_dev = &disp_dev->vpbe_dev->v4l2_dev;
|
|
vbd->lock = &vpbe_display_layer->opslock;
|
|
vbd->vfl_dir = VFL_DIR_TX;
|
|
vbd->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
|
|
|
|
if (disp_dev->vpbe_dev->current_timings.timings_type &
|
|
VPBE_ENC_STD)
|
|
vbd->tvnorms = (V4L2_STD_525_60 | V4L2_STD_625_50);
|
|
|
|
snprintf(vbd->name, sizeof(vbd->name),
|
|
"DaVinci_VPBE Display_DRIVER_V%d.%d.%d",
|
|
(VPBE_DISPLAY_VERSION_CODE >> 16) & 0xff,
|
|
(VPBE_DISPLAY_VERSION_CODE >> 8) & 0xff,
|
|
(VPBE_DISPLAY_VERSION_CODE) & 0xff);
|
|
|
|
vpbe_display_layer->device_id = i;
|
|
|
|
vpbe_display_layer->layer_info.id =
|
|
((i == VPBE_DISPLAY_DEVICE_0) ? WIN_VID0 : WIN_VID1);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int register_device(struct vpbe_layer *vpbe_display_layer,
|
|
struct vpbe_display *disp_dev,
|
|
struct platform_device *pdev)
|
|
{
|
|
int err;
|
|
|
|
v4l2_info(&disp_dev->vpbe_dev->v4l2_dev,
|
|
"Trying to register VPBE display device.\n");
|
|
v4l2_info(&disp_dev->vpbe_dev->v4l2_dev,
|
|
"layer=%p,layer->video_dev=%p\n",
|
|
vpbe_display_layer,
|
|
&vpbe_display_layer->video_dev);
|
|
|
|
vpbe_display_layer->video_dev.queue = &vpbe_display_layer->buffer_queue;
|
|
err = video_register_device(&vpbe_display_layer->video_dev,
|
|
VFL_TYPE_VIDEO,
|
|
-1);
|
|
if (err)
|
|
return -ENODEV;
|
|
|
|
vpbe_display_layer->disp_dev = disp_dev;
|
|
/* set the driver data in platform device */
|
|
platform_set_drvdata(pdev, disp_dev);
|
|
video_set_drvdata(&vpbe_display_layer->video_dev,
|
|
vpbe_display_layer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* vpbe_display_probe()
|
|
* This function creates device entries by register itself to the V4L2 driver
|
|
* and initializes fields of each layer objects
|
|
*/
|
|
static int vpbe_display_probe(struct platform_device *pdev)
|
|
{
|
|
struct vpbe_display *disp_dev;
|
|
struct v4l2_device *v4l2_dev;
|
|
struct resource *res = NULL;
|
|
struct vb2_queue *q;
|
|
int k;
|
|
int i;
|
|
int err;
|
|
int irq;
|
|
|
|
printk(KERN_DEBUG "vpbe_display_probe\n");
|
|
/* Allocate memory for vpbe_display */
|
|
disp_dev = devm_kzalloc(&pdev->dev, sizeof(*disp_dev), GFP_KERNEL);
|
|
if (!disp_dev)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&disp_dev->dma_queue_lock);
|
|
/*
|
|
* Scan all the platform devices to find the vpbe
|
|
* controller device and get the vpbe_dev object
|
|
*/
|
|
err = bus_for_each_dev(&platform_bus_type, NULL, disp_dev,
|
|
vpbe_device_get);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
v4l2_dev = &disp_dev->vpbe_dev->v4l2_dev;
|
|
/* Initialize the vpbe display controller */
|
|
if (disp_dev->vpbe_dev->ops.initialize) {
|
|
err = disp_dev->vpbe_dev->ops.initialize(&pdev->dev,
|
|
disp_dev->vpbe_dev);
|
|
if (err) {
|
|
v4l2_err(v4l2_dev, "Error initing vpbe\n");
|
|
err = -ENOMEM;
|
|
goto probe_out;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) {
|
|
if (init_vpbe_layer(i, disp_dev, pdev)) {
|
|
err = -ENODEV;
|
|
goto probe_out;
|
|
}
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!res) {
|
|
v4l2_err(v4l2_dev, "Unable to get VENC interrupt resource\n");
|
|
err = -ENODEV;
|
|
goto probe_out;
|
|
}
|
|
|
|
irq = res->start;
|
|
err = devm_request_irq(&pdev->dev, irq, venc_isr, 0,
|
|
VPBE_DISPLAY_DRIVER, disp_dev);
|
|
if (err) {
|
|
v4l2_err(v4l2_dev, "VPBE IRQ request failed\n");
|
|
goto probe_out;
|
|
}
|
|
|
|
for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) {
|
|
/* initialize vb2 queue */
|
|
q = &disp_dev->dev[i]->buffer_queue;
|
|
memset(q, 0, sizeof(*q));
|
|
q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
|
|
q->drv_priv = disp_dev->dev[i];
|
|
q->ops = &video_qops;
|
|
q->mem_ops = &vb2_dma_contig_memops;
|
|
q->buf_struct_size = sizeof(struct vpbe_disp_buffer);
|
|
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
|
q->min_buffers_needed = 1;
|
|
q->lock = &disp_dev->dev[i]->opslock;
|
|
q->dev = disp_dev->vpbe_dev->pdev;
|
|
err = vb2_queue_init(q);
|
|
if (err) {
|
|
v4l2_err(v4l2_dev, "vb2_queue_init() failed\n");
|
|
goto probe_out;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&disp_dev->dev[i]->dma_queue);
|
|
|
|
if (register_device(disp_dev->dev[i], disp_dev, pdev)) {
|
|
err = -ENODEV;
|
|
goto probe_out;
|
|
}
|
|
}
|
|
|
|
v4l2_dbg(1, debug, v4l2_dev,
|
|
"Successfully completed the probing of vpbe v4l2 device\n");
|
|
|
|
return 0;
|
|
|
|
probe_out:
|
|
for (k = 0; k < VPBE_DISPLAY_MAX_DEVICES; k++) {
|
|
/* Unregister video device */
|
|
if (disp_dev->dev[k]) {
|
|
video_unregister_device(&disp_dev->dev[k]->video_dev);
|
|
kfree(disp_dev->dev[k]);
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* vpbe_display_remove()
|
|
* It un-register hardware layer from V4L2 driver
|
|
*/
|
|
static int vpbe_display_remove(struct platform_device *pdev)
|
|
{
|
|
struct vpbe_layer *vpbe_display_layer;
|
|
struct vpbe_display *disp_dev = platform_get_drvdata(pdev);
|
|
struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev;
|
|
int i;
|
|
|
|
v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_remove\n");
|
|
|
|
/* deinitialize the vpbe display controller */
|
|
if (vpbe_dev->ops.deinitialize)
|
|
vpbe_dev->ops.deinitialize(&pdev->dev, vpbe_dev);
|
|
/* un-register device */
|
|
for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) {
|
|
/* Get the pointer to the layer object */
|
|
vpbe_display_layer = disp_dev->dev[i];
|
|
/* Unregister video device */
|
|
video_unregister_device(&vpbe_display_layer->video_dev);
|
|
|
|
}
|
|
for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) {
|
|
kfree(disp_dev->dev[i]);
|
|
disp_dev->dev[i] = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver vpbe_display_driver = {
|
|
.driver = {
|
|
.name = VPBE_DISPLAY_DRIVER,
|
|
.bus = &platform_bus_type,
|
|
},
|
|
.probe = vpbe_display_probe,
|
|
.remove = vpbe_display_remove,
|
|
};
|
|
|
|
module_platform_driver(vpbe_display_driver);
|
|
|
|
MODULE_DESCRIPTION("TI DM644x/DM355/DM365 VPBE Display controller");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Texas Instruments");
|