diff options
Diffstat (limited to 'drivers/video/mxc/mxc_ipuv3_fb.c')
-rw-r--r-- | drivers/video/mxc/mxc_ipuv3_fb.c | 269 |
1 files changed, 257 insertions, 12 deletions
diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c index 0818a328c4de..429b7b863c24 100644 --- a/drivers/video/mxc/mxc_ipuv3_fb.c +++ b/drivers/video/mxc/mxc_ipuv3_fb.c @@ -46,6 +46,8 @@ #include <linux/mxcfb.h> #include <linux/uaccess.h> #include <linux/fsl_devices.h> +#include <linux/earlysuspend.h> +#include <linux/workqueue.h> #include <asm/mach-types.h> #include <mach/ipu-v3.h> #include "mxc_dispdrv.h" @@ -61,6 +63,8 @@ * Structure containing the MXC specific framebuffer information. */ struct mxcfb_info { + spinlock_t lock; + struct fb_info *fbi; int default_bpp; int cur_blank; int next_blank; @@ -80,6 +84,7 @@ struct mxcfb_info { uint32_t alpha_mem_len; uint32_t ipu_ch_irq; uint32_t ipu_ch_nf_irq; + uint32_t ipu_vsync_pre_irq; uint32_t ipu_alp_ch_irq; uint32_t cur_ipu_buf; uint32_t cur_ipu_alpha_buf; @@ -97,6 +102,18 @@ struct mxcfb_info { struct mxc_dispdrv_handle *dispdrv; struct fb_var_screeninfo cur_var; + + int vsync_pre_report_active; + ktime_t vsync_pre_timestamp; + ktime_t vsync_nf_timestamp; + struct workqueue_struct *vsync_pre_queue; + struct work_struct vsync_pre_work; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend fbdrv_earlysuspend; +#endif + int panel_width_mm; + int panel_height_mm; }; struct mxcfb_pfmt { @@ -135,6 +152,11 @@ enum { static bool g_dp_in_use[2]; LIST_HEAD(fb_alloc_list); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mxcfb_early_suspend(struct early_suspend *h); +static void mxcfb_later_resume(struct early_suspend *h); +#endif + /* Return default standard(RGB) pixel format */ static uint32_t bpp_to_pixfmt(int bpp) { @@ -251,6 +273,7 @@ static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id) static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id); static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id); +static irqreturn_t mxcfb_vsync_pre_irq_handler(int irq, void *dev_id); static int mxcfb_blank(int blank, struct fb_info *info); static int mxcfb_map_video_memory(struct fb_info *fbi); static int mxcfb_unmap_video_memory(struct fb_info *fbi); @@ -894,8 +917,16 @@ static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) var->pixclock); } - var->height = -1; - var->width = -1; + if (var->height == 0 && mxc_fbi->panel_height_mm) + var->height = mxc_fbi->panel_height_mm; + else if (var->height == 0) + var->height = -1; + + if (var->width == 0 && mxc_fbi->panel_width_mm) + var->width = mxc_fbi->panel_width_mm; + else if (var->width == 0) + var->width = -1; + var->grayscale = 0; return 0; @@ -947,6 +978,35 @@ static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, return ret; } +static void mxcfb_vsync_pre_work(struct work_struct *work) +{ + struct mxcfb_info *mxc_fbi = + container_of(work, struct mxcfb_info, vsync_pre_work); + char *envp[2]; + char buf[64]; + unsigned long flags; + + spin_lock_irqsave(&mxc_fbi->lock, flags); + snprintf(buf, sizeof(buf), "VSYNC=%llu", + ktime_to_ns(mxc_fbi->vsync_pre_timestamp)); + spin_unlock_irqrestore(&mxc_fbi->lock, flags); + + envp[0] = buf; + envp[1] = NULL; + kobject_uevent_env(&mxc_fbi->fbi->dev->kobj, KOBJ_CHANGE, envp); +} + +static void mxcfb_enable_vsync_pre(struct mxcfb_info *mxc_fbi) +{ + ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); + ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); +} + +static void mxcfb_disable_vsync_pre(struct mxcfb_info *mxc_fbi) +{ + ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_vsync_pre_irq); +} + /* * Function to handle custom ioctls for MXC framebuffer. * @@ -1136,6 +1196,8 @@ static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) } case MXCFB_WAIT_FOR_VSYNC: { + unsigned long flags; + unsigned long long timestamp; if (mxc_fbi->ipu_ch == MEM_FG_SYNC) { /* BG should poweron */ struct mxcfb_info *bg_mxcfbi = NULL; @@ -1163,7 +1225,18 @@ static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq); retval = wait_for_completion_interruptible_timeout( - &mxc_fbi->vsync_complete, 1 * HZ); + &mxc_fbi->vsync_complete, HZ/10); + + spin_lock_irqsave(&mxc_fbi->lock, flags); + timestamp = ktime_to_ns(mxc_fbi->vsync_nf_timestamp); + spin_unlock_irqrestore(&mxc_fbi->lock, flags); + dev_vdbg(fbi->device, "ts = %llu", timestamp); + + if (copy_to_user((void *)arg, ×tamp, sizeof(timestamp))) { + retval = -EFAULT; + break; + } + if (retval == 0) { dev_err(fbi->device, "MXCFB_WAIT_FOR_VSYNC: timeout %d\n", @@ -1331,6 +1404,27 @@ static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) break; } + case MXCFB_ENABLE_VSYNC_EVENT: + { + int enable; + if (get_user(enable, (int __user *)arg)) + return -EFAULT; + + if (mxc_fbi->ipu_ch == MEM_FG_SYNC) + return -EINVAL; + + /* Only can control the vsync state when screen is not blank */ + if (mxc_fbi->vsync_pre_report_active != enable && + mxc_fbi->cur_blank == FB_BLANK_UNBLANK) { + if (enable) + mxcfb_enable_vsync_pre(mxc_fbi); + else + mxcfb_disable_vsync_pre(mxc_fbi); + } + + mxc_fbi->vsync_pre_report_active = enable; + break; + } case MXCFB_CSC_UPDATE: { struct mxcfb_csc_matrix csc; @@ -1648,10 +1742,25 @@ static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id) struct fb_info *fbi = dev_id; struct mxcfb_info *mxc_fbi = fbi->par; + spin_lock(&mxc_fbi->lock); + mxc_fbi->vsync_nf_timestamp = ktime_get(); + spin_unlock(&mxc_fbi->lock); complete(&mxc_fbi->vsync_complete); return IRQ_HANDLED; } +static irqreturn_t mxcfb_vsync_pre_irq_handler(int irq, void *dev_id) +{ + struct fb_info *fbi = dev_id; + struct mxcfb_info *mxc_fbi = fbi->par; + + spin_lock(&mxc_fbi->lock); + mxc_fbi->vsync_pre_timestamp = ktime_get(); + spin_unlock(&mxc_fbi->lock); + queue_work(mxc_fbi->vsync_pre_queue, &mxc_fbi->vsync_pre_work); + return IRQ_HANDLED; +} + static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) { struct fb_info *fbi = dev_id; @@ -1664,7 +1773,7 @@ static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id) /* * Suspends the framebuffer and blanks the screen. Power management support */ -static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +static int mxcfb_core_suspend(struct platform_device *pdev, pm_message_t state) { struct fb_info *fbi = platform_get_drvdata(pdev); struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; @@ -1696,9 +1805,23 @@ static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) } /* + * Suspends the framebuffer and blanks the screen. Power management support + */ +static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (strstr(mxc_fbi->dispdrv->drv->name, "hdmi")) + return mxcfb_core_suspend(pdev, state); + + return 0; +} + +/* * Resumes the framebuffer and unblanks the screen. Power management support */ -static int mxcfb_resume(struct platform_device *pdev) +static int mxcfb_core_resume(struct platform_device *pdev) { struct fb_info *fbi = platform_get_drvdata(pdev); struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; @@ -1721,6 +1844,20 @@ static int mxcfb_resume(struct platform_device *pdev) } /* + * Resumes the framebuffer and unblanks the screen. Power management support + */ +static int mxcfb_resume(struct platform_device *pdev) +{ + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par; + + if (strstr(mxc_fbi->dispdrv->drv->name, "hdmi")) + return mxcfb_core_resume(pdev); + + return 0; +} + +/* * Main framebuffer functions */ @@ -2066,6 +2203,18 @@ static int mxcfb_register(struct fb_info *fbi) } ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq); + if (mxcfbi->ipu_vsync_pre_irq != -1) { + if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_vsync_pre_irq, + mxcfb_vsync_pre_irq_handler, 0, + MXCFB_NAME, fbi) != 0) { + dev_err(fbi->device, "Error registering VSYNC irq " + "handler.\n"); + ret = -EBUSY; + goto err2; + } + ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_vsync_pre_irq); + } + if (mxcfbi->ipu_alp_ch_irq != -1) if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, mxcfb_alpha_irq_handler, IPU_IRQF_ONESHOT, @@ -2073,7 +2222,7 @@ static int mxcfb_register(struct fb_info *fbi) dev_err(fbi->device, "Error registering alpha irq " "handler.\n"); ret = -EBUSY; - goto err2; + goto err3; } if (!mxcfbi->late_init) { @@ -2085,7 +2234,7 @@ static int mxcfb_register(struct fb_info *fbi) console_unlock(); if (ret < 0) { dev_err(fbi->device, "Error fb_set_var ret:%d\n", ret); - goto err3; + goto err4; } if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { @@ -2095,7 +2244,7 @@ static int mxcfb_register(struct fb_info *fbi) if (ret < 0) { dev_err(fbi->device, "Error fb_blank ret:%d\n", ret); - goto err4; + goto err5; } } } else { @@ -2112,13 +2261,12 @@ static int mxcfb_register(struct fb_info *fbi) } } - ret = register_framebuffer(fbi); if (ret < 0) - goto err5; + goto err6; return ret; -err5: +err6: if (mxcfbi->next_blank == FB_BLANK_UNBLANK) { console_lock(); if (!mxcfbi->late_init) @@ -2130,10 +2278,13 @@ err5: } console_unlock(); } +err5: err4: -err3: if (mxcfbi->ipu_alp_ch_irq != -1) ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi); +err3: + if (mxcfbi->ipu_vsync_pre_irq != -1) + ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_vsync_pre_irq, fbi); err2: ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi); err1: @@ -2180,6 +2331,8 @@ static int mxcfb_setup_overlay(struct platform_device *pdev, mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF; mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK; mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF; + /* Vsync-pre irq is invalid for overlay channel. */ + mxcfbi_fg->ipu_vsync_pre_irq = -1; mxcfbi_fg->ipu_ch = MEM_FG_SYNC; mxcfbi_fg->ipu_di = -1; mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt; @@ -2255,6 +2408,8 @@ static int mxcfb_probe(struct platform_device *pdev) struct fb_info *fbi; struct mxcfb_info *mxcfbi; struct resource *res; + struct device *disp_dev; + char buf[32]; int ret = 0; /* Initialize FB structures */ @@ -2269,9 +2424,14 @@ static int mxcfb_probe(struct platform_device *pdev) goto get_fb_option_failed; mxcfbi = (struct mxcfb_info *)fbi->par; + spin_lock_init(&mxcfbi->lock); + mxcfbi->fbi = fbi; mxcfbi->ipu_int_clk = plat_data->int_clk; mxcfbi->late_init = plat_data->late_init; mxcfbi->first_set_par = true; + mxcfbi->panel_width_mm = plat_data->panel_width_mm; + mxcfbi->panel_height_mm = plat_data->panel_height_mm; + ret = mxcfb_dispdrv_init(pdev, fbi); if (ret < 0) goto init_dispdrv_failed; @@ -2304,6 +2464,9 @@ static int mxcfb_probe(struct platform_device *pdev) mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF; mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK; mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF; + mxcfbi->ipu_vsync_pre_irq = mxcfbi->ipu_di ? + IPU_IRQ_VSYNC_PRE_1 : + IPU_IRQ_VSYNC_PRE_0; mxcfbi->ipu_ch = MEM_BG_SYNC; /* Unblank the primary fb only by default */ if (pdev->id == 0) @@ -2346,6 +2509,9 @@ static int mxcfb_probe(struct platform_device *pdev) mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF; mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK; mxcfbi->ipu_alp_ch_irq = -1; + mxcfbi->ipu_vsync_pre_irq = mxcfbi->ipu_di ? + IPU_IRQ_VSYNC_PRE_1 : + IPU_IRQ_VSYNC_PRE_0; mxcfbi->ipu_ch = MEM_DC_SYNC; mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN; @@ -2366,6 +2532,34 @@ static int mxcfb_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Error %d on creating file for disp " " device propety\n", ret); + disp_dev = mxc_dispdrv_getdev(mxcfbi->dispdrv); + if (disp_dev) { + ret = sysfs_create_link(&fbi->dev->kobj, + &disp_dev->kobj, "disp_dev"); + if (ret) + dev_err(&pdev->dev, + "Error %d on creating file\n", ret); + } + + INIT_WORK(&mxcfbi->vsync_pre_work, mxcfb_vsync_pre_work); + + snprintf(buf, sizeof(buf), "mxcfb%d-vsync-pre", fbi->node); + mxcfbi->vsync_pre_queue = create_singlethread_workqueue(buf); + if (mxcfbi->vsync_pre_queue == NULL) { + dev_err(fbi->device, + "Failed to alloc vsync-pre workqueue\n"); + ret = -ENOMEM; + goto workqueue_alloc_failed; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + mxcfbi->fbdrv_earlysuspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + mxcfbi->fbdrv_earlysuspend.suspend = mxcfb_early_suspend; + mxcfbi->fbdrv_earlysuspend.resume = mxcfb_later_resume; + mxcfbi->fbdrv_earlysuspend.data = pdev; + register_early_suspend(&mxcfbi->fbdrv_earlysuspend); +#endif + #ifdef CONFIG_LOGO fb_prepare_logo(fbi, 0); fb_show_logo(fbi, 0); @@ -2373,6 +2567,7 @@ static int mxcfb_probe(struct platform_device *pdev) return 0; +workqueue_alloc_failed: mxcfb_setupoverlay_failed: mxcfb_register_failed: get_ipu_failed: @@ -2394,6 +2589,10 @@ static int mxcfb_remove(struct platform_device *pdev) if (!fbi) return 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&mxc_fbi->fbdrv_earlysuspend); +#endif + device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property); device_remove_file(fbi->dev, &dev_attr_fsl_disp_property); mxcfb_blank(FB_BLANK_POWERDOWN, fbi); @@ -2430,6 +2629,52 @@ static struct platform_driver mxcfb_driver = { .resume = mxcfb_resume, }; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mxcfb_early_suspend(struct early_suspend *h) +{ + struct platform_device *pdev = (struct platform_device *)h->data; + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + struct fb_event event; + int blank = FB_BLANK_POWERDOWN; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return; + + if (strstr(mxcfbi->dispdrv->drv->name, "hdmi")) { + /* Only black the hdmi fb due to audio dependency */ + memset(fbi->screen_base, 0, fbi->fix.smem_len); + return; + } + + mxcfb_core_suspend(pdev, state); + event.info = fbi; + event.data = ␣ + fb_notifier_call_chain(FB_EVENT_BLANK, &event); +} + +static void mxcfb_later_resume(struct early_suspend *h) +{ + struct platform_device *pdev = (struct platform_device *)h->data; + struct fb_info *fbi = platform_get_drvdata(pdev); + struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par; + struct fb_event event; + + if (mxcfbi->ipu_ch == MEM_FG_SYNC) + return; + + /* HDMI resume function has been called */ + if (strstr(mxcfbi->dispdrv->drv->name, "hdmi")) + return; + + mxcfb_core_resume(pdev); + event.info = fbi; + event.data = &mxcfbi->next_blank; + fb_notifier_call_chain(FB_EVENT_BLANK, &event); +} +#endif + /*! * Main entry function for the framebuffer. The function registers the power * management callback functions with the kernel and also registers the MXCFB |