diff options
Diffstat (limited to 'drivers/video/mxc/mxc_ipuv3_fb.c')
-rw-r--r-- | drivers/video/mxc/mxc_ipuv3_fb.c | 227 |
1 files changed, 222 insertions, 5 deletions
diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c index 745c485016d0..8a052e0433d3 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; @@ -78,6 +82,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; @@ -95,6 +100,15 @@ struct mxcfb_info { struct mxc_dispdrv_handle *dispdrv; struct fb_var_screeninfo cur_var; + + int vsync_pre_report_active; + ktime_t vsync_pre_timestamp; + struct workqueue_struct *vsync_pre_queue; + struct work_struct vsync_pre_work; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend fbdrv_earlysuspend; +#endif }; struct mxcfb_alloc_list { @@ -114,6 +128,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 + static uint32_t bpp_to_pixfmt(struct fb_info *fbi) { uint32_t pixfmt = 0; @@ -156,6 +175,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); @@ -889,6 +909,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. * @@ -1273,6 +1322,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; + } default: retval = -EINVAL; } @@ -1580,6 +1650,19 @@ static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id) 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; @@ -1592,7 +1675,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; @@ -1624,9 +1707,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; @@ -1649,6 +1746,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 */ @@ -1980,6 +2091,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, @@ -1987,7 +2110,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; } mxcfb_check_var(&fbi->var, fbi); @@ -2015,12 +2138,15 @@ static int mxcfb_register(struct fb_info *fbi) ret = register_framebuffer(fbi); if (ret < 0) - goto err3; + goto err4; return ret; -err3: +err4: 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: @@ -2067,6 +2193,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; @@ -2142,6 +2270,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; ret = mxcfb_option_setup(pdev); @@ -2156,6 +2286,8 @@ static int mxcfb_probe(struct platform_device *pdev) } mxcfbi = (struct mxcfb_info *)fbi->par; + spin_lock_init(&mxcfbi->lock); + mxcfbi->fbi = fbi; mxcfbi->ipu_int_clk = plat_data->int_clk; ret = mxcfb_dispdrv_init(pdev, fbi); if (ret < 0) @@ -2187,6 +2319,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) @@ -2228,6 +2363,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; @@ -2248,6 +2386,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); @@ -2255,6 +2421,7 @@ static int mxcfb_probe(struct platform_device *pdev) return 0; +workqueue_alloc_failed: mxcfb_setupoverlay_failed: mxcfb_register_failed: get_ipu_failed: @@ -2276,6 +2443,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); @@ -2312,6 +2483,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 |