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 <xhc@rock-chips.com>
This commit is contained in:
xuhuicong 2017-04-07 16:42:21 +08:00 committed by Huang, Tao
parent 428fab17d9
commit 1f58bcd52f
4 changed files with 890 additions and 26 deletions

View 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

View File

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

View File

@ -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

View 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;
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
u8 vsync_pol, hsync_pol, data_pol, hdmi_dvi;
if (hdmi->hdmi_data.video_mode.mdataenablepolarity)
de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH;
else
de = HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW;
/* 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);
/* disable rx detect */
hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE,
HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
/* 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);
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);
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);