Merge branch 'drm-dwhdmi-devel' of git://ftp.arm.linux.org.uk/~rmk/linux-arm into...
[pandora-kernel.git] / drivers / gpu / drm / bridge / dw_hdmi.c
index eeb0169..0083d4e 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/hdmi.h>
 #include <linux/mutex.h>
 #include <linux/of_device.h>
+#include <linux/spinlock.h>
 
 #include <drm/drm_of.h>
 #include <drm/drmP.h>
@@ -81,7 +82,6 @@ static const u16 csc_coeff_rgb_in_eitu709[3][4] = {
 };
 
 struct hdmi_vmode {
-       bool mdvi;
        bool mdataenablepolarity;
 
        unsigned int mpixelclock;
@@ -122,9 +122,18 @@ struct dw_hdmi {
 
        struct i2c_adapter *ddc;
        void __iomem *regs;
+       bool sink_is_hdmi;
+       bool sink_has_audio;
 
+       struct mutex mutex;             /* for state below and previous_mode */
+       bool disabled;                  /* DRM has disabled our bridge */
+
+       spinlock_t audio_lock;
        struct mutex audio_mutex;
        unsigned int sample_rate;
+       unsigned int audio_cts;
+       unsigned int audio_n;
+       bool audio_enable;
        int ratio;
 
        void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
@@ -346,7 +355,11 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
        dev_dbg(hdmi->dev, "%s: samplerate=%ukHz ratio=%d pixelclk=%luMHz N=%d cts=%d\n",
                __func__, sample_rate, ratio, pixel_clk, n, cts);
 
-       hdmi_set_cts_n(hdmi, cts, n);
+       spin_lock_irq(&hdmi->audio_lock);
+       hdmi->audio_n = n;
+       hdmi->audio_cts = cts;
+       hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0);
+       spin_unlock_irq(&hdmi->audio_lock);
 }
 
 static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi)
@@ -365,6 +378,38 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
        mutex_unlock(&hdmi->audio_mutex);
 }
 
+void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
+{
+       mutex_lock(&hdmi->audio_mutex);
+       hdmi->sample_rate = rate;
+       hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock,
+                                hdmi->sample_rate, hdmi->ratio);
+       mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate);
+
+void dw_hdmi_audio_enable(struct dw_hdmi *hdmi)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&hdmi->audio_lock, flags);
+       hdmi->audio_enable = true;
+       hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
+       spin_unlock_irqrestore(&hdmi->audio_lock, flags);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_audio_enable);
+
+void dw_hdmi_audio_disable(struct dw_hdmi *hdmi)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&hdmi->audio_lock, flags);
+       hdmi->audio_enable = false;
+       hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0);
+       spin_unlock_irqrestore(&hdmi->audio_lock, flags);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_audio_disable);
+
 /*
  * this submodule is responsible for the video data synchronization.
  * for example, for RGB 4:4:4 input, the data map is defined as
@@ -695,9 +740,9 @@ static int hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
        return 0;
 }
 
-static void dw_hdmi_phy_enable_power(struct dw_hdmi *hdmi, u8 enable)
+static void dw_hdmi_phy_enable_powerdown(struct dw_hdmi *hdmi, bool enable)
 {
-       hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
+       hdmi_mask_writeb(hdmi, !enable, HDMI_PHY_CONF0,
                         HDMI_PHY_CONF0_PDZ_OFFSET,
                         HDMI_PHY_CONF0_PDZ_MASK);
 }
@@ -837,7 +882,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
        /* REMOVE CLK TERM */
        hdmi_phy_i2c_write(hdmi, 0x8000, 0x05);  /* CKCALCTRL */
 
-       dw_hdmi_phy_enable_power(hdmi, 1);
+       dw_hdmi_phy_enable_powerdown(hdmi, false);
 
        /* toggle TMDS enable */
        dw_hdmi_phy_enable_tmds(hdmi, 0);
