]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
Revert "Revert "video: tegra: dp: add hotplug support""
authorAlex Waterman <alexw@nvidia.com>
Thu, 2 Jul 2015 17:36:06 +0000 (10:36 -0700)
committerPavan Kunapuli <pkunapuli@nvidia.com>
Fri, 3 Jul 2015 06:29:41 +0000 (23:29 -0700)
This reverts commit d28f169329addd2eba1af30a3dafb8608f8f5506
because this change is causing boot failure on norrin_t132 L4T
platform.

Bug 1661607

Signed-off-by: Alex Waterman <alexw@nvidia.com>
Change-Id: I06592af84b0ede05038479ebd1359a4b72ec1839
Reviewed-on: http://git-master/r/765398
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
Tested-by: Bharat Nihalani <bnihalani@nvidia.com>
drivers/video/tegra/dc/dc_priv.h
drivers/video/tegra/dc/dp.c
drivers/video/tegra/dc/dp.h
drivers/video/tegra/dc/dpaux_regs.h

index 86ae1865e95b37e63a0ef5f87e0e89d179e04169..88952d92f2d4ec3057ae665583518bd7fe97f2fa 100644 (file)
@@ -43,7 +43,8 @@
 #endif
 
 #define tegra_dc_hotplug_supported(dc) (dc && dc->out ? \
-               (dc->out->hotplug_gpio >= 0) : 0)
+               (dc->out->hotplug_gpio >= 0 || \
+               dc->out->type == TEGRA_DC_OUT_DP) : 0)
 
 static inline int tegra_dc_io_start(struct tegra_dc *dc)
 {
index 80516720c5913c26425eac984346b0b3af978475..5ccda4fe42bd3c3cbf96808fc9ec0fddfc0ea908 100644 (file)
@@ -44,7 +44,7 @@
 #include "fake_panel.h"
 #endif /*CONFIG_TEGRA_DC_FAKE_PANEL_SUPPORT*/
 
-static bool tegra_dp_debug;
+static bool tegra_dp_debug = true;
 module_param(tegra_dp_debug, bool, 0644);
 MODULE_PARM_DESC(tegra_dp_debug, "Enable to print all link configs");
 
@@ -64,7 +64,10 @@ static int tegra_dp_full_lt(struct tegra_dc_dp_data *dp);
 static bool tegra_dc_dp_calc_config(struct tegra_dc_dp_data *dp,
        const struct tegra_dc_mode *mode,
        struct tegra_dc_dp_link_config *cfg);
-
+static bool tegra_dc_dp_hpd(struct tegra_dc_dp_data *dp);
+static void tegra_dp_hpd_worker(struct work_struct *work);
+static void tegra_dp_hotplug_notify(struct tegra_dc_dp_data *dp,
+                                       bool is_asserted);
 
 static inline u32 tegra_dpaux_readl(struct tegra_dc_dp_data *dp, u32 reg)
 {
@@ -889,6 +892,80 @@ static const struct file_operations link_speed_fops = {
        .release        = single_release,
 };
 
+/* show current hpd state */
+static int tegra_dp_hotplug_dbg_show(struct seq_file *m, void *unused)
+{
+       struct tegra_dc_dp_data *dp = m->private;
+       struct tegra_dc *dc = dp->dc;
+
+       if (WARN_ON(!dp || !dc || !dc->out))
+               return -EINVAL;
+
+       seq_printf(m, "dp hpd state: %d\n", dc->out->hotplug_state);
+
+       return 0;
+}
+
+/*
+ * sw control for hpd.
+ * 0 is normal state, hw drives hpd.
+ * -1 is force deassert, sw drives hpd.
+ * 1 is force assert, sw drives hpd.
+ * before releasing to hw, sw must ensure hpd state is normal, i.e. 0
+ */
+static ssize_t tegra_dp_hotplug_dbg_write(struct file *file,
+                               const char __user *addr,
+                               size_t len, loff_t *pos)
+{
+       struct seq_file *m = file->private_data;
+       struct tegra_dc_dp_data *dp = m->private;
+       struct tegra_dc *dc = dp->dc;
+       long new_hpd_state;
+       int ret;
+
+       if (WARN_ON(!dp || !dc || !dc->out))
+               return -EINVAL;
+
+       ret = kstrtol_from_user(addr, len, 10, &new_hpd_state);
+       if (ret < 0)
+               return ret;
+
+       if (dc->out->hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT &&
+               new_hpd_state != TEGRA_HPD_STATE_FORCE_DEASSERT &&
+               tegra_dc_hotplug_supported(dc)) {
+               enable_irq(dp->irq);
+       } else if (dc->out->hotplug_state != TEGRA_HPD_STATE_FORCE_DEASSERT &&
+               new_hpd_state == TEGRA_HPD_STATE_FORCE_DEASSERT &&
+               tegra_dc_hotplug_supported(dc)) {
+               disable_irq(dp->irq);
+       }
+
+       dc->out->hotplug_state = new_hpd_state;
+
+       /*
+        * sw controlled plug/unplug.
+        * wait for any already executing hpd worker thread.
+        * No debounce delay, schedule immediately
+        */
+       cancel_work_sync(&dp->hpd_work);
+       schedule_work(&dp->hpd_work);
+
+       return len;
+}
+
+static int tegra_dp_hotplug_dbg_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, tegra_dp_hotplug_dbg_show, inode->i_private);
+}
+
+static const struct file_operations tegra_dp_hotplug_dbg_fops = {
+       .open = tegra_dp_hotplug_dbg_open,
+       .read = seq_read,
+       .write = tegra_dp_hotplug_dbg_write,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
 static struct dentry *dpdir;
 
 static void tegra_dc_dp_debug_create(struct tegra_dc_dp_data *dp)
@@ -909,6 +986,12 @@ static void tegra_dc_dp_debug_create(struct tegra_dc_dp_data *dp)
                &link_speed_fops);
        if (!retval)
                goto free_out;
