[RFC] drm/exynos: added hdcp driver for contents protection.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



HDCP stands for High-bandwidth Digital Content Protection.
This is a newer form of Digital Rights Management(secure DRM)
that was designed to control digital video and audio content.
Contains an integrated HDCP encryption engine for video/audio content protection.
supports version HDCP v1.1.
Exynos AP supports embedded HDCP key system.
The HDCP key value is fused during fabrication, based on customer's request.

Exynos HDCP scenario.
- start encryption.
- receive Bcaps, Bksv from peer device.
- check repeater caps from Bcaps.
- send An, Aksv to peer device.
- receive Rj from peer device.
- compare Ri,Rj. If same and not repeater, then start encryption.
- If not same, retry. If repeater, then start second authentication.
- stop encryption.

Signed-off-by: Eunchul Kim <chulspro.kim@xxxxxxxxxxx>
---
 drivers/gpu/drm/exynos/Kconfig          |    6 +
 drivers/gpu/drm/exynos/Makefile         |    1 +
 drivers/gpu/drm/exynos/exynos_drm_drv.c |   12 +
 drivers/gpu/drm/exynos/exynos_drm_drv.h |    1 +
 drivers/gpu/drm/exynos/exynos_hdcp.c    | 1164 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/exynos/exynos_hdcp.h    |   47 ++
 drivers/gpu/drm/exynos/exynos_hdmi.c    |   11 +
 drivers/gpu/drm/exynos/exynos_hdmi.h    |    7 +
 drivers/gpu/drm/exynos/regs-hdmi.h      |  177 +++++
 9 files changed, 1426 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/exynos/exynos_hdcp.c
 create mode 100644 drivers/gpu/drm/exynos/exynos_hdcp.h

diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig
index 1d1f1e5..93c2f00 100644
--- a/drivers/gpu/drm/exynos/Kconfig
+++ b/drivers/gpu/drm/exynos/Kconfig
@@ -34,6 +34,12 @@ config DRM_EXYNOS_HDMI
 	help
 	  Choose this option if you want to use Exynos HDMI for DRM.
 
+config DRM_EXYNOS_HDCP
+	bool "Exynos DRM HDCP"
+	depends on DRM_EXYNOS_HDMI
+	help
+	  Choose this option if you want to use Exynos HDCP in HDMI for DRM.
+
 config DRM_EXYNOS_VIDI
 	bool "Exynos DRM Virtual Display"
 	depends on DRM_EXYNOS
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile
index 639b49e..58d8fb7 100644
--- a/drivers/gpu/drm/exynos/Makefile
+++ b/drivers/gpu/drm/exynos/Makefile
@@ -14,6 +14,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD)	+= exynos_drm_fimd.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI)	+= exynos_hdmi.o exynos_mixer.o \
 					   exynos_ddc.o exynos_hdmiphy.o \
 					   exynos_drm_hdmi.o
+exynosdrm-$(CONFIG_DRM_EXYNOS_HDCP) += exynos_hdcp.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI)	+= exynos_drm_vidi.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_G2D)	+= exynos_drm_g2d.o
 exynosdrm-$(CONFIG_DRM_EXYNOS_IPP)	+= exynos_drm_ipp.o
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c
index e0a8e80..0d2ada1 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c
@@ -345,6 +345,11 @@ static int __init exynos_drm_init(void)
 #endif
 
 #ifdef CONFIG_DRM_EXYNOS_HDMI
+#ifdef CONFIG_DRM_EXYNOS_HDCP
+	ret = platform_driver_register(&hdcp_driver);
+	if (ret < 0)
+		goto out_hdcp;
+#endif
 	ret = platform_driver_register(&hdmi_driver);
 	if (ret < 0)
 		goto out_hdmi;
@@ -452,6 +457,10 @@ out_common_hdmi:
 out_mixer:
 	platform_driver_unregister(&hdmi_driver);
 out_hdmi:
+#ifdef CONFIG_DRM_EXYNOS_HDCP
+	platform_driver_unregister(&hdcp_driver);
+out_hdcp:
+#endif
 #endif
 
 #ifdef CONFIG_DRM_EXYNOS_FIMD
@@ -494,6 +503,9 @@ static void __exit exynos_drm_exit(void)
 	platform_driver_unregister(&exynos_drm_common_hdmi_driver);
 	platform_driver_unregister(&mixer_driver);
 	platform_driver_unregister(&hdmi_driver);
+#ifdef CONFIG_DRM_EXYNOS_HDCP
+	platform_driver_unregister(&hdcp_driver);
+#endif
 #endif
 
 #ifdef CONFIG_DRM_EXYNOS_VIDI
diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h
index f5a9774..c591ffc 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.h
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h
@@ -344,6 +344,7 @@ extern int exynos_platform_device_hdmi_register(void);
 void exynos_platform_device_hdmi_unregister(void);
 
 extern struct platform_driver fimd_driver;
+extern struct platform_driver hdcp_driver;
 extern struct platform_driver hdmi_driver;
 extern struct platform_driver mixer_driver;
 extern struct platform_driver exynos_drm_common_hdmi_driver;
