linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
@ 2014-12-01 21:56 Jilai Wang
  2014-12-03  1:46 ` Bjorn Andersson
  2014-12-03 16:51 ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Rob Clark
  0 siblings, 2 replies; 15+ messages in thread
From: Jilai Wang @ 2014-12-01 21:56 UTC (permalink / raw)
  To: dri-devel; +Cc: linux-arm-msm, linux-kernel, robdclark, Jilai Wang

Add HDMI HDCP support including HDCP PartI/II/III authentication.

Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
---
 drivers/gpu/drm/msm/Makefile              |    1 +
 drivers/gpu/drm/msm/hdmi/hdmi.c           |   44 +
 drivers/gpu/drm/msm/hdmi/hdmi.h           |   31 +
 drivers/gpu/drm/msm/hdmi/hdmi_audio.c     |    1 -
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c    |    3 +-
 drivers/gpu/drm/msm/hdmi/hdmi_connector.c |    7 +-
 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c      | 1600 +++++++++++++++++++++++++++++
 7 files changed, 1682 insertions(+), 5 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 6283dcb..9d88ff3 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -11,6 +11,7 @@ msm-y := \
 	hdmi/hdmi_audio.o \
 	hdmi/hdmi_bridge.o \
 	hdmi/hdmi_connector.o \
+	hdmi/hdmi_hdcp.o \
 	hdmi/hdmi_i2c.o \
 	hdmi/hdmi_phy_8960.o \
 	hdmi/hdmi_phy_8x60.o \
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c
index 9d00dcb..f0111c0 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.c
@@ -20,7 +20,9 @@
 void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 {
 	uint32_t ctrl = 0;
+	unsigned long flags;
 
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	if (power_on) {
 		ctrl |= HDMI_CTRL_ENABLE;
 		if (!hdmi->hdmi_mode) {
@@ -35,6 +37,7 @@ void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 	}
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 	DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
 			power_on ? "Enable" : "Disable", ctrl);
 }
@@ -49,6 +52,9 @@ irqreturn_t hdmi_irq(int irq, void *dev_id)
 	/* Process DDC: */
 	hdmi_i2c_irq(hdmi->i2c);
 
+	/* Process HDCP: */
+	hdmi_hdcp_irq(hdmi->hdcp_ctrl);
+
 	/* TODO audio.. */
 
 	return IRQ_HANDLED;
@@ -59,6 +65,16 @@ void hdmi_destroy(struct kref *kref)
 	struct hdmi *hdmi = container_of(kref, struct hdmi, refcount);
 	struct hdmi_phy *phy = hdmi->phy;
 
+	/*
+	 * at this point, hpd has been disabled,
+	 * after flush workq, it's safe to deinit hdcp
+	 */
+	if (hdmi->workq) {
+		flush_workqueue(hdmi->workq);
+		destroy_workqueue(hdmi->workq);
+	}
+	hdmi_hdcp_destroy(hdmi);
+
 	if (phy)
 		phy->funcs->destroy(phy);
 
@@ -75,6 +91,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
 	struct msm_drm_private *priv = dev->dev_private;
 	struct platform_device *pdev = priv->hdmi_pdev;
 	struct hdmi_platform_config *config;
+	struct resource *res;
 	int i, ret;
 
 	if (!pdev) {
@@ -97,6 +114,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
 	hdmi->pdev = pdev;
 	hdmi->config = config;
 	hdmi->encoder = encoder;
+	spin_lock_init(&hdmi->reg_lock);
 
 	hdmi_audio_infoframe_init(&hdmi->audio.infoframe);
 
@@ -119,6 +137,22 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
 		goto fail;
 	}
 
+	/* HDCP needs physical address of hdmi register */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		config->mmio_name);
+	hdmi->mmio_phy_addr = res->start;
+
+	if (config->qfprom_mmio_name) {
+		hdmi->qfprom_mmio = msm_ioremap(pdev,
+			config->qfprom_mmio_name, "HDMI_QFPROM");
+		if (IS_ERR(hdmi->qfprom_mmio)) {
+			dev_info(&pdev->dev, "can't find qfprom resource\n");
+			hdmi->qfprom_mmio = NULL;
+		}
+	} else {
+		hdmi->qfprom_mmio = NULL;
+	}
+
 	BUG_ON(config->hpd_reg_cnt > ARRAY_SIZE(hdmi->hpd_regs));
 	for (i = 0; i < config->hpd_reg_cnt; i++) {
 		struct regulator *reg;
@@ -181,6 +215,8 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
 		hdmi->pwr_clks[i] = clk;
 	}
 
+	hdmi->workq = alloc_ordered_workqueue("msm_hdmi", 0);
+
 	hdmi->i2c = hdmi_i2c_init(hdmi);
 	if (IS_ERR(hdmi->i2c)) {
 		ret = PTR_ERR(hdmi->i2c);
@@ -205,6 +241,13 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
 		goto fail;
 	}
 
+	hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
+	if (IS_ERR(hdmi->hdcp_ctrl)) {
+		ret = PTR_ERR(hdmi->hdcp_ctrl);
+		dev_warn(dev->dev, "failed to init hdcp: %d(disabled)\n", ret);
+		hdmi->hdcp_ctrl = NULL;
+	}
+
 	if (!config->shared_irq) {
 		hdmi->irq = platform_get_irq(pdev, 0);
 		if (hdmi->irq < 0) {
@@ -314,6 +357,7 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	}
 
 	config.mmio_name     = "core_physical";
+	config.qfprom_mmio_name = "qfprom_physical";
 	config.ddc_clk_gpio  = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-clk");
 	config.ddc_data_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-data");
 	config.hpd_gpio      = get_gpio(dev, of_node, "qcom,hdmi-tx-hpd");
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h
index b981995..10c4ea6 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.h
@@ -37,6 +37,8 @@ struct hdmi_audio {
 	int rate;
 };
 
+struct hdmi_hdcp_ctrl;
+
 struct hdmi {
 	struct kref refcount;
 
@@ -53,6 +55,8 @@ struct hdmi {
 	unsigned long int pixclock;
 
 	void __iomem *mmio;
+	void __iomem *qfprom_mmio;
+	phys_addr_t mmio_phy_addr;
 
 	struct regulator *hpd_regs[2];
 	struct regulator *pwr_regs[2];
@@ -70,12 +74,25 @@ struct hdmi {
 	bool hdmi_mode;               /* are we in hdmi mode? */
 
 	int irq;
+	struct workqueue_struct *workq;
+
+	struct hdmi_hdcp_ctrl *hdcp_ctrl;
+
+	/*
+	* spinlock to protect registers shared by different execution
+	* REG_HDMI_CTRL
+	* REG_HDMI_DDC_ARBITRATION
+	* REG_HDMI_HDCP_INT_CTRL
+	* REG_HDMI_HPD_CTRL
+	*/
+	spinlock_t reg_lock;
 };
 
 /* platform config data (ie. from DT, or pdata) */
 struct hdmi_platform_config {
 	struct hdmi_phy *(*phy_init)(struct hdmi *hdmi);
 	const char *mmio_name;
+	const char *qfprom_mmio_name;
 
 	/* regulators that need to be on for hpd: */
 	const char **hpd_reg_names;
@@ -115,6 +132,11 @@ static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
 	return msm_readl(hdmi->mmio + reg);
 }
 
+static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
+{
+	return msm_readl(hdmi->qfprom_mmio + reg);
+}
+
 static inline struct hdmi * hdmi_reference(struct hdmi *hdmi)
 {
 	kref_get(&hdmi->refcount);
@@ -179,4 +201,13 @@ void hdmi_i2c_irq(struct i2c_adapter *i2c);
 void hdmi_i2c_destroy(struct i2c_adapter *i2c);
 struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi);
 
+/*
+ * hdcp
+ */
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi);
+void hdmi_hdcp_destroy(struct hdmi *hdmi);
+int hdmi_hdcp_on(struct hdmi *hdmi);
+void hdmi_hdcp_off(struct hdmi *hdmi);
+
 #endif /* __HDMI_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
index 872485f..df232e2 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
@@ -203,7 +203,6 @@ int hdmi_audio_update(struct hdmi *hdmi)
 		audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4);
 		audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE;
 	} else {
-		hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE);
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT;
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND;
 		vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE;
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
index f6cf745..de6bc56 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
@@ -106,6 +106,7 @@ static void hdmi_bridge_pre_enable(struct drm_bridge *bridge)
 
 	phy->funcs->powerup(phy, hdmi->pixclock);
 	hdmi_set_mode(hdmi, true);
+	hdmi_hdcp_on(hdmi);
 }
 
 static void hdmi_bridge_enable(struct drm_bridge *bridge)
@@ -146,8 +147,6 @@ static void hdmi_bridge_mode_set(struct drm_bridge *bridge,
 
 	hdmi->pixclock = mode->clock * 1000;
 
-	hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1;
-
 	hstart = mode->htotal - mode->hsync_start;
 	hend   = mode->htotal - mode->hsync_start + mode->hdisplay;
 
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
index 4aca2a3..4256669 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
@@ -140,6 +140,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 	struct hdmi_phy *phy = hdmi->phy;
 	uint32_t hpd_ctrl;
 	int i, ret;
+	unsigned long flags;
 
 	ret = gpio_config(hdmi, true);
 	if (ret) {
@@ -185,6 +186,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			HDMI_HPD_INT_CTRL_INT_EN);
 
 	/* set timeout to 4.1ms (max) for hardware debounce */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
 	hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
 
@@ -193,6 +195,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
 	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
 			HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 
 	return 0;
 
@@ -248,7 +251,6 @@ hotplug_work(struct work_struct *work)
 void hdmi_connector_irq(struct drm_connector *connector)
 {
 	struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
-	struct msm_drm_private *priv = connector->dev->dev_private;
 	struct hdmi *hdmi = hdmi_connector->hdmi;
 	uint32_t hpd_int_status, hpd_int_ctrl;
 
@@ -272,7 +274,7 @@ void hdmi_connector_irq(struct drm_connector *connector)
 			hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
 		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
 
-		queue_work(priv->wq, &hdmi_connector->hpd_work);
+		queue_work(hdmi->workq, &hdmi_connector->hpd_work);
 	}
 }
 
@@ -350,6 +352,7 @@ static int hdmi_connector_get_modes(struct drm_connector *connector)
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
 
+	hdmi->hdmi_mode = drm_detect_hdmi_monitor(edid);
 	drm_mode_connector_update_edid_property(connector, edid);
 
 	if (edid) {
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
new file mode 100644
index 0000000..6996466
--- /dev/null
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
@@ -0,0 +1,1600 @@
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hdmi.h"
+#include <soc/qcom/scm.h>
+
+#define TZ_HDCP_CMD_ID 0x00004401
+#define HDCP_REG_ENABLE 0x01
+#define HDCP_REG_DISABLE 0x00
+#define HDCP_PORT_ADDR 0x74
+
+#define HDCP_INT_CLR (BIT(1) | BIT(5) | BIT(7) | BIT(9) | BIT(13))
+#define HDCP_INT_STATUS (BIT(0) | BIT(4) | BIT(8) | BIT(12))
+
+#define AUTH_WORK_RETRIES_TIME (100)
+#define AUTH_RETRIES_TIME (30)
+
+/* QFPROM Registers for HDMI/HDCP */
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_LSB  (0x000000F8)
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_MSB  (0x000000FC)
+#define HDCP_KSV_LSB                     (0x000060D8)
+#define HDCP_KSV_MSB                     (0x000060DC)
+
+enum DS_TYPE {  /* type of downstream device */
+	DS_UNKNOWN,
+	DS_RECEIVER,
+	DS_REPEATER,
+};
+
+enum hdmi_hdcp_state {
+	HDCP_STATE_INACTIVE,
+	HDCP_STATE_AUTHENTICATING,
+	HDCP_STATE_AUTHENTICATED,
+	HDCP_STATE_AUTH_FAIL
+};
+
+struct hdmi_hdcp_reg_data {
+	uint32_t reg_id;
+	uint32_t off;
+	char *name;
+	uint32_t reg_val;
+};
+
+struct hdmi_hdcp_ctrl {
+	struct hdmi *hdmi;
+	uint32_t auth_retries;
+	uint32_t tz_hdcp;
+	enum hdmi_hdcp_state hdcp_state;
+	struct mutex state_mutex;
+	struct delayed_work hdcp_reauth_work;
+	struct delayed_work hdcp_auth_part1_1_work;
+	struct delayed_work hdcp_auth_part1_2_work;
+	struct work_struct hdcp_auth_part1_3_work;
+	struct delayed_work hdcp_auth_part2_1_work;
+	struct delayed_work hdcp_auth_part2_2_work;
+	struct delayed_work hdcp_auth_part2_3_work;
+	struct delayed_work hdcp_auth_part2_4_work;
+	struct work_struct hdcp_auth_prepare_work;
+	uint32_t work_retry_cnt;
+	uint32_t ksv_fifo_w_index;
+
+	/*
+	 * store aksv from qfprom
+	 */
+	uint8_t aksv[5];
+	bool aksv_valid;
+	uint32_t ds_type;
+	uint8_t bksv[5];
+	uint8_t dev_count;
+	uint8_t depth;
+	uint8_t ksv_list[5 * 127];
+	bool max_cascade_exceeded;
+	bool max_dev_exceeded;
+};
+
+static int hdmi_ddc_read(struct hdmi *hdmi, uint16_t addr, uint8_t offset,
+	uint8_t *data, uint16_t data_len, bool no_align)
+{
+	int rc = 0;
+	int retry = 5;
+	uint8_t *buf = NULL;
+	uint32_t request_len;
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &offset,
+		}, {
+			.addr	= addr >> 1,
+			.flags	= I2C_M_RD,
+			.len	= data_len,
+			.buf	= data,
+		}
+	};
+
+	DBG("Start DDC read");
+retry:
+	if (no_align) {
+		rc = i2c_transfer(hdmi->i2c, msgs, 2);
+	} else {
+		request_len = 32 * ((data_len + 31) / 32);
+		buf = kmalloc(request_len, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		msgs[1].buf = buf;
+		rc = i2c_transfer(hdmi->i2c, msgs, 2);
+	}
+
+	retry--;
+	if (rc == 2) {
+		rc = 0;
+		if (!no_align)
+			memcpy(data, buf, data_len);
+	} else if (retry > 0) {
+		goto retry;
+	} else {
+		rc = -EIO;
+	}
+
+	kfree(buf);
+	DBG("End DDC read %d", rc);
+
+	return rc;
+}
+
+static int hdmi_ddc_write(struct hdmi *hdmi, uint16_t addr, uint8_t offset,
+	uint8_t *data, uint16_t data_len)
+{
+	int rc = 0;
+	int retry = 10;
+	uint8_t *buf = NULL;
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+		}
+	};
+
+	DBG("Start DDC write");
+	buf = kmalloc(data_len + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = offset;
+	memcpy(&buf[1], data, data_len);
+	msgs[0].buf = buf;
+	msgs[0].len = data_len + 1;
+retry:
+	rc = i2c_transfer(hdmi->i2c, msgs, 1);
+
+	retry--;
+	if (rc == 1)
+		rc = 0;
+	else if (retry > 0)
+		goto retry;
+	else
+		rc = -EIO;
+
+	kfree(buf);
+	DBG("End DDC write %d", rc);
+
+	return rc;
+}
+
+static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, uint32_t *preg,
+	uint32_t *pdata, uint32_t count)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	struct scm_hdcp_req scm_buf[SCM_HDCP_MAX_REG];
+	uint32_t resp, phy_addr, idx = 0;
+	int i, ret = 0;
+
+	if (count == 0)
+		return 0;
+
+	if (!preg || !pdata) {
+		pr_err("%s: Invalid pointer\n", __func__);
+		return -EINVAL;
+	}
+
+	if (hdcp_ctrl->tz_hdcp) {
+		phy_addr = (uint32_t)hdmi->mmio_phy_addr;
+
+		while (count) {
+			memset(scm_buf, 0, sizeof(scm_buf));
+			for (i = 0; i < count && i < SCM_HDCP_MAX_REG; i++) {
+				scm_buf[i].addr = phy_addr + preg[idx];
+				scm_buf[i].val  = pdata[idx];
+				idx++;
+			}
+			ret = scm_call(SCM_SVC_HDCP, SCM_CMD_HDCP,
+				scm_buf, sizeof(scm_buf), &resp, sizeof(resp));
+
+			if (ret || resp) {
+				pr_err("%s: error: scm_call ret = %d, resp = %d\n",
+					__func__, ret, resp);
+				ret = -EINVAL;
+				break;
+			}
+
+			count -= i;
+		}
+	} else {
+		for (i = 0; i < count; i++)
+			hdmi_write(hdmi, preg[i], pdata[i]);
+	}
+
+	return ret;
+}
+
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi;
+	uint32_t regval, hdcp_int_status;
+	unsigned long flags;
+
+	if (!hdcp_ctrl) {
+		DBG("HDCP is disabled");
+		return;
+	}
+
+	hdmi = hdcp_ctrl->hdmi;
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	regval = hdmi_read(hdmi, REG_HDMI_HDCP_INT_CTRL);
+	hdcp_int_status = regval & HDCP_INT_STATUS;
+	if (!hdcp_int_status) {
+		spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+		return;
+	}
+	/* Clear Interrupts */
+	regval |= hdcp_int_status << 1;
+	/* Clear AUTH_FAIL_INFO as well */
+	if (hdcp_int_status & BIT(4))
+		regval |= BIT(7);
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, regval);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	DBG("hdcp irq %x", regval);
+	if (hdcp_int_status & BIT(0)) {
+		/* AUTH_SUCCESS_INT */
+		pr_info("%s:AUTH_SUCCESS_INT received\n", __func__);
+		if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state)
+			queue_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_auth_part1_3_work);
+	}
+
+	if (hdcp_int_status & BIT(4)) {
+		/* AUTH_FAIL_INT */
+		regval = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		pr_info("%s: AUTH_FAIL_INT rcvd, LINK0_STATUS=0x%08x\n",
+			__func__, regval);
+		if (HDCP_STATE_AUTHENTICATED == hdcp_ctrl->hdcp_state)
+			queue_delayed_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_reauth_work, HZ/2);
+		else if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state)
+			queue_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_auth_part1_3_work);
+	}
+
+	if (hdcp_int_status & BIT(8)) {
+		/* DDC_XFER_REQ_INT */
+		pr_info("%s:DDC_XFER_REQ_INT received\n", __func__);
+	}
+
+	if (hdcp_int_status & BIT(12)) {
+		/* DDC_XFER_DONE_INT */
+		pr_info("%s:DDC_XFER_DONE received\n", __func__);
+	}
+} /* hdmi_hdcp_isr */
+
+static int hdmi_hdcp_count_one(uint8_t *array, uint8_t len)
+{
+	int i, j, count = 0;
+
+	for (i = 0; i < len; i++)
+		for (j = 0; j < 8; j++)
+			count += (((array[i] >> j) & 0x1) ? 1 : 0);
+
+	return count;
+}
+
+static int hdmi_hdcp_read_validate_aksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t qfprom_aksv_lsb, qfprom_aksv_msb;
+
+	/* Fetch aksv from QFPROM, this info should be public. */
+	qfprom_aksv_lsb = hdmi_qfprom_read(hdmi, HDCP_KSV_LSB);
+	qfprom_aksv_msb = hdmi_qfprom_read(hdmi, HDCP_KSV_MSB);
+
+	hdcp_ctrl->aksv[0] =  qfprom_aksv_lsb        & 0xFF;
+	hdcp_ctrl->aksv[1] = (qfprom_aksv_lsb >> 8)  & 0xFF;
+	hdcp_ctrl->aksv[2] = (qfprom_aksv_lsb >> 16) & 0xFF;
+	hdcp_ctrl->aksv[3] = (qfprom_aksv_lsb >> 24) & 0xFF;
+	hdcp_ctrl->aksv[4] =  qfprom_aksv_msb        & 0xFF;
+
+	/* check there are 20 ones in AKSV */
+	if (hdmi_hdcp_count_one(hdcp_ctrl->aksv, 5) != 20) {
+		pr_err("%s: AKSV QFPROM doesn't have 20 1's, 20 0's\n",
+			__func__);
+		pr_err("%s: QFPROM AKSV chk failed (AKSV=%02x%08x)\n",
+			__func__, qfprom_aksv_msb,
+			qfprom_aksv_lsb);
+		return -EINVAL;
+	}
+	DBG("AKSV=%02x%08x", qfprom_aksv_msb, qfprom_aksv_lsb);
+
+	return 0;
+}
+
+static void reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int hdcp_ddc_ctrl1_reg;
+	int hdcp_ddc_status;
+	int failure;
+	int nack0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	/* Check for any DDC transfer failures */
+	hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+	failure = (hdcp_ddc_status >> 16) & 0x1;
+	nack0 = (hdcp_ddc_status >> 14) & 0x1;
+	DBG("On Entry: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
+		hdcp_ddc_status, failure, nack0);
+
+	if (failure == 0x1) {
+		/*
+		 * Indicates that the last HDCP HW DDC transfer failed.
+		 * This occurs when a transfer is attempted with HDCP DDC
+		 * disabled (HDCP_DDC_DISABLE=1) or the number of retries
+		 * matches HDCP_DDC_RETRY_CNT.
+		 * Failure occurred,  let's clear it.
+		 */
+		DBG("DDC failure detected.HDCP_DDC_STATUS=0x%08x",
+			hdcp_ddc_status);
+
+		/* First, Disable DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, BIT(0));
+
+		/* ACK the Failure to Clear it */
+		hdcp_ddc_ctrl1_reg = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1);
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1,
+			hdcp_ddc_ctrl1_reg | BIT(0));
+
+		/* Check if the FAILURE got Cleared */
+		hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		hdcp_ddc_status = (hdcp_ddc_status >> 16) & BIT(0);
+		if (hdcp_ddc_status == 0x0)
+			DBG("HDCP DDC Failure cleared");
+		else
+			pr_info("%s: Unable to clear HDCP DDC Failure\n",
+				__func__);
+
+		/* Re-Enable HDCP DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
+	}
+
+	if (nack0 == 0x1) {
+		DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+		/* Reset HDMI DDC software status */
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
+			hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(3));
+		msleep(20);
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
+			hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~(BIT(3)));
+
+		/* Reset HDMI DDC Controller */
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
+			hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(1));
+		msleep(20);
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
+			hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~BIT(1));
+		DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+	}
+
+	hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+
+	failure = (hdcp_ddc_status >> 16) & BIT(0);
+	nack0 = (hdcp_ddc_status >> 14) & BIT(0);
+	DBG("On Exit: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
+		hdcp_ddc_status, failure, nack0);
+}
+
+static void hdmi_hdcp_hw_ddc_clean(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	uint32_t hdcp_ddc_status, ddc_hw_status;
+	uint32_t ddc_xfer_done, ddc_xfer_req, ddc_hw_done;
+	uint32_t ddc_hw_not_ready;
+	uint32_t timeout_count;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	if (hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS) == 0)
+		return;
+
+	/* Wait to be clean on DDC HW engine */
+	timeout_count = 100;
+	do {
+		hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		ddc_hw_status = hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS);
+		ddc_xfer_done = hdcp_ddc_status & BIT(10);
+		ddc_xfer_req = hdcp_ddc_status & BIT(4);
+		ddc_hw_done = ddc_hw_status & BIT(3);
+		ddc_hw_not_ready = !ddc_xfer_done ||
+			ddc_xfer_req || !ddc_hw_done;
+
+		DBG("timeout count(%d):ddc hw%sready",
+			timeout_count, ddc_hw_not_ready ? " not " : " ");
+		DBG("hdcp_ddc_status[0x%x], ddc_hw_status[0x%x]",
+			hdcp_ddc_status, ddc_hw_status);
+		if (ddc_hw_not_ready)
+			msleep(20);
+	} while (ddc_hw_not_ready && --timeout_count);
+}
+
+/*
+ * Only retries defined times then abort current authenticating process
+ */
+static int hdmi_msm_if_abort_reauth(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+
+	if (++hdcp_ctrl->auth_retries == AUTH_RETRIES_TIME) {
+		mutex_lock(&hdcp_ctrl->state_mutex);
+		hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+		mutex_unlock(&hdcp_ctrl->state_mutex);
+
+		hdcp_ctrl->auth_retries = 0;
+		rc = -ERANGE;
+	}
+
+	return rc;
+}
+
+static void hdmi_hdcp_reauth_work(struct work_struct *work)
+{
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_reauth_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	int rc;
+
+	DBG("HDCP REAUTH WORK");
+	mutex_lock(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAIL;
+	mutex_unlock(&hdcp_ctrl->state_mutex);
+
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
+		hdmi_read(hdmi, REG_HDMI_HPD_CTRL) & ~BIT(28));
+
+	/* Disable HDCP interrupts */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET, BIT(0));
+
+	/* Wait to be clean on DDC HW engine */
+	hdmi_hdcp_hw_ddc_clean(hdcp_ctrl);
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	/* Enable HPD circuitry */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
+		hdmi_read(hdmi, REG_HDMI_HPD_CTRL) | BIT(28));
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	rc = hdmi_msm_if_abort_reauth(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: abort reauthentication!\n", __func__);
+		return;
+	}
+
+	mutex_lock(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	mutex_unlock(&hdcp_ctrl->state_mutex);
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_prepare_work);
+}
+
+static void hdmi_hdcp_auth_prepare_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_auth_prepare_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t qfprom_aksv_lsb, qfprom_aksv_msb;
+	uint32_t link0_status;
+	uint32_t regval;
+	unsigned long flags;
+	int ret;
+
+	if (!hdcp_ctrl->aksv_valid) {
+		ret = hdmi_hdcp_read_validate_aksv(hdcp_ctrl);
+		if (ret) {
+			pr_err("%s: ASKV validation failed\n", __func__);
+			mutex_lock(&hdcp_ctrl->state_mutex);
+			hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+			mutex_unlock(&hdcp_ctrl->state_mutex);
+			return;
+		}
+		hdcp_ctrl->aksv_valid = true;
+	}
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	/* disable HDMI Encrypt */
+	regval = hdmi_read(hdmi, REG_HDMI_CTRL);
+	regval &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, regval);
+
+	/* Enabling Software DDC */
+	regval = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	regval &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, regval);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Write AKSV read from QFPROM to the HDCP registers.
+	 * This step is needed for HDCP authentication and must be
+	 * written before enabling HDCP.
+	 */
+	qfprom_aksv_lsb = hdcp_ctrl->aksv[3];
+	qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[2];
+	qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[1];
+	qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[0];
+	qfprom_aksv_msb = hdcp_ctrl->aksv[4];
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_LOWER_AKSV, qfprom_aksv_lsb);
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_UPPER_AKSV, qfprom_aksv_msb);
+
+	/*
+	 * HDCP setup prior to enabling HDCP_CTRL.
+	 * Setup seed values for random number An.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL0, 0xB1FFB0FF);
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE);
+
+	/* Disable the RngCipher state */
+	hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL,
+		hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL) & ~(BIT(2)));
+	DBG("HDCP_DEBUG_CTRL=0x%08x",
+		hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL));
+
+	/*
+	 * Ensure that all register writes are completed before
+	 * enabling HDCP cipher
+	 */
+	wmb();
+
+	/*
+	 * Enable HDCP
+	 * This needs to be done as early as possible in order for the
+	 * hardware to make An available to read
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, BIT(0));
+
+	/*
+	 * If we had stale values for the An ready bit, it should most
+	 * likely be cleared now after enabling HDCP cipher
+	 */
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	DBG("After enabling HDCP Link0_Status=0x%08x", link0_status);
+	if (!(link0_status & (BIT(8) | BIT(9))))
+		DBG("An not ready after enabling HDCP");
+
+	/* Clear any DDC failures from previous tries before enable HDCP*/
+	reset_hdcp_ddc_failures(hdcp_ctrl);
+
+	DBG("Queuing work to start HDCP authentication");
+	hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+	queue_delayed_work(hdmi->workq,
+		&hdcp_ctrl->hdcp_auth_part1_1_work, HZ/2);
+}
+
+static void hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t regval;
+	unsigned long flags;
+
+	DBG("hdcp auth failed, queue reauth work");
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	regval = hdmi_read(hdmi, REG_HDMI_CTRL);
+	regval &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, regval);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+	queue_delayed_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work, HZ/2);
+}
+
+static void hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t regval;
+	unsigned long flags;
+
+	/*
+	 * Disable software DDC before going into part3 to make sure
+	 * there is no Arbitration between software and hardware for DDC
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	regval = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	regval |= HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, regval);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Ensure that the state did not change during authentication.
+	 * If it did, it means that deauthenticate/reauthenticate was
+	 * called. In that case, this function doesn't need to enable encryption
+	 */
+	mutex_lock(&hdcp_ctrl->state_mutex);
+	if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state) {
+		hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED;
+		hdcp_ctrl->auth_retries = 0;
+
+		/* enable HDMI Encrypt */
+		spin_lock_irqsave(&hdmi->reg_lock, flags);
+		regval = hdmi_read(hdmi, REG_HDMI_CTRL);
+		regval |= HDMI_CTRL_ENCRYPTED;
+		hdmi_write(hdmi, REG_HDMI_CTRL, regval);
+		spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+		mutex_unlock(&hdcp_ctrl->state_mutex);
+	} else {
+		mutex_unlock(&hdcp_ctrl->state_mutex);
+		DBG("HDCP state changed during authentication");
+	}
+}
+
+/*
+ * hdcp authenticating part 1: 1st
+ * Wait Key/An ready
+ * Read BCAPS from sink
+ * Write BCAPS and AKSV into HDCP engine
+ * Write An and AKSV to sink
+ * Read BKSV from sink and write into HDCP engine
+ */
+static int hdmi_hdcp_check_key_an_ready(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t link0_status, an_ready, keys_state;
+
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	/* Wait for HDCP keys to be checked and validated */
+	keys_state = (link0_status >> 28) & 0x7;
+	if (keys_state != HDCP_KEYS_STATE_VALID) {
+		DBG("Keys not ready(%d). s=%d, l0=%0x08x",
+			hdcp_ctrl->work_retry_cnt,
+			keys_state, link0_status);
+
+		return -EAGAIN;
+	}
+
+	/* Wait for An0 and An1 bit to be ready */
+	an_ready = (link0_status & BIT(8)) && (link0_status & BIT(9));
+	if (!an_ready) {
+		DBG("An not ready(%d). l0_status=0x%08x",
+			hdcp_ctrl->work_retry_cnt, link0_status);
+
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static int hdmi_hdcp_send_aksv_an(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t link0_aksv_0, link0_aksv_1;
+	uint32_t link0_an_0, link0_an_1;
+	uint8_t aksv[5];
+	uint8_t an[8];
+
+	/* Read An0 and An1 */
+	link0_an_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA5);
+	link0_an_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA6);
+
+	/* Read AKSV */
+	link0_aksv_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA3);
+	link0_aksv_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4);
+
+	DBG("Link ASKV=%08x%08x", link0_aksv_0, link0_aksv_1);
+	/* Copy An and AKSV to byte arrays for transmission */
+	aksv[0] =  link0_aksv_0        & 0xFF;
+	aksv[1] = (link0_aksv_0 >> 8)  & 0xFF;
+	aksv[2] = (link0_aksv_0 >> 16) & 0xFF;
+	aksv[3] = (link0_aksv_0 >> 24) & 0xFF;
+	aksv[4] =  link0_aksv_1        & 0xFF;
+
+	an[0] =  link0_an_0        & 0xFF;
+	an[1] = (link0_an_0 >> 8)  & 0xFF;
+	an[2] = (link0_an_0 >> 16) & 0xFF;
+	an[3] = (link0_an_0 >> 24) & 0xFF;
+	an[4] =  link0_an_1        & 0xFF;
+	an[5] = (link0_an_1 >> 8)  & 0xFF;
+	an[6] = (link0_an_1 >> 16) & 0xFF;
+	an[7] = (link0_an_1 >> 24) & 0xFF;
+
+	/* Write An to offset 0x18 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x18, an, 8);
+	if (rc) {
+		pr_err("%s:An write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-An=%08x%08x", link0_an_1, link0_an_0);
+
+	/* Write AKSV to offset 0x10 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x10, aksv, 5);
+	if (rc) {
+		pr_err("%s:AKSV write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-AKSV=%02x%08x", link0_aksv_1 & 0xFF, link0_aksv_0);
+
+	return 0;
+}
+
+static int hdmi_hdcp_recv_bksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t link0_bksv_0, link0_bksv_1;
+	uint8_t *bksv = NULL;
+	uint32_t reg[2], data[2];
+
+	bksv = hdcp_ctrl->bksv;
+
+	/* Read BKSV at offset 0x00 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x00, bksv, 5, true);
+	if (rc) {
+		pr_err("%s:BKSV read failed\n", __func__);
+		return rc;
+	}
+
+	/* check there are 20 ones in BKSV */
+	if (hdmi_hdcp_count_one(bksv, 5) != 20) {
+		pr_err(": BKSV doesn't have 20 1's and 20 0's\n");
+		pr_err(": BKSV chk fail. BKSV=%02x%02x%02x%02x%02x\n",
+			bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]);
+		return -EINVAL;
+	}
+
+	link0_bksv_0 = bksv[3];
+	link0_bksv_0 = (link0_bksv_0 << 8) | bksv[2];
+	link0_bksv_0 = (link0_bksv_0 << 8) | bksv[1];
+	link0_bksv_0 = (link0_bksv_0 << 8) | bksv[0];
+	link0_bksv_1 = bksv[4];
+	DBG(":BKSV=%02x%08x", link0_bksv_1, link0_bksv_0);
+
+	/* Write BKSV read from sink to HDCP registers */
+	reg[0] = REG_HDMI_HDCP_RCVPORT_DATA0;
+	data[0] = link0_bksv_0;
+	reg[1] = REG_HDMI_HDCP_RCVPORT_DATA1;
+	data[1] = link0_bksv_1;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static int hdmi_hdcp_recv_bcaps(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t reg, data;
+	uint8_t bcaps;
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1, true);
+	if (rc) {
+		pr_err("%s:BCAPS read failed\n", __func__);
+		return rc;
+	}
+	DBG("BCAPS=%02x", bcaps);
+
+	/* receiver (0), repeater (1) */
+	hdcp_ctrl->ds_type = (bcaps & BIT(6)) ? DS_REPEATER : DS_RECEIVER;
+
+	/* Write BCAPS to the hardware */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = (uint32_t)bcaps;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+	return rc;
+}
+
+static void hdmi_hdcp_auth_part1_1_work(struct work_struct *work)
+{
+	int rc = 0;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part1_1_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		pr_err("%s: invalid state. returning\n", __func__);
+		return;
+	}
+
+	hdcp_ctrl->work_retry_cnt--;
+
+	/*
+	 * Wait for AKSV key and An ready
+	 */
+	rc = hdmi_hdcp_check_key_an_ready(hdcp_ctrl);
+	if ((rc == -EAGAIN) && hdcp_ctrl->work_retry_cnt) {
+		queue_delayed_work(hdmi->workq,
+			&hdcp_ctrl->hdcp_auth_part1_1_work, HZ/50);
+		return;
+	} else if (rc) {
+		pr_err("%s: Key/An not ready, abort\n", __func__);
+		goto error;
+	}
+
+	/*
+	 * Read BCAPS and send to HDCP engine
+	 */
+	rc = hdmi_hdcp_recv_bcaps(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: read bcaps error, abort\n", __func__);
+		goto error;
+	}
+
+	/*
+	 * 1.1_Features turned off by default.
+	 * No need to write AInfo since 1.1_Features is disabled.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4, 0);
+
+	/*
+	 * Send AKSV and An to sink
+	 */
+	rc = hdmi_hdcp_send_aksv_an(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:An/Aksv write failed\n", __func__);
+		goto error;
+	}
+
+	/* Read BKSV and send to HDCP engine*/
+	rc = hdmi_hdcp_recv_bksv(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:BKSV Process failed\n", __func__);
+		goto error;
+	}
+
+	/* Enable HDCP interrupts and ack/clear any stale interrupts */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0xE6);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * HDCP Compliance Test case 1A-01:
+	 * Wait here at least 100ms before reading R0'
+	 */
+	hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+	queue_delayed_work(hdmi->workq,
+		&hdcp_ctrl->hdcp_auth_part1_2_work, HZ/8);
+
+	return;
+error:
+	pr_err("%s: Authentication Part I failed %d\n", __func__, rc);
+	hdmi_hdcp_auth_fail(hdcp_ctrl);
+}
+
+/*
+ * hdcp authenticating part 1: 2nd
+ * read R0' from sink and pass it to HDCP engine
+ */
+static void hdmi_hdcp_auth_part1_2_work(struct work_struct *work)
+{
+	int rc = 0;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part1_2_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint8_t buf[2];
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		pr_err("%s: invalid state. returning\n", __func__);
+		return;
+	}
+
+	/* Read R0' at offset 0x08 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x08, buf, 2, true);
+	if (rc) {
+		pr_err("%s:R0' read failed\n", __func__);
+		goto error;
+	}
+	DBG("R0'=%02x%02x", buf[1], buf[0]);
+
+	/* Write R0' to HDCP registers and check to see if it is a match */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA2_0,
+		(((uint32_t)buf[1]) << 8) | buf[0]);
+
+	return;
+error:
+	pr_err("%s: Authentication Part I:2 failed %d\n", __func__, rc);
+	hdmi_hdcp_auth_fail(hdcp_ctrl);
+}
+
+/*
+ * hdcp authenticating part 1: 3rd
+ * Wait for authenticating result: R0/R0' are matched or not
+ */
+static void hdmi_hdcp_auth_part1_3_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part1_3_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	bool is_match;
+	uint32_t link0_status;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		pr_err("%s: invalid state. returning\n", __func__);
+		return;
+	}
+
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	is_match = link0_status & BIT(12);
+	if (!is_match) {
+		pr_err("%s: Authentication Part I failed\n", __func__);
+		hdmi_hdcp_auth_fail(hdcp_ctrl);
+	} else {
+		/* Enable HDCP Encryption */
+		hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, BIT(0) | BIT(8));
+		DBG("Authentication Part I successful");
+		if (hdcp_ctrl->ds_type == DS_REPEATER) {
+			/* queue HDCP PartII work */
+			hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+			queue_delayed_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_auth_part2_1_work, 0);
+		} else {
+			hdmi_hdcp_auth_done(hdcp_ctrl);
+		}
+	}
+}
+
+/*
+ * hdcp authenticating part 2: 1st
+ * wait until sink (repeater)'s ksv fifo ready
+ * read bstatus from sink and write to HDCP engine
+ */
+static int hdmi_hdcp_recv_bstatus(struct hdmi_hdcp_ctrl *hdcp_ctrl,
+	uint8_t bcaps)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint16_t bstatus;
+	bool max_devs_exceeded = false, max_cascade_exceeded = false;
+	uint32_t repeater_cascade_depth = 0, down_stream_devices = 0;
+	uint32_t reg, data;
+	uint8_t buf[2];
+
+	memset(buf, 0, sizeof(buf));
+
+	/* Read BSTATUS at offset 0x41 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x41, buf, 2, false);
+	if (rc) {
+		pr_err("%s: BSTATUS read failed\n", __func__);
+		goto error;
+	}
+	bstatus = buf[1];
+	bstatus = (bstatus << 8) | buf[0];
+
+	/* Write BSTATUS and BCAPS to HDCP registers */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = bcaps | (bstatus << 8);
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+	if (rc) {
+		pr_err("%s: BSTATUS write failed\n", __func__);
+		goto error;
+	}
+
+	down_stream_devices = bstatus & 0x7F;
+	repeater_cascade_depth = (bstatus >> 8) & 0x7;
+	max_devs_exceeded = (bstatus & BIT(7)) ? true : false;
+	max_cascade_exceeded = (bstatus & BIT(11)) ? true : false;
+
+	if (down_stream_devices == 0) {
+		/*
+		 * If no downstream devices are attached to the repeater
+		 * then part II fails.
+		 * todo: The other approach would be to continue PART II.
+		 */
+		pr_err("%s: No downstream devices\n", __func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-05:
+	 * Check if no. of devices connected to repeater
+	 * exceed max_devices_connected from bit 7 of Bstatus.
+	 */
+	if (max_devs_exceeded) {
+		pr_err("%s: no. of devs connected exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-06:
+	 * Check if no. of cascade connected to repeater
+	 * exceed max_cascade_connected from bit 11 of Bstatus.
+	 */
+	if (max_cascade_exceeded) {
+		pr_err("%s: no. of cascade conn exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+error:
+	hdcp_ctrl->dev_count = down_stream_devices;
+	hdcp_ctrl->max_cascade_exceeded = max_cascade_exceeded;
+	hdcp_ctrl->max_dev_exceeded = max_devs_exceeded;
+	hdcp_ctrl->depth = repeater_cascade_depth;
+	return rc;
+}
+
+static void hdmi_hdcp_auth_part2_1_work(struct work_struct *work)
+{
+	int rc;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part2_1_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint8_t bcaps;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		pr_err("%s: invalid state. returning", __func__);
+		return;
+	}
+
+	hdcp_ctrl->work_retry_cnt--;
+
+	/*
+	 * Wait until READY bit is set in BCAPS, as per HDCP specifications
+	 * maximum permitted time to check for READY bit is five seconds.
+	 */
+	/* Read BCAPS at offset 0x40 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1, false);
+	if (rc) {
+		pr_err("%s: BCAPS read failed\n", __func__);
+		goto error;
+	}
+
+	if (!(bcaps & BIT(5))) {
+		if (hdcp_ctrl->work_retry_cnt) {
+			DBG("ksv fifo (%d) is not ready",
+				hdcp_ctrl->work_retry_cnt);
+			queue_delayed_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_auth_part2_1_work, HZ/20);
+			return;
+		} else {
+			pr_err("%s: timeout to wait ksv ready\n",
+				__func__);
+			rc = -ETIMEDOUT;
+			goto error;
+		}
+	}
+
+	rc = hdmi_hdcp_recv_bstatus(hdcp_ctrl, bcaps);
+	if (rc) {
+		pr_err("%s: bstatus error\n", __func__);
+		goto error;
+	}
+
+	hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+	queue_delayed_work(hdmi->workq,
+		&hdcp_ctrl->hdcp_auth_part2_2_work, 0);
+
+	return;
+error:
+	pr_err("%s: Authentication Part II failed %d\n", __func__, rc);
+	hdmi_hdcp_auth_fail(hdcp_ctrl);
+}
+
+/*
+ * hdcp authenticating part 2: 2nd
+ * read ksv fifo from sink
+ * transfer V' from sink to HDCP engine
+ * reset SHA engine
+ */
+int hdmi_hdcp_read_v(struct hdmi *hdmi, char *name,
+	uint32_t off, uint32_t *val)
+{
+	int rc = 0;
+	uint8_t buf[4];
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, off, buf, 4, true);
+	if (rc) {
+		pr_err("%s: Read %s failed\n", __func__,
+			name);
+		return rc;
+	}
+
+	if (val)
+		*val = (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]);
+
+	DBG("%s: buf[0]=%x, buf[1]=%x, buf[2]=%x, buf[3]=%x", name,
+		buf[0], buf[1], buf[2], buf[3]);
+
+	return rc;
+}
+
+static int hdmi_hdcp_transfer_v_h(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	int rc = 0;
+	struct hdmi_hdcp_reg_data reg_data[]  = {
+		{REG_HDMI_HDCP_RCVPORT_DATA7,  0x20, "V' H0"},
+		{REG_HDMI_HDCP_RCVPORT_DATA8,  0x24, "V' H1"},
+		{REG_HDMI_HDCP_RCVPORT_DATA9,  0x28, "V' H2"},
+		{REG_HDMI_HDCP_RCVPORT_DATA10, 0x2C, "V' H3"},
+		{REG_HDMI_HDCP_RCVPORT_DATA11, 0x30, "V' H4"},
+	};
+	struct hdmi_hdcp_reg_data *rd;
+	uint32_t size = ARRAY_SIZE(reg_data);
+	uint32_t reg[ARRAY_SIZE(reg_data)], data[ARRAY_SIZE(reg_data)];
+	int i;
+
+	for (i = 0; i < size; i++) {
+		rd = &reg_data[i];
+		rc = hdmi_hdcp_read_v(hdmi, rd->name,
+			rd->off, &data[i]);
+		if (rc)
+			goto error;
+
+		reg[i] = reg_data[i].reg_id;
+	}
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, size);
+
+error:
+	return rc;
+}
+
+static int hdmi_hdcp_recv_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint8_t *ksv_fifo = NULL;
+	uint32_t ksv_bytes;
+
+	ksv_fifo = hdcp_ctrl->ksv_list;
+	ksv_bytes = 5 * hdcp_ctrl->dev_count;
+	memset(ksv_fifo, 0,
+		sizeof(hdcp_ctrl->ksv_list));
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x43,
+		ksv_fifo, ksv_bytes, true);
+	if (rc)
+		pr_err("%s: KSV FIFO read failed\n", __func__);
+
+	return rc;
+}
+
+static int hdmi_hdcp_reset_sha_engine(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	uint32_t reg[2], data[2];
+	uint32_t rc  = 0;
+
+	reg[0] = REG_HDMI_HDCP_SHA_CTRL;
+	data[0] = HDCP_REG_ENABLE;
+	reg[1] = REG_HDMI_HDCP_SHA_CTRL;
+	data[1] = HDCP_REG_DISABLE;
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static void hdmi_hdcp_auth_part2_2_work(struct work_struct *work)
+{
+	int rc;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part2_2_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		DBG("invalid state. returning");
+		return;
+	}
+
+	hdcp_ctrl->work_retry_cnt--;
+
+	/*
+	 * Read KSV FIFO over DDC
+	 * Key Selection vector FIFO Used to pull downstream KSVs
+	 * from HDCP Repeaters.
+	 * All bytes (DEVICE_COUNT * 5) must be read in a single,
+	 * auto incrementing access.
+	 * All bytes read as 0x00 for HDCP Receivers that are not
+	 * HDCP Repeaters (REPEATER == 0).
+	 */
+	rc = hdmi_hdcp_recv_ksv_fifo(hdcp_ctrl);
+	if (rc && hdcp_ctrl->work_retry_cnt) {
+		/*
+		 * HDCP Compliance Test case 1B-01:
+		 * Wait until all the ksv bytes have been
+		 * read from the KSV FIFO register.
+		 */
+		queue_delayed_work(hdmi->workq,
+			&hdcp_ctrl->hdcp_auth_part2_2_work, HZ/40);
+		return;
+	} else if (rc) {
+		pr_err("%s: error to recv ksv fifo\n", __func__);
+		goto error;
+	}
+
+	rc = hdmi_hdcp_transfer_v_h(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: transfer V failed\n", __func__);
+		goto error;
+	}
+
+	/* reset SHA engine before write ksv fifo */
+	rc = hdmi_hdcp_reset_sha_engine(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: fail to reset sha engine\n", __func__);
+		goto error;
+	}
+
+	hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+	hdcp_ctrl->ksv_fifo_w_index = 0;
+	queue_delayed_work(hdmi->workq,
+		&hdcp_ctrl->hdcp_auth_part2_3_work, 0);
+
+	return;
+error:
+	pr_err("%s: Authentication Part II failed\n", __func__);
+	hdmi_hdcp_auth_fail(hdcp_ctrl);
+}
+
+/*
+ * Write KSV FIFO to HDCP_SHA_DATA.
+ * This is done 1 byte at time starting with the LSB.
+ * Once 64 bytes have been written, we need to poll for
+ * HDCP_SHA_BLOCK_DONE before writing any further
+ * If the last byte is written, we need to poll for
+ * HDCP_SHA_COMP_DONE to wait until HW finish
+ */
+static int hdmi_hdcp_write_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int i;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t ksv_bytes, last_byte = 0;
+	uint8_t *ksv_fifo = NULL;
+	uint32_t reg_val, data, reg;
+	uint32_t rc  = 0;
+
+	ksv_bytes  = 5 * hdcp_ctrl->dev_count;
+
+	/* Check if need to wait for HW completion */
+	if (hdcp_ctrl->ksv_fifo_w_index) {
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_SHA_STATUS);
+		DBG("HDCP_SHA_STATUS=%08x", reg_val);
+		if (hdcp_ctrl->ksv_fifo_w_index == ksv_bytes) {
+			/* check COMP_DONE if last write */
+			if (reg_val & BIT(4)) {
+				DBG("COMP_DONE");
+				return 0;
+			} else {
+				return -EAGAIN;
+			}
+		} else {
+			/* check BLOCK_DONE if not last write */
+			if (!(reg_val & BIT(0)))
+				return -EAGAIN;
+			else
+				DBG("BLOCK_DONE");
+		}
+	}
+
+	ksv_bytes  -= hdcp_ctrl->ksv_fifo_w_index;
+	if (ksv_bytes <= 64)
+		last_byte = 1;
+	else
+		ksv_bytes = 64;
+
+	ksv_fifo = hdcp_ctrl->ksv_list;
+	ksv_fifo += hdcp_ctrl->ksv_fifo_w_index;
+
+	for (i = 0; i < ksv_bytes; i++) {
+		/* Write KSV byte and set DONE bit[0] for last byte*/
+		reg_val = ksv_fifo[i] << 16;
+		if ((i == (ksv_bytes - 1)) && last_byte)
+			reg_val |= BIT(0);
+
+		reg = REG_HDMI_HDCP_SHA_DATA;
+		data = reg_val;
+		rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+		if (rc)
+			return rc;
+	}
+
+	hdcp_ctrl->ksv_fifo_w_index += ksv_bytes;
+
+	/*
+	 *return -EAGAIN to notify caller to wait for COMP_DONE or BLOCK_DONE
+	 */
+	return -EAGAIN;
+}
+
+/*
+ * hdcp authenticating part 2: 3rd
+ * write ksv fifo into HDCP engine
+ */
+static void hdmi_hdcp_auth_part2_3_work(struct work_struct *work)
+{
+	int rc;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part2_3_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		DBG("invalid state. returning");
+		return;
+	}
+
+	hdcp_ctrl->work_retry_cnt--;
+
+	rc = hdmi_hdcp_write_ksv_fifo(hdcp_ctrl);
+	if ((rc == -EAGAIN) && hdcp_ctrl->work_retry_cnt) {
+		queue_delayed_work(hdmi->workq,
+			&hdcp_ctrl->hdcp_auth_part2_3_work, HZ/50);
+		return;
+	} else if (rc) {
+		pr_err("%s: error write ksv fifo\n", __func__);
+		goto error;
+	}
+
+	hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
+	queue_delayed_work(hdmi->workq,
+		&hdcp_ctrl->hdcp_auth_part2_4_work, HZ/50);
+
+	return;
+error:
+	pr_err("%s: Authentication Part II failed\n", __func__);
+	hdmi_hdcp_auth_fail(hdcp_ctrl);
+} /* hdmi_hdcp_authentication_part2 */
+
+/*
+ * hdcp authenticating part 2: 4th
+ * Wait for V_MATCHES
+ */
+static void hdmi_hdcp_auth_part2_4_work(struct work_struct *work)
+{
+	int rc = 0;
+	struct delayed_work *dw = to_delayed_work(work);
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
+		struct hdmi_hdcp_ctrl, hdcp_auth_part2_4_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	uint32_t link0_status;
+
+	if (HDCP_STATE_AUTHENTICATING != hdcp_ctrl->hdcp_state) {
+		DBG("invalid state. returning");
+		return;
+	}
+
+	hdcp_ctrl->work_retry_cnt--;
+
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	if (!(link0_status & BIT(20))) {
+		if (hdcp_ctrl->work_retry_cnt) {
+			queue_delayed_work(hdmi->workq,
+				&hdcp_ctrl->hdcp_auth_part2_4_work, HZ/50);
+			return;
+		} else {
+			rc = -ETIMEDOUT;
+			pr_err("%s: HDCP V Match timedout", __func__);
+		}
+	}
+
+	if (rc) {
+		pr_err("%s: Authentication Part II failed\n", __func__);
+		hdmi_hdcp_auth_fail(hdcp_ctrl);
+	} else {
+		pr_info("%s: Authentication Part II successful\n",
+			__func__);
+		hdmi_hdcp_auth_done(hdcp_ctrl);
+	}
+}
+
+int hdmi_hdcp_on(struct hdmi *hdmi)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = hdmi->hdcp_ctrl;
+	uint32_t regval;
+	unsigned long flags;
+
+	if (!hdcp_ctrl) {
+		pr_warn("%s:hdcp_ctrl is NULL\n", __func__);
+		return 0;
+	}
+
+	if (HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) {
+		DBG("still active or activating. returning");
+		return 0;
+	}
+
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	regval = hdmi_read(hdmi, REG_HDMI_CTRL);
+	regval &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, regval);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	mutex_lock(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	mutex_unlock(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->auth_retries = 0;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_prepare_work);
+
+	return 0;
+}
+
+void hdmi_hdcp_off(struct hdmi *hdmi)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = hdmi->hdcp_ctrl;
+	unsigned long flags;
+	uint32_t regval;
+	int rc = 0;
+
+	if (!hdcp_ctrl) {
+		pr_err("%s:hdcp_ctrl is NULL\n", __func__);
+		return;
+	}
+
+	if (HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) {
+		DBG("hdcp inactive. returning");
+		return;
+	}
+
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
+		hdmi_read(hdmi, REG_HDMI_HPD_CTRL) & ~BIT(28));
+
+	/*
+	 * Disable HDCP interrupts.
+	 * Also, need to set the state to inactive here so that any ongoing
+	 * reauth works will know that the HDCP session has been turned off.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Cancel any pending auth/reauth attempts.
+	 * If one is ongoing, this will wait for it to finish.
+	 * No more reauthentication attempts will be scheduled since we
+	 * set the current state to inactive.
+	 */
+	rc = cancel_work_sync(&hdcp_ctrl->hdcp_auth_prepare_work);
+	if (rc)
+		DBG("Deleted hdcp auth prepare work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_reauth_work);
+	if (rc)
+		DBG("Deleted hdcp reauth work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part1_1_work);
+	if (rc)
+		DBG("Deleted hdcp auth part1_1 work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part1_2_work);
+	if (rc)
+		DBG("Deleted hdcp auth part1_2 work");
+	rc = cancel_work_sync(&hdcp_ctrl->hdcp_auth_part1_3_work);
+	if (rc)
+		DBG("Deleted hdcp auth part1_3 work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_1_work);
+	if (rc)
+		DBG("Deleted hdcp auth part2_1 work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_2_work);
+	if (rc)
+		DBG("Deleted hdcp auth part2_2 work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_3_work);
+	if (rc)
+		DBG("Deleted hdcp auth part2_3 work");
+	rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_4_work);
+	if (rc)
+		DBG("Deleted hdcp auth part2_4 work");
+
+	/* set state to inactive after all work cancelled */
+	mutex_lock(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+	mutex_unlock(&hdcp_ctrl->state_mutex);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET, BIT(0));
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	regval = hdmi_read(hdmi, REG_HDMI_CTRL);
+	regval &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, regval);
+
+	/* Enable HPD circuitry */
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
+		hdmi_read(hdmi, REG_HDMI_HPD_CTRL) | BIT(28));
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	DBG("HDCP: Off");
+} /* hdmi_hdcp_off */
+
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi)
+{
+	uint32_t scm_buf = TZ_HDCP_CMD_ID;
+	struct hdmi_hdcp_ctrl *hdcp_ctrl;
+	uint32_t ret  = 0;
+	uint32_t resp = 0;
+
+	if (!hdmi->qfprom_mmio) {
+		pr_err("%s: HDCP is not supported without qfprom\n",
+			__func__);
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	hdcp_ctrl = kzalloc(sizeof(*hdcp_ctrl), GFP_KERNEL);
+	if (!hdcp_ctrl) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	INIT_WORK(&hdcp_ctrl->hdcp_auth_prepare_work,
+		hdmi_hdcp_auth_prepare_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_reauth_work, hdmi_hdcp_reauth_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part1_1_work,
+		hdmi_hdcp_auth_part1_1_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part1_2_work,
+		hdmi_hdcp_auth_part1_2_work);
+	INIT_WORK(&hdcp_ctrl->hdcp_auth_part1_3_work,
+		hdmi_hdcp_auth_part1_3_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_1_work,
+		hdmi_hdcp_auth_part2_1_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_2_work,
+		hdmi_hdcp_auth_part2_2_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_3_work,
+		hdmi_hdcp_auth_part2_3_work);
+	INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_4_work,
+		hdmi_hdcp_auth_part2_4_work);
+
+	hdcp_ctrl->hdmi = hdmi;
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+	mutex_init(&hdcp_ctrl->state_mutex);
+	hdcp_ctrl->aksv_valid = false;
+
+	ret = scm_call(SCM_SVC_INFO, SCM_CMD_HDCP, &scm_buf,
+		sizeof(scm_buf), &resp, sizeof(resp));
+	if (ret) {
+		pr_err("%s: error: scm_call ret = %d, resp = %d\n",
+			__func__, ret, resp);
+		goto fail;
+	} else {
+		DBG("tz_hdcp = %d", resp);
+		hdcp_ctrl->tz_hdcp = resp;
+	}
+
+	return hdcp_ctrl;
+fail:
+	kfree(hdcp_ctrl);
+	return ERR_PTR(ret);
+} /* hdmi_hdcp_init */
+
+void hdmi_hdcp_destroy(struct hdmi *hdmi)
+{
+	if (hdmi && hdmi->hdcp_ctrl) {
+		kfree(hdmi->hdcp_ctrl);
+		hdmi->hdcp_ctrl = NULL;
+	}
+}
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-01 21:56 [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Jilai Wang
@ 2014-12-03  1:46 ` Bjorn Andersson
  2014-12-03 16:42   ` Rob Clark
  2014-12-03 16:51 ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Rob Clark
  1 sibling, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2014-12-03  1:46 UTC (permalink / raw)
  To: Jilai Wang; +Cc: dri-devel, linux-arm-msm, linux-kernel, Rob Clark

On Mon, Dec 1, 2014 at 1:56 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>
> Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
> ---

Hi Jilai,

[..]

> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c

[..]

>
> @@ -119,6 +137,22 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
>                 goto fail;
>         }
>
> +       /* HDCP needs physical address of hdmi register */
> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +               config->mmio_name);

Is this guaranteed to be available at all times? You should probably
do some error handling here.

> +       hdmi->mmio_phy_addr = res->start;
> +
> +       if (config->qfprom_mmio_name) {

Should this check really be here? This will always be set if CONFIG_OF is set
and never otherwise and that seems strange to me.

Perhaps you should add the string to the !CONFIG_OF platforms as well or simply
add #ifdef CONFIG_OF around this section if that's what you really want. (but
seems more like you forgot the non-of case).

> +               hdmi->qfprom_mmio = msm_ioremap(pdev,
> +                       config->qfprom_mmio_name, "HDMI_QFPROM");


Is this a special hdmi qfprom or are you ioremapping _the_ qfprom here?

If so I did suggest that we expose it as a syscon but I think Stephen Boyd had
some other ideas.

> +               if (IS_ERR(hdmi->qfprom_mmio)) {
> +                       dev_info(&pdev->dev, "can't find qfprom resource\n");
> +                       hdmi->qfprom_mmio = NULL;
> +               }
> +       } else {
> +               hdmi->qfprom_mmio = NULL;

hdmi_qfprom_read() seems to be called and read from qfprom_mmio no matter how
this ended. Are you sure this (both error paths) shouldn't be handled as a
fatal error?

'hdmi' is kzalloc and hence already NULL.

[..]

> @@ -205,6 +241,13 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
>                 goto fail;
>         }
>
> +       hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
> +       if (IS_ERR(hdmi->hdcp_ctrl)) {
> +               ret = PTR_ERR(hdmi->hdcp_ctrl);
> +               dev_warn(dev->dev, "failed to init hdcp: %d(disabled)\n", ret);
> +               hdmi->hdcp_ctrl = NULL;

So either you treat this as an error or you don't.

If you're fine continuing execution without hdcp_ctrl then you shouldn't set
ret. But in that case it you should probably not print a warning every time you
enter hdmi_hdcp_on() and an error on hdmi_hdcp_off().

[..]

> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h

[..]

> @@ -70,12 +74,25 @@ struct hdmi {
>         bool hdmi_mode;               /* are we in hdmi mode? */
>
>         int irq;
> +       struct workqueue_struct *workq;

Do you really need a special workqueue for this, can't you use the system work
queue (or system_long_wq)?

[..]

>
> +static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
> +{
> +       return msm_readl(hdmi->qfprom_mmio + reg);

As stated above, qfprom_mmio might be NULL.

> +}
> +

[..]

> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c

[..]

> +
> +#define TZ_HDCP_CMD_ID 0x00004401

Add a definition of SCM_SVC_HDCP being 17 to scm.h and define SCM_CMD_HDCP to 1
here instead.

[..]

> +
> +struct hdmi_hdcp_reg_data {
> +       uint32_t reg_id;

You should use u32 instead of uint32_t in the kernel.

> +       uint32_t off;
> +       char *name;
> +       uint32_t reg_val;
> +};
> +
> +struct hdmi_hdcp_ctrl {
> +       struct hdmi *hdmi;
> +       uint32_t auth_retries;
> +       uint32_t tz_hdcp;

Turn this into a bool named something like has_tz_hdcp instead, as that's what
it really means.

> +       enum hdmi_hdcp_state hdcp_state;
> +       struct mutex state_mutex;
> +       struct delayed_work hdcp_reauth_work;
> +       struct delayed_work hdcp_auth_part1_1_work;
> +       struct delayed_work hdcp_auth_part1_2_work;
> +       struct work_struct hdcp_auth_part1_3_work;
> +       struct delayed_work hdcp_auth_part2_1_work;
> +       struct delayed_work hdcp_auth_part2_2_work;
> +       struct delayed_work hdcp_auth_part2_3_work;
> +       struct delayed_work hdcp_auth_part2_4_work;
> +       struct work_struct hdcp_auth_prepare_work;

You shouldn't use "work" as a way to express states in your state machine.
Better have 1 auth work function that does all these steps, probably having
them split in functions just like you do now.

That way you can have 1 function running the pass of authentication, starting
by checking if you're reauthing or not then processing each step one by one,
sleeping inbetween them. You can have the functions return -EAGAIN to indicate
that you need to retry the current operation and so on.

This would split the state machine from the state executioners and simplify
your code.

> +       uint32_t work_retry_cnt;
> +       uint32_t ksv_fifo_w_index;
> +
> +       /*
> +        * store aksv from qfprom
> +        */
> +       uint8_t aksv[5];

You should use u8 intead of uint8_t in the kernel.

> +       bool aksv_valid;
> +       uint32_t ds_type;
> +       uint8_t bksv[5];
> +       uint8_t dev_count;
> +       uint8_t depth;
> +       uint8_t ksv_list[5 * 127];
> +       bool max_cascade_exceeded;
> +       bool max_dev_exceeded;
> +};
> +
> +static int hdmi_ddc_read(struct hdmi *hdmi, uint16_t addr, uint8_t offset,
> +       uint8_t *data, uint16_t data_len, bool no_align)
> +{

As far as I can see you can replace this entire function with:

return i2c_smbus_read_i2c_block_data(hdmi->i2c, addr + offset, data_len, data);

Although note that it would return data_len instead of 0 on success.

> +       int rc = 0;
> +       int retry = 5;
> +       uint8_t *buf = NULL;
> +       uint32_t request_len;
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = addr >> 1,
> +                       .flags  = 0,
> +                       .len    = 1,
> +                       .buf    = &offset,
> +               }, {
> +                       .addr   = addr >> 1,
> +                       .flags  = I2C_M_RD,
> +                       .len    = data_len,
> +                       .buf    = data,
> +               }
> +       };
> +
> +       DBG("Start DDC read");
> +retry:
> +       if (no_align) {
> +               rc = i2c_transfer(hdmi->i2c, msgs, 2);
> +       } else {
> +               request_len = 32 * ((data_len + 31) / 32);

Why are you doing this. The documentation for i2c_msg states that if you set
I2C_M_RECV_LEN you have to be able to do something like this, but you don't.

I don't see anything special with the buffers you're reading into, so you
should be able to just use i2c_smbus_read_i2c_block_data() and be done with it.

> +               buf = kmalloc(request_len, GFP_KERNEL);
> +               if (!buf)
> +                       return -ENOMEM;
> +
> +               msgs[1].buf = buf;
> +               rc = i2c_transfer(hdmi->i2c, msgs, 2);
> +       }
> +
> +       retry--;
> +       if (rc == 2) {
> +               rc = 0;
> +               if (!no_align)
> +                       memcpy(data, buf, data_len);
> +       } else if (retry > 0) {
> +               goto retry;
> +       } else {
> +               rc = -EIO;
> +       }
> +
> +       kfree(buf);
> +       DBG("End DDC read %d", rc);
> +
> +       return rc;
> +}
> +
> +static int hdmi_ddc_write(struct hdmi *hdmi, uint16_t addr, uint8_t offset,
> +       uint8_t *data, uint16_t data_len)
> +{

And as for hdmi_ddc_read, this would be:

i2c_smbus_write_i2c_block_data(hdmi->i2c, addr + offset, data_len, data);

> +       int rc = 0;
> +       int retry = 10;
> +       uint8_t *buf = NULL;
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = addr >> 1,
> +                       .flags  = 0,
> +                       .len    = 1,
> +               }
> +       };
> +
> +       DBG("Start DDC write");
> +       buf = kmalloc(data_len + 1, GFP_KERNEL);
> +       if (!buf)
> +               return -ENOMEM;
> +
> +       buf[0] = offset;
> +       memcpy(&buf[1], data, data_len);
> +       msgs[0].buf = buf;
> +       msgs[0].len = data_len + 1;
> +retry:
> +       rc = i2c_transfer(hdmi->i2c, msgs, 1);
> +
> +       retry--;
> +       if (rc == 1)
> +               rc = 0;
> +       else if (retry > 0)
> +               goto retry;
> +       else
> +               rc = -EIO;
> +
> +       kfree(buf);
> +       DBG("End DDC write %d", rc);
> +
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, uint32_t *preg,
> +       uint32_t *pdata, uint32_t count)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       struct scm_hdcp_req scm_buf[SCM_HDCP_MAX_REG];
> +       uint32_t resp, phy_addr, idx = 0;
> +       int i, ret = 0;
> +
> +       if (count == 0)
> +               return 0;

There are no calls to this function where count can be 0, so you can drop this
check.

> +
> +       if (!preg || !pdata) {
> +               pr_err("%s: Invalid pointer\n", __func__);
> +               return -EINVAL;
> +       }

There are no calls to this function where either of these are NULL, so you can
drop the entire block.

> +
> +       if (hdcp_ctrl->tz_hdcp) {
> +               phy_addr = (uint32_t)hdmi->mmio_phy_addr;
> +
> +               while (count) {
> +                       memset(scm_buf, 0, sizeof(scm_buf));
> +                       for (i = 0; i < count && i < SCM_HDCP_MAX_REG; i++) {
> +                               scm_buf[i].addr = phy_addr + preg[idx];
> +                               scm_buf[i].val  = pdata[idx];
> +                               idx++;
> +                       }
> +                       ret = scm_call(SCM_SVC_HDCP, SCM_CMD_HDCP,
> +                               scm_buf, sizeof(scm_buf), &resp, sizeof(resp));

SCM_SVC_HDCP nor SCM_CMD_HDCP are defined, here. See the comment above related
to TZ_HDCP_CMD_ID.

> +
> +                       if (ret || resp) {
> +                               pr_err("%s: error: scm_call ret = %d, resp = %d\n",
> +                                       __func__, ret, resp);
> +                               ret = -EINVAL;
> +                               break;
> +                       }
> +
> +                       count -= i;
> +               }
> +       } else {
> +               for (i = 0; i < count; i++)
> +                       hdmi_write(hdmi, preg[i], pdata[i]);
> +       }
> +
> +       return ret;
> +}
> +
> +void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi;
> +       uint32_t regval, hdcp_int_status;
> +       unsigned long flags;
> +
> +       if (!hdcp_ctrl) {
> +               DBG("HDCP is disabled");
> +               return;
> +       }

No need to print a debug line here every time.

I would have preferred if you made the call from hdmi_irq() conditional
instead, then you would need to check here...

> +
> +       hdmi = hdcp_ctrl->hdmi;

And you could have folded this into the declaration above.

> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       regval = hdmi_read(hdmi, REG_HDMI_HDCP_INT_CTRL);
> +       hdcp_int_status = regval & HDCP_INT_STATUS;
> +       if (!hdcp_int_status) {
> +               spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +               return;
> +       }
> +       /* Clear Interrupts */
> +       regval |= hdcp_int_status << 1;
> +       /* Clear AUTH_FAIL_INFO as well */
> +       if (hdcp_int_status & BIT(4))
> +               regval |= BIT(7);
> +       hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, regval);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       DBG("hdcp irq %x", regval);
> +       if (hdcp_int_status & BIT(0)) {
> +               /* AUTH_SUCCESS_INT */
> +               pr_info("%s:AUTH_SUCCESS_INT received\n", __func__);
> +               if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state)
> +                       queue_work(hdmi->workq,
> +                               &hdcp_ctrl->hdcp_auth_part1_3_work);
> +       }
> +
> +       if (hdcp_int_status & BIT(4)) {
> +               /* AUTH_FAIL_INT */
> +               regval = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
> +               pr_info("%s: AUTH_FAIL_INT rcvd, LINK0_STATUS=0x%08x\n",
> +                       __func__, regval);
> +               if (HDCP_STATE_AUTHENTICATED == hdcp_ctrl->hdcp_state)
> +                       queue_delayed_work(hdmi->workq,
> +                               &hdcp_ctrl->hdcp_reauth_work, HZ/2);
> +               else if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state)
> +                       queue_work(hdmi->workq,
> +                               &hdcp_ctrl->hdcp_auth_part1_3_work);
> +       }
> +
> +       if (hdcp_int_status & BIT(8)) {
> +               /* DDC_XFER_REQ_INT */
> +               pr_info("%s:DDC_XFER_REQ_INT received\n", __func__);
> +       }
> +
> +       if (hdcp_int_status & BIT(12)) {
> +               /* DDC_XFER_DONE_INT */
> +               pr_info("%s:DDC_XFER_DONE received\n", __func__);
> +       }

You should drop the pr_info's here, as they add don't do really do anything.

> +} /* hdmi_hdcp_isr */

Drop the comment.

> +
> +static int hdmi_hdcp_count_one(uint8_t *array, uint8_t len)
> +{

If you can get your buffer into two u32's you can use hweight32 to do this
instead.

> +       int i, j, count = 0;
> +
> +       for (i = 0; i < len; i++)
> +               for (j = 0; j < 8; j++)
> +                       count += (((array[i] >> j) & 0x1) ? 1 : 0);
> +
> +       return count;
> +}
> +
> +static int hdmi_hdcp_read_validate_aksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t qfprom_aksv_lsb, qfprom_aksv_msb;
> +
> +       /* Fetch aksv from QFPROM, this info should be public. */
> +       qfprom_aksv_lsb = hdmi_qfprom_read(hdmi, HDCP_KSV_LSB);
> +       qfprom_aksv_msb = hdmi_qfprom_read(hdmi, HDCP_KSV_MSB);
> +
> +       hdcp_ctrl->aksv[0] =  qfprom_aksv_lsb        & 0xFF;
> +       hdcp_ctrl->aksv[1] = (qfprom_aksv_lsb >> 8)  & 0xFF;
> +       hdcp_ctrl->aksv[2] = (qfprom_aksv_lsb >> 16) & 0xFF;
> +       hdcp_ctrl->aksv[3] = (qfprom_aksv_lsb >> 24) & 0xFF;
> +       hdcp_ctrl->aksv[4] =  qfprom_aksv_msb        & 0xFF;

The only time you use these are in hdmi_hdcp_auth_prepare_work() where you
reverse this operation to get them back into two u32's of lsb and msb. Better
just store them as such then.

> +
> +       /* check there are 20 ones in AKSV */
> +       if (hdmi_hdcp_count_one(hdcp_ctrl->aksv, 5) != 20) {

And you can do hweight32(msb) + hweight32(lsb) != 20 here.

> +               pr_err("%s: AKSV QFPROM doesn't have 20 1's, 20 0's\n",
> +                       __func__);
> +               pr_err("%s: QFPROM AKSV chk failed (AKSV=%02x%08x)\n",
> +                       __func__, qfprom_aksv_msb,
> +                       qfprom_aksv_lsb);
> +               return -EINVAL;
> +       }
> +       DBG("AKSV=%02x%08x", qfprom_aksv_msb, qfprom_aksv_lsb);
> +
> +       return 0;
> +}
> +
> +static void reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int hdcp_ddc_ctrl1_reg;
> +       int hdcp_ddc_status;
> +       int failure;
> +       int nack0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +
> +       /* Check for any DDC transfer failures */
> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
> +       failure = (hdcp_ddc_status >> 16) & 0x1;

failure = hdcp_ddc_status & BIT(16);

> +       nack0 = (hdcp_ddc_status >> 14) & 0x1;

nack0 = hdcp_ddc_status & BIT(14);

> +       DBG("On Entry: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
> +               hdcp_ddc_status, failure, nack0);
> +
> +       if (failure == 0x1) {
> +               /*
> +                * Indicates that the last HDCP HW DDC transfer failed.
> +                * This occurs when a transfer is attempted with HDCP DDC
> +                * disabled (HDCP_DDC_DISABLE=1) or the number of retries
> +                * matches HDCP_DDC_RETRY_CNT.
> +                * Failure occurred,  let's clear it.
> +                */
> +               DBG("DDC failure detected.HDCP_DDC_STATUS=0x%08x",
> +                       hdcp_ddc_status);
> +
> +               /* First, Disable DDC */
> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, BIT(0));
> +
> +               /* ACK the Failure to Clear it */
> +               hdcp_ddc_ctrl1_reg = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1);
> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1,
> +                       hdcp_ddc_ctrl1_reg | BIT(0));
> +
> +               /* Check if the FAILURE got Cleared */
> +               hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);

Replace the following lines with:

if (hdcp_ddc_status & BIT(16))
pr_info("%s: Unable to clear HDCP DDC Failure\n", __func__);

No need to print the debug statement either...

> +               hdcp_ddc_status = (hdcp_ddc_status >> 16) & BIT(0);
> +               if (hdcp_ddc_status == 0x0)
> +                       DBG("HDCP DDC Failure cleared");
> +               else
> +                       pr_info("%s: Unable to clear HDCP DDC Failure\n",
> +                               __func__);
> +
> +               /* Re-Enable HDCP DDC */
> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
> +       }
> +
> +       if (nack0 == 0x1) {
> +               DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
> +               /* Reset HDMI DDC software status */
> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(3));

Split all these in:
val = hdmi_read()
val |= foo
hdmi_write(val);

To make this readable.

> +               msleep(20);
> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~(BIT(3)));
> +
> +               /* Reset HDMI DDC Controller */
> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(1));
> +               msleep(20);
> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~BIT(1));
> +               DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
> +       }
> +

Just end the function here, no need for the extra debug printouts...

> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
> +
> +       failure = (hdcp_ddc_status >> 16) & BIT(0);
> +       nack0 = (hdcp_ddc_status >> 14) & BIT(0);
> +       DBG("On Exit: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
> +               hdcp_ddc_status, failure, nack0);
> +}
> +
> +static void hdmi_hdcp_hw_ddc_clean(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       uint32_t hdcp_ddc_status, ddc_hw_status;
> +       uint32_t ddc_xfer_done, ddc_xfer_req, ddc_hw_done;
> +       uint32_t ddc_hw_not_ready;
> +       uint32_t timeout_count;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +
> +       if (hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS) == 0)
> +               return;
> +
> +       /* Wait to be clean on DDC HW engine */
> +       timeout_count = 100;
> +       do {
> +               hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
> +               ddc_hw_status = hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS);

An empty line between the reads and the logic would make things much easier to
read.

> +               ddc_xfer_done = hdcp_ddc_status & BIT(10);
> +               ddc_xfer_req = hdcp_ddc_status & BIT(4);
> +               ddc_hw_done = ddc_hw_status & BIT(3);
> +               ddc_hw_not_ready = !ddc_xfer_done ||
> +                       ddc_xfer_req || !ddc_hw_done;
> +

if (!ddc_hw_not_ready)
break;

Simplifies the bottom part of the loop...and then flip the logic around to
remove the double negation.

> +               DBG("timeout count(%d):ddc hw%sready",
> +                       timeout_count, ddc_hw_not_ready ? " not " : " ");
> +               DBG("hdcp_ddc_status[0x%x], ddc_hw_status[0x%x]",
> +                       hdcp_ddc_status, ddc_hw_status);

Don't dump 200 lines of debug prints just because this times out.

> +               if (ddc_hw_not_ready)
> +                       msleep(20);
> +       } while (ddc_hw_not_ready && --timeout_count);
> +}
> +
> +/*
> + * Only retries defined times then abort current authenticating process
> + */
> +static int hdmi_msm_if_abort_reauth(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +
> +       if (++hdcp_ctrl->auth_retries == AUTH_RETRIES_TIME) {
> +               mutex_lock(&hdcp_ctrl->state_mutex);
> +               hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
> +               mutex_unlock(&hdcp_ctrl->state_mutex);
> +
> +               hdcp_ctrl->auth_retries = 0;
> +               rc = -ERANGE;
> +       }
> +
> +       return rc;
> +}
> +
> +static void hdmi_hdcp_reauth_work(struct work_struct *work)
> +{
> +       struct delayed_work *dw = to_delayed_work(work);
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(dw,
> +               struct hdmi_hdcp_ctrl, hdcp_reauth_work);
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       unsigned long flags;
> +       int rc;
> +
> +       DBG("HDCP REAUTH WORK");
> +       mutex_lock(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAIL;
> +       mutex_unlock(&hdcp_ctrl->state_mutex);
> +
> +       /*
> +        * Disable HPD circuitry.
> +        * This is needed to reset the HDCP cipher engine so that when we
> +        * attempt a re-authentication, HW would clear the AN0_READY and
> +        * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
> +        */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
> +               hdmi_read(hdmi, REG_HDMI_HPD_CTRL) & ~BIT(28));

Split things like this into:

val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
val &= ~BIT(28);
hdmi_write(hdmi, REG_HDMI_HPD_CTRL,val);

For readability.

> +
> +       /* Disable HDCP interrupts */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       hdmi_write(hdmi, REG_HDMI_HDCP_RESET, BIT(0));
> +
> +       /* Wait to be clean on DDC HW engine */
> +       hdmi_hdcp_hw_ddc_clean(hdcp_ctrl);
> +
> +       /* Disable encryption and disable the HDCP block */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
> +
> +       /* Enable HPD circuitry */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
> +               hdmi_read(hdmi, REG_HDMI_HPD_CTRL) | BIT(28));
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       rc = hdmi_msm_if_abort_reauth(hdcp_ctrl);

Just inline the hdmi_msm_if_abort_reauth() here, if you extract the state
handling to 1 worker function (that sleeps inbetween the steps) you don't need
to lock and there's not much code left in the function.

> +       if (rc) {
> +               pr_err("%s: abort reauthentication!\n", __func__);
> +               return;
> +       }
> +
> +       mutex_lock(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
> +       mutex_unlock(&hdcp_ctrl->state_mutex);
> +       queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_prepare_work);
> +}
> +
> +static void hdmi_hdcp_auth_prepare_work(struct work_struct *work)
> +{
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
> +               struct hdmi_hdcp_ctrl, hdcp_auth_prepare_work);
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t qfprom_aksv_lsb, qfprom_aksv_msb;
> +       uint32_t link0_status;
> +       uint32_t regval;
> +       unsigned long flags;
> +       int ret;
> +
> +       if (!hdcp_ctrl->aksv_valid) {
> +               ret = hdmi_hdcp_read_validate_aksv(hdcp_ctrl);
> +               if (ret) {
> +                       pr_err("%s: ASKV validation failed\n", __func__);
> +                       mutex_lock(&hdcp_ctrl->state_mutex);
> +                       hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
> +                       mutex_unlock(&hdcp_ctrl->state_mutex);
> +                       return;
> +               }
> +               hdcp_ctrl->aksv_valid = true;
> +       }
> +
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       /* disable HDMI Encrypt */
> +       regval = hdmi_read(hdmi, REG_HDMI_CTRL);
> +       regval &= ~HDMI_CTRL_ENCRYPTED;
> +       hdmi_write(hdmi, REG_HDMI_CTRL, regval);
> +
> +       /* Enabling Software DDC */
> +       regval = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
> +       regval &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION;
> +       hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, regval);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       /*
> +        * Write AKSV read from QFPROM to the HDCP registers.
> +        * This step is needed for HDCP authentication and must be
> +        * written before enabling HDCP.
> +        */
> +       qfprom_aksv_lsb = hdcp_ctrl->aksv[3];
> +       qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[2];
> +       qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[1];
> +       qfprom_aksv_lsb = (qfprom_aksv_lsb << 8) | hdcp_ctrl->aksv[0];
> +       qfprom_aksv_msb = hdcp_ctrl->aksv[4];

As noted when you extracted these, better just keep them as lsb and msg in
hdcp_ctrl.

> +       hdmi_write(hdmi, REG_HDMI_HDCP_SW_LOWER_AKSV, qfprom_aksv_lsb);
> +       hdmi_write(hdmi, REG_HDMI_HDCP_SW_UPPER_AKSV, qfprom_aksv_msb);
> +
> +       /*
> +        * HDCP setup prior to enabling HDCP_CTRL.
> +        * Setup seed values for random number An.
> +        */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL0, 0xB1FFB0FF);
> +       hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE);
> +
> +       /* Disable the RngCipher state */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL,
> +               hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL) & ~(BIT(2)));
> +       DBG("HDCP_DEBUG_CTRL=0x%08x",
> +               hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL));
> +
> +       /*
> +        * Ensure that all register writes are completed before
> +        * enabling HDCP cipher
> +        */
> +       wmb();
> +
> +       /*
> +        * Enable HDCP
> +        * This needs to be done as early as possible in order for the
> +        * hardware to make An available to read
> +        */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, BIT(0));
> +
> +       /*
> +        * If we had stale values for the An ready bit, it should most
> +        * likely be cleared now after enabling HDCP cipher
> +        */
> +       link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
> +       DBG("After enabling HDCP Link0_Status=0x%08x", link0_status);
> +       if (!(link0_status & (BIT(8) | BIT(9))))
> +               DBG("An not ready after enabling HDCP");
> +
> +       /* Clear any DDC failures from previous tries before enable HDCP*/
> +       reset_hdcp_ddc_failures(hdcp_ctrl);
> +
> +       DBG("Queuing work to start HDCP authentication");
> +       hdcp_ctrl->work_retry_cnt = AUTH_WORK_RETRIES_TIME;
> +       queue_delayed_work(hdmi->workq,
> +               &hdcp_ctrl->hdcp_auth_part1_1_work, HZ/2);
> +}
> +
> +static void hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t regval;
> +       unsigned long flags;
> +
> +       DBG("hdcp auth failed, queue reauth work");
> +       /* clear HDMI Encrypt */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       regval = hdmi_read(hdmi, REG_HDMI_CTRL);
> +       regval &= ~HDMI_CTRL_ENCRYPTED;
> +       hdmi_write(hdmi, REG_HDMI_CTRL, regval);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +       queue_delayed_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work, HZ/2);
> +}
> +
> +static void hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t regval;
> +       unsigned long flags;
> +
> +       /*
> +        * Disable software DDC before going into part3 to make sure
> +        * there is no Arbitration between software and hardware for DDC
> +        */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       regval = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
> +       regval |= HDMI_DDC_ARBITRATION_HW_ARBITRATION;
> +       hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, regval);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       /*
> +        * Ensure that the state did not change during authentication.
> +        * If it did, it means that deauthenticate/reauthenticate was
> +        * called. In that case, this function doesn't need to enable encryption
> +        */
> +       mutex_lock(&hdcp_ctrl->state_mutex);
> +       if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state) {
> +               hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED;
> +               hdcp_ctrl->auth_retries = 0;
> +
> +               /* enable HDMI Encrypt */
> +               spin_lock_irqsave(&hdmi->reg_lock, flags);
> +               regval = hdmi_read(hdmi, REG_HDMI_CTRL);
> +               regval |= HDMI_CTRL_ENCRYPTED;
> +               hdmi_write(hdmi, REG_HDMI_CTRL, regval);
> +               spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +               mutex_unlock(&hdcp_ctrl->state_mutex);
> +       } else {
> +               mutex_unlock(&hdcp_ctrl->state_mutex);

Please move the mutex_unlock outside the if statement, and drop the else.

> +               DBG("HDCP state changed during authentication");
> +       }
> +}
> +
> +/*
> + * hdcp authenticating part 1: 1st
> + * Wait Key/An ready
> + * Read BCAPS from sink
> + * Write BCAPS and AKSV into HDCP engine
> + * Write An and AKSV to sink
> + * Read BKSV from sink and write into HDCP engine
> + */
> +static int hdmi_hdcp_check_key_an_ready(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t link0_status, an_ready, keys_state;
> +
> +       link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
> +       /* Wait for HDCP keys to be checked and validated */
> +       keys_state = (link0_status >> 28) & 0x7;
> +       if (keys_state != HDCP_KEYS_STATE_VALID) {
> +               DBG("Keys not ready(%d). s=%d, l0=%0x08x",
> +                       hdcp_ctrl->work_retry_cnt,
> +                       keys_state, link0_status);
> +
> +               return -EAGAIN;
> +       }
> +
> +       /* Wait for An0 and An1 bit to be ready */
> +       an_ready = (link0_status & BIT(8)) && (link0_status & BIT(9));
> +       if (!an_ready) {
> +               DBG("An not ready(%d). l0_status=0x%08x",
> +                       hdcp_ctrl->work_retry_cnt, link0_status);
> +
> +               return -EAGAIN;
> +       }
> +
> +       return 0;
> +}
> +
> +static int hdmi_hdcp_send_aksv_an(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t link0_aksv_0, link0_aksv_1;
> +       uint32_t link0_an_0, link0_an_1;
> +       uint8_t aksv[5];
> +       uint8_t an[8];
> +
> +       /* Read An0 and An1 */
> +       link0_an_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA5);
> +       link0_an_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA6);
> +
> +       /* Read AKSV */
> +       link0_aksv_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA3);
> +       link0_aksv_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4);
> +
> +       DBG("Link ASKV=%08x%08x", link0_aksv_0, link0_aksv_1);
> +       /* Copy An and AKSV to byte arrays for transmission */
> +       aksv[0] =  link0_aksv_0        & 0xFF;
> +       aksv[1] = (link0_aksv_0 >> 8)  & 0xFF;
> +       aksv[2] = (link0_aksv_0 >> 16) & 0xFF;
> +       aksv[3] = (link0_aksv_0 >> 24) & 0xFF;
> +       aksv[4] =  link0_aksv_1        & 0xFF;
> +
> +       an[0] =  link0_an_0        & 0xFF;
> +       an[1] = (link0_an_0 >> 8)  & 0xFF;
> +       an[2] = (link0_an_0 >> 16) & 0xFF;
> +       an[3] = (link0_an_0 >> 24) & 0xFF;
> +       an[4] =  link0_an_1        & 0xFF;
> +       an[5] = (link0_an_1 >> 8)  & 0xFF;
> +       an[6] = (link0_an_1 >> 16) & 0xFF;
> +       an[7] = (link0_an_1 >> 24) & 0xFF;
> +

Turn link0_an_{0,1} in an array of 2 u32 elements and possibly make sure that
they are little endian and then just call hdmi_ddc_write(..., &link0_an,
sizeof(link0_an));

> +       /* Write An to offset 0x18 */
> +       rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x18, an, 8);
> +       if (rc) {
> +               pr_err("%s:An write failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("Link0-An=%08x%08x", link0_an_1, link0_an_0);
> +
> +       /* Write AKSV to offset 0x10 */
> +       rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x10, aksv, 5);
> +       if (rc) {
> +               pr_err("%s:AKSV write failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("Link0-AKSV=%02x%08x", link0_aksv_1 & 0xFF, link0_aksv_0);
> +
> +       return 0;
> +}
> +
> +static int hdmi_hdcp_recv_bksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t link0_bksv_0, link0_bksv_1;
> +       uint8_t *bksv = NULL;
> +       uint32_t reg[2], data[2];
> +
> +       bksv = hdcp_ctrl->bksv;
> +
> +       /* Read BKSV at offset 0x00 */
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x00, bksv, 5, true);
> +       if (rc) {
> +               pr_err("%s:BKSV read failed\n", __func__);
> +               return rc;
> +       }
> +
> +       /* check there are 20 ones in BKSV */
> +       if (hdmi_hdcp_count_one(bksv, 5) != 20) {
> +               pr_err(": BKSV doesn't have 20 1's and 20 0's\n");
> +               pr_err(": BKSV chk fail. BKSV=%02x%02x%02x%02x%02x\n",
> +                       bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]);
> +               return -EINVAL;
> +       }
> +
> +       link0_bksv_0 = bksv[3];
> +       link0_bksv_0 = (link0_bksv_0 << 8) | bksv[2];
> +       link0_bksv_0 = (link0_bksv_0 << 8) | bksv[1];
> +       link0_bksv_0 = (link0_bksv_0 << 8) | bksv[0];
> +       link0_bksv_1 = bksv[4];

Either read these straight into two u32, or at least do:

a  = bksv[4];
b  = bksv[3] << 24;
b |= bksv[2] << 16;
b |= bksv[1] << 8;
b |= bksv[0];

> +       DBG(":BKSV=%02x%08x", link0_bksv_1, link0_bksv_0);
> +
> +       /* Write BKSV read from sink to HDCP registers */
> +       reg[0] = REG_HDMI_HDCP_RCVPORT_DATA0;
> +       data[0] = link0_bksv_0;
> +       reg[1] = REG_HDMI_HDCP_RCVPORT_DATA1;
> +       data[1] = link0_bksv_1;
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
> +
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_recv_bcaps(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint32_t reg, data;
> +       uint8_t bcaps;
> +
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1, true);
> +       if (rc) {
> +               pr_err("%s:BCAPS read failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("BCAPS=%02x", bcaps);
> +
> +       /* receiver (0), repeater (1) */
> +       hdcp_ctrl->ds_type = (bcaps & BIT(6)) ? DS_REPEATER : DS_RECEIVER;
> +
> +       /* Write BCAPS to the hardware */
> +       reg = REG_HDMI_HDCP_RCVPORT_DATA12;
> +       data = (uint32_t)bcaps;

Just move these into the function call...

> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
> +
> +       return rc;
> +}
> +

[..]

> +
> +/*
> + * hdcp authenticating part 2: 1st
> + * wait until sink (repeater)'s ksv fifo ready
> + * read bstatus from sink and write to HDCP engine
> + */
> +static int hdmi_hdcp_recv_bstatus(struct hdmi_hdcp_ctrl *hdcp_ctrl,
> +       uint8_t bcaps)
> +{
> +       int rc;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint16_t bstatus;
> +       bool max_devs_exceeded = false, max_cascade_exceeded = false;
> +       uint32_t repeater_cascade_depth = 0, down_stream_devices = 0;
> +       uint32_t reg, data;
> +       uint8_t buf[2];
> +
> +       memset(buf, 0, sizeof(buf));

If read returns okay buf should have been written to, so this should not be
needed.

> +
> +       /* Read BSTATUS at offset 0x41 */
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x41, buf, 2, false);
> +       if (rc) {
> +               pr_err("%s: BSTATUS read failed\n", __func__);
> +               goto error;
> +       }
> +       bstatus = buf[1];
> +       bstatus = (bstatus << 8) | buf[0];

bstatus = buf[1] << 8 | buf[0];

> +

[..]

> +}
> +

[..]

> +
> +/*
> + * hdcp authenticating part 2: 2nd
> + * read ksv fifo from sink
> + * transfer V' from sink to HDCP engine
> + * reset SHA engine
> + */
> +int hdmi_hdcp_read_v(struct hdmi *hdmi, char *name,
> +       uint32_t off, uint32_t *val)
> +{

You can replace this entire function with:

hdmi_dcc_read(hdmi, HDCP_PORT_ADDR, off, val, sizeof(*val), true);

> +       int rc = 0;
> +       uint8_t buf[4];
> +
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, off, buf, 4, true);
> +       if (rc) {
> +               pr_err("%s: Read %s failed\n", __func__,
> +                       name);
> +               return rc;
> +       }
> +
> +       if (val)
> +               *val = (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]);
> +
> +       DBG("%s: buf[0]=%x, buf[1]=%x, buf[2]=%x, buf[3]=%x", name,
> +               buf[0], buf[1], buf[2], buf[3]);
> +
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_transfer_v_h(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       int rc = 0;
> +       struct hdmi_hdcp_reg_data reg_data[]  = {
> +               {REG_HDMI_HDCP_RCVPORT_DATA7,  0x20, "V' H0"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA8,  0x24, "V' H1"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA9,  0x28, "V' H2"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA10, 0x2C, "V' H3"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA11, 0x30, "V' H4"},
> +       };
> +       struct hdmi_hdcp_reg_data *rd;
> +       uint32_t size = ARRAY_SIZE(reg_data);
> +       uint32_t reg[ARRAY_SIZE(reg_data)], data[ARRAY_SIZE(reg_data)];

Move the data variable to it's own line to make things easier to read.

> +       int i;
> +
> +       for (i = 0; i < size; i++) {
> +               rd = &reg_data[i];
> +               rc = hdmi_hdcp_read_v(hdmi, rd->name,
> +                       rd->off, &data[i]);
> +               if (rc)
> +                       goto error;
> +
> +               reg[i] = reg_data[i].reg_id;
> +       }
> +
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, size);
> +
> +error:
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_recv_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       uint8_t *ksv_fifo = NULL;
> +       uint32_t ksv_bytes;
> +
> +       ksv_fifo = hdcp_ctrl->ksv_list;
> +       ksv_bytes = 5 * hdcp_ctrl->dev_count;
> +       memset(ksv_fifo, 0,
> +               sizeof(hdcp_ctrl->ksv_list));

Drop the local ksv_fifo and

memset(hdcp_ctrl->ksv_list, 0, sizeof(hdcp_ctrl->ksv_list));

as well as passing hdcp_ctrl->ksv_list to hdmi_ddc_read directly.

> +
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x43,
> +               ksv_fifo, ksv_bytes, true);
> +       if (rc)
> +               pr_err("%s: KSV FIFO read failed\n", __func__);
> +
> +       return rc;
> +}
> +

[..]

> +int hdmi_hdcp_on(struct hdmi *hdmi)
> +{
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl = hdmi->hdcp_ctrl;
> +       uint32_t regval;
> +       unsigned long flags;
> +
> +       if (!hdcp_ctrl) {
> +               pr_warn("%s:hdcp_ctrl is NULL\n", __func__);
> +               return 0;

There's little point in having a return value if you return success no matter
what.

> +       }
> +
> +       if (HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) {
> +               DBG("still active or activating. returning");
> +               return 0;
> +       }
> +
> +       /* clear HDMI Encrypt */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       regval = hdmi_read(hdmi, REG_HDMI_CTRL);
> +       regval &= ~HDMI_CTRL_ENCRYPTED;
> +       hdmi_write(hdmi, REG_HDMI_CTRL, regval);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       mutex_lock(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
> +       mutex_unlock(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->auth_retries = 0;
> +       queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_prepare_work);
> +
> +       return 0;
> +}
> +
> +void hdmi_hdcp_off(struct hdmi *hdmi)
> +{
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl = hdmi->hdcp_ctrl;
> +       unsigned long flags;
> +       uint32_t regval;
> +       int rc = 0;
> +
> +       if (!hdcp_ctrl) {
> +               pr_err("%s:hdcp_ctrl is NULL\n", __func__);
> +               return;
> +       }
> +
> +       if (HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) {
> +               DBG("hdcp inactive. returning");
> +               return;
> +       }
> +
> +       /*
> +        * Disable HPD circuitry.
> +        * This is needed to reset the HDCP cipher engine so that when we
> +        * attempt a re-authentication, HW would clear the AN0_READY and
> +        * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
> +        */
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
> +               hdmi_read(hdmi, REG_HDMI_HPD_CTRL) & ~BIT(28));

Split into:
val = read();
val &= ~BIT(28);
write(val);

Please put 28 in a define with a sane name related to HDP circuitry...

> +
> +       /*
> +        * Disable HDCP interrupts.
> +        * Also, need to set the state to inactive here so that any ongoing
> +        * reauth works will know that the HDCP session has been turned off.
> +        */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       /*
> +        * Cancel any pending auth/reauth attempts.
> +        * If one is ongoing, this will wait for it to finish.
> +        * No more reauthentication attempts will be scheduled since we
> +        * set the current state to inactive.
> +        */
> +       rc = cancel_work_sync(&hdcp_ctrl->hdcp_auth_prepare_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth prepare work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_reauth_work);
> +       if (rc)
> +               DBG("Deleted hdcp reauth work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part1_1_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part1_1 work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part1_2_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part1_2 work");
> +       rc = cancel_work_sync(&hdcp_ctrl->hdcp_auth_part1_3_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part1_3 work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_1_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part2_1 work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_2_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part2_2 work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_3_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part2_3 work");
> +       rc = cancel_delayed_work_sync(&hdcp_ctrl->hdcp_auth_part2_4_work);
> +       if (rc)
> +               DBG("Deleted hdcp auth part2_4 work");

Just drop all these debug printouts, they don't add anything but clutter.

> +
> +       /* set state to inactive after all work cancelled */
> +       mutex_lock(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
> +       mutex_unlock(&hdcp_ctrl->state_mutex);

You shouldn't have to lock here, your workers are dead.

> +
> +       hdmi_write(hdmi, REG_HDMI_HDCP_RESET, BIT(0));
> +
> +       /* Disable encryption and disable the HDCP block */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
> +
> +       spin_lock_irqsave(&hdmi->reg_lock, flags);
> +       regval = hdmi_read(hdmi, REG_HDMI_CTRL);
> +       regval &= ~HDMI_CTRL_ENCRYPTED;
> +       hdmi_write(hdmi, REG_HDMI_CTRL, regval);
> +
> +       /* Enable HPD circuitry */
> +       hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
> +               hdmi_read(hdmi, REG_HDMI_HPD_CTRL) | BIT(28));

Split it into:
val = read()
val |= BIT(28);
write(val);

> +       spin_unlock_irqrestore(&hdmi->reg_lock, flags);
> +
> +       DBG("HDCP: Off");
> +} /* hdmi_hdcp_off */

Remove the comment.

> +
> +struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi)
> +{
> +       uint32_t scm_buf = TZ_HDCP_CMD_ID;
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl;
> +       uint32_t ret  = 0;
> +       uint32_t resp = 0;
> +
> +       if (!hdmi->qfprom_mmio) {
> +               pr_err("%s: HDCP is not supported without qfprom\n",
> +                       __func__);
> +               ret = -EINVAL;
> +               goto fail;

Just return here directly.

> +       }
> +
> +       hdcp_ctrl = kzalloc(sizeof(*hdcp_ctrl), GFP_KERNEL);
> +       if (!hdcp_ctrl) {
> +               ret = -ENOMEM;
> +               goto fail;
> +       }
> +
> +       INIT_WORK(&hdcp_ctrl->hdcp_auth_prepare_work,
> +               hdmi_hdcp_auth_prepare_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_reauth_work, hdmi_hdcp_reauth_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part1_1_work,
> +               hdmi_hdcp_auth_part1_1_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part1_2_work,
> +               hdmi_hdcp_auth_part1_2_work);
> +       INIT_WORK(&hdcp_ctrl->hdcp_auth_part1_3_work,
> +               hdmi_hdcp_auth_part1_3_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_1_work,
> +               hdmi_hdcp_auth_part2_1_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_2_work,
> +               hdmi_hdcp_auth_part2_2_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_3_work,
> +               hdmi_hdcp_auth_part2_3_work);
> +       INIT_DELAYED_WORK(&hdcp_ctrl->hdcp_auth_part2_4_work,
> +               hdmi_hdcp_auth_part2_4_work);
> +

As I stated before, replace all these steps with one worker function
that does something like:

{
if (state == AUTHENTICATED)
re_auth();

auth_prepare();
msleep(500);

while (auth_part_1_1() == -EAGAIN)
msleep(20);

msleep(160);

auth_part_1_2();
...
}

> +       hdcp_ctrl->hdmi = hdmi;
> +       hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
> +       mutex_init(&hdcp_ctrl->state_mutex);
> +       hdcp_ctrl->aksv_valid = false;
> +
> +       ret = scm_call(SCM_SVC_INFO, SCM_CMD_HDCP, &scm_buf,
> +               sizeof(scm_buf), &resp, sizeof(resp));

You're calling command 1 on SCM_SVC_INFO, this is not SCM_CMD_HDCP but rather
IS_CALL_AVAIL_CMD. The parameter, 0x00004401, is 17 << 10 | 1; meaning service
17 command 1.

You should use [1] and replace this with:

ret = scm_is_call_available(SCM_SVC_HDCP, SCM_CMD_HDCP);

> +       if (ret) {
> +               pr_err("%s: error: scm_call ret = %d, resp = %d\n",
> +                       __func__, ret, resp);
> +               goto fail;
> +       } else {

There's no need for the else here, as you just goto'ed.

> +               DBG("tz_hdcp = %d", resp);
> +               hdcp_ctrl->tz_hdcp = resp;
> +       }
> +
> +       return hdcp_ctrl;
> +fail:
> +       kfree(hdcp_ctrl);
> +       return ERR_PTR(ret);
> +} /* hdmi_hdcp_init */

Drop the comment.

> +
> +void hdmi_hdcp_destroy(struct hdmi *hdmi)
> +{
> +       if (hdmi && hdmi->hdcp_ctrl) {
> +               kfree(hdmi->hdcp_ctrl);
> +               hdmi->hdcp_ctrl = NULL;
> +       }
> +}

[1] https://lkml.org/lkml/2014/8/4/768

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-03  1:46 ` Bjorn Andersson
@ 2014-12-03 16:42   ` Rob Clark
  2014-12-03 17:16     ` jilaiw
  0 siblings, 1 reply; 15+ messages in thread