+       if (tegra_dc_is_ext_dp_panel(dp->dc)) {
+               retval = debugfs_create_file("hotplug", S_IRUGO, dpdir, dp,
+                                               &tegra_dp_hotplug_dbg_fops);
+               if (!retval)
+                       goto free_out;
+       }
 
        return;
 free_out:
@@ -1563,6 +1646,7 @@ static void tegra_dp_lt_worker(struct work_struct *work)
                return;
 
        tegra_dc_io_start(dp->dc);
+       mutex_lock(&dp->hpd_lock);
        tegra_sor_clk_enable(dp->sor);
        tegra_dpaux_clk_enable(dp);
 
@@ -1572,9 +1656,19 @@ static void tegra_dp_lt_worker(struct work_struct *work)
 
        tegra_dpaux_clk_disable(dp);
        tegra_sor_clk_disable(dp->sor);
+       mutex_unlock(&dp->hpd_lock);
        tegra_dc_io_end(dp->dc);
 }
 
+static void tegra_dp_hpd_irq_handler(struct tegra_dc_dp_data *dp)
+{
+       trace_printk("dp: tegra_dp_hpd_irq_handler was called\n");
+       if (tegra_dc_is_ext_dp_panel(dp->dc))
+               schedule_work(&dp->hpd_work);
+       else
+               complete_all(&dp->hpd_plug);
+}
+
 static irqreturn_t tegra_dp_irq(int irq, void *ptr)
 {
        struct tegra_dc_dp_data *dp = ptr;
@@ -1590,16 +1684,17 @@ static irqreturn_t tegra_dp_irq(int irq, void *ptr)
        status = tegra_dpaux_readl(dp, DPAUX_INTR_AUX);
        tegra_dpaux_writel(dp, DPAUX_INTR_AUX, status);
 
-       if (status & DPAUX_INTR_AUX_PLUG_EVENT_PENDING)
-               complete_all(&dp->hpd_plug);
+       tegra_dc_io_end(dc);
+
+       if (status & (DPAUX_INTR_AUX_PLUG_EVENT_PENDING |
+                       DPAUX_INTR_AUX_UNPLUG_EVENT_PENDING))
+               tegra_dp_hpd_irq_handler(dp);
+       else if (status & DPAUX_INTR_AUX_IRQ_EVENT_PENDING)
+               schedule_work(&dp->lt_work);
 
        if (status & DPAUX_INTR_AUX_TX_DONE_PENDING)
                complete_all(&dp->aux_tx);
 
-       if (status & DPAUX_INTR_AUX_IRQ_EVENT_PENDING)
-               schedule_work(&dp->lt_work);
-
-       tegra_dc_io_end(dc);
        return IRQ_HANDLED;
 }
 
