]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
video: tegra: dc: fallback to HDCP 1.x
authorAly Hirani <ahirani@nvidia.com>
Tue, 6 Oct 2015 22:42:23 +0000 (15:42 -0700)
committermobile promotions <svcmobile_promotions@nvidia.com>
Fri, 23 Oct 2015 15:32:51 +0000 (08:32 -0700)
Implemented fallback to HDMI 1.x for non 2.2 rx. This change:

1. Exposes the TEGRA_HPD_* as an exported function
2. Adds the detection logic in the HDCP driver to fail HDCP 2.2 and
trigger HDCP 1.4
3. During the fallback, uses the TEGRA_HPD_* function to force hot
unplug and restore to hw state

The force hotplug is needed for some receivers that do not behave
correctly when HDCP 2.2 is started and then aborted half-way.

Bug 1691483

Change-Id: Ie7d7611cff64cb36c3ae63474e5bdb71257eee65
Signed-off-by: Sharath Sarangpur <ssarangpur@nvidia.com>
Reviewed-on: http://git-master/r/819836
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
drivers/video/tegra/dc/dc.c
drivers/video/tegra/dc/hdmi2.0.c
drivers/video/tegra/dc/hdmihdcp.c
drivers/video/tegra/dc/hdmihdcp.h
drivers/video/tegra/dc/of_dc.c

index 887a5a3fcb541eaf336c2b24701974f1d84ab2c9..2b0689b3f16dab5df1da4a9c4613c3e833733809 100644 (file)
@@ -1428,6 +1428,7 @@ static int dbg_hotplug_show(struct seq_file *s, void *unused)
        if (WARN_ON(!dc || !dc->out))
                return -EINVAL;
 
+       rmb();
        seq_put_decimal_ll(s, '\0', dc->out->hotplug_state);
        seq_putc(s, '\n');
        return 0;
@@ -1445,6 +1446,7 @@ static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr,
        struct tegra_dc *dc = m ? m->private : NULL;
        int ret;
        long new_state;
+       int hotplug_state;
 
        if (WARN_ON(!dc || !dc->out))
                return -EINVAL;
@@ -1454,14 +1456,16 @@ static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr,
                return ret;
 
        mutex_lock(&dc->lock);
-       if (dc->out->hotplug_state == 0 && new_state != 0
+       rmb();
+       hotplug_state = dc->out->hotplug_state;
+       if (hotplug_state == 0 && new_state != 0
                        && tegra_dc_hotplug_supported(dc)) {
                /* was 0, now -1 or 1.
                 * we are overriding the hpd GPIO, so ignore the interrupt. */
                int gpio_irq = gpio_to_irq(dc->out->hotplug_gpio);
 
                disable_irq(gpio_irq);
-       } else if (dc->out->hotplug_state != 0 && new_state == 0
+       } else if (hotplug_state != 0 && new_state == 0
                        && tegra_dc_hotplug_supported(dc)) {
                /* was -1 or 1, and now 0
                 * restore the interrupt for hpd GPIO. */
@@ -1471,6 +1475,7 @@ static ssize_t dbg_hotplug_write(struct file *file, const char __user *addr,
        }
 
        dc->out->hotplug_state = new_state;
+       wmb();
 
        /* retrigger the hotplug */
        if (dc->out_ops->detect)
@@ -2026,14 +2031,18 @@ EXPORT_SYMBOL(tegra_dc_get_connected);
 bool tegra_dc_hpd(struct tegra_dc *dc)
 {
        int hpd = false;
+       int hotplug_state;
 
        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)
+       rmb();
+       hotplug_state = dc->out->hotplug_state;
+
+       if (hotplug_state != TEGRA_HPD_STATE_NORMAL) {
+               if (hotplug_state == TEGRA_HPD_STATE_FORCE_ASSERT)
                        return true;
-               if (dc->out->hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT)
+               if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT)
                        return false;
        }
 
index b0cd269cc58779c986f2df997ee69d17bbb9ba5c..a9509afab5b04bebe43bc6b06d264dd8b5627c9a 100644 (file)
@@ -2209,11 +2209,48 @@ static int tegra_hdmi_hotplug_dbg_show(struct seq_file *m, void *unused)
        if (WARN_ON(!hdmi || !dc || !dc->out))
                return -EINVAL;
 
+       rmb();
        seq_printf(m, "hdmi hpd state: %d\n", dc->out->hotplug_state);
 
        return 0;
 }
 
