video: tegra: dc: fallback to HDCP 1.x
Aly Hirani [Tue, 6 Oct 2015 22:42:23 +0000 (15:42 -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 887a5a3..2b0689b 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 b0cd269..a9509af 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 d9e9c4b..891de10 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 99981ec..22da9da 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 5a59b0a..433b951 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);
        }