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....@samsung.com>
---
 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....@samsung.com>
+ *
+ * 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....@samsung.com>
+ *
+ * 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@lists.freedesktop.org
http://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to