@@ -1764,11 +1859,13 @@ static int tegra_dc_dp_init(struct tegra_dc *dc)
        #endif
 
        INIT_WORK(&dp->lt_work, tegra_dp_lt_worker);
+       INIT_WORK(&dp->hpd_work, tegra_dp_hpd_worker);
        init_completion(&dp->hpd_plug);
        init_completion(&dp->aux_tx);
 
        mutex_init(&dp->dpaux_lock);
        mutex_init(&dp->lt_lock);
+       mutex_init(&dp->hpd_lock);
 
        tegra_dc_set_outdata(dc, dp);
        tegra_dc_dp_debug_create(dp);
@@ -1818,36 +1915,6 @@ static void tegra_dp_hpd_config(struct tegra_dc_dp_data *dp)
 #undef TEGRA_DP_HPD_UNPLUG_MIN_US
 }
 
-static int tegra_dp_hpd_plug(struct tegra_dc_dp_data *dp)
-{
-#define TEGRA_DP_HPD_PLUG_TIMEOUT_MS   500
-       u32 val;
-       int err = 0;
-
-       might_sleep();
-
-       if (!tegra_platform_is_silicon()) {
-               msleep(TEGRA_DP_HPD_PLUG_TIMEOUT_MS);
-               return 0;
-       }
-
-       INIT_COMPLETION(dp->hpd_plug);
-       tegra_dp_int_en(dp, DPAUX_INTR_EN_AUX_PLUG_EVENT);
-
-       val = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT);
-       if (likely(val & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED))
-               err = 0;
-       else if (!wait_for_completion_timeout(&dp->hpd_plug,
-               msecs_to_jiffies(TEGRA_DP_HPD_PLUG_TIMEOUT_MS)))
-               err = -ENODEV;
-
-       tegra_dp_int_dis(dp, DPAUX_INTR_EN_AUX_PLUG_EVENT);
-
-       return err;
-
-#undef TEGRA_DP_HPD_PLUG_TIMEOUT_MS
-}
-
 static void tegra_dp_set_tx_pu(struct tegra_dc_dp_data *dp, u32 pe[4],
                                u32 vs[4], u32 pc[4])
 {
@@ -2346,29 +2413,49 @@ static void tegra_dp_link_config(struct tegra_dc_dp_data *dp)
 
 static int tegra_dp_edid(struct tegra_dc_dp_data *dp)
 {
+#define MAX_RETRY 100
+#define MIN_RETRY_DELAY_US 200
+#define MAX_RETRY_DELAY_US (MIN_RETRY_DELAY_US + 200)
+
        struct tegra_dc *dc = dp->dc;
-       struct fb_monspecs specs;
-       int err;
+       size_t attempt_cnt = 0;
+       int err = 0;
+
+       if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP ||
+               tegra_platform_is_linsim())
+               return err;
 
-       memset(&specs, 0 , sizeof(specs));
+       if (IS_ERR_OR_NULL(dp->dp_edid)) {
+               dev_err(&dp->dc->ndev->dev, "dp: edid not initialized\n");
+               return PTR_ERR(dp->dp_edid);
+       }
+
+       memset(&dp->mon_spec, 0, sizeof(dp->mon_spec));
+
+       do {
+               err = tegra_edid_get_monspecs(dp->dp_edid,
+                                               &dp->mon_spec);
+               if (err < 0)
+                       usleep_range(MIN_RETRY_DELAY_US, MAX_RETRY_DELAY_US);
+               else
+                       break;
+       } while (++attempt_cnt < MAX_RETRY);
 
-       err = tegra_edid_get_monspecs(dp->dp_edid, &specs);
        if (err < 0) {
-               dev_err(&dc->ndev->dev,
-                       "dp: Failed to get EDID data\n");
-               goto fail;
+               dev_err(&dc->ndev->dev, "dp: Failed to get EDID data\n");
+               return err;
        }
 
        /* set bpp if EDID provides primary color depth */
        dc->out->depth =
-               dc->out->depth ? : specs.bpc ? specs.bpc * 3 : 18;
+               dc->out->depth ? : dp->mon_spec.bpc ? dp->mon_spec.bpc * 3 : 18;
        dev_info(&dc->ndev->dev,
                "dp: EDID: %d bpc panel, set to %d bpp\n",
-                specs.bpc, dc->out->depth);
+                dp->mon_spec.bpc, dc->out->depth);
 
        /* in mm */