diff --git a/drivers/gpu/drm/exynos/exynos_hdcp.c b/drivers/gpu/drm/exynos/exynos_hdcp.c
new file mode 100644
index 0000000..58a345c
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_hdcp.c
@@ -0,0 +1,1164 @@
+/*
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Authors:
+ *	Eunchul Kim <chulspro.kim@xxxxxxxxxxx>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+
+#include <drm/drmP.h>
+#include <drm/exynos_drm.h>
+#include "exynos_drm_drv.h"
+#include "exynos_drm_hdmi.h"
+#include "exynos_hdmi.h"
+#include "exynos_hdcp.h"
+#include "regs-hdmi.h"
+
+/*
+ * HDCP stands for High-bandwidth Digital Content Protection.
+ * contains an integrated HDCP encryption engine
+ * for video/audio content protection.
+ * supports version HDCP v1.1.
+ * Exynos supports embedded HDCP key system.
+ * The HDCP key value is fused during fabrication, based on customer's request.
+ *
+ * First part authentication protocol.
+ * The HDCP transmitter(Device A) can initiate authentication at any time.
+ * Authentication is initiated by the HDCP transmitter by sending an An, Aksv.
+ * An(64-bit psedo-random value), Aksv(Transmitter Key Selection Vector).
+ * The HDCP Receiver(Device B) generates Rj.
+ * The HDCP Receiver responds by sending a response message Bksv.
+ * Bksv(Receiver Key Selection Vector).
+ * If authentication was successfull, then Ri will be equal to Rj.
+ *
+ * Second part authentication protocol.
+ * The second part of the authentication protocol is required
+ * if the HDCP receiver is an HDCP repeater.
+ * The HDCP transmitter executes the second part of the protocol only
+ * when the REPEATER bit is set,
+ * indicating that the attached HDCP receiver is an HDCP repeater.
+ * This part of the protocol assembles a list of all downstream
+ * KSVs attached to the HDCP repeater through a permitted connection tree,
+ * enabling revocation support upstream.
+ *
+ * Third part authentication protocol.
+ * The third part of the authentication protocol occurs during
+ * the vertical blanking interval preceding the frame for which it applies.
+ * Each of the two HDCP devices calculates new cipher initialization values,
+ * Ki and Mi, and a third value Ri. and asynchronous polling every 2 seconds.
+ *
+ * Exynos scenario.
+ * 1. start encryption.
+ * 2. receive Bcaps, Bksv from peer device.
+ * 3. check repeater caps from Bcaps.
+ * 4. send An, Aksv to peer device.
+ * 5. receive Rj from peer device.
+ * 6. compare Ri, Rj. If same and not repeater, then start encryption.
+ * 7. If not same, retry. If repeater, then start second authentication.
+ * 8. stop encryption.
+ */
+
+/*
+ * TODO
+ * - need to fix compare timing.
+ * - need to fix dpms start timing.
+ */
+
+#define HDCP_AN_SIZE		8
+#define HDCP_AKSV_SIZE	5
+#define HDCP_BKSV_SIZE	5
+#define HDCP_MAX_KEY_SIZE	16
+#define HDCP_BCAPS_SIZE	1
+#define HDCP_BSTATUS_SIZE	2
+#define HDCP_SHA_1_HASH_SIZE	20
+#define HDCP_MAX_DEVS	128
+#define HDCP_KSV_SIZE	5
+
+#define HDCP_KSV_FIFO_READY	(0x1 << 5)
+#define HDCP_MAX_CASCADE_EXCEEDED	(0x1 << 3)
+#define HDCP_MAX_DEVS_EXCEEDED	(0x1 << 7)
+
+/* offset of HDCP port */
+#define HDCP_BKSV	0x00
+#define HDCP_RI	0x08
+#define HDCP_AKSV	0x10
+#define HDCP_AN	0x18
+#define HDCP_SHA1	0x20
+#define HDCP_BCAPS	0x40
+#define HDCP_BSTATUS	0x41
+#define HDCP_KSVFIFO	0x43
+
+#define HDCP_RI_LEN	2
+#define HDCP_RJ_LEN	2
+#define HDCP_DDC_DELAY	25
+#define HDCP_AKSV_DELAY	100
+#define HDCP_BKSV_DELAY	100
+#define HDCP_BCAPS_DELAY	100
+#define HDCP_LOADKEY_DELAY	120
+#define HDCP_RESET_DELAY	16
+#define HDCP_I2C_RETRIES	5
+#define HDCP_LOADKEY_RETRIES	1000
+#define HDCP_BKSV_RETRIES	14
+#define HDCP_REPEATER_RETRIES	50
+#define HDCP_REPEATER_KSV_RETRIES	10000
+#define HDCP_ENCRYPTION_RETRIES	10
+
+enum hdcp_error {
+	HDCP_ERR_MAX_CASCADE,
+	HDCP_ERR_MAX_DEVS,
+	HDCP_ERR_REPEATER_ILLEGAL_DEVICE,
+	HDCP_ERR_REPEATER_TIMEOUT,
+};
+
+enum hdcp_event {
+	HDCP_EVENT_STOP	= 1 << 0,
+	HDCP_EVENT_START	= 1 << 1,
+	HDCP_EVENT_READ_BKSV_START	= 1 << 2,
+	HDCP_EVENT_WRITE_AKSV_START	= 1 << 4,
+	HDCP_EVENT_CHECK_RI_START	= 1 << 8,
+	HDCP_EVENT_SECOND_AUTH_START	= 1 << 16,
+};
+
+/*
+ * A structure of event work information.
+ *
+ * @work: work structure.
+ * @event: event id of hdcp.
+ */
+struct hdcp_event_work {
+	struct work_struct	work;
+	u32	event;
+};
+
+/*
+ * A structure of context.
+ *
+ * @regs: memory mapped io registers.
+ * @ddc_port: hdmi ddc port.
+ * @event_work: work information of hdcp event.
+ * @wq: work queue struct.
+ * @is_repeater: true is repeater, false is sink.
+ * @hpd: HPD config value.
+ * @hdcp_mutex: mutex for HDCP.
+ * @powered : HDCP power state.
+ */
+struct hdcp_context {
+	void __iomem	*regs;
+	struct i2c_client *ddc_port;
+	struct hdcp_event_work event_work;
+	struct workqueue_struct	*wq;
+	bool is_repeater;
+	atomic_t *hpd;
+	struct mutex hdcp_mutex;
+	bool powered;
+};
+
+static struct i2c_client *hdcp_ddc;
+
+static inline u8 hdcp_is_streaming(struct hdcp_context *ctx)
+{
+	u8 hpd = atomic_read(ctx->hpd);
+
+	DRM_DEBUG_KMS("%s:hpd[%d]\n", __func__, hpd);
+
+	return hpd;
+}
+
+static int hdcp_i2c_recv(struct hdcp_context *ctx, u8 offset, u8 *buf, int len)
+{
+	struct i2c_client *client = ctx->ddc_port;
+	int ret, retries = HDCP_I2C_RETRIES;
+
+	struct i2c_msg msgs[] = {
+		[0] = {
+			.addr = client->addr,
+			.flags = 0,
+			.len = 1,
+			.buf = &offset
+		},
+		[1] = {
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = len,
+			.buf = buf
+		}
+	};
+
+	DRM_DEBUG_KMS("%s:offset[0x%x]len[0x%x]\n", __func__, offset, len);
+
+	/*
+	 * The core i2c driver will automatically retry the transfer if the
+	 * adapter reports EAGAIN. However, we find that bit-banging transfers
+	 * are susceptible to errors under a heavily loaded machine and
+	 * generate spurious NAKs and timeouts. Retrying the transfer
+	 * of the individual block a few times seems to overcome this.
+	 */
+	do {
+		if (!hdcp_is_streaming(ctx))
+			return 0;
+
+		ret = i2c_transfer(client->adapter, msgs, 2);
+		if (ret == -ENXIO)
+			goto err_i2c_recv;
+
+		if (ret < 0 || ret != 2)
+			DRM_ERROR("failed to recv %d retry.\n", ret);
+		else
+			break;
+
+		msleep(HDCP_DDC_DELAY);
+	} while (ret != 2 && --retries);
+
+	if (!retries)
+		goto err_i2c_recv;
+
+	DRM_DEBUG_KMS("%s:success to recv HDCP via I2C.\n", __func__);
+
+	return 0;
+
+err_i2c_recv:
+	DRM_ERROR("failed to recv HDCP via I2C.\n");
+	return ret;
+}
+
+static int hdcp_i2c_send(struct hdcp_context *ctx, u8 offset, u8 *buf, int len)
+{
+	struct i2c_client *client = ctx->ddc_port;
+	int ret, retries = HDCP_I2C_RETRIES;
+	u8 msg[len+1];
+
+	DRM_DEBUG_KMS("%s:offset[0x%x]len[0x%x]\n", __func__, offset, len);
+
+	msg[0] = offset;
+	memcpy(&msg[1], buf, len);
+
+	/*
+	 * The core i2c driver will automatically retry the transfer if the
+	 * adapter reports EAGAIN. However, we find that bit-banging transfers
+	 * are susceptible to errors under a heavily loaded machine and
+	 * generate spurious NAKs and timeouts. Retrying the transfer
+	 * of the individual block a few times seems to overcome this.
+	 */
+	do {
+		if (!hdcp_is_streaming(ctx))
+			return 0;
+
+		ret = i2c_master_send(client, msg, len+1);
+		if (ret == -ENXIO)
+			goto err_i2c_send;
+
+		if (ret < 0 || ret < len + 1)
+			DRM_ERROR("failed to send %d retry.\n", ret);
+		else
+			break;
+
+		msleep(HDCP_DDC_DELAY);
+	} while (ret != 2 && --retries);
+
+	if (!retries)
+		goto err_i2c_send;
+
+	DRM_DEBUG_KMS("%s:success to send HDCP via I2C.\n", __func__);
+
+	return 0;
+
+err_i2c_send:
+	DRM_ERROR("failed to send HDCP via I2C.\n");
+	return ret;
+}
+
+static inline u8 hdcp_reg_readb(struct hdcp_context *ctx, u32 reg_id)
+{
+	return readb(ctx->regs + reg_id);
+}
+
+static inline void hdcp_reg_readb_bytes(struct hdcp_context *ctx, u32 reg_id,
+		u8 *buf, int bytes)
+{
+	int i;
+
+	for (i = 0; i < bytes; ++i)
+		buf[i] = readb(ctx->regs + reg_id + i * 4);
+}
+
+static inline void hdcp_reg_writeb(struct hdcp_context *ctx, u32 reg_id,
+	u8 value)
+{
+	writeb(value, ctx->regs + reg_id);
+}
+
+static inline void hdcp_reg_writeb_bytes(struct hdcp_context *ctx,
+	u32 reg_id, u8 *buf, u32 size)
+{
+	int i;
+
+	for (i = 0; i < size; ++i)
+		writeb(buf[i], ctx->regs + reg_id + i * 4);
+}
+
+static inline void hdcp_reg_writeb_mask(struct hdcp_context *ctx,
+	u32 reg_id, u8 value, u8 mask)
+{
+	u32 old = readb(ctx->regs + reg_id);
+	value = (value & mask) | (old & ~mask);
+	writeb(value, ctx->regs + reg_id);
+}
+
+static void hdcp_set_int_mask(struct hdcp_context *ctx, u8 mask, bool enable)
+{
+	DRM_DEBUG_KMS("%s:enable[%d]\n", __func__, enable);
+
+	if (enable) {
+		mask |= HDMI_INTC_EN_GLOBAL;
+		hdcp_reg_writeb_mask(ctx, HDMI_INTC_CON, ~0, mask);
+	} else
+		hdcp_reg_writeb_mask(ctx, HDMI_INTC_CON, 0,
+			HDMI_INTC_EN_GLOBAL);
+}
+
+static void hdcp_sw_reset(struct hdcp_context *ctx)
+{
+	u8 val;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	val = hdcp_reg_readb(ctx, HDMI_INTC_CON);
+
+	hdcp_set_int_mask(ctx, HDMI_INTC_EN_HPD_PLUG, 0);
+	hdcp_set_int_mask(ctx, HDMI_INTC_EN_HPD_UNPLUG, 0);
+
+	hdcp_reg_writeb_mask(ctx, HDMI_HPD, ~0, HDMI_HPD_SEL_I_HPD);
+	hdcp_reg_writeb_mask(ctx, HDMI_HPD, 0, HDMI_SW_HPD_PLUGGED);
+	hdcp_reg_writeb_mask(ctx, HDMI_HPD, ~0, HDMI_SW_HPD_PLUGGED);
+	hdcp_reg_writeb_mask(ctx, HDMI_HPD, 0, HDMI_HPD_SEL_I_HPD);
+
+	if (val & HDMI_INTC_EN_HPD_PLUG)
+		hdcp_set_int_mask(ctx, HDMI_INTC_EN_HPD_PLUG, 1);
+
+	if (val & HDMI_INTC_EN_HPD_UNPLUG)
+		hdcp_set_int_mask(ctx, HDMI_INTC_EN_HPD_UNPLUG, 1);
+}
+
+static void hdcp_encryption(struct hdcp_context *ctx, bool enable)
+{
+	DRM_DEBUG_KMS("%s:enable[%d]\n", __func__, enable);
+
+	/* hdcp encoder control */
+	if (enable)
+		hdcp_reg_writeb_mask(ctx, HDMI_ENC_EN, ~0,
+			HDMI_HDCP_ENC_ENABLE);
+	else
+		hdcp_reg_writeb_mask(ctx, HDMI_ENC_EN, 0,
+			HDMI_HDCP_ENC_ENABLE);
+}
+
+static int hdcp_loadkey(struct hdcp_context *ctx)
+{
+	u8 val;
+	int retries = HDCP_LOADKEY_RETRIES;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	hdcp_reg_writeb_mask(ctx, HDMI_EFUSE_CTRL, ~0,
+		HDMI_EFUSE_CTRL_HDCP_KEY_READ);
+
+	do {
+		val = hdcp_reg_readb(ctx, HDMI_EFUSE_STATUS);
+		if (val & HDMI_EFUSE_ECC_DONE)
+			break;
+		mdelay(1);
+	} while (--retries);
+
+	if (!retries)
+		goto hdcp_err;
+
+	val = hdcp_reg_readb(ctx, HDMI_EFUSE_STATUS);
+
+	if (val & HDMI_EFUSE_ECC_FAIL)
+		goto hdcp_err;
+
+	DRM_DEBUG_KMS("%s:hdcp key read success.\n", __func__);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to read EFUSE val.\n");
+	return -EINVAL;
+}
+
+static void hdcp_poweron(struct hdcp_context *ctx)
+{
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	mutex_lock(&ctx->hdcp_mutex);
+	if (ctx->powered)
+		goto out;
+
+	hdcp_sw_reset(ctx);
+	hdcp_encryption(ctx, false);
+
+	msleep(HDCP_LOADKEY_DELAY);
+	if (hdcp_loadkey(ctx) < 0) {
+		DRM_ERROR("failed to load hdcp key.\n");
+		goto out;
+	}
+
+	hdcp_reg_writeb(ctx, HDMI_GCP_CON, HDMI_GCP_CON_NO_TRAN);
+	hdcp_reg_writeb(ctx, HDMI_STATUS_EN, HDMI_INT_EN_ALL);
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL1, HDMI_HDCP_CP_DESIRED_EN);
+
+	hdcp_set_int_mask(ctx, HDMI_INTC_EN_HDCP, 1);
+
+	ctx->powered = true;
+
+	DRM_DEBUG_KMS("%s:start encription.\n", __func__);
+
+out:
+	mutex_unlock(&ctx->hdcp_mutex);
+}
+
+static void hdcp_poweroff(struct hdcp_context *ctx)
+{
+	u8 val;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	mutex_lock(&ctx->hdcp_mutex);
+	if (!ctx->powered)
+		goto out;
+
+	ctx->powered = false;
+
+	hdcp_set_int_mask(ctx, HDMI_INTC_EN_HDCP, 0);
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL1, 0x0);
+	hdcp_reg_writeb_mask(ctx, HDMI_HPD, 0, HDMI_HPD_SEL_I_HPD);
+
+	val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN |
+		HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN;
+	hdcp_reg_writeb_mask(ctx, HDMI_STATUS_EN, 0, val);
+	hdcp_reg_writeb_mask(ctx, HDMI_STATUS_EN, ~0, val);
+
+	hdcp_reg_writeb_mask(ctx, HDMI_SYS_STATUS, ~0, HDMI_INT_EN_ALL);
+
+	hdcp_encryption(ctx, false);
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CHECK_RESULT, HDMI_HDCP_CLR_ALL_RESULTS);
+
+	ctx->regs = NULL;
+
+	DRM_DEBUG_KMS("%s:stop encription.\n", __func__);
+
+out:
+	mutex_unlock(&ctx->hdcp_mutex);
+}
+
+static int hdcp_recv_bcaps(struct hdcp_context *ctx)
+{
+	u8 bcaps = 0;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	if (hdcp_i2c_recv(ctx, HDCP_BCAPS, &bcaps,
+	    HDCP_BCAPS_SIZE) < 0)
+		goto hdcp_err;
+
+	DRM_DEBUG_KMS("%s:Bcaps[0x%x]\n", __func__, bcaps);
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_BCAPS, bcaps);
+
+	if (bcaps & HDMI_HDCP_BCAPS_REPEATER)
+		ctx->is_repeater = true;
+	else
+		ctx->is_repeater = false;
+
+	DRM_DEBUG_KMS("%s:is_repeater[%s]\n", __func__,
+		ctx->is_repeater ? "REPEAT" : "SINK");
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to recv bcaps.\n");
+	return -EIO;
+}
+
+static int hdcp_recv_bksv(struct hdcp_context *ctx)
+{
+	u8 bksv[HDCP_BKSV_SIZE];
+	int i, j;
+	u32 one = 0, zero = 0, result = 0;
+	int retries = HDCP_BKSV_RETRIES;
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	memset(bksv, 0x0, sizeof(bksv));
+
+	do {
+		if (hdcp_i2c_recv(ctx, HDCP_BKSV, bksv,
+		    HDCP_BKSV_SIZE) < 0)
+			goto hdcp_err;
+
+		for (i = 0; i < HDCP_BKSV_SIZE; i++)
+			DRM_DEBUG_KMS("%s:Bksv[%d][0x%x]\n",
+				__func__, i, bksv[i]);
+
+		for (i = 0; i < HDCP_BKSV_SIZE; i++) {
+			for (j = 0; j < 8; j++) {
+				result = bksv[i] & (0x1 << j);
+
+				if (result == 0)
+					zero++;
+				else
+					one++;
+			}
+		}
+
+		if ((zero == 20) && (one == 20)) {
+			DRM_DEBUG_KMS("%s:success.\n",	__func__);
+
+			hdcp_reg_writeb_bytes(ctx, HDMI_HDCP_BKSV(0), bksv,
+				HDCP_BKSV_SIZE);
+			break;
+		}
+
+		DRM_ERROR("invalid bksv retries[%d]\n", retries);
+		msleep(HDCP_BKSV_DELAY);
+	} while (--retries);
+
+	if (!retries)
+		goto hdcp_err;
+
+	DRM_DEBUG_KMS("%s:retries[%d]\n", __func__, retries);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to recv bksv.\n");
+	return -EIO;
+}
+
+static int hdcp_recv_b_caps_ksv(struct hdcp_context *ctx)
+{
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	if (hdcp_recv_bcaps(ctx) < 0)
+		goto hdcp_err;
+
+	if (hdcp_recv_bksv(ctx) < 0)
+		goto hdcp_err;
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to check bksv.\n");
+	return -EIO;
+}
+
+static int hdcp_check_repeater(struct hdcp_context *ctx)
+{
+	int ret = -EINVAL, val, i;
+	u32 dev_cnt;
+	u8 bcaps = 0;
+	u8 status[HDCP_BSTATUS_SIZE];
+	u8 rx_v[HDCP_SHA_1_HASH_SIZE];
+	u8 ksv_list[HDCP_MAX_DEVS * HDCP_KSV_SIZE];
+	int cnt;
+	int retries1 = HDCP_REPEATER_RETRIES;
+	int retries2;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	memset(status, 0x0, sizeof(status));
+	memset(rx_v, 0x0, sizeof(rx_v));
+	memset(ksv_list, 0x0, sizeof(ksv_list));
+
+	do {
+		if (hdcp_recv_bcaps(ctx) < 0)
+			goto hdcp_err;
+
+		bcaps = hdcp_reg_readb(ctx, HDMI_HDCP_BCAPS);
+		if (bcaps & HDCP_KSV_FIFO_READY) {
+			DRM_DEBUG_KMS("%s:ksv fifo not ready.\n", __func__);
+			break;
+		}
+
+		msleep(HDCP_BCAPS_DELAY);
+	} while (--retries1);
+
+	if (!retries1) {
+		ret = HDCP_ERR_REPEATER_TIMEOUT;
+		goto hdcp_err;
+	}
+
+	DRM_DEBUG_KMS("%s:ksv fifo ready.\n", __func__);
+
+	if (hdcp_i2c_recv(ctx, HDCP_BSTATUS, status,
+	    HDCP_BSTATUS_SIZE) < 0)
+		goto hdcp_err;
+
+	if (status[1] & HDCP_MAX_CASCADE_EXCEEDED) {
+		ret = HDCP_ERR_MAX_CASCADE;
+		goto hdcp_err;
+	} else if (status[0] & HDCP_MAX_DEVS_EXCEEDED) {
+		ret = HDCP_ERR_MAX_DEVS;
+		goto hdcp_err;
+	}
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_BSTATUS_0, status[0]);
+	hdcp_reg_writeb(ctx, HDMI_HDCP_BSTATUS_1, status[1]);
+
+	DRM_DEBUG_KMS("%s:status0[0x%x],status1[0x%x]\n",
+		__func__, status[0], status[1]);
+
+	dev_cnt = status[0] & 0x7f;
+	DRM_DEBUG_KMS("%s:dev_cnt[%d]\n", __func__, dev_cnt);
+
+	if (dev_cnt) {
+		if (hdcp_i2c_recv(ctx, HDCP_KSVFIFO, ksv_list,
+			dev_cnt * HDCP_KSV_SIZE) < 0)
+			goto hdcp_err;
+
+		cnt = 0;
+		do {
+			hdcp_reg_writeb_bytes(ctx, HDMI_HDCP_KSV_LIST(0),
+					&ksv_list[cnt * 5], HDCP_KSV_SIZE);
+
+			val = HDMI_HDCP_KSV_WRITE_DONE;
+			if (cnt == dev_cnt - 1)
+				val |= HDMI_HDCP_KSV_END;
+
+			hdcp_reg_writeb(ctx, HDMI_HDCP_KSV_LIST_CON, val);
+
+			if (cnt < dev_cnt - 1) {
+				retries2 = HDCP_REPEATER_KSV_RETRIES;
+				do {
+					val = hdcp_reg_readb(ctx,
+						HDMI_HDCP_KSV_LIST_CON);
+					if (val & HDMI_HDCP_KSV_READ)
+						break;
+				} while (--retries2);
+
+				if (!retries2)
+					DRM_DEBUG_KMS("%s:ksv not readed.\n",
+						__func__);
+			}
+			cnt++;
+		} while (cnt < dev_cnt);
+	} else
+		hdcp_reg_writeb(ctx, HDMI_HDCP_KSV_LIST_CON,
+			HDMI_HDCP_KSV_LIST_EMPTY);
+
+	if (hdcp_i2c_recv(ctx, HDCP_SHA1, rx_v,
+	    HDCP_SHA_1_HASH_SIZE) < 0)
+		goto hdcp_err;
+
+	for (i = 0; i < HDCP_SHA_1_HASH_SIZE; i++)
+		DRM_DEBUG_KMS("%s:SHA-1 rx[0x%x]\n", __func__, rx_v[i]);
+
+	hdcp_reg_writeb_bytes(ctx, HDMI_HDCP_SHA1(0), rx_v,
+		HDCP_SHA_1_HASH_SIZE);
+
+	val = hdcp_reg_readb(ctx, HDMI_HDCP_SHA_RESULT);
+	if (val & HDMI_HDCP_SHA_VALID_RD) {
+		if (val & HDMI_HDCP_SHA_VALID) {
+			DRM_DEBUG_KMS("%s:SHA-1 result is ok.\n", __func__);
+			hdcp_reg_writeb(ctx, HDMI_HDCP_SHA_RESULT, 0x0);
+		} else {
+			DRM_DEBUG_KMS("%s:SHA-1 result is not vaild.\n",
+				__func__);
+			hdcp_reg_writeb(ctx, HDMI_HDCP_SHA_RESULT, 0x0);
+			goto hdcp_err;
+		}
+	} else {
+		DRM_DEBUG_KMS("%s:SHA-1 result is not ready.\n", __func__);
+		hdcp_reg_writeb(ctx, HDMI_HDCP_SHA_RESULT, 0x0);
+		goto hdcp_err;
+	}
+
+	DRM_DEBUG_KMS("%s:done.\n", __func__);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to check repeater.\n");
+	return ret;
+}
+
+static int hdcp_start_encryption(struct hdcp_context *ctx)
+{
+	u8 val;
+	int retries = HDCP_ENCRYPTION_RETRIES;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	do {
+		val = hdcp_reg_readb(ctx, HDMI_SYS_STATUS);
+
+		if (val & HDMI_AUTHEN_ACK_AUTH) {
+			hdcp_encryption(ctx, true);
+			break;
+		}
+
+		mdelay(1);
+	} while (--retries);
+
+	if (!retries)
+		goto hdcp_err;
+
+	DRM_DEBUG_KMS("%s:retries[%d]\n", __func__, retries);
+
+	return 0;
+
+hdcp_err:
+	hdcp_encryption(ctx, false);
+	DRM_ERROR("failed to start encription.\n");
+	return -EIO;
+}
+
+static int hdcp_start_second_auth(struct hdcp_context *ctx)
+{
+	int ret = 0;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	ret = hdcp_check_repeater(ctx);
+
+	if (ret) {
+		DRM_DEBUG_KMS("%s:ret[%d]\n", __func__, ret);
+
+		switch (ret) {
+		case HDCP_ERR_REPEATER_ILLEGAL_DEVICE:
+			hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL2, 0x1);
+			mdelay(1);
+			hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL2, 0x0);
+
+			DRM_DEBUG_KMS("%s:illegal device.\n", __func__);
+			break;
+		case HDCP_ERR_REPEATER_TIMEOUT:
+			hdcp_reg_writeb_mask(ctx, HDMI_HDCP_CTRL1, ~0,
+					HDMI_HDCP_SET_REPEATER_TIMEOUT);
+			hdcp_reg_writeb_mask(ctx, HDMI_HDCP_CTRL1, 0,
+					HDMI_HDCP_SET_REPEATER_TIMEOUT);
+
+			DRM_DEBUG_KMS("%s:timeout.\n", __func__);
+			break;
+		case HDCP_ERR_MAX_CASCADE:
+			DRM_DEBUG_KMS("%s:exceeded MAX_CASCADE.\n", __func__);
+			break;
+		case HDCP_ERR_MAX_DEVS:
+			DRM_DEBUG_KMS("%s:exceeded MAX_DEVS.\n", __func__);
+			break;
+		default:
+			break;
+		}
+
+		goto hdcp_err;
+	}
+
+	hdcp_start_encryption(ctx);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to check second authentication.\n");
+	return -EIO;
+}
+
+static int hdcp_send_key(struct hdcp_context *ctx, int size,
+	int reg, int offset)
+{
+	u8 buf[HDCP_MAX_KEY_SIZE];
+	int cnt, zero = 0;
+	int i;
+
+	DRM_DEBUG_KMS("%s:size[%d]reg[0x%x]offset[0x%x]\n", __func__,
+		size, reg, offset);
+
+	memset(buf, 0x0, sizeof(buf));
+	hdcp_reg_readb_bytes(ctx, reg, buf, size);
+
+	for (cnt = 0; cnt < size; cnt++) {
+		DRM_DEBUG_KMS("%s:%s:cnt[%d]buf[0x%x]\n", __func__,
+			offset == HDCP_AN ? "An" : "Aksv", cnt, buf[cnt]);
+		if (buf[cnt] == 0)
+			zero++;
+	}
+
+	if (zero == size) {
+		DRM_ERROR("%s: %s is null.\n", __func__,
+			offset == HDCP_AN ? "An" : "Aksv");
+		goto hdcp_err;
+	}
+
+	if (hdcp_i2c_send(ctx, offset, buf, size) < 0)
+		goto hdcp_err;
+
+	for (i = 1; i < size + 1; i++)
+		DRM_DEBUG_KMS("%s: %s %d[0x%x].\n", __func__,
+			offset == HDCP_AN ? "An" : "Aksv", i-1, buf[i-1]);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to write %s key.\n",
+		offset == HDCP_AN ? "An" : "Aksv");
+	return -EIO;
+}
+
+static int hdcp_send_aksv(struct hdcp_context *ctx)
+{
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	if (hdcp_send_key(ctx, HDCP_AN_SIZE, HDMI_HDCP_AN(0), HDCP_AN) < 0) {
+		DRM_ERROR("failed to write an.\n");
+		goto hdcp_err;
+	}
+
+	DRM_DEBUG_KMS("%s:write an is done.\n", __func__);
+
+	if (hdcp_send_key(ctx, HDCP_AKSV_SIZE, HDMI_HDCP_AKSV(0),
+	    HDCP_AKSV) < 0) {
+		DRM_ERROR("failed to send aksv.\n");
+		goto hdcp_err;
+	}
+
+	msleep(HDCP_AKSV_DELAY);
+
+	DRM_DEBUG_KMS("%s:write aksv is done.\n", __func__);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to write aksv.\n");
+	return -EIO;
+}
+
+static int hdcp_check_ri_rj(struct hdcp_context *ctx)
+{
+	u8 ri[HDCP_RI_LEN] = {0, 0};
+	u8 rj[HDCP_RJ_LEN] = {0, 0};
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		goto hdcp_err;
+
+	ri[0] = hdcp_reg_readb(ctx, HDMI_HDCP_RI_0);
+	ri[1] = hdcp_reg_readb(ctx, HDMI_HDCP_RI_1);
+
+	if (hdcp_i2c_recv(ctx, HDCP_RI, rj, HDCP_RJ_LEN) < 0) {
+		DRM_ERROR("failed to receive rj.\n");
+		goto hdcp_err;
+	}
+
+	DRM_DEBUG_KMS("%s:ri[0x%x,0x%x]\n", __func__, ri[0] , ri[1]);
+	DRM_DEBUG_KMS("%s:rj[0x%x,0x%x]\n", __func__, rj[0] , rj[1]);
+
+	if ((ri[0] == rj[0]) && (ri[1] == rj[1]) && (ri[0] | ri[1]))
+		hdcp_reg_writeb(ctx, HDMI_HDCP_CHECK_RESULT,
+				HDMI_HDCP_RI_MATCH_RESULT_Y);
+	else {
+		hdcp_reg_writeb(ctx, HDMI_HDCP_CHECK_RESULT,
+				HDMI_HDCP_RI_MATCH_RESULT_N);
+
+		DRM_DEBUG_KMS("%s:failed to compare ri with rj.\n", __func__);
+		return 0;
+	}
+
+	if (!ctx->is_repeater)
+		hdcp_start_encryption(ctx);
+
+	DRM_DEBUG_KMS("%s:done.\n", __func__);
+
+	return 0;
+
+hdcp_err:
+	DRM_ERROR("failed to check ri, rj.\n");
+	return -EIO;
+}
+
+static void hdcp_reset_auth(struct hdcp_context *ctx)
+{
+	u8 val;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!hdcp_is_streaming(ctx))
+		return;
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL1, 0x0);
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CTRL2, 0x0);
+
+	hdcp_encryption(ctx, false);
+
+	val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN |
+		HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN;
+	hdcp_reg_writeb_mask(ctx, HDMI_STATUS_EN, 0, val);
+
+	hdcp_reg_writeb(ctx, HDMI_HDCP_CHECK_RESULT, HDMI_HDCP_CLR_ALL_RESULTS);
+
+	/* need some delay (at least 1 frame) */
+	mdelay(HDCP_RESET_DELAY);
+
+	hdcp_sw_reset(ctx);
+
+	val = HDMI_UPDATE_RI_INT_EN | HDMI_WRITE_INT_EN |
+		HDMI_WATCHDOG_INT_EN | HDMI_WTFORACTIVERX_INT_EN;
+	hdcp_reg_writeb_mask(ctx, HDMI_STATUS_EN, ~0, val);
+	hdcp_reg_writeb_mask(ctx, HDMI_HDCP_CTRL1, ~0, HDMI_HDCP_CP_DESIRED_EN);
+	hdcp_reg_writeb_mask(ctx, HDMI_INTC_CON, 0, HDMI_INTC_EN_HDCP);
+
+	DRM_DEBUG_KMS("%s:done.\n", __func__);
+}
+
+static void hdcp_event_wq(struct work_struct *work)
+{
+	struct hdcp_context *ctx = container_of((struct hdcp_event_work *)work,
+		struct hdcp_context, event_work);
+	struct hdcp_event_work *event_work = (struct hdcp_event_work *)work;
+
+	DRM_DEBUG_KMS("%s:event[0x%x]\n", __func__, event_work->event);
+
+	if (!ctx->powered)
+		return;
+
+	if (!hdcp_is_streaming(ctx))
+		return;
+
+	if (event_work->event & HDCP_EVENT_READ_BKSV_START) {
+		if (hdcp_recv_b_caps_ksv(ctx) < 0)
+			goto hdcp_err;
+		else
+			event_work->event &= ~HDCP_EVENT_READ_BKSV_START;
+	}
+
+	if (event_work->event & HDCP_EVENT_SECOND_AUTH_START) {
+		if (hdcp_start_second_auth(ctx) < 0)
+			goto hdcp_err;
+		else
+			event_work->event &= ~HDCP_EVENT_SECOND_AUTH_START;
+	}
+
+	if (event_work->event & HDCP_EVENT_WRITE_AKSV_START) {
+		if (hdcp_send_aksv(ctx) < 0)
+			goto hdcp_err;
+		else
+			event_work->event  &= ~HDCP_EVENT_WRITE_AKSV_START;
+	}
+
+	if (event_work->event & HDCP_EVENT_CHECK_RI_START) {
+		if (hdcp_check_ri_rj(ctx) < 0)
+			goto hdcp_err;
+		else
+			event_work->event &= ~HDCP_EVENT_CHECK_RI_START;
+	}
+
+	return;
+
+hdcp_err:
+	hdcp_reset_auth(ctx);
+}
+
+static void hdcp_dpms(void *data, int mode)
+{
+	struct hdcp_context *ctx = data;
+
+	DRM_DEBUG_KMS("%s:mode[%d]\n", __func__, mode);
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		hdcp_poweron(ctx);
+		break;
+	case DRM_MODE_DPMS_STANDBY:
+	case DRM_MODE_DPMS_SUSPEND:
+	case DRM_MODE_DPMS_OFF:
+		hdcp_poweroff(ctx);
+		break;
+	default:
+		DRM_DEBUG_KMS("unknown dpms mode: %d\n", mode);
+		break;
+	}
+}
+
+static void hdcp_commit(void *data)
+{
+	struct hdcp_context *ctx = data;
+	u32 event = 0;
+	u8 flag;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (!ctx->powered)
+		return;
+
+	if (!hdcp_is_streaming(ctx))
+		return;
+
+	flag = hdcp_reg_readb(ctx, HDMI_SYS_STATUS);
+
+	DRM_DEBUG_KMS("%s:flag[0x%x]\n", __func__, flag);
+
+	if (flag & HDMI_WTFORACTIVERX_INT_OCC) {
+		event |= HDCP_EVENT_READ_BKSV_START;
+		hdcp_reg_writeb_mask(ctx, HDMI_SYS_STATUS, ~0,
+			HDMI_WTFORACTIVERX_INT_OCC);
+		hdcp_reg_writeb(ctx, HDMI_HDCP_I2C_INT, 0x0);
+	}
+
+	if (flag & HDMI_WRITE_INT_OCC) {
+		event |= HDCP_EVENT_WRITE_AKSV_START;
+		hdcp_reg_writeb_mask(ctx, HDMI_SYS_STATUS, ~0,
+			HDMI_WRITE_INT_OCC);
+		hdcp_reg_writeb(ctx, HDMI_HDCP_AN_INT, 0x0);
+	}
+
+	if (flag & HDMI_UPDATE_RI_INT_OCC) {
+		event |= HDCP_EVENT_CHECK_RI_START;
+		hdcp_reg_writeb_mask(ctx, HDMI_SYS_STATUS, ~0,
+			HDMI_UPDATE_RI_INT_OCC);
+		hdcp_reg_writeb(ctx, HDMI_HDCP_RI_INT, 0x0);
+	}
+
+	if (flag & HDMI_WATCHDOG_INT_OCC) {
+		event |= HDCP_EVENT_SECOND_AUTH_START;
+		hdcp_reg_writeb_mask(ctx, HDMI_SYS_STATUS, ~0,
+			HDMI_WATCHDOG_INT_OCC);
+		hdcp_reg_writeb(ctx, HDMI_HDCP_WDT_INT, 0x0);
+	}
+
+	if (flag & HDMI_AUTHEN_ACK_AUTH)
+		DRM_DEBUG_KMS("%s:authentication success.\n", __func__);
+
+	if (!event) {
+		DRM_DEBUG_KMS("%s:unknown irq\n", __func__);
+		return;
+	}
+
+	ctx->event_work.event |= event;
+	queue_work(ctx->wq, (struct work_struct *)&ctx->event_work);
+}
+
+static struct exynos_hdcp_ops hdmi_ops = {
+	/* manager */
+	.dpms = hdcp_dpms,
+	.commit = hdcp_commit,
+};
+
+void exynos_hdcp_attach_ddc_client(struct i2c_client *ddc)
+{
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (ddc)
+		hdcp_ddc = ddc;
+}
+
+int exynos_hdcp_register(void *data, void __iomem *regs, atomic_t *hpd)
+{
+	struct hdcp_context *ctx = data;
+
+	DRM_DEBUG_KMS("%s:regs[0x%x]\n", __func__, (int)regs);
+
+	if (!hdcp_ddc) {
+		DRM_ERROR("failed to get ddc port.\n");
+		return -ENODEV;
+	}
+
+	ctx->ddc_port = hdcp_ddc;
+	ctx->hpd = hpd;
+	ctx->regs = regs;
+
+	return 0;
+}
+
+static int __devinit hdcp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct exynos_drm_hdmi_context *drm_hdcp_ctx;
+	struct hdcp_context *ctx;
+	int ret = -EINVAL;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	drm_hdcp_ctx = devm_kzalloc(dev, sizeof(struct exynos_drm_hdmi_context),
+		GFP_KERNEL);
+	if (!drm_hdcp_ctx) {
+		DRM_ERROR("failed to allocate common hdmi context.\n");
+		return -ENOMEM;
+	}
+
+	ctx = devm_kzalloc(dev, sizeof(struct hdcp_context), GFP_KERNEL);
+	if (!ctx) {
+		DRM_ERROR("failed to get ctx memory.\n");
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	ctx->wq = create_workqueue("hdcp");
+	if (!ctx->wq) {
+		ret = -ENOMEM;
+		goto err_workqueue;
+	}
+
+	INIT_WORK((struct work_struct *)&ctx->event_work, hdcp_event_wq);
+	drm_hdcp_ctx->ctx = (void *)ctx;
+
+	mutex_init(&ctx->hdcp_mutex);
+
+	platform_set_drvdata(pdev, drm_hdcp_ctx);
+
+	exynos_hdcp_ops_register(&hdmi_ops);
+
+	dev_info(dev, "drm hdcp registered successfully.\n");
+
+	return 0;
+
+err_workqueue:
+	devm_kfree(dev, ctx);
+err_free:
+	devm_kfree(dev, drm_hdcp_ctx);
+	return ret;
+}
+
+static int __devexit hdcp_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct exynos_drm_hdmi_context *drm_hdcp_ctx =
+		platform_get_drvdata(pdev);
+	struct hdcp_context *ctx = drm_hdcp_ctx->ctx;
+
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	devm_kfree(dev, ctx);
+	devm_kfree(dev, drm_hdcp_ctx);
+
+	return 0;
+}
+
+struct platform_driver hdcp_driver = {
+	.probe		= hdcp_probe,
+	.remove		= __devexit_p(hdcp_remove),
+	.driver		= {
+		.name	= "exynos-hdcp",
+		.owner	= THIS_MODULE,
+	},
+};
diff --git a/drivers/gpu/drm/exynos/exynos_hdcp.h b/drivers/gpu/drm/exynos/exynos_hdcp.h
new file mode 100644
index 0000000..86d0c79
--- /dev/null
+++ b/drivers/gpu/drm/exynos/exynos_hdcp.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *
+ * Authors:
+ *	Eunchul Kim <chulspro.kim@xxxxxxxxxxx>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _EXYNOS_HDCP_H_
+#define _EXYNOS_HDCP_H_
+
+#ifdef CONFIG_DRM_EXYNOS_HDCP
+extern void exynos_hdcp_attach_ddc_client(struct i2c_client *ddc);
+extern int exynos_hdcp_register(void *data, void __iomem *regs, atomic_t *hpd);
+#else
+static inline void exynos_hdcp_attach_ddc_client(struct i2c_client *ddc)
+{
+
+}
+
+static inline int exynos_hdcp_register(void *data, void __iomem *regs,
+	atomic_t *hpd)
+{
+	return -ENODEV;
+}
+#endif
+
+#endif /* _EXYNOS_HDCP_H_ */
+
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c
index 2c46b6c..f8dd504 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.c
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.c
@@ -42,6 +42,7 @@
 #include "exynos_drm_hdmi.h"
 
 #include "exynos_hdmi.h"