From: Rob Clark @ 2014-12-03 16:42 UTC (permalink / raw)
  To: Bjorn Andersson; +Cc: Jilai Wang, dri-devel, linux-arm-msm, linux-kernel

On Tue, Dec 2, 2014 at 8:46 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
> On Mon, Dec 1, 2014 at 1:56 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
>> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>>
>> Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
>> ---
>
> Hi Jilai,
>
> [..]
>
>> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c
>
> [..]
>
>>
>> @@ -119,6 +137,22 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
>>                 goto fail;
>>         }
>>
>> +       /* HDCP needs physical address of hdmi register */
>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>> +               config->mmio_name);
>
> Is this guaranteed to be available at all times? You should probably
> do some error handling here.

Hi Bjorn,

(as mentioned on irc, but just repeating here for posterity)

this is actually ok in this case, since the previous ioremap would have failed.

>> +       hdmi->mmio_phy_addr = res->start;
>> +
>> +       if (config->qfprom_mmio_name) {
>
> Should this check really be here? This will always be set if CONFIG_OF is set
> and never otherwise and that seems strange to me.
>
> Perhaps you should add the string to the !CONFIG_OF platforms as well or simply
> add #ifdef CONFIG_OF around this section if that's what you really want. (but
> seems more like you forgot the non-of case).

