2296 lines
59 KiB
C
2296 lines
59 KiB
C
/*
|
|
* Copyright (C) 2011-2015 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* The code contained herein is licensed under the GNU General Public
|
|
* License. You may obtain a copy of the GNU General Public License
|
|
* Version 2 or later at the following locations:
|
|
*
|
|
* http://www.opensource.org/licenses/gpl-license.html
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
|
|
#include <linux/console.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ipu-v3.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mxcfb.h>
|
|
#include <linux/mxc_v4l2.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/types.h>
|
|
#include <linux/videodev2.h>
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <media/videobuf-dma-contig.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-ioctl.h>
|
|
|
|
#define UYVY_BLACK (0x00800080)
|
|
#define RGB_BLACK (0x0)
|
|
#define UV_BLACK (0x80)
|
|
#define Y_BLACK (0x0)
|
|
|
|
#define MAX_FB_NUM 6
|
|
#define FB_BUFS 3
|
|
#define VDOA_FB_BUFS (FB_BUFS - 1)
|
|
#define VALID_HEIGHT_1080P (1080)
|
|
#define FRAME_HEIGHT_1080P (1088)
|
|
#define FRAME_WIDTH_1080P (1920)
|
|
#define CHECK_TILED_1080P_DISPLAY(vout) \
|
|
((((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12) || \
|
|
((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12F)) &&\
|
|
((vout)->task.input.width == FRAME_WIDTH_1080P) && \
|
|
((vout)->task.input.height == FRAME_HEIGHT_1080P) && \
|
|
((vout)->task.input.crop.w == FRAME_WIDTH_1080P) && \
|
|
(((vout)->task.input.crop.h == FRAME_HEIGHT_1080P) || \
|
|
((vout)->task.input.crop.h == VALID_HEIGHT_1080P)) && \
|
|
((vout)->task.output.width == FRAME_WIDTH_1080P) && \
|
|
((vout)->task.output.height == VALID_HEIGHT_1080P) && \
|
|
((vout)->task.output.crop.w == FRAME_WIDTH_1080P) && \
|
|
((vout)->task.output.crop.h == VALID_HEIGHT_1080P))
|
|
#define CHECK_TILED_1080P_STREAM(vout) \
|
|
((((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12) || \
|
|
((vout)->task.input.format == IPU_PIX_FMT_TILED_NV12F)) &&\
|
|
((vout)->task.input.width == FRAME_WIDTH_1080P) && \
|
|
((vout)->task.input.crop.w == FRAME_WIDTH_1080P) && \
|
|
((vout)->task.input.height == FRAME_HEIGHT_1080P) && \
|
|
((vout)->task.input.crop.h == FRAME_HEIGHT_1080P))
|
|
#define IS_PLANAR_PIXEL_FORMAT(format) \
|
|
(format == IPU_PIX_FMT_NV12 || \
|
|
format == IPU_PIX_FMT_YUV420P2 || \
|
|
format == IPU_PIX_FMT_YUV420P || \
|
|
format == IPU_PIX_FMT_YVU420P || \
|
|
format == IPU_PIX_FMT_YUV422P || \
|
|
format == IPU_PIX_FMT_YVU422P || \
|
|
format == IPU_PIX_FMT_YUV444P)
|
|
|
|
#define NSEC_PER_FRAME_30FPS (33333333)
|
|
|
|
struct mxc_vout_fb {
|
|
char *name;
|
|
int ipu_id;
|
|
struct v4l2_rect crop_bounds;
|
|
unsigned int disp_fmt;
|
|
bool disp_support_csc;
|
|
bool disp_support_windows;
|
|
};
|
|
|
|
struct dma_mem {
|
|
void *vaddr;
|
|
dma_addr_t paddr;
|
|
size_t size;
|
|
};
|
|
|
|
struct mxc_vout_output {
|
|
int open_cnt;
|
|
struct fb_info *fbi;
|
|
unsigned long fb_smem_start;
|
|
unsigned long fb_smem_len;
|
|
struct video_device *vfd;
|
|
struct mutex mutex;
|
|
struct mutex task_lock;
|
|
struct mutex accs_lock;
|
|
enum v4l2_buf_type type;
|
|
|
|
struct videobuf_queue vbq;
|
|
spinlock_t vbq_lock;
|
|
|
|
struct list_head queue_list;
|
|
struct list_head active_list;
|
|
|
|
struct v4l2_rect crop_bounds;
|
|
unsigned int disp_fmt;
|
|
struct mxcfb_pos win_pos;
|
|
bool disp_support_windows;
|
|
bool disp_support_csc;
|
|
|
|
bool fmt_init;
|
|
bool release;
|
|
bool linear_bypass_pp;
|
|
bool vdoa_1080p;
|
|
bool tiled_bypass_pp;
|
|
struct v4l2_rect in_rect;
|
|
struct ipu_task task;
|
|
struct ipu_task vdoa_task;
|
|
struct dma_mem vdoa_work;
|
|
struct dma_mem vdoa_output[VDOA_FB_BUFS];
|
|
|
|
bool timer_stop;
|
|
struct hrtimer timer;
|
|
struct workqueue_struct *v4l_wq;
|
|
struct work_struct disp_work;
|
|
unsigned long frame_count;
|
|
unsigned long vdi_frame_cnt;
|
|
ktime_t start_ktime;
|
|
|
|
int ctrl_rotate;
|
|
int ctrl_vflip;
|
|
int ctrl_hflip;
|
|
|
|
dma_addr_t disp_bufs[FB_BUFS];
|
|
|
|
struct videobuf_buffer *pre1_vb;
|
|
struct videobuf_buffer *pre2_vb;
|
|
};
|
|
|
|
struct mxc_vout_dev {
|
|
struct device *dev;
|
|
struct v4l2_device v4l2_dev;
|
|
struct mxc_vout_output *out[MAX_FB_NUM];
|
|
int out_num;
|
|
};
|
|
|
|
/* Driver Configuration macros */
|
|
#define VOUT_NAME "mxc_vout"
|
|
|
|
/* Variables configurable through module params*/
|
|
static int debug;
|
|
static int vdi_rate_double;
|
|
static int video_nr = 16;
|
|
|
|
/* Module parameters */
|
|
module_param(video_nr, int, S_IRUGO);
|
|
MODULE_PARM_DESC(video_nr, "video device numbers");
|
|
module_param(debug, int, 0600);
|
|
MODULE_PARM_DESC(debug, "Debug level (0-1)");
|
|
module_param(vdi_rate_double, int, 0600);
|
|
MODULE_PARM_DESC(vdi_rate_double, "vdi frame rate double on/off");
|
|
|
|
static const struct v4l2_fmtdesc mxc_formats[] = {
|
|
{
|
|
.description = "RGB565",
|
|
.pixelformat = V4L2_PIX_FMT_RGB565,
|
|
},
|
|
{
|
|
.description = "BGR24",
|
|
.pixelformat = V4L2_PIX_FMT_BGR24,
|
|
},
|
|
{
|
|
.description = "RGB24",
|
|
.pixelformat = V4L2_PIX_FMT_RGB24,
|
|
},
|
|
{
|
|
.description = "RGB32",
|
|
.pixelformat = V4L2_PIX_FMT_RGB32,
|
|
},
|
|
{
|
|
.description = "BGR32",
|
|
.pixelformat = V4L2_PIX_FMT_BGR32,
|
|
},
|
|
{
|
|
.description = "NV12",
|
|
.pixelformat = V4L2_PIX_FMT_NV12,
|
|
},
|
|
{
|
|
.description = "UYVY",
|
|
.pixelformat = V4L2_PIX_FMT_UYVY,
|
|
},
|
|
{
|
|
.description = "YUYV",
|
|
.pixelformat = V4L2_PIX_FMT_YUYV,
|
|
},
|
|
{
|
|
.description = "YUV422 planar",
|
|
.pixelformat = V4L2_PIX_FMT_YUV422P,
|
|
},
|
|
{
|
|
.description = "YUV444",
|
|
.pixelformat = V4L2_PIX_FMT_YUV444,
|
|
},
|
|
{
|
|
.description = "YUV420",
|
|
.pixelformat = V4L2_PIX_FMT_YUV420,
|
|
},
|
|
{
|
|
.description = "YVU420",
|
|
.pixelformat = V4L2_PIX_FMT_YVU420,
|
|
},
|
|
{
|
|
.description = "TILED NV12P",
|
|
.pixelformat = IPU_PIX_FMT_TILED_NV12,
|
|
},
|
|
{
|
|
.description = "TILED NV12F",
|
|
.pixelformat = IPU_PIX_FMT_TILED_NV12F,
|
|
},
|
|
{
|
|
.description = "YUV444 planar",
|
|
.pixelformat = IPU_PIX_FMT_YUV444P,
|
|
},
|
|
};
|
|
|
|
#define NUM_MXC_VOUT_FORMATS (ARRAY_SIZE(mxc_formats))
|
|
|
|
#define DEF_INPUT_WIDTH 320
|
|
#define DEF_INPUT_HEIGHT 240
|
|
|
|
static int mxc_vidioc_streamoff(struct file *file, void *fh,
|
|
enum v4l2_buf_type i);
|
|
|
|
static struct mxc_vout_fb g_fb_setting[MAX_FB_NUM];
|
|
static int config_disp_output(struct mxc_vout_output *vout);
|
|
static void release_disp_output(struct mxc_vout_output *vout);
|
|
|
|
static DEFINE_MUTEX(gfb_mutex);
|
|
static DEFINE_MUTEX(gfbi_mutex);
|
|
|
|
static unsigned int get_frame_size(struct mxc_vout_output *vout)
|
|
{
|
|
unsigned int size;
|
|
|
|
if (IPU_PIX_FMT_TILED_NV12 == vout->task.input.format)
|
|
size = TILED_NV12_FRAME_SIZE(vout->task.input.width,
|
|
vout->task.input.height);
|
|
else if (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format) {
|
|
size = TILED_NV12_FRAME_SIZE(vout->task.input.width,
|
|
vout->task.input.height/2);
|
|
size *= 2;
|
|
} else
|
|
size = vout->task.input.width * vout->task.input.height *
|
|
fmt_to_bpp(vout->task.input.format)/8;
|
|
|
|
return size;
|
|
}
|
|
|
|
static void free_dma_buf(struct mxc_vout_output *vout, struct dma_mem *buf)
|
|
{
|
|
dma_free_coherent(vout->vbq.dev, buf->size, buf->vaddr, buf->paddr);
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"free dma size:0x%x, paddr:0x%x\n",
|
|
buf->size, buf->paddr);
|
|
memset(buf, 0, sizeof(*buf));
|
|
}
|
|
|
|
static int alloc_dma_buf(struct mxc_vout_output *vout, struct dma_mem *buf)
|
|
{
|
|
|
|
buf->vaddr = dma_alloc_coherent(vout->vbq.dev, buf->size, &buf->paddr,
|
|
GFP_DMA | GFP_KERNEL);
|
|
if (!buf->vaddr) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"cannot get dma buf size:0x%x\n", buf->size);
|
|
return -ENOMEM;
|
|
}
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"alloc dma buf size:0x%x, paddr:0x%x\n", buf->size, buf->paddr);
|
|
return 0;
|
|
}
|
|
|
|
static ipu_channel_t get_ipu_channel(struct fb_info *fbi)
|
|
{
|
|
ipu_channel_t ipu_ch = CHAN_NONE;
|
|
mm_segment_t old_fs;
|
|
|
|
if (fbi->fbops->fb_ioctl) {
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
fbi->fbops->fb_ioctl(fbi, MXCFB_GET_FB_IPU_CHAN,
|
|
(unsigned long)&ipu_ch);
|
|
set_fs(old_fs);
|
|
}
|
|
|
|
return ipu_ch;
|
|
}
|
|
|
|
static unsigned int get_ipu_fmt(struct fb_info *fbi)
|
|
{
|
|
mm_segment_t old_fs;
|
|
unsigned int fb_fmt;
|
|
|
|
if (fbi->fbops->fb_ioctl) {
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
fbi->fbops->fb_ioctl(fbi, MXCFB_GET_DIFMT,
|
|
(unsigned long)&fb_fmt);
|
|
set_fs(old_fs);
|
|
}
|
|
|
|
return fb_fmt;
|
|
}
|
|
|
|
static void update_display_setting(void)
|
|
{
|
|
int i;
|
|
struct fb_info *fbi;
|
|
struct v4l2_rect bg_crop_bounds[2];
|
|
|
|
mutex_lock(&gfb_mutex);
|
|
for (i = 0; i < num_registered_fb; i++) {
|
|
fbi = registered_fb[i];
|
|
|
|
memset(&g_fb_setting[i], 0, sizeof(struct mxc_vout_fb));
|
|
|
|
if (!strncmp(fbi->fix.id, "DISP3", 5))
|
|
g_fb_setting[i].ipu_id = 0;
|
|
else
|
|
g_fb_setting[i].ipu_id = 1;
|
|
|
|
g_fb_setting[i].name = fbi->fix.id;
|
|
g_fb_setting[i].crop_bounds.left = 0;
|
|
g_fb_setting[i].crop_bounds.top = 0;
|
|
g_fb_setting[i].crop_bounds.width = fbi->var.xres;
|
|
g_fb_setting[i].crop_bounds.height = fbi->var.yres;
|
|
g_fb_setting[i].disp_fmt = get_ipu_fmt(fbi);
|
|
|
|
if (get_ipu_channel(fbi) == MEM_BG_SYNC) {
|
|
bg_crop_bounds[g_fb_setting[i].ipu_id] =
|
|
g_fb_setting[i].crop_bounds;
|
|
g_fb_setting[i].disp_support_csc = true;
|
|
} else if (get_ipu_channel(fbi) == MEM_FG_SYNC) {
|
|
g_fb_setting[i].disp_support_csc = true;
|
|
g_fb_setting[i].disp_support_windows = true;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < num_registered_fb; i++) {
|
|
fbi = registered_fb[i];
|
|
|
|
if (get_ipu_channel(fbi) == MEM_FG_SYNC)
|
|
g_fb_setting[i].crop_bounds =
|
|
bg_crop_bounds[g_fb_setting[i].ipu_id];
|
|
}
|
|
mutex_unlock(&gfb_mutex);
|
|
}
|
|
|
|
/* called after g_fb_setting filled by update_display_setting */
|
|
static int update_setting_from_fbi(struct mxc_vout_output *vout,
|
|
struct fb_info *fbi)
|
|
{
|
|
int i;
|
|
bool found = false;
|
|
|
|
mutex_lock(&gfbi_mutex);
|
|
|
|
update_display_setting();
|
|
|
|
for (i = 0; i < MAX_FB_NUM; i++) {
|
|
if (g_fb_setting[i].name) {
|
|
if (!strcmp(fbi->fix.id, g_fb_setting[i].name)) {
|
|
vout->crop_bounds = g_fb_setting[i].crop_bounds;
|
|
vout->disp_fmt = g_fb_setting[i].disp_fmt;
|
|
vout->disp_support_csc =
|
|
g_fb_setting[i].disp_support_csc;
|
|
vout->disp_support_windows =
|
|
g_fb_setting[i].disp_support_windows;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
v4l2_err(vout->vfd->v4l2_dev, "can not find output\n");
|
|
mutex_unlock(&gfbi_mutex);
|
|
return -EINVAL;
|
|
}
|
|
strlcpy(vout->vfd->name, fbi->fix.id, sizeof(vout->vfd->name));
|
|
|
|
memset(&vout->task, 0, sizeof(struct ipu_task));
|
|
|
|
vout->task.input.width = DEF_INPUT_WIDTH;
|
|
vout->task.input.height = DEF_INPUT_HEIGHT;
|
|
vout->task.input.crop.pos.x = 0;
|
|
vout->task.input.crop.pos.y = 0;
|
|
vout->task.input.crop.w = DEF_INPUT_WIDTH;
|
|
vout->task.input.crop.h = DEF_INPUT_HEIGHT;
|
|
|
|
vout->task.output.width = vout->crop_bounds.width;
|
|
vout->task.output.height = vout->crop_bounds.height;
|
|
vout->task.output.crop.pos.x = 0;
|
|
vout->task.output.crop.pos.y = 0;
|
|
vout->task.output.crop.w = vout->crop_bounds.width;
|
|
vout->task.output.crop.h = vout->crop_bounds.height;
|
|
if (colorspaceofpixel(vout->disp_fmt) == YUV_CS)
|
|
vout->task.output.format = IPU_PIX_FMT_UYVY;
|
|
else
|
|
vout->task.output.format = IPU_PIX_FMT_RGB565;
|
|
|
|
mutex_unlock(&gfbi_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned long get_jiffies(struct timeval *t)
|
|
{
|
|
struct timeval cur;
|
|
|
|
if (t->tv_usec >= 1000000) {
|
|
t->tv_sec += t->tv_usec / 1000000;
|
|
t->tv_usec = t->tv_usec % 1000000;
|
|
}
|
|
|
|
do_gettimeofday(&cur);
|
|
if ((t->tv_sec < cur.tv_sec)
|
|
|| ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec)))
|
|
return jiffies;
|
|
|
|
if (t->tv_usec < cur.tv_usec) {
|
|
cur.tv_sec = t->tv_sec - cur.tv_sec - 1;
|
|
cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec;
|
|
} else {
|
|
cur.tv_sec = t->tv_sec - cur.tv_sec;
|
|
cur.tv_usec = t->tv_usec - cur.tv_usec;
|
|
}
|
|
|
|
return jiffies + timeval_to_jiffies(&cur);
|
|
}
|
|
|
|
static bool deinterlace_3_field(struct mxc_vout_output *vout)
|
|
{
|
|
return (vout->task.input.deinterlace.enable &&
|
|
(vout->task.input.deinterlace.motion != HIGH_MOTION));
|
|
}
|
|
|
|
static int set_field_fmt(struct mxc_vout_output *vout, enum v4l2_field field)
|
|
{
|
|
struct ipu_deinterlace *deinterlace = &vout->task.input.deinterlace;
|
|
|
|
switch (field) {
|
|
/* Images are in progressive format, not interlaced */
|
|
case V4L2_FIELD_NONE:
|
|
case V4L2_FIELD_ANY:
|
|
deinterlace->enable = false;
|
|
deinterlace->field_fmt = 0;
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "Progressive frame.\n");
|
|
break;
|
|
case V4L2_FIELD_INTERLACED_TB:
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"Enable deinterlace TB.\n");
|
|
deinterlace->enable = true;
|
|
deinterlace->field_fmt = IPU_DEINTERLACE_FIELD_TOP;
|
|
break;
|
|
case V4L2_FIELD_INTERLACED_BT:
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"Enable deinterlace BT.\n");
|
|
deinterlace->enable = true;
|
|
deinterlace->field_fmt = IPU_DEINTERLACE_FIELD_BOTTOM;
|
|
break;
|
|
default:
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"field format:%d not supported yet!\n", field);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IPU_PIX_FMT_TILED_NV12F == vout->task.input.format) {
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"tiled fmt enable deinterlace.\n");
|
|
deinterlace->enable = true;
|
|
}
|
|
|
|
if (deinterlace->enable && vdi_rate_double)
|
|
deinterlace->field_fmt |= IPU_DEINTERLACE_RATE_EN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_pp_bypass(struct mxc_vout_output *vout)
|
|
{
|
|
if ((IPU_PIX_FMT_TILED_NV12 == vout->task.input.format) ||
|
|
(IPU_PIX_FMT_TILED_NV12F == vout->task.input.format))
|
|
return false;
|
|
if ((vout->task.input.width == vout->task.output.width) &&
|
|
(vout->task.input.height == vout->task.output.height) &&
|
|
(vout->task.input.crop.w == vout->task.output.crop.w) &&
|
|
(vout->task.input.crop.h == vout->task.output.crop.h) &&
|
|
(vout->task.output.rotate < IPU_ROTATE_HORIZ_FLIP) &&
|
|
!vout->task.input.deinterlace.enable) {
|
|
if (vout->disp_support_csc)
|
|
return true;
|
|
else if (!need_csc(vout->task.input.format, vout->disp_fmt))
|
|
return true;
|
|
/*
|
|
* input crop show to full output which can show based on
|
|
* xres_virtual/yres_virtual
|
|
*/
|
|
} else if ((vout->task.input.crop.w == vout->task.output.crop.w) &&
|
|
(vout->task.output.crop.w == vout->task.output.width) &&
|
|
(vout->task.input.crop.h == vout->task.output.crop.h) &&
|
|
(vout->task.output.crop.h ==
|
|
vout->task.output.height) &&
|
|
(vout->task.output.rotate < IPU_ROTATE_HORIZ_FLIP) &&
|
|
!vout->task.input.deinterlace.enable) {
|
|
if (vout->disp_support_csc)
|
|
return true;
|
|
else if (!need_csc(vout->task.input.format, vout->disp_fmt))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void setup_buf_timer(struct mxc_vout_output *vout,
|
|
struct videobuf_buffer *vb)
|
|
{
|
|
ktime_t expiry_time, now;
|
|
|
|
/* if timestamp is 0, then default to 30fps */
|
|
if ((vb->ts.tv_sec == 0) && (vb->ts.tv_usec == 0))
|
|
expiry_time = ktime_add_ns(vout->start_ktime,
|
|
NSEC_PER_FRAME_30FPS * vout->frame_count);
|
|
else
|
|
expiry_time = timeval_to_ktime(vb->ts);
|
|
|
|
now = hrtimer_cb_get_time(&vout->timer);
|
|
if ((now.tv64 > expiry_time.tv64)) {
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"warning: timer timeout already expired.\n");
|
|
expiry_time = now;
|
|
}
|
|
|
|
hrtimer_start(&vout->timer, expiry_time, HRTIMER_MODE_ABS);
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "timer handler next "
|
|
"schedule: %lldnsecs\n", expiry_time.tv64);
|
|
}
|
|
|
|
static int show_buf(struct mxc_vout_output *vout, int idx,
|
|
struct ipu_pos *ipos)
|
|
{
|
|
struct fb_info *fbi = vout->fbi;
|
|
struct fb_var_screeninfo var;
|
|
int ret;
|
|
u32 fb_base = 0;
|
|
|
|
memcpy(&var, &fbi->var, sizeof(var));
|
|
|
|
if (vout->linear_bypass_pp || vout->tiled_bypass_pp) {
|
|
/*
|
|
* crack fb base
|
|
* NOTE: should not do other fb operation during v4l2
|
|
*/
|
|
console_lock();
|
|
fb_base = fbi->fix.smem_start;
|
|
fbi->fix.smem_start = vout->task.output.paddr;
|
|
fbi->var.yoffset = ipos->y + 1;
|
|
var.xoffset = ipos->x;
|
|
var.yoffset = ipos->y;
|
|
var.vmode |= FB_VMODE_YWRAP;
|
|
ret = fb_pan_display(fbi, &var);
|
|
fbi->fix.smem_start = fb_base;
|
|
console_unlock();
|
|
} else {
|
|
console_lock();
|
|
var.yoffset = idx * fbi->var.yres;
|
|
var.vmode &= ~FB_VMODE_YWRAP;
|
|
ret = fb_pan_display(fbi, &var);
|
|
console_unlock();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void disp_work_func(struct work_struct *work)
|
|
{
|
|
struct mxc_vout_output *vout =
|
|
container_of(work, struct mxc_vout_output, disp_work);
|
|
struct videobuf_queue *q = &vout->vbq;
|
|
struct videobuf_buffer *vb, *vb_next = NULL;
|
|
unsigned long flags = 0;
|
|
struct ipu_pos ipos;
|
|
int ret = 0;
|
|
u32 in_fmt = 0, in_width = 0, in_height = 0;
|
|
u32 vdi_cnt = 0;
|
|
u32 vdi_frame;
|
|
u32 index = 0;
|
|
u32 ocrop_h = 0;
|
|
u32 o_height = 0;
|
|
u32 tiled_interlaced = 0;
|
|
bool tiled_fmt = false;
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "disp work begin one frame\n");
|
|
|
|
spin_lock_irqsave(q->irqlock, flags);
|
|
|
|
if (list_empty(&vout->active_list)) {
|
|
v4l2_warn(vout->vfd->v4l2_dev,
|
|
"no entry in active_list, should not be here\n");
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
return;
|
|
}
|
|
|
|
vb = list_first_entry(&vout->active_list,
|
|
struct videobuf_buffer, queue);
|
|
ret = set_field_fmt(vout, vb->field);
|
|
if (ret < 0) {
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
return;
|
|
}
|
|
if (deinterlace_3_field(vout)) {
|
|
if (list_is_singular(&vout->active_list)) {
|
|
if (list_empty(&vout->queue_list)) {
|
|
vout->timer_stop = true;
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
v4l2_warn(vout->vfd->v4l2_dev,
|
|
"no enough entry for 3 fields "
|
|
"deinterlacer\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We need to use the next vb even if it is
|
|
* not on the active list.
|
|
*/
|
|
vb_next = list_first_entry(&vout->queue_list,
|
|
struct videobuf_buffer, queue);
|
|
} else
|
|
vb_next = list_first_entry(vout->active_list.next,
|
|
struct videobuf_buffer, queue);
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"cur field_fmt:%d, next field_fmt:%d.\n",
|
|
vb->field, vb_next->field);
|
|
/* repeat the last field during field format changing */
|
|
if ((vb->field != vb_next->field) &&
|
|
(vb_next->field != V4L2_FIELD_NONE))
|
|
vb_next = vb;
|
|
}
|
|
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
|
|
vdi_frame_rate_double:
|
|
mutex_lock(&vout->task_lock);
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"v4l2 frame_cnt:%ld, vb_field:%d, fmt:%d\n",
|
|
vout->frame_count, vb->field,
|
|
vout->task.input.deinterlace.field_fmt);
|
|
if (vb->memory == V4L2_MEMORY_USERPTR)
|
|
vout->task.input.paddr = vb->baddr;
|
|
else
|
|
vout->task.input.paddr = videobuf_to_dma_contig(vb);
|
|
|
|
if (vout->task.input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_EN)
|
|
index = vout->vdi_frame_cnt % FB_BUFS;
|
|
else
|
|
index = vout->frame_count % FB_BUFS;
|
|
if (vout->linear_bypass_pp) {
|
|
vout->task.output.paddr = vout->task.input.paddr;
|
|
ipos.x = vout->task.input.crop.pos.x;
|
|
ipos.y = vout->task.input.crop.pos.y;
|
|
} else {
|
|
if (deinterlace_3_field(vout)) {
|
|
if (vb->memory == V4L2_MEMORY_USERPTR)
|
|
vout->task.input.paddr_n = vb_next->baddr;
|
|
else
|
|
vout->task.input.paddr_n =
|
|
videobuf_to_dma_contig(vb_next);
|
|
}
|
|
vout->task.output.paddr = vout->disp_bufs[index];
|
|
if (vout->vdoa_1080p) {
|
|
o_height = vout->task.output.height;
|
|
ocrop_h = vout->task.output.crop.h;
|
|
vout->task.output.height = FRAME_HEIGHT_1080P;
|
|
vout->task.output.crop.h = FRAME_HEIGHT_1080P;
|
|
}
|
|
tiled_fmt =
|
|
(IPU_PIX_FMT_TILED_NV12 == vout->task.input.format) ||
|
|
(IPU_PIX_FMT_TILED_NV12F == vout->task.input.format);
|
|
if (vout->tiled_bypass_pp) {
|
|
ipos.x = vout->task.input.crop.pos.x;
|
|
ipos.y = vout->task.input.crop.pos.y;
|
|
} else if (tiled_fmt) {
|
|
vout->vdoa_task.input.paddr = vout->task.input.paddr;
|
|
if (deinterlace_3_field(vout))
|
|
vout->vdoa_task.input.paddr_n =
|
|
vout->task.input.paddr_n;
|
|
vout->vdoa_task.output.paddr = vout->vdoa_work.paddr;
|
|
ret = ipu_queue_task(&vout->vdoa_task);
|
|
if (ret < 0) {
|
|
mutex_unlock(&vout->task_lock);
|
|
goto err;
|
|
}
|
|
vout->task.input.paddr = vout->vdoa_task.output.paddr;
|
|
in_fmt = vout->task.input.format;
|
|
in_width = vout->task.input.width;
|
|
in_height = vout->task.input.height;
|
|
vout->task.input.format = vout->vdoa_task.output.format;
|
|
vout->task.input.width = vout->vdoa_task.output.width;
|
|
vout->task.input.height = vout->vdoa_task.output.height;
|
|
if (vout->task.input.deinterlace.enable) {
|
|
tiled_interlaced = 1;
|
|
vout->task.input.deinterlace.enable = 0;
|
|
}
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"tiled queue task\n");
|
|
}
|
|
ret = ipu_queue_task(&vout->task);
|
|
if ((!vout->tiled_bypass_pp) && tiled_fmt) {
|
|
vout->task.input.format = in_fmt;
|
|
vout->task.input.width = in_width;
|
|
vout->task.input.height = in_height;
|
|
}
|
|
if (tiled_interlaced)
|
|
vout->task.input.deinterlace.enable = 1;
|
|
if (ret < 0) {
|
|
mutex_unlock(&vout->task_lock);
|
|
goto err;
|
|
}
|
|
if (vout->vdoa_1080p) {
|
|
vout->task.output.crop.h = ocrop_h;
|
|
vout->task.output.height = o_height;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&vout->task_lock);
|
|
|
|
ret = show_buf(vout, index, &ipos);
|
|
if (ret < 0)
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"show buf with ret %d\n", ret);
|
|
|
|
if (vout->task.input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_EN) {
|
|
vdi_frame = vout->task.input.deinterlace.field_fmt
|
|
& IPU_DEINTERLACE_RATE_FRAME1;
|
|
if (vdi_frame)
|
|
vout->task.input.deinterlace.field_fmt &=
|
|
~IPU_DEINTERLACE_RATE_FRAME1;
|
|
else
|
|
vout->task.input.deinterlace.field_fmt |=
|
|
IPU_DEINTERLACE_RATE_FRAME1;
|
|
vout->vdi_frame_cnt++;
|
|
vdi_cnt++;
|
|
if (vdi_cnt < IPU_DEINTERLACE_MAX_FRAME)
|
|
goto vdi_frame_rate_double;
|
|
}
|
|
spin_lock_irqsave(q->irqlock, flags);
|
|
|
|
list_del(&vb->queue);
|
|
|
|
/*
|
|
* The videobuf before the last one has been shown. Set
|
|
* VIDEOBUF_DONE state here to avoid tearing issue in ic bypass
|
|
* case, which makes sure a buffer being shown will not be
|
|
* dequeued to be overwritten. It also brings side-effect that
|
|
* the last 2 buffers can not be dequeued correctly, apps need
|
|
* to take care of it.
|
|
*/
|
|
if (vout->pre2_vb) {
|
|
vout->pre2_vb->state = VIDEOBUF_DONE;
|
|
wake_up_interruptible(&vout->pre2_vb->done);
|
|
vout->pre2_vb = NULL;
|
|
}
|
|
|
|
if (vout->linear_bypass_pp) {
|
|
vout->pre2_vb = vout->pre1_vb;
|
|
vout->pre1_vb = vb;
|
|
} else {
|
|
if (vout->pre1_vb) {
|
|
vout->pre1_vb->state = VIDEOBUF_DONE;
|
|
wake_up_interruptible(&vout->pre1_vb->done);
|
|
vout->pre1_vb = NULL;
|
|
}
|
|
vb->state = VIDEOBUF_DONE;
|
|
wake_up_interruptible(&vb->done);
|
|
}
|
|
|
|
vout->frame_count++;
|
|
|
|
/* pick next queue buf to setup timer */
|
|
if (list_empty(&vout->queue_list))
|
|
vout->timer_stop = true;
|
|
else {
|
|
vb = list_first_entry(&vout->queue_list,
|
|
struct videobuf_buffer, queue);
|
|
setup_buf_timer(vout, vb);
|
|
}
|
|
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev, "disp work finish one frame\n");
|
|
|
|
return;
|
|
err:
|
|
v4l2_err(vout->vfd->v4l2_dev, "display work fail ret = %d\n", ret);
|
|
vout->timer_stop = true;
|
|
vb->state = VIDEOBUF_ERROR;
|
|
return;
|
|
}
|
|
|
|
static enum hrtimer_restart mxc_vout_timer_handler(struct hrtimer *timer)
|
|
{
|
|
struct mxc_vout_output *vout = container_of(timer,
|
|
struct mxc_vout_output,
|
|
timer);
|
|
struct videobuf_queue *q = &vout->vbq;
|
|
struct videobuf_buffer *vb;
|
|
unsigned long flags = 0;
|
|
|
|
spin_lock_irqsave(q->irqlock, flags);
|
|
|
|
/*
|
|
* put first queued entry into active, if previous entry did not
|
|
* finish, setup current entry's timer again.
|
|
*/
|
|
if (list_empty(&vout->queue_list)) {
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
/* move videobuf from queued list to active list */
|
|
vb = list_first_entry(&vout->queue_list,
|
|
struct videobuf_buffer, queue);
|
|
list_del(&vb->queue);
|
|
list_add_tail(&vb->queue, &vout->active_list);
|
|
|
|
if (queue_work(vout->v4l_wq, &vout->disp_work) == 0) {
|
|
v4l2_warn(vout->vfd->v4l2_dev,
|
|
"disp work was in queue already, queue buf again next time\n");
|
|
list_del(&vb->queue);
|
|
list_add(&vb->queue, &vout->queue_list);
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
vb->state = VIDEOBUF_ACTIVE;
|
|
|
|
spin_unlock_irqrestore(q->irqlock, flags);
|
|
|
|
return HRTIMER_NORESTART;
|
|
}
|
|
|
|
/* Video buffer call backs */
|
|
|
|
/*
|
|
* Buffer setup function is called by videobuf layer when REQBUF ioctl is
|
|
* called. This is used to setup buffers and return size and count of
|
|
* buffers allocated. After the call to this buffer, videobuf layer will
|
|
* setup buffer queue depending on the size and count of buffers
|
|
*/
|
|
static int mxc_vout_buffer_setup(struct videobuf_queue *q, unsigned int *count,
|
|
unsigned int *size)
|
|
{
|
|
struct mxc_vout_output *vout = q->priv_data;
|
|
unsigned int frame_size;
|
|
|
|
if (!vout)
|
|
return -EINVAL;
|
|
|
|
if (V4L2_BUF_TYPE_VIDEO_OUTPUT != q->type)
|
|
return -EINVAL;
|
|
|
|
frame_size = get_frame_size(vout);
|
|
*size = PAGE_ALIGN(frame_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function will be called when VIDIOC_QBUF ioctl is called.
|
|
* It prepare buffers before give out for the display. This function
|
|
* converts user space virtual address into physical address if userptr memory
|
|
* exchange mechanism is used.
|
|
*/
|
|
static int mxc_vout_buffer_prepare(struct videobuf_queue *q,
|
|
struct videobuf_buffer *vb,
|
|
enum v4l2_field field)
|
|
{
|
|
vb->state = VIDEOBUF_PREPARED;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Buffer queue funtion will be called from the videobuf layer when _QBUF
|
|
* ioctl is called. It is used to enqueue buffer, which is ready to be
|
|
* displayed.
|
|
* This function is protected by q->irqlock.
|
|
*/
|
|
static void mxc_vout_buffer_queue(struct videobuf_queue *q,
|
|
struct videobuf_buffer *vb)
|
|
{
|
|
struct mxc_vout_output *vout = q->priv_data;
|
|
struct videobuf_buffer *active_vb;
|
|
|
|
list_add_tail(&vb->queue, &vout->queue_list);
|
|
vb->state = VIDEOBUF_QUEUED;
|
|
|
|
if (vout->timer_stop) {
|
|
if (deinterlace_3_field(vout) &&
|
|
!list_empty(&vout->active_list)) {
|
|
active_vb = list_first_entry(&vout->active_list,
|
|
struct videobuf_buffer, queue);
|
|
setup_buf_timer(vout, active_vb);
|
|
} else {
|
|
setup_buf_timer(vout, vb);
|
|
}
|
|
vout->timer_stop = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Buffer release function is called from videobuf layer to release buffer
|
|
* which are already allocated
|
|
*/
|
|
static void mxc_vout_buffer_release(struct videobuf_queue *q,
|
|
struct videobuf_buffer *vb)
|
|
{
|
|
vb->state = VIDEOBUF_NEEDS_INIT;
|
|
}
|
|
|
|
static int mxc_vout_mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
int ret;
|
|
struct mxc_vout_output *vout = file->private_data;
|
|
|
|
if (!vout)
|
|
return -ENODEV;
|
|
|
|
ret = videobuf_mmap_mapper(&vout->vbq, vma);
|
|
if (ret < 0)
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"offset invalid [offset=0x%lx]\n",
|
|
(vma->vm_pgoff << PAGE_SHIFT));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_release(struct file *file)
|
|
{
|
|
unsigned int ret = 0;
|
|
struct videobuf_queue *q;
|
|
struct mxc_vout_output *vout = file->private_data;
|
|
|
|
if (!vout)
|
|
return 0;
|
|
|
|
mutex_lock(&vout->accs_lock);
|
|
if (--vout->open_cnt == 0) {
|
|
q = &vout->vbq;
|
|
if (q->streaming)
|
|
mxc_vidioc_streamoff(file, vout, vout->type);
|
|
else {
|
|
release_disp_output(vout);
|
|
videobuf_queue_cancel(q);
|
|
}
|
|
destroy_workqueue(vout->v4l_wq);
|
|
ret = videobuf_mmap_free(q);
|
|
}
|
|
|
|
mutex_unlock(&vout->accs_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_open(struct file *file)
|
|
{
|
|
struct mxc_vout_output *vout = NULL;
|
|
int ret = 0;
|
|
|
|
vout = video_drvdata(file);
|
|
|
|
if (vout == NULL)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&vout->accs_lock);
|
|
if (vout->open_cnt++ == 0) {
|
|
vout->ctrl_rotate = 0;
|
|
vout->ctrl_vflip = 0;
|
|
vout->ctrl_hflip = 0;
|
|
ret = update_setting_from_fbi(vout, vout->fbi);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
vout->v4l_wq = create_singlethread_workqueue("v4l2q");
|
|
if (!vout->v4l_wq) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"Could not create work queue\n");
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
INIT_WORK(&vout->disp_work, disp_work_func);
|
|
|
|
INIT_LIST_HEAD(&vout->queue_list);
|
|
INIT_LIST_HEAD(&vout->active_list);
|
|
|
|
vout->fmt_init = false;
|
|
vout->frame_count = 0;
|
|
vout->vdi_frame_cnt = 0;
|
|
|
|
vout->win_pos.x = 0;
|
|
vout->win_pos.y = 0;
|
|
vout->release = true;
|
|
}
|
|
|
|
file->private_data = vout;
|
|
|
|
err:
|
|
mutex_unlock(&vout->accs_lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* V4L2 ioctls
|
|
*/
|
|
static int mxc_vidioc_querycap(struct file *file, void *fh,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
strlcpy(cap->driver, VOUT_NAME, sizeof(cap->driver));
|
|
strlcpy(cap->card, vout->vfd->name, sizeof(cap->card));
|
|
cap->bus_info[0] = '\0';
|
|
cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxc_vidioc_enum_fmt_vid_out(struct file *file, void *fh,
|
|
struct v4l2_fmtdesc *fmt)
|
|
{
|
|
if (fmt->index >= NUM_MXC_VOUT_FORMATS)
|
|
return -EINVAL;
|
|
|
|
strlcpy(fmt->description, mxc_formats[fmt->index].description,
|
|
sizeof(fmt->description));
|
|
fmt->pixelformat = mxc_formats[fmt->index].pixelformat;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxc_vidioc_g_fmt_vid_out(struct file *file, void *fh,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
struct v4l2_rect rect;
|
|
|
|
f->fmt.pix.width = vout->task.input.width;
|
|
f->fmt.pix.height = vout->task.input.height;
|
|
f->fmt.pix.pixelformat = vout->task.input.format;
|
|
f->fmt.pix.sizeimage = get_frame_size(vout);
|
|
|
|
if (f->fmt.pix.priv) {
|
|
rect.left = vout->task.input.crop.pos.x;
|
|
rect.top = vout->task.input.crop.pos.y;
|
|
rect.width = vout->task.input.crop.w;
|
|
rect.height = vout->task.input.crop.h;
|
|
if (copy_to_user((void __user *)f->fmt.pix.priv,
|
|
&rect, sizeof(rect)))
|
|
return -EFAULT;
|
|
}
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"frame_size:0x%x, pix_fmt:0x%x\n",
|
|
f->fmt.pix.sizeimage,
|
|
vout->task.input.format);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int ipu_try_task(struct mxc_vout_output *vout)
|
|
{
|
|
int ret;
|
|
struct ipu_task *task = &vout->task;
|
|
|
|
again:
|
|
ret = ipu_check_task(task);
|
|
if (ret != IPU_CHECK_OK) {
|
|
if (ret > IPU_CHECK_ERR_MIN) {
|
|
if (ret == IPU_CHECK_ERR_SPLIT_INPUTW_OVER ||
|
|
ret == IPU_CHECK_ERR_W_DOWNSIZE_OVER) {
|
|
task->input.crop.w -= 8;
|
|
goto again;
|
|
}
|
|
if (ret == IPU_CHECK_ERR_SPLIT_INPUTH_OVER ||
|
|
ret == IPU_CHECK_ERR_H_DOWNSIZE_OVER) {
|
|
task->input.crop.h -= 8;
|
|
goto again;
|
|
}
|
|
if (ret == IPU_CHECK_ERR_SPLIT_OUTPUTW_OVER) {
|
|
if (vout->disp_support_windows) {
|
|
task->output.width -= 8;
|
|
task->output.crop.w =
|
|
task->output.width;
|
|
} else
|
|
task->output.crop.w -= 8;
|
|
goto again;
|
|
}
|
|
if (ret == IPU_CHECK_ERR_SPLIT_OUTPUTH_OVER) {
|
|
if (vout->disp_support_windows) {
|
|
task->output.height -= 8;
|
|
task->output.crop.h =
|
|
task->output.height;
|
|
} else
|
|
task->output.crop.h -= 8;
|
|
goto again;
|
|
}
|
|
ret = -EINVAL;
|
|
}
|
|
} else
|
|
ret = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int vdoaipu_try_task(struct mxc_vout_output *vout)
|
|
{
|
|
int ret;
|
|
int is_1080p_stream;
|
|
int in_width, in_height;
|
|
size_t size;
|
|
struct ipu_task *ipu_task = &vout->task;
|
|
struct ipu_crop *icrop = &ipu_task->input.crop;
|
|
struct ipu_task *vdoa_task = &vout->vdoa_task;
|
|
u32 deinterlace = 0;
|
|
u32 in_fmt;
|
|
|
|
if (vout->task.input.deinterlace.enable)
|
|
deinterlace = 1;
|
|
|
|
memset(vdoa_task, 0, sizeof(*vdoa_task));
|
|
vdoa_task->output.format = IPU_PIX_FMT_NV12;
|
|
memcpy(&vdoa_task->input, &ipu_task->input,
|
|
sizeof(ipu_task->input));
|
|
if ((icrop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
|
|
(icrop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN)) {
|
|
vdoa_task->input.crop.w =
|
|
ALIGN(icrop->w, IPU_PIX_FMT_TILED_NV12_MBALIGN);
|
|
vdoa_task->input.crop.h =
|
|
ALIGN(icrop->h, IPU_PIX_FMT_TILED_NV12_MBALIGN);
|
|
}
|
|
vdoa_task->output.width = vdoa_task->input.crop.w;
|
|
vdoa_task->output.height = vdoa_task->input.crop.h;
|
|
vdoa_task->output.crop.w = vdoa_task->input.crop.w;
|
|
vdoa_task->output.crop.h = vdoa_task->input.crop.h;
|
|
|
|
size = PAGE_ALIGN(vdoa_task->input.crop.w *
|
|
vdoa_task->input.crop.h *
|
|
fmt_to_bpp(vdoa_task->output.format)/8);
|
|
if (size > vout->vdoa_work.size) {
|
|
if (vout->vdoa_work.vaddr)
|
|
free_dma_buf(vout, &vout->vdoa_work);
|
|
vout->vdoa_work.size = size;
|
|
ret = alloc_dma_buf(vout, &vout->vdoa_work);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
ret = ipu_check_task(vdoa_task);
|
|
if (ret != IPU_CHECK_OK)
|
|
return -EINVAL;
|
|
|
|
is_1080p_stream = CHECK_TILED_1080P_STREAM(vout);
|
|
if (is_1080p_stream)
|
|
ipu_task->input.crop.h = VALID_HEIGHT_1080P;
|
|
in_fmt = ipu_task->input.format;
|
|
in_width = ipu_task->input.width;
|
|
in_height = ipu_task->input.height;
|
|
ipu_task->input.format = vdoa_task->output.format;
|
|
ipu_task->input.height = vdoa_task->output.height;
|
|
ipu_task->input.width = vdoa_task->output.width;
|
|
if (deinterlace)
|
|
ipu_task->input.deinterlace.enable = 0;
|
|
ret = ipu_try_task(vout);
|
|
if (deinterlace)
|
|
ipu_task->input.deinterlace.enable = 1;
|
|
ipu_task->input.format = in_fmt;
|
|
ipu_task->input.width = in_width;
|
|
ipu_task->input.height = in_height;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_try_task(struct mxc_vout_output *vout)
|
|
{
|
|
int ret = 0;
|
|
struct ipu_output *output = &vout->task.output;
|
|
struct ipu_input *input = &vout->task.input;
|
|
struct ipu_crop *crop = &input->crop;
|
|
u32 o_height = 0;
|
|
u32 ocrop_h = 0;
|
|
bool tiled_fmt = false;
|
|
bool tiled_need_pp = false;
|
|
|
|
vout->vdoa_1080p = CHECK_TILED_1080P_DISPLAY(vout);
|
|
if (vout->vdoa_1080p) {
|
|
input->crop.h = FRAME_HEIGHT_1080P;
|
|
o_height = output->height;
|
|
ocrop_h = output->crop.h;
|
|
output->height = FRAME_HEIGHT_1080P;
|
|
output->crop.h = FRAME_HEIGHT_1080P;
|
|
}
|
|
|
|
if ((IPU_PIX_FMT_TILED_NV12 == input->format) ||
|
|
(IPU_PIX_FMT_TILED_NV12F == input->format)) {
|
|
if ((input->width % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
|
|
(input->height % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
|
|
(crop->pos.x % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
|
|
(crop->pos.y % IPU_PIX_FMT_TILED_NV12_MBALIGN)) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"ERR: tiled fmt needs 16 pixel align.\n");
|
|
return -EINVAL;
|
|
}
|
|
if ((crop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN) ||
|
|
(crop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN))
|
|
tiled_need_pp = true;
|
|
} else {
|
|
crop->w -= crop->w % 8;
|
|
crop->h -= crop->h % 8;
|
|
}
|
|
/* assume task.output already set by S_CROP */
|
|
vout->linear_bypass_pp = is_pp_bypass(vout);
|
|
if (vout->linear_bypass_pp) {
|
|
v4l2_info(vout->vfd->v4l2_dev, "Bypass IC.\n");
|
|
output->format = input->format;
|
|
} else {
|
|
/* if need CSC, choose IPU-DP or IPU_IC do it */
|
|
if (vout->disp_support_csc) {
|
|
if (colorspaceofpixel(input->format) == YUV_CS)
|
|
output->format = IPU_PIX_FMT_UYVY;
|
|
else
|
|
output->format = IPU_PIX_FMT_RGB565;
|
|
} else {
|
|
if (colorspaceofpixel(vout->disp_fmt) == YUV_CS)
|
|
output->format = IPU_PIX_FMT_UYVY;
|
|
else
|
|
output->format = IPU_PIX_FMT_RGB565;
|
|
}
|
|
|
|
vout->tiled_bypass_pp = false;
|
|
if ((IPU_PIX_FMT_TILED_NV12 == input->format) ||
|
|
(IPU_PIX_FMT_TILED_NV12F == input->format)) {
|
|
/* check resize/rotate/flip, or csc task */
|
|
if (!(tiled_need_pp ||
|
|
(IPU_ROTATE_NONE != output->rotate) ||
|
|
(input->crop.w != output->crop.w) ||
|
|
(input->crop.h != output->crop.h) ||
|
|
(!vout->disp_support_csc &&
|
|
(colorspaceofpixel(vout->disp_fmt) == RGB_CS)))
|
|
) {
|
|
/* IC bypass */
|
|
output->format = IPU_PIX_FMT_NV12;
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"tiled bypass pp\n");
|
|
vout->tiled_bypass_pp = true;
|
|
}
|
|
tiled_fmt = true;
|
|
}
|
|
|
|
if ((!vout->tiled_bypass_pp) && tiled_fmt)
|
|
ret = vdoaipu_try_task(vout);
|
|
else
|
|
ret = ipu_try_task(vout);
|
|
}
|
|
|
|
if (vout->vdoa_1080p) {
|
|
output->height = o_height;
|
|
output->crop.h = ocrop_h;
|
|
}
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"icrop.w:%u, icrop.h:%u, iw:%u, ih:%u,"
|
|
"ocrop.w:%u, ocrop.h:%u, ow:%u, oh:%u\n",
|
|
input->crop.w, input->crop.h,
|
|
input->width, input->height,
|
|
output->crop.w, output->crop.h,
|
|
output->width, output->height);
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_try_format(struct mxc_vout_output *vout,
|
|
struct v4l2_format *f)
|
|
{
|
|
int ret = 0;
|
|
struct v4l2_rect rect;
|
|
|
|
if ((f->fmt.pix.field != V4L2_FIELD_NONE) &&
|
|
(IPU_PIX_FMT_TILED_NV12 == vout->task.input.format)) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"progressive tiled fmt should used V4L2_FIELD_NONE!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (f->fmt.pix.priv && copy_from_user(&rect,
|
|
(void __user *)f->fmt.pix.priv, sizeof(rect)))
|
|
return -EFAULT;
|
|
|
|
vout->task.input.width = f->fmt.pix.width;
|
|
vout->task.input.height = f->fmt.pix.height;
|
|
vout->task.input.format = f->fmt.pix.pixelformat;
|
|
|
|
ret = set_field_fmt(vout, f->fmt.pix.field);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (f->fmt.pix.priv) {
|
|
vout->task.input.crop.pos.x = rect.left;
|
|
vout->task.input.crop.pos.y = rect.top;
|
|
vout->task.input.crop.w = rect.width;
|
|
vout->task.input.crop.h = rect.height;
|
|
} else {
|
|
vout->task.input.crop.pos.x = 0;
|
|
vout->task.input.crop.pos.y = 0;
|
|
vout->task.input.crop.w = f->fmt.pix.width;
|
|
vout->task.input.crop.h = f->fmt.pix.height;
|
|
}
|
|
memcpy(&vout->in_rect, &vout->task.input.crop, sizeof(vout->in_rect));
|
|
|
|
ret = mxc_vout_try_task(vout);
|
|
if (!ret) {
|
|
if (f->fmt.pix.priv) {
|
|
rect.width = vout->task.input.crop.w;
|
|
rect.height = vout->task.input.crop.h;
|
|
if (copy_to_user((void __user *)f->fmt.pix.priv,
|
|
&rect, sizeof(rect)))
|
|
ret = -EFAULT;
|
|
} else {
|
|
f->fmt.pix.width = vout->task.input.crop.w;
|
|
f->fmt.pix.height = vout->task.input.crop.h;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool mxc_vout_need_fb_reconfig(struct mxc_vout_output *vout,
|
|
struct mxc_vout_output *pre_vout)
|
|
{
|
|
if (!vout->vbq.streaming)
|
|
return false;
|
|
|
|
if (vout->tiled_bypass_pp)
|
|
return true;
|
|
|
|
if (vout->linear_bypass_pp != pre_vout->linear_bypass_pp)
|
|
return true;
|
|
|
|
/* cropped output resolution or format are changed */
|
|
if (vout->task.output.format != pre_vout->task.output.format ||
|
|
vout->task.output.crop.w != pre_vout->task.output.crop.w ||
|
|
vout->task.output.crop.h != pre_vout->task.output.crop.h)
|
|
return true;
|
|
|
|
/* overlay: window position or resolution are changed */
|
|
if (vout->disp_support_windows &&
|
|
(vout->win_pos.x != pre_vout->win_pos.x ||
|
|
vout->win_pos.y != pre_vout->win_pos.y ||
|
|
vout->task.output.width != pre_vout->task.output.width ||
|
|
vout->task.output.height != pre_vout->task.output.height))
|
|
return true;
|
|
|
|
/* background: cropped position is changed */
|
|
if (!vout->disp_support_windows &&
|
|
(vout->task.output.crop.pos.x !=
|
|
pre_vout->task.output.crop.pos.x ||
|
|
vout->task.output.crop.pos.y !=
|
|
pre_vout->task.output.crop.pos.y))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int mxc_vidioc_s_fmt_vid_out(struct file *file, void *fh,
|
|
struct v4l2_format *f)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
int ret = 0;
|
|
|
|
if (vout->vbq.streaming)
|
|
return -EBUSY;
|
|
|
|
mutex_lock(&vout->task_lock);
|
|
ret = mxc_vout_try_format(vout, f);
|
|
if (ret >= 0)
|
|
vout->fmt_init = true;
|
|
mutex_unlock(&vout->task_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_cropcap(struct file *file, void *fh,
|
|
struct v4l2_cropcap *cropcap)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
cropcap->bounds = vout->crop_bounds;
|
|
cropcap->defrect = vout->crop_bounds;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxc_vidioc_g_crop(struct file *file, void *fh,
|
|
struct v4l2_crop *crop)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
if (vout->disp_support_windows) {
|
|
crop->c.left = vout->win_pos.x;
|
|
crop->c.top = vout->win_pos.y;
|
|
crop->c.width = vout->task.output.width;
|
|
crop->c.height = vout->task.output.height;
|
|
} else {
|
|
if (vout->task.output.crop.w && vout->task.output.crop.h) {
|
|
crop->c.left = vout->task.output.crop.pos.x;
|
|
crop->c.top = vout->task.output.crop.pos.y;
|
|
crop->c.width = vout->task.output.crop.w;
|
|
crop->c.height = vout->task.output.crop.h;
|
|
} else {
|
|
crop->c.left = 0;
|
|
crop->c.top = 0;
|
|
crop->c.width = vout->task.output.width;
|
|
crop->c.height = vout->task.output.height;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxc_vidioc_s_crop(struct file *file, void *fh,
|
|
const struct v4l2_crop *crop)
|
|
{
|
|
struct mxc_vout_output *vout = fh, *pre_vout;
|
|
struct v4l2_rect *b = &vout->crop_bounds;
|
|
struct v4l2_crop fix_up_crop;
|
|
int ret = 0;
|
|
|
|
memcpy(&fix_up_crop, crop, sizeof(*crop));
|
|
|
|
if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
if (crop->c.width < 0 || crop->c.height < 0)
|
|
return -EINVAL;
|
|
|
|
if (crop->c.width == 0)
|
|
fix_up_crop.c.width = b->width - b->left;
|
|
if (crop->c.height == 0)
|
|
fix_up_crop.c.height = b->height - b->top;
|
|
|
|
if (crop->c.top < b->top)
|
|
fix_up_crop.c.top = b->top;
|
|
if (crop->c.top >= b->top + b->height)
|
|
fix_up_crop.c.top = b->top + b->height - 1;
|
|
if (crop->c.height > b->top - crop->c.top + b->height)
|
|
fix_up_crop.c.height =
|
|
b->top - fix_up_crop.c.top + b->height;
|
|
|
|
if (crop->c.left < b->left)
|
|
fix_up_crop.c.left = b->left;
|
|
if (crop->c.left >= b->left + b->width)
|
|
fix_up_crop.c.left = b->left + b->width - 1;
|
|
if (crop->c.width > b->left - crop->c.left + b->width)
|
|
fix_up_crop.c.width =
|
|
b->left - fix_up_crop.c.left + b->width;
|
|
|
|
/* stride line limitation */
|
|
fix_up_crop.c.height -= fix_up_crop.c.height % 8;
|
|
fix_up_crop.c.width -= fix_up_crop.c.width % 8;
|
|
if ((fix_up_crop.c.width <= 0) || (fix_up_crop.c.height <= 0) ||
|
|
((fix_up_crop.c.left + fix_up_crop.c.width) >
|
|
(b->left + b->width)) ||
|
|
((fix_up_crop.c.top + fix_up_crop.c.height) >
|
|
(b->top + b->height))) {
|
|
v4l2_err(vout->vfd->v4l2_dev, "s_crop err: %d, %d, %d, %d",
|
|
fix_up_crop.c.left, fix_up_crop.c.top,
|
|
fix_up_crop.c.width, fix_up_crop.c.height);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* the same setting, return */
|
|
if (vout->disp_support_windows) {
|
|
if ((vout->win_pos.x == fix_up_crop.c.left) &&
|
|
(vout->win_pos.y == fix_up_crop.c.top) &&
|
|
(vout->task.output.crop.w == fix_up_crop.c.width) &&
|
|
(vout->task.output.crop.h == fix_up_crop.c.height))
|
|
return 0;
|
|
} else {
|
|
if ((vout->task.output.crop.pos.x == fix_up_crop.c.left) &&
|
|
(vout->task.output.crop.pos.y == fix_up_crop.c.top) &&
|
|
(vout->task.output.crop.w == fix_up_crop.c.width) &&
|
|
(vout->task.output.crop.h == fix_up_crop.c.height))
|
|
return 0;
|
|
}
|
|
|
|
pre_vout = vmalloc(sizeof(*pre_vout));
|
|
if (!pre_vout)
|
|
return -ENOMEM;
|
|
|
|
/* wait current work finish */
|
|
if (vout->vbq.streaming)
|
|
flush_workqueue(vout->v4l_wq);
|
|
|
|
mutex_lock(&vout->task_lock);
|
|
|
|
memcpy(pre_vout, vout, sizeof(*vout));
|
|
|
|
if (vout->disp_support_windows) {
|
|
vout->task.output.crop.pos.x = 0;
|
|
vout->task.output.crop.pos.y = 0;
|
|
vout->win_pos.x = fix_up_crop.c.left;
|
|
vout->win_pos.y = fix_up_crop.c.top;
|
|
vout->task.output.width = fix_up_crop.c.width;
|
|
vout->task.output.height = fix_up_crop.c.height;
|
|
} else {
|
|
vout->task.output.crop.pos.x = fix_up_crop.c.left;
|
|
vout->task.output.crop.pos.y = fix_up_crop.c.top;
|
|
}
|
|
|
|
vout->task.output.crop.w = fix_up_crop.c.width;
|
|
vout->task.output.crop.h = fix_up_crop.c.height;
|
|
|
|
/*
|
|
* must S_CROP before S_FMT, for fist time S_CROP, will not check
|
|
* ipu task, it will check in S_FMT, after S_FMT, S_CROP should
|
|
* check ipu task too.
|
|
*/
|
|
if (vout->fmt_init) {
|
|
memcpy(&vout->task.input.crop, &vout->in_rect,
|
|
sizeof(vout->in_rect));
|
|
ret = mxc_vout_try_task(vout);
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"vout check task failed\n");
|
|
memcpy(vout, pre_vout, sizeof(*vout));
|
|
goto done;
|
|
}
|
|
|
|
if (mxc_vout_need_fb_reconfig(vout, pre_vout)) {
|
|
ret = config_disp_output(vout);
|
|
if (ret < 0)
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"Config display output failed\n");
|
|
}
|
|
}
|
|
|
|
done:
|
|
vfree(pre_vout);
|
|
mutex_unlock(&vout->task_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_queryctrl(struct file *file, void *fh,
|
|
struct v4l2_queryctrl *ctrl)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_ROTATE:
|
|
ret = v4l2_ctrl_query_fill(ctrl, 0, 270, 90, 0);
|
|
break;
|
|
case V4L2_CID_VFLIP:
|
|
ret = v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0);
|
|
break;
|
|
case V4L2_CID_HFLIP:
|
|
ret = v4l2_ctrl_query_fill(ctrl, 0, 1, 1, 0);
|
|
break;
|
|
case V4L2_CID_MXC_MOTION:
|
|
ret = v4l2_ctrl_query_fill(ctrl, 0, 2, 1, 0);
|
|
break;
|
|
default:
|
|
ctrl->name[0] = '\0';
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_g_ctrl(struct file *file, void *fh,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
int ret = 0;
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_ROTATE:
|
|
ctrl->value = vout->ctrl_rotate;
|
|
break;
|
|
case V4L2_CID_VFLIP:
|
|
ctrl->value = vout->ctrl_vflip;
|
|
break;
|
|
case V4L2_CID_HFLIP:
|
|
ctrl->value = vout->ctrl_hflip;
|
|
break;
|
|
case V4L2_CID_MXC_MOTION:
|
|
if (vout->task.input.deinterlace.enable)
|
|
ctrl->value = vout->task.input.deinterlace.motion;
|
|
else
|
|
ctrl->value = 0;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void setup_task_rotation(struct mxc_vout_output *vout)
|
|
{
|
|
if (vout->ctrl_rotate == 0) {
|
|
if (vout->ctrl_vflip && vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_180;
|
|
else if (vout->ctrl_vflip)
|
|
vout->task.output.rotate = IPU_ROTATE_VERT_FLIP;
|
|
else if (vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_HORIZ_FLIP;
|
|
else
|
|
vout->task.output.rotate = IPU_ROTATE_NONE;
|
|
} else if (vout->ctrl_rotate == 90) {
|
|
if (vout->ctrl_vflip && vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_LEFT;
|
|
else if (vout->ctrl_vflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT_VFLIP;
|
|
else if (vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT_HFLIP;
|
|
else
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT;
|
|
} else if (vout->ctrl_rotate == 180) {
|
|
if (vout->ctrl_vflip && vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_NONE;
|
|
else if (vout->ctrl_vflip)
|
|
vout->task.output.rotate = IPU_ROTATE_HORIZ_FLIP;
|
|
else if (vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_VERT_FLIP;
|
|
else
|
|
vout->task.output.rotate = IPU_ROTATE_180;
|
|
} else if (vout->ctrl_rotate == 270) {
|
|
if (vout->ctrl_vflip && vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT;
|
|
else if (vout->ctrl_vflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT_HFLIP;
|
|
else if (vout->ctrl_hflip)
|
|
vout->task.output.rotate = IPU_ROTATE_90_RIGHT_VFLIP;
|
|
else
|
|
vout->task.output.rotate = IPU_ROTATE_90_LEFT;
|
|
}
|
|
}
|
|
|
|
static int mxc_vidioc_s_ctrl(struct file *file, void *fh,
|
|
struct v4l2_control *ctrl)
|
|
{
|
|
int ret = 0;
|
|
struct mxc_vout_output *vout = fh, *pre_vout;
|
|
|
|
pre_vout = vmalloc(sizeof(*pre_vout));
|
|
if (!pre_vout)
|
|
return -ENOMEM;
|
|
|
|
/* wait current work finish */
|
|
if (vout->vbq.streaming)
|
|
flush_workqueue(vout->v4l_wq);
|
|
|
|
mutex_lock(&vout->task_lock);
|
|
|
|
memcpy(pre_vout, vout, sizeof(*vout));
|
|
|
|
switch (ctrl->id) {
|
|
case V4L2_CID_ROTATE:
|
|
{
|
|
vout->ctrl_rotate = (ctrl->value/90) * 90;
|
|
if (vout->ctrl_rotate > 270)
|
|
vout->ctrl_rotate = 270;
|
|
setup_task_rotation(vout);
|
|
break;
|
|
}
|
|
case V4L2_CID_VFLIP:
|
|
{
|
|
vout->ctrl_vflip = ctrl->value;
|
|
setup_task_rotation(vout);
|
|
break;
|
|
}
|
|
case V4L2_CID_HFLIP:
|
|
{
|
|
vout->ctrl_hflip = ctrl->value;
|
|
setup_task_rotation(vout);
|
|
break;
|
|
}
|
|
case V4L2_CID_MXC_MOTION:
|
|
{
|
|
vout->task.input.deinterlace.motion = ctrl->value;
|
|
break;
|
|
}
|
|
default:
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
if (vout->fmt_init) {
|
|
memcpy(&vout->task.input.crop, &vout->in_rect,
|
|
sizeof(vout->in_rect));
|
|
ret = mxc_vout_try_task(vout);
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"vout check task failed\n");
|
|
memcpy(vout, pre_vout, sizeof(*vout));
|
|
goto done;
|
|
}
|
|
|
|
if (mxc_vout_need_fb_reconfig(vout, pre_vout)) {
|
|
ret = config_disp_output(vout);
|
|
if (ret < 0)
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"Config display output failed\n");
|
|
}
|
|
}
|
|
|
|
done:
|
|
vfree(pre_vout);
|
|
mutex_unlock(&vout->task_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_reqbufs(struct file *file, void *fh,
|
|
struct v4l2_requestbuffers *req)
|
|
{
|
|
int ret = 0;
|
|
struct mxc_vout_output *vout = fh;
|
|
struct videobuf_queue *q = &vout->vbq;
|
|
|
|
if (req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
|
|
return -EINVAL;
|
|
|
|
/* should not be here after streaming, videobuf_reqbufs will control */
|
|
mutex_lock(&vout->task_lock);
|
|
|
|
ret = videobuf_reqbufs(q, req);
|
|
|
|
mutex_unlock(&vout->task_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_querybuf(struct file *file, void *fh,
|
|
struct v4l2_buffer *b)
|
|
{
|
|
int ret;
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
ret = videobuf_querybuf(&vout->vbq, b);
|
|
if (!ret) {
|
|
/* return physical address */
|
|
struct videobuf_buffer *vb = vout->vbq.bufs[b->index];
|
|
if (b->flags & V4L2_BUF_FLAG_MAPPED)
|
|
b->m.offset = videobuf_to_dma_contig(vb);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_qbuf(struct file *file, void *fh,
|
|
struct v4l2_buffer *buffer)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
return videobuf_qbuf(&vout->vbq, buffer);
|
|
}
|
|
|
|
static int mxc_vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
|
|
if (!vout->vbq.streaming)
|
|
return -EINVAL;
|
|
|
|
if (file->f_flags & O_NONBLOCK)
|
|
return videobuf_dqbuf(&vout->vbq, (struct v4l2_buffer *)b, 1);
|
|
else
|
|
return videobuf_dqbuf(&vout->vbq, (struct v4l2_buffer *)b, 0);
|
|
}
|
|
|
|
static int set_window_position(struct mxc_vout_output *vout,
|
|
struct mxcfb_pos *pos)
|
|
{
|
|
struct fb_info *fbi = vout->fbi;
|
|
mm_segment_t old_fs;
|
|
int ret = 0;
|
|
|
|
if (vout->disp_support_windows) {
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
ret = fbi->fbops->fb_ioctl(fbi, MXCFB_SET_OVERLAY_POS,
|
|
(unsigned long)pos);
|
|
set_fs(old_fs);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int config_disp_output(struct mxc_vout_output *vout)
|
|
{
|
|
struct dma_mem *buf = NULL;
|
|
struct fb_info *fbi = vout->fbi;
|
|
struct fb_var_screeninfo var;
|
|
struct mxcfb_pos pos;
|
|
int i, fb_num, ret;
|
|
u32 fb_base;
|
|
u32 size;
|
|
u32 display_buf_size;
|
|
u32 *pixel = NULL;
|
|
u32 color;
|
|
int j;
|
|
|
|
memcpy(&var, &fbi->var, sizeof(var));
|
|
fb_base = fbi->fix.smem_start;
|
|
|
|
var.xres = vout->task.output.width;
|
|
var.yres = vout->task.output.height;
|
|
if (vout->linear_bypass_pp || vout->tiled_bypass_pp) {
|
|
fb_num = 1;
|
|
/* input crop */
|
|
if (vout->task.input.width > vout->task.output.width)
|
|
var.xres_virtual = vout->task.input.width;
|
|
else
|
|
var.xres_virtual = var.xres;
|
|
if (vout->task.input.height > vout->task.output.height)
|
|
var.yres_virtual = vout->task.input.height;
|
|
else
|
|
var.yres_virtual = var.yres;
|
|
var.rotate = vout->task.output.rotate;
|
|
var.vmode |= FB_VMODE_YWRAP;
|
|
} else {
|
|
fb_num = FB_BUFS;
|
|
var.xres_virtual = var.xres;
|
|
var.yres_virtual = fb_num * var.yres;
|
|
var.vmode &= ~FB_VMODE_YWRAP;
|
|
}
|
|
var.bits_per_pixel = fmt_to_bpp(vout->task.output.format);
|
|
var.nonstd = vout->task.output.format;
|
|
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"set display fb to %d %d\n",
|
|
var.xres, var.yres);
|
|
|
|
/*
|
|
* To setup the overlay fb from scratch without
|
|
* the last time overlay fb position or resolution's
|
|
* impact, we take the following steps:
|
|
* - blank fb
|
|
* - set fb position to the starting point
|
|
* - reconfigure fb
|
|
* - set fb position to a specific point
|
|
* - unblank fb
|
|
* This procedure applies to non-overlay fbs as well.
|
|
*/
|
|
console_lock();
|
|
fbi->flags |= FBINFO_MISC_USEREVENT;
|
|
fb_blank(fbi, FB_BLANK_POWERDOWN);
|
|
fbi->flags &= ~FBINFO_MISC_USEREVENT;
|
|
console_unlock();
|
|
|
|
pos.x = 0;
|
|
pos.y = 0;
|
|
ret = set_window_position(vout, &pos);
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev, "failed to set fb position "
|
|
"to starting point\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Init display channel through fb API */
|
|
var.yoffset = 0;
|
|
var.activate |= FB_ACTIVATE_FORCE;
|
|
console_lock();
|
|
fbi->flags |= FBINFO_MISC_USEREVENT;
|
|
ret = fb_set_var(fbi, &var);
|
|
fbi->flags &= ~FBINFO_MISC_USEREVENT;
|
|
console_unlock();
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"ERR:%s fb_set_var ret:%d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = set_window_position(vout, &vout->win_pos);
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev, "failed to set fb position\n");
|
|
return ret;
|
|
}
|
|
|
|
if (vout->linear_bypass_pp || vout->tiled_bypass_pp)
|
|
display_buf_size = fbi->fix.line_length * fbi->var.yres_virtual;
|
|
else
|
|
display_buf_size = fbi->fix.line_length * fbi->var.yres;
|
|
for (i = 0; i < fb_num; i++)
|
|
vout->disp_bufs[i] = fbi->fix.smem_start + i * display_buf_size;
|
|
if (vout->tiled_bypass_pp) {
|
|
size = PAGE_ALIGN(vout->task.input.crop.w *
|
|
vout->task.input.crop.h *
|
|
fmt_to_bpp(vout->task.output.format)/8);
|
|
if (size > vout->vdoa_output[0].size) {
|
|
for (i = 0; i < VDOA_FB_BUFS; i++) {
|
|
buf = &vout->vdoa_output[i];
|
|
if (buf->vaddr)
|
|
free_dma_buf(vout, buf);
|
|
buf->size = size;
|
|
ret = alloc_dma_buf(vout, buf);
|
|
if (ret < 0)
|
|
goto err;
|
|
}
|
|
}
|
|
for (i = fb_num; i < (fb_num + VDOA_FB_BUFS); i++)
|
|
vout->disp_bufs[i] =
|
|
vout->vdoa_output[i - fb_num].paddr;
|
|
}
|
|
vout->fb_smem_len = fbi->fix.smem_len;
|
|
vout->fb_smem_start = fbi->fix.smem_start;
|
|
if (fb_base != fbi->fix.smem_start) {
|
|
v4l2_dbg(1, debug, vout->vfd->v4l2_dev,
|
|
"realloc fb mem size:0x%x@0x%lx,old paddr @0x%x\n",
|
|
fbi->fix.smem_len, fbi->fix.smem_start, fb_base);
|
|
}
|
|
|
|
/* fill black when video config changed */
|
|
color = colorspaceofpixel(vout->task.output.format) == YUV_CS ?
|
|
UYVY_BLACK : RGB_BLACK;
|
|
if (IS_PLANAR_PIXEL_FORMAT(vout->task.output.format)) {
|
|
size = display_buf_size * 8 /
|
|
fmt_to_bpp(vout->task.output.format);
|
|
memset(fbi->screen_base, Y_BLACK, size);
|
|
memset(fbi->screen_base + size, UV_BLACK,
|
|
display_buf_size - size);
|
|
} else {
|
|
pixel = (u32 *)fbi->screen_base;
|
|
for (i = 0; i < ((display_buf_size * fb_num) >> 2); i++)
|
|
*pixel++ = color;
|
|
}
|
|
console_lock();
|
|
fbi->flags |= FBINFO_MISC_USEREVENT;
|
|
ret = fb_blank(fbi, FB_BLANK_UNBLANK);
|
|
fbi->flags &= ~FBINFO_MISC_USEREVENT;
|
|
console_unlock();
|
|
vout->release = false;
|
|
|
|
return ret;
|
|
err:
|
|
for (j = i - 1; j >= 0; j--) {
|
|
buf = &vout->vdoa_output[j];
|
|
if (buf->vaddr)
|
|
free_dma_buf(vout, buf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static inline void wait_for_vsync(struct mxc_vout_output *vout)
|
|
{
|
|
struct fb_info *fbi = vout->fbi;
|
|
mm_segment_t old_fs;
|
|
|
|
if (fbi->fbops->fb_ioctl) {
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
fbi->fbops->fb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
|
|
(unsigned long)NULL);
|
|
set_fs(old_fs);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void release_disp_output(struct mxc_vout_output *vout)
|
|
{
|
|
struct fb_info *fbi = vout->fbi;
|
|
struct mxcfb_pos pos;
|
|
|
|
if (vout->release)
|
|
return;
|
|
console_lock();
|
|
fbi->flags |= FBINFO_MISC_USEREVENT;
|
|
fb_blank(fbi, FB_BLANK_POWERDOWN);
|
|
fbi->flags &= ~FBINFO_MISC_USEREVENT;
|
|
console_unlock();
|
|
|
|
/* restore pos to 0,0 avoid fb pan display hang? */
|
|
pos.x = 0;
|
|
pos.y = 0;
|
|
set_window_position(vout, &pos);
|
|
|
|
if (get_ipu_channel(fbi) == MEM_BG_SYNC) {
|
|
console_lock();
|
|
fbi->fix.smem_start = vout->disp_bufs[0];
|
|
fbi->flags |= FBINFO_MISC_USEREVENT;
|
|
fb_blank(fbi, FB_BLANK_UNBLANK);
|
|
fbi->flags &= ~FBINFO_MISC_USEREVENT;
|
|
console_unlock();
|
|
|
|
}
|
|
|
|
vout->release = true;
|
|
}
|
|
|
|
static int mxc_vidioc_streamon(struct file *file, void *fh,
|
|
enum v4l2_buf_type i)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
struct videobuf_queue *q = &vout->vbq;
|
|
int ret;
|
|
|
|
if (q->streaming) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"video output already run\n");
|
|
ret = -EBUSY;
|
|
goto done;
|
|
}
|
|
|
|
if (deinterlace_3_field(vout) && list_is_singular(&q->stream)) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"deinterlacing: need queue 2 frame before streamon\n");
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
ret = config_disp_output(vout);
|
|
if (ret < 0) {
|
|
v4l2_err(vout->vfd->v4l2_dev,
|
|
"Config display output failed\n");
|
|
goto done;
|
|
}
|
|
|
|
hrtimer_init(&vout->timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
|
|
vout->timer.function = mxc_vout_timer_handler;
|
|
vout->timer_stop = true;
|
|
vout->frame_count = 0;
|
|
vout->vdi_frame_cnt = 0;
|
|
|
|
vout->start_ktime = hrtimer_cb_get_time(&vout->timer);
|
|
|
|
vout->pre1_vb = NULL;
|
|
vout->pre2_vb = NULL;
|
|
|
|
ret = videobuf_streamon(q);
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vidioc_streamoff(struct file *file, void *fh,
|
|
enum v4l2_buf_type i)
|
|
{
|
|
struct mxc_vout_output *vout = fh;
|
|
struct videobuf_queue *q = &vout->vbq;
|
|
int ret = 0;
|
|
|
|
if (q->streaming) {
|
|
flush_workqueue(vout->v4l_wq);
|
|
|
|
hrtimer_cancel(&vout->timer);
|
|
|
|
/*
|
|
* Wait for 2 vsyncs to make sure
|
|
* frames are drained on triple
|
|
* buffer.
|
|
*/
|
|
wait_for_vsync(vout);
|
|
wait_for_vsync(vout);
|
|
|
|
release_disp_output(vout);
|
|
|
|
ret = videobuf_streamoff(&vout->vbq);
|
|
}
|
|
INIT_LIST_HEAD(&vout->queue_list);
|
|
INIT_LIST_HEAD(&vout->active_list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct v4l2_ioctl_ops mxc_vout_ioctl_ops = {
|
|
.vidioc_querycap = mxc_vidioc_querycap,
|
|
.vidioc_enum_fmt_vid_out = mxc_vidioc_enum_fmt_vid_out,
|
|
.vidioc_g_fmt_vid_out = mxc_vidioc_g_fmt_vid_out,
|
|
.vidioc_s_fmt_vid_out = mxc_vidioc_s_fmt_vid_out,
|
|
.vidioc_cropcap = mxc_vidioc_cropcap,
|
|
.vidioc_g_crop = mxc_vidioc_g_crop,
|
|
.vidioc_s_crop = mxc_vidioc_s_crop,
|
|
.vidioc_queryctrl = mxc_vidioc_queryctrl,
|
|
.vidioc_g_ctrl = mxc_vidioc_g_ctrl,
|
|
.vidioc_s_ctrl = mxc_vidioc_s_ctrl,
|
|
.vidioc_reqbufs = mxc_vidioc_reqbufs,
|
|
.vidioc_querybuf = mxc_vidioc_querybuf,
|
|
.vidioc_qbuf = mxc_vidioc_qbuf,
|
|
.vidioc_dqbuf = mxc_vidioc_dqbuf,
|
|
.vidioc_streamon = mxc_vidioc_streamon,
|
|
.vidioc_streamoff = mxc_vidioc_streamoff,
|
|
};
|
|
|
|
static const struct v4l2_file_operations mxc_vout_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.mmap = mxc_vout_mmap,
|
|
.open = mxc_vout_open,
|
|
.release = mxc_vout_release,
|
|
};
|
|
|
|
static struct video_device mxc_vout_template = {
|
|
.name = "MXC Video Output",
|
|
.fops = &mxc_vout_fops,
|
|
.ioctl_ops = &mxc_vout_ioctl_ops,
|
|
.release = video_device_release,
|
|
};
|
|
|
|
static struct videobuf_queue_ops mxc_vout_vbq_ops = {
|
|
.buf_setup = mxc_vout_buffer_setup,
|
|
.buf_prepare = mxc_vout_buffer_prepare,
|
|
.buf_release = mxc_vout_buffer_release,
|
|
.buf_queue = mxc_vout_buffer_queue,
|
|
};
|
|
|
|
static void mxc_vout_free_output(struct mxc_vout_dev *dev)
|
|
{
|
|
int i;
|
|
int j;
|
|
struct mxc_vout_output *vout;
|
|
struct video_device *vfd;
|
|
|
|
for (i = 0; i < dev->out_num; i++) {
|
|
vout = dev->out[i];
|
|
vfd = vout->vfd;
|
|
if (vout->vdoa_work.vaddr)
|
|
free_dma_buf(vout, &vout->vdoa_work);
|
|
for (j = 0; j < VDOA_FB_BUFS; j++) {
|
|
if (vout->vdoa_output[j].vaddr)
|
|
free_dma_buf(vout, &vout->vdoa_output[j]);
|
|
}
|
|
if (vfd) {
|
|
if (!video_is_registered(vfd))
|
|
video_device_release(vfd);
|
|
else
|
|
video_unregister_device(vfd);
|
|
}
|
|
kfree(vout);
|
|
}
|
|
}
|
|
|
|
static int mxc_vout_setup_output(struct mxc_vout_dev *dev)
|
|
{
|
|
struct videobuf_queue *q;
|
|
struct fb_info *fbi;
|
|
struct mxc_vout_output *vout;
|
|
int i, ret = 0;
|
|
|
|
update_display_setting();
|
|
|
|
/* all output/overlay based on fb */
|
|
for (i = 0; i < num_registered_fb; i++) {
|
|
fbi = registered_fb[i];
|
|
|
|
vout = kzalloc(sizeof(struct mxc_vout_output), GFP_KERNEL);
|
|
if (!vout) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
dev->out[dev->out_num] = vout;
|
|
dev->out_num++;
|
|
|
|
vout->fbi = fbi;
|
|
vout->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
vout->vfd = video_device_alloc();
|
|
if (!vout->vfd) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
*vout->vfd = mxc_vout_template;
|
|
vout->vfd->debug = debug;
|
|
vout->vfd->v4l2_dev = &dev->v4l2_dev;
|
|
vout->vfd->lock = &vout->mutex;
|
|
vout->vfd->vfl_dir = VFL_DIR_TX;
|
|
|
|
mutex_init(&vout->mutex);
|
|
mutex_init(&vout->task_lock);
|
|
mutex_init(&vout->accs_lock);
|
|
|
|
strlcpy(vout->vfd->name, fbi->fix.id, sizeof(vout->vfd->name));
|
|
|
|
video_set_drvdata(vout->vfd, vout);
|
|
|
|
if (video_register_device(vout->vfd,
|
|
VFL_TYPE_GRABBER, video_nr + i) < 0) {
|
|
ret = -ENODEV;
|
|
break;
|
|
}
|
|
|
|
q = &vout->vbq;
|
|
q->dev = dev->dev;
|
|
spin_lock_init(&vout->vbq_lock);
|
|
videobuf_queue_dma_contig_init(q, &mxc_vout_vbq_ops, q->dev,
|
|
&vout->vbq_lock, vout->type, V4L2_FIELD_NONE,
|
|
sizeof(struct videobuf_buffer), vout, NULL);
|
|
|
|
v4l2_info(vout->vfd->v4l2_dev, "V4L2 device registered as %s\n",
|
|
video_device_node_name(vout->vfd));
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct mxc_vout_dev *dev;
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
dev->dev = &pdev->dev;
|
|
dev->dev->dma_mask = kmalloc(sizeof(*dev->dev->dma_mask), GFP_KERNEL);
|
|
*dev->dev->dma_mask = DMA_BIT_MASK(32);
|
|
dev->dev->coherent_dma_mask = DMA_BIT_MASK(32);
|
|
|
|
ret = v4l2_device_register(dev->dev, &dev->v4l2_dev);
|
|
if (ret) {
|
|
dev_err(dev->dev, "v4l2_device_register failed\n");
|
|
goto free_dev;
|
|
}
|
|
|
|
ret = mxc_vout_setup_output(dev);
|
|
if (ret < 0)
|
|
goto rel_vdev;
|
|
|
|
return 0;
|
|
|
|
rel_vdev:
|
|
mxc_vout_free_output(dev);
|
|
v4l2_device_unregister(&dev->v4l2_dev);
|
|
free_dev:
|
|
kfree(dev);
|
|
return ret;
|
|
}
|
|
|
|
static int mxc_vout_remove(struct platform_device *pdev)
|
|
{
|
|
struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
|
|
struct mxc_vout_dev *dev = container_of(v4l2_dev, struct
|
|
mxc_vout_dev, v4l2_dev);
|
|
|
|
mxc_vout_free_output(dev);
|
|
v4l2_device_unregister(v4l2_dev);
|
|
kfree(dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mxc_v4l2_dt_ids[] = {
|
|
{ .compatible = "fsl,mxc_v4l2_output", },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
static struct platform_driver mxc_vout_driver = {
|
|
.driver = {
|
|
.name = "mxc_v4l2_output",
|
|
.of_match_table = mxc_v4l2_dt_ids,
|
|
},
|
|
.probe = mxc_vout_probe,
|
|
.remove = mxc_vout_remove,
|
|
};
|
|
|
|
static int __init mxc_vout_init(void)
|
|
{
|
|
if (platform_driver_register(&mxc_vout_driver) != 0) {
|
|
printk(KERN_ERR VOUT_NAME ":Could not register Video driver\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void mxc_vout_cleanup(void)
|
|
{
|
|
platform_driver_unregister(&mxc_vout_driver);
|
|
}
|
|
|
|
module_init(mxc_vout_init);
|
|
module_exit(mxc_vout_cleanup);
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_DESCRIPTION("V4L2-driver for MXC video output");
|
|
MODULE_LICENSE("GPL");
|