+int tegra_hdmi_get_hotplug_state(struct tegra_hdmi *hdmi)
+{
+       rmb();
+       return hdmi->dc->out->hotplug_state;
+}
+
+void tegra_hdmi_set_hotplug_state(struct tegra_hdmi *hdmi, int new_hpd_state)
+{
+       struct tegra_dc *dc = hdmi->dc;
+       int hotplug_state;
+
+       rmb();
+       hotplug_state = dc->out->hotplug_state;
+
+       if (hotplug_state == TEGRA_HPD_STATE_NORMAL &&
+                       new_hpd_state != TEGRA_HPD_STATE_NORMAL &&
+                       tegra_dc_hotplug_supported(dc)) {
+               disable_irq(gpio_to_irq(dc->out->hotplug_gpio));
+       } else if (hotplug_state != TEGRA_HPD_STATE_NORMAL &&
+                       new_hpd_state == TEGRA_HPD_STATE_NORMAL &&
+                       tegra_dc_hotplug_supported(dc)) {
+               enable_irq(gpio_to_irq(dc->out->hotplug_gpio));
+       }
+
+       dc->out->hotplug_state = new_hpd_state;
+       wmb();
+
+       /*
+        * sw controlled plug/unplug.
+        * wait for any already executing hpd worker thread.
+        * No debounce delay, schedule immedately
+        */
+       cancel_delayed_work_sync(&hdmi->hpd_worker);
+       schedule_delayed_work(&hdmi->hpd_worker, 0);
+}
+
 /*
  * sw control for hpd.
  * 0 is normal state, hw drives hpd.
@@ -2238,25 +2275,7 @@ static ssize_t tegra_hdmi_hotplug_dbg_write(struct file *file,
        if (ret < 0)
                return ret;
 
-       if (dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL &&
-               new_hpd_state != TEGRA_HPD_STATE_NORMAL &&
-               tegra_dc_hotplug_supported(dc)) {
-               disable_irq(gpio_to_irq(dc->out->hotplug_gpio));
-       } else if (dc->out->hotplug_state != TEGRA_HPD_STATE_NORMAL &&
-               new_hpd_state == TEGRA_HPD_STATE_NORMAL &&
-               tegra_dc_hotplug_supported(dc)) {
-               enable_irq(gpio_to_irq(dc->out->hotplug_gpio));
-       }
-
-       dc->out->hotplug_state = new_hpd_state;
-
-       /*
-        * sw controlled plug/unplug.
-        * wait for any already executing hpd worker thread.
-        * No debounce delay, schedule immedately
-        */
-       cancel_delayed_work_sync(&hdmi->hpd_worker);
-       schedule_delayed_work(&hdmi->hpd_worker, 0);
+       tegra_hdmi_set_hotplug_state(hdmi, new_hpd_state);
 
        return len;
 }
index d9e9c4b07dcb149ff6fb37c9471bdac6f7a99134..891de1069afe4c522ce40e8a022d144abc6de39f 100644 (file)
@@ -71,6 +71,9 @@ static DECLARE_WAIT_QUEUE_HEAD(wq_worker);
 #define HDCP_DEBUG                      0
 #define SEQ_NUM_M_MAX_RETRIES          1
 
+#define HDCP_FALLBACK_1X                0xdeadbeef
+#define HDCP_NON_22_RX                  0x0300
+
 #ifdef VERBOSE_DEBUG
 #define nvhdcp_vdbg(...)       \
                pr_debug("nvhdcp: " __VA_ARGS__)
@@ -90,6 +93,7 @@ static DECLARE_WAIT_QUEUE_HEAD(wq_worker);
                pr_info("nvhdcp: " __VA_ARGS__)
 
 static u8 g_seq_num_m_retries;
