drm: bridge: dw-hdmi: add hdcp1.4 support
authorxuhuicong <xhc@rock-chips.com>
Fri, 7 Apr 2017 08:42:21 +0000 (16:42 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Fri, 16 Jun 2017 06:41:15 +0000 (14:41 +0800)
First, write hdcp key by "ProvisioningTool" if you want to
enable hdcp function, or else will auth fail.

To check whether the hdcp is enable or not
  #cat /sys/class/misc/hdmi_hdcp1x/enable
  0:hdcp is disabled
  1:hdcp is enabled, hdmi screen will be pink if it is failed;
  2:hdcp is enabled, hdmi screen will be normal if it is failed;

Enable or disable hdcp function
  #echo 0 > /sys/class/misc/hdmi_hdcp1x/enable
  #echo 1 > /sys/class/misc/hdmi_hdcp1x/enable
  #echo 2 > /sys/class/misc/hdmi_hdcp1x/enable

Get the status of hdcp
  #cat /sys/class/misc/hdmi_hdcp1x/status
  The result will be one of the follow list:
    hdcp disable;
    hdcp_auth_start
    hdcp_auth_success;
    hdcp_auth_fail;
    unknown status.

Change-Id: Iac6c7d6a1196ce9cf2869d7916bbe6c8941ec13b
Signed-off-by: xuhuicong <xhc@rock-chips.com>
drivers/gpu/drm/bridge/Makefile
drivers/gpu/drm/bridge/dw-hdmi-hdcp.c [new file with mode: 0644]
drivers/gpu/drm/bridge/dw-hdmi-hdcp.h [new file with mode: 0644]
drivers/gpu/drm/bridge/dw-hdmi.c

index 33291a94f96db612e7f34c887e32e823f55665f3..dea158758ea5829d852d14fdd3b73d30313d47c6 100644 (file)
@@ -1,6 +1,6 @@
 ccflags-y := -Iinclude/drm
 
-obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
+obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-hdcp.o
 obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
 obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
 obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
diff --git a/drivers/gpu/drm/bridge/dw-hdmi-hdcp.c b/drivers/gpu/drm/bridge/dw-hdmi-hdcp.c
new file mode 100644 (file)
index 0000000..e50ee5b
--- /dev/null
@@ -0,0 +1,752 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author Huicong Xu <xhc@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/cryptohash.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/iopoll.h>
+#include <linux/irq.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/spinlock.h>
+#include <linux/soc/rockchip/rk_vendor_storage.h>
+#include <drm/bridge/dw_hdmi.h>
+
+#include "dw-hdmi.h"
+#include "dw-hdmi-hdcp.h"
+
+#define HDCP_KEY_SIZE          308
+#define HDCP_KEY_SEED_SIZE     2
+
+#define KSV_LEN                        5
+#define HEADER                 10
+#define SHAMAX                 20
+
+#define MAX_DOWNSTREAM_DEVICE_NUM      5
+#define DPK_WR_OK_TIMEOUT_US           30000
+#define HDMI_HDCP1X_ID                 5
+
+/* HDCP Registers */
+#define HDMI_HDCPREG_RMCTL     0x780e
+#define HDMI_HDCPREG_RMSTS     0x780f
+#define HDMI_HDCPREG_SEED0     0x7810
+#define HDMI_HDCPREG_SEED1     0x7811
+#define HDMI_HDCPREG_DPK0      0x7812
+#define HDMI_HDCPREG_DPK1      0x7813
+#define HDMI_HDCPREG_DPK2      0x7814
+#define HDMI_HDCPREG_DPK3      0x7815
+#define HDMI_HDCPREG_DPK4      0x7816
+#define HDMI_HDCPREG_DPK5      0x7817
+#define HDMI_HDCPREG_DPK6      0x7818
+#define HDMI_HDCP2REG_CTRL     0x7904
+#define HDMI_HDCP2REG_MASK     0x790c
+#define HDMI_HDCP2REG_MUTE     0x790e
+
+enum dw_hdmi_hdcp_state {
+       DW_HDCP_DISABLED,
+       DW_HDCP_AUTH_START,
+       DW_HDCP_AUTH_SUCCESS,
+       DW_HDCP_AUTH_FAIL,
+};
+
+enum {
+       DW_HDMI_HDCP_KSV_LEN = 8,
+       DW_HDMI_HDCP_SHA_LEN = 20,
+       DW_HDMI_HDCP_DPK_LEN = 280,
+       DW_HDMI_HDCP_KEY_LEN = 308,
+       DW_HDMI_HDCP_SEED_LEN = 2,
+};
+
+enum {
+       HDMI_MC_CLKDIS_HDCPCLK_MASK = 0x40,
+       HDMI_MC_CLKDIS_HDCPCLK_ENABLE = 0x00,
+
+       HDMI_A_SRMCTRL_SHA1_FAIL_MASK = 0X08,
+       HDMI_A_SRMCTRL_SHA1_FAIL_DISABLE = 0X00,
+       HDMI_A_SRMCTRL_SHA1_FAIL_ENABLE = 0X08,
+
+       HDMI_A_SRMCTRL_KSV_UPDATE_MASK = 0X04,
+       HDMI_A_SRMCTRL_KSV_UPDATE_DISABLE = 0X00,
+       HDMI_A_SRMCTRL_KSV_UPDATE_ENABLE = 0X04,
+
+       HDMI_A_SRMCTRL_KSV_MEM_REQ_MASK = 0X01,
+       HDMI_A_SRMCTRL_KSV_MEM_REQ_DISABLE = 0X00,
+       HDMI_A_SRMCTRL_KSV_MEM_REQ_ENABLE = 0X01,
+
+       HDMI_A_SRMCTRL_KSV_MEM_ACCESS_MASK = 0X02,
+       HDMI_A_SRMCTRL_KSV_MEM_ACCESS_DISABLE = 0X00,
+       HDMI_A_SRMCTRL_KSV_MEM_ACCESS_ENABLE = 0X02,
+
+       HDMI_A_SRM_BASE_MAX_DEVS_EXCEEDED = 0x80,
+       HDMI_A_SRM_BASE_DEVICE_COUNT = 0x7f,
+
+       HDMI_A_SRM_BASE_MAX_CASCADE_EXCEEDED = 0x08,
+
+       HDMI_A_APIINTSTAT_KSVSHA1_CALC_INT = 0x02,
+
+       /* HDCPREG_RMSTS field values */
+       DPK_WR_OK_STS = 0x40,
+
+       HDMI_A_HDCP22_MASK = 0x40,
+
+       HDMI_HDCP2_OVR_EN_MASK = 0x02,
+       HDMI_HDCP2_OVR_ENABLE = 0x02,
+       HDMI_HDCP2_OVR_DISABLE = 0x00,
+
+       HDMI_HDCP2_FORCE_MASK = 0x04,
+       HDMI_HDCP2_FORCE_ENABLE = 0x04,
+       HDMI_HDCP2_FORCE_DISABLE = 0x00,
+};
+
+struct sha_t {
+       u8 mlength[8];
+       u8 mblock[64];
+       int mindex;
+       int mcomputed;
+       int mcorrupted;
+       unsigned int mdigest[5];
+};
+
+static struct miscdevice mdev;
+
+static inline unsigned int shacircularshift(unsigned int bits,
+                                           unsigned int word)
+{
+       return (((word << bits) & 0xFFFFFFFF) | (word >> (32 - bits)));
+}
+
+static void hdcp_modb(struct dw_hdcp *hdcp, u8 data, u8 mask, unsigned int reg)
+{
+       struct dw_hdmi *hdmi = hdcp->hdmi;
+       u8 val = hdcp->read(hdmi, reg) & ~mask;
+
+       val |= data & mask;
+       hdcp->write(hdmi, val, reg);
+}
+
+static void sha_reset(struct sha_t *sha)
+{
+       u32 i = 0;
+
+       sha->mindex = 0;
+       sha->mcomputed = false;
+       sha->mcorrupted = false;
+       for (i = 0; i < sizeof(sha->mlength); i++)
+               sha->mlength[i] = 0;
+
+       sha_init(sha->mdigest);
+}
+
+static void sha_processblock(struct sha_t *sha)
+{
+       u32 array[SHA_WORKSPACE_WORDS];
+
+       sha_transform(sha->mdigest, sha->mblock, array);
+       sha->mindex = 0;
+}
+
+static void sha_padmessage(struct sha_t *sha)
+{
+       /*
+        *  Check to see if the current message block is too small to hold
+        *  the initial padding bits and length.  If so, we will pad the
+        *  block, process it, and then continue padding into a second
+        *  block.
+        */
+       if (sha->mindex > 55) {
+               sha->mblock[sha->mindex++] = 0x80;
+               while (sha->mindex < 64)
+                       sha->mblock[sha->mindex++] = 0;
+
+               sha_processblock(sha);
+               while (sha->mindex < 56)
+                       sha->mblock[sha->mindex++] = 0;
+       } else {
+               sha->mblock[sha->mindex++] = 0x80;
+               while (sha->mindex < 56)
+                       sha->mblock[sha->mindex++] = 0;
+       }
+
+       /* Store the message length as the last 8 octets */
+       sha->mblock[56] = sha->mlength[7];
+       sha->mblock[57] = sha->mlength[6];
+       sha->mblock[58] = sha->mlength[5];
+       sha->mblock[59] = sha->mlength[4];
+       sha->mblock[60] = sha->mlength[3];
+       sha->mblock[61] = sha->mlength[2];
+       sha->mblock[62] = sha->mlength[1];
+       sha->mblock[63] = sha->mlength[0];
+
+       sha_processblock(sha);
+}
+
+static int sha_result(struct sha_t *sha)
+{
+       if (sha->mcorrupted)
+               return false;
+
+       if (sha->mcomputed == 0) {
+               sha_padmessage(sha);
+               sha->mcomputed = true;
+       }
+       return true;
+}
+
+static void sha_input(struct sha_t *sha, const u8 *data, u32 size)
+{
+       int i = 0;
+       unsigned int j = 0;
+       int rc = true;
+
+       if (data == 0 || size == 0)
+               return;
+
+       if (sha->mcomputed || sha->mcorrupted) {
+               sha->mcorrupted = true;
+               return;
+       }
+       while (size-- && !sha->mcorrupted) {
+               sha->mblock[sha->mindex++] = *data;
+
+               for (i = 0; i < 8; i++) {
+                       rc = true;
+                       for (j = 0; j < sizeof(sha->mlength); j++) {
+                               sha->mlength[j]++;
+                               if (sha->mlength[j] != 0) {
+                                       rc = false;
+                                       break;
+                               }
+                       }
+                       sha->mcorrupted = (sha->mcorrupted  ||
+                                          rc) ? true : false;
+               }
+               /* if corrupted then message is too long */
+               if (sha->mindex == 64)
+                       sha_processblock(sha);
+               data++;
+       }
+}
+
+static int hdcp_verify_ksv(const u8 *data, u32 size)
+{
+       u32 i = 0;
+       struct sha_t sha;
+
+       if ((!data) || (size < (HEADER + SHAMAX)))
+               return false;
+
+       sha_reset(&sha);
+       sha_input(&sha, data, size - SHAMAX);
+       if (sha_result(&sha) == false)
+               return false;
+
+       for (i = 0; i < SHAMAX; i++) {
+               if (data[size - SHAMAX + i] != (u8)(sha.mdigest[i / 4]
+                               >> ((i % 4) * 8)))
+                       return false;
+       }
+       return true;
+}
+
+static int hdcp_load_keys_cb(struct dw_hdcp *hdcp)
+{
+       u32 size;
+       u8 hdcp_vendor_data[320];
+
+       hdcp->keys = kmalloc(HDCP_KEY_SIZE, GFP_KERNEL);
+       if (!hdcp->keys)
+               return -ENOMEM;
+
+       hdcp->seeds = kmalloc(HDCP_KEY_SEED_SIZE, GFP_KERNEL);
+       if (!hdcp->seeds) {
+               kfree(hdcp->keys);
+               return -ENOMEM;
+       }
+
+       size = rk_vendor_read(HDMI_HDCP1X_ID, hdcp_vendor_data, 314);
+       if (size < (HDCP_KEY_SIZE + HDCP_KEY_SEED_SIZE)) {
+               dev_dbg(hdcp->dev, "HDCP: read size %d\n", size);
+               memset(hdcp->keys, 0, HDCP_KEY_SIZE);
+               memset(hdcp->seeds, 0, HDCP_KEY_SEED_SIZE);
+       } else {
+               memcpy(hdcp->keys, hdcp_vendor_data, HDCP_KEY_SIZE);
+               memcpy(hdcp->seeds, hdcp_vendor_data + HDCP_KEY_SIZE,
+                      HDCP_KEY_SEED_SIZE);
+       }
+       return 0;
+}
+
+static int dw_hdmi_hdcp_load_key(struct dw_hdcp *hdcp)
+{
+       int i, j;
+       int ret, val;
+       void __iomem *reg_rmsts_addr;
+       struct hdcp_keys *hdcp_keys;
+       struct dw_hdmi *hdmi = hdcp->hdmi;
+
+       if (!hdcp->keys) {
+               ret = hdcp_load_keys_cb(hdcp);
+               if (ret)
+                       return ret;
+       }
+       hdcp_keys = hdcp->keys;
+
+       if (hdcp->reg_io_width == 4)
+               reg_rmsts_addr = hdcp->regs + (HDMI_HDCPREG_RMSTS << 2);
+       else if (hdcp->reg_io_width == 1)
+               reg_rmsts_addr = hdcp->regs + HDMI_HDCPREG_RMSTS;
+       else
+               return -EPERM;
+
+       /* Disable decryption logic */
+       hdcp->write(hdmi, 0, HDMI_HDCPREG_RMCTL);
+       ret = readx_poll_timeout(readl, reg_rmsts_addr, val,
+                                val & DPK_WR_OK_STS, 1000,
+                                DPK_WR_OK_TIMEOUT_US);
+       if (ret)
+               return ret;
+       hdcp->write(hdmi, 0, HDMI_HDCPREG_DPK6);
+       hdcp->write(hdmi, 0, HDMI_HDCPREG_DPK5);
+
+       /* The useful data in ksv should be 5 byte */
+       for (i = 4; i >= 0; i--)
+               hdcp->write(hdmi, hdcp_keys->KSV[i], HDMI_HDCPREG_DPK0 + i);
+       ret = readx_poll_timeout(readl, reg_rmsts_addr, val,
+                                val & DPK_WR_OK_STS, 1000,
+                                DPK_WR_OK_TIMEOUT_US);
+
+       if (ret)
+               return ret;
+
+       /* Enable decryption logic */
+       if (hdcp->seeds) {
+               hdcp->write(hdmi, 1, HDMI_HDCPREG_RMCTL);
+               hdcp->write(hdmi, hdcp->seeds[0], HDMI_HDCPREG_SEED1);
+               hdcp->write(hdmi, hdcp->seeds[1], HDMI_HDCPREG_SEED0);
+       } else {
+               hdcp->write(hdmi, 0, HDMI_HDCPREG_RMCTL);
+       }
+
+       /* Write encrypt device private key */
+       for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) {
+               for (j = 6; j >= 0; j--)
+                       hdcp->write(hdmi, hdcp_keys->devicekey[i + j],
+                                   HDMI_HDCPREG_DPK0 + j);
+               ret = readx_poll_timeout(readl, reg_rmsts_addr, val,
+                                        val & DPK_WR_OK_STS, 1000,
+                                        DPK_WR_OK_TIMEOUT_US);
+
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+static int dw_hdmi_hdcp_start(struct dw_hdcp *hdcp)
+{
+       struct dw_hdmi *hdmi = hdcp->hdmi;
+
+       if (!hdcp->enable)
+               return -EPERM;
+
+       if (!(hdcp->read(hdmi, HDMI_HDCPREG_RMSTS) & 0x3f))
+               dw_hdmi_hdcp_load_key(hdcp);
+
+       hdcp_modb(hdcp, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE,
+                 HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK,
+                 HDMI_FC_INVIDCONF);
+
+       hdcp->remaining_times = hdcp->retry_times;
+       if (hdcp->read(hdmi, HDMI_CONFIG1_ID) & HDMI_A_HDCP22_MASK) {
+               if (hdcp->hdcp2_enable == 0) {
+                       hdcp_modb(hdcp, HDMI_HDCP2_OVR_ENABLE |
+                                 HDMI_HDCP2_FORCE_DISABLE,
+                                 HDMI_HDCP2_OVR_EN_MASK |
+                                 HDMI_HDCP2_FORCE_MASK,
+                                 HDMI_HDCP2REG_CTRL);
+                       hdcp->write(hdmi, 0xff, HDMI_HDCP2REG_MASK);
+                       hdcp->write(hdmi, 0xff, HDMI_HDCP2REG_MUTE);
+               } else {
+                       hdcp_modb(hdcp, HDMI_HDCP2_OVR_DISABLE |
+                                 HDMI_HDCP2_FORCE_DISABLE,
+                                 HDMI_HDCP2_OVR_EN_MASK |
+                                 HDMI_HDCP2_FORCE_MASK,
+                                 HDMI_HDCP2REG_CTRL);
+                       hdcp->write(hdmi, 0x00, HDMI_HDCP2REG_MASK);
+                       hdcp->write(hdmi, 0x00, HDMI_HDCP2REG_MUTE);
+               }
+       }
+
+       hdcp->write(hdmi, 0x40, HDMI_A_OESSWCFG);
+                   hdcp_modb(hdcp, HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE |
+                   HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE |
+                   HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE,
+                   HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK |
+                   HDMI_A_HDCPCFG0_EN11FEATURE_MASK |
+                   HDMI_A_HDCPCFG0_SYNCRICHECK_MASK, HDMI_A_HDCPCFG0);
+
+       hdcp_modb(hdcp, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE |
+                 HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE,
+                 HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK |
+                 HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK, HDMI_A_HDCPCFG1);
+
+       /* Reset HDCP Engine */
+       if (hdcp->read(hdmi, HDMI_MC_CLKDIS) & HDMI_MC_CLKDIS_HDCPCLK_MASK) {
+               hdcp_modb(hdcp, HDMI_A_HDCPCFG1_SWRESET_ASSERT,
+                         HDMI_A_HDCPCFG1_SWRESET_MASK, HDMI_A_HDCPCFG1);
+       }
+
+       hdcp->write(hdmi, 0x00, HDMI_A_APIINTMSK);
+       hdcp_modb(hdcp, HDMI_A_HDCPCFG0_RXDETECT_ENABLE,
+                 HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
+
+       /*
+        * XXX: to sleep 100ms here between output hdmi and enable hdcpclk,
+        * otherwise hdcp auth fail when Connect to repeater
+        */
+       msleep(100);
+       hdcp_modb(hdcp, HDMI_MC_CLKDIS_HDCPCLK_ENABLE,
+                 HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS);
+
+       hdcp->status = DW_HDCP_AUTH_START;
+       dev_dbg(hdcp->dev, "%s success\n", __func__);
+       return 0;
+}
+
+static int dw_hdmi_hdcp_stop(struct dw_hdcp *hdcp)
+{
+       struct dw_hdmi *hdmi = hdcp->hdmi;
+
+       if (!hdcp->enable)
+               return -EPERM;
+
+       hdcp_modb(hdcp, HDMI_MC_CLKDIS_HDCPCLK_DISABLE,
+                 HDMI_MC_CLKDIS_HDCPCLK_MASK, HDMI_MC_CLKDIS);
+       hdcp->write(hdmi, 0xff, HDMI_A_APIINTMSK);
+
+       hdcp_modb(hdcp, HDMI_A_HDCPCFG0_RXDETECT_DISABLE,
+                 HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
+
+       hdcp_modb(hdcp, HDMI_A_SRMCTRL_SHA1_FAIL_DISABLE |
+                 HDMI_A_SRMCTRL_KSV_UPDATE_DISABLE,
+                 HDMI_A_SRMCTRL_SHA1_FAIL_MASK |
+                 HDMI_A_SRMCTRL_KSV_UPDATE_MASK, HDMI_A_SRMCTRL);
+
+       hdcp->status = DW_HDCP_DISABLED;
+       return 0;
+}
+
+static int dw_hdmi_hdcp_ksvsha1(struct dw_hdcp *hdcp)
+{
+       int rc = 0, value, list, i;
+       char bstaus0, bstaus1;
+       char *ksvlistbuf;
+       struct dw_hdmi *hdmi = hdcp->hdmi;
+
+       hdcp_modb(hdcp, HDMI_A_SRMCTRL_KSV_MEM_REQ_ENABLE,
+                 HDMI_A_SRMCTRL_KSV_MEM_REQ_MASK, HDMI_A_SRMCTRL);
+
+       list = 20;
+       do {
+               value = hdcp->read(hdmi, HDMI_A_SRMCTRL);
+               usleep_range(500, 1000);
+       } while ((value & HDMI_A_SRMCTRL_KSV_MEM_ACCESS_MASK) == 0 && --list);
+
+       if ((value & HDMI_A_SRMCTRL_KSV_MEM_ACCESS_MASK) == 0) {
+               dev_err(hdcp->dev, "KSV memory can not access\n");
+               rc = -EPERM;
+               goto out;
+       }
+
+       hdcp->read(hdmi, HDMI_A_SRM_BASE);
+       bstaus0 = hdcp->read(hdmi, HDMI_A_SRM_BASE + 1);
+       bstaus1 = hdcp->read(hdmi, HDMI_A_SRM_BASE + 2);
+
+       if (bstaus0 & HDMI_A_SRM_BASE_MAX_DEVS_EXCEEDED) {
+               dev_err(hdcp->dev, "MAX_DEVS_EXCEEDED\n");
+               rc = -EPERM;
+               goto out;
+       }
+
+       list = bstaus0 & HDMI_A_SRM_BASE_DEVICE_COUNT;
+       if (list > MAX_DOWNSTREAM_DEVICE_NUM) {
+               dev_err(hdcp->dev, "MAX_DOWNSTREAM_DEVICE_NUM\n");
+               rc = -EPERM;
+               goto out;
+       }
+       if (bstaus1 & HDMI_A_SRM_BASE_MAX_CASCADE_EXCEEDED) {
+               dev_err(hdcp->dev, "MAX_CASCADE_EXCEEDED\n");
+               rc = -EPERM;
+               goto out;
+       }
+
+       value = (list * KSV_LEN) + HEADER + SHAMAX;
+       ksvlistbuf = kmalloc(value, GFP_KERNEL);
+       if (!ksvlistbuf) {
+               rc = -ENOMEM;
+               goto out;
+       }
+
+       ksvlistbuf[(list * KSV_LEN)] = bstaus0;
+       ksvlistbuf[(list * KSV_LEN) + 1] = bstaus1;
+       for (i = 2; i < value; i++) {
+               if (i < HEADER) /* BSTATUS & M0 */
+                       ksvlistbuf[(list * KSV_LEN) + i] =
+                               hdcp->read(hdmi, HDMI_A_SRM_BASE + i + 1);
+               else if (i < (HEADER + (list * KSV_LEN))) /* KSV list */
+                       ksvlistbuf[i - HEADER] =
+                               hdcp->read(hdmi, HDMI_A_SRM_BASE + i + 1);
+               else /* SHA */
+                       ksvlistbuf[i] =
+                               hdcp->read(hdmi, HDMI_A_SRM_BASE + i + 1);
+       }
+       if (hdcp_verify_ksv(ksvlistbuf, value) == true) {
+               rc = 0;
+               dev_dbg(hdcp->dev, "ksv check valid\n");
+       } else {
+               dev_err(hdcp->dev, "ksv check invalid\n");
+               rc = -1;
+       }
+       kfree(ksvlistbuf);
+out:
+       hdcp_modb(hdcp, HDMI_A_SRMCTRL_KSV_MEM_REQ_DISABLE,
+                 HDMI_A_SRMCTRL_KSV_MEM_REQ_MASK, HDMI_A_SRMCTRL);
+       return rc;
+}
+
+static void dw_hdmi_hdcp_2nd_auth(struct dw_hdcp *hdcp)
+{
+       if (dw_hdmi_hdcp_ksvsha1(hdcp))
+               hdcp_modb(hdcp, HDMI_A_SRMCTRL_SHA1_FAIL_ENABLE |
+                         HDMI_A_SRMCTRL_KSV_UPDATE_ENABLE,
+                         HDMI_A_SRMCTRL_SHA1_FAIL_MASK |
+                         HDMI_A_SRMCTRL_KSV_UPDATE_MASK, HDMI_A_SRMCTRL);
+       else
+               hdcp_modb(hdcp, HDMI_A_SRMCTRL_SHA1_FAIL_DISABLE |
+                         HDMI_A_SRMCTRL_KSV_UPDATE_ENABLE,
+                         HDMI_A_SRMCTRL_SHA1_FAIL_MASK |
+                         HDMI_A_SRMCTRL_KSV_UPDATE_MASK, HDMI_A_SRMCTRL);
+}
+
+static void dw_hdmi_hdcp_isr(struct dw_hdcp *hdcp, int hdcp_int)
+{
+       dev_dbg(hdcp->dev, "hdcp_int is 0x%02x\n", hdcp_int);
+       if (hdcp_int & HDMI_A_APIINTSTAT_KSVSHA1_CALC_INT) {
+               dev_dbg(hdcp->dev, "hdcp sink is a repeater\n");
+               dw_hdmi_hdcp_2nd_auth(hdcp);
+       }
+       if (hdcp_int & 0x40) {
+               hdcp->status = DW_HDCP_AUTH_FAIL;
+               if (hdcp->enable != 2)
+                       return;
+
+               if (hdcp->remaining_times > 1)
+                       hdcp->remaining_times--;
+               else if (hdcp->remaining_times == 1)
+                       hdcp_modb(hdcp,
+                                 HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE,
+                                 HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK,
+                                 HDMI_A_HDCPCFG1);
+       }
+       if (hdcp_int & 0x80) {
+               dev_dbg(hdcp->dev, "hdcp auth success\n");
+               hdcp->status = DW_HDCP_AUTH_SUCCESS;
+       }
+}
+
+static ssize_t hdcp_enable_read(struct device *device,
+                               struct device_attribute *attr, char *buf)
+{
+       int enable = 0;
+       struct dw_hdcp *hdcp = dev_get_drvdata(device);
+
+       if (hdcp)
+               enable = hdcp->enable;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", enable);
+}
+
+static ssize_t hdcp_enable_write(struct device *device,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+       int enable;
+       struct dw_hdcp *hdcp = dev_get_drvdata(device);
+
+       if (!hdcp)
+               return -EINVAL;
+
+       if (kstrtoint(buf, 0, &enable))
+               return -EINVAL;
+
+       if ((enable >= 0) && (hdcp->enable != enable)) {
+               if (enable) {
+                       hdcp->enable = enable;
+                       if (hdcp->read(hdcp->hdmi, HDMI_PHY_STAT0) &
+                           HDMI_PHY_HPD)
+                               dw_hdmi_hdcp_start(hdcp);
+               } else {
+                       dw_hdmi_hdcp_stop(hdcp);
+                       hdcp->enable = enable;
+               }
+       }
+
+       return count;
+}
+
+static DEVICE_ATTR(enable, 0644, hdcp_enable_read, hdcp_enable_write);
+
+static ssize_t hdcp_trytimes_read(struct device *device,
+                                 struct device_attribute *attr, char *buf)
+{
+       int trytimes = 0;
+       struct dw_hdcp *hdcp = dev_get_drvdata(device);
+
+       if (hdcp)
+               trytimes = hdcp->retry_times;
+
+       return snprintf(buf, PAGE_SIZE, "%d\n", trytimes);
+}
+
+static ssize_t hdcp_trytimes_write(struct device *device,
+                                  struct device_attribute *attr,
+                                  const char *buf, size_t count)
+{
+       int trytimes;
+       struct dw_hdcp *hdcp = dev_get_drvdata(device);
+
+       if (!hdcp)
+               return -EINVAL;
+
+       if (kstrtoint(buf, 0, &trytimes))
+               return -EINVAL;
+
+       if (hdcp->retry_times != trytimes) {
+               hdcp->retry_times = trytimes;
+               hdcp->remaining_times = hdcp->retry_times;
+       }
+
+       return count;
+}
+
+static DEVICE_ATTR(trytimes, 0644, hdcp_trytimes_read, hdcp_trytimes_write);
+
+static ssize_t hdcp_status_read(struct device *device,
+                               struct device_attribute *attr, char *buf)
+{
+       int status = DW_HDCP_DISABLED;
+       struct dw_hdcp *hdcp = dev_get_drvdata(device);
+
+       if (hdcp)
+               status = hdcp->status;
+
+       if (status == DW_HDCP_DISABLED)
+               return snprintf(buf, PAGE_SIZE, "hdcp disable\n");
+       else if (status == DW_HDCP_AUTH_START)
+               return snprintf(buf, PAGE_SIZE, "hdcp_auth_start\n");
+       else if (status == DW_HDCP_AUTH_SUCCESS)
+               return snprintf(buf, PAGE_SIZE, "hdcp_auth_success\n");
+       else if (status == DW_HDCP_AUTH_FAIL)
+               return snprintf(buf, PAGE_SIZE, "hdcp_auth_fail\n");
+       else
+               return snprintf(buf, PAGE_SIZE, "unknown status\n");
+}
+
+static DEVICE_ATTR(status, 0444, hdcp_status_read, NULL);
+
+static int dw_hdmi_hdcp_probe(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct dw_hdcp *hdcp = pdev->dev.platform_data;
+
+       mdev.minor = MISC_DYNAMIC_MINOR;
+       mdev.name = "hdmi_hdcp1x";
+       mdev.mode = 0666;
+
+       if (misc_register(&mdev)) {
+               dev_err(&pdev->dev, "HDCP: Could not add character driver\n");
+               return -EINVAL;
+       }
+
+       ret = device_create_file(mdev.this_device, &dev_attr_enable);
+       if (ret) {
+               dev_err(&pdev->dev, "HDCP: Could not add sys file enable\n");
+               ret = -EINVAL;
+               goto error0;
+       }
+
+       ret = device_create_file(mdev.this_device, &dev_attr_trytimes);
+       if (ret) {
+               dev_err(&pdev->dev, "HDCP: Could not add sys file trytimes\n");
+               ret = -EINVAL;
+               goto error1;
+       }
+
+       ret = device_create_file(mdev.this_device, &dev_attr_status);
+       if (ret) {
+               dev_err(&pdev->dev, "HDCP: Could not add sys file status\n");
+               ret = -EINVAL;
+               goto error2;
+       }
+       dev_set_drvdata(mdev.this_device, hdcp);
+
+       if (!(hdcp->read(hdcp->hdmi, HDMI_MC_CLKDIS) &
+           HDMI_MC_CLKDIS_HDCPCLK_MASK))
+               hdcp->enable = 1;
+
+       hdcp->dev = &pdev->dev;
+       hdcp->hdcp_start = dw_hdmi_hdcp_start;
+       hdcp->hdcp_stop = dw_hdmi_hdcp_stop;
+       hdcp->hdcp_isr = dw_hdmi_hdcp_isr;
+       hdcp->retry_times = 3;
+       dev_dbg(hdcp->dev, "%s success\n", __func__);
+       return 0;
+
+error2:
+       device_remove_file(mdev.this_device, &dev_attr_trytimes);
+error1:
+       device_remove_file(mdev.this_device, &dev_attr_enable);
+error0:
+       misc_deregister(&mdev);
+       return ret;
+}
+
+static int dw_hdmi_hdcp_remove(struct platform_device *pdev)
+{
+       struct dw_hdcp *hdcp = pdev->dev.platform_data;
+
+       device_remove_file(mdev.this_device, &dev_attr_trytimes);
+       device_remove_file(mdev.this_device, &dev_attr_enable);
+       device_remove_file(mdev.this_device, &dev_attr_status);
+       misc_deregister(&mdev);
+
+       kfree(hdcp->keys);
+       kfree(hdcp->seeds);
+
+       return 0;
+}
+
+static struct platform_driver dw_hdmi_hdcp_driver = {
+       .probe  = dw_hdmi_hdcp_probe,
+       .remove = dw_hdmi_hdcp_remove,
+       .driver = {
+               .name = DW_HDCP_DRIVER_NAME,
+       },
+};
+
+module_platform_driver(dw_hdmi_hdcp_driver);
diff --git a/drivers/gpu/drm/bridge/dw-hdmi-hdcp.h b/drivers/gpu/drm/bridge/dw-hdmi-hdcp.h
new file mode 100644 (file)
index 0000000..197f555
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author Huicong Xu <xhc@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DW_HDMI_HDCP_H
+#define DW_HDMI_HDCP_H
+
+#define DW_HDCP_DRIVER_NAME "dw-hdmi-hdcp"
+#define HDCP_PRIVATE_KEY_SIZE   280
+#define HDCP_KEY_SHA_SIZE       20
+
+struct hdcp_keys {
+       u8 KSV[8];
+       u8 devicekey[HDCP_PRIVATE_KEY_SIZE];
+       u8 sha1[HDCP_KEY_SHA_SIZE];
+};
+
+struct dw_hdcp {
+       int enable;
+       int retry_times;
+       int remaining_times;
+       char *seeds;
+       int invalidkey;
+       char *invalidkeys;
+       int hdcp2_enable;
+       int status;
+       u32 reg_io_width;
+
+       struct hdcp_keys *keys;
+       struct device *dev;
+       struct dw_hdmi *hdmi;
+       void __iomem *regs;
+
+       void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
+       u8 (*read)(struct dw_hdmi *hdmi, int offset);
+       int (*hdcp_start)(struct dw_hdcp *hdcp);
+       int (*hdcp_stop)(struct dw_hdcp *hdcp);
+       void (*hdcp_isr)(struct dw_hdcp *hdcp, int hdcp_int);
+};
+
+#endif
index 912210b54497110b94ebbe54b72f6665e0147963..473755e7bcced1e4e47edeacb7be5ece78e40894 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "dw-hdmi.h"
 #include "dw-hdmi-audio.h"
+#include "dw-hdmi-hdcp.h"
 
 #define HDMI_EDID_LEN          512
 #define DDC_SEGMENT_ADDR       0x30
@@ -161,7 +162,6 @@ struct hdmi_data_info {
        unsigned int enc_color_depth;
        unsigned int colorimetry;
        unsigned int pix_repet_factor;
-       unsigned int hdcp_enable;
        struct hdmi_vmode video_mode;
 };
 
@@ -194,7 +194,7 @@ struct dw_hdmi {
        struct drm_connector connector;
        struct drm_encoder *encoder;
        struct drm_bridge bridge;
-
+       struct platform_device *hdcp_dev;
        enum dw_hdmi_devtype dev_type;
        unsigned int version;
 
@@ -206,6 +206,7 @@ struct dw_hdmi {
 
        struct hdmi_data_info hdmi_data;
        const struct dw_hdmi_plat_data *plat_data;
+       struct dw_hdcp *hdcp;
 
        int vic;
 
@@ -1436,23 +1437,36 @@ static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
  * HDMI TX Setup
  */
 
-static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi)
+static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi,
+                               const struct drm_display_mode *mode)
 {
-       u8 de;
-
-       if (hdmi->hdmi_data.video_mode.mdataenablepolarity)
-               de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH;
-       else
-               de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW;
-
-       /* disable rx detect */
-       hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE,
-                 HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
-
-       hdmi_modb(hdmi, de, HDMI_A_VIDPOLCFG_DATAENPOL_MASK, HDMI_A_VIDPOLCFG);
-
-       hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE,
-                 HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
+       struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
+       u8 vsync_pol, hsync_pol, data_pol, hdmi_dvi;
+
+       /* Configure the video polarity */
+       vsync_pol = mode->flags & DRM_MODE_FLAG_PVSYNC ?
+                   HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH :
+                   HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW;
+       hsync_pol = mode->flags & DRM_MODE_FLAG_PHSYNC ?
+                   HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH :
+                   HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW;
+       data_pol = vmode->mdataenablepolarity ?
+                   HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH :
+                   HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW;
+       hdmi_modb(hdmi, vsync_pol | hsync_pol | data_pol,
+                 HDMI_A_VIDPOLCFG_VSYNCPOL_MASK |
+                 HDMI_A_VIDPOLCFG_HSYNCPOL_MASK |
+                 HDMI_A_VIDPOLCFG_DATAENPOL_MASK,
+                 HDMI_A_VIDPOLCFG);
+
+       /* Config the display mode */
+       hdmi_dvi = hdmi->sink_is_hdmi ? HDMI_A_HDCPCFG0_HDMIDVI_HDMI :
+                  HDMI_A_HDCPCFG0_HDMIDVI_DVI;
+       hdmi_modb(hdmi, hdmi_dvi, HDMI_A_HDCPCFG0_HDMIDVI_MASK,
+                 HDMI_A_HDCPCFG0);
+
+       if (hdmi->hdcp)
+               hdmi->hdcp->hdcp_start(hdmi->hdcp);
 }
 
 static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
@@ -1634,11 +1648,10 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
         * fc_invidconf.HDCP_keepout must be set (1'b1)
         * when activate the scrambler feature.
         */
-       inv_val = (hdmi->hdmi_data.hdcp_enable ||
-                  vmode->mpixelclock > 340000000 ||
+       inv_val = (vmode->mpixelclock > 340000000 ||
                   hdmi->connector.lte_340mcsc_scramble ?
-               HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
-               HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
+                  HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
+                  HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
 
        inv_val |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
                HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
@@ -1899,7 +1912,6 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
         */
        hdmi->hdmi_data.pix_repet_factor =
                (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0;
-       hdmi->hdmi_data.hdcp_enable = 0;
        hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
 
        /* HDMI Initialization Step B.1 */
@@ -1936,8 +1948,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
        hdmi_video_packetize(hdmi);
        hdmi_video_csc(hdmi);
        hdmi_video_sample(hdmi);
-       hdmi_tx_hdcp_config(hdmi);
-
+       hdmi_tx_hdcp_config(hdmi, mode);
        dw_hdmi_clear_overflow(hdmi);
        if (hdmi->cable_plugin && hdmi->sink_is_hdmi)
                hdmi_enable_overflow_interrupts(hdmi);
@@ -2010,6 +2021,8 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi)
                hdmi->phy.enabled = false;
        }
 
+       if (hdmi->hdcp)
+               hdmi->hdcp->hdcp_stop(hdmi->hdcp);
        hdmi->bridge_is_on = false;
 }
 
@@ -2240,7 +2253,7 @@ static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
 static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
 {
        struct dw_hdmi *hdmi = dev_id;
-       u8 intr_stat;
+       u8 intr_stat, hdcp_stat;
        irqreturn_t ret = IRQ_NONE;
 
        if (hdmi->i2c)
@@ -2252,13 +2265,20 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
                return IRQ_WAKE_THREAD;
        }
 
+       hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT);
+       if (hdcp_stat) {
+               dev_dbg(hdmi->dev, "HDCP irq %#x\n", hdcp_stat);
+               hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK);
+               return IRQ_WAKE_THREAD;
+       }
+
        return ret;
 }
 
 static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
 {
        struct dw_hdmi *hdmi = dev_id;
-       u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat;
+       u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat, hdcp_stat;
 
        intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
        phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0);
@@ -2318,6 +2338,14 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
        hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
                    HDMI_IH_MUTE_PHY_STAT0);
 
+       hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT);
+       if (hdcp_stat) {
+               if (hdmi->hdcp)
+                       hdmi->hdcp->hdcp_isr(hdmi->hdcp, hdcp_stat);
+               hdmi_writeb(hdmi, hdcp_stat, HDMI_A_APIINTCLR);
+               hdmi_writeb(hdmi, 0x00, HDMI_A_APIINTMSK);
+       }
+
        return IRQ_HANDLED;
 }
 