or just not cared enough to make HDCP (since it is optional) work on
old pre-DT vendor kernel ;-)

fwiw, eventually the !CONFIG_OF stuff will go away.. it just exists
because some devices and some use-cases still need old downstream
kernels :-(

>> +               hdmi->qfprom_mmio = msm_ioremap(pdev,
>> +                       config->qfprom_mmio_name, "HDMI_QFPROM");
>
>
> Is this a special hdmi qfprom or are you ioremapping _the_ qfprom here?
>
> If so I did suggest that we expose it as a syscon but I think Stephen Boyd had
> some other ideas.

afaict (but Jilai can correct me), it is *the* qfprom.. that seems to
be how things worked in android kernels.  Some better mechanism would
be really nice.

>> +               if (IS_ERR(hdmi->qfprom_mmio)) {
>> +                       dev_info(&pdev->dev, "can't find qfprom resource\n");
>> +                       hdmi->qfprom_mmio = NULL;
>> +               }
>> +       } else {
>> +               hdmi->qfprom_mmio = NULL;
>
> hdmi_qfprom_read() seems to be called and read from qfprom_mmio no matter how
> this ended. Are you sure this (both error paths) shouldn't be handled as a
> fatal error?

hdmi_hdcp_init() fails (and then we continue without HDCP) if
qfprom_mmio is NULL..

> 'hdmi' is kzalloc and hence already NULL.
>
> [..]
>
>> @@ -205,6 +241,13 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder)
>>                 goto fail;
>>         }
>>
>> +       hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
>> +       if (IS_ERR(hdmi->hdcp_ctrl)) {
>> +               ret = PTR_ERR(hdmi->hdcp_ctrl);
>> +               dev_warn(dev->dev, "failed to init hdcp: %d(disabled)\n", ret);
>> +               hdmi->hdcp_ctrl = NULL;
>
> So either you treat this as an error or you don't.
>
> If you're fine continuing execution without hdcp_ctrl then you shouldn't set
> ret. But in that case it you should probably not print a warning every time you
> enter hdmi_hdcp_on() and an error on hdmi_hdcp_off().

agreed, I think it would be better for hdcp_on/off() to take struct
hdmi_hdcp_ctrl as param (and just not be called if hdmi->hdcp_ctrl is
null)

[snip]

> [..]
>
>> +
>> +struct hdmi_hdcp_reg_data {
>> +       uint32_t reg_id;
>
> You should use u32 instead of uint32_t in the kernel.

tbh, I'd prefer sticking to stdint types..  before stdint was a thing,
u32 made sense

>> +       uint32_t off;
>> +       char *name;
>> +       uint32_t reg_val;
>> +};
>> +
>> +struct hdmi_hdcp_ctrl {
>> +       struct hdmi *hdmi;
>> +       uint32_t auth_retries;
>> +       uint32_t tz_hdcp;
>
> Turn this into a bool named something like has_tz_hdcp instead, as that's what
> it really means.
>
>> +       enum hdmi_hdcp_state hdcp_state;
>> +       struct mutex state_mutex;
>> +       struct delayed_work hdcp_reauth_work;
>> +       struct delayed_work hdcp_auth_part1_1_work;
>> +       struct delayed_work hdcp_auth_part1_2_work;
>> +       struct work_struct hdcp_auth_part1_3_work;
>> +       struct delayed_work hdcp_auth_part2_1_work;
>> +       struct delayed_work hdcp_auth_part2_2_work;
>> +       struct delayed_work hdcp_auth_part2_3_work;
>> +       struct delayed_work hdcp_auth_part2_4_work;
>> +       struct work_struct hdcp_auth_prepare_work;
>
> You shouldn't use "work" as a way to express states in your state machine.
> Better have 1 auth work function that does all these steps, probably having
> them split in functions just like you do now.
>
> That way you can have 1 function running the pass of authentication, starting
> by checking if you're reauthing or not then processing each step one by one,
> sleeping inbetween them. You can have the functions return -EAGAIN to indicate
> that you need to retry the current operation and so on.
>
> This would split the state machine from the state executioners and simplify
> your code.

As a side note (disclaimer, I'm not an hdcp expert), but I wonder if
eventually some of that should be extracted out into some helpers in
drm core.  I guess that is something we'll figure out when a 2nd
driver gains upstream HDCP support.  One big work w/ msleep()'s does
sound like it would be easier to understand (and therefore easier to
refactor out into helpers).

[snip]

>> +
>> +static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, uint32_t *preg,
>> +       uint32_t *pdata, uint32_t count)
>> +{
>> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
>> +       struct scm_hdcp_req scm_buf[SCM_HDCP_MAX_REG];
>> +       uint32_t resp, phy_addr, idx = 0;
>> +       int i, ret = 0;
>> +
>> +       if (count == 0)
>> +               return 0;
>
> There are no calls to this function where count can be 0, so you can drop this
> check.
>
>> +
>> +       if (!preg || !pdata) {
>> +               pr_err("%s: Invalid pointer\n", __func__);
>> +               return -EINVAL;
>> +       }
>
> There are no calls to this function where either of these are NULL, so you can
> drop the entire block.

or just WARN_ON() (for both this and the count).. I find that makes
the constraints more clear for someone coming along later and mucking
with that code

>
>> +
>> +       if (hdcp_ctrl->tz_hdcp) {
>> +               phy_addr = (uint32_t)hdmi->mmio_phy_addr;
>> +
>> +               while (count) {
>> +                       memset(scm_buf, 0, sizeof(scm_buf));
>> +                       for (i = 0; i < count && i < SCM_HDCP_MAX_REG; i++) {
>> +                               scm_buf[i].addr = phy_addr + preg[idx];
>> +                               scm_buf[i].val  = pdata[idx];
>> +                               idx++;
>> +                       }
>> +                       ret = scm_call(SCM_SVC_HDCP, SCM_CMD_HDCP,
>> +                               scm_buf, sizeof(scm_buf), &resp, sizeof(resp));
>
> SCM_SVC_HDCP nor SCM_CMD_HDCP are defined, here. See the comment above related
> to TZ_HDCP_CMD_ID.
>
>> +
>> +                       if (ret || resp) {
>> +                               pr_err("%s: error: scm_call ret = %d, resp = %d\n",
>> +                                       __func__, ret, resp);
>> +                               ret = -EINVAL;
>> +                               break;
>> +                       }
>> +
>> +                       count -= i;
>> +               }
>> +       } else {
>> +               for (i = 0; i < count; i++)
>> +                       hdmi_write(hdmi, preg[i], pdata[i]);
>> +       }
>> +
>> +       return ret;
>> +}
>> +
>> +void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
>> +{
>> +       struct hdmi *hdmi;
>> +       uint32_t regval, hdcp_int_status;
>> +       unsigned long flags;
>> +
>> +       if (!hdcp_ctrl) {
>> +               DBG("HDCP is disabled");
>> +               return;
>> +       }
>
> No need to print a debug line here every time.
>
> I would have preferred if you made the call from hdmi_irq() conditional
> instead, then you would need to check here...

me too

[snip]

>> +static void reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
>> +{
>> +       int hdcp_ddc_ctrl1_reg;
>> +       int hdcp_ddc_status;
>> +       int failure;
>> +       int nack0;
>> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
>> +
>> +       /* Check for any DDC transfer failures */
>> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
>> +       failure = (hdcp_ddc_status >> 16) & 0x1;
>
> failure = hdcp_ddc_status & BIT(16);
>
>> +       nack0 = (hdcp_ddc_status >> 14) & 0x1;
>
> nack0 = hdcp_ddc_status & BIT(14);
>
>> +       DBG("On Entry: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
>> +               hdcp_ddc_status, failure, nack0);
>> +
>> +       if (failure == 0x1) {
>> +               /*
>> +                * Indicates that the last HDCP HW DDC transfer failed.
>> +                * This occurs when a transfer is attempted with HDCP DDC
>> +                * disabled (HDCP_DDC_DISABLE=1) or the number of retries
>> +                * matches HDCP_DDC_RETRY_CNT.
>> +                * Failure occurred,  let's clear it.
>> +                */
>> +               DBG("DDC failure detected.HDCP_DDC_STATUS=0x%08x",
>> +                       hdcp_ddc_status);
>> +
>> +               /* First, Disable DDC */
>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, BIT(0));
>> +
>> +               /* ACK the Failure to Clear it */
>> +               hdcp_ddc_ctrl1_reg = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1);
>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1,
>> +                       hdcp_ddc_ctrl1_reg | BIT(0));
>> +
>> +               /* Check if the FAILURE got Cleared */
>> +               hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
>
> Replace the following lines with:
>
> if (hdcp_ddc_status & BIT(16))
> pr_info("%s: Unable to clear HDCP DDC Failure\n", __func__);
>
> No need to print the debug statement either...
>
>> +               hdcp_ddc_status = (hdcp_ddc_status >> 16) & BIT(0);
>> +               if (hdcp_ddc_status == 0x0)
>> +                       DBG("HDCP DDC Failure cleared");
>> +               else
>> +                       pr_info("%s: Unable to clear HDCP DDC Failure\n",
>> +                               __func__);
>> +
>> +               /* Re-Enable HDCP DDC */
>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
>> +       }
>> +
>> +       if (nack0 == 0x1) {
>> +               DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
>> +               /* Reset HDMI DDC software status */
>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(3));
>
> Split all these in:
> val = hdmi_read()
> val |= foo
> hdmi_write(val);
>
> To make this readable.
>
>> +               msleep(20);
>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~(BIT(3)));
>> +
>> +               /* Reset HDMI DDC Controller */
>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(1));
>> +               msleep(20);
>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~BIT(1));
>> +               DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
>> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
>> +       }
>> +
>
> Just end the function here, no need for the extra debug printouts...
>
>> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
>> +
>> +       failure = (hdcp_ddc_status >> 16) & BIT(0);
>> +       nack0 = (hdcp_ddc_status >> 14) & BIT(0);
>> +       DBG("On Exit: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
>> +               hdcp_ddc_status, failure, nack0);
>> +}

