From patchwork Fri Dec 21 09:47:57 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eunchul Kim X-Patchwork-Id: 1902661 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by patchwork1.kernel.org (Postfix) with ESMTP id 1DD9A40076 for ; Fri, 21 Dec 2012 09:48:56 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 1B725E6758 for ; Fri, 21 Dec 2012 01:48:56 -0800 (PST) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mailout2.samsung.com (mailout2.samsung.com [203.254.224.25]) by gabe.freedesktop.org (Postfix) with ESMTP id 166DFE60DC for ; Fri, 21 Dec 2012 01:47:39 -0800 (PST) Received: from epcpsbgm1.samsung.com (epcpsbgm1 [203.254.230.26]) by mailout2.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0MFD00L14KCOPL40@mailout2.samsung.com> for dri-devel@lists.freedesktop.org; Fri, 21 Dec 2012 18:47:36 +0900 (KST) Received: from epcpsbgm1.samsung.com ( [203.254.230.42]) by epcpsbgm1.samsung.com (EPCPMTA) with SMTP id 40.03.01231.83034D05; Fri, 21 Dec 2012 18:47:36 +0900 (KST) X-AuditID: cbfee61a-b7fa66d0000004cf-3d-50d43038bc44 Received: from epmmp2 ( [203.254.227.17]) by epcpsbgm1.samsung.com (EPCPMTA) with SMTP id BF.F2.01231.83034D05; Fri, 21 Dec 2012 18:47:36 +0900 (KST) Received: from localhost.localdomain ([10.90.54.32]) by mmp2.samsung.com (Oracle Communications Messaging Server 7u4-24.01 (7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTPA id <0MFD002QCKJ97360@mmp2.samsung.com> for dri-devel@lists.freedesktop.org; Fri, 21 Dec 2012 18:47:36 +0900 (KST) From: Eunchul Kim To: dri-devel@lists.freedesktop.org, inki.dae@samsung.com Subject: [RFC] drm/exynos: added hdcp driver for contents protection. Date: Fri, 21 Dec 2012 18:47:57 +0900 Message-id: <1356083277-11676-2-git-send-email-chulspro.kim@samsung.com> X-Mailer: git-send-email 1.7.0.4 In-reply-to: <1356083277-11676-1-git-send-email-chulspro.kim@samsung.com> References: <1356083277-11676-1-git-send-email-chulspro.kim@samsung.com> DLP-Filter: Pass X-MTR: 20000000000000000@CPGS X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFrrLLMWRmVeSWpSXmKPExsVy+t8zLV0LgysBBitmaFlc+fqezYHR4373 caYAxigum5TUnMyy1CJ9uwSujBdHmhgL5t5jqnj25BVbA+PzeUxdjBwcEgImEs9Pa3cxcgKZ YhIX7q1n62Lk4hASWMYocXXafhaIhInE1YsdjBCJ6YwSl66+ZQdJCAmsYJLYusAaxGYT0JE4 cquZGcQWEbCUeLD1NzNIA7PAT0aJdU/OsoIkhAVcJdrXTQJrZhFQlTjw/BpYnFfAQ+LB4h+M ENsUJFqXHQKr4RTwlJi8+CAbxDIPiV27DjJC9ApIfJt8iAXiA1mJTQeYIVqvs0lc38IFYUtK HFxxg2UCo/ACRoZVjKKpBckFxUnpuYZ6xYm5xaV56XrJ+bmbGCFhKLWDcWWDxSFGAQ5GJR5e RrPLAUKsiWXFlbmHGCU4mJVEeJm+AoV4UxIrq1KL8uOLSnNSiw8x+gAdMpFZSjQ5HxgjeSXx hsYGxoaGloZmppamBjiElcR5mz1SAoQE0hNLUrNTUwtSi2DGMXFwSjUw5vzjNc7jfC178Lmt wVWZd6973peo5SaE1FzXTxdYMU3mT3nv3ovrGA9sm231TWM582/pk3/23+hTPdvGo3mtizmh RsJt05yGxZuq2m/FnZvusyiOPzv9c6LOg6KQqDtaGVdZQjfMvLSy79cdxZ7JD3vWMGeqPWrS MGWKSukL1V3z/d1v1fZaJZbijERDLeai4kQAovYn/XACAAA= X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFupjkeLIzCtJLcpLzFFi42I5/e+xoK6FwZUAg3mdchZXvr5nc2D0uN99 nCmAMaqB0SYjNTEltUghNS85PyUzL91WyTs43jne1MzAUNfQ0sJcSSEvMTfVVsnFJ0DXLTMH aKySQlliTilQKCCxuFhJ3w7ThNAQN10LmMYIXd+QILgeIwM0kLCOMePFkSbGgrn3mCqePXnF 1sD4fB5TFyMnh4SAicTVix2MELaYxIV769m6GLk4hASmM0pcuvqWHSQhJLCCSWLrAmsQm01A R+LIrWZmEFtEwFLiwdbfzCANzAI/GSXWPTnLCpIQFnCVaF83CayZRUBV4sDza2BxXgEPiQeL f0BtU5BoXXYIrIZTwFNi8uKDbBDLPCR27TrIOIGRdwEjwypG0dSC5ILipPRcQ73ixNzi0rx0 veT83E2M4EB/JrWDcWWDxSFGAQ5GJR5eRrPLAUKsiWXFlbmHGCU4mJVEeJm+AoV4UxIrq1KL 8uOLSnNSiw8x+gBdNZFZSjQ5HxiFeSXxhsYmZkaWRmbGJubGxjiElcR5mz1SAoQE0hNLUrNT UwtSi2DGMXFwSjUw8i+yCJZ6+mfrZP4i53OaIYZccxK3zGCeFdrW8Lkqd23xzLb6RxnHP3a9 jRO5ZG0n4rr/D8edpRUWIpt59c9PNquJFt7/d4vDnvuTDx2SmBKuo5jppjqpQfpogZb3tINf lFpFBRL4Pv1ZcezKxpx6WRvHnmMmqy5znYyqiLr52Eru1P9/9rl7lFiKMxINtZiLihMBmx0U GaECAAA= X-CFilter-Loop: Reflected Cc: jy0.jeon@samsung.com, sw0312.kim@samsung.com, jaejoon.seo@samsung.com, kyungmin.park@samsung.com, jmock.shin@samsung.com, lsmin.lee@samsung.com, th908.kim@samsung.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org Errors-To: dri-devel-bounces+patchwork-dri-devel=patchwork.kernel.org@lists.freedesktop.org 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 --- 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 + * + * 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 +#include + +#include +#include +#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 + * + * 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 #include @@ -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)