+#include "exynos_hdcp.h"
 
 #include <linux/gpio.h>
 #include <media/s5p_hdmi.h>
@@ -1253,6 +1254,16 @@ static void hdmi_v14_regs_dump(struct hdmi_context *hdata, char *prefix)
 #undef DUMPREG
 }
 
+static struct exynos_hdcp_ops *hdcp_ops;
+
+void exynos_hdcp_ops_register(struct exynos_hdcp_ops *ops)
+{
+	DRM_DEBUG_KMS("%s\n", __func__);
+
+	if (ops)
+		hdcp_ops = ops;
+}
+
 static void hdmi_regs_dump(struct hdmi_context *hdata, char *prefix)
 {
 	if (hdata->type == HDMI_TYPE13)
diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.h b/drivers/gpu/drm/exynos/exynos_hdmi.h
index 1c3b6d8..f4ae937 100644
--- a/drivers/gpu/drm/exynos/exynos_hdmi.h
+++ b/drivers/gpu/drm/exynos/exynos_hdmi.h
@@ -28,8 +28,15 @@
 #ifndef _EXYNOS_HDMI_H_
 #define _EXYNOS_HDMI_H_
 
+struct exynos_hdcp_ops {
+	/* manager */
+	void (*dpms)(void *ctx, int mode);
+	void (*commit)(void *ctx);
+};
+
 void hdmi_attach_ddc_client(struct i2c_client *ddc);
 void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy);