DBG() stuff can always be enabled at runtime.. and when it comes to
failures seen with certain monitors, it is usually the monitor the
user has and not the ones the developer has, which have fun bugs ;-)

So in general if it is something useful for debugging a failure, when
you can just ask the user to 'echo 15 >
/sys/modules/drm/parameters/debug' then plug in monitor and send
dmesg, I don't mind keeping it.

BR,
-R

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-01 21:56 [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Jilai Wang
  2014-12-03  1:46 ` Bjorn Andersson
@ 2014-12-03 16:51 ` Rob Clark
  1 sibling, 0 replies; 15+ messages in thread
From: Rob Clark @ 2014-12-03 16:51 UTC (permalink / raw)
  To: Jilai Wang; +Cc: dri-devel, linux-arm-msm, Linux Kernel Mailing List

On Mon, Dec 1, 2014 at 4:56 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> +               /* Enable HDCP Encryption */
> +               hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, BIT(0) | BIT(8));

btw, as Bjron mentioned, there are a lot of these open coded BIT(n)..
but from a quick check, the first two or three I looked at, we already
have the appropriate defines in the rnndb generated headers, to
instead do:

        /* Enable HDCP Encryption */
        hdmi_write(hdmi, REG_HDMI_HDCP_CTRL,
                HDMI_HDCP_CTRL_ENABLE |
                HDMI_HDCP_CTRL_ENCRYPTION_ENABLE);

that seems somewhat more readable to me ;-)

BR,
-R

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-03 16:42   ` Rob Clark
@ 2014-12-03 17:16     ` jilaiw
  2014-12-03 17:20       ` Rob Clark
  2014-12-03 17:32       ` Bjorn Andersson
  0 siblings, 2 replies; 15+ messages in thread
From: jilaiw @ 2014-12-03 17:16 UTC (permalink / raw)
  To: Rob Clark
  Cc: Bjorn Andersson, Jilai Wang, dri-devel, linux-arm-msm, linux-kernel

Hi Bjorn,

> On Tue, Dec 2, 2014 at 8:46 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
>> On Mon, Dec 1, 2014 at 1:56 PM, Jilai Wang <jilaiw@codeaurora.org>
>> wrote:
>>> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>>>
>>> Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
>>> ---
>>
>> Hi Jilai,
>>
>> [..]
>>
>>> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c
>>> b/drivers/gpu/drm/msm/hdmi/hdmi.c
>>
>> [..]
>>
>>>
>>> @@ -119,6 +137,22 @@ struct hdmi *hdmi_init(struct drm_device *dev,
>>> struct drm_encoder *encoder)
>>>                 goto fail;
>>>         }
>>>
>>> +       /* HDCP needs physical address of hdmi register */
>>> +       res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
>>> +               config->mmio_name);
>>
>> Is this guaranteed to be available at all times? You should probably
>> do some error handling here.
>
> Hi Bjorn,
>
> (as mentioned on irc, but just repeating here for posterity)
>
> this is actually ok in this case, since the previous ioremap would have
> failed.
>
>>> +       hdmi->mmio_phy_addr = res->start;
>>> +
>>> +       if (config->qfprom_mmio_name) {
>>
>> Should this check really be here? This will always be set if CONFIG_OF
>> is set
>> and never otherwise and that seems strange to me.
>>
>> Perhaps you should add the string to the !CONFIG_OF platforms as well or
>> simply
>> add #ifdef CONFIG_OF around this section if that's what you really want.
>> (but
>> seems more like you forgot the non-of case).
>
> or just not cared enough to make HDCP (since it is optional) work on
> old pre-DT vendor kernel ;-)
>
> fwiw, eventually the !CONFIG_OF stuff will go away.. it just exists
> because some devices and some use-cases still need old downstream
> kernels :-(
>
>>> +               hdmi->qfprom_mmio = msm_ioremap(pdev,
>>> +                       config->qfprom_mmio_name, "HDMI_QFPROM");
>>
>>
>> Is this a special hdmi qfprom or are you ioremapping _the_ qfprom here?
>>
>> If so I did suggest that we expose it as a syscon but I think Stephen
>> Boyd had
>> some other ideas.
>
> afaict (but Jilai can correct me), it is *the* qfprom.. that seems to
> be how things worked in android kernels.  Some better mechanism would
> be really nice.
>
>>> +               if (IS_ERR(hdmi->qfprom_mmio)) {
>>> +                       dev_info(&pdev->dev, "can't find qfprom
>>> resource\n");
>>> +                       hdmi->qfprom_mmio = NULL;
>>> +               }
>>> +       } else {
>>> +               hdmi->qfprom_mmio = NULL;
>>
>> hdmi_qfprom_read() seems to be called and read from qfprom_mmio no
>> matter how
>> this ended. Are you sure this (both error paths) shouldn't be handled as
>> a
>> fatal error?
>
> hdmi_hdcp_init() fails (and then we continue without HDCP) if
> qfprom_mmio is NULL..
>
>> 'hdmi' is kzalloc and hence already NULL.
>>
>> [..]
>>
>>> @@ -205,6 +241,13 @@ struct hdmi *hdmi_init(struct drm_device *dev,
>>> struct drm_encoder *encoder)
>>>                 goto fail;
>>>         }
>>>
>>> +       hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
>>> +       if (IS_ERR(hdmi->hdcp_ctrl)) {
>>> +               ret = PTR_ERR(hdmi->hdcp_ctrl);
>>> +               dev_warn(dev->dev, "failed to init hdcp:
>>> %d(disabled)\n", ret);
>>> +               hdmi->hdcp_ctrl = NULL;
>>
>> So either you treat this as an error or you don't.
>>
>> If you're fine continuing execution without hdcp_ctrl then you shouldn't
>> set
>> ret. But in that case it you should probably not print a warning every
>> time you
>> enter hdmi_hdcp_on() and an error on hdmi_hdcp_off().
>
> agreed, I think it would be better for hdcp_on/off() to take struct
> hdmi_hdcp_ctrl as param (and just not be called if hdmi->hdcp_ctrl is
> null)
>
> [snip]
>
>> [..]
>>
>>> +
>>> +struct hdmi_hdcp_reg_data {
>>> +       uint32_t reg_id;
>>
>> You should use u32 instead of uint32_t in the kernel.
>
> tbh, I'd prefer sticking to stdint types..  before stdint was a thing,
> u32 made sense
>
>>> +       uint32_t off;
>>> +       char *name;
>>> +       uint32_t reg_val;
>>> +};
>>> +
>>> +struct hdmi_hdcp_ctrl {
>>> +       struct hdmi *hdmi;
>>> +       uint32_t auth_retries;
>>> +       uint32_t tz_hdcp;
>>
>> Turn this into a bool named something like has_tz_hdcp instead, as
>> that's what
>> it really means.
>>
>>> +       enum hdmi_hdcp_state hdcp_state;
>>> +       struct mutex state_mutex;
>>> +       struct delayed_work hdcp_reauth_work;
>>> +       struct delayed_work hdcp_auth_part1_1_work;
>>> +       struct delayed_work hdcp_auth_part1_2_work;
>>> +       struct work_struct hdcp_auth_part1_3_work;
>>> +       struct delayed_work hdcp_auth_part2_1_work;
>>> +       struct delayed_work hdcp_auth_part2_2_work;
>>> +       struct delayed_work hdcp_auth_part2_3_work;
>>> +       struct delayed_work hdcp_auth_part2_4_work;
>>> +       struct work_struct hdcp_auth_prepare_work;
>>
>> You shouldn't use "work" as a way to express states in your state
>> machine.
>> Better have 1 auth work function that does all these steps, probably
>> having
>> them split in functions just like you do now.
>>
>> That way you can have 1 function running the pass of authentication,
>> starting
>> by checking if you're reauthing or not then processing each step one by
>> one,
>> sleeping inbetween them. You can have the functions return -EAGAIN to
>> indicate
>> that you need to retry the current operation and so on.
>>
>> This would split the state machine from the state executioners and
>> simplify
>> your code.
>
> As a side note (disclaimer, I'm not an hdcp expert), but I wonder if
> eventually some of that should be extracted out into some helpers in
> drm core.  I guess that is something we'll figure out when a 2nd
> driver gains upstream HDCP support.  One big work w/ msleep()'s does
> sound like it would be easier to understand (and therefore easier to
> refactor out into helpers).
>
> [snip]
>

The reason that I break the partI/PartII work into these small works
because I want to avoid to use msleep in work.
Otherwise cancel a HDCP work may cause long delay up to several seconds.

>>> +
>>> +static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, uint32_t
>>> *preg,
>>> +       uint32_t *pdata, uint32_t count)
>>> +{
>>> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
>>> +       struct scm_hdcp_req scm_buf[SCM_HDCP_MAX_REG];
>>> +       uint32_t resp, phy_addr, idx = 0;
>>> +       int i, ret = 0;
>>> +
>>> +       if (count == 0)
>>> +               return 0;
>>
>> There are no calls to this function where count can be 0, so you can
>> drop this
>> check.
>>
>>> +
>>> +       if (!preg || !pdata) {
>>> +               pr_err("%s: Invalid pointer\n", __func__);
>>> +               return -EINVAL;
>>> +       }
>>
>> There are no calls to this function where either of these are NULL, so
>> you can
>> drop the entire block.
>
> or just WARN_ON() (for both this and the count).. I find that makes
> the constraints more clear for someone coming along later and mucking
> with that code
>
>>
>>> +
>>> +       if (hdcp_ctrl->tz_hdcp) {
>>> +               phy_addr = (uint32_t)hdmi->mmio_phy_addr;
>>> +
>>> +               while (count) {
>>> +                       memset(scm_buf, 0, sizeof(scm_buf));
>>> +                       for (i = 0; i < count && i < SCM_HDCP_MAX_REG;
>>> i++) {
>>> +                               scm_buf[i].addr = phy_addr + preg[idx];
>>> +                               scm_buf[i].val  = pdata[idx];
>>> +                               idx++;
>>> +                       }
>>> +                       ret = scm_call(SCM_SVC_HDCP, SCM_CMD_HDCP,
>>> +                               scm_buf, sizeof(scm_buf), &resp,
>>> sizeof(resp));
>>
>> SCM_SVC_HDCP nor SCM_CMD_HDCP are defined, here. See the comment above
>> related
>> to TZ_HDCP_CMD_ID.
>>
>>> +
>>> +                       if (ret || resp) {
>>> +                               pr_err("%s: error: scm_call ret = %d,
>>> resp = %d\n",
>>> +                                       __func__, ret, resp);
>>> +                               ret = -EINVAL;
>>> +                               break;
>>> +                       }
>>> +
>>> +                       count -= i;
>>> +               }
>>> +       } else {
>>> +               for (i = 0; i < count; i++)
>>> +                       hdmi_write(hdmi, preg[i], pdata[i]);
>>> +       }
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
>>> +{
>>> +       struct hdmi *hdmi;
>>> +       uint32_t regval, hdcp_int_status;
>>> +       unsigned long flags;
>>> +
>>> +       if (!hdcp_ctrl) {
>>> +               DBG("HDCP is disabled");
>>> +               return;
>>> +       }
>>
>> No need to print a debug line here every time.
>>
>> I would have preferred if you made the call from hdmi_irq() conditional
>> instead, then you would need to check here...
>
> me too
>
> [snip]
>
>>> +static void reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
>>> +{
>>> +       int hdcp_ddc_ctrl1_reg;
>>> +       int hdcp_ddc_status;
>>> +       int failure;
>>> +       int nack0;
>>> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
>>> +
>>> +       /* Check for any DDC transfer failures */
>>> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
>>> +       failure = (hdcp_ddc_status >> 16) & 0x1;
>>
>> failure = hdcp_ddc_status & BIT(16);
>>
>>> +       nack0 = (hdcp_ddc_status >> 14) & 0x1;
>>
>> nack0 = hdcp_ddc_status & BIT(14);
>>
>>> +       DBG("On Entry: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
>>> +               hdcp_ddc_status, failure, nack0);
>>> +
>>> +       if (failure == 0x1) {
>>> +               /*
>>> +                * Indicates that the last HDCP HW DDC transfer failed.
>>> +                * This occurs when a transfer is attempted with HDCP
>>> DDC
>>> +                * disabled (HDCP_DDC_DISABLE=1) or the number of
>>> retries
>>> +                * matches HDCP_DDC_RETRY_CNT.
>>> +                * Failure occurred,  let's clear it.
>>> +                */
>>> +               DBG("DDC failure detected.HDCP_DDC_STATUS=0x%08x",
>>> +                       hdcp_ddc_status);
>>> +
>>> +               /* First, Disable DDC */
>>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, BIT(0));
>>> +
>>> +               /* ACK the Failure to Clear it */
>>> +               hdcp_ddc_ctrl1_reg = hdmi_read(hdmi,
>>> REG_HDMI_HDCP_DDC_CTRL_1);
>>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1,
>>> +                       hdcp_ddc_ctrl1_reg | BIT(0));
>>> +
>>> +               /* Check if the FAILURE got Cleared */
>>> +               hdcp_ddc_status = hdmi_read(hdmi,
>>> REG_HDMI_HDCP_DDC_STATUS);
>>
>> Replace the following lines with:
>>
>> if (hdcp_ddc_status & BIT(16))
>> pr_info("%s: Unable to clear HDCP DDC Failure\n", __func__);
>>
>> No need to print the debug statement either...
>>
>>> +               hdcp_ddc_status = (hdcp_ddc_status >> 16) & BIT(0);
>>> +               if (hdcp_ddc_status == 0x0)
>>> +                       DBG("HDCP DDC Failure cleared");
>>> +               else
>>> +                       pr_info("%s: Unable to clear HDCP DDC
>>> Failure\n",
>>> +                               __func__);
>>> +
>>> +               /* Re-Enable HDCP DDC */
>>> +               hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
>>> +       }
>>> +
>>> +       if (nack0 == 0x1) {
>>> +               DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
>>> +               /* Reset HDMI DDC software status */
>>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(3));
>>
>> Split all these in:
>> val = hdmi_read()
>> val |= foo
>> hdmi_write(val);
>>
>> To make this readable.
>>
>>> +               msleep(20);
>>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) &
>>> ~(BIT(3)));
>>> +
>>> +               /* Reset HDMI DDC Controller */
>>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) | BIT(1));
>>> +               msleep(20);
>>> +               hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_CTRL) & ~BIT(1));
>>> +               DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
>>> +                       hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
>>> +       }
>>> +
>>
>> Just end the function here, no need for the extra debug printouts...
>>
>>> +       hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
>>> +
>>> +       failure = (hdcp_ddc_status >> 16) & BIT(0);
>>> +       nack0 = (hdcp_ddc_status >> 14) & BIT(0);
>>> +       DBG("On Exit: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
>>> +               hdcp_ddc_status, failure, nack0);
>>> +}
>
> DBG() stuff can always be enabled at runtime.. and when it comes to
> failures seen with certain monitors, it is usually the monitor the
> user has and not the ones the developer has, which have fun bugs ;-)
>
> So in general if it is something useful for debugging a failure, when
> you can just ask the user to 'echo 15 >
> /sys/modules/drm/parameters/debug' then plug in monitor and send
> dmesg, I don't mind keeping it.
>
> BR,
> -R
>



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-03 17:16     ` jilaiw
@ 2014-12-03 17:20       ` Rob Clark
  2014-12-03 17:32       ` Bjorn Andersson
  1 sibling, 0 replies; 15+ messages in thread
From: Rob Clark @ 2014-12-03 17:20 UTC (permalink / raw)
  To: Jilai Wang; +Cc: Bjorn Andersson, dri-devel, linux-arm-msm, linux-kernel

On Wed, Dec 3, 2014 at 12:16 PM,  <jilaiw@codeaurora.org> wrote:
>>>> +       enum hdmi_hdcp_state hdcp_state;
>>>> +       struct mutex state_mutex;
>>>> +       struct delayed_work hdcp_reauth_work;
>>>> +       struct delayed_work hdcp_auth_part1_1_work;
>>>> +       struct delayed_work hdcp_auth_part1_2_work;
>>>> +       struct work_struct hdcp_auth_part1_3_work;
>>>> +       struct delayed_work hdcp_auth_part2_1_work;
>>>> +       struct delayed_work hdcp_auth_part2_2_work;
>>>> +       struct delayed_work hdcp_auth_part2_3_work;
>>>> +       struct delayed_work hdcp_auth_part2_4_work;
>>>> +       struct work_struct hdcp_auth_prepare_work;
>>>
>>> You shouldn't use "work" as a way to express states in your state
>>> machine.
>>> Better have 1 auth work function that does all these steps, probably
>>> having
>>> them split in functions just like you do now.
>>>
>>> That way you can have 1 function running the pass of authentication,
>>> starting
>>> by checking if you're reauthing or not then processing each step one by
>>> one,
>>> sleeping inbetween them. You can have the functions return -EAGAIN to
>>> indicate
>>> that you need to retry the current operation and so on.
>>>
>>> This would split the state machine from the state executioners and
>>> simplify
>>> your code.
>>
>> As a side note (disclaimer, I'm not an hdcp expert), but I wonder if
>> eventually some of that should be extracted out into some helpers in
>> drm core.  I guess that is something we'll figure out when a 2nd
>> driver gains upstream HDCP support.  One big work w/ msleep()'s does
>> sound like it would be easier to understand (and therefore easier to
>> refactor out into helpers).
>>
>> [snip]
>>
>
> The reason that I break the partI/PartII work into these small works
> because I want to avoid to use msleep in work.
> Otherwise cancel a HDCP work may cause long delay up to several seconds.


hmm, yeah, several seconds is long enough for user to plug/unplug
several displays ;-)

But I guess a wait_event_timeout() plus a wq that is signalled on hpd
(or whenever we need to cancel) instead of msleep might do the trick?

BR,
-R

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-03 17:16     ` jilaiw
  2014-12-03 17:20       ` Rob Clark
@ 2014-12-03 17:32       ` Bjorn Andersson
  2014-12-03 17:49         ` jilaiw
  1 sibling, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2014-12-03 17:32 UTC (permalink / raw)
  To: Jilai Wang; +Cc: Rob Clark, dri-devel, linux-arm-msm, linux-kernel

On Wed, Dec 3, 2014 at 9:16 AM,  <jilaiw@codeaurora.org> wrote:
[..]
>>>> +       enum hdmi_hdcp_state hdcp_state;
>>>> +       struct mutex state_mutex;
>>>> +       struct delayed_work hdcp_reauth_work;
>>>> +       struct delayed_work hdcp_auth_part1_1_work;
>>>> +       struct delayed_work hdcp_auth_part1_2_work;
>>>> +       struct work_struct hdcp_auth_part1_3_work;
>>>> +       struct delayed_work hdcp_auth_part2_1_work;
>>>> +       struct delayed_work hdcp_auth_part2_2_work;
>>>> +       struct delayed_work hdcp_auth_part2_3_work;
>>>> +       struct delayed_work hdcp_auth_part2_4_work;
>>>> +       struct work_struct hdcp_auth_prepare_work;
>>>
>>> You shouldn't use "work" as a way to express states in your state
>>> machine.
>>> Better have 1 auth work function that does all these steps, probably
>>> having
>>> them split in functions just like you do now.
>>>
>>> That way you can have 1 function running the pass of authentication,
>>> starting
>>> by checking if you're reauthing or not then processing each step one by
>>> one,
>>> sleeping inbetween them. You can have the functions return -EAGAIN to
>>> indicate
>>> that you need to retry the current operation and so on.
>>>
>>> This would split the state machine from the state executioners and
>>> simplify
>>> your code.
>>
>> As a side note (disclaimer, I'm not an hdcp expert), but I wonder if
>> eventually some of that should be extracted out into some helpers in
>> drm core.  I guess that is something we'll figure out when a 2nd
>> driver gains upstream HDCP support.  One big work w/ msleep()'s does
>> sound like it would be easier to understand (and therefore easier to
>> refactor out into helpers).
>>
>> [snip]
>>
>
> The reason that I break the partI/PartII work into these small works
> because I want to avoid to use msleep in work.
> Otherwise cancel a HDCP work may cause long delay up to several seconds.
>

I definitely think the steps are nice size and make things easy to understand.

If you make the steps that require a retry return out to the main
state handling function with a -EAGAIN, then you can have check if you
should retry or abort based on cancellation. Giving you the worst case
cancellation time of the longest sleep...

As Rob suggest you could use wait_event_timeout() or something to
improve this, but on the other hand the worst case here seems to be
the HZ/2 after prepare_auth; others are HZ/8, HZ/20 and HZ/50; not
"seconds".

So I would start with a simple msleep() for implementation simplicity
and then enhance that in a follow up commit (if needed)...

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support
  2014-12-03 17:32       ` Bjorn Andersson
