/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; bool input_crop; }; 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; static int mxc_vidioc_s_input_crop(struct mxc_vout_output *vout, const struct v4l2_crop *crop); static int mxc_vidioc_g_input_crop(struct mxc_vout_output *vout, struct v4l2_crop *crop); /* 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 = 0; 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->input_crop = false; 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 long mxc_vout_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct mxc_vout_output *vout = file->private_data; struct v4l2_crop crop; int ret; switch (cmd) { case VIDIOC_S_INPUT_CROP: if (copy_from_user(&crop, (void __user *)arg, sizeof(struct v4l2_crop))) return -EFAULT; ret = mxc_vidioc_s_input_crop(vout, &crop); break; case VIDIOC_G_INPUT_CROP: mxc_vidioc_g_input_crop(vout, &crop); ret = copy_from_user((void __user *)arg, &crop, sizeof(struct v4l2_crop)); break; default: ret = video_ioctl2(file, cmd, arg); } 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->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT; cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; 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; 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); 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; 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 (vout->input_crop == false) { 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; } 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; ret = mxc_vout_try_task(vout); if (!ret) { 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 mxc_vidioc_s_input_crop(struct mxc_vout_output *vout, const struct v4l2_crop *crop) { int ret = 0; if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) return -EINVAL; if (crop->c.width < 0 || crop->c.height < 0) return -EINVAL; vout->task.input.crop.pos.x = crop->c.left; vout->task.input.crop.pos.y = crop->c.top; vout->task.input.crop.w = crop->c.width; vout->task.input.crop.h = crop->c.height; vout->input_crop = true; memcpy(&vout->in_rect, &vout->task.input.crop, sizeof(vout->in_rect)); return ret; } static int mxc_vidioc_g_input_crop(struct mxc_vout_output *vout, struct v4l2_crop *crop) { int ret = 0; crop->c.left = vout->task.input.crop.pos.x; crop->c.top = vout->task.input.crop.pos.y; crop->c.width = vout->task.input.crop.w; crop->c.height = vout->task.input.crop.h; return ret; } 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 = mxc_vout_ioctl, .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->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");