+void exynos_hdcp_ops_register(struct exynos_hdcp_ops *ops);
 
 extern struct i2c_driver hdmiphy_driver;
 extern struct i2c_driver ddc_driver;
diff --git a/drivers/gpu/drm/exynos/regs-hdmi.h b/drivers/gpu/drm/exynos/regs-hdmi.h
index ef1b3eb..7b62c4c 100644
--- a/drivers/gpu/drm/exynos/regs-hdmi.h
+++ b/drivers/gpu/drm/exynos/regs-hdmi.h
@@ -24,6 +24,7 @@
 #define HDMI_CORE_BASE(x)		((x) + 0x00010000)
 #define HDMI_I2S_BASE(x)		((x) + 0x00040000)
 #define HDMI_TG_BASE(x)			((x) + 0x00050000)
+#define HDMI_EFUSE_BASE(x)		((x) + 0x00060000)
 
 /* Control registers */
 #define HDMI_INTC_CON			HDMI_CTRL_BASE(0x0000)
@@ -120,10 +121,15 @@
 #define HDMI_INTC_EN_GLOBAL		(1 << 6)
 #define HDMI_INTC_EN_HPD_PLUG		(1 << 3)
 #define HDMI_INTC_EN_HPD_UNPLUG		(1 << 2)
+#define HDMI_INTC_EN_HDCP			(1 << 0)
 
 /* HDMI_INTC_FLAG */
 #define HDMI_INTC_FLAG_HPD_PLUG		(1 << 3)
 #define HDMI_INTC_FLAG_HPD_UNPLUG	(1 << 2)