-       dc->out->h_size = dc->out->h_size ? : specs.max_x * 10;
-       dc->out->v_size = dc->out->v_size ? : specs.max_y * 10;
+       dc->out->h_size = dc->out->h_size ? : dp->mon_spec.max_x * 10;
+       dc->out->v_size = dc->out->v_size ? : dp->mon_spec.max_y * 10;
 
        /*
         * EDID specifies either the acutal screen sizes or
@@ -2379,14 +2466,18 @@ static int tegra_dp_edid(struct tegra_dc_dp_data *dp)
        dc->out->width = dc->out->width ? : dc->out->h_size;
        dc->out->height = dc->out->height ? : dc->out->v_size;
 
-       if (!dc->out->modes)
-               tegra_dc_set_fb_mode(dc, specs.modedb, false);
+       if (!tegra_dc_is_ext_dp_panel(dc)) {
+               if (!dc->out->modes)
+                       tegra_dc_set_fb_mode(dc, dp->mon_spec.modedb, false);
+               tegra_dc_setup_clk(dc, dc->clk);
+               kfree(dp->mon_spec.modedb);
+       }
 
-       tegra_dc_setup_clk(dc, dc->clk);
-       kfree(specs.modedb);
        return 0;
-fail:
-       return err;
+
+#undef MAX_RETRY_DELAY_US
+#undef MIN_RETRY_DELAY_US
+#undef MAX_RETRY
 }
 
 static inline void tegra_dp_reset(struct tegra_dc_dp_data *dp)
@@ -2406,7 +2497,10 @@ static inline void tegra_dp_default_int(struct tegra_dc_dp_data *dp,
                return;
 
        if (enable)
-               tegra_dp_int_en(dp, DPAUX_INTR_EN_AUX_IRQ_EVENT);
+               tegra_dp_int_en(dp, DPAUX_INTR_EN_AUX_IRQ_EVENT |
+               DPAUX_INTR_EN_AUX_PLUG_EVENT |
+               (!tegra_dc_is_ext_dp_panel(dp->dc) ? 0 :
+               DPAUX_INTR_EN_AUX_UNPLUG_EVENT));
        else
                tegra_dp_int_dis(dp, DPAUX_INTR_EN_AUX_IRQ_EVENT);
 }
@@ -2416,6 +2510,10 @@ static void tegra_dc_dp_enable(struct tegra_dc *dc)
        struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
        int ret;
 
+       trace_printk("dp: tegra_dc_dp_enable\n");
+       if (dp->enabled)
+               return;
+
        tegra_dp_reset(dp);
        if (dp->sor->safe_clk)
                tegra_disp_clk_prepare_enable(dp->sor->safe_clk);
@@ -2425,18 +2523,26 @@ static void tegra_dc_dp_enable(struct tegra_dc *dc)
        tegra_dpaux_enable(dp);
 
        if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) {
-               tegra_dp_enable_irq(dp->irq);
                tegra_dp_default_int(dp, true);
-
                tegra_dp_hpd_config(dp);
-               if (tegra_dp_hpd_plug(dp) < 0) {
-                       dev_info(&dc->ndev->dev,
-                               "dp: no panel/monitor plugged\n");
-                       dc->connected = false; /* unplugged during suspend */
-                       goto error_enable;
+
+               if (!tegra_dc_is_ext_dp_panel(dc)) {
+                       tegra_dp_enable_irq(dp->irq);
+                       if (!tegra_dc_dp_hpd(dp))
+                               goto error_enable;
                }
        }
 