@@ -2606,6 +2634,35 @@ static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi *hdmi)
                            hdmi, &dw_hdmi_phy_fops);
 }
 
+static void dw_hdmi_register_hdcp(struct device *dev, struct dw_hdmi *hdmi,
+                                 u32 val)
+{
+       struct dw_hdcp hdmi_hdcp = {
+               .hdmi = hdmi,
+               .write = hdmi_writeb,
+               .read = hdmi_readb,
+               .regs = hdmi->regs,
+               .reg_io_width = val,
+               .enable = 0,
+       };
+       struct platform_device_info hdcp_device_info = {
+               .parent = dev,
+               .id = PLATFORM_DEVID_AUTO,
+               .res = NULL,
+               .num_res = 0,
+               .name = DW_HDCP_DRIVER_NAME,
+               .data = &hdmi_hdcp,
+               .size_data = sizeof(hdmi_hdcp),
+               .dma_mask = DMA_BIT_MASK(32),
+       };
+
+       hdmi->hdcp_dev = platform_device_register_full(&hdcp_device_info);
+       if (IS_ERR(hdmi->hdcp_dev))
+               dev_err(dev, "failed to register hdcp!\n");
+       else
+               hdmi->hdcp = hdmi->hdcp_dev->dev.platform_data;
+}
+
 int dw_hdmi_bind(struct device *dev, struct device *master,
                 void *data, struct drm_encoder *encoder,
                 struct resource *iores, int irq,
@@ -2617,9 +2674,9 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        struct device_node *ddc_node;
        struct dw_hdmi *hdmi;
        int ret;
-       u32 val = 1;
        u8 prod_id0;
        u8 prod_id1;
+       u32 val = 1;
        u8 config0;
        u8 config3;
 
@@ -2826,6 +2883,7 @@ int dw_hdmi_bind(struct device *dev, struct device *master,
        dev_set_drvdata(dev, hdmi);
 
        dw_hdmi_register_debugfs(dev, hdmi);
+       dw_hdmi_register_hdcp(dev, hdmi, val);
 
        return 0;
 
@@ -2855,6 +2913,9 @@ void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
        if (hdmi->audio && !IS_ERR(hdmi->audio))
                platform_device_unregister(hdmi->audio);
 
+       if (hdmi->hdcp_dev && !IS_ERR(hdmi->hdcp_dev))
+               platform_device_unregister(hdmi->hdcp_dev);
+
        /* Disable all interrupts */
        hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);