+#define HDMI_INTC_FLAG_HDCP			(1 << 0)
+
+/* HDMI_HDCP_KEY_LOAD */
+#define HDMI_HDCP_KEY_LOAD_DONE	(1 << 0)
 
 /* HDMI_PHY_RSTOUT */
 #define HDMI_PHY_SW_RSTOUT		(1 << 0)
@@ -142,6 +148,27 @@
 #define HDMI_VID_PREAMBLE_DIS		(1 << 5)
 #define HDMI_GUARD_BAND_DIS		(1 << 1)
 
+/* STATUS */
+#define HDMI_AUTHEN_ACK_AUTH			(1 << 7)
+#define HDMI_AUTHEN_ACK_NOT			(0 << 7)
+#define HDMI_AUD_FIFO_OVF_FULL			(1 << 6)
+#define HDMI_AUD_FIFO_OVF_NOT			(0 << 6)
+#define HDMI_UPDATE_RI_INT_OCC			(1 << 4)
+#define HDMI_UPDATE_RI_INT_NOT			(0 << 4)
+#define HDMI_UPDATE_RI_INT_CLEAR		(1 << 4)
+#define HDMI_UPDATE_PJ_INT_OCC			(1 << 3)
+#define HDMI_UPDATE_PJ_INT_NOT			(0 << 3)
+#define HDMI_UPDATE_PJ_INT_CLEAR		(1 << 3)
+#define HDMI_WRITE_INT_OCC			(1 << 2)
+#define HDMI_WRITE_INT_NOT			(0 << 2)
+#define HDMI_WRITE_INT_CLEAR			(1 << 2)
+#define HDMI_WATCHDOG_INT_OCC			(1 << 1)
+#define HDMI_WATCHDOG_INT_NOT			(0 << 1)
+#define HDMI_WATCHDOG_INT_CLEAR			(1 << 1)
+#define HDMI_WTFORACTIVERX_INT_OCC		(1)
+#define HDMI_WTFORACTIVERX_INT_NOT		(0)
+#define HDMI_WTFORACTIVERX_INT_CLEAR		(1)
+
 /* HDMI_PHY_STATUS */
 #define HDMI_PHY_STATUS_READY		(1 << 0)
 
