From 1f58bcd52f652ff32c9527e4c1b096850710b2ad Mon Sep 17 00:00:00 2001 From: xuhuicong Date: Fri, 7 Apr 2017 16:42:21 +0800 Subject: [PATCH] drm: bridge: dw-hdmi: add hdcp1.4 support 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 --- drivers/gpu/drm/bridge/Makefile | 2 +- drivers/gpu/drm/bridge/dw-hdmi-hdcp.c | 752 ++++++++++++++++++++++++++ drivers/gpu/drm/bridge/dw-hdmi-hdcp.h | 51 ++ drivers/gpu/drm/bridge/dw-hdmi.c | 117 +++- 4 files changed, 893 insertions(+), 29 deletions(-) create mode 100644 drivers/gpu/drm/bridge/dw-hdmi-hdcp.c create mode 100644 drivers/gpu/drm/bridge/dw-hdmi-hdcp.h diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 33291a94f96d..dea158758ea5 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -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 index 000000000000..e50ee5bf541b --- /dev/null +++ b/drivers/gpu/drm/bridge/dw-hdmi-hdcp.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author Huicong Xu + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000000..197f55561fa1 --- /dev/null +++ b/drivers/gpu/drm/bridge/dw-hdmi-hdcp.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author Huicong Xu + * + * 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 diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c index 912210b54497..473755e7bcce 100644 --- a/drivers/gpu/drm/bridge/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/dw-hdmi.c @@ -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); -- 2.34.1