@ 2014-12-03 17:49         ` jilaiw
  2015-01-13 20:43           ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2) Jilai Wang
  0 siblings, 1 reply; 15+ messages in thread
From: jilaiw @ 2014-12-03 17:49 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: Jilai Wang, Rob Clark, dri-devel, linux-arm-msm, linux-kernel

> On Wed, Dec 3, 2014 at 9:16 AM,  <jilaiw@codeaurora.org> wrote:
> [..]
>>>>> +       enum hdmi_hdcp_state hdcp_state;
>>>>> +       struct mutex state_mutex;
>>>>> +       struct delayed_work hdcp_reauth_work;
>>>>> +       struct delayed_work hdcp_auth_part1_1_work;
>>>>> +       struct delayed_work hdcp_auth_part1_2_work;
>>>>> +       struct work_struct hdcp_auth_part1_3_work;
>>>>> +       struct delayed_work hdcp_auth_part2_1_work;
>>>>> +       struct delayed_work hdcp_auth_part2_2_work;
>>>>> +       struct delayed_work hdcp_auth_part2_3_work;
>>>>> +       struct delayed_work hdcp_auth_part2_4_work;
>>>>> +       struct work_struct hdcp_auth_prepare_work;
>>>>
>>>> You shouldn't use "work" as a way to express states in your state
>>>> machine.
>>>> Better have 1 auth work function that does all these steps, probably
>>>> having
>>>> them split in functions just like you do now.
>>>>
>>>> That way you can have 1 function running the pass of authentication,
>>>> starting
>>>> by checking if you're reauthing or not then processing each step one
>>>> by
>>>> one,
>>>> sleeping inbetween them. You can have the functions return -EAGAIN to
>>>> indicate
>>>> that you need to retry the current operation and so on.
>>>>
>>>> This would split the state machine from the state executioners and
>>>> simplify
>>>> your code.
>>>
>>> As a side note (disclaimer, I'm not an hdcp expert), but I wonder if
>>> eventually some of that should be extracted out into some helpers in
>>> drm core.  I guess that is something we'll figure out when a 2nd
>>> driver gains upstream HDCP support.  One big work w/ msleep()'s does
>>> sound like it would be easier to understand (and therefore easier to
>>> refactor out into helpers).
>>>
>>> [snip]
>>>
>>
>> The reason that I break the partI/PartII work into these small works
>> because I want to avoid to use msleep in work.
>> Otherwise cancel a HDCP work may cause long delay up to several seconds.
>>
>
> I definitely think the steps are nice size and make things easy to
> understand.
>
> If you make the steps that require a retry return out to the main
> state handling function with a -EAGAIN, then you can have check if you
> should retry or abort based on cancellation. Giving you the worst case
> cancellation time of the longest sleep...
>
> As Rob suggest you could use wait_event_timeout() or something to
> improve this, but on the other hand the worst case here seems to be
> the HZ/2 after prepare_auth; others are HZ/8, HZ/20 and HZ/50; not
> "seconds".
>
> So I would start with a simple msleep() for implementation simplicity
> and then enhance that in a follow up commit (if needed)...
>
> Regards,
> Bjorn
>

The worst wait time could be 5 seconds if the downstream device is a
REPEATER. The maximum retry time is set to 100 and the delay for each time
is HZ/20, then the maximum wait time before abort will be 5 seconds.
As you suggested, I can use the flag to notify the retry process to abort
earlier in case this work needs to be canceled which can reduce the delay
to HZ/20 then. Or as Rob's suggestion to wait for hpd event.

Thanks,
Jilai



^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2014-12-03 17:49         ` jilaiw
@ 2015-01-13 20:43           ` Jilai Wang
  2015-01-30 21:51             ` Bjorn Andersson
  0 siblings, 1 reply; 15+ messages in thread
From: Jilai Wang @ 2015-01-13 20:43 UTC (permalink / raw)
  To: dri-devel; +Cc: linux-arm-msm, linux-kernel, robdclark, Jilai Wang

Add HDMI HDCP support including HDCP PartI/II/III authentication.
V1: Initial Change
V2: Address Bjorn&Rob's comments
    Refactor the authentication process to use single work instead
    of multiple work for different authentication stages.

Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
---
 drivers/gpu/drm/msm/Makefile              |    1 +
 drivers/gpu/drm/msm/hdmi/hdmi.c           |   45 +-
 drivers/gpu/drm/msm/hdmi/hdmi.h           |   31 +
 drivers/gpu/drm/msm/hdmi/hdmi_audio.c     |    1 -
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c    |    8 +-
 drivers/gpu/drm/msm/hdmi/hdmi_connector.c |    7 +-
 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c      | 1445 +++++++++++++++++++++++++++++
 7 files changed, 1530 insertions(+), 8 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index e5464a0..2b32d4d 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -12,6 +12,7 @@ msm-y := \
 	hdmi/hdmi_audio.o \
 	hdmi/hdmi_bridge.o \
 	hdmi/hdmi_connector.o \
+	hdmi/hdmi_hdcp.o \
 	hdmi/hdmi_i2c.o \
 	hdmi/hdmi_phy_8960.o \
 	hdmi/hdmi_phy_8x60.o \
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c
index b2e610e..7713155 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.c
@@ -22,7 +22,9 @@
 void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 {
 	uint32_t ctrl = 0;
+	unsigned long flags;
 
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	if (power_on) {
 		ctrl |= HDMI_CTRL_ENABLE;
 		if (!hdmi->hdmi_mode) {
@@ -37,6 +39,7 @@ void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 	}
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 	DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
 			power_on ? "Enable" : "Disable", ctrl);
 }
@@ -51,6 +54,10 @@ static irqreturn_t hdmi_irq(int irq, void *dev_id)
 	/* Process DDC: */
 	hdmi_i2c_irq(hdmi->i2c);
 
+	/* Process HDCP: */
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_irq(hdmi->hdcp_ctrl);
+
 	/* TODO audio.. */
 
 	return IRQ_HANDLED;
@@ -60,6 +67,15 @@ static void hdmi_destroy(struct hdmi *hdmi)
 {
 	struct hdmi_phy *phy = hdmi->phy;
 
+	/*
+	 * at this point, hpd has been disabled,
+	 * after flush workq, it's safe to deinit hdcp
+	 */
+	if (hdmi->workq) {
+		flush_workqueue(hdmi->workq);
+		destroy_workqueue(hdmi->workq);
+	}
+	hdmi_hdcp_destroy(hdmi);
 	if (phy)
 		phy->funcs->destroy(phy);
 
@@ -82,6 +98,7 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 {
 	struct hdmi_platform_config *config = pdev->dev.platform_data;
 	struct hdmi *hdmi = NULL;
+	struct resource *res;
 	int i, ret;
 
 	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
@@ -92,6 +109,7 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 
 	hdmi->pdev = pdev;
 	hdmi->config = config;
+	spin_lock_init(&hdmi->reg_lock);
 
 	/* not sure about which phy maps to which msm.. probably I miss some */
 	if (config->phy_init)
@@ -112,6 +130,18 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		goto fail;
 	}
 
+	/* HDCP needs physical address of hdmi register */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		config->mmio_name);
+	hdmi->mmio_phy_addr = res->start;
+
+	hdmi->qfprom_mmio = msm_ioremap(pdev,
+		config->qfprom_mmio_name, "HDMI_QFPROM");
+	if (IS_ERR(hdmi->qfprom_mmio)) {
+		dev_info(&pdev->dev, "can't find qfprom resource\n");
+		hdmi->qfprom_mmio = NULL;
+	}
+
 	hdmi->hpd_regs = devm_kzalloc(&pdev->dev, sizeof(hdmi->hpd_regs[0]) *
 			config->hpd_reg_cnt, GFP_KERNEL);
 	if (!hdmi->hpd_regs) {
@@ -194,6 +224,8 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		hdmi->pwr_clks[i] = clk;
 	}
 
+	hdmi->workq = alloc_ordered_workqueue("msm_hdmi", 0);
+
 	hdmi->i2c = hdmi_i2c_init(hdmi);
 	if (IS_ERR(hdmi->i2c)) {
 		ret = PTR_ERR(hdmi->i2c);
@@ -202,6 +234,12 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		goto fail;
 	}
 
+	hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
+	if (IS_ERR(hdmi->hdcp_ctrl)) {
+		dev_warn(&pdev->dev, "failed to init hdcp: disabled\n");
+		hdmi->hdcp_ctrl = NULL;
+	}
+
 	return hdmi;
 
 fail:
@@ -384,6 +422,7 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	}
 
 	hdmi_cfg->mmio_name     = "core_physical";
+	hdmi_cfg->qfprom_mmio_name = "qfprom_physical";
 	hdmi_cfg->ddc_clk_gpio  = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-clk");
 	hdmi_cfg->ddc_data_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-data");
 	hdmi_cfg->hpd_gpio      = get_gpio(dev, of_node, "qcom,hdmi-tx-hpd");
@@ -398,7 +437,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	if (cpu_is_apq8064()) {
 		static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
 		config.phy_init      = hdmi_phy_8960_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -411,7 +449,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	} else if (cpu_is_msm8960() || cpu_is_msm8960ab()) {
 		static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
 		config.phy_init      = hdmi_phy_8960_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -426,7 +463,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 				"8901_hdmi_mvs", "8901_mpp0"
 		};
 		config.phy_init      = hdmi_phy_8x60_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -437,6 +473,9 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 		config.mux_en_gpio   = -1;
 		config.mux_sel_gpio  = -1;
 	}
+	config.mmio_name     = "hdmi_msm_hdmi_addr";
+	config.qfprom_mmio_name = "hdmi_msm_qfprom_addr";
+
 	hdmi_cfg = &config;
 #endif
 	dev->platform_data = hdmi_cfg;
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h
index 9973e03..db4e049 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.h
@@ -37,6 +37,8 @@ struct hdmi_audio {
 	int rate;
 };
 
+struct hdmi_hdcp_ctrl;
+
 struct hdmi {
 	struct drm_device *dev;
 	struct platform_device *pdev;
@@ -51,6 +53,8 @@ struct hdmi {
 	unsigned long int pixclock;
 
 	void __iomem *mmio;
+	void __iomem *qfprom_mmio;
+	phys_addr_t mmio_phy_addr;
 
 	struct regulator **hpd_regs;
 	struct regulator **pwr_regs;
@@ -68,12 +72,25 @@ struct hdmi {
 	bool hdmi_mode;               /* are we in hdmi mode? */
 
 	int irq;
+	struct workqueue_struct *workq;
+
+	struct hdmi_hdcp_ctrl *hdcp_ctrl;
+
+	/*
+	* spinlock to protect registers shared by different execution
+	* REG_HDMI_CTRL
+	* REG_HDMI_DDC_ARBITRATION
+	* REG_HDMI_HDCP_INT_CTRL
+	* REG_HDMI_HPD_CTRL
+	*/
+	spinlock_t reg_lock;
 };
 
 /* platform config data (ie. from DT, or pdata) */
 struct hdmi_platform_config {
 	struct hdmi_phy *(*phy_init)(struct hdmi *hdmi);
 	const char *mmio_name;
+	const char *qfprom_mmio_name;
 
 	/* regulators that need to be on for hpd: */
 	const char **hpd_reg_names;
@@ -109,6 +126,11 @@ static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
 	return msm_readl(hdmi->mmio + reg);
 }
 
+static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
+{
+	return msm_readl(hdmi->qfprom_mmio + reg);
+}
+
 /*
  * The phy appears to be different, for example between 8960 and 8x60,
  * so split the phy related functions out and load the correct one at
@@ -162,4 +184,13 @@ void hdmi_i2c_irq(struct i2c_adapter *i2c);
 void hdmi_i2c_destroy(struct i2c_adapter *i2c);
 struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi);
 
+/*
+ * hdcp
+ */
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi);
+void hdmi_hdcp_destroy(struct hdmi *hdmi);
+void hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+void hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+
 #endif /* __HDMI_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
index 872485f..df232e2 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
@@ -203,7 +203,6 @@ int hdmi_audio_update(struct hdmi *hdmi)
 		audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4);
 		audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE;
 	} else {
-		hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE);
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT;
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND;
 		vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE;
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
index 6902ad6..dd0933b 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
@@ -105,6 +105,9 @@ static void hdmi_bridge_pre_enable(struct drm_bridge *bridge)
 
 	phy->funcs->powerup(phy, hdmi->pixclock);
 	hdmi_set_mode(hdmi, true);
+
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_on(hdmi->hdcp_ctrl);
 }
 
 static void hdmi_bridge_enable(struct drm_bridge *bridge)
@@ -121,6 +124,9 @@ static void hdmi_bridge_post_disable(struct drm_bridge *bridge)
 	struct hdmi *hdmi = hdmi_bridge->hdmi;
 	struct hdmi_phy *phy = hdmi->phy;
 
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_off(hdmi->hdcp_ctrl);
+
 	DBG("power down");
 	hdmi_set_mode(hdmi, false);
 	phy->funcs->powerdown(phy);
@@ -145,8 +151,6 @@ static void hdmi_bridge_mode_set(struct drm_bridge *bridge,
 
 	hdmi->pixclock = mode->clock * 1000;
 
-	hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1;
-
 	hstart = mode->htotal - mode->hsync_start;
 	hend   = mode->htotal - mode->hsync_start + mode->hdisplay;
 
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
index 9aebc47..9c1eaf0 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
@@ -140,6 +140,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 	struct hdmi_phy *phy = hdmi->phy;
 	uint32_t hpd_ctrl;
 	int i, ret;
+	unsigned long flags;
 
 	for (i = 0; i < config->hpd_reg_cnt; i++) {
 		ret = regulator_enable(hdmi->hpd_regs[i]);
@@ -185,6 +186,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			HDMI_HPD_INT_CTRL_INT_EN);
 
 	/* set timeout to 4.1ms (max) for hardware debounce */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
 	hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
 
@@ -193,6 +195,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
 	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
 			HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 
 	return 0;
 
@@ -239,7 +242,6 @@ hotplug_work(struct work_struct *work)
 void hdmi_connector_irq(struct drm_connector *connector)
 {
 	struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
-	struct msm_drm_private *priv = connector->dev->dev_private;
 	struct hdmi *hdmi = hdmi_connector->hdmi;
 	uint32_t hpd_int_status, hpd_int_ctrl;
 
@@ -263,7 +265,7 @@ void hdmi_connector_irq(struct drm_connector *connector)
 			hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
 		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
 
-		queue_work(priv->wq, &hdmi_connector->hpd_work);
+		queue_work(hdmi->workq, &hdmi_connector->hpd_work);
 	}
 }
 
@@ -339,6 +341,7 @@ static int hdmi_connector_get_modes(struct drm_connector *connector)
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
 