@@ -154,6 +181,84 @@
 #define HDMI_TG_EN			(1 << 0)
 #define HDMI_FIELD_EN			(1 << 1)
 
+/* STATUS_EN */
+#define HDMI_AUD_FIFO_OVF_EN			(1 << 6)
+#define HDMI_AUD_FIFO_OVF_DIS			(0 << 6)
+#define HDMI_UPDATE_RI_INT_EN			(1 << 4)
+#define HDMI_UPDATE_RI_INT_DIS			(0 << 4)
+#define HDMI_UPDATE_PJ_INT_EN			(1 << 3)
+#define HDMI_UPDATE_PJ_INT_DIS			(0 << 3)
+#define HDMI_WRITE_INT_EN			(1 << 2)
+#define HDMI_WRITE_INT_DIS			(0 << 2)
+#define HDMI_WATCHDOG_INT_EN			(1 << 1)
+#define HDMI_WATCHDOG_INT_DIS			(0 << 1)
+#define HDMI_WTFORACTIVERX_INT_EN		(1)
+#define HDMI_WTFORACTIVERX_INT_DIS		(0)
+#define HDMI_INT_EN_ALL				(HDMI_UPDATE_RI_INT_EN|\
+						HDMI_UPDATE_PJ_INT_DIS|\
+						HDMI_WRITE_INT_EN|\
+						HDMI_WATCHDOG_INT_EN|\
+						HDMI_WTFORACTIVERX_INT_EN)
+#define HDMI_INT_DIS_ALL			(~0x1F)
+
+/* HDMI_HPD */
+#define HDMI_SW_HPD_PLUGGED			(1 << 1)
+#define HDMI_SW_HPD_UNPLUGGED			(0 << 1)
+#define HDMI_HPD_SEL_I_HPD			(1)
+#define HDMI_HPD_SEL_SW_HPD			(0)
+
+/* ENC_EN */
+#define HDMI_HDCP_ENC_ENABLE			(1)
+#define HDMI_HDCP_ENC_DISABLE			(0)
+
+/* HDCP Register */
+
+/* HDCP_SHA1_00~19 */
+
+/* HDCP_KSV_LIST_0~4 */
+
+/* HDCP_KSV_LIST_CON */
+#define HDMI_HDCP_KSV_WRITE_DONE		(0x1 << 3)
+#define HDMI_HDCP_KSV_LIST_EMPTY		(0x1 << 2)
+#define HDMI_HDCP_KSV_END			(0x1 << 1)
+#define HDMI_HDCP_KSV_READ			(0x1 << 0)
+
+/* HDCP_CTRL1 */
+#define HDMI_HDCP_EN_PJ_EN			(1 << 4)
+#define HDMI_HDCP_EN_PJ_DIS			(~(1 << 4))
+#define HDMI_HDCP_SET_REPEATER_TIMEOUT		(1 << 2)
+#define HDMI_HDCP_CLEAR_REPEATER_TIMEOUT	(~(1 << 2))
+#define HDMI_HDCP_CP_DESIRED_EN			(1 << 1)
+#define HDMI_HDCP_CP_DESIRED_DIS		(~(1 << 1))
+#define HDMI_HDCP_ENABLE_1_1_FEATURE_EN		(1)
+#define HDMI_HDCP_ENABLE_1_1_FEATURE_DIS	(~(1))
+
+/* HDCP_CHECK_RESULT */
+#define HDMI_HDCP_PI_MATCH_RESULT_Y		((0x1 << 3) | (0x1 << 2))
+#define HDMI_HDCP_PI_MATCH_RESULT_N		((0x1 << 3) | (0x0 << 2))
+#define HDMI_HDCP_RI_MATCH_RESULT_Y		((0x1 << 1) | (0x1 << 0))
+#define HDMI_HDCP_RI_MATCH_RESULT_N		((0x1 << 1) | (0x0 << 0))
+#define HDMI_HDCP_CLR_ALL_RESULTS		(0)
+
+/* HDCP_BKSV0~4 */
+/* HDCP_AKSV0~4 */
+
+/* HDCP_BCAPS */
+#define HDMI_HDCP_BCAPS_REPEATER		(1 << 6)
+#define HDMI_HDCP_BCAPS_READY			(1 << 5)
+#define HDMI_HDCP_BCAPS_FAST			(1 << 4)
+#define HDMI_HDCP_BCAPS_1_1_FEATURES		(1 << 1)
+#define HDMI_HDCP_BCAPS_FAST_REAUTH		(1)
+
+/* HDCP_BSTATUS_0/1 */
+/* HDCP_Ri_0/1 */
+/* HDCP_I2C_INT */
+/* HDCP_AN_INT */
+/* HDCP_WATCHDOG_INT */
+/* HDCP_RI_INT/1 */
+/* HDCP_Ri_Compare_0 */
+/* HDCP_Ri_Compare_1 */
+/* HDCP_Frame_Count */
 
 /* HDMI Version 1.4 */
 /* Control registers */
