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;