OMAPDSS: HDMI: clean up PHY power handling
authorTomi Valkeinen <tomi.valkeinen@ti.com>
Thu, 6 Jun 2013 10:08:35 +0000 (13:08 +0300)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Mon, 17 Jun 2013 11:00:54 +0000 (14:00 +0300)
The TRM tells to set PHY to TXON only after getting LINK_CONNECT, and to
set PHY to OFF or LDOON after getting LINK_DISCONNECT, in order to avoid
damage to the PHY.

We don't currently do it quite like that. Instead of using the HDMI
interrupts, we use HPD signal. This works, but is not actually quite
correct, as HPD comes at a different time than LINK_CONNECT and
LINK_DISCONNECT interrupts. Also, the HPD GPIO is a property of the TPD
level shifter, not HDMI IP, so handling the GPIO in the HDMI driver is
wrong.

This patch implements the PHY power handling correctly, using the
interrupts.

There is a corner case that causes some additional difficulties: we may
get both LINK_CONNECT and LINK_DISCONNECT interrupts at the same time.
This is handled in the code by retrying: turning off the PHY, clearing
the interrupt status, and re-enabling the PHY. This causes a new
LINK_CONNECT interrupt to happen if a cable is connected.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
drivers/video/omap2/dss/hdmi.c
drivers/video/omap2/dss/ti_hdmi.h
drivers/video/omap2/dss/ti_hdmi_4xxx_ip.c
drivers/video/omap2/dss/ti_hdmi_4xxx_ip.h

index 0f93903d440932708f2b5bc68db795fbb6e3f656..0fb3662e28b60a878313208da0d25f759095a8bd 100644 (file)
@@ -1072,6 +1072,12 @@ static int omapdss_hdmihw_probe(struct platform_device *pdev)
        if (IS_ERR(hdmi.ip_data.base_wp))
                return PTR_ERR(hdmi.ip_data.base_wp);
 
+       hdmi.ip_data.irq = platform_get_irq(pdev, 0);
+       if (hdmi.ip_data.irq < 0) {
+               DSSERR("platform_get_irq failed\n");
+               return -ENODEV;
+       }
+
        r = hdmi_get_clocks(pdev);
        if (r) {
                DSSERR("can't get clocks\n");
index 216aa704f9d72ef08b06a82865d5b8fda03cc876..2f7fbc8945789520bda4359f10841fa207d0ff38 100644 (file)
@@ -155,6 +155,7 @@ struct hdmi_ip_data {
        unsigned long   core_av_offset;
        unsigned long   pll_offset;
        unsigned long   phy_offset;
+       int             irq;
        const struct ti_hdmi_ip_ops *ops;
        struct hdmi_config cfg;
        struct hdmi_pll_info pll_data;
index e18b222ed739081819c35ef6276e7b7094068fa4..052f2db35d625b09fe0433b41b4251ff03f062bb 100644 (file)
@@ -38,6 +38,9 @@
 #include "dss.h"
 #include "dss_features.h"
 
+#define HDMI_IRQ_LINK_CONNECT          (1 << 25)
+#define HDMI_IRQ_LINK_DISCONNECT       (1 << 26)
+
 static inline void hdmi_write_reg(void __iomem *base_addr,
                                const u16 idx, u32 val)
 {
@@ -233,37 +236,39 @@ void ti_hdmi_4xxx_pll_disable(struct hdmi_ip_data *ip_data)
        hdmi_set_pll_pwr(ip_data, HDMI_PLLPWRCMD_ALLOFF);
 }
 
-static int hdmi_check_hpd_state(struct hdmi_ip_data *ip_data)
+static irqreturn_t hdmi_irq_handler(int irq, void *data)
 {
-       bool hpd;
-       int r;
-
-       mutex_lock(&ip_data->lock);
-
-       hpd = gpio_get_value(ip_data->hpd_gpio);
+       struct hdmi_ip_data *ip_data = data;
+       void __iomem *wp_base = hdmi_wp_base(ip_data);
+       u32 irqstatus;
+
+       irqstatus = hdmi_read_reg(wp_base, HDMI_WP_IRQSTATUS);
+       hdmi_write_reg(wp_base, HDMI_WP_IRQSTATUS, irqstatus);
+       /* flush posted write */
+       hdmi_read_reg(wp_base, HDMI_WP_IRQSTATUS);
+
+       if ((irqstatus & HDMI_IRQ_LINK_CONNECT) &&
+                       irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
+               /*
+                * If we get both connect and disconnect interrupts at the same
+                * time, turn off the PHY, clear interrupts, and restart, which
+                * raises connect interrupt if a cable is connected, or nothing
+                * if cable is not connected.
+                */
+               hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF);
 
-       if (hpd)
-               r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_TXON);
-       else
-               r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON);
+               hdmi_write_reg(wp_base, HDMI_WP_IRQSTATUS,
+                       HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT);
+               /* flush posted write */
+               hdmi_read_reg(wp_base, HDMI_WP_IRQSTATUS);
 
-       if (r) {
-               DSSERR("Failed to %s PHY TX power\n",
-                               hpd ? "enable" : "disable");
-               goto err;
+               hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON);
+       } else if (irqstatus & HDMI_IRQ_LINK_CONNECT) {
+               hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_TXON);
+       } else if (irqstatus & HDMI_IRQ_LINK_DISCONNECT) {
+               hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON);
        }
 