+	hdmi->hdmi_mode = drm_detect_hdmi_monitor(edid);
 	drm_mode_connector_update_edid_property(connector, edid);
 
 	if (edid) {
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
new file mode 100644
index 0000000..857e273
--- /dev/null
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
@@ -0,0 +1,1445 @@
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hdmi.h"
+#include <soc/qcom/scm.h>
+
+#define SCM_CMD_HDCP 0x01
+#define SCM_HDCP_MAX_REG 5
+
+struct scm_hdcp_req {
+	u32 addr;
+	u32 val;
+};
+
+#define HDCP_REG_ENABLE 0x01
+#define HDCP_REG_DISABLE 0x00
+#define HDCP_PORT_ADDR 0x74
+
+#define HDCP_INT_STATUS_MASK ( \
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_XFER_REQ_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_XFER_DONE_INT)
+
+#define AUTH_WORK_RETRIES_TIME 100
+#define AUTH_RETRIES_TIME 30
+
+/* QFPROM Registers for HDMI/HDCP */
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_LSB  0x000000F8
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_MSB  0x000000FC
+#define HDCP_KSV_LSB                     0x000060D8
+#define HDCP_KSV_MSB                     0x000060DC
+
+enum DS_TYPE {  /* type of downstream device */
+	DS_UNKNOWN,
+	DS_RECEIVER,
+	DS_REPEATER,
+};
+
+enum hdmi_hdcp_state {
+	HDCP_STATE_NO_AKSV,
+	HDCP_STATE_INACTIVE,
+	HDCP_STATE_AUTHENTICATING,
+	HDCP_STATE_AUTHENTICATED,
+	HDCP_STATE_AUTH_FAILED
+};
+
+struct hdmi_hdcp_reg_data {
+	u32 reg_id;
+	u32 off;
+	char *name;
+	u32 reg_val;
+};
+
+struct hdmi_hdcp_ctrl {
+	struct hdmi *hdmi;
+	u32 auth_retries;
+	bool tz_hdcp;
+	enum hdmi_hdcp_state hdcp_state;
+	struct work_struct hdcp_auth_work;
+	struct work_struct hdcp_reauth_work;
+
+#define AUTH_ABORT_EV 1
+#define AUTH_RESULT_RDY_EV 2
+	unsigned long auth_event;
+	wait_queue_head_t auth_event_queue;
+
+	u32 ksv_fifo_w_index;
+	/*
+	 * store aksv from qfprom
+	 */
+	u32 aksv_lsb;
+	u32 aksv_msb;
+	bool aksv_valid;
+	u32 ds_type;
+	u32 bksv_lsb;
+	u32 bksv_msb;
+	u8 dev_count;
+	u8 depth;
+	u8 ksv_list[5 * 127];
+	bool max_cascade_exceeded;
+	bool max_dev_exceeded;
+};
+
+static int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset,
+	u8 *data, u16 data_len)
+{
+	int rc;
+	int retry = 5;
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &offset,
+		}, {
+			.addr	= addr >> 1,
+			.flags	= I2C_M_RD,
+			.len	= data_len,
+			.buf	= data,
+		}
+	};
+
+	DBG("Start DDC read");
+retry:
+	rc = i2c_transfer(hdmi->i2c, msgs, 2);
+
+	retry--;
+	if (rc == 2)
+		rc = 0;
+	else if (retry > 0)
+		goto retry;
+	else
+		rc = -EIO;
+
+	DBG("End DDC read %d", rc);
+
+	return rc;
+}
+
+#define HDCP_DDC_WRITE_MAX_BYTE_NUM 32
+
+static int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset,
+	u8 *data, u16 data_len)
+{
+	int rc;
+	int retry = 10;
+	u8 buf[HDCP_DDC_WRITE_MAX_BYTE_NUM];
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+		}
+	};
+
+	DBG("Start DDC write");
+	if (data_len > (HDCP_DDC_WRITE_MAX_BYTE_NUM - 1)) {
+		pr_err("%s: write size too big\n", __func__);
+		return -ERANGE;
+	}
+
+	buf[0] = offset;
+	memcpy(&buf[1], data, data_len);
+	msgs[0].buf = buf;
+	msgs[0].len = data_len + 1;
+retry:
+	rc = i2c_transfer(hdmi->i2c, msgs, 1);
+
+	retry--;
+	if (rc == 1)
+		rc = 0;
+	else if (retry > 0)
+		goto retry;
+	else
+		rc = -EIO;
+
+	DBG("End DDC write %d", rc);
+
+	return rc;
+}
+
+static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, u32 *preg,
+	u32 *pdata, u32 count)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	struct scm_hdcp_req scm_buf[SCM_HDCP_MAX_REG];
+	u32 resp, phy_addr, idx = 0;
+	int i, ret = 0;
+
+	WARN_ON(!pdata || !preg || (count == 0));
+
+	if (hdcp_ctrl->tz_hdcp) {
+		phy_addr = (u32)hdmi->mmio_phy_addr;
+
+		while (count) {
+			memset(scm_buf, 0, sizeof(scm_buf));
+			for (i = 0; i < count && i < SCM_HDCP_MAX_REG; i++) {
+				scm_buf[i].addr = phy_addr + preg[idx];
+				scm_buf[i].val  = pdata[idx];
+				idx++;
+			}
+			ret = scm_call(SCM_SVC_HDCP, SCM_CMD_HDCP,
+				scm_buf, sizeof(scm_buf), &resp, sizeof(resp));
+
+			if (ret || resp) {
+				pr_err("%s: error: scm_call ret=%d, resp=%d\n",
+					__func__, ret, resp);
+				ret = -EINVAL;
+				break;
+			}
+
+			count -= i;
+		}
+	} else {
+		for (i = 0; i < count; i++)
+			hdmi_write(hdmi, preg[i], pdata[i]);
+	}
+
+	return ret;
+}
+
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val, hdcp_int_status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_INT_CTRL);
+	hdcp_int_status = reg_val & HDCP_INT_STATUS_MASK;
+	if (!hdcp_int_status) {
+		spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+		return;
+	}
+	/* Clear Interrupts */
+	reg_val |= hdcp_int_status << 1;
+	/* Clear AUTH_FAIL_INFO as well */
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT)
+		reg_val |= HDMI_HDCP_INT_CTRL_AUTH_FAIL_INFO_ACK;
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	DBG("hdcp irq %x", hdcp_int_status);
+
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_INT) {
+		pr_info("%s:AUTH_SUCCESS_INT received\n", __func__);
+		if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state) {
+			set_bit(AUTH_RESULT_RDY_EV, &hdcp_ctrl->auth_event);
+			wake_up_all(&hdcp_ctrl->auth_event_queue);
+		}
+	}
+
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT) {
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		pr_info("%s: AUTH_FAIL_INT rcvd, LINK0_STATUS=0x%08x\n",
+			__func__, reg_val);
+		if (HDCP_STATE_AUTHENTICATED == hdcp_ctrl->hdcp_state)
+			queue_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work);
+		else if (HDCP_STATE_AUTHENTICATING ==
+				hdcp_ctrl->hdcp_state) {
+			set_bit(AUTH_RESULT_RDY_EV, &hdcp_ctrl->auth_event);
+			wake_up_all(&hdcp_ctrl->auth_event_queue);
+		}
+	}
+}
+
+static int hdmi_hdcp_msleep(struct hdmi_hdcp_ctrl *hdcp_ctrl, u32 ms, u32 ev)
+{
+	int rc;
+
+	rc = wait_event_timeout(hdcp_ctrl->auth_event_queue,
+		!!test_bit(ev, &hdcp_ctrl->auth_event),
+		msecs_to_jiffies(ms));
+	if (rc) {
+		pr_info("%s: msleep is canceled by event %d\n",
+				__func__, ev);
+		clear_bit(ev, &hdcp_ctrl->auth_event);
+		return -ECANCELED;
+	}
+
+	return 0;
+}
+
+static int hdmi_hdcp_read_validate_aksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	/* Fetch aksv from QFPROM, this info should be public. */
+	hdcp_ctrl->aksv_lsb = hdmi_qfprom_read(hdmi, HDCP_KSV_LSB);
+	hdcp_ctrl->aksv_msb = hdmi_qfprom_read(hdmi, HDCP_KSV_MSB);
+
+	/* check there are 20 ones in AKSV */
+	if ((hweight32(hdcp_ctrl->aksv_lsb) + hweight32(hdcp_ctrl->aksv_msb))
+			!= 20) {
+		pr_err("%s: AKSV QFPROM doesn't have 20 1's, 20 0's\n",
+			__func__);
+		pr_err("%s: QFPROM AKSV chk failed (AKSV=%02x%08x)\n",
+			__func__, hdcp_ctrl->aksv_msb,
+			hdcp_ctrl->aksv_lsb);
+		return -EINVAL;
+	}
+	DBG("AKSV=%02x%08x", hdcp_ctrl->aksv_msb, hdcp_ctrl->aksv_lsb);
+
+	return 0;
+}
+
+static int reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val, failure, nack0;
+	int rc = 0;
+
+	/* Check for any DDC transfer failures */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+	failure = reg_val & HDMI_HDCP_DDC_STATUS_FAILED;
+	nack0 = reg_val & HDMI_HDCP_DDC_STATUS_NACK0;
+	DBG("HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
+		reg_val, failure, nack0);
+
+	if (failure) {
+		/*
+		 * Indicates that the last HDCP HW DDC transfer failed.
+		 * This occurs when a transfer is attempted with HDCP DDC
+		 * disabled (HDCP_DDC_DISABLE=1) or the number of retries
+		 * matches HDCP_DDC_RETRY_CNT.
+		 * Failure occurred,  let's clear it.
+		 */
+		DBG("DDC failure detected");
+
+		/* First, Disable DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0,
+			HDMI_HDCP_DDC_CTRL_0_DISABLE);
+
+		/* ACK the Failure to Clear it */
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1);
+		reg_val |= HDMI_HDCP_DDC_CTRL_1_FAILED_ACK;
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, reg_val);
+
+		/* Check if the FAILURE got Cleared */
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		if (reg_val & HDMI_HDCP_DDC_STATUS_FAILED)
+			pr_info("%s: Unable to clear HDCP DDC Failure\n",
+				__func__);
+
+		/* Re-Enable HDCP DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
+	}
+
+	if (nack0) {
+		DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+		/* Reset HDMI DDC software status */
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val |= HDMI_DDC_CTRL_SW_STATUS_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val &= ~HDMI_DDC_CTRL_SW_STATUS_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		/* Reset HDMI DDC Controller */
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val |= HDMI_DDC_CTRL_SOFT_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		/* If previous msleep is aborted, skip this msleep */
+		if (!rc)
+			rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val &= ~HDMI_DDC_CTRL_SOFT_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+		DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+	}
+
+	return rc;
+}
+
+static int hdmi_hdcp_hw_ddc_clean(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 hdcp_ddc_status, ddc_hw_status;
+	u32 xfer_done, xfer_req, hw_done;
+	bool hw_not_ready;
+	u32 timeout_count;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	if (hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS) == 0)
+		return 0;
+
+	/* Wait to be clean on DDC HW engine */
+	timeout_count = 100;
+	do {
+		hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		ddc_hw_status = hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS);
+
+		xfer_done = hdcp_ddc_status & HDMI_HDCP_DDC_STATUS_XFER_DONE;
+		xfer_req = hdcp_ddc_status & HDMI_HDCP_DDC_STATUS_XFER_REQ;
+		hw_done = ddc_hw_status & HDMI_DDC_HW_STATUS_DONE;
+		hw_not_ready = !xfer_done || xfer_req || !hw_done;
+
+		if (hw_not_ready)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_warn("%s: hw_ddc_clean failed\n", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static void hdmi_hdcp_reauth_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_reauth_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	u32 reg_val;
+
+	DBG("HDCP REAUTH WORK");
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val &= ~HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+
+	/* Disable HDCP interrupts */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET,
+		HDMI_HDCP_RESET_LINK0_DEAUTHENTICATE);
+
+	/* Wait to be clean on DDC HW engine */
+	if (hdmi_hdcp_hw_ddc_clean(hdcp_ctrl)) {
+		pr_info("%s: reauth work aborted\n", __func__);
+		return;
+	}
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	/* Enable HPD circuitry */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val |= HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Only retry defined times then abort current authenticating process
+	 */
+	if (++hdcp_ctrl->auth_retries == AUTH_RETRIES_TIME) {
+		hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+		hdcp_ctrl->auth_retries = 0;
+		pr_info("%s: abort reauthentication!\n", __func__);
+
+		return;
+	}
+
+	DBG("Queue AUTH WORK");
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_work);
+}
+
+static int hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	u32 reg_val;
+	unsigned long flags;
+	int rc;
+
+	if (!hdcp_ctrl->aksv_valid) {
+		rc = hdmi_hdcp_read_validate_aksv(hdcp_ctrl);
+		if (rc) {
+			pr_err("%s: ASKV validation failed\n", __func__);
+			hdcp_ctrl->hdcp_state = HDCP_STATE_NO_AKSV;
+			return -ENOTSUPP;
+		}
+		hdcp_ctrl->aksv_valid = true;
+	}
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	/* disable HDMI Encrypt */
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+
+	/* Enabling Software DDC */
+	reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	reg_val &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Write AKSV read from QFPROM to the HDCP registers.
+	 * This step is needed for HDCP authentication and must be
+	 * written before enabling HDCP.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_LOWER_AKSV, hdcp_ctrl->aksv_lsb);
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_UPPER_AKSV, hdcp_ctrl->aksv_msb);
+
+	/*
+	 * HDCP setup prior to enabling HDCP_CTRL.
+	 * Setup seed values for random number An.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL0, 0xB1FFB0FF);
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE);
+
+	/* Disable the RngCipher state */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL);
+	reg_val &= ~HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER;
+	hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, reg_val);
+	DBG("HDCP_DEBUG_CTRL=0x%08x",
+		hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL));
+
+	/*
+	 * Ensure that all register writes are completed before
+	 * enabling HDCP cipher
+	 */
+	wmb();
+
+	/*
+	 * Enable HDCP
+	 * This needs to be done as early as possible in order for the
+	 * hardware to make An available to read
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, HDMI_HDCP_CTRL_ENABLE);
+
+	/*
+	 * If we had stale values for the An ready bit, it should most
+	 * likely be cleared now after enabling HDCP cipher
+	 */
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	DBG("After enabling HDCP Link0_Status=0x%08x", link0_status);
+	if (!(link0_status &
+		(HDMI_HDCP_LINK0_STATUS_AN_0_READY |
+		HDMI_HDCP_LINK0_STATUS_AN_1_READY)))
+		DBG("An not ready after enabling HDCP");
+
+	/* Clear any DDC failures from previous tries before enable HDCP*/
+	rc = reset_hdcp_ddc_failures(hdcp_ctrl);
+
+	return rc;
+}
+
+static void hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	DBG("hdcp auth failed, queue reauth work");
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAILED;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work);
+}
+
+static void hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	/*
+	 * Disable software DDC before going into part3 to make sure
+	 * there is no Arbitration between software and hardware for DDC
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	reg_val |= HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/* enable HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val |= HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED;
+	hdcp_ctrl->auth_retries = 0;
+}
+
+/*
+ * hdcp authenticating part 1
+ * Wait Key/An ready
+ * Read BCAPS from sink
+ * Write BCAPS and AKSV into HDCP engine
+ * Write An and AKSV to sink
+ * Read BKSV from sink and write into HDCP engine
+ */
+static int hdmi_hdcp_wait_key_an_ready(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status, keys_state;
+	u32 timeout_count;
+	bool an_ready;
+
+	/* Wait for HDCP keys to be checked and validated */
+	timeout_count = 100;
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		keys_state = (link0_status >> 28) & 0x7;
+		if (keys_state == HDCP_KEYS_STATE_VALID)
+			break;
+
+		DBG("Keys not ready(%d). s=%d, l0=%0x08x",
+			timeout_count, keys_state, link0_status);
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait key state timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	timeout_count = 100;
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		an_ready = (link0_status & HDMI_HDCP_LINK0_STATUS_AN_0_READY)
+			&& (link0_status & HDMI_HDCP_LINK0_STATUS_AN_1_READY);
+		if (an_ready)
+			break;
+
+		DBG("An not ready(%d). l0_status=0x%08x",
+			timeout_count, link0_status);
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait An timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static int hdmi_hdcp_send_aksv_an(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_aksv_0, link0_aksv_1;
+	u32 link0_an[2];
+	u8 aksv[5];
+
+	/* Read An0 and An1 */
+	link0_an[0] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA5);
+	link0_an[1] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA6);
+
+	/* Read AKSV */
+	link0_aksv_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA3);
+	link0_aksv_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4);
+
+	DBG("Link ASKV=%08x%08x", link0_aksv_0, link0_aksv_1);
+	/* Copy An and AKSV to byte arrays for transmission */
+	aksv[0] =  link0_aksv_0        & 0xFF;
+	aksv[1] = (link0_aksv_0 >> 8)  & 0xFF;
+	aksv[2] = (link0_aksv_0 >> 16) & 0xFF;
+	aksv[3] = (link0_aksv_0 >> 24) & 0xFF;
+	aksv[4] =  link0_aksv_1        & 0xFF;
+
+	/* Write An to offset 0x18 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x18, (u8 *)link0_an,
+		(u16)sizeof(link0_an));
+	if (rc) {
+		pr_err("%s:An write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-An=%08x%08x", link0_an[0], link0_an[1]);
+
+	/* Write AKSV to offset 0x10 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x10, aksv, 5);
+	if (rc) {
+		pr_err("%s:AKSV write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-AKSV=%02x%08x", link0_aksv_1 & 0xFF, link0_aksv_0);
+
+	return 0;
+}
+
+static int hdmi_hdcp_recv_bksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u8 bksv[5];
+	u32 reg[2], data[2];
+
+	/* Read BKSV at offset 0x00 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x00, bksv, 5);
+	if (rc) {
+		pr_err("%s:BKSV read failed\n", __func__);
+		return rc;
+	}
+
+	hdcp_ctrl->bksv_lsb = bksv[0] | (bksv[1] << 8) |
+		(bksv[2] << 16) | (bksv[3] << 24);
+	hdcp_ctrl->bksv_msb = bksv[4];
+	DBG(":BKSV=%02x%08x", hdcp_ctrl->bksv_msb, hdcp_ctrl->bksv_lsb);
+
+	/* check there are 20 ones in BKSV */
+	if ((hweight32(hdcp_ctrl->bksv_lsb) + hweight32(hdcp_ctrl->bksv_msb))
+			!= 20) {
+		pr_err(": BKSV doesn't have 20 1's and 20 0's\n");
+		pr_err(": BKSV chk fail. BKSV=%02x%02x%02x%02x%02x\n",
+			bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]);
+		return -EINVAL;
+	}
+
+	/* Write BKSV read from sink to HDCP registers */
+	reg[0] = REG_HDMI_HDCP_RCVPORT_DATA0;
+	data[0] = hdcp_ctrl->bksv_lsb;
+	reg[1] = REG_HDMI_HDCP_RCVPORT_DATA1;
+	data[1] = hdcp_ctrl->bksv_msb;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static int hdmi_hdcp_recv_bcaps(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg, data;
+	u8 bcaps;
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);
+	if (rc) {
+		pr_err("%s:BCAPS read failed\n", __func__);
+		return rc;
+	}
+	DBG("BCAPS=%02x", bcaps);
+
+	/* receiver (0), repeater (1) */
+	hdcp_ctrl->ds_type = (bcaps & BIT(6)) ? DS_REPEATER : DS_RECEIVER;
+
+	/* Write BCAPS to the hardware */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = (u32)bcaps;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part1_key_exchange(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	int rc;
+
+	/* Wait for AKSV key and An ready */
+	rc = hdmi_hdcp_wait_key_an_ready(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: wait key and an ready failed\n", __func__);
+		return rc;
+	};
+
+	/* Read BCAPS and send to HDCP engine */
+	rc = hdmi_hdcp_recv_bcaps(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: read bcaps error, abort\n", __func__);
+		return rc;
+	}
+
+	/*
+	 * 1.1_Features turned off by default.
+	 * No need to write AInfo since 1.1_Features is disabled.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4, 0);
+
+	/* Send AKSV and An to sink */
+	rc = hdmi_hdcp_send_aksv_an(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:An/Aksv write failed\n", __func__);
+		return rc;
+	}
+
+	/* Read BKSV and send to HDCP engine*/
+	rc = hdmi_hdcp_recv_bksv(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:BKSV Process failed\n", __func__);
+		return rc;
+	}
+
+	/* Enable HDCP interrupts and ack/clear any stale interrupts */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL,
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_ACK |
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_MASK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_ACK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_MASK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_INFO_ACK);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	return 0;
+}
+
+/* read R0' from sink and pass it to HDCP engine */
+static int hdmi_hdcp_auth_part1_recv_r0(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	int rc = 0;
+	u8 buf[2];
+
+	/*
+	 * HDCP Compliance Test case 1A-01:
+	 * Wait here at least 100ms before reading R0'
+	 */
+	rc = hdmi_hdcp_msleep(hdcp_ctrl, 125, AUTH_ABORT_EV);
+	if (rc)
+		return rc;
+
+	/* Read R0' at offset 0x08 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x08, buf, 2);
+	if (rc) {
+		pr_err("%s:R0' read failed\n", __func__);
+		return rc;
+	}
+	DBG("R0'=%02x%02x", buf[1], buf[0]);
+
+	/* Write R0' to HDCP registers and check to see if it is a match */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA2_0,
+		(((u32)buf[1]) << 8) | buf[0]);
+
+	return 0;
+}
+
+/* Wait for authenticating result: R0/R0' are matched or not */
+static int hdmi_hdcp_auth_part1_verify_r0(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	int rc;
+
+	/* wait for hdcp irq, 10 sec should be long enough */
+	rc = hdmi_hdcp_msleep(hdcp_ctrl, 10000, AUTH_RESULT_RDY_EV);
+	if (!rc) {
+		pr_err("%s: Wait Auth IRQ timeout\n", __func__);
+		return -ETIMEDOUT;
+	}
+
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	if (!(link0_status & HDMI_HDCP_LINK0_STATUS_RI_MATCHES)) {
+		pr_err("%s: Authentication Part I failed\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Enable HDCP Encryption */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL,
+		HDMI_HDCP_CTRL_ENABLE |
+		HDMI_HDCP_CTRL_ENCRYPTION_ENABLE);
+
+	return 0;
+}
+
+static int hdmi_hdcp_recv_check_bstatus(struct hdmi_hdcp_ctrl *hdcp_ctrl,
+	u16 *pbstatus)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	bool max_devs_exceeded = false, max_cascade_exceeded = false;
+	u32 repeater_cascade_depth = 0, down_stream_devices = 0;
+	u16 bstatus;
+	u8 buf[2];
+
+	/* Read BSTATUS at offset 0x41 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x41, buf, 2);
+	if (rc) {
+		pr_err("%s: BSTATUS read failed\n", __func__);
+		goto error;
+	}
+	*pbstatus = bstatus = (buf[1] << 8) | buf[0];
+
+
+	down_stream_devices = bstatus & 0x7F;
+	repeater_cascade_depth = (bstatus >> 8) & 0x7;
+	max_devs_exceeded = (bstatus & BIT(7)) ? true : false;
+	max_cascade_exceeded = (bstatus & BIT(11)) ? true : false;
+
+	if (down_stream_devices == 0) {
+		/*
+		 * If no downstream devices are attached to the repeater
+		 * then part II fails.
+		 * todo: The other approach would be to continue PART II.
+		 */
+		pr_err("%s: No downstream devices\n", __func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-05:
+	 * Check if no. of devices connected to repeater
+	 * exceed max_devices_connected from bit 7 of Bstatus.
+	 */
+	if (max_devs_exceeded) {
+		pr_err("%s: no. of devs connected exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-06:
+	 * Check if no. of cascade connected to repeater
+	 * exceed max_cascade_connected from bit 11 of Bstatus.
+	 */
+	if (max_cascade_exceeded) {
+		pr_err("%s: no. of cascade conn exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+error:
+	hdcp_ctrl->dev_count = down_stream_devices;
+	hdcp_ctrl->max_cascade_exceeded = max_cascade_exceeded;
+	hdcp_ctrl->max_dev_exceeded = max_devs_exceeded;
+	hdcp_ctrl->depth = repeater_cascade_depth;
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part2_wait_ksv_fifo_ready(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg, data;
+	u32 timeout_count;
+	u16 bstatus;
+	u8 bcaps;
+
+	/*
+	 * Wait until READY bit is set in BCAPS, as per HDCP specifications
+	 * maximum permitted time to check for READY bit is five seconds.
+	 */
+	timeout_count = 100;
+	do {
+		/* Read BCAPS at offset 0x40 */
+		rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);
+		if (rc) {
+			pr_err("%s: BCAPS read failed\n", __func__);
+			return rc;
+		}
+
+		if (bcaps & BIT(5))
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait KSV fifo ready timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	rc = hdmi_hdcp_recv_check_bstatus(hdcp_ctrl, &bstatus);
+	if (rc) {
+		pr_err("%s: bstatus error\n", __func__);
+		return rc;
+	}
+
+	/* Write BSTATUS and BCAPS to HDCP registers */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = bcaps | (bstatus << 8);
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+	if (rc) {
+		pr_err("%s: BSTATUS write failed\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * hdcp authenticating part 2: 2nd
+ * read ksv fifo from sink
+ * transfer V' from sink to HDCP engine
+ * reset SHA engine
+ */
+static int hdmi_hdcp_transfer_v_h(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	int rc = 0;
+	struct hdmi_hdcp_reg_data reg_data[]  = {
+		{REG_HDMI_HDCP_RCVPORT_DATA7,  0x20, "V' H0"},
+		{REG_HDMI_HDCP_RCVPORT_DATA8,  0x24, "V' H1"},
+		{REG_HDMI_HDCP_RCVPORT_DATA9,  0x28, "V' H2"},
+		{REG_HDMI_HDCP_RCVPORT_DATA10, 0x2C, "V' H3"},
+		{REG_HDMI_HDCP_RCVPORT_DATA11, 0x30, "V' H4"},
+	};
+	struct hdmi_hdcp_reg_data *rd;
+	u32 size = ARRAY_SIZE(reg_data);
+	u32 reg[ARRAY_SIZE(reg_data)];
+	u32 data[ARRAY_SIZE(reg_data)];
+	int i;
+
+	for (i = 0; i < size; i++) {
+		rd = &reg_data[i];
+		rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR,
+			rd->off, (u8 *)&data[i], (u16)sizeof(data[i]));
+		if (rc) {
+			pr_err("%s: Read %s failed\n", __func__, rd->name);
+			goto error;
+		}
+
+		DBG("%s =%x", rd->name, data[i]);
+		reg[i] = reg_data[i].reg_id;
+	}
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, size);
+
+error:
+	return rc;
+}
+
+static int hdmi_hdcp_recv_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 ksv_bytes;
+
+	ksv_bytes = 5 * hdcp_ctrl->dev_count;
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x43,
+		hdcp_ctrl->ksv_list, ksv_bytes);
+	if (rc)
+		pr_err("%s: KSV FIFO read failed\n", __func__);
+
+	return rc;
+}
+
+static int hdmi_hdcp_reset_sha_engine(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	u32 reg[2], data[2];
+	u32 rc  = 0;
+
+	reg[0] = REG_HDMI_HDCP_SHA_CTRL;
+	data[0] = HDCP_REG_ENABLE;
+	reg[1] = REG_HDMI_HDCP_SHA_CTRL;
+	data[1] = HDCP_REG_DISABLE;
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part2_recv_ksv_fifo(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 timeout_count;
+
+	/*
+	 * Read KSV FIFO over DDC
+	 * Key Selection vector FIFO Used to pull downstream KSVs
+	 * from HDCP Repeaters.
+	 * All bytes (DEVICE_COUNT * 5) must be read in a single,
+	 * auto incrementing access.
+	 * All bytes read as 0x00 for HDCP Receivers that are not
+	 * HDCP Repeaters (REPEATER == 0).
+	 */
+	timeout_count = 100;
+	do {
+		rc = hdmi_hdcp_recv_ksv_fifo(hdcp_ctrl);
+		if (!rc)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Recv ksv fifo timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 25, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	rc = hdmi_hdcp_transfer_v_h(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: transfer V failed\n", __func__);
+		return rc;
+	}
+
+	/* reset SHA engine before write ksv fifo */
+	rc = hdmi_hdcp_reset_sha_engine(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: fail to reset sha engine\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Write KSV FIFO to HDCP_SHA_DATA.
+ * This is done 1 byte at time starting with the LSB.
+ * Once 64 bytes have been written, we need to poll for
+ * HDCP_SHA_BLOCK_DONE before writing any further
+ * If the last byte is written, we need to poll for
+ * HDCP_SHA_COMP_DONE to wait until HW finish
+ */
+static int hdmi_hdcp_write_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int i;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 ksv_bytes, last_byte = 0;
+	u8 *ksv_fifo = NULL;
+	u32 reg_val, data, reg;
+	u32 rc  = 0;
+
+	ksv_bytes  = 5 * hdcp_ctrl->dev_count;
+
+	/* Check if need to wait for HW completion */
+	if (hdcp_ctrl->ksv_fifo_w_index) {
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_SHA_STATUS);
+		DBG("HDCP_SHA_STATUS=%08x", reg_val);
+		if (hdcp_ctrl->ksv_fifo_w_index == ksv_bytes) {
+			/* check COMP_DONE if last write */
+			if (reg_val & HDMI_HDCP_SHA_STATUS_COMP_DONE) {
+				DBG("COMP_DONE");
+				return 0;
+			} else {
+				return -EAGAIN;
+			}
+		} else {
+			/* check BLOCK_DONE if not last write */
+			if (!(reg_val & HDMI_HDCP_SHA_STATUS_BLOCK_DONE))
+				return -EAGAIN;
+			else
+				DBG("BLOCK_DONE");
+		}
+	}
+
+	ksv_bytes  -= hdcp_ctrl->ksv_fifo_w_index;
+	if (ksv_bytes <= 64)
+		last_byte = 1;
+	else
+		ksv_bytes = 64;
+
+	ksv_fifo = hdcp_ctrl->ksv_list;
+	ksv_fifo += hdcp_ctrl->ksv_fifo_w_index;
+
+	for (i = 0; i < ksv_bytes; i++) {
+		/* Write KSV byte and set DONE bit[0] for last byte*/
+		reg_val = ksv_fifo[i] << 16;
+		if ((i == (ksv_bytes - 1)) && last_byte)
+			reg_val |= HDMI_HDCP_SHA_DATA_DONE;
+
+		reg = REG_HDMI_HDCP_SHA_DATA;
+		data = reg_val;
+		rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+		if (rc)
+			return rc;
+	}
+
+	hdcp_ctrl->ksv_fifo_w_index += ksv_bytes;
+
+	/*
+	 *return -EAGAIN to notify caller to wait for COMP_DONE or BLOCK_DONE
+	 */
+	return -EAGAIN;
+}
+
+/* write ksv fifo into HDCP engine */
+static int hdmi_hdcp_auth_part2_write_ksv_fifo(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 timeout_count;
+
+	hdcp_ctrl->ksv_fifo_w_index = 0;
+	timeout_count = 100;
+	do {
+		rc = hdmi_hdcp_write_ksv_fifo(hdcp_ctrl);
+		if (!rc)
+			break;
+
+		if (rc != -EAGAIN)
+			return rc;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Write KSV fifo timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static int hdmi_hdcp_auth_part2_check_v_match(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	u32 timeout_count = 100;
+
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		if (link0_status & HDMI_HDCP_LINK0_STATUS_V_MATCHES)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+				pr_err("%s: HDCP V Match timedout", __func__);
+				return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static void hdmi_hdcp_auth_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_auth_work);
+	int rc;
+
+	rc = hdmi_hdcp_auth_prepare(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: auth prepare failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	/* HDCP PartI */
+	rc = hdmi_hdcp_auth_part1_key_exchange(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: key exchange failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part1_recv_r0(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: receive r0 failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part1_verify_r0(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: verify r0 failed %d\n", __func__, rc);
+		goto end;
+	}
+	pr_info("%s: Authentication Part I successful\n", __func__);
+	if (hdcp_ctrl->ds_type == DS_RECEIVER)
+		goto end;
+
+	/* HDCP PartII */
+	rc = hdmi_hdcp_auth_part2_wait_ksv_fifo_ready(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: wait ksv fifo ready failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_recv_ksv_fifo(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: recv ksv fifo failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_write_ksv_fifo(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: write ksv fifo failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_check_v_match(hdcp_ctrl);
+	if (rc)
+		pr_err("%s: check v match failed %d\n", __func__, rc);
+
+end:
+	if (rc == -ECANCELED) {
+		pr_info("%s: hdcp authentication canceled\n", __func__);
+	} else if (rc == -ENOTSUPP) {
+		pr_info("%s: hdcp is not supported\n", __func__);
+	} else if (rc) {
+		pr_err("%s: hdcp authentication failed\n", __func__);
+		hdmi_hdcp_auth_fail(hdcp_ctrl);
+	} else {
+		hdmi_hdcp_auth_done(hdcp_ctrl);
+	}
+}
+
+void hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	if ((HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) ||
+		(HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) {
+		DBG("still active or activating or no askv. returning");
+		return;
+	}
+
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->auth_event = 0;
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	hdcp_ctrl->auth_retries = 0;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_work);
+}
+
+void hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	u32 reg_val;
+
+	if ((HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) ||
+		(HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) {
+		DBG("hdcp inactive or no aksv. returning");
+		return;
+	}
+
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val &= ~HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+
+	/*
+	 * Disable HDCP interrupts.
+	 * Also, need to set the state to inactive here so that any ongoing
+	 * reauth works will know that the HDCP session has been turned off.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Cancel any pending auth/reauth attempts.
+	 * If one is ongoing, this will wait for it to finish.
+	 * No more reauthentication attempts will be scheduled since we
+	 * set the current state to inactive.
+	 */
+	set_bit(AUTH_ABORT_EV, &hdcp_ctrl->auth_event);
+	wake_up_all(&hdcp_ctrl->auth_event_queue);
+	cancel_work_sync(&hdcp_ctrl->hdcp_auth_work);
+	cancel_work_sync(&hdcp_ctrl->hdcp_reauth_work);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET,
+		HDMI_HDCP_RESET_LINK0_DEAUTHENTICATE);
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+
+	/* Enable HPD circuitry */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val |= HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+
+	DBG("HDCP: Off");
+}
+
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = NULL;
+
+	if (!hdmi->qfprom_mmio) {
+		pr_err("%s: HDCP is not supported without qfprom\n",
+			__func__);
+		return ERR_PTR(-EINVAL);
+	}
+
+	hdcp_ctrl = kzalloc(sizeof(*hdcp_ctrl), GFP_KERNEL);
+	if (!hdcp_ctrl)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_WORK(&hdcp_ctrl->hdcp_auth_work, hdmi_hdcp_auth_work);
+	INIT_WORK(&hdcp_ctrl->hdcp_reauth_work, hdmi_hdcp_reauth_work);
+	init_waitqueue_head(&hdcp_ctrl->auth_event_queue);
+	hdcp_ctrl->hdmi = hdmi;
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+	hdcp_ctrl->aksv_valid = false;
+
+	if (scm_is_call_available(SCM_SVC_HDCP, SCM_CMD_HDCP) > 0)
+		hdcp_ctrl->tz_hdcp = true;
+	else
+		hdcp_ctrl->tz_hdcp = false;
+
+	return hdcp_ctrl;
+}
+
+void hdmi_hdcp_destroy(struct hdmi *hdmi)
+{
+	if (hdmi && hdmi->hdcp_ctrl) {
+		kfree(hdmi->hdcp_ctrl);
+		hdmi->hdcp_ctrl = NULL;
+	}
+}
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2015-01-13 20:43           ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2) Jilai Wang
@ 2015-01-30 21:51             ` Bjorn Andersson
  2015-01-30 22:39               ` Bjorn Andersson
  0 siblings, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2015-01-30 21:51 UTC (permalink / raw)
  To: Jilai Wang; +Cc: dri-devel, linux-arm-msm, linux-kernel, Rob Clark

On Tue, Jan 13, 2015 at 12:43 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> Add HDMI HDCP support including HDCP PartI/II/III authentication.
> V1: Initial Change
> V2: Address Bjorn&Rob's comments
>     Refactor the authentication process to use single work instead
>     of multiple work for different authentication stages.
>
Looks cleaner and the SCM parts look good now.

But the ddc communication still makes me wonder, see below.

[..]
> diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c

[..]

> +
> +static int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset,
> +       u8 *data, u16 data_len)
> +{
> +       int rc;
> +       int retry = 5;
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = addr >> 1,
> +                       .flags  = 0,
> +                       .len    = 1,
> +                       .buf    = &offset,
> +               }, {
> +                       .addr   = addr >> 1,
> +                       .flags  = I2C_M_RD,
> +                       .len    = data_len,
> +                       .buf    = data,
> +               }
> +       };
> +
> +       DBG("Start DDC read");
> +retry:
> +       rc = i2c_transfer(hdmi->i2c, msgs, 2);
> +
> +       retry--;
> +       if (rc == 2)
> +               rc = 0;
> +       else if (retry > 0)
> +               goto retry;
> +       else
> +               rc = -EIO;
> +
> +       DBG("End DDC read %d", rc);
> +
> +       return rc;
> +}

Looking back and forth at this, to me this really looks like
i2c_smbus_read_i2c_block_data().

> +
> +#define HDCP_DDC_WRITE_MAX_BYTE_NUM 32

This matches I2C_SMBUS_BLOCK_MAX, indicating further that there's
something in common here.

> +
> +static int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset,
> +       u8 *data, u16 data_len)
> +{
> +       int rc;
> +       int retry = 10;
> +       u8 buf[HDCP_DDC_WRITE_MAX_BYTE_NUM];
> +       struct i2c_msg msgs[] = {
> +               {
> +                       .addr   = addr >> 1,
> +                       .flags  = 0,
> +                       .len    = 1,
> +               }
> +       };
> +
> +       DBG("Start DDC write");
> +       if (data_len > (HDCP_DDC_WRITE_MAX_BYTE_NUM - 1)) {
> +               pr_err("%s: write size too big\n", __func__);
> +               return -ERANGE;
> +       }
> +
> +       buf[0] = offset;
> +       memcpy(&buf[1], data, data_len);
> +       msgs[0].buf = buf;
> +       msgs[0].len = data_len + 1;
> +retry:
> +       rc = i2c_transfer(hdmi->i2c, msgs, 1);
> +
> +       retry--;
> +       if (rc == 1)
> +               rc = 0;
> +       else if (retry > 0)
> +               goto retry;
> +       else
> +               rc = -EIO;
> +
> +       DBG("End DDC write %d", rc);
> +
> +       return rc;
> +}

And this looks like i2c_smbus_write_i2c_block_data()

> +

[..]

> +
> +static int hdmi_hdcp_send_aksv_an(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       u32 link0_aksv_0, link0_aksv_1;
> +       u32 link0_an[2];
> +       u8 aksv[5];
> +
> +       /* Read An0 and An1 */
> +       link0_an[0] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA5);
> +       link0_an[1] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA6);
> +
> +       /* Read AKSV */
> +       link0_aksv_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA3);
> +       link0_aksv_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4);
> +
> +       DBG("Link ASKV=%08x%08x", link0_aksv_0, link0_aksv_1);
> +       /* Copy An and AKSV to byte arrays for transmission */
> +       aksv[0] =  link0_aksv_0        & 0xFF;
> +       aksv[1] = (link0_aksv_0 >> 8)  & 0xFF;
> +       aksv[2] = (link0_aksv_0 >> 16) & 0xFF;
> +       aksv[3] = (link0_aksv_0 >> 24) & 0xFF;
> +       aksv[4] =  link0_aksv_1        & 0xFF;
> +
> +       /* Write An to offset 0x18 */
> +       rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x18, (u8 *)link0_an,
> +               (u16)sizeof(link0_an));

rc = i2c_smbus_write_i2c_block_data(hdmi->i2c, 0x18, sizeof(link0_an),
link0_an);
if (rc < 0) {

> +       if (rc) {
> +               pr_err("%s:An write failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("Link0-An=%08x%08x", link0_an[0], link0_an[1]);
> +
> +       /* Write AKSV to offset 0x10 */
> +       rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x10, aksv, 5);

rc = i2c_smbus_write_i2c_block_data(hdmi->i2c, 0x10, sizeof(aksv), aksv);
if (rc < 0) {

> +       if (rc) {
> +               pr_err("%s:AKSV write failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("Link0-AKSV=%02x%08x", link0_aksv_1 & 0xFF, link0_aksv_0);
> +
> +       return 0;
> +}
> +
> +static int hdmi_hdcp_recv_bksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       u8 bksv[5];
> +       u32 reg[2], data[2];
> +
> +       /* Read BKSV at offset 0x00 */
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x00, bksv, 5);
> +       if (rc) {

You should be able to replace this with:

rc = i2c_smbus_read_i2c_block_data(hdmi->i2c, 0x0, 5, bksv);
if (rc < 0) {

> +               pr_err("%s:BKSV read failed\n", __func__);
> +               return rc;
> +       }
> +
> +       hdcp_ctrl->bksv_lsb = bksv[0] | (bksv[1] << 8) |
> +               (bksv[2] << 16) | (bksv[3] << 24);
> +       hdcp_ctrl->bksv_msb = bksv[4];
> +       DBG(":BKSV=%02x%08x", hdcp_ctrl->bksv_msb, hdcp_ctrl->bksv_lsb);
> +
> +       /* check there are 20 ones in BKSV */
> +       if ((hweight32(hdcp_ctrl->bksv_lsb) + hweight32(hdcp_ctrl->bksv_msb))
> +                       != 20) {
> +               pr_err(": BKSV doesn't have 20 1's and 20 0's\n");
> +               pr_err(": BKSV chk fail. BKSV=%02x%02x%02x%02x%02x\n",
> +                       bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]);
> +               return -EINVAL;
> +       }
> +
> +       /* Write BKSV read from sink to HDCP registers */
> +       reg[0] = REG_HDMI_HDCP_RCVPORT_DATA0;
> +       data[0] = hdcp_ctrl->bksv_lsb;
> +       reg[1] = REG_HDMI_HDCP_RCVPORT_DATA1;
> +       data[1] = hdcp_ctrl->bksv_msb;
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
> +
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_recv_bcaps(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc = 0;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       u32 reg, data;
> +       u8 bcaps;
> +
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);

int bcaps;
bcaps = i2c_smbus_read_byte_data(hdmi->i2c, 0x40);
if (bcaps < 0) {

> +       if (rc) {
> +               pr_err("%s:BCAPS read failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("BCAPS=%02x", bcaps);
> +
> +       /* receiver (0), repeater (1) */
> +       hdcp_ctrl->ds_type = (bcaps & BIT(6)) ? DS_REPEATER : DS_RECEIVER;
> +
> +       /* Write BCAPS to the hardware */
> +       reg = REG_HDMI_HDCP_RCVPORT_DATA12;
> +       data = (u32)bcaps;
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
> +
> +       return rc;
> +}
> +

[..]

> +
> +/* read R0' from sink and pass it to HDCP engine */
> +static int hdmi_hdcp_auth_part1_recv_r0(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       int rc = 0;
> +       u8 buf[2];
> +
> +       /*
> +        * HDCP Compliance Test case 1A-01:
> +        * Wait here at least 100ms before reading R0'
> +        */
> +       rc = hdmi_hdcp_msleep(hdcp_ctrl, 125, AUTH_ABORT_EV);
> +       if (rc)
> +               return rc;
> +
> +       /* Read R0' at offset 0x08 */
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x08, buf, 2);

rc = i2c_smbus_read_word_data(hdmi->i2c, 0x8);
if (rc < 0) {

}

> +       if (rc) {
> +               pr_err("%s:R0' read failed\n", __func__);
> +               return rc;
> +       }
> +       DBG("R0'=%02x%02x", buf[1], buf[0]);
> +
> +       /* Write R0' to HDCP registers and check to see if it is a match */
> +       hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA2_0,
> +               (((u32)buf[1]) << 8) | buf[0]);

hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA2_0, rc);

> +
> +       return 0;
> +}
> +

[..]

> +
> +static int hdmi_hdcp_recv_check_bstatus(struct hdmi_hdcp_ctrl *hdcp_ctrl,
> +       u16 *pbstatus)
> +{
> +       int rc;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       bool max_devs_exceeded = false, max_cascade_exceeded = false;
> +       u32 repeater_cascade_depth = 0, down_stream_devices = 0;
> +       u16 bstatus;
> +       u8 buf[2];
> +
> +       /* Read BSTATUS at offset 0x41 */
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x41, buf, 2);

rc = i2c_smbus_read_word_data(hdmi->i2c, 0x41);
if (rc < 0) {

}

*pbstatus = bstatus = rc;

> +       if (rc) {
> +               pr_err("%s: BSTATUS read failed\n", __func__);
> +               goto error;
> +       }
> +       *pbstatus = bstatus = (buf[1] << 8) | buf[0];
> +
> +
> +       down_stream_devices = bstatus & 0x7F;
> +       repeater_cascade_depth = (bstatus >> 8) & 0x7;
> +       max_devs_exceeded = (bstatus & BIT(7)) ? true : false;
> +       max_cascade_exceeded = (bstatus & BIT(11)) ? true : false;
> +
> +       if (down_stream_devices == 0) {
> +               /*
> +                * If no downstream devices are attached to the repeater
> +                * then part II fails.
> +                * todo: The other approach would be to continue PART II.
> +                */
> +               pr_err("%s: No downstream devices\n", __func__);
> +               rc = -EINVAL;
> +               goto error;
> +       }
> +
> +       /*
> +        * HDCP Compliance 1B-05:
> +        * Check if no. of devices connected to repeater
> +        * exceed max_devices_connected from bit 7 of Bstatus.
> +        */
> +       if (max_devs_exceeded) {
> +               pr_err("%s: no. of devs connected exceeds max allowed",
> +                       __func__);
> +               rc = -EINVAL;
> +               goto error;
> +       }
> +
> +       /*
> +        * HDCP Compliance 1B-06:
> +        * Check if no. of cascade connected to repeater
> +        * exceed max_cascade_connected from bit 11 of Bstatus.
> +        */
> +       if (max_cascade_exceeded) {
> +               pr_err("%s: no. of cascade conn exceeds max allowed",
> +                       __func__);
> +               rc = -EINVAL;
> +               goto error;
> +       }
> +
> +error:
> +       hdcp_ctrl->dev_count = down_stream_devices;
> +       hdcp_ctrl->max_cascade_exceeded = max_cascade_exceeded;
> +       hdcp_ctrl->max_dev_exceeded = max_devs_exceeded;
> +       hdcp_ctrl->depth = repeater_cascade_depth;
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_auth_part2_wait_ksv_fifo_ready(
> +       struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       u32 reg, data;
> +       u32 timeout_count;
> +       u16 bstatus;
> +       u8 bcaps;
> +
> +       /*
> +        * Wait until READY bit is set in BCAPS, as per HDCP specifications
> +        * maximum permitted time to check for READY bit is five seconds.
> +        */
> +       timeout_count = 100;
> +       do {
> +               /* Read BCAPS at offset 0x40 */
> +               rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);

rc = i2c_smbus_read_byte_data(hdmi->i2c, 0x40);
if (rc < 0) {

}
bcaps = rc;

> +               if (rc) {
> +                       pr_err("%s: BCAPS read failed\n", __func__);
> +                       return rc;
> +               }
> +
> +               if (bcaps & BIT(5))
> +                       break;
> +
> +               timeout_count--;
> +               if (!timeout_count) {
> +                       pr_err("%s: Wait KSV fifo ready timedout", __func__);
> +                       return -ETIMEDOUT;
> +               }
> +
> +               rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
> +               if (rc)
> +                       return rc;
> +       } while (1);
> +
> +       rc = hdmi_hdcp_recv_check_bstatus(hdcp_ctrl, &bstatus);
> +       if (rc) {
> +               pr_err("%s: bstatus error\n", __func__);
> +               return rc;
> +       }
> +
> +       /* Write BSTATUS and BCAPS to HDCP registers */
> +       reg = REG_HDMI_HDCP_RCVPORT_DATA12;
> +       data = bcaps | (bstatus << 8);
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
> +       if (rc) {
> +               pr_err("%s: BSTATUS write failed\n", __func__);
> +               return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +/*
> + * hdcp authenticating part 2: 2nd
> + * read ksv fifo from sink
> + * transfer V' from sink to HDCP engine
> + * reset SHA engine
> + */
> +static int hdmi_hdcp_transfer_v_h(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       int rc = 0;
> +       struct hdmi_hdcp_reg_data reg_data[]  = {
> +               {REG_HDMI_HDCP_RCVPORT_DATA7,  0x20, "V' H0"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA8,  0x24, "V' H1"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA9,  0x28, "V' H2"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA10, 0x2C, "V' H3"},
> +               {REG_HDMI_HDCP_RCVPORT_DATA11, 0x30, "V' H4"},
> +       };
> +       struct hdmi_hdcp_reg_data *rd;
> +       u32 size = ARRAY_SIZE(reg_data);
> +       u32 reg[ARRAY_SIZE(reg_data)];
> +       u32 data[ARRAY_SIZE(reg_data)];
> +       int i;
> +
> +       for (i = 0; i < size; i++) {
> +               rd = &reg_data[i];
> +               rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR,
> +                       rd->off, (u8 *)&data[i], (u16)sizeof(data[i]));

rc = i2c_smbus_read_i2c_block_data(hdmi->i2c, rd->off, sizeof(u32), &data[i]);
if (rc < 0) {

> +               if (rc) {
> +                       pr_err("%s: Read %s failed\n", __func__, rd->name);
> +                       goto error;
> +               }
> +
> +               DBG("%s =%x", rd->name, data[i]);
> +               reg[i] = reg_data[i].reg_id;
> +       }
> +
> +       rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, size);
> +
> +error:
> +       return rc;
> +}
> +
> +static int hdmi_hdcp_recv_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
> +{
> +       int rc;
> +       struct hdmi *hdmi = hdcp_ctrl->hdmi;
> +       u32 ksv_bytes;
> +
> +       ksv_bytes = 5 * hdcp_ctrl->dev_count;
> +
> +       rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x43,
> +               hdcp_ctrl->ksv_list, ksv_bytes);

rc = i2c_smbus_read_i2c_block_data(hdmi->i2c, 0x43, ksv_bytes,
hdcp_ctrl->ksv_list);
if (rc < 0) {

> +       if (rc)
> +               pr_err("%s: KSV FIFO read failed\n", __func__);
> +
> +       return rc;
> +}
> +

Maybe I'm missundersanding how smbus or ddc works, please let me know
what you think.

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2015-01-30 21:51             ` Bjorn Andersson
@ 2015-01-30 22:39               ` Bjorn Andersson
  2015-02-10 19:34                 ` Bjorn Andersson
  0 siblings, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2015-01-30 22:39 UTC (permalink / raw)
  To: Jilai Wang; +Cc: dri-devel, linux-arm-msm, linux-kernel, Rob Clark

On Fri, Jan 30, 2015 at 1:51 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
> On Tue, Jan 13, 2015 at 12:43 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
>> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>> V1: Initial Change
>> V2: Address Bjorn&Rob's comments
>>     Refactor the authentication process to use single work instead
>>     of multiple work for different authentication stages.
>>
> Looks cleaner and the SCM parts look good now.
>
> But the ddc communication still makes me wonder, see below.
>

Rob made me aware about the fact that the hdmi driver is both
implementing a driver for the i2c controller and now for the hdcp
client and hence doesn't have an i2c_client handle.

So unless this is redesigned to be split in a separate i2c client
driver we probably have to have the ddc functions like this.

I'm fine with this as is for now.

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2015-01-30 22:39               ` Bjorn Andersson
@ 2015-02-10 19:34                 ` Bjorn Andersson
  2015-02-11 18:59                   ` jilaiw
  0 siblings, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2015-02-10 19:34 UTC (permalink / raw)
  To: Jilai Wang; +Cc: dri-devel, linux-arm-msm, linux-kernel, Rob Clark

On Fri, Jan 30, 2015 at 2:39 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
> On Fri, Jan 30, 2015 at 1:51 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
>> On Tue, Jan 13, 2015 at 12:43 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
>>> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>>> V1: Initial Change
>>> V2: Address Bjorn&Rob's comments
>>>     Refactor the authentication process to use single work instead
>>>     of multiple work for different authentication stages.
>>>
>> Looks cleaner and the SCM parts look good now.
>>
>> But the ddc communication still makes me wonder, see below.
>>
>
> Rob made me aware about the fact that the hdmi driver is both
> implementing a driver for the i2c controller and now for the hdcp
> client and hence doesn't have an i2c_client handle.
>
> So unless this is redesigned to be split in a separate i2c client
> driver we probably have to have the ddc functions like this.
>
> I'm fine with this as is for now.
>

After digging some more in the i2c stack I found others using
i2c_new_dummy() to create a "dummy" i2c client from and adaptor and an
address.
By introducing that we could make hdmi->i2c an actual i2c_client and
use the client api in here.

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2015-02-10 19:34                 ` Bjorn Andersson
@ 2015-02-11 18:59                   ` jilaiw
  2015-02-11 22:23                     ` Bjorn Andersson
  0 siblings, 1 reply; 15+ messages in thread
From: jilaiw @ 2015-02-11 18:59 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: Jilai Wang, dri-devel, linux-arm-msm, linux-kernel, Rob Clark

There is one issue to use i2c_smbus_XX functions:
i2c_smbus_read_i2c_block_data has limitation with the maximum count 
I2C_SMBUS_BLOCK_MAX.
But in function hdmi_hdcp_recv_ksv_fifo, since the downstream ksv_fifo
size will exceed this limitation and must be read in a single transaction,
we can't use this function then.

> On Fri, Jan 30, 2015 at 2:39 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
>> On Fri, Jan 30, 2015 at 1:51 PM, Bjorn Andersson <bjorn@kryo.se> wrote:
>>> On Tue, Jan 13, 2015 at 12:43 PM, Jilai Wang <jilaiw@codeaurora.org>
>>> wrote:
>>>> Add HDMI HDCP support including HDCP PartI/II/III authentication.
>>>> V1: Initial Change
>>>> V2: Address Bjorn&Rob's comments
>>>>     Refactor the authentication process to use single work instead
>>>>     of multiple work for different authentication stages.
>>>>
>>> Looks cleaner and the SCM parts look good now.
>>>
>>> But the ddc communication still makes me wonder, see below.
>>>
>>
>> Rob made me aware about the fact that the hdmi driver is both
>> implementing a driver for the i2c controller and now for the hdcp
>> client and hence doesn't have an i2c_client handle.
>>
>> So unless this is redesigned to be split in a separate i2c client
>> driver we probably have to have the ddc functions like this.
>>
>> I'm fine with this as is for now.
>>
>
> After digging some more in the i2c stack I found others using
> i2c_new_dummy() to create a "dummy" i2c client from and adaptor and an
> address.
> By introducing that we could make hdmi->i2c an actual i2c_client and
> use the client api in here.
>
> Regards,
> Bjorn
>



^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2)
  2015-02-11 18:59                   ` jilaiw
@ 2015-02-11 22:23                     ` Bjorn Andersson
  2015-04-02 21:49                       ` [PATCH] drm/msm/hdmi: add hdmi hdcp support (V3) Jilai Wang
  0 siblings, 1 reply; 15+ messages in thread
From: Bjorn Andersson @ 2015-02-11 22:23 UTC (permalink / raw)
  To: Jilai Wang, Wolfram Sang
  Cc: dri-devel, linux-arm-msm, linux-kernel, Rob Clark

On Wed, Feb 11, 2015 at 10:59 AM,  <jilaiw@codeaurora.org> wrote:
> There is one issue to use i2c_smbus_XX functions:
> i2c_smbus_read_i2c_block_data has limitation with the maximum count
> I2C_SMBUS_BLOCK_MAX.
> But in function hdmi_hdcp_recv_ksv_fifo, since the downstream ksv_fifo
> size will exceed this limitation and must be read in a single transaction,
> we can't use this function then.
>

That's interesting, because we have the same problem in the Synaptics
driver, where we want to read more than 32 bytes with a method that
follows the smbus flow.

Wolfram, do you have any input on making i2c_smbus* functions work for
larger accesses? Or should we just conclude that it's not "smbus" and
open code this in the various places?

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH] drm/msm/hdmi: add hdmi hdcp support (V3)
  2015-02-11 22:23                     ` Bjorn Andersson
@ 2015-04-02 21:49                       ` Jilai Wang
  0 siblings, 0 replies; 15+ messages in thread
From: Jilai Wang @ 2015-04-02 21:49 UTC (permalink / raw)
  To: dri-devel; +Cc: linux-arm-msm, linux-kernel, robdclark, Jilai Wang

Add HDMI HDCP support including HDCP PartI/II/III authentication.
V1: Initial Change
V2: Address Bjorn&Rob's comments
    Refactor the authentication process to use single work instead
    of multiple work for different authentication stages.
V3: Update to align with qcom SCM api.

Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
---
 drivers/gpu/drm/msm/Makefile              |    1 +
 drivers/gpu/drm/msm/hdmi/hdmi.c           |   45 +-
 drivers/gpu/drm/msm/hdmi/hdmi.h           |   31 +
 drivers/gpu/drm/msm/hdmi/hdmi_audio.c     |    1 -
 drivers/gpu/drm/msm/hdmi/hdmi_bridge.c    |    8 +-
 drivers/gpu/drm/msm/hdmi/hdmi_connector.c |    7 +-
 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c      | 1437 +++++++++++++++++++++++++++++
 7 files changed, 1522 insertions(+), 8 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 674a132..be1f7ff 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -9,6 +9,7 @@ msm-y := \
 	hdmi/hdmi_audio.o \
 	hdmi/hdmi_bridge.o \
 	hdmi/hdmi_connector.o \
+	hdmi/hdmi_hdcp.o \
 	hdmi/hdmi_i2c.o \
 	hdmi/hdmi_phy_8960.o \
 	hdmi/hdmi_phy_8x60.o \
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c
index 8145362..049723d 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.c
@@ -22,7 +22,9 @@
 void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 {
 	uint32_t ctrl = 0;
+	unsigned long flags;
 
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	if (power_on) {
 		ctrl |= HDMI_CTRL_ENABLE;
 		if (!hdmi->hdmi_mode) {
@@ -37,6 +39,7 @@ void hdmi_set_mode(struct hdmi *hdmi, bool power_on)
 	}
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 	DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
 			power_on ? "Enable" : "Disable", ctrl);
 }
@@ -51,6 +54,10 @@ static irqreturn_t hdmi_irq(int irq, void *dev_id)
 	/* Process DDC: */
 	hdmi_i2c_irq(hdmi->i2c);
 
+	/* Process HDCP: */
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_irq(hdmi->hdcp_ctrl);
+
 	/* TODO audio.. */
 
 	return IRQ_HANDLED;
@@ -60,6 +67,15 @@ static void hdmi_destroy(struct hdmi *hdmi)
 {
 	struct hdmi_phy *phy = hdmi->phy;
 
+	/*
+	 * at this point, hpd has been disabled,
+	 * after flush workq, it's safe to deinit hdcp
+	 */
+	if (hdmi->workq) {
+		flush_workqueue(hdmi->workq);
+		destroy_workqueue(hdmi->workq);
+	}
+	hdmi_hdcp_destroy(hdmi);
 	if (phy)
 		phy->funcs->destroy(phy);
 
@@ -77,6 +93,7 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 {
 	struct hdmi_platform_config *config = pdev->dev.platform_data;
 	struct hdmi *hdmi = NULL;
+	struct resource *res;
 	int i, ret;
 
 	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
@@ -87,6 +104,7 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 
 	hdmi->pdev = pdev;
 	hdmi->config = config;
+	spin_lock_init(&hdmi->reg_lock);
 
 	/* not sure about which phy maps to which msm.. probably I miss some */
 	if (config->phy_init)
@@ -107,6 +125,18 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		goto fail;
 	}
 
+	/* HDCP needs physical address of hdmi register */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		config->mmio_name);
+	hdmi->mmio_phy_addr = res->start;
+
+	hdmi->qfprom_mmio = msm_ioremap(pdev,
+		config->qfprom_mmio_name, "HDMI_QFPROM");
+	if (IS_ERR(hdmi->qfprom_mmio)) {
+		dev_info(&pdev->dev, "can't find qfprom resource\n");
+		hdmi->qfprom_mmio = NULL;
+	}
+
 	hdmi->hpd_regs = devm_kzalloc(&pdev->dev, sizeof(hdmi->hpd_regs[0]) *
 			config->hpd_reg_cnt, GFP_KERNEL);
 	if (!hdmi->hpd_regs) {
@@ -189,6 +219,8 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		hdmi->pwr_clks[i] = clk;
 	}
 
+	hdmi->workq = alloc_ordered_workqueue("msm_hdmi", 0);
+
 	hdmi->i2c = hdmi_i2c_init(hdmi);
 	if (IS_ERR(hdmi->i2c)) {
 		ret = PTR_ERR(hdmi->i2c);
@@ -197,6 +229,12 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
 		goto fail;
 	}
 
+	hdmi->hdcp_ctrl = hdmi_hdcp_init(hdmi);
+	if (IS_ERR(hdmi->hdcp_ctrl)) {
+		dev_warn(&pdev->dev, "failed to init hdcp: disabled\n");
+		hdmi->hdcp_ctrl = NULL;
+	}
+
 	return hdmi;
 
 fail:
@@ -376,6 +414,7 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	}
 
 	hdmi_cfg->mmio_name     = "core_physical";
+	hdmi_cfg->qfprom_mmio_name = "qfprom_physical";
 	hdmi_cfg->ddc_clk_gpio  = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-clk");
 	hdmi_cfg->ddc_data_gpio = get_gpio(dev, of_node, "qcom,hdmi-tx-ddc-data");
 	hdmi_cfg->hpd_gpio      = get_gpio(dev, of_node, "qcom,hdmi-tx-hpd");
@@ -391,7 +430,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	if (cpu_is_apq8064()) {
 		static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
 		config.phy_init      = hdmi_phy_8960_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -404,7 +442,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 	} else if (cpu_is_msm8960() || cpu_is_msm8960ab()) {
 		static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
 		config.phy_init      = hdmi_phy_8960_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -419,7 +456,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 				"8901_hdmi_mvs", "8901_mpp0"
 		};
 		config.phy_init      = hdmi_phy_8x60_init;
-		config.mmio_name     = "hdmi_msm_hdmi_addr";
 		config.hpd_reg_names = hpd_reg_names;
 		config.hpd_reg_cnt   = ARRAY_SIZE(hpd_reg_names);
 		config.hpd_clk_names = hpd_clk_names;
@@ -430,6 +466,9 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
 		config.mux_en_gpio   = -1;
 		config.mux_sel_gpio  = -1;
 	}
+	config.mmio_name     = "hdmi_msm_hdmi_addr";
+	config.qfprom_mmio_name = "hdmi_msm_qfprom_addr";
+
 	hdmi_cfg = &config;
 #endif
 	dev->platform_data = hdmi_cfg;
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h
index 68fdfb3..588f7a6 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.h
@@ -37,6 +37,8 @@ struct hdmi_audio {
 	int rate;
 };
 
+struct hdmi_hdcp_ctrl;
+
 struct hdmi {
 	struct drm_device *dev;
 	struct platform_device *pdev;
@@ -51,6 +53,8 @@ struct hdmi {
 	unsigned long int pixclock;
 
 	void __iomem *mmio;
+	void __iomem *qfprom_mmio;
+	phys_addr_t mmio_phy_addr;
 
 	struct regulator **hpd_regs;
 	struct regulator **pwr_regs;
@@ -68,12 +72,25 @@ struct hdmi {
 	bool hdmi_mode;               /* are we in hdmi mode? */
 
 	int irq;
+	struct workqueue_struct *workq;
+
+	struct hdmi_hdcp_ctrl *hdcp_ctrl;
+
+	/*
+	* spinlock to protect registers shared by different execution
+	* REG_HDMI_CTRL
+	* REG_HDMI_DDC_ARBITRATION
+	* REG_HDMI_HDCP_INT_CTRL
+	* REG_HDMI_HPD_CTRL
+	*/
+	spinlock_t reg_lock;
 };
 
 /* platform config data (ie. from DT, or pdata) */
 struct hdmi_platform_config {
 	struct hdmi_phy *(*phy_init)(struct hdmi *hdmi);
 	const char *mmio_name;
+	const char *qfprom_mmio_name;
 
 	/* regulators that need to be on for hpd: */
 	const char **hpd_reg_names;
@@ -109,6 +126,11 @@ static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
 	return msm_readl(hdmi->mmio + reg);
 }
 
+static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
+{
+	return msm_readl(hdmi->qfprom_mmio + reg);
+}
+
 /*
  * The phy appears to be different, for example between 8960 and 8x60,
  * so split the phy related functions out and load the correct one at
@@ -163,4 +185,13 @@ void hdmi_i2c_irq(struct i2c_adapter *i2c);
 void hdmi_i2c_destroy(struct i2c_adapter *i2c);
 struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi);
 
+/*
+ * hdcp
+ */
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi);
+void hdmi_hdcp_destroy(struct hdmi *hdmi);
+void hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+void hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl);
+
 #endif /* __HDMI_CONNECTOR_H__ */
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
index 872485f..df232e2 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_audio.c
@@ -203,7 +203,6 @@ int hdmi_audio_update(struct hdmi *hdmi)
 		audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4);
 		audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE;
 	} else {
-		hdmi_write(hdmi, REG_HDMI_GC, HDMI_GC_MUTE);
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT;
 		acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND;
 		vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE;
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
index a7a1d82..1dd093e 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
@@ -102,6 +102,9 @@ static void hdmi_bridge_pre_enable(struct drm_bridge *bridge)
 
 	phy->funcs->powerup(phy, hdmi->pixclock);
 	hdmi_set_mode(hdmi, true);
+
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_on(hdmi->hdcp_ctrl);
 }
 
 static void hdmi_bridge_enable(struct drm_bridge *bridge)