@@ -872,18 +917,17 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
 static int dw_hdmi_phy_init(struct dw_hdmi *hdmi)
 {
        int i, ret;
-       bool cscon = false;
+       bool cscon;
 
        /*check csc whether needed activated in HDMI mode */
-       cscon = (is_color_space_conversion(hdmi) &&
-                       !hdmi->hdmi_data.video_mode.mdvi);
+       cscon = hdmi->sink_is_hdmi && is_color_space_conversion(hdmi);
 
        /* HDMI Phy spec says to do the phy initialization sequence twice */
        for (i = 0; i < 2; i++) {
                dw_hdmi_phy_sel_data_en_pol(hdmi, 1);
                dw_hdmi_phy_sel_interface_control(hdmi, 0);
                dw_hdmi_phy_enable_tmds(hdmi, 0);
-               dw_hdmi_phy_enable_power(hdmi, 0);
+               dw_hdmi_phy_enable_powerdown(hdmi, true);
 
                /* Enable CSC */
                ret = hdmi_phy_configure(hdmi, 0, 8, cscon);
@@ -1052,9 +1096,9 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
                HDMI_FC_INVIDCONF_IN_I_P_INTERLACED :
                HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE;
 
-       inv_val |= (vmode->mdvi ?
-               HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE :
-               HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE);
+       inv_val |= hdmi->sink_is_hdmi ?
+               HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE :
+               HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE;
 
        hdmi_writeb(hdmi, inv_val, HDMI_FC_INVIDCONF);
 
@@ -1100,7 +1144,7 @@ static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi)
                return;
 
        dw_hdmi_phy_enable_tmds(hdmi, 0);
-       dw_hdmi_phy_enable_power(hdmi, 0);
+       dw_hdmi_phy_enable_powerdown(hdmi, true);
 
        hdmi->phy_enabled = false;
 }
@@ -1181,10 +1225,8 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
 
        if (!hdmi->vic) {
                dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n");
-               hdmi->hdmi_data.video_mode.mdvi = true;
        } else {
                dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic);
-               hdmi->hdmi_data.video_mode.mdvi = false;
        }
 
        if ((hdmi->vic == 6) || (hdmi->vic == 7) ||
@@ -1195,18 +1237,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        else
                hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
 
-       if ((hdmi->vic == 10) || (hdmi->vic == 11) ||
-           (hdmi->vic == 12) || (hdmi->vic == 13) ||
-           (hdmi->vic == 14) || (hdmi->vic == 15) ||
-           (hdmi->vic == 25) || (hdmi->vic == 26) ||
-           (hdmi->vic == 27) || (hdmi->vic == 28) ||
-           (hdmi->vic == 29) || (hdmi->vic == 30) ||
-           (hdmi->vic == 35) || (hdmi->vic == 36) ||
-           (hdmi->vic == 37) || (hdmi->vic == 38))
-               hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1;
-       else
-               hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
-
+       hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0;
        hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0;
 
        /* TODO: Get input format from IPU (via FB driver interface) */
@@ -1230,18 +1261,22 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        /* HDMI Initialization Step B.3 */
        dw_hdmi_enable_video_path(hdmi);
 
-       /* not for DVI mode */
-       if (hdmi->hdmi_data.video_mode.mdvi) {
-               dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
-       } else {
-               dev_dbg(hdmi->dev, "%s CEA mode\n", __func__);
+       if (hdmi->sink_has_audio) {
+               dev_dbg(hdmi->dev, "sink has audio support\n");
 
                /* HDMI Initialization Step E - Configure audio */
                hdmi_clk_regenerator_update_pixel_clock(hdmi);
                hdmi_enable_audio_clk(hdmi);
+       }
+
+       /* not for DVI mode */
+       if (hdmi->sink_is_hdmi) {
+               dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__);
 
                /* HDMI Initialization Step F - Configure AVI InfoFrame */
                hdmi_config_AVI(hdmi, mode);
+       } else {
+               dev_dbg(hdmi->dev, "%s DVI mode\n", __func__);
        }
 
        hdmi_video_packetize(hdmi);
@@ -1250,7 +1285,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        hdmi_tx_hdcp_config(hdmi);
 
        dw_hdmi_clear_overflow(hdmi);
-       if (hdmi->cable_plugin && !hdmi->hdmi_data.video_mode.mdvi)
+       if (hdmi->cable_plugin && hdmi->sink_is_hdmi)
                hdmi_enable_overflow_interrupts(hdmi);
 
        return 0;
@@ -1343,10 +1378,12 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
 {
        struct dw_hdmi *hdmi = bridge->driver_private;
 
-       dw_hdmi_setup(hdmi, mode);
+       mutex_lock(&hdmi->mutex);
 
        /* Store the display mode for plugin/DKMS poweron events */
        memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode));