-err:
-       mutex_unlock(&ip_data->lock);
-       return r;
-}
-
-static irqreturn_t hpd_irq_handler(int irq, void *data)
-{
-       struct hdmi_ip_data *ip_data = data;
-
-       hdmi_check_hpd_state(ip_data);
-
        return IRQ_HANDLED;
 }
 
@@ -272,6 +277,12 @@ int ti_hdmi_4xxx_phy_enable(struct hdmi_ip_data *ip_data)
        u16 r = 0;
        void __iomem *phy_base = hdmi_phy_base(ip_data);
 
+       hdmi_write_reg(hdmi_wp_base(ip_data), HDMI_WP_IRQENABLE_CLR,
+                       0xffffffff);
+
+       hdmi_write_reg(hdmi_wp_base(ip_data), HDMI_WP_IRQSTATUS,
+                       HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT);
+
        r = hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_LDOON);
        if (r)
                return r;
@@ -297,29 +308,23 @@ int ti_hdmi_4xxx_phy_enable(struct hdmi_ip_data *ip_data)
        /* Write to phy address 3 to change the polarity control */
        REG_FLD_MOD(phy_base, HDMI_TXPHY_PAD_CFG_CTRL, 0x1, 27, 27);
 
-       r = request_threaded_irq(gpio_to_irq(ip_data->hpd_gpio),
-                                NULL, hpd_irq_handler,
-                                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
-                                IRQF_ONESHOT, "hpd", ip_data);
+       r = request_threaded_irq(ip_data->irq, NULL, hdmi_irq_handler,
+                                IRQF_ONESHOT, "OMAP HDMI", ip_data);
        if (r) {
-               DSSERR("HPD IRQ request failed\n");
+               DSSERR("HDMI IRQ request failed\n");
                hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF);
                return r;
        }
 
-       r = hdmi_check_hpd_state(ip_data);
-       if (r) {
-               free_irq(gpio_to_irq(ip_data->hpd_gpio), ip_data);
-               hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF);
-               return r;
-       }
+       hdmi_write_reg(hdmi_wp_base(ip_data), HDMI_WP_IRQENABLE_SET,
+                       HDMI_IRQ_LINK_CONNECT | HDMI_IRQ_LINK_DISCONNECT);
 
        return 0;
 }
 
 void ti_hdmi_4xxx_phy_disable(struct hdmi_ip_data *ip_data)
 {
-       free_irq(gpio_to_irq(ip_data->hpd_gpio), ip_data);
+       free_irq(ip_data->irq, ip_data);
 
        hdmi_set_phy_pwr(ip_data, HDMI_PHYPWRCMD_OFF);
 }
index 8366ae19e82eece140606c7178e6a70216fbec87..6ef2f929a76d7fa59be556adc6aee7141bfb0809 100644 (file)
@@ -33,6 +33,7 @@
 #define HDMI_WP_IRQSTATUS                      0x28
 #define HDMI_WP_PWR_CTRL                       0x40
 #define HDMI_WP_IRQENABLE_SET                  0x2C
+#define HDMI_WP_IRQENABLE_CLR                  0x30
 #define HDMI_WP_VIDEO_CFG                      0x50
 #define HDMI_WP_VIDEO_SIZE                     0x60
 #define HDMI_WP_VIDEO_TIMING_H                 0x68