@@ -118,6 +121,9 @@ static void hdmi_bridge_post_disable(struct drm_bridge *bridge)
 	struct hdmi *hdmi = hdmi_bridge->hdmi;
 	struct hdmi_phy *phy = hdmi->phy;
 
+	if (hdmi->hdcp_ctrl)
+		hdmi_hdcp_off(hdmi->hdcp_ctrl);
+
 	DBG("power down");
 	hdmi_set_mode(hdmi, false);
 	phy->funcs->powerdown(phy);
@@ -142,8 +148,6 @@ static void hdmi_bridge_mode_set(struct drm_bridge *bridge,
 
 	hdmi->pixclock = mode->clock * 1000;
 
-	hdmi->hdmi_mode = drm_match_cea_mode(mode) > 1;
-
 	hstart = mode->htotal - mode->hsync_start;
 	hend   = mode->htotal - mode->hsync_start + mode->hdisplay;
 
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
index b62cdb9..302ba63 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_connector.c
@@ -140,6 +140,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 	struct hdmi_phy *phy = hdmi->phy;
 	uint32_t hpd_ctrl;
 	int i, ret;
+	unsigned long flags;
 
 	for (i = 0; i < config->hpd_reg_cnt; i++) {
 		ret = regulator_enable(hdmi->hpd_regs[i]);
@@ -185,6 +186,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			HDMI_HPD_INT_CTRL_INT_EN);
 
 	/* set timeout to 4.1ms (max) for hardware debounce */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
 	hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
 	hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
 
@@ -193,6 +195,7 @@ static int hpd_enable(struct hdmi_connector *hdmi_connector)
 			~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
 	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
 			HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
 
 	return 0;
 
@@ -239,7 +242,6 @@ hotplug_work(struct work_struct *work)
 void hdmi_connector_irq(struct drm_connector *connector)
 {
 	struct hdmi_connector *hdmi_connector = to_hdmi_connector(connector);
-	struct msm_drm_private *priv = connector->dev->dev_private;
 	struct hdmi *hdmi = hdmi_connector->hdmi;
 	uint32_t hpd_int_status, hpd_int_ctrl;
 
@@ -263,7 +265,7 @@ void hdmi_connector_irq(struct drm_connector *connector)
 			hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
 		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
 
-		queue_work(priv->wq, &hdmi_connector->hpd_work);
+		queue_work(hdmi->workq, &hdmi_connector->hpd_work);
 	}
 }
 
@@ -339,6 +341,7 @@ static int hdmi_connector_get_modes(struct drm_connector *connector)
 
 	hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
 