+static u8 g_fallback;
 
 static struct tegra_dc *tegra_dc_hdmi_get_dc(struct tegra_hdmi *hdmi)
 {
@@ -1165,6 +1169,13 @@ static int tsec_hdcp_authentication(struct tegra_nvhdcp *nvhdcp,
                        err = -EINVAL;
                        goto exit;
                }
+
+               if (hdcp_context->msg.rxinfo & HDCP_NON_22_RX) {
+                       err = HDCP_FALLBACK_1X;
+                       g_fallback = 1;
+                       goto exit;
+               }
+
                err =  tsec_hdcp_verify_vprime(hdcp_context);
                if (err)
                        goto exit;
@@ -1229,6 +1240,38 @@ exit:
        return err;
 }
 
+int tegra_hdmi_get_hotplug_state(struct tegra_hdmi *hdmi);
+void tegra_hdmi_set_hotplug_state(struct tegra_hdmi *hdmi, int new_hpd_state);
+
+static void nvhdcp_fallback_worker(struct work_struct *work)
+{
+       struct tegra_nvhdcp *nvhdcp =
+               container_of(to_delayed_work(work), struct tegra_nvhdcp, fallback_work);
+       struct tegra_hdmi *hdmi = nvhdcp->hdmi;
+       struct tegra_dc *dc = tegra_dc_hdmi_get_dc(hdmi);
+       int hotplug_state;
+       bool dc_enabled;
+
+       hotplug_state = tegra_hdmi_get_hotplug_state(hdmi);
+
+       mutex_lock(&dc->lock);
+       dc_enabled = dc->enabled;
+       mutex_unlock(&dc->lock);
+
+       if (hotplug_state == TEGRA_HPD_STATE_NORMAL) {
+               tegra_hdmi_set_hotplug_state(hdmi, TEGRA_HPD_STATE_FORCE_DEASSERT);
+               cancel_delayed_work(&nvhdcp->fallback_work);
+               queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
+                       msecs_to_jiffies(1000));
+       } else if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT && dc_enabled) {
+               cancel_delayed_work(&nvhdcp->fallback_work);
+               queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
+                       msecs_to_jiffies(1000));
+       } else if (hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT && !dc_enabled) {
+               tegra_hdmi_set_hotplug_state(hdmi, TEGRA_HPD_STATE_NORMAL);
+       }
+}
+
 static void nvhdcp_downstream_worker(struct work_struct *work)
 {
        struct tegra_nvhdcp *nvhdcp =
@@ -1240,6 +1283,8 @@ static void nvhdcp_downstream_worker(struct work_struct *work)
        u32 tmp;
        u32 res;
 
+       g_fallback = 0;
+
        nvhdcp_vdbg("%s():started thread %s\n", __func__, nvhdcp->name);
        tegra_dc_io_start(dc);
 
@@ -1479,6 +1524,13 @@ static int link_integrity_check(struct tegra_nvhdcp *nvhdcp,
                        err = -EINVAL;
                        goto exit;
                }
+               if (hdcp_context->msg.rxinfo & HDCP_NON_22_RX) {
+                       g_fallback = 1;
+                       cancel_delayed_work(&nvhdcp->fallback_work);
+                       queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
+                                                       msecs_to_jiffies(10));
+                       goto exit;
+               }
                err =  tsec_hdcp_verify_vprime(hdcp_context);
                if (err)
                        goto exit;
@@ -1501,6 +1553,7 @@ static void nvhdcp2_downstream_worker(struct work_struct *work)
        struct tegra_hdmi *hdmi = nvhdcp->hdmi;
        struct tegra_dc *dc = tegra_dc_hdmi_get_dc(hdmi);
        int e;
+       int ret;
        struct hdcp_context_t hdcp_context;
        g_seq_num_m_retries = 0;
 
@@ -1528,7 +1581,14 @@ static void nvhdcp2_downstream_worker(struct work_struct *work)
        nvhdcp_vdbg("%s():hpd=%d\n", __func__, nvhdcp->plugged);
        mutex_unlock(&nvhdcp->lock);
 
