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

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

 



Dear Sean Paul.

Thank's for your comment.

I am in charge of Display Part in Samsung TN.
and I also use exynos drm and I contributed some part to dri-devel(e.g IPPs).

Your comment is very helpful to me.
I answer to your comment. please check my answer.

Thank's
BR
Eunchul Kim

On 12/22/2012 04:29 AM, Sean Paul wrote:
On Fri, Dec 21, 2012 at 4:47 AM, Eunchul Kim <chulspro.kim@xxxxxxxxxxx> wrote:
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>
---

Hey Eunchul,
Through some unfortunate duplication of work, I've also been working
on HDCP for exynos drm.

- Look's good news. because we can upgrade exynos drm together.


I uploaded my patch a couple of days ago
https://gerrit.chromium.org/gerrit/#/c/39960.

- I didn't subscribe chromium.org. so, I didn't see your patch.
  I will check your patch for understanding your design about HDCP.
  How can i subscribe your gerrit for the future?
  because we have very many changes in HDMI, FIMD side in our local git.
  I want to synchronize from yours to avoid duplication work.
  I am in charge of all Display Part H/W IP in exynos DRM. please refer it.


A few high level comments on your patch:

- Why implement a new driver? You're using the irq and registers from

- Yes your are right in some part. I think you didn't see the connection with HDMI driver and HDCP driver. so, you got the question. IMO, HDMI driver is very big currently, so, If we add
  HDCP code directly in HDMI driver.
  I can't handle HDCP driver on/off using configuration(defconfig).
  so, I designed different file and make some ops(callback).
  I will check one more time between in-side and out-side with our team.

the hdmi IP block, seems to make more sense to do it right in the hdmi
driver. There's a fair bit of code gymnastics that you're doing to
make hdcp visible to hdmi that just wouldn't be necessary if it was
all included.

- I understood what your mean. I will consider about your comment.

- I'm not sure what your interface to userspace is. I think it makes
most sense to implement this as a connector property. Here's a patch I
uploaded a couple weeks ago
https://gerrit.chromium.org/gerrit/#/c/38845/

- connector property is good idea. I also think that.(currently we always enable this)
  I will check your patch.
  thank's. It's good information.


A couple more comments inline.

  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;

Event is just mirroring HDMI_SYS_STATUS, I don't think there's any
need to define it and a whole set of events when we already have
defined bitmasks (ex: HDMI_WRITE_INT_OCC vs.
HDCP_EVENT_WRITE_AKSV_START)

- changed it.


+       queue_work(ctx->wq, (struct work_struct *)&ctx->event_work);
+}
+
+static struct exynos_hdcp_ops hdmi_ops = {
+       /* manager */
+       .dpms = hdcp_dpms,
+       .commit = hdcp_commit,

Where do you intend on calling this from within the hdmi driver?

- Yes, HDMI driver called this ops(callback).

Ideally this would be checked after an HDCP interrupt, so will you

- HDMI driver call dpms after HDMI plug/unplug interrupt.
  HDMI driver call commit after HDCP interrupt.

call it from the HDMI interrupt handler? I also don't think "commit"

- Yes. What do you prefer? DRM side using "commit" for execution.
  so, I used it.

is a particularly good name.

Lastly, I'm not sure why you need an hdmi_ops structure for this,
can't you just expose the definitions in your hdcp header?

- I register ops to HDMI driver. and If driver registered in HDMI driver.
  HDMI driver handle HDCP driver. In this case, HDCP working.


+};
+
+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;
+

This is pretty hacky, I think it would be much better to just do hdcp
in the hdmi driver. Of course, I'm biased since that's what my
implementation did :).

- hmm ~ I respect your opinion. I will consider one more time.


+       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;
+       }

I'm not sure what you intend to do with this structure.

- HDCP ctx have some information of data. I want to get this ctx using drm_hdcp_ctx.
  please see the exynos_drm_hdmi.c. this driver handle mixer/vp and hdmi.
  It's same with hdcp controlling.


+
+       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);

Again, if you put this in hdmi driver, you could just use its
workqueue and avoid spinning a new one up.

- I designed two kind of mechanism about this. frist one is based on workqueue.
  second one is based on callback.
  HDCP encryption validation works evey 2 seconds as you know.
  so, I worry about performance issue. so, I choose workqueue.
  I will check one more time between workqueue and callback.
  and what is nice position about HDCP.


+       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


_______________________________________________
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