drm/bridge: dw-hdmi-qp: Add hdcp driver

Signed-off-by: Algea Cao <algea.cao@rock-chips.com>
Change-Id: Ic71fc6c8345c9e2987e9f2507d7ba4e0eaf90a1e
This commit is contained in:
Algea Cao
2023-06-19 09:26:16 +08:00
committed by Tao Huang
parent ce2a8c86e6
commit d772f406a5
7 changed files with 1117 additions and 30 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-hdcp.o \
dw-hdmi-qp.o
dw-hdmi-qp.o dw-hdmi-qp-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 dw-hdmi-qp-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o dw-hdmi-qp-cec.o
@@ -0,0 +1,650 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) Rockchip Electronics Co.Ltd
* Author:
* Algea Cao <algea.cao@rock-chips.com>
*/
#include <linux/clk.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 <crypto/sha.h>
#include <drm/bridge/dw_hdmi.h>
#include "dw-hdmi-qp.h"
#include "dw-hdmi-qp-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 {
HDCP14_R0_TIMER_OVR_EN_MASK = 0x01,
HDCP14_R0_TIMER_OVR_EN = 0x01,
HDCP14_R0_TIMER_OVR_DISABLE = 0x00,
HDCP14_RI_TIMER_OVR_EN_MASK = 0x80,
HDCP14_RI_TIMER_OVR_EN = 0x80,
HDCP14_RI_TIMER_OVR_DISABLE = 0x00,
HDCP14_R0_TIMER_OVR_VALUE_MASK = 0x1e,
HDCP14_RI_TIMER_OVR_VALUE_MASK = 0xff00,
HDCP14_KEY_WR_OK = 0x100,
HDCP14_HPD_MASK = 0x01,
HDCP14_HPD_EN = 0x01,
HDCP14_HPD_DISABLE = 0x00,
HDCP14_ENCRYPTION_ENABLE_MASK = 0x04,
HDCP14_ENCRYPTION_ENABLE = 0x04,
HDCP14_ENCRYPTION_DISABLE = 0x04,
HDCP14_KEY_DECRYPT_EN_MASK = 0x400,
HDCP14_KEY_DECRYPT_EN = 0x400,
HDCP14_KEY_DECRYPT_DISABLE = 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 inline unsigned int shacircularshift(unsigned int bits,
unsigned int word)
{
return (((word << bits) & 0xFFFFFFFF) | (word >> (32 - bits)));
}
static void hdcp_modb(struct dw_qp_hdcp *hdcp, u32 data, u32 mask, u32 reg)
{
struct dw_hdmi_qp *hdmi = hdcp->hdmi;
u32 val = hdcp->read(hdmi, reg) & ~mask;
val |= data & mask;
hdcp->write(hdmi, val, reg);
}
static int hdcp_load_keys_cb(struct dw_qp_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_err(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_hdcp_qp_hdcp_load_key(struct dw_qp_hdcp *hdcp)
{
int i, j;
int ret, val;
void __iomem *reg_rmsts_addr;
struct dw_hdmi_qp_hdcp_keys *hdcp_keys;
struct dw_hdmi_qp *hdmi = hdcp->hdmi;
u32 ksv, dkl, dkh;
if (!hdcp->keys) {
ret = hdcp_load_keys_cb(hdcp);
if (ret)
return ret;
}
hdcp_keys = hdcp->keys;
reg_rmsts_addr = hdcp->regs + HDCP14_KEY_STATUS;
/* hdcp key has been written */
if (hdcp->read(hdmi, HDCP14_KEY_STATUS) & 0x3f) {
dev_info(hdcp->dev, "hdcp key has been written\n");
return 0;
}
ksv = hdcp_keys->KSV[0] | hdcp_keys->KSV[1] << 8 |
hdcp_keys->KSV[2] << 16 | hdcp_keys->KSV[3] << 24;
hdcp->write(hdmi, ksv, HDCP14_AKSV_L);
ksv = hdcp_keys->KSV[4];
hdcp->write(hdmi, ksv, HDCP14_AKSV_H);
if (hdcp->seeds) {
hdcp_modb(hdcp, HDCP14_KEY_DECRYPT_EN,
HDCP14_KEY_DECRYPT_EN_MASK,
HDCP14_CONFIG0);
hdcp->write(hdmi, (hdcp->seeds[0] << 8) | hdcp->seeds[1],
HDCP14_KEY_SEED);
} else {
hdcp_modb(hdcp, HDCP14_KEY_DECRYPT_DISABLE,
HDCP14_KEY_DECRYPT_EN_MASK,
HDCP14_CONFIG0);
}
for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) {
dkl = 0;
dkh = 0;
for (j = 0; j < 4; j++)
dkl |= hdcp_keys->devicekey[i + j] << (j * 8);
for (j = 4; j < 7; j++)
dkh |= hdcp_keys->devicekey[i + j] << ((j - 4) * 8);
hdcp->write(hdmi, dkh, HDCP14_KEY_H);
hdcp->write(hdmi, dkl, HDCP14_KEY_L);
ret = readx_poll_timeout(readl, reg_rmsts_addr, val,
val & HDCP14_KEY_WR_OK, 1000,
DPK_WR_OK_TIMEOUT_US);
if (ret) {
dev_err(hdcp->dev, "hdcp key write err\n");
return ret;
}
}
return 0;
}
static void dw_hdcp_qp_hdcp_restart(struct dw_qp_hdcp *hdcp)
{
mutex_lock(&hdcp->mutex);
if (!hdcp->remaining_times) {
mutex_unlock(&hdcp->mutex);
return;
}
hdcp_modb(hdcp, 0, HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_CONFIG0);
hdcp->write(hdcp->hdmi, 1, HDCP14_CONFIG1);
mdelay(50);
hdcp->write(hdcp->hdmi, HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N,
AVP_1_INT_CLEAR);
hdcp_modb(hdcp, HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N,
HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N, AVP_1_INT_MASK_N);
hdcp_modb(hdcp, HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_CONFIG0);
hdcp->remaining_times--;
mutex_unlock(&hdcp->mutex);
}
static int dw_hdcp_qp_hdcp_start(struct dw_qp_hdcp *hdcp)
{
struct dw_hdmi_qp *hdmi = hdcp->hdmi;
dw_hdcp_qp_hdcp_load_key(hdcp);
mutex_lock(&hdcp->mutex);
hdcp->remaining_times = hdcp->retry_times;
hdcp->write(hdmi, HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N, AVP_1_INT_CLEAR);
hdcp_modb(hdcp, HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N,
HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N, AVP_1_INT_MASK_N);
mdelay(50);
hdcp_modb(hdcp, HDCP14_ENCRYPTION_ENABLE | HDCP14_HPD_EN,
HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_CONFIG0);
hdcp->status = DW_HDCP_AUTH_START;
dev_info(hdcp->dev, "start hdcp\n");
mutex_unlock(&hdcp->mutex);
queue_work(hdcp->workqueue, &hdcp->work);
return 0;
}
static int dw_hdcp_qp_hdcp_stop(struct dw_qp_hdcp *hdcp)
{
mutex_lock(&hdcp->mutex);
hdcp_modb(hdcp, 0, HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_CONFIG0);
hdcp_modb(hdcp, 0, HDCP14_AUTH_CHG_MASK_N | HDCP14_KSV_LIST_DONE_MASK_N, AVP_1_INT_MASK_N);
hdcp->write(hdcp->hdmi, 0, HDCP14_CONFIG1);
hdcp->status = DW_HDCP_DISABLED;
mutex_unlock(&hdcp->mutex);
return 0;
}
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;
sha1_init(sha->mdigest);
}
static void sha_processblock(struct sha_t *sha)
{
u32 array[SHA1_WORKSPACE_WORDS];
sha1_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 void dw_hdcp_qp_hdcp_2nd_auth(struct dw_qp_hdcp *hdcp)
{
u8 *data;
u32 len;
len = (hdcp->read(hdcp->hdmi, HDCP14_STATUS0) & HDCP14_RPT_DEVICE_COUNT) >> 9;
len = len * KSV_LEN + BSTATUS_LEN + M0_LEN + SHAMAX;
data = kmalloc(len, GFP_KERNEL);
if (!data)
return;
hdcp->get_mem(hdcp->hdmi, data, len);
if (hdcp_verify_ksv(data, len))
hdcp->write(hdcp->hdmi, HDCP14_SHA1_MSG_CORRECT_P, HDCP14_CONFIG1);
else
dw_hdcp_qp_hdcp_restart(hdcp);
}
static void dw_hdcp_qp_hdcp_auth(struct dw_qp_hdcp *hdcp, u32 hdcp_status)
{
if (!(hdcp_status & BIT(2))) {
mutex_lock(&hdcp->mutex);
if (hdcp->status == DW_HDCP_DISABLED) {
mutex_unlock(&hdcp->mutex);
return;
}
dev_err(hdcp->dev, "hdcp auth failed\n");
hdcp_modb(hdcp, 0, HDCP14_ENCRYPTION_ENABLE_MASK | HDCP14_HPD_MASK,
HDCP14_CONFIG0);
hdcp->status = DW_HDCP_AUTH_FAIL;
mutex_unlock(&hdcp->mutex);
dw_hdcp_qp_hdcp_restart(hdcp);
} else {
mutex_lock(&hdcp->mutex);
dev_info(hdcp->dev, "hdcp auth success\n");
hdcp->status = DW_HDCP_AUTH_SUCCESS;
mutex_unlock(&hdcp->mutex);
}
}
static void dw_hdcp_qp_hdcp_isr(struct dw_qp_hdcp *hdcp, u32 avp_int, u32 hdcp_status)
{
if (hdcp->status == DW_HDCP_DISABLED)
return;
dev_info(hdcp->dev, "hdcp_int is 0x%02x\n", hdcp_status);
if (avp_int & HDCP14_KSV_LIST_DONE_MASK_N)
dw_hdcp_qp_hdcp_2nd_auth(hdcp);
if (avp_int & HDCP14_AUTH_CHG_MASK_N)
dw_hdcp_qp_hdcp_auth(hdcp, hdcp_status);
}
static ssize_t trytimes_show(struct device *device,
struct device_attribute *attr, char *buf)
{
int trytimes = 0;
struct dw_qp_hdcp *hdcp = dev_get_drvdata(device);
if (hdcp)
trytimes = hdcp->retry_times;
return snprintf(buf, PAGE_SIZE, "%d\n", trytimes);
}
static ssize_t trytimes_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
int trytimes;
struct dw_qp_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_RW(trytimes);
static ssize_t status_show(struct device *device,
struct device_attribute *attr, char *buf)
{
int status = DW_HDCP_DISABLED;
struct dw_qp_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_RO(status);
static struct attribute *dw_hdmi_qp_hdcp_attrs[] = {
&dev_attr_trytimes.attr,
&dev_attr_status.attr,
NULL
};
ATTRIBUTE_GROUPS(dw_hdmi_qp_hdcp);
/* If sink is a repeater, we need to wait ksv list ready */
static void dw_hdmi_qp_hdcp(struct work_struct *p_work)
{
struct dw_qp_hdcp *hdcp = container_of(p_work, struct dw_qp_hdcp, work);
u32 val;
int i = 500;
while (i--) {
usleep_range(7000, 8000);
mutex_lock(&hdcp->mutex);
if (hdcp->status == DW_HDCP_DISABLED) {
dev_dbg(hdcp->dev, "hdcp is disabled, don't wait repeater ready\n");
mutex_unlock(&hdcp->mutex);
return;
}
val = hdcp->read(hdcp->hdmi, HDCP14_STATUS1);
/* sink isn't repeater or ksv fifo ready, stop waiting */
if (!(val & HDCP14_RCV_REPEATER) || (val & HDCP14_RCV_KSV_FIFO_READY)) {
dev_dbg(hdcp->dev, "wait ksv fifo finished\n");
mutex_unlock(&hdcp->mutex);
return;
}
mutex_unlock(&hdcp->mutex);
}
if (i < 0) {
dev_err(hdcp->dev, "wait repeater ready time out\n");
dw_hdcp_qp_hdcp_restart(hdcp);
}
}
static int dw_hdcp_qp_hdcp_probe(struct platform_device *pdev)
{
int ret = 0;
struct dw_qp_hdcp *hdcp = pdev->dev.platform_data;
/* retry time if hdcp auth fail. unlimited time if set 0 */
hdcp->dev = &pdev->dev;
hdcp->hdcp_start = dw_hdcp_qp_hdcp_start;
hdcp->hdcp_stop = dw_hdcp_qp_hdcp_stop;
hdcp->hdcp_isr = dw_hdcp_qp_hdcp_isr;
ret = device_add_groups(hdcp->dev, dw_hdmi_qp_hdcp_groups);
if (ret) {
dev_err(hdcp->dev, "Failed to add sysfs files group\n");
return ret;
}
platform_set_drvdata(pdev, hdcp);
hdcp->workqueue = create_workqueue("hdcp_queue");
INIT_WORK(&hdcp->work, dw_hdmi_qp_hdcp);
hdcp->retry_times = 3;
mutex_init(&hdcp->mutex);
dev_info(hdcp->dev, "%s success\n", __func__);
return 0;
}
static int dw_hdcp_qp_hdcp_remove(struct platform_device *pdev)
{
struct dw_qp_hdcp *hdcp = pdev->dev.platform_data;
cancel_work_sync(&hdcp->work);
flush_workqueue(hdcp->workqueue);
destroy_workqueue(hdcp->workqueue);
device_remove_groups(hdcp->dev, dw_hdmi_qp_hdcp_groups);
kfree(hdcp->keys);
kfree(hdcp->seeds);
return 0;
}
static struct platform_driver dw_hdcp_qp_hdcp_driver = {
.probe = dw_hdcp_qp_hdcp_probe,
.remove = dw_hdcp_qp_hdcp_remove,
.driver = {
.name = DW_HDCP_QP_DRIVER_NAME,
},
};
module_platform_driver(dw_hdcp_qp_hdcp_driver);
MODULE_DESCRIPTION("DW HDMI QP transmitter HDCP driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) Rockchip Electronics Co.Ltd
* Author:
* Algea Cao <algea.cao@rock-chips.com>
*/
#ifndef DW_HDMI_QP_HDCP_H
#define DW_HDMI_QP_HDCP_H
#include <linux/miscdevice.h>
#define DW_HDCP_QP_DRIVER_NAME "dw-hdmi-qp-hdcp"
#define PRIVATE_KEY_SIZE 280
#define KEY_SHA_SIZE 20
#define KSV_LEN 5
#define BSTATUS_LEN 2
#define M0_LEN 8
#define SHAMAX 20
struct dw_hdmi_qp_hdcp_keys {
u8 KSV[8];
u8 devicekey[PRIVATE_KEY_SIZE];
u8 sha1[KEY_SHA_SIZE];
};
struct dw_qp_hdcp {
int retry_times;
int remaining_times;
char *seeds;
int invalidkey;
char *invalidkeys;
int hdcp2_enable;
int status;
u32 reg_io_width;
struct dw_hdmi_qp_hdcp_keys *keys;
struct device *dev;
struct dw_hdmi_qp *hdmi;
void __iomem *regs;
struct mutex mutex;
struct work_struct work;
struct workqueue_struct *workqueue;
void (*write)(struct dw_hdmi_qp *hdmi, u32 val, int offset);
u32 (*read)(struct dw_hdmi_qp *hdmi, int offset);
void (*get_mem)(struct dw_hdmi_qp *hdmi, u8 *data, u32 len);
int (*hdcp_start)(struct dw_qp_hdcp *hdcp);
int (*hdcp_stop)(struct dw_qp_hdcp *hdcp);
void (*hdcp_isr)(struct dw_qp_hdcp *hdcp, u32 avp_int, u32 hdcp_status);
};
#endif
+324 -28
View File
@@ -4,6 +4,7 @@
* Author:
* Algea Cao <algea.cao@rock-chips.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
@@ -25,6 +26,7 @@
#include <drm/drm_dsc.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
#include <drm/drm_hdcp.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
@@ -38,6 +40,7 @@
#include "dw-hdmi-qp-audio.h"
#include "dw-hdmi-qp.h"
#include "dw-hdmi-qp-cec.h"
#include "dw-hdmi-qp-hdcp.h"
#include <media/cec-notifier.h>
@@ -52,6 +55,19 @@
#define HDMI14_MAX_TMDSCLK 340000000
#define HDMI20_MAX_TMDSCLK_KHZ 600000
#define HDMI_VH0 0x20
#define HDMI_HDCP_ADDR 0x3a
#define HDMI_BCAPS 0x40
#define HDMI_HDCP14_SUPPORT BIT(7)
#define HDMI_HDCP2_VERSION 0x50
#define HDMI_HDCP2_SUPPORT BIT(2)
#define SINK_CAP_HDCP14 BIT(0)
#define SINK_CAP_HDCP2 BIT(1)
#define HDMI_HDCP2_AUTH BIT(1)
#define HDMI_HDCP14_AUTH BIT(0)
static const unsigned int dw_hdmi_cable[] = {
EXTCON_DISP_HDMI,
EXTCON_NONE,
@@ -231,7 +247,7 @@ struct dw_hdmi_qp {
struct hdmi_qp_data_info hdmi_data;
const struct dw_hdmi_plat_data *plat_data;
struct dw_qp_hdcp *hdcp;
int vic;
int main_irq;
int avp_irq;
@@ -250,6 +266,7 @@ struct dw_hdmi_qp {
struct i2c_adapter *ddc;
void __iomem *regs;
void __iomem *hdcp14_mem;
bool sink_is_hdmi;
bool sink_has_audio;
bool dclk_en;
@@ -270,6 +287,8 @@ struct dw_hdmi_qp {
bool rxsense; /* rxsense state */
u8 phy_mask; /* desired phy int mask settings */
u8 mc_clkdis; /* clock disable register */
u8 hdcp_caps;
u8 hdcp_status;
bool update;
bool hdr2sdr;
@@ -947,8 +966,6 @@ static void dw_hdmi_i2c_init(struct dw_hdmi_qp *hdmi)
/* Software reset */
hdmi_writel(hdmi, 0x01, I2CM_CONTROL0);
hdmi_writel(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0);
hdmi_modb(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0);
/* Clear DONE and ERROR interrupts */
@@ -1879,6 +1896,58 @@ hdmi_get_tmdsclock(struct dw_hdmi_qp *hdmi, unsigned long mpixelclock)
return tmdsclock;
}
static void dw_hdmi_qp_hdcp_enable(struct dw_hdmi_qp *hdmi,
struct drm_connector *connector)
{
int ret, val;
const struct drm_connector_state *conn_state = connector->state;
void *data = hdmi->plat_data->phy_data;
if (conn_state->content_protection != DRM_MODE_CONTENT_PROTECTION_DESIRED)
return;
/* sink support hdcp2.x */
if (hdmi->hdcp_caps & SINK_CAP_HDCP2) {
hdmi_writel(hdmi, HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ, AVP_3_INT_CLEAR);
hdmi_modb(hdmi, HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ,
HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ, AVP_3_INT_MASK_N);
hdmi_writel(hdmi, 0x35, HDCP2LOGIC_ESM_GPIO_IN);
hdmi_modb(hdmi, 0, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
if (hdmi->plat_data->set_hdcp2_enable)
hdmi->plat_data->set_hdcp2_enable(data, true);
/* wait hdcp2.X auth success */
ret = regmap_read_poll_timeout(hdmi->regm, HDCP2LOGIC_ESM_GPIO_OUT, val,
FIELD_GET(HDCP2_AUTHENTICATION_SUCCESS, val),
10000, 2000000);
if (ret) {
hdmi->hdcp_status &= ~HDMI_HDCP2_AUTH;
dev_info(hdmi->dev, "hdcp2 auth failed,start hdcp1.4\n");
hdmi_writel(hdmi, 0, HDCP2LOGIC_ESM_GPIO_IN);
hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
if (hdmi->plat_data->set_hdcp2_enable)
hdmi->plat_data->set_hdcp2_enable(data, false);
if (hdmi->hdcp && hdmi->hdcp->hdcp_start)
hdmi->hdcp->hdcp_start(hdmi->hdcp);
goto exit;
}
hdmi->hdcp_status |= HDMI_HDCP2_AUTH;
drm_hdcp_update_content_protection(connector, DRM_MODE_CONTENT_PROTECTION_ENABLED);
dev_info(hdmi->dev, "HDCP2 authentication succeed\n");
} else {
if (hdmi->hdcp && hdmi->hdcp->hdcp_start)
hdmi->hdcp->hdcp_start(hdmi->hdcp);
}
exit:
if (hdmi->plat_data->set_hdcp_status)
hdmi->plat_data->set_hdcp_status(data, hdmi->hdcp_status);
}
static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi,
const struct drm_connector *connector,
struct drm_display_mode *mode)
@@ -2040,6 +2109,7 @@ static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi,
dev_info(hdmi->dev, "%s DVI mode\n", __func__);
}
dw_hdmi_qp_hdcp_enable(hdmi, hdmi->curr_conn);
hdmi->frl_switch = false;
return 0;
}
@@ -2129,6 +2199,58 @@ static bool dw_hdmi_qp_check_output_type_changed(struct dw_hdmi_qp *hdmi)
return false;
}
static ssize_t hdcp_ddc_read(struct i2c_adapter *adapter, u8 address,
u8 offset, void *buffer)
{
int ret;
struct i2c_msg msgs[2] = {
{
.addr = address,
.flags = 0,
.len = 1,
.buf = &offset,
}, {
.addr = address,
.flags = I2C_M_RD,
.len = 1,
.buf = buffer,
}
};
ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
return ret;
if (ret != ARRAY_SIZE(msgs))
return -EPROTO;
return 0;
}
static u8 dw_hdmi_qp_hdcp_capable(struct dw_hdmi_qp *hdmi)
{
u8 version = 0;
u8 bcaps;
int ret;
ret = hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, HDMI_BCAPS, &bcaps);
if (ret < 0) {
dev_err(hdmi->dev, "get hdcp1.4 capable failed:%d\n", ret);
return 0;
}
if (bcaps & HDMI_HDCP14_SUPPORT)
version |= SINK_CAP_HDCP14;
ret = hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, HDMI_HDCP2_VERSION, &bcaps);
if (ret < 0) {
dev_err(hdmi->dev, "get hdcp2.x capable failed:%d\n", ret);
return 0;
}
if (bcaps & HDMI_HDCP2_SUPPORT)
version |= SINK_CAP_HDCP2;
return version;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct dw_hdmi_qp *hdmi =
@@ -2178,6 +2300,7 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
if (hdmi->plat_data->get_yuv422_format)
hdmi->plat_data->get_yuv422_format(connector, edid);
dw_hdmi_update_hdr_property(connector);
hdmi->hdcp_caps = dw_hdmi_qp_hdcp_capable(hdmi);
if (ret > 0 && hdmi->plat_data->split_mode) {
struct dw_hdmi_qp *secondary = NULL;
void *secondary_data;
@@ -2460,6 +2583,37 @@ static bool check_hdr_color_change(struct drm_connector_state *old_state,
return false;
}
static bool check_dw_hdcp_state_changed(struct drm_connector *conn,
struct drm_atomic_state *state)
{
struct drm_connector_state *old_state, *new_state;
u64 old_cp, new_cp;
old_state = drm_atomic_get_old_connector_state(state, conn);
new_state = drm_atomic_get_new_connector_state(state, conn);
old_cp = old_state->content_protection;
new_cp = new_state->content_protection;
if (old_state->hdcp_content_type != new_state->hdcp_content_type &&
new_cp != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
new_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
return true;
}
if (!new_state->crtc) {
if (old_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED)
new_state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
return false;
}
if (old_cp == new_cp ||
(old_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED &&
new_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED))
return false;
return true;
}
static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
struct drm_atomic_state *state)
{
@@ -2605,6 +2759,9 @@ static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
}
}
if (check_dw_hdcp_state_changed(connector, state))
crtc_state->mode_changed = true;
return 0;
}
@@ -2791,6 +2948,7 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
{
struct dw_hdmi_qp *hdmi = bridge->driver_private;
void *data = hdmi->plat_data->phy_data;
const struct drm_connector_state *conn_state = hdmi->curr_conn->state;
if (hdmi->panel)
drm_panel_disable(hdmi->panel);
@@ -2799,6 +2957,19 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
hdmi_writel(hdmi, 1, PKTSCHED_PKT_CONTROL0);
mdelay(50);
hdmi_modb(hdmi, 0, HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ,
AVP_3_INT_MASK_N);
if (hdmi->hdcp && hdmi->hdcp->hdcp_stop)
hdmi->hdcp->hdcp_stop(hdmi->hdcp);
hdmi_writel(hdmi, 0, HDCP2LOGIC_ESM_GPIO_IN);
if (conn_state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
drm_hdcp_update_content_protection(hdmi->curr_conn,
DRM_MODE_CONTENT_PROTECTION_DESIRED);
if (hdmi->plat_data->set_hdcp_status)
hdmi->plat_data->set_hdcp_status(data, hdmi->hdcp_status);
extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false);
handle_plugged_change(hdmi, false);
mutex_lock(&hdmi->mutex);
@@ -2815,7 +2986,7 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
hdmi_writel(hdmi, 0, FLT_CONFIG0);
hdmi_writel(hdmi, 0, SCRAMB_CONFIG0);
/* set sink frl mode disable */
if (hdmi->curr_conn && dw_hdmi_support_scdc(hdmi, &hdmi->curr_conn->display_info))
if (dw_hdmi_support_scdc(hdmi, &hdmi->curr_conn->display_info))
drm_scdc_writeb(hdmi->ddc, 0x31, 0);
hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
@@ -2947,17 +3118,77 @@ static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
static irqreturn_t dw_hdmi_qp_avp_hardirq(int irq, void *dev_id)
{
struct dw_hdmi_qp *hdmi = dev_id;
u32 stat;
u32 stat1, stat3;
stat = hdmi_readl(hdmi, AVP_1_INT_STATUS);
if (stat) {
dev_dbg(hdmi->dev, "HDCP irq %#x\n", stat);
stat &= ~stat;
hdmi_writel(hdmi, stat, AVP_1_INT_MASK_N);
return IRQ_WAKE_THREAD;
stat1 = hdmi_readl(hdmi, AVP_1_INT_STATUS);
stat3 = hdmi_readl(hdmi, AVP_3_INT_STATUS);
if (!stat1 && !stat3)
return IRQ_NONE;
return IRQ_WAKE_THREAD;
}
static irqreturn_t dw_hdmi_qp_avp_irq(int irq, void *dev_id)
{
struct dw_hdmi_qp *hdmi = dev_id;
struct drm_connector_state *conn_state;
void *data = hdmi->plat_data->phy_data;
u32 stat1, stat3, val;
stat1 = hdmi_readl(hdmi, AVP_1_INT_STATUS);
stat3 = hdmi_readl(hdmi, AVP_3_INT_STATUS);
hdmi_writel(hdmi, stat1, AVP_1_INT_CLEAR);
hdmi_writel(hdmi, stat3, AVP_3_INT_CLEAR);
if (!hdmi->curr_conn || !hdmi->curr_conn->state)
return IRQ_HANDLED;
conn_state = hdmi->curr_conn->state;
val = conn_state->content_protection;
if (hdmi->hdcp && hdmi->hdcp->hdcp_isr) {
u32 hdcp_status = hdmi_readl(hdmi, HDCP14_STATUS0);
if (stat1 & HDCP14_AUTH_CHG_MASK_N) {
/* hdcp14 auth success */
if (hdcp_status & BIT(2)) {
hdmi->hdcp_status |= HDMI_HDCP14_AUTH;
if (conn_state->content_protection !=
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
} else if (!(hdcp_status & BIT(2))) {
hdmi->hdcp_status &= ~HDMI_HDCP14_AUTH;
if (conn_state->content_protection !=
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
val = DRM_MODE_CONTENT_PROTECTION_DESIRED;
}
conn_state->content_protection = val;
}
hdmi->hdcp->hdcp_isr(hdmi->hdcp, stat1, hdcp_status);
}
return IRQ_NONE;
if (stat3 & HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ) {
stat3 = hdmi_readl(hdmi, HDCP2LOGIC_ESM_GPIO_OUT);
if (stat3 & HDCP2_AUTHENTICATION_SUCCESS) {
hdmi->hdcp_status |= HDMI_HDCP2_AUTH;
if (conn_state->content_protection !=
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
val = DRM_MODE_CONTENT_PROTECTION_ENABLED;
} else if (!(stat3 & HDCP2_AUTHENTICATION_SUCCESS)) {
hdmi->hdcp_status &= ~HDMI_HDCP2_AUTH;
if (conn_state->content_protection !=
DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
val = DRM_MODE_CONTENT_PROTECTION_DESIRED;
}
conn_state->content_protection = val;
}
if (hdmi->plat_data->set_hdcp_status)
hdmi->plat_data->set_hdcp_status(data, hdmi->hdcp_status);
return IRQ_HANDLED;
}
static irqreturn_t dw_hdmi_qp_earc_hardirq(int irq, void *dev_id)
@@ -2976,21 +3207,6 @@ static irqreturn_t dw_hdmi_qp_earc_hardirq(int irq, void *dev_id)
return IRQ_NONE;
}
static irqreturn_t dw_hdmi_qp_avp_irq(int irq, void *dev_id)
{
struct dw_hdmi_qp *hdmi = dev_id;
u32 stat;
stat = hdmi_readl(hdmi, AVP_1_INT_STATUS);
if (!stat)
return IRQ_NONE;
hdmi_writel(hdmi, stat, AVP_1_INT_CLEAR);
return IRQ_HANDLED;
}
static irqreturn_t dw_hdmi_qp_earc_irq(int irq, void *dev_id)
{
struct dw_hdmi_qp *hdmi = dev_id;
@@ -3383,6 +3599,70 @@ static void dw_hdmi_register_debugfs(struct device *dev, struct dw_hdmi_qp *hdmi
hdmi, &dw_hdmi_ctrl_fops);
}
static void dw_hdmi_qp_hdcp14_get_mem(struct dw_hdmi_qp *hdmi, u8 *data, u32 len)
{
u32 ksv_len, i, val;
void *hdmi_data = hdmi->plat_data->phy_data;
if (hdmi->plat_data->set_hdcp14_mem)
hdmi->plat_data->set_hdcp14_mem(hdmi_data, true);
ksv_len = len - BSTATUS_LEN - M0_LEN - SHAMAX;
for (i = 0; i < len; i++) {
/* read ksv list */
if (i < ksv_len)
val = readl(hdmi->hdcp14_mem + HDMI_HDCP14_MEM_KSV0 + i * 4);
/* read bstatus */
else if (i < len - SHAMAX - M0_LEN)
val = readl(hdmi->hdcp14_mem + HDMI_HDCP14_MEM_BSTATUS0 +
(i - ksv_len) * 4);
/* read M0 */
else if (i < len - SHAMAX)
val = readl(hdmi->hdcp14_mem + HDMI_HDCP14_MEM_M0_1 +
(i - ksv_len - BSTATUS_LEN) * 4);
else
/* VH0 save in external memory is error, we need to read VH0 via ddc */
hdcp_ddc_read(hdmi->ddc, HDMI_HDCP_ADDR, HDMI_VH0 + i - (len - SHAMAX),
&val);
data[i] = val;
}
if (hdmi->plat_data->set_hdcp14_mem)
hdmi->plat_data->set_hdcp14_mem(hdmi_data, false);
}
static int dw_hdmi_qp_register_hdcp(struct device *dev,
struct dw_hdmi_qp *hdmi)
{
struct dw_qp_hdcp hdmi_hdcp = {
.hdmi = hdmi,
.write = hdmi_writel,
.read = hdmi_readl,
.regs = hdmi->regs,
.get_mem = dw_hdmi_qp_hdcp14_get_mem,
};
struct platform_device_info hdcp_device_info = {
.parent = dev,
.id = PLATFORM_DEVID_AUTO,
.res = NULL,
.num_res = 0,
.name = DW_HDCP_QP_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");
return -ENOMEM;
}
hdmi->hdcp = hdmi->hdcp_dev->dev.platform_data;
return 0;
}
static struct dw_hdmi_qp *
__dw_hdmi_probe(struct platform_device *pdev,
const struct dw_hdmi_plat_data *plat_data)
@@ -3558,7 +3838,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->avp_irq = irq;
ret = devm_request_threaded_irq(dev, hdmi->avp_irq,
dw_hdmi_qp_avp_hardirq,
dw_hdmi_qp_avp_irq, IRQF_SHARED,
dw_hdmi_qp_avp_irq, IRQF_ONESHOT,
dev_name(dev), hdmi);
if (ret)
goto err_aud;
@@ -3611,6 +3891,20 @@ __dw_hdmi_probe(struct platform_device *pdev,
dw_hdmi_register_debugfs(dev, hdmi);
if (hdmi_readl(hdmi, CONFIG_REG) & CONFIG_HDCP14) {
iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
hdmi->hdcp14_mem = devm_ioremap_resource(dev, iores);
if (IS_ERR(hdmi->hdcp14_mem)) {
ret = PTR_ERR(hdmi->hdcp14_mem);
goto err_cec;
}
ret = dw_hdmi_qp_register_hdcp(dev, hdmi);
if (ret)
goto err_cec;
}
return hdmi;
err_cec:
@@ -3663,6 +3957,8 @@ static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi)
hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder);
if (!IS_ERR(hdmi->cec))
platform_device_unregister(hdmi->cec);
if (!IS_ERR(hdmi->hdcp_dev))
platform_device_unregister(hdmi->hdcp_dev);
if (hdmi->i2c)
i2c_del_adapter(&hdmi->i2c->adap);
else
+16 -1
View File
@@ -13,6 +13,7 @@
#define CONFIG_REG 0xc
#define CONFIG_CEC BIT(28)
#define CONFIG_AUD_UD BIT(23)
#define CONFIG_HDCP14 BIT(8)
#define CORE_TIMESTAMP_HHMM 0x14
#define CORE_TIMESTAMP_MMDD 0x18
#define CORE_TIMESTAMP_YYYY 0x1c
@@ -139,7 +140,7 @@
#define FRAME_COMPOSER_CONFIG8 0x860
#define FRAME_COMPOSER_CONFIG9 0x864
#define KEEPOUT_REKEY_CFG GENMASK(9, 8)
#define KEEPOUT_REKEY_ALWAYS 0x2
#define KEEPOUT_REKEY_ALWAYS (0x2 << 8)
#define FRAME_COMPOSER_CONTROL0 0x86c
/* Video Monitor Registers */
#define VIDEO_MONITOR_CONFIG0 0x880
@@ -155,9 +156,13 @@
#define HDCP2_BYPASS BIT(0)
#define HDCP2LOGIC_ESM_GPIO_IN 0x8e4
#define HDCP2LOGIC_ESM_GPIO_OUT 0x8e8
#define HDCP2_AUTHENTICATION_SUCCESS BIT(6)
/* HDCP14 Registers */
#define HDCP14_CONFIG0 0x900
#define HDCP14_OESS_ESSS_OVR_VALUE BIT(14)
#define HDCP14_OESS_ESSS_OVR_EN BIT(13)
#define HDCP14_CONFIG1 0x904
#define HDCP14_SHA1_MSG_CORRECT_P BIT(3)
#define HDCP14_CONFIG2 0x908
#define HDCP14_CONFIG3 0x90c
#define HDCP14_KEY_SEED 0x914
@@ -169,7 +174,10 @@
#define HDCP14_AN_H 0x92c
#define HDCP14_AN_L 0x930
#define HDCP14_STATUS0 0x934
#define HDCP14_RPT_DEVICE_COUNT 0xFE00
#define HDCP14_STATUS1 0x938
#define HDCP14_RCV_REPEATER BIT(6)
#define HDCP14_RCV_KSV_FIFO_READY BIT(5)
/* Scrambler Registers */
#define SCRAMB_CONFIG0 0x960
/* Video Configuration Registers */
@@ -792,6 +800,7 @@
#define AVP_1_INT_STATUS 0x3820
#define AVP_1_INT_MASK_N 0x3824
#define HDCP14_AUTH_CHG_MASK_N BIT(6)
#define HDCP14_KSV_LIST_DONE_MASK_N BIT(1)
#define AVP_1_INT_CLEAR 0x3828
#define AVP_1_INT_FORCE 0x382c
#define AVP_2_INT_STATUS 0x3830
@@ -802,6 +811,7 @@
#define AVP_3_INT_MASK_N 0x3844
#define AVP_3_INT_CLEAR 0x3848
#define AVP_3_INT_FORCE 0x384c
#define HDCP2_ESM_P0_GPIO_OUT_2_CHG_IRQ BIT(17)
#define AVP_4_INT_STATUS 0x3850
#define AVP_4_INT_MASK_N 0x3854
#define AVP_4_INT_CLEAR 0x3858
@@ -832,4 +842,9 @@
#define EARCRX_1_INT_CLEAR 0x4828
#define EARCRX_1_INT_FORCE 0x482c
#define HDMI_HDCP14_MEM_KSV0 0x4f08
#define HDMI_HDCP14_MEM_BSTATUS0 0x5958
#define HDMI_HDCP14_MEM_M0_1 0x5960
#define HDMI_HDCP14_MEM_M0_7 0x597c
#endif /* __DW_HDMI_QP_H__ */
@@ -17,6 +17,7 @@
#include <drm/drm_crtc_helper.h>
#include <drm/drm_dsc.h>
#include <drm/drm_edid.h>
#include <drm/drm_hdcp.h>
#include <drm/bridge/dw_hdmi.h>
#include <drm/drm_edid.h>
#include <drm/drm_of.h>
@@ -105,6 +106,8 @@
#define RK3588_HDMI1_LEVEL_INT BIT(24)
#define RK3588_HDMI1_INTR_CHANGE_CNT (0x7 << 21)
#define RK3588_GRF_VO1_CON1 0x0004
#define HDCP1_P1_GPIO_IN BIT(9)
#define RK3588_GRF_VO1_CON3 0x000c
#define RK3588_COLOR_FORMAT_MASK 0xf
#define RK3588_RGB 0
@@ -129,6 +132,8 @@
#define RK3588_HDMI0_GRANT_SW BIT(11)
#define RK3588_HDMI1_GRANT_SEL BIT(12)
#define RK3588_HDMI1_GRANT_SW BIT(13)
#define RK3588_GRF_VO1_CON4 0x0010
#define RK3588_HDMI_HDCP14_MEM_EN BIT(15)
#define RK3588_GRF_VO1_CON6 0x0018
#define RK3588_GRF_VO1_CON7 0x001c
@@ -216,6 +221,7 @@ struct rockchip_hdmi {
struct drm_property *output_type_capacity;
struct drm_property *allm_capacity;
struct drm_property *allm_enable;
struct drm_property *hdcp_state_property;
struct drm_property_blob *hdr_panel_blob_ptr;
struct drm_property_blob *next_hdr_data_ptr;
@@ -232,6 +238,7 @@ struct rockchip_hdmi {
u8 max_lanes;
u8 add_func;
u8 edid_colorimetry;
u8 hdcp_status;
struct rockchip_drm_dsc_cap dsc_cap;
struct next_hdr_sink_data next_hdr_data;
struct dw_hdmi_link_config link_cfg;
@@ -1889,6 +1896,26 @@ static void rk3588_set_color_format(struct rockchip_hdmi *hdmi, u64 bus_format,
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val);
}
static void rk3588_set_hdcp_status(void *data, u8 status)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
hdmi->hdcp_status = status;
}
static void rk3588_set_hdcp2_enable(void *data, bool enable)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
u32 val;
if (enable)
val = HIWORD_UPDATE(HDCP1_P1_GPIO_IN, HDCP1_P1_GPIO_IN);
else
val = HIWORD_UPDATE(0, HDCP1_P1_GPIO_IN);
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON1, val);
}
static void rk3588_set_grf_cfg(void *data)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
@@ -2562,6 +2589,18 @@ static void dw_hdmi_rockchip_set_ddc_io(void *data, bool enable)
}
}
static void dw_hdmi_rockchip_set_hdcp14_mem(void *data, bool enable)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
u32 val;
val = HIWORD_UPDATE(enable << 15, RK3588_HDMI_HDCP14_MEM_EN);
if (!hdmi->id)
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON4, val);
else
regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON7, val);
}
static const struct drm_prop_enum_list color_depth_enum_list[] = {
{ 0, "Automatic" }, /* Prefer highest color depth */
{ 8, "24bit" },
@@ -2608,6 +2647,7 @@ dw_hdmi_rockchip_attach_properties(struct drm_connector *connector,
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;
struct drm_property *prop;
struct rockchip_drm_private *private = connector->dev->dev_private;
int ret;
switch (color) {
case MEDIA_BUS_FMT_RGB101010_1X30:
@@ -2776,6 +2816,21 @@ dw_hdmi_rockchip_attach_properties(struct drm_connector *connector,
drm_object_attach_property(&connector->base,
connector->colorspace_property, 0);
drm_object_attach_property(&connector->base, private->connector_id_prop, hdmi->id);
ret = drm_connector_attach_content_protection_property(connector, true);
if (ret) {
dev_err(hdmi->dev, "failed to attach content protection: %d\n", ret);
return;
}
prop = drm_property_create_range(connector->dev, 0, RK_IF_PROP_ENCRYPTED,
RK_IF_HDCP_ENCRYPTED_NONE, RK_IF_HDCP_ENCRYPTED_LEVEL2);
if (!prop) {
dev_err(hdmi->dev, "create hdcp encrypted prop for hdmi%d failed\n", hdmi->id);
return;
}
hdmi->hdcp_state_property = prop;
drm_object_attach_property(&connector->base, prop, RK_IF_HDCP_ENCRYPTED_NONE);
}
static void
@@ -2909,6 +2964,7 @@ dw_hdmi_rockchip_set_property(struct drm_connector *connector,
hdmi->enable_allm = val;
if (allm_enable != hdmi->enable_allm)
dw_hdmi_qp_set_allm_enable(hdmi->hdmi_qp, hdmi->enable_allm);
} else if (property == hdmi->hdcp_state_property) {
return 0;
}
@@ -2985,6 +3041,14 @@ dw_hdmi_rockchip_get_property(struct drm_connector *connector,
} else if (property == hdmi->allm_enable) {
*val = hdmi->enable_allm;
return 0;
} else if (property == hdmi->hdcp_state_property) {
if (hdmi->hdcp_status & BIT(1))
*val = RK_IF_HDCP_ENCRYPTED_LEVEL2;
else if (hdmi->hdcp_status & BIT(0))
*val = RK_IF_HDCP_ENCRYPTED_LEVEL1;
else
*val = RK_IF_HDCP_ENCRYPTED_NONE;
return 0;
}
DRM_ERROR("Unknown property [PROP:%d:%s]\n",
@@ -3458,6 +3522,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
plat_data->get_colorimetry =
dw_hdmi_rockchip_get_colorimetry;
plat_data->get_link_cfg = dw_hdmi_rockchip_get_link_cfg;
plat_data->set_hdcp2_enable = rk3588_set_hdcp2_enable;
plat_data->set_hdcp_status = rk3588_set_hdcp_status;
plat_data->set_grf_cfg = rk3588_set_grf_cfg;
plat_data->get_grf_color_fmt = rk3588_get_grf_color_fmt;
plat_data->convert_to_split_mode = drm_mode_convert_to_split_mode;
@@ -3473,6 +3539,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
dw_hdmi_rockchip_set_prev_bus_format;
plat_data->set_ddc_io =
dw_hdmi_rockchip_set_ddc_io;
plat_data->set_hdcp14_mem =
dw_hdmi_rockchip_set_hdcp14_mem;
plat_data->property_ops = &dw_hdmi_rockchip_property_ops;
secondary = rockchip_hdmi_find_by_id(dev->driver, !hdmi->id);
+3
View File
@@ -244,6 +244,8 @@ struct dw_hdmi_plat_data {
int (*get_next_hdr_data)(void *data, struct edid *edid,
struct drm_connector *connector);
struct dw_hdmi_link_config *(*get_link_cfg)(void *data);
void (*set_hdcp_status)(void *data, u8 status);
void (*set_hdcp2_enable)(void *data, bool enable);
void (*set_grf_cfg)(void *data);
u64 (*get_grf_color_fmt)(void *data);
void (*convert_to_split_mode)(struct drm_display_mode *mode);
@@ -256,6 +258,7 @@ struct dw_hdmi_plat_data {
void (*set_prev_bus_format)(void *data, unsigned long bus_format);
int (*get_colorimetry)(void *data, struct edid *edid);
void (*set_ddc_io)(void *data, bool enable);
void (*set_hdcp14_mem)(void *data, bool enable);
/* Vendor Property support */
const struct dw_hdmi_property_ops *property_ops;