-       if (tsec_hdcp_authentication(nvhdcp, &hdcp_context)) {
+       ret = tsec_hdcp_authentication(nvhdcp, &hdcp_context);
+       if (ret == HDCP_FALLBACK_1X) {
+               cancel_delayed_work(&nvhdcp->fallback_work);
+               queue_delayed_work(nvhdcp->fallback_wq, &nvhdcp->fallback_work,
+                                               msecs_to_jiffies(10));
+               mutex_lock(&nvhdcp->lock);
+               goto lost_hdmi;
+       } else if (ret) {
                mutex_lock(&nvhdcp->lock);
                goto failure;
        }
@@ -1625,9 +1685,15 @@ static int tegra_nvhdcp_on(struct tegra_nvhdcp *nvhdcp)
                /* Do not stop nauthentication if i2c version reads fail as  */
                /* HDCP 1.x test 1A-04 expects reading HDCP regs */
                if (hdcp2version & HDCP_HDCP2_VERSION_HDCP22_YES) {
-                       INIT_DELAYED_WORK(&nvhdcp->work,
-                               nvhdcp2_downstream_worker);
-                       nvhdcp->hdcp22 = HDCP22_PROTOCOL;
+                       if (g_fallback) {
+                               INIT_DELAYED_WORK(&nvhdcp->work,
+                                       nvhdcp_downstream_worker);
+                               nvhdcp->hdcp22 = HDCP1X_PROTOCOL;
+                       } else {
+                               INIT_DELAYED_WORK(&nvhdcp->work,
+                                       nvhdcp2_downstream_worker);
+                               nvhdcp->hdcp22 = HDCP22_PROTOCOL;
+                       }
                } else {
                        INIT_DELAYED_WORK(&nvhdcp->work,
                                nvhdcp_downstream_worker);
@@ -1877,6 +1943,9 @@ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_hdmi *hdmi,
        nvhdcp->state = STATE_UNAUTHENTICATED;
 
        nvhdcp->downstream_wq = create_singlethread_workqueue(nvhdcp->name);
+       nvhdcp->fallback_wq = create_singlethread_workqueue(nvhdcp->name);
+
+       INIT_DELAYED_WORK(&nvhdcp->fallback_work, nvhdcp_fallback_worker);
 
        nvhdcp->miscdev.minor = MISC_DYNAMIC_MINOR;
        nvhdcp->miscdev.name = nvhdcp->name;
@@ -1891,6 +1960,7 @@ struct tegra_nvhdcp *tegra_nvhdcp_create(struct tegra_hdmi *hdmi,
        return nvhdcp;
 free_workqueue:
        destroy_workqueue(nvhdcp->downstream_wq);
+       destroy_workqueue(nvhdcp->fallback_wq);
        i2c_release_client(nvhdcp->client);
 free_nvhdcp:
        kfree(nvhdcp);
@@ -1903,6 +1973,7 @@ void tegra_nvhdcp_destroy(struct tegra_nvhdcp *nvhdcp)
        misc_deregister(&nvhdcp->miscdev);
        tegra_nvhdcp_off(nvhdcp);
        destroy_workqueue(nvhdcp->downstream_wq);
+       destroy_workqueue(nvhdcp->fallback_wq);
        i2c_release_client(nvhdcp->client);
        kfree(nvhdcp);
 }
index 99981ec14969aae7dc0fb50a96ad79bd5ae0396f..22da9da391d226dda2fc9f9d83fa5dc26383759e 100644 (file)
@@ -69,6 +69,8 @@ struct tegra_nvhdcp {
        char                            hdcp22;
        u8                              max_retries;
        u8                              repeater;
+       struct workqueue_struct         *fallback_wq;
+       struct delayed_work             fallback_work;
 };
 
 #ifdef CONFIG_TEGRA_HDMIHDCP
index 5a59b0ac204c4e349a33a5296ad987504abfd0df..433b9512fb6526cf422bcb9a08d46f107f7982d1 100644 (file)
@@ -472,6 +472,7 @@ static int parse_disp_default_out(struct platform_device *ndev,
        if (!of_property_read_u32(np,
                        "nvidia,out-hotplug-state", &temp)) {
                        default_out->hotplug_state = (unsigned) temp;
+                       wmb();
                        OF_DC_LOG("out-hotplug-state %d\n", temp);
        }