@@ -421,6 +526,22 @@
 #define HDMI_I2S_MUX_CH			HDMI_I2S_BASE(0x054)
 #define HDMI_I2S_MUX_CUV		HDMI_I2S_BASE(0x058)
 
+/* HDMI eFUSE registers */
+#define HDMI_EFUSE_CTRL			HDMI_EFUSE_BASE(0x000)
+#define HDMI_EFUSE_STATUS		HDMI_EFUSE_BASE(0x004)
+#define HDMI_EFUSE_ADDR_WIDTH		HDMI_EFUSE_BASE(0x008)
+#define HDMI_EFUSE_SIGDEV_ASSERT	HDMI_EFUSE_BASE(0x00c)
+#define HDMI_EFUSE_SIGDEV_DE_ASSERT	HDMI_EFUSE_BASE(0x010)
+#define HDMI_EFUSE_PRCHG_ASSERT		HDMI_EFUSE_BASE(0x014)
+#define HDMI_EFUSE_PRCHG_DE_ASSERT	HDMI_EFUSE_BASE(0x018)
+#define HDMI_EFUSE_FSET_ASSERT		HDMI_EFUSE_BASE(0x01c)
+#define HDMI_EFUSE_FSET_DE_ASSERT	HDMI_EFUSE_BASE(0x020)
+#define HDMI_EFUSE_SENSING		HDMI_EFUSE_BASE(0x024)
+#define HDMI_EFUSE_SCK_ASSERT		HDMI_EFUSE_BASE(0x028)
+#define HDMI_EFUSE_SCK_DE_ASSERT	HDMI_EFUSE_BASE(0x02c)
+#define HDMI_EFUSE_SDOUT_OFFSET		HDMI_EFUSE_BASE(0x030)
+#define HDMI_EFUSE_READ_OFFSET		HDMI_EFUSE_BASE(0x034)
+
 /* I2S bit definition */
 
 /* I2S_CLK_CON */
