summaryrefslogtreecommitdiff
path: root/drivers/video/mxc/mxc_ipuv3_fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/mxc/mxc_ipuv3_fb.c')
-rw-r--r--drivers/video/mxc/mxc_ipuv3_fb.c269
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, &timestamp, 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 = &blank;
+ 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