+       /* DP panel was disconnected after disabling device. */
+       if (dc->connected && !tegra_dc_dp_hpd(dp) &&
+               tegra_dc_is_ext_dp_panel(dc)) {
+               schedule_work(&dp->hpd_work);
+               goto error_enable;
+       }
+
+       if (!dp->probed && tegra_dc_is_ext_dp_panel(dc))
+               goto error_enable;
+
        ret = tegra_dp_panel_power_state(dp, NV_DPCD_SET_POWER_VAL_D0_NORMAL);
        if (ret < 0) {
                dev_err(&dp->dc->ndev->dev,
@@ -2444,11 +2550,8 @@ static void tegra_dc_dp_enable(struct tegra_dc *dc)
                goto error_enable;
        }
 
-       if (dp->dp_edid && !dp->dp_edid->data &&
-               (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP) &&
-               !tegra_platform_is_linsim())
+       if (!tegra_dc_is_ext_dp_panel(dc))
                tegra_dp_edid(dp);
-
        tegra_dp_dpcd_init(dp);
 
        tegra_dc_sor_enable_dp(dp->sor);
@@ -2458,11 +2561,12 @@ static void tegra_dc_dp_enable(struct tegra_dc *dc)
        tegra_dp_lt(dp);
 
        tegra_dc_sor_attach(dp->sor);
+       tegra_dc_setup_clk(dc, dc->clk);
        tegra_dphdcp_set_plug(dp->dphdcp, true);
+       if (!tegra_dc_is_ext_dp_panel(dc))
+               tegra_dp_default_int(dp, false);
        tegra_dc_io_end(dc);
        dp->enabled = true;
-       tegra_dp_default_int(dp, false);
-       tegra_dc_io_end(dc);
        return;
 
 error_enable:
@@ -2470,9 +2574,8 @@ error_enable:
        tegra_dpaux_pad_power(dp->dc,
                dp->dc->ndev->id == 0 ? TEGRA_DPAUX_INSTANCE_0
                : TEGRA_DPAUX_INSTANCE_1, false);
-
-       tegra_dpaux_clk_disable(dp);
        tegra_dc_io_end(dc);
+       dc->connected = false;
        return;
 }
 
@@ -2513,12 +2616,14 @@ static void tegra_dc_dp_disable(struct tegra_dc *dc)
 {
        struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
 
+       trace_printk("dp: tegra_dc_dp_disable\n");
        tegra_dc_io_start(dc);
 
        tegra_dp_default_int(dp, false);
        cancel_work_sync(&dp->lt_work);
 
-       if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP)
+       if (dp->dc->out->type != TEGRA_DC_OUT_FAKE_DP &&
+               !tegra_dc_is_ext_dp_panel(dc))
                tegra_dp_disable_irq(dp->irq);
 
        if (!dp->enabled) {
@@ -2533,13 +2638,6 @@ static void tegra_dc_dp_disable(struct tegra_dc *dc)
        /* Power down SOR */
        tegra_dc_sor_detach(dp->sor);
        tegra_dc_sor_disable(dp->sor, false);
-
-       if (!tegra_platform_is_linsim()) {
-               tegra_dpaux_clk_disable(dp);
-               tegra_dp_clk_disable(dp);
-               if (dp->sor->safe_clk)
-                       clk_disable_unprepare(dp->sor->safe_clk);
-       }
        tegra_dc_io_end(dc);
        dp->enabled = false;
 }
@@ -2579,27 +2677,67 @@ static long tegra_dc_dp_setup_clk(struct tegra_dc *dc, struct clk *clk)
        return tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
 }
 
-/* used by tegra_dc_probe() to detect connection(HPD) status at boot */
-static bool tegra_dc_dp_detect(struct tegra_dc *dc)
+static bool tegra_dc_dp_hpd(struct tegra_dc_dp_data *dp)
 {
-       struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
+       struct tegra_dc *dc = dp->dc;
+       bool hpd_status_plugged = false;
        u32 rd;
 
-       if (dp->dc->out->type == TEGRA_DC_OUT_FAKE_DP ||
+       if (WARN_ON(!dc || !dc->out))
+               return false;
+
+       if (dc->out->hotplug_state != TEGRA_HPD_STATE_NORMAL) {
+               if (dc->out->hotplug_state == TEGRA_HPD_STATE_FORCE_ASSERT)
+                       return true;
+               if (dc->out->hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT)
+                       return false;
+       }
+
+       if (!tegra_dc_hotplug_supported(dc))
+               return true;
+
+       if (dc->out->type == TEGRA_DC_OUT_FAKE_DP ||
                tegra_platform_is_linsim())
-               return  true;
+               return true;
 
        tegra_dc_io_start(dc);
        tegra_dpaux_clk_enable(dp);
        rd = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT);
-       tegra_dpaux_clk_disable(dp);
-       tegra_dc_io_end(dc);
+
+       hpd_status_plugged = rd & DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED;
+       if (unlikely(!hpd_status_plugged && !tegra_dc_is_ext_dp_panel(dc)))
+               if (wait_for_completion_timeout(&dp->hpd_plug,
+                       msecs_to_jiffies(500))) {
+                       hpd_status_plugged = true;
+                       rd = tegra_dpaux_readl(dp, DPAUX_DP_AUXSTAT);
+               }
+
        dev_info(&dc->ndev->dev,
                "dp: DPAUX_DP_AUXSTAT:0x%08x HPD:%splugged\n",
-               rd, (DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED & rd) ? "" : "un");
-       return (DPAUX_DP_AUXSTAT_HPD_STATUS_PLUGGED & rd) ? true : false;
+               rd, hpd_status_plugged ? "" : "un");
+
+       if (dc->out->hotplug_report && tegra_dc_is_ext_dp_panel(dc))
+               dc->out->hotplug_report(hpd_status_plugged);
+       tegra_dc_io_end(dc);
+
+       return hpd_status_plugged;
 }
 