@@ -570,6 +691,62 @@
 #define HDMI_I2S_CUV_R_DATA_MASK	(0x7 << 4)
 #define HDMI_I2S_CUV_L_DATA_MASK	(0x7)
 
+/* GCP_CON */
+#define HDMI_GCP_CON_EN_1ST_VSYNC	(1 << 3)
+#define HDMI_GCP_CON_EN_2ST_VSYNC	(1 << 2)
+#define HDMI_GCP_CON_TRANS_EVERY_VSYNC	(2)
+#define HDMI_GCP_CON_NO_TRAN		(0)
+#define HDMI_GCP_CON_TRANS_ONCE		(1)
+#define HDMI_GCP_CON_TRANS_EVERY_VSYNC	(2)
+
+/* GCP_BYTE1 */
+#define HDMI_GCP_BYTE1_MASK		(0xFF)
+
+/* GCP_BYTE2 */
+#define HDMI_GCP_BYTE2_PP_MASK		(0xF << 4)
+#define HDMI_GCP_24BPP			(1 << 2)
+#define HDMI_GCP_30BPP			(1 << 0 | 1 << 2)
+#define HDMI_GCP_36BPP			(1 << 1 | 1 << 2)
+#define HDMI_GCP_48BPP			(1 << 0 | 1 << 1 | 1 << 2)
+
+/* GCP_BYTE3 */
+#define HDMI_GCP_BYTE3_MASK		(0xFF)
+
+/* HDCP E-FUSE Control Register */
+/* HDCP_E_FUSE_CTRL */
+#define HDMI_EFUSE_CTRL_HDCP_KEY_READ		(1 << 0)
+
+/* HDCP_E_FUSE_STATUS */
+#define HDMI_EFUSE_ECC_FAIL			(1 << 2)
+#define HDMI_EFUSE_ECC_BUSY			(1 << 1)
+#define HDMI_EFUSE_ECC_DONE			(1)
+
+/* EFUSE_ADDR_WIDTH */
+/* EFUSE_SIGDEV_ASSERT */
+/* EFUSE_SIGDEV_DE-ASSERT */
+/* EFUSE_PRCHG_ASSERT */
+/* EFUSE_PRCHG_DE-ASSERT */
+/* EFUSE_FSET_ASSERT */
+/* EFUSE_FSET_DE-ASSERT */
+/* EFUSE_SENSING */
+/* EFUSE_SCK_ASSERT */
+/* EFUSE_SCK_DEASSERT */
+/* EFUSE_SDOUT_OFFSET */
+/* EFUSE_READ_OFFSET */
+
+/* HDCP_SHA_RESULT */
+#define HDMI_HDCP_SHA_VALID_NO_RD		(0 << 1)
+#define HDMI_HDCP_SHA_VALID_RD			(1 << 1)
+#define HDMI_HDCP_SHA_VALID			(1)
+#define HDMI_HDCP_SHA_NO_VALID			(0)
+
+/* Audio InfoFrame Register */
+
+/* AUI_CON */
+#define HDMI_AUI_CON_NO_TRAN			(0 << 0)
+#define HDMI_AUI_CON_TRANS_ONCE			(1 << 0)
+#define HDMI_AUI_CON_TRANS_EVERY_VSYNC		(2 << 0)
+
 /* Timing generator registers */
 /* TG configure/status registers */
 #define HDMI_TG_VACT_ST3_L		HDMI_TG_BASE(0x0068)
-- 
1.7.0.4

_______________________________________________
dri-devel mailing list
dri-devel@xxxxxxxxxxxxxxxxxxxxx
http://lists.freedesktop.org/mailman/listinfo/dri-devel


[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux