From 9d940175bbffc82b5ec70b195312c6f32b35f51f Mon Sep 17 00:00:00 2001
From: Sandor Yu <Sandor.yu@nxp.com>
Date: Wed, 30 Dec 2020 16:07:41 +0800
Subject: [PATCH 20/49] MLK-25199-5: drm: bridge: mhdp_hdcp: add HDMI TX HDCP
 driver

This patch adds an initial HDMI TX HDCP driver
for Cadence MHDP HDMI TX hardware.

Both HDCP2.2 and HDCP1.4 are supported.

HDCP function could be enabled by command:
modetest -w CONNECTOR_ID:"Content Protection":1

Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
Reviewed-by: Robby Cai <robby.cai@nxp.com>
---
 drivers/gpu/drm/bridge/cadence/Kconfig        |    4 +
 drivers/gpu/drm/bridge/cadence/Makefile       |    1 +
 .../gpu/drm/bridge/cadence/cdns-hdmi-core.c   |  190 ++-
 .../gpu/drm/bridge/cadence/cdns-hdmi-hdcp.c   | 1167 +++++++++++++++++
 .../gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c   |  300 +++++
 .../gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h   |   36 +
 include/drm/bridge/cdns-mhdp.h                |   92 +-
 7 files changed, 1776 insertions(+), 14 deletions(-)
 create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-hdmi-hdcp.c
 create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c
 create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h

diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig
index c271ab24a99a..4c27836eb367 100644
--- a/drivers/gpu/drm/bridge/cadence/Kconfig
+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
@@ -43,6 +43,11 @@ config DRM_CDNS_AUDIO
 	tristate "Cadence MHDP Audio driver"
 	depends on DRM_CDNS_MHDP
 
+config DRM_CDNS_HDMI_HDCP
+	tristate "Cadence MHDP HDMI HDCP driver"
+	depends on DRM_CDNS_HDMI
+	select DRM_DISPLAY_HDCP_HELPER
+
 config DRM_CDNS_HDMI_CEC
 	tristate "Cadence MHDP HDMI CEC driver"
 	select CEC_CORE
diff --git a/drivers/gpu/drm/bridge/cadence/Makefile b/drivers/gpu/drm/bridge/cadence/Makefile
index 618290870ba5..1b824252ae76 100644
--- a/drivers/gpu/drm/bridge/cadence/Makefile
+++ b/drivers/gpu/drm/bridge/cadence/Makefile
@@ -8,6 +8,7 @@ cdns_mhdp_drmcore-y := cdns-mhdp-common.o cdns-mhdp-dp.o cdns-mhdp-hdmi.o
 cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o
 cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o
 cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_AUDIO) += cdns-mhdp-audio.o
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI_HDCP) += cdns-mhdp-hdcp.o cdns-hdmi-hdcp.o
 cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI_CEC) += cdns-mhdp-cec.o
 
 obj-$(CONFIG_DRM_CDNS_MHDP)		+= cdns_mhdp_drmcore.o
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
index 84c175997740..dc393f6b75e7 100644
--- a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
@@ -1,7 +1,7 @@
 /*
  * Cadence High-Definition Multimedia Interface (HDMI) driver
  *
- * Copyright (C) 2019-2020 NXP Semiconductor, Inc.
+ * Copyright (C) 2019-2021 NXP Semiconductor, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -13,6 +13,7 @@
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder_slave.h>
+#include <drm/display/drm_hdcp.h>
 #include <drm/drm_of.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_print.h>
@@ -27,6 +28,131 @@
 #include <linux/mutex.h>
 #include <linux/of_device.h>
 
+#include "cdns-mhdp-hdcp.h"
+
+static ssize_t HDCPTX_do_reauth_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_do_reauth = __ATTR_WO(HDCPTX_do_reauth);
+
+static ssize_t HDCPTX_do_reauth_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count)
+{
+    int value, ret;
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+	ret = cdns_mhdp_hdcp_tx_reauth(mhdp, 1);
+
+	sscanf(buf, "%d", &value);
+
+    if (ret < 0) {
+		dev_err(dev, "%s cdns_mhdp_hdcp_tx_reauth failed\n", __func__);
+		return -1;
+	}
+	return count;
+}
+
+static ssize_t HDCPTX_Version_show(struct device *dev,
+			struct device_attribute *attr, char *buf);
+static ssize_t HDCPTX_Version_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_Version = __ATTR_RW(HDCPTX_Version);
+
+static ssize_t HDCPTX_Version_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+	int value;
+
+	sscanf(buf, "%d", &value);
+	if (value == 2)
+		mhdp->hdcp.config = 2;
+	else if (value == 1)
+		mhdp->hdcp.config = 1;
+	else if (value == 3)
+		mhdp->hdcp.config = 3;
+	else
+		mhdp->hdcp.config = 0;
+
+	return count;
+}
+
+ssize_t HDCPTX_Version_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+	return sprintf(buf, "%d\n", mhdp->hdcp.config);
+}
+
+static ssize_t HDCPTX_Status_show(struct device *dev,
+			struct device_attribute *attr, char *buf);
+static ssize_t HDCPTX_Status_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_Status = __ATTR_RW(HDCPTX_Status);
+
+ssize_t HDCPTX_Status_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+	switch (mhdp->hdcp.state) {
+	case HDCP_STATE_NO_AKSV:
+		return sprintf(buf, "%d :HDCP_STATE_NO_AKSV \n", mhdp->hdcp.state);
+	case HDCP_STATE_INACTIVE:
+		return sprintf(buf, "%d :HDCP_STATE_INACTIVE \n", mhdp->hdcp.state);
+	case HDCP_STATE_ENABLING:
+		return sprintf(buf, "%d :HDCP_STATE_ENABLING \n", mhdp->hdcp.state);
+	case HDCP_STATE_AUTHENTICATING:
+		return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATING \n", mhdp->hdcp.state);
+	case HDCP_STATE_AUTHENTICATED:
+		return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATED \n", mhdp->hdcp.state);
+	case HDCP_STATE_DISABLING:
+		return sprintf(buf, "%d :HDCP_STATE_DISABLING \n", mhdp->hdcp.state);
+	case HDCP_STATE_AUTH_FAILED:
+		return sprintf(buf, "%d :HDCP_STATE_AUTH_FAILED \n", mhdp->hdcp.state);
+	default:
+		return sprintf(buf, "%d :HDCP_STATE don't exist \n", mhdp->hdcp.state);
+    }
+}
+
+ssize_t HDCPTX_Status_store(struct device *dev,
+			struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+	int value;
+
+	if (count == 2) {
+		sscanf(buf, "%d", &value);
+		if ((value >= HDCP_STATE_NO_AKSV) && (value <= HDCP_STATE_AUTH_FAILED)) {
+			mhdp->hdcp.state = value;
+			return count;
+		} else {
+			dev_err(dev, "%s &hdp->state invalid\n", __func__);
+			return -1;
+		}
+	}
+
+	dev_info(dev, "%s &hdp->state desired %s count=%d\n ", __func__, buf, (int)count);
+
+    if (strncmp(buf, "HDCP_STATE_NO_AKSV", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_NO_AKSV;
+    else if (strncmp(buf, "HDCP_STATE_INACTIVE", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+    else if (strncmp(buf, "HDCP_STATE_ENABLING", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_ENABLING;
+    else if (strncmp(buf, "HDCP_STATE_AUTHENTICATING", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING;
+    else if (strncmp(buf, "HDCP_STATE_AUTHENTICATED", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED;
+    else if (strncmp(buf, "HDCP_STATE_DISABLING", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_DISABLING;
+    else if (strncmp(buf, "HDCP_STATE_AUTH_FAILED", count - 1) == 0)
+		mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+    else
+		dev_err(dev, "%s &hdp->state invalid\n", __func__);
+		return -1;
+    return count;
+}
+
 static void hdmi_sink_config(struct cdns_mhdp_device *mhdp)
 {
 	struct drm_scdc *scdc = &mhdp->connector.base.display_info.hdmi.scdc;
@@ -319,6 +445,22 @@ static bool blob_equal(const struct drm_property_blob *a,
 	return !a == !b;
 }
 
+static void cdns_hdmi_bridge_disable(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+	cdns_hdmi_hdcp_disable(mhdp);
+}
+
+static void cdns_hdmi_bridge_enable(struct drm_bridge *bridge)
+{
+	struct cdns_mhdp_device *mhdp = bridge->driver_private;
+	struct drm_connector_state *conn_state = mhdp->connector.base.state;
+
+	if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED)
+		cdns_hdmi_hdcp_enable(mhdp);
+}
+
 static int cdns_hdmi_connector_atomic_check(struct drm_connector *connector,
 					    struct drm_atomic_state *state)
 {
@@ -329,12 +471,17 @@ static int cdns_hdmi_connector_atomic_check(struct drm_connector *connector,
 	struct drm_crtc *crtc = new_con_state->crtc;
 	struct drm_crtc_state *new_crtc_state;
 
+	cdns_hdmi_hdcp_atomic_check(connector, old_con_state, new_con_state);
+	if (!new_con_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(new_crtc_state))
+		return PTR_ERR(new_crtc_state);
+
 	if (!blob_equal(new_con_state->hdr_output_metadata,
 			old_con_state->hdr_output_metadata) ||
 	    new_con_state->colorspace != old_con_state->colorspace) {
-		new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
-		if (IS_ERR(new_crtc_state))
-			return PTR_ERR(new_crtc_state);
 
 		new_crtc_state->mode_changed =
 			!new_con_state->hdr_output_metadata ||
@@ -342,6 +489,15 @@ static int cdns_hdmi_connector_atomic_check(struct drm_connector *connector,
 			new_con_state->colorspace != old_con_state->colorspace;
 	}
 
+	/*
+	 * These properties are handled by fastset, and might not end up in a
+	 * modeset.
+	 */
+	if (new_con_state->picture_aspect_ratio !=
+	    old_con_state->picture_aspect_ratio ||
+	    new_con_state->content_type != old_con_state->content_type ||
+	    new_con_state->scaling_mode != old_con_state->scaling_mode)
+		new_crtc_state->mode_changed = true;
 	return 0;
 }
 
@@ -388,6 +544,7 @@ static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge,
 
 	drm_connector_attach_encoder(connector, encoder);
 
+	drm_connector_attach_content_protection_property(connector, true);
 	return 0;
 }
 
@@ -439,7 +596,7 @@ static void cdns_hdmi_bridge_mode_set(struct drm_bridge *bridge,
 	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
 	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
 
-	DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock); 
+	DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock);
 	memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
 
 	mutex_lock(&mhdp->lock);
@@ -518,6 +675,8 @@ bool cdns_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
 
 static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = {
 	.attach = cdns_hdmi_bridge_attach,
+	.enable = cdns_hdmi_bridge_enable,
+	.disable = cdns_hdmi_bridge_disable,
 	.mode_set = cdns_hdmi_bridge_mode_set,
 	.mode_valid = cdns_hdmi_bridge_mode_valid,
 	.mode_fixup = cdns_hdmi_bridge_mode_fixup,
@@ -645,7 +804,7 @@ static int __cdns_hdmi_probe(struct platform_device *pdev,
 						mhdp->irq[IRQ_IN]);
 		return -EINVAL;
 	}
-	
+
 	irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
 	ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
 					NULL, cdns_hdmi_irq_thread,
@@ -659,6 +818,25 @@ static int __cdns_hdmi_probe(struct platform_device *pdev,
 
 	cdns_hdmi_parse_dt(mhdp);
 
+	ret = cdns_hdmi_hdcp_init(mhdp, pdev->dev.of_node);
+	if (ret < 0)
+		DRM_WARN("Failed to initialize HDCP\n");
+
+	if (device_create_file(mhdp->dev, &HDCPTX_do_reauth)) {
+		printk(KERN_ERR "Unable to create HDCPTX_do_reauth sysfs\n");
+		device_remove_file(mhdp->dev, &HDCPTX_do_reauth);
+	}
+
+	if (device_create_file(mhdp->dev, &HDCPTX_Version)) {
+		printk(KERN_ERR "Unable to create HDCPTX_Version sysfs\n");
+		device_remove_file(mhdp->dev, &HDCPTX_Version);
+	}
+
+	if (device_create_file(mhdp->dev, &HDCPTX_Status)) {
+		printk(KERN_ERR "Unable to create HDCPTX_Status sysfs\n");
+		device_remove_file(mhdp->dev, &HDCPTX_Status);
+	}
+
 	if (cdns_mhdp_read_hpd(mhdp))
 		enable_irq(mhdp->irq[IRQ_OUT]);
 	else
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-hdcp.c
new file mode 100644
index 000000000000..e2a3bc7fb42b
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-hdcp.c
@@ -0,0 +1,1167 @@
+/*
+ * Cadence HDMI HDCP driver
+ *
+ * Copyright (C) 2021 NXP Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/display/drm_hdcp.h>
+#include <drm/drm_print.h>
+#include <linux/firmware.h>
+
+#include "cdns-mhdp-hdcp.h"
+
+/* Default will be to use KM unless it has been explicitly */
+#ifndef HDCP_USE_KMKEY
+	#define HDCP_USE_KMKEY 1
+#endif
+
+#define CDNS_HDCP_ACTIVATE (0x1 << 2)
+
+#define IMX_FW_TIMEOUT_MS      (64 * 1000)
+#define IMX_HDCP_PAIRING_FIRMWARE "imx/hdcp-pairing.bin"
+
+#define GENERAL_BUS_SETTINGS_DPCD_BUS_BIT           0
+#define GENERAL_BUS_SETTINGS_DPCD_BUS_LOCK_BIT      1
+#define GENERAL_BUS_SETTINGS_HDCP_BUS_BIT           2
+#define GENERAL_BUS_SETTINGS_HDCP_BUS_LOCK_BIT      3
+#define GENERAL_BUS_SETTINGS_CAPB_OWNER_BIT         4
+#define GENERAL_BUS_SETTINGS_CAPB_OWNER_LOCK_BIT    5
+
+#define GENERAL_BUS_SETTINGS_RESP_DPCD_BUS_BIT      0
+#define GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT      1
+#define GENERAL_BUS_SETTINGS_RESP_CAPB_OWNER_BIT    2
+
+/* HDCP TX ports working mode (HDCP 2.2 or 1.4) */
+enum {
+	HDCP_TX_2,		/* lock only with HDCP2 */
+	HDCP_TX_1,		/* lock only with HDCP1 */
+	HDCP_TX_BOTH,	/* lock on HDCP2 or 1 depend on other side */
+};
+
+/* HDCP TX ports stream type (relevant if receiver is repeater) */
+enum {
+	HDCP_CONTENT_TYPE_0,  /* May be transmitted by
+							 The HDCP Repeater to all HDCP Devices. */
+	HDCP_CONTENT_TYPE_1,  /* Must not be transmitted by the HDCP Repeater to
+							 HDCP 1.x-compliant Devices and HDCP 2.0-compliant Repeaters */
+};
+
+/* different error types for HDCP_TX_STATUS_CHANGE */
+enum {
+	HDCP_TRAN_ERR_NO_ERROR,
+	HDCP_TRAN_ERR_HPD_IS_DOWN,
+	HDCP_TRAN_ERR_SRM_FAILURE,
+	HDCP_TRAN_ERR_SIGNATURE_VERIFICATION,
+	HDCP_TRAN_ERR_H_TAG_DIFF_H,
+	HDCP_TRAN_ERR_V_TAG_DIFF_V,
+	HDCP_TRAN_ERR_LOCALITY_CHECK,
+	HDCP_TRAN_ERR_DDC,
+	HDCP_TRAN_ERR_REAUTH_REQ,
+	HDCP_TRAN_ERR_TOPOLOGY,
+	HDCP_TRAN_ERR_HDCP_RSVD1,
+	HDCP_TRAN_ERR_HDMI_CAPABILITY,
+	HDCP_TRAN_ERR_RI,
+	HDCP_TRAN_ERR_WATCHDOG_EXPIRED,
+};
+
+static char const *g_last_error[16] = {
+	"No Error",
+	"HPD is down",
+	"SRM failure",
+	"Signature verification error",
+	"h tag != h",
+	"V tag diff v",
+	"Locality check",
+	"DDC error",
+	"REAUTH_REQ",
+	"Topology error",
+	"Verify receiver ID list failed",
+	"HDCP_RSVD1 was not 0,0,0",
+	"HDMI capability or mode",
+	"RI result was different than expected",
+	"WatchDog expired",
+	"Repeater integrity failed"
+};
+
+#define HDCP_MAX_RECEIVERS 32
+#define HDCP_RECEIVER_ID_SIZE_BYTES 5
+#define HPD_EVENT 1
+#define HDCP_STATUS_SIZE         0x5
+#define HDCP_PORT_STS_AUTH       0x1
+#define HDCP_PORT_STS_REPEATER   0x2
+#define HDCP_PORT_STS_TYPE_MASK  0xc
+#define HDCP_PORT_STS_TYPE_SHIFT 0x2
+#define HDCP_PORT_STS_AUTH_STREAM_ID_SHIFT 0x4
+#define HDCP_PORT_STS_AUTH_STREAM_ID_MASK 0x10
+#define HDCP_PORT_STS_LAST_ERR_SHIFT 0x5
+#define HDCP_PORT_STS_LAST_ERR_MASK  (0x0F << 5)
+#define GET_HDCP_PORT_STS_LAST_ERR(__sts__) \
+	(((__sts__) & HDCP_PORT_STS_LAST_ERR_MASK) >> \
+	 HDCP_PORT_STS_LAST_ERR_SHIFT)
+#define HDCP_PORT_STS_1_1_FEATURES   0x200
+
+#define HDCP_CONFIG_NONE    ((u8) 0)
+#define HDCP_CONFIG_1_4     ((u8) 1) /* use HDCP 1.4 only */
+#define HDCP_CONFIG_2_2     ((u8) 2) /* use HDCP 2.2 only */
+
+/* Default timeout to use for wait4event in milliseconds */
+#define HDCP_EVENT_TO_DEF 800
+/* Timeout value to use for repeater receiver ID check, spec says 3s */
+#define HDCP_EVENT_TO_RPT 3500
+
+static int hdmi_hdcp_check_link(struct cdns_mhdp_device *mhdp);
+
+static void print_port_status(u16 sts)
+{
+	char const *rx_type[4] = { "Unknown", "HDCP 1", "HDCP 2", "Unknown" };
+
+	DRM_DEBUG_KMS("INFO: HDCP Port Status: 0x%04x\n", sts);
+	DRM_DEBUG_KMS(" Authenticated: %d\n", sts & HDCP_PORT_STS_AUTH);
+	DRM_DEBUG_KMS(" Receiver is repeater: %d\n", sts & HDCP_PORT_STS_REPEATER);
+	DRM_DEBUG_KMS(" RX Type: %s\n",
+				rx_type[(sts & HDCP_PORT_STS_TYPE_MASK) >> HDCP_PORT_STS_TYPE_SHIFT]);
+	DRM_DEBUG_KMS(" AuthStreamId: %d\n", sts & HDCP_PORT_STS_AUTH_STREAM_ID_MASK);
+	DRM_DEBUG_KMS(" Last Error: %s\n",
+				g_last_error[(sts & HDCP_PORT_STS_LAST_ERR_MASK) >> HDCP_PORT_STS_LAST_ERR_SHIFT]);
+	DRM_DEBUG_KMS(" Enable 1.1 Features: %d\n", sts & HDCP_PORT_STS_1_1_FEATURES);
+}
+
+static void print_events(u8 events)
+{
+	if (events & HDMI_TX_HPD_EVENT)
+		DRM_INFO("INFO: HDMI_TX_HPD_EVENT\n");
+	if (events & HDCPTX_STATUS_EVENT)
+		DRM_INFO("INFO: HDCPTX_STATUS_EVENT\n");
+	if (events & HDCPTX_IS_KM_STORED_EVENT)
+		DRM_INFO("INFO: HDCPTX_IS_KM_STORED_EVENT\n");
+	if (events & HDCPTX_STORE_KM_EVENT)
+		DRM_INFO("INFO: HDCPTX_STORE_KM_EVENT\n");
+	if (events & HDCPTX_IS_RECEIVER_ID_VALID_EVENT)
+		DRM_INFO("INFO: HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n");
+}
+
+static u8 wait4event(struct cdns_mhdp_device *mhdp, u8 *events,
+			       u32 event_to_wait, u32 timeout_ms)
+{
+	u8 reg_events;
+	u8 returned_events;
+	u8 event_mask = event_to_wait | HDCPTX_STATUS_EVENT;
+	unsigned timeout;
+
+	timeout = timeout_ms;
+	do {
+		if (timeout == 0)
+			goto timeout_err;
+		timeout--;
+		udelay(1000);
+		reg_events = cdns_mhdp_get_event(mhdp);
+		*events |= reg_events;
+	} while (((event_mask & *events) == 0) && (event_to_wait > HDMI_TX_HPD_EVENT));
+
+	returned_events = *events & event_mask;
+	if (*events != returned_events) {
+		u32 unexpected_events = ~event_mask & *events;
+
+		DRM_INFO("INFO: %s() all 0x%08x  expected 0x%08x unexpected 0x%08x",
+			 __func__, *events, returned_events, unexpected_events);
+		DRM_INFO("INFO: %s() All events:\n", __func__);
+		print_events(*events);
+
+		DRM_INFO("INFO: %s() expected events:\n", __func__);
+		print_events(returned_events);
+
+		DRM_INFO("INFO: %s() unexpected events:\n", __func__);
+		print_events(unexpected_events);
+	} else
+		print_events(*events);
+
+	*events &= ~event_mask;
+
+	return returned_events;
+
+timeout_err:
+	DRM_INFO("INFO: %s() Timed out with events:\n", __func__);
+	print_events(event_to_wait);
+	return 0;
+}
+
+static u16 hdmi_hdcp_get_status(struct cdns_mhdp_device *mhdp)
+{
+	u8 hdcp_status[HDCP_STATUS_SIZE];
+	u16 hdcp_port_status;
+
+	cdns_mhdp_hdcp_tx_status_req(mhdp, hdcp_status, HDCP_STATUS_SIZE);
+	hdcp_port_status = (hdcp_status[0] << 8) | hdcp_status[1];
+
+	return hdcp_port_status;
+}
+
+static inline u8 check_event(u8 events, u8 tested)
+{
+	if ((events & tested) == 0)
+		return 0;
+	return 1;
+}
+
+/* Prints status. Returns error code (0 = no error) */
+static u8 hdmi_hdcp_handle_status(u16 status)
+{
+	print_port_status(status);
+	if (status & HDCP_PORT_STS_LAST_ERR_MASK)
+		DRM_ERROR("ERROR: HDCP error was set to %s\n",
+			  g_last_error[((status & HDCP_PORT_STS_LAST_ERR_MASK)
+					>> HDCP_PORT_STS_LAST_ERR_SHIFT)]);
+	return GET_HDCP_PORT_STS_LAST_ERR(status);
+}
+
+static int hdmi_hdcp_set_config(struct cdns_mhdp_device *mhdp, u8 hdcp_config)
+{
+	u8 bus_config, retEvents;
+	u16 hdcp_port_status;
+	int ret;
+
+	/* Clearing out existing events */
+	wait4event(mhdp, &mhdp->hdcp.events, HDMI_TX_HPD_EVENT, HDCP_EVENT_TO_DEF);
+	mhdp->hdcp.events = 0;
+
+	if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) {
+		DRM_DEBUG_KMS("INFO: Switching HDCP Commands to SAPB.\n");
+		bus_config = (1 << GENERAL_BUS_SETTINGS_HDCP_BUS_BIT);
+		ret = cdns_mhdp_apb_conf(mhdp, bus_config);
+		if (ret) {
+			DRM_ERROR("Failed to set APB configuration.\n");
+			if (ret & (1 << GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT))/* 1 - locked */
+				DRM_ERROR("Failed to switch HDCP to SAPB Mailbox\n");
+			return -1;
+		}
+		DRM_DEBUG_KMS("INFO: HDCP switched to SAPB\n");
+	}
+
+	/* HDCP 2.2(and/or 1.4) | activate | km-key |  0 */
+	hdcp_config |=  CDNS_HDCP_ACTIVATE | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3);
+
+	DRM_DEBUG_KMS("INFO: Enabling HDCP...\n");
+	ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_config);
+	if (ret < 0)
+		DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n");
+
+	/* Wait until HDCP_TX_STATUS EVENT appears */
+	DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n");
+	retEvents = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF);
+
+	/* Set TX STATUS REQUEST */
+	DRM_DEBUG_KMS("INFO: Getting port status\n");
+	hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+	if (hdmi_hdcp_handle_status(hdcp_port_status) != 0)
+		return -1;
+
+	return 0;
+}
+
+static int hdmi_hdcp_auth_check(struct cdns_mhdp_device *mhdp)
+{
+	u16 hdcp_port_status;
+	int ret;
+
+	DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n");
+	mhdp->hdcp.events = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF+HDCP_EVENT_TO_DEF);
+	if (mhdp->hdcp.events == 0)
+		return -1;
+
+	DRM_DEBUG_KMS("HDCP: HDCPTX_STATUS_EVENT\n");
+	hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+	ret = hdmi_hdcp_handle_status(hdcp_port_status);
+	if (ret != 0) {
+		if (ret == HDCP_TRAN_ERR_REAUTH_REQ) {
+			DRM_ERROR("HDCP_TRAN_ERR_REAUTH_REQ-->one more try!\n");
+			return 1;
+		} else
+			return -1;
+	}
+
+	if (hdcp_port_status & HDCP_PORT_STS_AUTH) {
+		DRM_INFO("Authentication completed successfully!\n");
+		/* Dump hdmi and phy register */
+		mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED;
+		return 0;
+	}
+
+	DRM_WARN("Authentication failed\n");
+	mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+	return -1;
+}
+
+inline void hdmi_hdcp_swap_id(u8 *in, u8 *out)
+{
+	int i;
+
+	for (i = 0; i < HDCP_RECEIVER_ID_SIZE_BYTES; i++)
+		out[HDCP_RECEIVER_ID_SIZE_BYTES - (i + 1)] = in[i];
+}
+
+inline void hdmi_hdcp_swap_list(u8 *list_in, u8 *list_out, int num_ids)
+{
+	int i;
+
+	for (i = 0; i < num_ids; i++)
+		hdmi_hdcp_swap_id(&list_in[i * HDCP_RECEIVER_ID_SIZE_BYTES],
+				 &list_out[i * HDCP_RECEIVER_ID_SIZE_BYTES]);
+}
+
+static int hdmi_hdcp_check_receviers(struct cdns_mhdp_device *mhdp)
+{
+	u8 ret_events;
+	u8 hdcp_num_rec, i;
+	u8 hdcp_rec_id[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES];
+	u8 hdcp_rec_id_temp[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES];
+	u16 hdcp_port_status = 0;
+	int ret;
+
+	DRM_INFO("INFO: Waiting for Receiver ID valid event\n");
+	ret_events = 0;
+	do {
+		u8 events = 0;
+		u8 hdcp_last_error = 0;
+		events = check_event(ret_events,
+				     HDCPTX_IS_RECEIVER_ID_VALID_EVENT);
+		DRM_DEBUG_KMS("INFO: Waiting HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n");
+		ret_events = wait4event(mhdp, &mhdp->hdcp.events,
+					HDCPTX_IS_RECEIVER_ID_VALID_EVENT,
+					(mhdp->hdcp.sink_is_repeater ?
+					 HDCP_EVENT_TO_RPT : HDCP_EVENT_TO_DEF));
+		if (ret_events == 0) {
+			/* time out occurred, return error */
+			DRM_ERROR("HDCP error did not get receiver IDs\n");
+			return -1;
+		}
+		if (check_event(ret_events, HDCPTX_STATUS_EVENT) != 0) {
+			/* There was a status update, could be due to HPD
+			   going down or some other error, check if an error
+			   was set, if so exit.
+			*/
+			hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+			hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status);
+			if (hdmi_hdcp_handle_status(hdcp_port_status)) {
+				DRM_ERROR("HDCP error no: %u\n", hdcp_last_error);
+				return -1;
+			} else {
+				/* No error logged, keep going.
+				 * If this somehow happened at same time, then need to
+				 * put the HDCPTX_STATUS_EVENT back into the global
+				 * events pool and checked later. */
+				mhdp->hdcp.events |= HDCPTX_STATUS_EVENT;
+
+				/* Special condition when connected to HDCP 1.4 repeater
+				 * with no downstream devices attached, then will not
+				 * get receiver ID list but instead will reach
+				 * authenticated state. */
+				if ((mhdp->hdcp.hdcp_version == HDCP_TX_1) && (mhdp->hdcp.sink_is_repeater == 1) &&
+				    ((hdcp_port_status & HDCP_PORT_STS_AUTH) == HDCP_PORT_STS_AUTH)) {
+					DRM_INFO("Connected to HDCP 1.4 repeater with no downstream devices!\n");
+					return 0;
+				}
+
+				msleep(20);
+			}
+		}
+	} while (check_event(ret_events,
+			     HDCPTX_IS_RECEIVER_ID_VALID_EVENT) == 0);
+
+	DRM_INFO("INFO: Requesting Receivers ID's\n");
+
+	hdcp_num_rec = 0;
+	memset(&hdcp_rec_id, 0, sizeof(hdcp_rec_id));
+
+	ret = cdns_mhdp_hdcp_tx_is_receiver_id_valid(mhdp, (u8 *)hdcp_rec_id, &hdcp_num_rec);
+	if (ret) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp tx receiver ID.\n");
+		return -1;
+	}
+
+	if (hdcp_num_rec == 0) {
+		DRM_DEBUG_KMS("WARN: Failed to get receiver list\n");
+		/* Unknown problem, return error */
+		return -1;
+	}
+
+	DRM_INFO("INFO: Number of Receivers: %d\n", hdcp_num_rec);
+
+	for (i = 0; i < hdcp_num_rec; ++i) {
+		DRM_INFO("\tReveiver ID%2d: %.2X%.2X%.2X%.2X%.2X\n",
+			 i,
+			 hdcp_rec_id[i][0],
+			 hdcp_rec_id[i][1],
+			 hdcp_rec_id[i][2],
+			 hdcp_rec_id[i][3],
+			 hdcp_rec_id[i][4]
+			);
+	}
+
+	/* swap ids byte order */
+	hdmi_hdcp_swap_list(&hdcp_rec_id[0][0],
+			   &hdcp_rec_id_temp[0][0], hdcp_num_rec);
+
+	/* Check Receiver ID's against revocation list in SRM */
+	if (drm_hdcp_check_ksvs_revoked(mhdp->drm_dev, (u8 *)hdcp_rec_id_temp, hdcp_num_rec)) {
+		mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+		DRM_ERROR("INFO: Receiver check fails\n");
+		return -1;
+	}
+
+	ret = cdns_mhdp_hdcp_tx_respond_receiver_id_valid(mhdp, 1);
+	DRM_INFO("INFO: Responding with Receiver ID's OK!, ret=%d\n", ret);
+	return ret;
+}
+
+#ifdef STORE_PAIRING
+static int hdmi_hdcp_get_stored_pairing(struct cdns_mhdp_device *mhdp)
+{
+	int ret = 0;
+	unsigned long timeout = jiffies + msecs_to_jiffies(IMX_FW_TIMEOUT_MS);
+	unsigned long sleep = 1000;
+	const struct firmware *fw;
+
+	DRM_DEBUG_KMS("%s()\n", __func__);
+
+	while (time_before(jiffies, timeout)) {
+		ret = request_firmware(&fw, hdmi_hdcp_PAIRING_FIRMWARE, mhdp->dev);
+		if (ret == -ENOENT) {
+			msleep(sleep);
+			sleep *= 2;
+			continue;
+		} else if (ret) {
+			DRM_DEV_INFO(mhdp->dev, "HDCP pairing data not found\n");
+			goto out;
+		}
+
+		mhdp->hdcp.num_paired = fw->size /
+			sizeof(struct hdcp_trans_pairing_data);
+		if (mhdp->hdcp.num_paired > MAX_STORED_KM) {
+			/* todo: handle dropping */
+			mhdp->hdcp.num_paired = MAX_STORED_KM;
+			DRM_DEV_INFO(mhdp->dev,
+				     "too many paired receivers - dropping older entries\n");
+		}
+		memcpy(&mhdp->hdcp.pairing[0], fw->data,
+		       sizeof(struct hdcp_trans_pairing_data) * mhdp->hdcp.num_paired);
+		release_firmware(fw);
+		goto out;
+	}
+
+	DRM_DEV_ERROR(mhdp->dev, "Timed out trying to load firmware\n");
+	ret = -ETIMEDOUT;
+	out:
+	return ret;
+}
+#endif
+
+static int hdmi_hdcp_find_km_store(struct cdns_mhdp_device *mhdp,
+				  u8 receiver[HDCP_PAIRING_R_ID])
+{
+	int i;
+
+	DRM_DEBUG_KMS("%s()\n", __func__);
+	for (i = 0; i < mhdp->hdcp.num_paired; i++) {
+		if (memcmp(receiver, mhdp->hdcp.pairing[i].receiver_id,
+			   HDCP_PAIRING_R_ID) == 0) {
+			DRM_INFO("HDCP: found receiver id: 0x%x%x%x%x%x\n",
+			receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]);
+			return i;
+		}
+	}
+	DRM_INFO("HDCP: receiver id: 0x%x%x%x%x%x not stored\n",
+		 receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]);
+	return -1;
+}
+
+static int hdmi_hdcp_store_km(struct cdns_mhdp_device *mhdp,
+			     struct hdcp_trans_pairing_data *pairing,
+			     int stored_km_index)
+{
+	int i, temp_index;
+	struct hdcp_trans_pairing_data temp_pairing;
+
+	DRM_DEBUG_KMS("%s()\n", __func__);
+
+	if (stored_km_index < 0) {
+		/* drop one entry if array is full */
+		if (mhdp->hdcp.num_paired == MAX_STORED_KM)
+			mhdp->hdcp.num_paired--;
+
+		temp_index = mhdp->hdcp.num_paired;
+		mhdp->hdcp.num_paired++;
+		if (!pairing) {
+			DRM_ERROR("NULL HDCP pairing data!\n");
+			return  -1;
+		} else
+			/* save the new stored km */
+			temp_pairing = *pairing;
+	} else {
+		/* save the current stored km */
+		temp_index = stored_km_index;
+		temp_pairing = mhdp->hdcp.pairing[stored_km_index];
+	}
+
+	/* move entries one slot to the end */
+	for (i = temp_index; i > 0; i--)
+		mhdp->hdcp.pairing[i] = mhdp->hdcp.pairing[i - 1];
+
+	/* save the current/new entry at the beginning */
+	mhdp->hdcp.pairing[0] = temp_pairing;
+
+	return 0;
+}
+
+static inline int hdmi_hdcp_auth_22(struct cdns_mhdp_device *mhdp)
+{
+	int km_idx = -1;
+	u8 retEvents;
+	u16 hdcp_port_status;
+	u8 resp[HDCP_STATUS_SIZE];
+	struct hdcp_trans_pairing_data pairing;
+	int ret;
+
+	DRM_DEBUG_KMS("HDCP: Start 2.2 Authentication\n");
+	mhdp->hdcp.sink_is_repeater = 0;
+
+	/* Wait until HDCP2_TX_IS_KM_STORED EVENT appears */
+	retEvents = 0;
+	DRM_DEBUG_KMS("INFO: Wait until HDCP2_TX_IS_KM_STORED EVENT appears\n");
+	while (check_event(retEvents, HDCPTX_IS_KM_STORED_EVENT) == 0) {
+		DRM_DEBUG_KMS("INFO: Waiting FOR _IS_KM_STORED EVENT\n");
+		retEvents = wait4event(mhdp, &mhdp->hdcp.events,
+				       HDCPTX_IS_KM_STORED_EVENT, HDCP_EVENT_TO_DEF);
+		if (retEvents == 0)
+			/* time out occurred, return error */
+			return -1;
+		if (check_event(retEvents, HDCPTX_STATUS_EVENT) != 0) {
+			/* There was a status update, could be due to HPD
+			   going down or some other error, check if an error
+			   was set, if so exit.
+			*/
+			hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+			if (hdmi_hdcp_handle_status(hdcp_port_status) != 0)
+				return -1;
+		}
+	}
+
+	DRM_DEBUG_KMS("HDCP: HDCPTX_IS_KM_STORED_EVENT\n");
+
+	/* Set HDCP2 TX KM STORED REQUEST */
+	ret = cdns_mhdp_hdcp2_tx_is_km_stored_req(mhdp, resp, HDCP_STATUS_SIZE);
+	if (ret) {
+		DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp2 tx km stored.\n");
+		return -1;
+	}
+
+	DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_IS_KM_STORED_REQ_blocking\n");
+	DRM_DEBUG_KMS("HDCP: Receiver ID: 0x%x%x%x%x%x\n",
+		      resp[0], resp[1], resp[2], resp[3], resp[4]);
+
+	km_idx = hdmi_hdcp_find_km_store(mhdp, resp);
+
+	/* Check if KM is stored */
+	if (km_idx >= 0) {
+		DRM_DEBUG_KMS("INFO: KM is stored\n");
+		/* Set HDCP2 TX RESPOND KM with stored KM */
+		ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, (u8 *)&mhdp->hdcp.pairing[km_idx],
+				sizeof(struct hdcp_trans_pairing_data));
+
+		DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_RESPOND_KM_blocking, ret=%d\n", ret);
+	} else { /* KM is not stored */
+		/* Set HDCP2 TX RESPOND KM with empty data */
+		ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, NULL, 0);
+		DRM_DEBUG_KMS("INFO: KM is not stored ret=%d\n", ret);
+	}
+
+	if (hdmi_hdcp_check_receviers(mhdp))
+		return -1;
+
+	/* Check if KM is not stored */
+	if (km_idx < 0) {
+		int loop_cnt = 0;
+
+		/* Wait until HDCP2_TX_STORE_KM EVENT appears */
+		retEvents = 0;
+		DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STORE_KM_EVENT\n");
+		while (check_event(retEvents, HDCPTX_STORE_KM_EVENT) == 0) {
+			retEvents = wait4event(mhdp, &mhdp->hdcp.events,
+					       HDCPTX_STORE_KM_EVENT, HDCP_EVENT_TO_DEF);
+			if (check_event(retEvents, HDCPTX_STATUS_EVENT)
+			    != 0) {
+				hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+				if (hdmi_hdcp_handle_status(hdcp_port_status)
+				    != 0)
+					return -1;
+			}
+			if (loop_cnt > 2) {
+				DRM_ERROR("Did not get event HDCPTX_STORE_KM_EVENT in time\n");
+				return -1;
+			} else
+				loop_cnt++;
+		}
+		DRM_DEBUG_KMS("HDCP: HDCPTX_STORE_KM_EVENT\n");
+
+		/* Set HDCP2_TX_STORE_KM REQUEST */
+		ret = cdns_mhdp_hdcp2_tx_store_km(mhdp, (u8 *)&pairing, sizeof(struct  hdcp_trans_pairing_data));
+		DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_STORE_KM_REQ_blocking ret=%d\n", ret);
+		hdmi_hdcp_store_km(mhdp, &pairing, km_idx);
+	} else
+		hdmi_hdcp_store_km(mhdp, NULL, km_idx);
+
+	/* Check if device was a repeater */
+	hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+
+	/* Exit if there was any errors logged at this point... */
+	if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) {
+		hdmi_hdcp_handle_status(hdcp_port_status);
+		return -1;
+	}
+
+	if (hdcp_port_status & HDCP_PORT_STS_REPEATER)
+		mhdp->hdcp.sink_is_repeater = 1;
+
+	/* If sink was a repeater, we will be getting additional IDs to validate...
+	 * Note that this one may take some time since spec allows up to 3s... */
+	if (mhdp->hdcp.sink_is_repeater)
+		if (hdmi_hdcp_check_receviers(mhdp))
+			return -1;
+
+	/* Slight delay to allow firmware to finish setting up authenticated state */
+	msleep(300);
+
+	DRM_INFO("Finished hdmi_hdcp_auth_22\n");
+	return 0;
+}
+
+static inline int hdmi_hdcp_auth_14(struct cdns_mhdp_device *mhdp)
+{
+	u16 hdcp_port_status;
+	int ret = 0;
+
+	DRM_DEBUG_KMS("HDCP: Starting 1.4 Authentication\n");
+	mhdp->hdcp.sink_is_repeater = 0;
+
+	ret = hdmi_hdcp_check_receviers(mhdp);
+	if (ret)
+		return -1;
+
+	/* Check if device was a repeater */
+	hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+
+	/* Exit if there was any errors logged at this point... */
+	if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) {
+		hdmi_hdcp_handle_status(hdcp_port_status);
+		return -1;
+	}
+
+	if (hdcp_port_status & HDCP_PORT_STS_REPEATER) {
+		DRM_INFO("Connected to a repeater\n");
+		mhdp->hdcp.sink_is_repeater = 1;
+	} else
+		DRM_INFO("Connected to a normal sink\n");
+
+	/* If sink was a repeater, we will be getting additional IDs to validate...
+	 * Note that this one may take some time since spec allows up to 3s... */
+	if (mhdp->hdcp.sink_is_repeater)
+		ret = hdmi_hdcp_check_receviers(mhdp);
+
+	/* Slight delay to allow firmware to finish setting up authenticated state */
+	msleep(300);
+
+	return ret;
+}
+
+static int hdmi_hdcp_auth(struct cdns_mhdp_device *mhdp, u8 hdcp_config)
+{
+	int ret = 0;
+
+	DRM_DEBUG_KMS("HDCP: Start Authentication\n");
+
+	if (mhdp->hdcp.reauth_in_progress == 0) {
+		ret = hdmi_hdcp_set_config(mhdp, hdcp_config);
+		if (ret) {
+			DRM_ERROR("hdmi_hdcp_set_config failed\n");
+			return -1;
+		}
+	}
+
+	mhdp->hdcp.reauth_in_progress = 0;
+	mhdp->hdcp.sink_is_repeater   = 0;
+	mhdp->hdcp.hdcp_version       = hdcp_config;
+
+	do {
+		if (mhdp->hdcp.cancel == 1) {
+			DRM_ERROR("mhdp->hdcp.cancel is TRUE\n");
+			return -ECANCELED;
+		}
+
+		if (hdcp_config == HDCP_TX_1)
+			ret = hdmi_hdcp_auth_14(mhdp);
+		else
+			ret = hdmi_hdcp_auth_22(mhdp);
+		if (ret) {
+			u16 hdcp_port_status;
+			DRM_ERROR("hdmi_hdcp_auth_%s failed\n",
+				  (hdcp_config == HDCP_TX_1) ? "14" : "22");
+			hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+			hdmi_hdcp_handle_status(hdcp_port_status);
+			return -1;
+		}
+
+		ret = hdmi_hdcp_auth_check(mhdp);
+	} while (ret == 1);
+
+	return ret;
+}
+
+static int _hdmi_hdcp_disable(struct cdns_mhdp_device *mhdp)
+{
+	int ret = 0;
+	u8 hdcp_cfg = (HDCP_USE_KMKEY << 4);
+
+	DRM_DEBUG_KMS("[%s:%d] HDCP is being disabled...\n",
+		      mhdp->connector.base.name, mhdp->connector.base.base.id);
+	DRM_DEBUG_KMS("INFO: Disabling HDCP...\n");
+
+	ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_cfg);
+	if (ret < 0)
+		DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n");
+
+	DRM_DEBUG_KMS("HDCP is disabled\n");
+
+	mhdp->hdcp.events = 0;
+
+	return ret;
+}
+
+static int _hdmi_hdcp_enable(struct cdns_mhdp_device *mhdp)
+{
+	int i, ret = 0, tries = 9;
+	u8 hpd_sts;
+
+	hpd_sts = cdns_mhdp_read_hpd(mhdp);
+	if (1 != hpd_sts) {
+		dev_info(mhdp->dev, "%s HDP detected low, set state to DISABLING\n", __func__);
+		mhdp->hdcp.state = HDCP_STATE_DISABLING;
+		return -1;
+	}
+
+	DRM_DEBUG_KMS("[%s:%d] HDCP is being enabled...\n",
+		      mhdp->connector.base.name, mhdp->connector.base.base.id);
+
+	mhdp->hdcp.events = 0;
+
+	/* Incase of authentication failures, HDCP spec expects reauth. */
+	/* TBD should this actually try 2.2 n times then 1.4? */
+	for (i = 0; i < tries; i++) {
+		if (mhdp->hdcp.config & HDCP_CONFIG_2_2) {
+			ret = hdmi_hdcp_auth(mhdp, HDCP_TX_2);
+			if (ret == 0)
+				return 0;
+			else if (ret == -ECANCELED)
+				return ret;
+			_hdmi_hdcp_disable(mhdp);
+		}
+	}
+
+	for (i = 0; i < tries; i++) {
+		if (mhdp->hdcp.config & HDCP_CONFIG_1_4) {
+			ret = hdmi_hdcp_auth(mhdp, HDCP_TX_1);
+			if (ret == 0)
+				return 0;
+			else if (ret == -ECANCELED)
+				return ret;
+			_hdmi_hdcp_disable(mhdp);
+		}
+		DRM_DEBUG_KMS("HDCP Auth failure (%d)\n", ret);
+	}
+
+	DRM_ERROR("HDCP authentication failed (%d tries/%d)\n", tries, ret);
+	return ret;
+}
+
+static void hdmi_hdcp_check_work(struct work_struct *work)
+{
+	struct cdns_mhdp_hdcp *hdcp = container_of(work,
+					     struct cdns_mhdp_hdcp, check_work.work);
+	struct cdns_mhdp_device *mhdp = container_of(hdcp,
+					   struct cdns_mhdp_device, hdcp);
+
+	/* todo: maybe we don't need to always schedule */
+	hdmi_hdcp_check_link(mhdp);
+	schedule_delayed_work(&hdcp->check_work, 50);
+}
+
+static void hdmi_hdcp_prop_work(struct work_struct *work)
+{
+	struct cdns_mhdp_hdcp *hdcp = container_of(work,
+					     struct cdns_mhdp_hdcp, prop_work);
+	struct cdns_mhdp_device *mhdp = container_of(hdcp,
+					   struct cdns_mhdp_device, hdcp);
+
+	struct drm_device *dev = mhdp->drm_dev;
+	struct drm_connector_state *state;
+
+	drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+	mutex_lock(&mhdp->hdcp.mutex);
+
+	/*
+	 * This worker is only used to flip between ENABLED/DESIRED. Either of
+	 * those to UNDESIRED is handled by core. If hdcp_value == UNDESIRED,
+	 * we're running just after hdcp has been disabled, so just exit
+	 */
+	if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+		state = mhdp->connector.base.state;
+		state->content_protection = mhdp->hdcp.value;
+	}
+
+	mutex_unlock(&mhdp->hdcp.mutex);
+	drm_modeset_unlock(&dev->mode_config.connection_mutex);
+}
+
+static void show_hdcp_supported(struct cdns_mhdp_device *mhdp)
+{
+	if ((mhdp->hdcp.config & (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) ==
+		    (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2))
+		DRM_INFO("Both HDCP 1.4 and 2 2 are enabled\n");
+	else if (mhdp->hdcp.config & HDCP_CONFIG_1_4)
+		DRM_INFO("Only HDCP 1.4 is enabled\n");
+	else if (mhdp->hdcp.config & HDCP_CONFIG_2_2)
+		DRM_INFO("Only HDCP 2.2 is enabled\n");
+	else
+		DRM_INFO("HDCP is disabled\n");
+}
+
+#ifdef DEBUG
+void hdmi_hdcp_show_pairing(struct cdns_mhdp_device *mhdp, struct hdcp_trans_pairing_data *p)
+{
+	char s[80];
+	int i, k;
+
+	DRM_INFO("Reveiver ID: %.2X%.2X%.2X%.2X%.2X\n",
+		 p->receiver_id[0],
+		 p->receiver_id[1],
+		 p->receiver_id[2],
+		 p->receiver_id[3],
+		 p->receiver_id[4]);
+	for (k = 0, i = 0; k < 16; k++)
+		i += snprintf(&s[i], sizeof(s), "%02x", p->m[k]);
+
+	DRM_INFO("\tm: %s\n", s);
+
+	for (k = 0, i = 0; k < 16; k++)
+		i += snprintf(&s[i], sizeof(s), "%02x", p->km[k]);
+
+	DRM_INFO("\tkm: %s\n", s);
+
+	for (k = 0, i = 0; k < 16; k++)
+		i += snprintf(&s[i], sizeof(s), "%02x", p->ekh[k]);
+
+	DRM_INFO("\tekh: %s\n", s);
+}
+#endif
+
+void hdmi_hdcp_dump_pairing(struct seq_file *s, void *data)
+{
+	struct cdns_mhdp_device *mhdp = data;
+#ifdef DEBUG
+	int i;
+	for (i = 0; i < mhdp->hdcp.num_paired; i++)
+		hdmi_hdcp_show_pairing(mhdp, &mhdp->hdcp.pairing[i]);
+#endif
+	seq_write(s, &mhdp->hdcp.pairing[0],
+		  mhdp->hdcp.num_paired * sizeof(struct hdcp_trans_pairing_data));
+}
+
+static int hdmi_hdcp_pairing_show(struct seq_file *s, void *data)
+{
+	hdmi_hdcp_dump_pairing(s, s->private);
+	return 0;
+}
+
+static int hdmi_hdcp_dump_pairing_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hdmi_hdcp_pairing_show, inode->i_private);
+}
+
+static const struct file_operations hdmi_hdcp_dump_fops = {
+	.open		= hdmi_hdcp_dump_pairing_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static void hdmi_hdcp_debugfs_init(struct cdns_mhdp_device *mhdp)
+{
+	struct dentry *d, *root;
+
+	root = debugfs_create_dir("imx-hdcp", NULL);
+	if (IS_ERR(root) || !root)
+		goto err;
+
+	d = debugfs_create_file("dump_pairing", 0444, root, mhdp,
+				&hdmi_hdcp_dump_fops);
+	if (!d)
+		goto err;
+	return;
+
+err:
+	dev_err(mhdp->dev, "Unable to create debugfs entries\n");
+}
+
+int cdns_hdmi_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node)
+{
+	const char *compat;
+	u32 temp;
+	int ret;
+
+	ret = of_property_read_string(of_node, "compatible", &compat);
+	if (ret) {
+		DRM_ERROR("Failed to compatible dts string\n");
+		return ret;
+	}
+	if (!strstr(compat, "hdmi"))
+		return -EPERM;
+
+	ret = of_property_read_u32(of_node, "hdcp-config", &temp);
+	if (ret) {
+		/* using highest level by default */
+		mhdp->hdcp.config = HDCP_CONFIG_2_2;
+		DRM_INFO("Failed to get HDCP config - using HDCP 2.2 only\n");
+	} else {
+		mhdp->hdcp.config = temp;
+		show_hdcp_supported(mhdp);
+	}
+
+	hdmi_hdcp_debugfs_init(mhdp);
+
+#ifdef USE_DEBUG_KEYS  /* reserve for hdcp test key */
+	{
+		u8 hdcp_cfg;
+		hdcp_cfg = HDCP_TX_2 | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3);
+		imx_hdmi_load_test_keys(mhdp, &hdcp_cfg);
+	}
+#endif
+
+	mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+
+	mutex_init(&mhdp->hdcp.mutex);
+	INIT_DELAYED_WORK(&mhdp->hdcp.check_work, hdmi_hdcp_check_work);
+	INIT_WORK(&mhdp->hdcp.prop_work, hdmi_hdcp_prop_work);
+
+	return 0;
+}
+
+int cdns_hdmi_hdcp_enable(struct cdns_mhdp_device *mhdp)
+{
+	int ret = 0;
+
+	mhdp->hdcp.reauth_in_progress = 0;
+
+#ifdef STORE_PAIRING
+	hdmi_hdcp_get_stored_pairing(mhdp);
+#endif
+	msleep(500);
+
+	mutex_lock(&mhdp->hdcp.mutex);
+
+	mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+	mhdp->hdcp.state = HDCP_STATE_ENABLING;
+	mhdp->hdcp.cancel = 0;
+
+	schedule_work(&mhdp->hdcp.prop_work);
+	schedule_delayed_work(&mhdp->hdcp.check_work, 50);
+
+	mutex_unlock(&mhdp->hdcp.mutex);
+
+	return ret;
+}
+
+int cdns_hdmi_hdcp_disable(struct cdns_mhdp_device *mhdp)
+{
+	int ret = 0;
+
+	mutex_lock(&mhdp->hdcp.mutex);
+	if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+		mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED;
+		mhdp->hdcp.state = HDCP_STATE_DISABLING;
+		mhdp->hdcp.cancel = 1;
+		schedule_work(&mhdp->hdcp.prop_work);
+	}
+
+	mutex_unlock(&mhdp->hdcp.mutex);
+
+	cancel_delayed_work_sync(&mhdp->hdcp.check_work);
+
+	return ret;
+}
+
+void cdns_hdmi_hdcp_atomic_check(struct drm_connector *connector,
+			   struct drm_connector_state *old_state,
+			   struct drm_connector_state *new_state)
+{
+	u64 old_cp = old_state->content_protection;
+	u64 new_cp = new_state->content_protection;
+	struct drm_crtc_state *crtc_state;
+
+	if (!new_state->crtc) {
+		/*
+		 * If the connector is being disabled with CP enabled, mark it
+		 * desired so it's re-enabled when the connector is brought back
+		 */
+		if (old_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED)
+			new_state->content_protection =
+				DRM_MODE_CONTENT_PROTECTION_DESIRED;
+		return;
+	}
+
+	/*
+	 * Nothing to do if the state didn't change, or HDCP was activated since
+	 * the last commit
+	 */
+	if (old_cp == new_cp ||
+	    (old_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED &&
+	     new_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED))
+		return;
+
+	crtc_state = drm_atomic_get_new_crtc_state(new_state->state, new_state->crtc);
+	crtc_state->mode_changed = true;
+}
+
+static int hdmi_hdcp_check_link(struct cdns_mhdp_device *mhdp)
+{
+	u16 hdcp_port_status = 0;
+	u8 hdcp_last_error = 0;
+	u8 hpd_sts;
+	int ret = 0;
+
+	mhdp->hdcp.reauth_in_progress = 0;
+	mutex_lock(&mhdp->lock);
+
+	if ((mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED)  ||
+		(mhdp->hdcp.state == HDCP_STATE_AUTHENTICATING) ||
+		(mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) ||
+		(mhdp->hdcp.state == HDCP_STATE_ENABLING)) {
+
+		/* In active states, check the HPD signal. Because of the IRQ
+		 * debounce delay, the state might not reflect the disconnection.
+		 * The FW could already have detected the HDP down and reported error */
+	   hpd_sts = cdns_mhdp_read_hpd(mhdp);
+		if (1 != hpd_sts)
+			mhdp->hdcp.state = HDCP_STATE_DISABLING;
+	}
+
+	if (mhdp->hdcp.state == HDCP_STATE_INACTIVE)
+		goto out;
+
+	if (mhdp->hdcp.state == HDCP_STATE_DISABLING) {
+		_hdmi_hdcp_disable(mhdp);
+		mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+		goto out;
+	}
+
+/* TODO items:
+    Need to make sure that any requests from the firmware are actually
+    processed so want to remove this first jump to 'out', i.e. process
+    reauthentication requests, cleanup errors and repeater receiver id
+    checks.
+*/
+	if (mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED) {
+		/* get port status */
+		hdcp_port_status = hdmi_hdcp_get_status(mhdp);
+		hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status);
+		if (hdcp_last_error == HDCP_TRAN_ERR_REAUTH_REQ) {
+			DRM_INFO("Sink requesting re-authentication\n");
+			mhdp->hdcp.state = HDCP_STATE_REAUTHENTICATING;
+		} else if (hdcp_last_error) {
+			DRM_ERROR("HDCP error no: %u\n", hdcp_last_error);
+
+			if (mhdp->hdcp.value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
+				goto out;
+			if (hdcp_port_status &  HDCP_PORT_STS_AUTH) {
+				if (mhdp->hdcp.value !=
+					    DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+					mhdp->hdcp.value =
+						DRM_MODE_CONTENT_PROTECTION_ENABLED;
+					schedule_work(&mhdp->hdcp.prop_work);
+					goto out;
+				}
+			}
+
+			mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+
+		} else if (mhdp->hdcp.sink_is_repeater) {
+			u8 new_events;
+			/* Check events... and process if HDCPTX_IS_RECEIVER_ID_VALID_EVENT. */
+			new_events = cdns_mhdp_get_event(mhdp);
+			mhdp->hdcp.events |= new_events;
+			if (check_event(mhdp->hdcp.events, HDCPTX_IS_RECEIVER_ID_VALID_EVENT)) {
+				DRM_INFO("Sink repeater updating receiver ID list...\n");
+				if (hdmi_hdcp_check_receviers(mhdp))
+					mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+			}
+		}
+	}
+
+	if (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) {
+		/* For now just deal with HDCP2.2 */
+		if (mhdp->hdcp.hdcp_version == HDCP_TX_2)
+			mhdp->hdcp.reauth_in_progress = 1;
+		else
+			mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+	}
+
+	if (mhdp->hdcp.state == HDCP_STATE_ENABLING) {
+		mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING;
+		ret = _hdmi_hdcp_enable(mhdp);
+		if (ret == -ECANCELED)
+			goto out;
+		else if (ret) {
+			DRM_ERROR("Failed to enable hdcp (%d)\n", ret);
+			mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+			schedule_work(&mhdp->hdcp.prop_work);
+			goto out;
+		}
+	}
+
+	if ((mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) ||
+			(mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING)) {
+
+		print_port_status(hdcp_port_status);
+		if (mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) {
+			DRM_DEBUG_KMS("[%s:%d] HDCP link failed, retrying authentication 0x%2x\n",
+				      mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status);
+			ret = _hdmi_hdcp_disable(mhdp);
+			if (ret) {
+				DRM_ERROR("Failed to disable hdcp (%d)\n", ret);
+				mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+				schedule_work(&mhdp->hdcp.prop_work);
+				goto out;
+			}
+		} else
+			DRM_DEBUG_KMS("[%s:%d] HDCP attempt reauthentication 0x%2x\n",
+				      mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status);
+
+		ret = _hdmi_hdcp_enable(mhdp);
+		if (ret == -ECANCELED)
+			goto out;
+		else if (ret) {
+			DRM_ERROR("Failed to enable hdcp (%d)\n", ret);
+			mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+			schedule_work(&mhdp->hdcp.prop_work);
+			goto out;
+		}
+	}
+
+out:
+	mutex_unlock(&mhdp->lock);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c
new file mode 100644
index 000000000000..587c5f953489
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c
@@ -0,0 +1,300 @@
+/*
+ * Cadence HDCP API driver
+ *
+ * Copyright (C) 2021 NXP Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <asm/unaligned.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_print.h>
+
+#include "cdns-mhdp.h"
+
+static u32 mhdp_hdcp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset)
+{
+	u32 val;
+
+	mutex_lock(&mhdp->iolock);
+
+	if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+		/* Remap address to low 4K APB bus */
+		writel(offset >> 12, mhdp->regs_sec + 8);
+		val = readl((offset & 0xfff) + mhdp->regs_base);
+	} else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB)
+		val = readl(mhdp->regs_sec + offset);
+
+	mutex_unlock(&mhdp->iolock);
+
+	return val;
+}
+
+static void mhdp_hdcp_bus_write(u32 val, struct cdns_mhdp_device *mhdp, u32 offset)
+{
+	mutex_lock(&mhdp->iolock);
+
+	if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+		/* Remap address to low 4K APB bus */
+		writel(offset >> 12, mhdp->regs_sec + 8);
+		writel(val, (offset & 0xfff) + mhdp->regs_base);
+	} else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB)
+		writel(val, mhdp->regs_sec + offset);
+
+	mutex_unlock(&mhdp->iolock);
+}
+
+static int mhdp_hdcp_mailbox_read(struct cdns_mhdp_device *mhdp)
+{
+	int val, ret;
+
+	ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_EMPTY_ADDR,
+				 val, !val, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	return mhdp_hdcp_bus_read(mhdp, MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int mhdp_hdcp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
+{
+	int ret, full;
+
+	ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_FULL_ADDR,
+				 full, !full, MAILBOX_RETRY_US,
+				 MAILBOX_TIMEOUT_US);
+	if (ret < 0)
+		return ret;
+
+	mhdp_hdcp_bus_write(val, mhdp, MAILBOX0_WR_DATA);
+
+	return 0;
+}
+
+static int mhdp_hdcp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
+					      u8 module_id, u8 opcode, u16 req_size)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	int ret;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++) {
+		ret = mhdp_hdcp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = get_unaligned_be16(header + 2);
+
+	if (opcode != header[0] || module_id != header[1] ||
+	    req_size != mbox_size) {
+		/*
+		 * If the message in mailbox is not what we want, we need to
+		 * clear the mailbox by reading its contents.
+		 */
+		for (i = 0; i < mbox_size; i++)
+			if (mhdp_hdcp_mailbox_read(mhdp) < 0)
+				break;
+
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mhdp_hdcp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
+					  u8 *buff, u16 buff_size)
+{
+	u32 i;
+	int ret;
+
+	for (i = 0; i < buff_size; i++) {
+		ret = mhdp_hdcp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		buff[i] = ret;
+	}
+
+	return 0;
+}
+
+static int mhdp_hdcp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
+				  u8 opcode, u16 size, u8 *message)
+{
+	u8 header[4];
+	int ret, i;
+
+	header[0] = opcode;
+	header[1] = module_id;
+	put_unaligned_be16(size, header + 2);
+
+	for (i = 0; i < 4; i++) {
+		ret = mhdp_hdcp_mailbox_write(mhdp, header[i]);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < size; i++) {
+		ret = mhdp_hdcp_mailbox_write(mhdp, message[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* HDCP API */
+int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config)
+{
+	return mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				     HDCP_TX_CONFIGURATION, sizeof(config), &config);
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_config);
+
+int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp,
+					u8 *msg, u16 len)
+{
+	return mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP2_TX_RESPOND_KM, len, msg);
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_respond_km);
+
+int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp,
+					u8 *status, u16 len)
+{
+	int ret;
+
+	ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				     HDCP_TX_STATUS_CHANGE, 0, NULL);
+	if (ret)
+		goto err_tx_req;
+
+	ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+						 HDCP_TX_STATUS_CHANGE, len);
+	if (ret)
+		goto err_tx_req;
+
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, status, len);
+	if (ret)
+		goto err_tx_req;
+
+err_tx_req:
+	if (ret)
+		DRM_ERROR("hdcp tx status req failed: %d\n", ret);
+	return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_status_req);
+
+int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len)
+{
+	int ret;
+
+	ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP2_TX_IS_KM_STORED, 0, NULL);
+	if (ret)
+		goto err_is_km;
+
+	ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP2_TX_IS_KM_STORED, len);
+	if (ret)
+		goto err_is_km;
+
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, data, len);
+
+err_is_km:
+	if (ret)
+		DRM_ERROR("hdcp2 tx is km stored req failed: %d\n", ret);
+	return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_is_km_stored_req);
+
+int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp,
+					u8 *resp, u16 len)
+{
+	int ret;
+
+	ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP2_TX_STORE_KM, 0, NULL);
+	if (ret)
+		goto err_store_km;
+
+	ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP2_TX_STORE_KM, len);
+	if (ret)
+		goto err_store_km;
+
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, resp, len);
+
+err_store_km:
+	return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_store_km);
+
+int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+					u8 *rx_id, u8 *num)
+{
+	u32 mbox_size, i;
+	u8 header[4];
+	u8 temp;
+	int ret;
+
+	ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP_TX_IS_RECEIVER_ID_VALID, 0, NULL);
+	if (ret)
+		goto err_rx_id;
+
+	/* read the header of the message */
+	for (i = 0; i < 4; i++) {
+		ret = mhdp_hdcp_mailbox_read(mhdp);
+		if (ret < 0)
+			return ret;
+
+		header[i] = ret;
+	}
+
+	mbox_size = get_unaligned_be16(header + 2);
+
+	if (HDCP_TX_IS_RECEIVER_ID_VALID != header[0] ||
+			MB_MODULE_ID_HDCP_TX != header[1])
+		return -EINVAL;
+
+	/* First get num of receivers */
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, num, 1);
+	if (ret)
+		goto err_rx_id;
+
+	/* skip second data */
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, &temp, 1);
+	if (ret)
+		goto err_rx_id;
+
+	/* get receivers ID */
+	ret = mhdp_hdcp_mailbox_read_receive(mhdp, rx_id, mbox_size - 2);
+
+err_rx_id:
+	return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_is_receiver_id_valid);
+
+int cdns_mhdp_hdcp_tx_respond_receiver_id_valid(
+				struct cdns_mhdp_device *mhdp, u8 val)
+{
+	return mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				      HDCP_TX_RESPOND_RECEIVER_ID_VALID, sizeof(val), &val);
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_respond_receiver_id_valid);
+
+int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg)
+{
+	return mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+				     HDCP_TX_DO_AUTH_REQ, sizeof(msg), &msg);
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_reauth);
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h
new file mode 100644
index 000000000000..4ce76dd1ee58
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 NXP Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifndef CDNS_HDMI_HDCP_H
+#define CDNS_HDMI_HDCP_H
+
+int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp,
+					u8 *msg, u16 len);
+int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config);
+int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp,
+					u8 *status, u16 len);
+int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len);
+int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp,
+					u8 *reg, u16 len);
+int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+					u8 *rx_id, u8 *num);
+int cdns_mhdp_hdcp_tx_respond_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+					u8 val);
+int cdns_mhdp_hdcp_tx_test_keys(struct cdns_mhdp_device *mhdp, u8 type, u8 resp);
+int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg);
+
+int cdns_hdmi_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node);
+int cdns_hdmi_hdcp_enable(struct cdns_mhdp_device *mhdp);
+int cdns_hdmi_hdcp_disable(struct cdns_mhdp_device *mhdp);
+void cdns_hdmi_hdcp_atomic_check(struct drm_connector *connector,
+			   struct drm_connector_state *old_state,
+			   struct drm_connector_state *new_state);
+
+#endif /* CDNS_HDMI_HDCP_H */
diff --git a/include/drm/bridge/cdns-mhdp.h b/include/drm/bridge/cdns-mhdp.h
index 338fa55b8bdf..5752c47b1a16 100644
--- a/include/drm/bridge/cdns-mhdp.h
+++ b/include/drm/bridge/cdns-mhdp.h
@@ -388,6 +388,27 @@
 #define HDMI_TX_TEST				0xBB
 #define HDMI_TX_EDID_INTERNAL		0xF0
 
+/* HDCP General opcode */
+#define HDCP_GENERAL_SET_LC_128		0x00
+#define HDCP_GENERAL_SET_SEED		0x01
+
+/* HDCP TX opcode */
+#define HDCP_TX_CONFIGURATION				0x00
+#define HDCP2_TX_SET_PUBLIC_KEY_PARAMS		0x01
+#define HDCP2_TX_SET_DEBUG_RANDOM_NUMBERS	0x02
+#define HDCP2_TX_RESPOND_KM					0x03
+#define HDCP1_TX_SEND_KEYS					0x04
+#define HDCP1_TX_SEND_RANDOM_AN				0x05
+#define HDCP_TX_STATUS_CHANGE				0x06
+#define HDCP2_TX_IS_KM_STORED				0x07
+#define HDCP2_TX_STORE_KM					0x08
+#define HDCP_TX_IS_RECEIVER_ID_VALID		0x09
+#define HDCP_TX_RESPOND_RECEIVER_ID_VALID	0x0A
+#define HDCP_TX_TEST_KEYS					0x0B
+#define HDCP2_TX_SET_KM_KEY_PARAMS			0x0C
+#define HDCP_TX_SET_CP_IRQ					0x0D
+#define HDCP_TX_DO_AUTH_REQ					0x0E
+
 #define FW_STANDBY				0
 #define FW_ACTIVE				1
 
@@ -428,12 +449,16 @@
 #define EQ_PHASE_FAILED				BIT(6)
 #define FASE_LT_FAILED				BIT(7)
 
-#define DPTX_HPD_EVENT				BIT(0)
-#define DPTX_TRAINING_EVENT			BIT(1)
-#define HDCP_TX_STATUS_EVENT			BIT(4)
-#define HDCP2_TX_IS_KM_STORED_EVENT		BIT(5)
-#define HDCP2_TX_STORE_KM_EVENT			BIT(6)
-#define HDCP_TX_IS_RECEIVER_ID_VALID_EVENT	BIT(7)
+#define DPTX_HPD_EVENT						   BIT(0)
+#define HDMI_TX_HPD_EVENT					   BIT(0)
+#define HDMI_RX_5V_EVENT					   BIT(0)
+#define DPTX_TRAINING_EVENT					   BIT(1)
+#define HDMI_RX_SCDC_CHANGE_EVENT			   BIT(1)
+#define HDCPTX_STATUS_EVENT					   BIT(4)
+#define HDCPRX_STATUS_EVENT					   BIT(4)
+#define HDCPTX_IS_KM_STORED_EVENT			   BIT(5)
+#define HDCPTX_STORE_KM_EVENT				   BIT(6)
+#define HDCPTX_IS_RECEIVER_ID_VALID_EVENT	   BIT(7)
 
 #define TU_SIZE					30
 #define CDNS_DP_MAX_LINK_RATE	540000
