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;
struct tegra_dc *dc = m ? m->private : NULL;
int ret;
long new_state;
+ int hotplug_state;
if (WARN_ON(!dc || !dc->out))
return -EINVAL;
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. */
}
dc->out->hotplug_state = new_state;
+ wmb();
/* retrigger the hotplug */
if (dc->out_ops->detect)
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;
}
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.
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;
}
#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__)
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)
{
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;
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 =
u32 tmp;
u32 res;
+ g_fallback = 0;
+
nvhdcp_vdbg("%s():started thread %s\n", __func__, nvhdcp->name);
tegra_dc_io_start(dc);
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;
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;
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;
}
/* 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);
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;
return nvhdcp;
free_workqueue:
destroy_workqueue(nvhdcp->downstream_wq);
+ destroy_workqueue(nvhdcp->fallback_wq);
i2c_release_client(nvhdcp->client);
free_nvhdcp:
kfree(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);
}