+
+       mutex_unlock(&hdmi->mutex);
 }
 
 static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
@@ -1360,14 +1397,20 @@ static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
 {
        struct dw_hdmi *hdmi = bridge->driver_private;
 
+       mutex_lock(&hdmi->mutex);
+       hdmi->disabled = true;
        dw_hdmi_poweroff(hdmi);
+       mutex_unlock(&hdmi->mutex);
 }
 
 static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
 {
        struct dw_hdmi *hdmi = bridge->driver_private;
 
+       mutex_lock(&hdmi->mutex);
        dw_hdmi_poweron(hdmi);
+       hdmi->disabled = false;
+       mutex_unlock(&hdmi->mutex);
 }
 
 static void dw_hdmi_bridge_nop(struct drm_bridge *bridge)
@@ -1390,7 +1433,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
        struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
                                             connector);
        struct edid *edid;
-       int ret;
+       int ret = 0;
 
        if (!hdmi->ddc)
                return 0;
@@ -1400,6 +1443,8 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
                dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
                        edid->width_cm, edid->height_cm);
 
+               hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+               hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
                drm_mode_connector_update_edid_property(connector, edid);
                ret = drm_add_edid_modes(connector, edid);
                kfree(edid);
@@ -1407,7 +1452,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
                dev_dbg(hdmi->dev, "failed to get edid\n");
        }
 
-       return 0;
+       return ret;
 }
 
 static enum drm_mode_status
@@ -1418,6 +1463,10 @@ dw_hdmi_connector_mode_valid(struct drm_connector *connector,
                                           struct dw_hdmi, connector);
        enum drm_mode_status mode_status = MODE_OK;
 
+       /* We don't support double-clocked modes */
+       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+               return MODE_BAD;
+
        if (hdmi->plat_data->mode_valid)
                mode_status = hdmi->plat_data->mode_valid(connector, mode);
 
@@ -1452,7 +1501,7 @@ static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
        .best_encoder = dw_hdmi_connector_best_encoder,
 };
 
-struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
+static struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
        .enable = dw_hdmi_bridge_enable,
        .disable = dw_hdmi_bridge_disable,
        .pre_enable = dw_hdmi_bridge_nop,
@@ -1484,20 +1533,20 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
        phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0);
 
        if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+               hdmi_modb(hdmi, ~phy_int_pol, HDMI_PHY_HPD, HDMI_PHY_POL0);
+               mutex_lock(&hdmi->mutex);
                if (phy_int_pol & HDMI_PHY_HPD) {
                        dev_dbg(hdmi->dev, "EVENT=plugin\n");
 
-                       hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0);
-
-                       dw_hdmi_poweron(hdmi);
+                       if (!hdmi->disabled)
+                               dw_hdmi_poweron(hdmi);
                } else {
                        dev_dbg(hdmi->dev, "EVENT=plugout\n");
 
-                       hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD,
-                                 HDMI_PHY_POL0);
-
-                       dw_hdmi_poweroff(hdmi);
+                       if (!hdmi->disabled)
+                               dw_hdmi_poweroff(hdmi);
                }
+               mutex_unlock(&hdmi->mutex);
                drm_helper_hpd_irq_event(hdmi->bridge->dev);
        }
 
@@ -1565,8 +1614,11 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        hdmi->sample_rate = 48000;
        hdmi->ratio = 100;
        hdmi->encoder = encoder;
+       hdmi->disabled = true;
 
+       mutex_init(&hdmi->mutex);
        mutex_init(&hdmi->audio_mutex);
+       spin_lock_init(&hdmi->audio_lock);
 
        of_property_read_u32(np, "reg-io-width", &val);