@@ -442,6 +467,7 @@
 #define F_VIF_DATA_WIDTH(x) (((x) & ((1 << 2) - 1)) << 2)
 #define F_HDMI_MODE(x) (((x) & ((1 << 2) - 1)) << 0)
 #define F_GCP_EN(x) (((x) & ((1 << 1) - 1)) << 12)
+#define F_CLEAR_AVMUTE(x) (((x) & ((1 << 1) - 1)) << 14)
 #define F_DATA_EN(x) (((x) & ((1 << 1) - 1)) << 15)
 #define F_HDMI2_PREAMBLE_EN(x) (((x) & ((1 << 1) - 1)) << 18)
 #define F_PIC_3D(x) (((x) & ((1 << 4) - 1)) << 7)
@@ -662,6 +688,55 @@ struct cdns_plat_data {
 	char *plat_name;
 };
 
+/* HDCP */
+#define MAX_STORED_KM 64
+#define HDCP_PAIRING_M_LEN 16
+#define HDCP_PAIRING_M_EKH 16
+#define HDCP_PAIRING_R_ID 5
+
+/* HDCP2_TX_SET_DEBUG_RANDOM_NUMBERS */
+#define DEBUG_RANDOM_NUMBERS_KM_LEN 16
+#define DEBUG_RANDOM_NUMBERS_RN_LEN 8
+#define DEBUG_RANDOM_NUMBERS_KS_LEN 16
+#define DEBUG_RANDOM_NUMBERS_RIV_LEN 8
+#define DEBUG_RANDOM_NUMBERS_RTX_LEN 8
+
+struct hdcp_trans_pairing_data {
+	u8 receiver_id[HDCP_PAIRING_R_ID];
+	u8 m[HDCP_PAIRING_M_LEN];
+	u8 km[DEBUG_RANDOM_NUMBERS_KM_LEN];
+	u8 ekh[HDCP_PAIRING_M_EKH];
+};
+
+enum hdmi_hdcp_state {
+	HDCP_STATE_NO_AKSV,
+	HDCP_STATE_INACTIVE,
+	HDCP_STATE_ENABLING,
+	HDCP_STATE_AUTHENTICATING,
+	HDCP_STATE_REAUTHENTICATING,
+	HDCP_STATE_AUTHENTICATED,
+	HDCP_STATE_DISABLING,
+	HDCP_STATE_AUTH_FAILED
+};
+
+struct cdns_mhdp_hdcp {
+	struct mutex mutex;
+	u64 value; /* protected by hdcp_mutex */
+	struct delayed_work check_work;
+	struct work_struct prop_work;
+	u8 state;
+	u8 cancel;
+	u8 bus_type;
+	u8 config;
+	struct hdcp_trans_pairing_data pairing[MAX_STORED_KM];
+	u8 num_paired;
+
+	u8 events;
+	u8 sink_is_repeater;
+	u8 reauth_in_progress;
+	u8 hdcp_version;
+};
+
 struct cdns_mhdp_device {
 	void __iomem		*regs_base;
 	void __iomem		*regs_sec;
@@ -669,6 +744,7 @@ struct cdns_mhdp_device {
 	int bus_type;
 
 	struct device		*dev;
+	struct drm_device *drm_dev;
 
 	struct cdns_mhdp_connector  connector;
 	struct clk		*spdif_clk;
@@ -722,6 +798,7 @@ struct cdns_mhdp_device {
 	hdmi_codec_plugged_cb plugged_cb;
 	struct device *codec_dev;
 	enum drm_connector_status last_connector_result;
+	struct cdns_mhdp_hdcp hdcp;
 };
 
 u32 cdns_mhdp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset);
@@ -742,6 +819,7 @@ int cdns_mhdp_get_edid_block(void *mhdp, u8 *edid,
 int cdns_mhdp_train_link(struct cdns_mhdp_device *mhdp);
 int cdns_mhdp_set_video_status(struct cdns_mhdp_device *mhdp, int active);
 int cdns_mhdp_config_video(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_apb_conf(struct cdns_mhdp_device *mhdp, u8 sel);
 
 /* Audio */
 int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp,
@@ -770,8 +848,6 @@ int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
 int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
 					      u8 module_id, u8 opcode,
 					      u16 req_size);
-int cdns_mhdp_mailbox_read(struct cdns_mhdp_device *mhdp);
-
 void cdns_mhdp_infoframe_set(struct cdns_mhdp_device *mhdp,
 					u8 entry_id, u8 packet_len, u8 *packet, u8 packet_type);
 int cdns_hdmi_get_edid_block(void *data, u8 *edid, u32 block, size_t length);
-- 
2.29.2