+/* used by tegra_dc_probe() to detect connection(HPD) status at boot */
+static bool tegra_dc_dp_detect(struct tegra_dc *dc)
+{
+       struct tegra_dc_dp_data *dp = tegra_dc_get_outdata(dc);
+       bool hpd_detected = tegra_dc_dp_hpd(dp);
+
+       dp->probed = true;
+       if (hpd_detected && tegra_dc_is_ext_dp_panel(dc))
+               schedule_work(&dp->hpd_work);
+
+       if (tegra_dc_is_ext_dp_panel(dc))
+               tegra_dp_enable_irq(dp->irq);
+
+       return hpd_detected;
+}
 
 static void tegra_dc_dp_modeset_notifier(struct tegra_dc *dc)
 {
@@ -2619,6 +2757,126 @@ static void tegra_dc_dp_modeset_notifier(struct tegra_dc *dc)
        tegra_dc_io_end(dc);
 }
 
+static bool tegra_dp_check_dc_constraint(const struct fb_videomode *mode)
+{
+       return (mode->hsync_len >= 1) && (mode->vsync_len >= 1) &&
+               (mode->lower_margin + mode->vsync_len +
+               mode->upper_margin > 1) &&
+               (mode->xres >= 16) && (mode->yres >= 16);
+}
+
+static bool tegra_dp_fb_mode_filter(const struct tegra_dc *dc,
+                               struct fb_videomode *mode)
+{
+       struct tegra_dc_dp_data *dp = dc->out_data;
+       u32 max_link_bw_rate = 2700;
+       unsigned long total_max_link_bw;
+       unsigned long mode_bw;
+       u8 max_link_bw;
+       u32 bits_per_pixel;
+
+       if (tegra_dc_dp_dpcd_read(dp, NV_DPCD_MAX_LINK_BANDWIDTH,
+                       &max_link_bw))
+               return false;
+
+       bits_per_pixel = (18 < dp->dc->out->depth) ? 24 : 18;
+
+       switch (max_link_bw) {
+       case SOR_LINK_SPEED_G1_62:
+               max_link_bw_rate = 1620;
+               break;
+       case SOR_LINK_SPEED_G2_7:
+               max_link_bw_rate = 2700;
+               break;
+       case SOR_LINK_SPEED_G5_4:
+               max_link_bw_rate = 5400;
+               break;
+       default:
+               dev_info(&dc->ndev->dev, "invalid link bw\n");
+               break;
+       }
+
+       /* max link bandwidth = lane_freq * lanes * 8 / 10 */
+       total_max_link_bw = (unsigned long)max_link_bw_rate
+                       * 1000 * 1000 * 8 / 10 * dc->out->dp_out->lanes;
+       mode_bw = (unsigned long)mode->xres * (unsigned long)mode->yres
+                       * mode->refresh * bits_per_pixel;
+
+       if (total_max_link_bw < mode_bw)
+               return false;
+
+       if (!mode->pixclock)
+               return false;
+
+       if (mode->xres > 4096)
+               return false;
+
+       if (mode->pixclock && tegra_dc_get_out_max_pixclock(dc) &&
+               mode->pixclock > tegra_dc_get_out_max_pixclock(dc))
+               return false;
+
+       /*
+        * Work around for modes that fail the constraint:
+        * V_FRONT_PORCH >= V_REF_TO_SYNC + 1
+        */
+       if (mode->lower_margin == 1) {
+               mode->lower_margin++;
+               mode->upper_margin--;
+       }
+
+       if (!tegra_dp_check_dc_constraint(mode))
+               return false;
+
+       return true;
+}
+
+static void tegra_dp_hotplug_notify(struct tegra_dc_dp_data *dp,
+                               bool is_asserted)
+{
+       struct tegra_dc *dc = dp->dc;
+       struct fb_monspecs *mon_spec;
+
+       if (is_asserted)
+               mon_spec = &dp->mon_spec;
+       else
+               mon_spec = NULL;
+
+       if (dc->fb) {
+               tegra_fb_update_monspecs(dp->dc->fb, mon_spec,
+                                       tegra_dp_fb_mode_filter);
+               tegra_fb_update_fix(dp->dc->fb, mon_spec);
+       }
+
+       dc->connected = is_asserted;
+       tegra_dc_ext_process_hotplug(dc->ndev->id);
+}
+
+static void tegra_dp_hpd_worker(struct work_struct *work)
+{
+       struct tegra_dc_dp_data *dp = container_of(work,
+                               struct tegra_dc_dp_data, hpd_work);
+       struct tegra_dc *dc = dp->dc;
+
+       mutex_lock(&dp->hpd_lock);
+       if (tegra_dc_dp_hpd(dp)) {
+               trace_printk("tegra_dp_hpd_worker: hpd plugged\n");
+               tegra_dc_unpowergate_locked(dc);
+
+               tegra_dpaux_pad_power(dc,
+                       dc->ndev->id == 0 ? TEGRA_DPAUX_INSTANCE_0
+                       : TEGRA_DPAUX_INSTANCE_1, true);
+               tegra_dp_edid(dp);
+
+               tegra_dc_powergate_locked(dc);
+               tegra_dp_hotplug_notify(dp, true);
+       } else {
+               trace_printk("tegra_dp_hpd_worker: hpd unplugged\n");
+               tegra_dc_disable(dc);
+               tegra_dp_hotplug_notify(dp, false);
+       }
+       mutex_unlock(&dp->hpd_lock);
+}
+
 struct tegra_dc_out_ops tegra_dc_dp_ops = {
        .init      = tegra_dc_dp_init,
        .destroy   = tegra_dc_dp_destroy,
index 454913d3e06edefd21022d949afef48a781321b8..05462712eee3fd981f444f24564185a7a858e3e0 100644 (file)
@@ -255,8 +255,13 @@ struct tegra_dc_dp_data {
        struct clk                      *dpaux_clk;
        struct clk                      *parent_clk; /* pll_dp clock */
 
+       struct fb_monspecs              mon_spec;
+
        struct work_struct              lt_work;
-       struct mutex            lt_lock;
+       struct mutex                    lt_lock;
+
+       struct work_struct              hpd_work;
+       struct mutex                    hpd_lock;
 
        u8                               revision;
 
@@ -266,6 +271,7 @@ struct tegra_dc_dp_data {
 
        bool                             enabled;
        bool                             suspended;
+       bool                            probed;
 
        struct tegra_edid               *dp_edid;
        struct completion               hpd_plug;
index 043ecb0788d8be4a8d419be9afffb040501426a4..5e6522999d7b84d264c04c896bf92b6cdabdc1cd 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * drivers/video/tegra/dc/dpaux_regs.h
  *
- * Copyright (c) 2011-2014, NVIDIA CORPORATION, All rights reserved.
+ * Copyright (c) 2011-2015, NVIDIA CORPORATION, All rights reserved.
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -25,6 +25,7 @@
 #define DPAUX_INTR_EN_AUX_TX_DONE              (0x1 << 3)
 #define DPAUX_INTR_AUX                                 (0x5)
 #define DPAUX_INTR_AUX_PLUG_EVENT_PENDING              (0x1 << 0)
+#define DPAUX_INTR_AUX_UNPLUG_EVENT_PENDING            (0x1 << 1)
 #define DPAUX_INTR_AUX_IRQ_EVENT_PENDING               (0x1 << 2)
 #define DPAUX_INTR_AUX_TX_DONE_PENDING         (0x1 << 3)
 #define DPAUX_DP_AUXDATA_WRITE_W(i)                 (0x9 + 4*(i))