summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev/mxc/mxc_hdmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/fbdev/mxc/mxc_hdmi.c')
-rw-r--r--drivers/video/fbdev/mxc/mxc_hdmi.c156
1 files changed, 88 insertions, 68 deletions
diff --git a/drivers/video/fbdev/mxc/mxc_hdmi.c b/drivers/video/fbdev/mxc/mxc_hdmi.c
index 9e844805c0d2..9dd02c0c7f51 100644
--- a/drivers/video/fbdev/mxc/mxc_hdmi.c
+++ b/drivers/video/fbdev/mxc/mxc_hdmi.c
@@ -72,6 +72,18 @@
#define YCBCR422_8BITS 3
#define XVYCC444 4
+static bool only_cea;
+module_param(only_cea, bool, 0644);
+MODULE_PARM_DESC(only_cea, "Allow only CEA modes");
+
+#if 0
+/* rely solely on the HPD pin for hot plug detection */
+#undef HDMI_DVI_STAT
+#define HDMI_DVI_STAT 2
+
+#undef HDMI_DVI_IH_STAT
+#define HDMI_DVI_IH_STAT 1
+#endif
/*
* We follow a flowchart which is in the "Synopsys DesignWare Courses
* HDMI Transmitter Controller User Guide, 1.30a", section 3.1
@@ -162,11 +174,11 @@ struct mxc_hdmi {
bool dft_mode_set;
char *dft_mode_str;
int default_bpp;
- u8 latest_intr_stat;
bool irq_enabled;
spinlock_t irq_lock;
bool phy_enabled;
struct fb_videomode default_mode;
+ struct fb_videomode previous_mode;
struct fb_videomode previous_non_vga_mode;
bool requesting_vga_for_initialization;
@@ -936,6 +948,9 @@ static u8 hdmi_edid_i2c_read(struct mxc_hdmi *hdmi,
return data;
}
+static int keepalive=1;
+module_param(keepalive, int, 0644);
+MODULE_PARM_DESC(keepalive, "Keep HDMI alive (don't detect disconnect)");
/* "Power-down enable (active low)"
* That mean that power up == 1! */
@@ -1261,9 +1276,6 @@ static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi)
|| (hdmi->blank != FB_BLANK_UNBLANK))
return;
- if (!hdmi->hdmi_data.video_mode.mDVI)
- hdmi_enable_overflow_interrupts();
-
/*check csc whether needed activated in HDMI mode */
cscon = (isColorSpaceConversion(hdmi) &&
!hdmi->hdmi_data.video_mode.mDVI);
@@ -1280,6 +1292,8 @@ static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi)
}
hdmi->phy_enabled = true;
+ if (!hdmi->hdmi_data.video_mode.mDVI)
+ hdmi_enable_overflow_interrupts();
}
static void hdmi_config_AVI(struct mxc_hdmi *hdmi)
@@ -1802,7 +1816,7 @@ static void mxc_hdmi_edid_rebuild_modelist(struct mxc_hdmi *hdmi)
mode = &hdmi->fbi->monspecs.modedb[i];
if (!(mode->vmode & FB_VMODE_INTERLACED) &&
- (mxc_edid_mode_to_vic(mode) != 0)) {
+ (!only_cea || mxc_edid_mode_to_vic(mode))) {
dev_dbg(&hdmi->pdev->dev, "Added mode %d:", i);
dev_dbg(&hdmi->pdev->dev,
@@ -1945,13 +1959,19 @@ static void mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi)
}
/* HDMI Initialization Steps D, E, F */
+ /*
+ * Without this the kernel hangs during boot with HDMI attached.
+ * It looks like we get an overflow IRQ storm.
+ */
+ mxc_hdmi_set_mode_to_vga_dvi(hdmi);
switch (edid_status) {
case HDMI_EDID_SUCCESS:
mxc_hdmi_edid_rebuild_modelist(hdmi);
break;
- /* Nothing to do if EDID same */
+ /* Rebuild even if they're the same in case only_cea changed */
case HDMI_EDID_SAME:
+ mxc_hdmi_edid_rebuild_modelist(hdmi);
break;
case HDMI_EDID_FAIL:
@@ -2006,74 +2026,70 @@ static void hotplug_worker(struct work_struct *work)
struct delayed_work *delay_work = to_delayed_work(work);
struct mxc_hdmi *hdmi =
container_of(delay_work, struct mxc_hdmi, hotplug_work);
- u32 phy_int_stat, phy_int_pol, phy_int_mask;
- u8 val;
+ u32 hdmi_phy_stat0, hdmi_phy_pol0, hdmi_phy_mask0;
unsigned long flags;
char event_string[32];
+ int isalive = 0;
char *envp[] = { event_string, NULL };
- phy_int_stat = hdmi->latest_intr_stat;
- phy_int_pol = hdmi_readb(HDMI_PHY_POL0);
- dev_dbg(&hdmi->pdev->dev, "phy_int_stat=0x%x, phy_int_pol=0x%x\n",
- phy_int_stat, phy_int_pol);
+ hdmi_phy_stat0 = hdmi_readb(HDMI_PHY_STAT0);
+ hdmi_phy_pol0 = hdmi_readb(HDMI_PHY_POL0);
+
+ dev_dbg(&hdmi->pdev->dev, "hdmi_phy_stat0=0x%x, hdmi_phy_pol0=0x%x\n",
+ hdmi_phy_stat0, hdmi_phy_pol0);
+
+ /* Make HPD intr active low to capture unplug event or
+ * active high to capture plugin event */
+ hdmi_writeb((HDMI_DVI_STAT & ~hdmi_phy_stat0), HDMI_PHY_POL0);
/* check cable status */
- if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) {
- /* cable connection changes */
- if (phy_int_pol & HDMI_PHY_HPD) {
- /* Plugin event */
- dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n");
- mxc_hdmi_cable_connected(hdmi);
-
- /* Make HPD intr active low to capture unplug event */
- val = hdmi_readb(HDMI_PHY_POL0);
- val &= ~HDMI_PHY_HPD;
- hdmi_writeb(val, HDMI_PHY_POL0);
-
- hdmi_set_cable_state(1);
-
- sprintf(event_string, "EVENT=plugin");
- kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
-#ifdef CONFIG_MXC_HDMI_CEC
- mxc_hdmi_cec_handle(0x80);
-#endif
- } else if (!(phy_int_pol & HDMI_PHY_HPD)) {
- /* Plugout event */
- dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n");
- hdmi_set_cable_state(0);
- mxc_hdmi_abort_stream();
- mxc_hdmi_cable_disconnected(hdmi);
+ if (hdmi_phy_stat0 & HDMI_DVI_STAT) {
+ /* Plugin event */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n");
+ mxc_hdmi_cable_connected(hdmi);
- /* Make HPD intr active high to capture plugin event */
- val = hdmi_readb(HDMI_PHY_POL0);
- val |= HDMI_PHY_HPD;
- hdmi_writeb(val, HDMI_PHY_POL0);
+ hdmi_set_cable_state(1);
- sprintf(event_string, "EVENT=plugout");
- kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+ sprintf(event_string, "EVENT=plugin");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
#ifdef CONFIG_MXC_HDMI_CEC
- mxc_hdmi_cec_handle(0x100);
+ mxc_hdmi_cec_handle(0x80);
+#endif
+ if (keepalive)
+ hdmi_writeb(HDMI_DVI_STAT, HDMI_PHY_POL0);
+ isalive=1;
+ } else if (!keepalive) {
+ /* Plugout event */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n");
+ hdmi_set_cable_state(0);
+ mxc_hdmi_abort_stream();
+ mxc_hdmi_cable_disconnected(hdmi);
+
+ sprintf(event_string, "EVENT=plugout");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+#ifdef CONFIG_MXC_HDMI_CEC
+ mxc_hdmi_cec_handle(0x100);
#endif
- } else
- dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n");
}
/* Lock here to ensure full powerdown sequence
* completed before next interrupt processed */
spin_lock_irqsave(&hdmi->irq_lock, flags);
- /* Re-enable HPD interrupts */
- phy_int_mask = hdmi_readb(HDMI_PHY_MASK0);
- phy_int_mask &= ~HDMI_PHY_HPD;
- hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0);
+ if (!(keepalive && isalive)) {
+ /* Re-enable HPD interrupts */
+ hdmi_phy_mask0 = hdmi_readb(HDMI_PHY_MASK0);
+ hdmi_phy_mask0 &= ~HDMI_DVI_STAT;
+ hdmi_writeb(hdmi_phy_mask0, HDMI_PHY_MASK0);
- /* Unmute interrupts */
- hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+ /* Unmute interrupts */
+ hdmi_writeb(~HDMI_DVI_IH_STAT, HDMI_IH_MUTE_PHY_STAT0);
- if (hdmi_readb(HDMI_IH_FC_STAT2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK)
- mxc_hdmi_clear_overflow(hdmi);
+ if (hdmi_readb(HDMI_IH_FC_STAT2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK)
+ mxc_hdmi_clear_overflow(hdmi);
+ }
spin_unlock_irqrestore(&hdmi->irq_lock, flags);
}
@@ -2096,7 +2112,7 @@ static void hdcp_hdp_worker(struct work_struct *work)
static irqreturn_t mxc_hdmi_hotplug(int irq, void *data)
{
struct mxc_hdmi *hdmi = data;
- u8 val, intr_stat;
+ u8 val;
unsigned long flags;
spin_lock_irqsave(&hdmi->irq_lock, flags);
@@ -2118,25 +2134,22 @@ static irqreturn_t mxc_hdmi_hotplug(int irq, void *data)
* HDMI registers.
*/
/* Capture status - used in hotplug_worker ISR */
- intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0);
-
- if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+ if (hdmi_readb(HDMI_IH_PHY_STAT0) & HDMI_DVI_IH_STAT) {
dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n");
- hdmi->latest_intr_stat = intr_stat;
/* Mute interrupts until handled */
val = hdmi_readb(HDMI_IH_MUTE_PHY_STAT0);
- val |= HDMI_IH_MUTE_PHY_STAT0_HPD;
+ val |= HDMI_DVI_IH_STAT;
hdmi_writeb(val, HDMI_IH_MUTE_PHY_STAT0);
val = hdmi_readb(HDMI_PHY_MASK0);
- val |= HDMI_PHY_HPD;
+ val |= HDMI_DVI_STAT;
hdmi_writeb(val, HDMI_PHY_MASK0);
/* Clear Hotplug interrupts */
- hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(HDMI_DVI_IH_STAT, HDMI_IH_PHY_STAT0);
schedule_delayed_work(&(hdmi->hotplug_work), msecs_to_jiffies(20));
}
@@ -2168,6 +2181,9 @@ static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event)
dev_dbg(&hdmi->pdev->dev, "%s - video mode changed\n", __func__);
+ /* Save mode as 'previous_mode' so that we can know if mode changed. */
+ memcpy(&hdmi->previous_mode, &m, sizeof(struct fb_videomode));
+
hdmi->vic = 0;
if (!hdmi->requesting_vga_for_initialization) {
/* Save mode if this isn't the result of requesting
@@ -2288,13 +2304,13 @@ static void mxc_hdmi_fb_registered(struct mxc_hdmi *hdmi)
HDMI_PHY_I2CM_CTLINT_ADDR);
/* enable cable hot plug irq */
- hdmi_writeb((u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0);
+ hdmi_writeb((u8)~HDMI_DVI_STAT, HDMI_PHY_MASK0);
/* Clear Hotplug interrupts */
- hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(HDMI_DVI_IH_STAT, HDMI_IH_PHY_STAT0);
/* Unmute interrupts */
- hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+ hdmi_writeb(~HDMI_DVI_IH_STAT, HDMI_IH_MUTE_PHY_STAT0);
hdmi->fb_reg = true;
@@ -2307,6 +2323,7 @@ static int mxc_hdmi_fb_event(struct notifier_block *nb,
{
struct fb_event *event = v;
struct mxc_hdmi *hdmi = container_of(nb, struct mxc_hdmi, nb);
+ struct fb_videomode *mode;
if (strcmp(event->info->fix.id, hdmi->fbi->fix.id))
return 0;
@@ -2326,7 +2343,10 @@ static int mxc_hdmi_fb_event(struct notifier_block *nb,
case FB_EVENT_MODE_CHANGE:
dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_MODE_CHANGE\n");
- if (hdmi->fb_reg)
+ mode = (struct fb_videomode *)event->data;
+ if ((hdmi->fb_reg) &&
+ (mode != NULL) &&
+ !fb_mode_is_equal(&hdmi->previous_mode, mode))
mxc_hdmi_setup(hdmi, val);
break;
@@ -2625,10 +2645,10 @@ static int mxc_hdmi_disp_init(struct mxc_dispdrv_handle *disp,
/* Configure registers related to HDMI interrupt
* generation before registering IRQ. */
- hdmi_writeb(HDMI_PHY_HPD, HDMI_PHY_POL0);
+ hdmi_writeb(HDMI_DVI_STAT, HDMI_PHY_POL0);
/* Clear Hotplug interrupts */
- hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(HDMI_DVI_IH_STAT, HDMI_IH_PHY_STAT0);
hdmi->nb.notifier_call = mxc_hdmi_fb_event;
ret = fb_register_client(&hdmi->nb);