+	hdmi->hdmi_mode = drm_detect_hdmi_monitor(edid);
 	drm_mode_connector_update_edid_property(connector, edid);
 
 	if (edid) {
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
new file mode 100644
index 0000000..1dc9c34
--- /dev/null
+++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c
@@ -0,0 +1,1437 @@
+/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "hdmi.h"
+#include <linux/qcom_scm.h>
+
+#define HDCP_REG_ENABLE 0x01
+#define HDCP_REG_DISABLE 0x00
+#define HDCP_PORT_ADDR 0x74
+
+#define HDCP_INT_STATUS_MASK ( \
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_XFER_REQ_INT | \
+		HDMI_HDCP_INT_CTRL_AUTH_XFER_DONE_INT)
+
+#define AUTH_WORK_RETRIES_TIME 100
+#define AUTH_RETRIES_TIME 30
+
+/* QFPROM Registers for HDMI/HDCP */
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_LSB  0x000000F8
+#define QFPROM_RAW_FEAT_CONFIG_ROW0_MSB  0x000000FC
+#define HDCP_KSV_LSB                     0x000060D8
+#define HDCP_KSV_MSB                     0x000060DC
+
+enum DS_TYPE {  /* type of downstream device */
+	DS_UNKNOWN,
+	DS_RECEIVER,
+	DS_REPEATER,
+};
+
+enum hdmi_hdcp_state {
+	HDCP_STATE_NO_AKSV,
+	HDCP_STATE_INACTIVE,
+	HDCP_STATE_AUTHENTICATING,
+	HDCP_STATE_AUTHENTICATED,
+	HDCP_STATE_AUTH_FAILED
+};
+
+struct hdmi_hdcp_reg_data {
+	u32 reg_id;
+	u32 off;
+	char *name;
+	u32 reg_val;
+};
+
+struct hdmi_hdcp_ctrl {
+	struct hdmi *hdmi;
+	u32 auth_retries;
+	bool tz_hdcp;
+	enum hdmi_hdcp_state hdcp_state;
+	struct work_struct hdcp_auth_work;
+	struct work_struct hdcp_reauth_work;
+
+#define AUTH_ABORT_EV 1
+#define AUTH_RESULT_RDY_EV 2
+	unsigned long auth_event;
+	wait_queue_head_t auth_event_queue;
+
+	u32 ksv_fifo_w_index;
+	/*
+	 * store aksv from qfprom
+	 */
+	u32 aksv_lsb;
+	u32 aksv_msb;
+	bool aksv_valid;
+	u32 ds_type;
+	u32 bksv_lsb;
+	u32 bksv_msb;
+	u8 dev_count;
+	u8 depth;
+	u8 ksv_list[5 * 127];
+	bool max_cascade_exceeded;
+	bool max_dev_exceeded;
+};
+
+static int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset,
+	u8 *data, u16 data_len)
+{
+	int rc;
+	int retry = 5;
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+			.buf	= &offset,
+		}, {
+			.addr	= addr >> 1,
+			.flags	= I2C_M_RD,
+			.len	= data_len,
+			.buf	= data,
+		}
+	};
+
+	DBG("Start DDC read");
+retry:
+	rc = i2c_transfer(hdmi->i2c, msgs, 2);
+
+	retry--;
+	if (rc == 2)
+		rc = 0;
+	else if (retry > 0)
+		goto retry;
+	else
+		rc = -EIO;
+
+	DBG("End DDC read %d", rc);
+
+	return rc;
+}
+
+#define HDCP_DDC_WRITE_MAX_BYTE_NUM 32
+
+static int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset,
+	u8 *data, u16 data_len)
+{
+	int rc;
+	int retry = 10;
+	u8 buf[HDCP_DDC_WRITE_MAX_BYTE_NUM];
+	struct i2c_msg msgs[] = {
+		{
+			.addr	= addr >> 1,
+			.flags	= 0,
+			.len	= 1,
+		}
+	};
+
+	DBG("Start DDC write");
+	if (data_len > (HDCP_DDC_WRITE_MAX_BYTE_NUM - 1)) {
+		pr_err("%s: write size too big\n", __func__);
+		return -ERANGE;
+	}
+
+	buf[0] = offset;
+	memcpy(&buf[1], data, data_len);
+	msgs[0].buf = buf;
+	msgs[0].len = data_len + 1;
+retry:
+	rc = i2c_transfer(hdmi->i2c, msgs, 1);
+
+	retry--;
+	if (rc == 1)
+		rc = 0;
+	else if (retry > 0)
+		goto retry;
+	else
+		rc = -EIO;
+
+	DBG("End DDC write %d", rc);
+
+	return rc;
+}
+
+static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, u32 *preg,
+	u32 *pdata, u32 count)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	struct qcom_scm_hdcp_req scm_buf[QCOM_SCM_HDCP_MAX_REQ_CNT];
+	u32 resp, phy_addr, idx = 0;
+	int i, ret = 0;
+
+	WARN_ON(!pdata || !preg || (count == 0));
+
+	if (hdcp_ctrl->tz_hdcp) {
+		phy_addr = (u32)hdmi->mmio_phy_addr;
+
+		while (count) {
+			memset(scm_buf, 0, sizeof(scm_buf));
+			for (i = 0; i < count && i < QCOM_SCM_HDCP_MAX_REQ_CNT;
+				i++) {
+				scm_buf[i].addr = phy_addr + preg[idx];
+				scm_buf[i].val  = pdata[idx];
+				idx++;
+			}
+			ret = qcom_scm_hdcp_req(scm_buf, i, &resp);
+
+			if (ret || resp) {
+				pr_err("%s: error: scm_call ret=%d resp=%u\n",
+					__func__, ret, resp);
+				ret = -EINVAL;
+				break;
+			}
+
+			count -= i;
+		}
+	} else {
+		for (i = 0; i < count; i++)
+			hdmi_write(hdmi, preg[i], pdata[i]);
+	}
+
+	return ret;
+}
+
+void hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val, hdcp_int_status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_INT_CTRL);
+	hdcp_int_status = reg_val & HDCP_INT_STATUS_MASK;
+	if (!hdcp_int_status) {
+		spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+		return;
+	}
+	/* Clear Interrupts */
+	reg_val |= hdcp_int_status << 1;
+	/* Clear AUTH_FAIL_INFO as well */
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT)
+		reg_val |= HDMI_HDCP_INT_CTRL_AUTH_FAIL_INFO_ACK;
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	DBG("hdcp irq %x", hdcp_int_status);
+
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_INT) {
+		pr_info("%s:AUTH_SUCCESS_INT received\n", __func__);
+		if (HDCP_STATE_AUTHENTICATING == hdcp_ctrl->hdcp_state) {
+			set_bit(AUTH_RESULT_RDY_EV, &hdcp_ctrl->auth_event);
+			wake_up_all(&hdcp_ctrl->auth_event_queue);
+		}
+	}
+
+	if (hdcp_int_status & HDMI_HDCP_INT_CTRL_AUTH_FAIL_INT) {
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		pr_info("%s: AUTH_FAIL_INT rcvd, LINK0_STATUS=0x%08x\n",
+			__func__, reg_val);
+		if (HDCP_STATE_AUTHENTICATED == hdcp_ctrl->hdcp_state)
+			queue_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work);
+		else if (HDCP_STATE_AUTHENTICATING ==
+				hdcp_ctrl->hdcp_state) {
+			set_bit(AUTH_RESULT_RDY_EV, &hdcp_ctrl->auth_event);
+			wake_up_all(&hdcp_ctrl->auth_event_queue);
+		}
+	}
+}
+
+static int hdmi_hdcp_msleep(struct hdmi_hdcp_ctrl *hdcp_ctrl, u32 ms, u32 ev)
+{
+	int rc;
+
+	rc = wait_event_timeout(hdcp_ctrl->auth_event_queue,
+		!!test_bit(ev, &hdcp_ctrl->auth_event),
+		msecs_to_jiffies(ms));
+	if (rc) {
+		pr_info("%s: msleep is canceled by event %d\n",
+				__func__, ev);
+		clear_bit(ev, &hdcp_ctrl->auth_event);
+		return -ECANCELED;
+	}
+
+	return 0;
+}
+
+static int hdmi_hdcp_read_validate_aksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	/* Fetch aksv from QFPROM, this info should be public. */
+	hdcp_ctrl->aksv_lsb = hdmi_qfprom_read(hdmi, HDCP_KSV_LSB);
+	hdcp_ctrl->aksv_msb = hdmi_qfprom_read(hdmi, HDCP_KSV_MSB);
+
+	/* check there are 20 ones in AKSV */
+	if ((hweight32(hdcp_ctrl->aksv_lsb) + hweight32(hdcp_ctrl->aksv_msb))
+			!= 20) {
+		pr_err("%s: AKSV QFPROM doesn't have 20 1's, 20 0's\n",
+			__func__);
+		pr_err("%s: QFPROM AKSV chk failed (AKSV=%02x%08x)\n",
+			__func__, hdcp_ctrl->aksv_msb,
+			hdcp_ctrl->aksv_lsb);
+		return -EINVAL;
+	}
+	DBG("AKSV=%02x%08x", hdcp_ctrl->aksv_msb, hdcp_ctrl->aksv_lsb);
+
+	return 0;
+}
+
+static int reset_hdcp_ddc_failures(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val, failure, nack0;
+	int rc = 0;
+
+	/* Check for any DDC transfer failures */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+	failure = reg_val & HDMI_HDCP_DDC_STATUS_FAILED;
+	nack0 = reg_val & HDMI_HDCP_DDC_STATUS_NACK0;
+	DBG("HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d",
+		reg_val, failure, nack0);
+
+	if (failure) {
+		/*
+		 * Indicates that the last HDCP HW DDC transfer failed.
+		 * This occurs when a transfer is attempted with HDCP DDC
+		 * disabled (HDCP_DDC_DISABLE=1) or the number of retries
+		 * matches HDCP_DDC_RETRY_CNT.
+		 * Failure occurred,  let's clear it.
+		 */
+		DBG("DDC failure detected");
+
+		/* First, Disable DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0,
+			HDMI_HDCP_DDC_CTRL_0_DISABLE);
+
+		/* ACK the Failure to Clear it */
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_CTRL_1);
+		reg_val |= HDMI_HDCP_DDC_CTRL_1_FAILED_ACK;
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_1, reg_val);
+
+		/* Check if the FAILURE got Cleared */
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		if (reg_val & HDMI_HDCP_DDC_STATUS_FAILED)
+			pr_info("%s: Unable to clear HDCP DDC Failure\n",
+				__func__);
+
+		/* Re-Enable HDCP DDC */
+		hdmi_write(hdmi, REG_HDMI_HDCP_DDC_CTRL_0, 0);
+	}
+
+	if (nack0) {
+		DBG("Before: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+		/* Reset HDMI DDC software status */
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val |= HDMI_DDC_CTRL_SW_STATUS_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val &= ~HDMI_DDC_CTRL_SW_STATUS_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		/* Reset HDMI DDC Controller */
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val |= HDMI_DDC_CTRL_SOFT_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+
+		/* If previous msleep is aborted, skip this msleep */
+		if (!rc)
+			rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+
+		reg_val = hdmi_read(hdmi, REG_HDMI_DDC_CTRL);
+		reg_val &= ~HDMI_DDC_CTRL_SOFT_RESET;
+		hdmi_write(hdmi, REG_HDMI_DDC_CTRL, reg_val);
+		DBG("After: HDMI_DDC_SW_STATUS=0x%08x",
+			hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS));
+	}
+
+	return rc;
+}
+
+static int hdmi_hdcp_hw_ddc_clean(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 hdcp_ddc_status, ddc_hw_status;
+	u32 xfer_done, xfer_req, hw_done;
+	bool hw_not_ready;
+	u32 timeout_count;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+
+	if (hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS) == 0)
+		return 0;
+
+	/* Wait to be clean on DDC HW engine */
+	timeout_count = 100;
+	do {
+		hdcp_ddc_status = hdmi_read(hdmi, REG_HDMI_HDCP_DDC_STATUS);
+		ddc_hw_status = hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS);
+
+		xfer_done = hdcp_ddc_status & HDMI_HDCP_DDC_STATUS_XFER_DONE;
+		xfer_req = hdcp_ddc_status & HDMI_HDCP_DDC_STATUS_XFER_REQ;
+		hw_done = ddc_hw_status & HDMI_DDC_HW_STATUS_DONE;
+		hw_not_ready = !xfer_done || xfer_req || !hw_done;
+
+		if (hw_not_ready)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_warn("%s: hw_ddc_clean failed\n", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static void hdmi_hdcp_reauth_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_reauth_work);
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	u32 reg_val;
+
+	DBG("HDCP REAUTH WORK");
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val &= ~HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+
+	/* Disable HDCP interrupts */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET,
+		HDMI_HDCP_RESET_LINK0_DEAUTHENTICATE);
+
+	/* Wait to be clean on DDC HW engine */
+	if (hdmi_hdcp_hw_ddc_clean(hdcp_ctrl)) {
+		pr_info("%s: reauth work aborted\n", __func__);
+		return;
+	}
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	/* Enable HPD circuitry */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val |= HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Only retry defined times then abort current authenticating process
+	 */
+	if (++hdcp_ctrl->auth_retries == AUTH_RETRIES_TIME) {
+		hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+		hdcp_ctrl->auth_retries = 0;
+		pr_info("%s: abort reauthentication!\n", __func__);
+
+		return;
+	}
+
+	DBG("Queue AUTH WORK");
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_work);
+}
+
+static int hdmi_hdcp_auth_prepare(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	u32 reg_val;
+	unsigned long flags;
+	int rc;
+
+	if (!hdcp_ctrl->aksv_valid) {
+		rc = hdmi_hdcp_read_validate_aksv(hdcp_ctrl);
+		if (rc) {
+			pr_err("%s: ASKV validation failed\n", __func__);
+			hdcp_ctrl->hdcp_state = HDCP_STATE_NO_AKSV;
+			return -ENOTSUPP;
+		}
+		hdcp_ctrl->aksv_valid = true;
+	}
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	/* disable HDMI Encrypt */
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+
+	/* Enabling Software DDC */
+	reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	reg_val &= ~HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Write AKSV read from QFPROM to the HDCP registers.
+	 * This step is needed for HDCP authentication and must be
+	 * written before enabling HDCP.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_LOWER_AKSV, hdcp_ctrl->aksv_lsb);
+	hdmi_write(hdmi, REG_HDMI_HDCP_SW_UPPER_AKSV, hdcp_ctrl->aksv_msb);
+
+	/*
+	 * HDCP setup prior to enabling HDCP_CTRL.
+	 * Setup seed values for random number An.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL0, 0xB1FFB0FF);
+	hdmi_write(hdmi, REG_HDMI_HDCP_ENTROPY_CTRL1, 0xF00DFACE);
+
+	/* Disable the RngCipher state */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL);
+	reg_val &= ~HDMI_HDCP_DEBUG_CTRL_RNG_CIPHER;
+	hdmi_write(hdmi, REG_HDMI_HDCP_DEBUG_CTRL, reg_val);
+	DBG("HDCP_DEBUG_CTRL=0x%08x",
+		hdmi_read(hdmi, REG_HDMI_HDCP_DEBUG_CTRL));
+
+	/*
+	 * Ensure that all register writes are completed before
+	 * enabling HDCP cipher
+	 */
+	wmb();
+
+	/*
+	 * Enable HDCP
+	 * This needs to be done as early as possible in order for the
+	 * hardware to make An available to read
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, HDMI_HDCP_CTRL_ENABLE);
+
+	/*
+	 * If we had stale values for the An ready bit, it should most
+	 * likely be cleared now after enabling HDCP cipher
+	 */
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	DBG("After enabling HDCP Link0_Status=0x%08x", link0_status);
+	if (!(link0_status &
+		(HDMI_HDCP_LINK0_STATUS_AN_0_READY |
+		HDMI_HDCP_LINK0_STATUS_AN_1_READY)))
+		DBG("An not ready after enabling HDCP");
+
+	/* Clear any DDC failures from previous tries before enable HDCP*/
+	rc = reset_hdcp_ddc_failures(hdcp_ctrl);
+
+	return rc;
+}
+
+static void hdmi_hdcp_auth_fail(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	DBG("hdcp auth failed, queue reauth work");
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTH_FAILED;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_reauth_work);
+}
+
+static void hdmi_hdcp_auth_done(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	/*
+	 * Disable software DDC before going into part3 to make sure
+	 * there is no Arbitration between software and hardware for DDC
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_DDC_ARBITRATION);
+	reg_val |= HDMI_DDC_ARBITRATION_HW_ARBITRATION;
+	hdmi_write(hdmi, REG_HDMI_DDC_ARBITRATION, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/* enable HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val |= HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATED;
+	hdcp_ctrl->auth_retries = 0;
+}
+
+/*
+ * hdcp authenticating part 1
+ * Wait Key/An ready
+ * Read BCAPS from sink
+ * Write BCAPS and AKSV into HDCP engine
+ * Write An and AKSV to sink
+ * Read BKSV from sink and write into HDCP engine
+ */
+static int hdmi_hdcp_wait_key_an_ready(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status, keys_state;
+	u32 timeout_count;
+	bool an_ready;
+
+	/* Wait for HDCP keys to be checked and validated */
+	timeout_count = 100;
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		keys_state = (link0_status >> 28) & 0x7;
+		if (keys_state == HDCP_KEYS_STATE_VALID)
+			break;
+
+		DBG("Keys not ready(%d). s=%d, l0=%0x08x",
+			timeout_count, keys_state, link0_status);
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait key state timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	timeout_count = 100;
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		an_ready = (link0_status & HDMI_HDCP_LINK0_STATUS_AN_0_READY)
+			&& (link0_status & HDMI_HDCP_LINK0_STATUS_AN_1_READY);
+		if (an_ready)
+			break;
+
+		DBG("An not ready(%d). l0_status=0x%08x",
+			timeout_count, link0_status);
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait An timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static int hdmi_hdcp_send_aksv_an(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_aksv_0, link0_aksv_1;
+	u32 link0_an[2];
+	u8 aksv[5];
+
+	/* Read An0 and An1 */
+	link0_an[0] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA5);
+	link0_an[1] = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA6);
+
+	/* Read AKSV */
+	link0_aksv_0 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA3);
+	link0_aksv_1 = hdmi_read(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4);
+
+	DBG("Link ASKV=%08x%08x", link0_aksv_0, link0_aksv_1);
+	/* Copy An and AKSV to byte arrays for transmission */
+	aksv[0] =  link0_aksv_0        & 0xFF;
+	aksv[1] = (link0_aksv_0 >> 8)  & 0xFF;
+	aksv[2] = (link0_aksv_0 >> 16) & 0xFF;
+	aksv[3] = (link0_aksv_0 >> 24) & 0xFF;
+	aksv[4] =  link0_aksv_1        & 0xFF;
+
+	/* Write An to offset 0x18 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x18, (u8 *)link0_an,
+		(u16)sizeof(link0_an));
+	if (rc) {
+		pr_err("%s:An write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-An=%08x%08x", link0_an[0], link0_an[1]);
+
+	/* Write AKSV to offset 0x10 */
+	rc = hdmi_ddc_write(hdmi, HDCP_PORT_ADDR, 0x10, aksv, 5);
+	if (rc) {
+		pr_err("%s:AKSV write failed\n", __func__);
+		return rc;
+	}
+	DBG("Link0-AKSV=%02x%08x", link0_aksv_1 & 0xFF, link0_aksv_0);
+
+	return 0;
+}
+
+static int hdmi_hdcp_recv_bksv(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u8 bksv[5];
+	u32 reg[2], data[2];
+
+	/* Read BKSV at offset 0x00 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x00, bksv, 5);
+	if (rc) {
+		pr_err("%s:BKSV read failed\n", __func__);
+		return rc;
+	}
+
+	hdcp_ctrl->bksv_lsb = bksv[0] | (bksv[1] << 8) |
+		(bksv[2] << 16) | (bksv[3] << 24);
+	hdcp_ctrl->bksv_msb = bksv[4];
+	DBG(":BKSV=%02x%08x", hdcp_ctrl->bksv_msb, hdcp_ctrl->bksv_lsb);
+
+	/* check there are 20 ones in BKSV */
+	if ((hweight32(hdcp_ctrl->bksv_lsb) + hweight32(hdcp_ctrl->bksv_msb))
+			!= 20) {
+		pr_err(": BKSV doesn't have 20 1's and 20 0's\n");
+		pr_err(": BKSV chk fail. BKSV=%02x%02x%02x%02x%02x\n",
+			bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]);
+		return -EINVAL;
+	}
+
+	/* Write BKSV read from sink to HDCP registers */
+	reg[0] = REG_HDMI_HDCP_RCVPORT_DATA0;
+	data[0] = hdcp_ctrl->bksv_lsb;
+	reg[1] = REG_HDMI_HDCP_RCVPORT_DATA1;
+	data[1] = hdcp_ctrl->bksv_msb;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static int hdmi_hdcp_recv_bcaps(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg, data;
+	u8 bcaps;
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);
+	if (rc) {
+		pr_err("%s:BCAPS read failed\n", __func__);
+		return rc;
+	}
+	DBG("BCAPS=%02x", bcaps);
+
+	/* receiver (0), repeater (1) */
+	hdcp_ctrl->ds_type = (bcaps & BIT(6)) ? DS_REPEATER : DS_RECEIVER;
+
+	/* Write BCAPS to the hardware */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = (u32)bcaps;
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part1_key_exchange(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	int rc;
+
+	/* Wait for AKSV key and An ready */
+	rc = hdmi_hdcp_wait_key_an_ready(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: wait key and an ready failed\n", __func__);
+		return rc;
+	};
+
+	/* Read BCAPS and send to HDCP engine */
+	rc = hdmi_hdcp_recv_bcaps(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: read bcaps error, abort\n", __func__);
+		return rc;
+	}
+
+	/*
+	 * 1.1_Features turned off by default.
+	 * No need to write AInfo since 1.1_Features is disabled.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA4, 0);
+
+	/* Send AKSV and An to sink */
+	rc = hdmi_hdcp_send_aksv_an(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:An/Aksv write failed\n", __func__);
+		return rc;
+	}
+
+	/* Read BKSV and send to HDCP engine*/
+	rc = hdmi_hdcp_recv_bksv(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s:BKSV Process failed\n", __func__);
+		return rc;
+	}
+
+	/* Enable HDCP interrupts and ack/clear any stale interrupts */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL,
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_ACK |
+		HDMI_HDCP_INT_CTRL_AUTH_SUCCESS_MASK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_ACK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_MASK |
+		HDMI_HDCP_INT_CTRL_AUTH_FAIL_INFO_ACK);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	return 0;
+}
+
+/* read R0' from sink and pass it to HDCP engine */
+static int hdmi_hdcp_auth_part1_recv_r0(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	int rc = 0;
+	u8 buf[2];
+
+	/*
+	 * HDCP Compliance Test case 1A-01:
+	 * Wait here at least 100ms before reading R0'
+	 */
+	rc = hdmi_hdcp_msleep(hdcp_ctrl, 125, AUTH_ABORT_EV);
+	if (rc)
+		return rc;
+
+	/* Read R0' at offset 0x08 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x08, buf, 2);
+	if (rc) {
+		pr_err("%s:R0' read failed\n", __func__);
+		return rc;
+	}
+	DBG("R0'=%02x%02x", buf[1], buf[0]);
+
+	/* Write R0' to HDCP registers and check to see if it is a match */
+	hdmi_write(hdmi, REG_HDMI_HDCP_RCVPORT_DATA2_0,
+		(((u32)buf[1]) << 8) | buf[0]);
+
+	return 0;
+}
+
+/* Wait for authenticating result: R0/R0' are matched or not */
+static int hdmi_hdcp_auth_part1_verify_r0(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	int rc;
+
+	/* wait for hdcp irq, 10 sec should be long enough */
+	rc = hdmi_hdcp_msleep(hdcp_ctrl, 10000, AUTH_RESULT_RDY_EV);
+	if (!rc) {
+		pr_err("%s: Wait Auth IRQ timeout\n", __func__);
+		return -ETIMEDOUT;
+	}
+
+	link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+	if (!(link0_status & HDMI_HDCP_LINK0_STATUS_RI_MATCHES)) {
+		pr_err("%s: Authentication Part I failed\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Enable HDCP Encryption */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL,
+		HDMI_HDCP_CTRL_ENABLE |
+		HDMI_HDCP_CTRL_ENCRYPTION_ENABLE);
+
+	return 0;
+}
+
+static int hdmi_hdcp_recv_check_bstatus(struct hdmi_hdcp_ctrl *hdcp_ctrl,
+	u16 *pbstatus)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	bool max_devs_exceeded = false, max_cascade_exceeded = false;
+	u32 repeater_cascade_depth = 0, down_stream_devices = 0;
+	u16 bstatus;
+	u8 buf[2];
+
+	/* Read BSTATUS at offset 0x41 */
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x41, buf, 2);
+	if (rc) {
+		pr_err("%s: BSTATUS read failed\n", __func__);
+		goto error;
+	}
+	*pbstatus = bstatus = (buf[1] << 8) | buf[0];
+
+
+	down_stream_devices = bstatus & 0x7F;
+	repeater_cascade_depth = (bstatus >> 8) & 0x7;
+	max_devs_exceeded = (bstatus & BIT(7)) ? true : false;
+	max_cascade_exceeded = (bstatus & BIT(11)) ? true : false;
+
+	if (down_stream_devices == 0) {
+		/*
+		 * If no downstream devices are attached to the repeater
+		 * then part II fails.
+		 * todo: The other approach would be to continue PART II.
+		 */
+		pr_err("%s: No downstream devices\n", __func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-05:
+	 * Check if no. of devices connected to repeater
+	 * exceed max_devices_connected from bit 7 of Bstatus.
+	 */
+	if (max_devs_exceeded) {
+		pr_err("%s: no. of devs connected exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * HDCP Compliance 1B-06:
+	 * Check if no. of cascade connected to repeater
+	 * exceed max_cascade_connected from bit 11 of Bstatus.
+	 */
+	if (max_cascade_exceeded) {
+		pr_err("%s: no. of cascade conn exceeds max allowed",
+			__func__);
+		rc = -EINVAL;
+		goto error;
+	}
+
+error:
+	hdcp_ctrl->dev_count = down_stream_devices;
+	hdcp_ctrl->max_cascade_exceeded = max_cascade_exceeded;
+	hdcp_ctrl->max_dev_exceeded = max_devs_exceeded;
+	hdcp_ctrl->depth = repeater_cascade_depth;
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part2_wait_ksv_fifo_ready(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg, data;
+	u32 timeout_count;
+	u16 bstatus;
+	u8 bcaps;
+
+	/*
+	 * Wait until READY bit is set in BCAPS, as per HDCP specifications
+	 * maximum permitted time to check for READY bit is five seconds.
+	 */
+	timeout_count = 100;
+	do {
+		/* Read BCAPS at offset 0x40 */
+		rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x40, &bcaps, 1);
+		if (rc) {
+			pr_err("%s: BCAPS read failed\n", __func__);
+			return rc;
+		}
+
+		if (bcaps & BIT(5))
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Wait KSV fifo ready timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	rc = hdmi_hdcp_recv_check_bstatus(hdcp_ctrl, &bstatus);
+	if (rc) {
+		pr_err("%s: bstatus error\n", __func__);
+		return rc;
+	}
+
+	/* Write BSTATUS and BCAPS to HDCP registers */
+	reg = REG_HDMI_HDCP_RCVPORT_DATA12;
+	data = bcaps | (bstatus << 8);
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+	if (rc) {
+		pr_err("%s: BSTATUS write failed\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * hdcp authenticating part 2: 2nd
+ * read ksv fifo from sink
+ * transfer V' from sink to HDCP engine
+ * reset SHA engine
+ */
+static int hdmi_hdcp_transfer_v_h(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	int rc = 0;
+	struct hdmi_hdcp_reg_data reg_data[]  = {
+		{REG_HDMI_HDCP_RCVPORT_DATA7,  0x20, "V' H0"},
+		{REG_HDMI_HDCP_RCVPORT_DATA8,  0x24, "V' H1"},
+		{REG_HDMI_HDCP_RCVPORT_DATA9,  0x28, "V' H2"},
+		{REG_HDMI_HDCP_RCVPORT_DATA10, 0x2C, "V' H3"},
+		{REG_HDMI_HDCP_RCVPORT_DATA11, 0x30, "V' H4"},
+	};
+	struct hdmi_hdcp_reg_data *rd;
+	u32 size = ARRAY_SIZE(reg_data);
+	u32 reg[ARRAY_SIZE(reg_data)];
+	u32 data[ARRAY_SIZE(reg_data)];
+	int i;
+
+	for (i = 0; i < size; i++) {
+		rd = &reg_data[i];
+		rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR,
+			rd->off, (u8 *)&data[i], (u16)sizeof(data[i]));
+		if (rc) {
+			pr_err("%s: Read %s failed\n", __func__, rd->name);
+			goto error;
+		}
+
+		DBG("%s =%x", rd->name, data[i]);
+		reg[i] = reg_data[i].reg_id;
+	}
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, size);
+
+error:
+	return rc;
+}
+
+static int hdmi_hdcp_recv_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 ksv_bytes;
+
+	ksv_bytes = 5 * hdcp_ctrl->dev_count;
+
+	rc = hdmi_ddc_read(hdmi, HDCP_PORT_ADDR, 0x43,
+		hdcp_ctrl->ksv_list, ksv_bytes);
+	if (rc)
+		pr_err("%s: KSV FIFO read failed\n", __func__);
+
+	return rc;
+}
+
+static int hdmi_hdcp_reset_sha_engine(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	u32 reg[2], data[2];
+	u32 rc  = 0;
+
+	reg[0] = REG_HDMI_HDCP_SHA_CTRL;
+	data[0] = HDCP_REG_ENABLE;
+	reg[1] = REG_HDMI_HDCP_SHA_CTRL;
+	data[1] = HDCP_REG_DISABLE;
+
+	rc = hdmi_hdcp_scm_wr(hdcp_ctrl, reg, data, 2);
+
+	return rc;
+}
+
+static int hdmi_hdcp_auth_part2_recv_ksv_fifo(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 timeout_count;
+
+	/*
+	 * Read KSV FIFO over DDC
+	 * Key Selection vector FIFO Used to pull downstream KSVs
+	 * from HDCP Repeaters.
+	 * All bytes (DEVICE_COUNT * 5) must be read in a single,
+	 * auto incrementing access.
+	 * All bytes read as 0x00 for HDCP Receivers that are not
+	 * HDCP Repeaters (REPEATER == 0).
+	 */
+	timeout_count = 100;
+	do {
+		rc = hdmi_hdcp_recv_ksv_fifo(hdcp_ctrl);
+		if (!rc)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Recv ksv fifo timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 25, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	rc = hdmi_hdcp_transfer_v_h(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: transfer V failed\n", __func__);
+		return rc;
+	}
+
+	/* reset SHA engine before write ksv fifo */
+	rc = hdmi_hdcp_reset_sha_engine(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: fail to reset sha engine\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Write KSV FIFO to HDCP_SHA_DATA.
+ * This is done 1 byte at time starting with the LSB.
+ * Once 64 bytes have been written, we need to poll for
+ * HDCP_SHA_BLOCK_DONE before writing any further
+ * If the last byte is written, we need to poll for
+ * HDCP_SHA_COMP_DONE to wait until HW finish
+ */
+static int hdmi_hdcp_write_ksv_fifo(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int i;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 ksv_bytes, last_byte = 0;
+	u8 *ksv_fifo = NULL;
+	u32 reg_val, data, reg;
+	u32 rc  = 0;
+
+	ksv_bytes  = 5 * hdcp_ctrl->dev_count;
+
+	/* Check if need to wait for HW completion */
+	if (hdcp_ctrl->ksv_fifo_w_index) {
+		reg_val = hdmi_read(hdmi, REG_HDMI_HDCP_SHA_STATUS);
+		DBG("HDCP_SHA_STATUS=%08x", reg_val);
+		if (hdcp_ctrl->ksv_fifo_w_index == ksv_bytes) {
+			/* check COMP_DONE if last write */
+			if (reg_val & HDMI_HDCP_SHA_STATUS_COMP_DONE) {
+				DBG("COMP_DONE");
+				return 0;
+			} else {
+				return -EAGAIN;
+			}
+		} else {
+			/* check BLOCK_DONE if not last write */
+			if (!(reg_val & HDMI_HDCP_SHA_STATUS_BLOCK_DONE))
+				return -EAGAIN;
+
+			DBG("BLOCK_DONE");
+		}
+	}
+
+	ksv_bytes  -= hdcp_ctrl->ksv_fifo_w_index;
+	if (ksv_bytes <= 64)
+		last_byte = 1;
+	else
+		ksv_bytes = 64;
+
+	ksv_fifo = hdcp_ctrl->ksv_list;
+	ksv_fifo += hdcp_ctrl->ksv_fifo_w_index;
+
+	for (i = 0; i < ksv_bytes; i++) {
+		/* Write KSV byte and set DONE bit[0] for last byte*/
+		reg_val = ksv_fifo[i] << 16;
+		if ((i == (ksv_bytes - 1)) && last_byte)
+			reg_val |= HDMI_HDCP_SHA_DATA_DONE;
+
+		reg = REG_HDMI_HDCP_SHA_DATA;
+		data = reg_val;
+		rc = hdmi_hdcp_scm_wr(hdcp_ctrl, &reg, &data, 1);
+
+		if (rc)
+			return rc;
+	}
+
+	hdcp_ctrl->ksv_fifo_w_index += ksv_bytes;
+
+	/*
+	 *return -EAGAIN to notify caller to wait for COMP_DONE or BLOCK_DONE
+	 */
+	return -EAGAIN;
+}
+
+/* write ksv fifo into HDCP engine */
+static int hdmi_hdcp_auth_part2_write_ksv_fifo(
+	struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc;
+	u32 timeout_count;
+
+	hdcp_ctrl->ksv_fifo_w_index = 0;
+	timeout_count = 100;
+	do {
+		rc = hdmi_hdcp_write_ksv_fifo(hdcp_ctrl);
+		if (!rc)
+			break;
+
+		if (rc != -EAGAIN)
+			return rc;
+
+		timeout_count--;
+		if (!timeout_count) {
+			pr_err("%s: Write KSV fifo timedout", __func__);
+			return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static int hdmi_hdcp_auth_part2_check_v_match(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	int rc = 0;
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 link0_status;
+	u32 timeout_count = 100;
+
+	do {
+		link0_status = hdmi_read(hdmi, REG_HDMI_HDCP_LINK0_STATUS);
+		if (link0_status & HDMI_HDCP_LINK0_STATUS_V_MATCHES)
+			break;
+
+		timeout_count--;
+		if (!timeout_count) {
+				pr_err("%s: HDCP V Match timedout", __func__);
+				return -ETIMEDOUT;
+		}
+
+		rc = hdmi_hdcp_msleep(hdcp_ctrl, 20, AUTH_ABORT_EV);
+		if (rc)
+			return rc;
+	} while (1);
+
+	return 0;
+}
+
+static void hdmi_hdcp_auth_work(struct work_struct *work)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = container_of(work,
+		struct hdmi_hdcp_ctrl, hdcp_auth_work);
+	int rc;
+
+	rc = hdmi_hdcp_auth_prepare(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: auth prepare failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	/* HDCP PartI */
+	rc = hdmi_hdcp_auth_part1_key_exchange(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: key exchange failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part1_recv_r0(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: receive r0 failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part1_verify_r0(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: verify r0 failed %d\n", __func__, rc);
+		goto end;
+	}
+	pr_info("%s: Authentication Part I successful\n", __func__);
+	if (hdcp_ctrl->ds_type == DS_RECEIVER)
+		goto end;
+
+	/* HDCP PartII */
+	rc = hdmi_hdcp_auth_part2_wait_ksv_fifo_ready(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: wait ksv fifo ready failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_recv_ksv_fifo(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: recv ksv fifo failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_write_ksv_fifo(hdcp_ctrl);
+	if (rc) {
+		pr_err("%s: write ksv fifo failed %d\n", __func__, rc);
+		goto end;
+	}
+
+	rc = hdmi_hdcp_auth_part2_check_v_match(hdcp_ctrl);
+	if (rc)
+		pr_err("%s: check v match failed %d\n", __func__, rc);
+
+end:
+	if (rc == -ECANCELED) {
+		pr_info("%s: hdcp authentication canceled\n", __func__);
+	} else if (rc == -ENOTSUPP) {
+		pr_info("%s: hdcp is not supported\n", __func__);
+	} else if (rc) {
+		pr_err("%s: hdcp authentication failed\n", __func__);
+		hdmi_hdcp_auth_fail(hdcp_ctrl);
+	} else {
+		hdmi_hdcp_auth_done(hdcp_ctrl);
+	}
+}
+
+void hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	u32 reg_val;
+	unsigned long flags;
+
+	if ((HDCP_STATE_INACTIVE != hdcp_ctrl->hdcp_state) ||
+		(HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) {
+		DBG("still active or activating or no askv. returning");
+		return;
+	}
+
+	/* clear HDMI Encrypt */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->auth_event = 0;
+	hdcp_ctrl->hdcp_state = HDCP_STATE_AUTHENTICATING;
+	hdcp_ctrl->auth_retries = 0;
+	queue_work(hdmi->workq, &hdcp_ctrl->hdcp_auth_work);
+}
+
+void hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl)
+{
+	struct hdmi *hdmi = hdcp_ctrl->hdmi;
+	unsigned long flags;
+	u32 reg_val;
+
+	if ((HDCP_STATE_INACTIVE == hdcp_ctrl->hdcp_state) ||
+		(HDCP_STATE_NO_AKSV == hdcp_ctrl->hdcp_state)) {
+		DBG("hdcp inactive or no aksv. returning");
+		return;
+	}
+
+	/*
+	 * Disable HPD circuitry.
+	 * This is needed to reset the HDCP cipher engine so that when we
+	 * attempt a re-authentication, HW would clear the AN0_READY and
+	 * AN1_READY bits in HDMI_HDCP_LINK0_STATUS register
+	 */
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val &= ~HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+
+	/*
+	 * Disable HDCP interrupts.
+	 * Also, need to set the state to inactive here so that any ongoing
+	 * reauth works will know that the HDCP session has been turned off.
+	 */
+	hdmi_write(hdmi, REG_HDMI_HDCP_INT_CTRL, 0);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	/*
+	 * Cancel any pending auth/reauth attempts.
+	 * If one is ongoing, this will wait for it to finish.
+	 * No more reauthentication attempts will be scheduled since we
+	 * set the current state to inactive.
+	 */
+	set_bit(AUTH_ABORT_EV, &hdcp_ctrl->auth_event);
+	wake_up_all(&hdcp_ctrl->auth_event_queue);
+	cancel_work_sync(&hdcp_ctrl->hdcp_auth_work);
+	cancel_work_sync(&hdcp_ctrl->hdcp_reauth_work);
+
+	hdmi_write(hdmi, REG_HDMI_HDCP_RESET,
+		HDMI_HDCP_RESET_LINK0_DEAUTHENTICATE);
+
+	/* Disable encryption and disable the HDCP block */
+	hdmi_write(hdmi, REG_HDMI_HDCP_CTRL, 0);
+
+	spin_lock_irqsave(&hdmi->reg_lock, flags);
+	reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+	reg_val &= ~HDMI_CTRL_ENCRYPTED;
+	hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+
+	/* Enable HPD circuitry */
+	reg_val = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
+	reg_val |= HDMI_HPD_CTRL_ENABLE;
+	hdmi_write(hdmi, REG_HDMI_HPD_CTRL, reg_val);
+	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
+
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+
+	DBG("HDCP: Off");
+}
+
+struct hdmi_hdcp_ctrl *hdmi_hdcp_init(struct hdmi *hdmi)
+{
+	struct hdmi_hdcp_ctrl *hdcp_ctrl = NULL;
+
+	if (!hdmi->qfprom_mmio) {
+		pr_err("%s: HDCP is not supported without qfprom\n",
+			__func__);
+		return ERR_PTR(-EINVAL);
+	}
+
+	hdcp_ctrl = kzalloc(sizeof(*hdcp_ctrl), GFP_KERNEL);
+	if (!hdcp_ctrl)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_WORK(&hdcp_ctrl->hdcp_auth_work, hdmi_hdcp_auth_work);
+	INIT_WORK(&hdcp_ctrl->hdcp_reauth_work, hdmi_hdcp_reauth_work);
+	init_waitqueue_head(&hdcp_ctrl->auth_event_queue);
+	hdcp_ctrl->hdmi = hdmi;
+	hdcp_ctrl->hdcp_state = HDCP_STATE_INACTIVE;
+	hdcp_ctrl->aksv_valid = false;
+
+	if (qcom_scm_hdcp_available())
+		hdcp_ctrl->tz_hdcp = true;
+	else
+		hdcp_ctrl->tz_hdcp = false;
+
+	return hdcp_ctrl;
+}
+
+void hdmi_hdcp_destroy(struct hdmi *hdmi)
+{
+	if (hdmi && hdmi->hdcp_ctrl) {
+		kfree(hdmi->hdcp_ctrl);
+		hdmi->hdcp_ctrl = NULL;
+	}
+}
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2015-04-02 21:49 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-01 21:56 [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Jilai Wang
2014-12-03  1:46 ` Bjorn Andersson
2014-12-03 16:42   ` Rob Clark
2014-12-03 17:16     ` jilaiw
2014-12-03 17:20       ` Rob Clark
2014-12-03 17:32       ` Bjorn Andersson
2014-12-03 17:49         ` jilaiw
2015-01-13 20:43           ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support (V2) Jilai Wang
2015-01-30 21:51             ` Bjorn Andersson
2015-01-30 22:39               ` Bjorn Andersson
2015-02-10 19:34                 ` Bjorn Andersson
2015-02-11 18:59                   ` jilaiw
2015-02-11 22:23                     ` Bjorn Andersson
2015-04-02 21:49                       ` [PATCH] drm/msm/hdmi: add hdmi hdcp support (V3) Jilai Wang
2014-12-03 16:51 ` [PATCH 2/2] drm/msm/hdmi: add hdmi hdcp support Rob Clark

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).