media: rockchip: hdmirx: add hdcp1.4 support

Signed-off-by: Chen Shunqing <csq@rock-chips.com>
Change-Id: I8c6e055b35d8873a6b0d1703591513ada452a0f4
This commit is contained in:
Chen Shunqing
2022-01-26 11:26:02 +08:00
committed by Tao Huang
parent d9eadb0377
commit 4c9ea35ec9
5 changed files with 382 additions and 2 deletions

View File

@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
rockchip-hdmirx-objs := rk_hdmirx.o rk_hdmirx_cec.o
rockchip-hdmirx-objs := rk_hdmirx.o rk_hdmirx_cec.o rk_hdmirx_hdcp.o
obj-$(CONFIG_VIDEO_ROCKCHIP_HDMIRX) += rockchip-hdmirx.o

View File

@@ -43,6 +43,7 @@
#include <sound/hdmi-codec.h>
#include "rk_hdmirx.h"
#include "rk_hdmirx_cec.h"
#include "rk_hdmirx_hdcp.h"
static struct class *hdmirx_class;
static int debug;
@@ -190,6 +191,7 @@ struct rk_hdmirx_dev {
struct clk_bulk_data *clks;
struct regmap *grf;
struct regmap *vo1_grf;
struct rk_hdmirx_hdcp *hdcp;
void __iomem *regs;
int audio_present;
int hdmi_irq;
@@ -205,6 +207,7 @@ struct rk_hdmirx_dev {
bool power_on;
bool initialized;
bool freq_qos_add;
bool hdcp1x_enable;
u32 num_clks;
u32 edid_blocks_written;
u32 hpd_trigger_level;
@@ -913,6 +916,21 @@ static int hdmirx_enum_dv_timings(struct file *file, void *_fh,
return v4l2_enum_dv_timings_cap(timings, &hdmirx_timings_cap, NULL, NULL);
}
static void hdmirx_register_hdcp(struct device *dev,
struct rk_hdmirx_dev *hdmirx_dev,
bool hdcp1x_enable)
{
struct rk_hdmirx_hdcp hdmirx_hdcp = {
.hdmirx = hdmirx_dev,
.write = hdmirx_writel,
.read = hdmirx_readl,
.enable = hdcp1x_enable,
.dev = hdmirx_dev->dev,
};
hdmirx_dev->hdcp = rk_hdmirx_hdcp_register(&hdmirx_hdcp);
}
static void hdmirx_scdc_init(struct rk_hdmirx_dev *hdmirx_dev)
{
hdmirx_update_bits(hdmirx_dev, I2C_SLAVE_CONFIG1,
@@ -1280,7 +1298,10 @@ static void hdmirx_dma_config(struct rk_hdmirx_dev *hdmirx_dev)
static void hdmirx_submodule_init(struct rk_hdmirx_dev *hdmirx_dev)
{
/* Note: if not config HDCP2_CONFIG, there will be some errors; */
hdmirx_writel(hdmirx_dev, HDCP2_CONFIG, 0x2);
hdmirx_update_bits(hdmirx_dev, HDCP2_CONFIG,
HDCP2_SWITCH_OVR_VALUE |
HDCP2_SWITCH_OVR_EN,
HDCP2_SWITCH_OVR_EN);
hdmirx_scdc_init(hdmirx_dev);
hdmirx_controller_init(hdmirx_dev);
}
@@ -2281,6 +2302,8 @@ static void hdmirx_delayed_work_hotplug(struct work_struct *work)
hdmirx_dma_config(hdmirx_dev);
hdmirx_interrupts_setup(hdmirx_dev, true);
hdmirx_audio_handle_plugged_change(hdmirx_dev, 1);
if (hdmirx_dev->hdcp && hdmirx_dev->hdcp->hdcp_start)
hdmirx_dev->hdcp->hdcp_start(hdmirx_dev->hdcp);
} else {
hdmirx_audio_handle_plugged_change(hdmirx_dev, 0);
hdmirx_update_bits(hdmirx_dev, SCDC_CONFIG, POWERPROVIDED, 0);
@@ -2733,6 +2756,9 @@ static int hdmirx_parse_dt(struct rk_hdmirx_dev *hdmirx_dev)
dev_warn(dev, "failed to get hpd-trigger-level, set high as default\n");
}
if (of_property_read_bool(np, "hdcp1x-enable"))
hdmirx_dev->hdcp1x_enable = true;
ret = of_reserved_mem_device_init(dev);
if (ret)
dev_warn(dev, "No reserved memory for HDMIRX, use default CMA\n");
@@ -3537,6 +3563,7 @@ static int hdmirx_probe(struct platform_device *pdev)
cec_data.irq = irq;
cec_data.edid = edid_init_data;
hdmirx_dev->cec = rk_hdmirx_cec_register(&cec_data);
hdmirx_register_hdcp(dev, hdmirx_dev, hdmirx_dev->hdcp1x_enable);
hdmirx_register_debugfs(hdmirx_dev->dev, hdmirx_dev);
@@ -3586,6 +3613,9 @@ static int hdmirx_remove(struct platform_device *pdev)
if (hdmirx_dev->cec_notifier)
cec_notifier_conn_unregister(hdmirx_dev->cec_notifier);
if (hdmirx_dev->hdcp)
rk_hdmirx_hdcp_unregister(hdmirx_dev->hdcp);
video_unregister_device(&hdmirx_dev->stream.vdev);
v4l2_ctrl_handler_free(&hdmirx_dev->hdl);
v4l2_device_unregister(&hdmirx_dev->v4l2_dev);

View File

@@ -154,7 +154,9 @@
#define HDCP14_KEY_SEED 0x02a0
#define KEY_DECRYPT_SEED_QST_MASK GENMASK(15, 0)
#define KEY_DECRYPT_SEED_QST(x) UPDATE(x, 15, 0)
#define HDCP14_STATUS 0x2b8
#define HDCP2_CONFIG 0x02f0
#define HDCP2_CONNECTED BIT(12)
#define HDCP2_SWITCH_OVR_VALUE BIT(2)
#define HDCP2_SWITCH_OVR_EN BIT(1)

View File

@@ -0,0 +1,277 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
*
* Author: Shunqing Chen <csq@rock-chips.com>
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rockchip/rockchip_sip.h>
#include <linux/sched.h>
#include <linux/soc/rockchip/rk_vendor_storage.h>
#include <linux/slab.h>
#include "rk_hdmirx.h"
#include "rk_hdmirx_hdcp.h"
enum rk_hdmirx_hdcp_state {
HDMIRX_HDCP_DISABLED,
HDMIRX_HDCP_AUTH_START,
HDMIRX_HDCP_AUTH_SUCCESS,
HDMIRX_HDCP_AUTH_FAIL,
};
static struct rk_hdmirx_hdcp *g_hdmirx_hdcp;
static void hdmirx_hdcp_write(struct rk_hdmirx_hdcp *hdcp, int reg, u32 val)
{
hdcp->write(hdcp->hdmirx, reg, val);
}
static u32 hdmirx_hdcp_read(struct rk_hdmirx_hdcp *hdcp, int reg)
{
return hdcp->read(hdcp->hdmirx, reg);
}
static void hdmirx_hdcp_update_bits(struct rk_hdmirx_hdcp *hdcp, int reg,
u32 mask, u32 data)
{
u32 val = hdmirx_hdcp_read(hdcp, reg) & ~mask;
val |= (data & mask);
hdmirx_hdcp_write(hdcp, reg, val);
}
static int hdcp_load_keys_cb(struct rk_hdmirx_hdcp *hdcp)
{
int size;
u8 hdcp_vendor_data[VENDOR_DATA_SIZE + 1];
struct hdcp_key_data_t *key_data;
void __iomem *base;
size = rk_vendor_read(HDMIRX_HDCP1X_ID, hdcp_vendor_data,
VENDOR_DATA_SIZE);
if (size < (HDCP_KEY_SIZE + HDCP_KEY_SEED_SIZE)) {
dev_dbg(hdcp->dev, "HDCP: read size %d\n", size);
return -EINVAL;
}
key_data = (struct hdcp_key_data_t *)hdcp_vendor_data;
if ((key_data->signature != HDCP_SIG_MAGIC)
|| !(key_data->flags & HDCP_FLG_AES))
hdcp->aes_encrypt = false;
else
hdcp->aes_encrypt = true;
base = sip_hdcp_request_share_memory(HDMI_RX);
if (!base)
return -ENOMEM;
memcpy(base, hdcp_vendor_data, size);
hdcp->keys_is_load = true;
return 0;
}
static int rk_hdmirx_hdcp_load_key(struct rk_hdmirx_hdcp *hdcp)
{
int ret;
if (!hdcp->keys_is_load) {
ret = hdcp_load_keys_cb(hdcp);
if (ret)
return ret;
}
hdcp->status = HDMIRX_HDCP_AUTH_START;
if (hdcp->aes_encrypt)
sip_hdcp_config(HDCP_FUNC_KEY_LOAD, HDMI_RX, 0);
else
sip_hdcp_config(HDCP_FUNC_KEY_LOAD, HDMI_RX, 1);
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG,
HDCP2_CONNECTED |
HDCP2_SWITCH_OVR_VALUE |
HDCP2_SWITCH_OVR_EN,
HDCP2_CONNECTED |
HDCP2_SWITCH_OVR_EN);
return 0;
}
static int rk_hdmirx_hdcp1x_start(struct rk_hdmirx_hdcp *hdcp)
{
if (!hdcp->enable)
return -EPERM;
rk_hdmirx_hdcp_load_key(hdcp);
dev_dbg(hdcp->dev, "%s success\n", __func__);
return 0;
}
static int rk_hdmirx_hdcp1x_stop(struct rk_hdmirx_hdcp *hdcp)
{
if (!hdcp->enable)
return -EPERM;
hdmirx_hdcp_update_bits(hdcp, GLOBAL_SWENABLE, HDCP_ENABLE, 0);
hdcp->status = HDMIRX_HDCP_DISABLED;
return 0;
}
static int rk_hdmirx_hdcp_start(struct rk_hdmirx_hdcp *hdcp)
{
if (hdcp->hdcp2 && hdcp->hdcp2->enable) {
hdcp->hdcp2->start();
return 0;
}
return rk_hdmirx_hdcp1x_start(hdcp);
}
static int rk_hdmirx_hdcp_stop(struct rk_hdmirx_hdcp *hdcp)
{
if (hdcp->hdcp2 && hdcp->hdcp2->stop) {
hdcp->hdcp2->stop();
dev_dbg(hdcp->dev, "hdcp2 stop\n");
}
return rk_hdmirx_hdcp1x_stop(hdcp);
}
static ssize_t enable_show(struct device *device,
struct device_attribute *attr, char *buf)
{
bool enable = 0;
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
if (hdcp)
enable = hdcp->enable;
return snprintf(buf, PAGE_SIZE, "%d\n", enable);
}
static ssize_t enable_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
bool enable;
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
if (!hdcp)
return -EINVAL;
if (kstrtobool(buf, &enable))
return -EINVAL;
if (hdcp->enable != enable) {
if (enable) {
hdcp->enable = enable;
rk_hdmirx_hdcp1x_start(hdcp);
} else {
rk_hdmirx_hdcp1x_stop(hdcp);
hdcp->enable = enable;
}
}
return count;
}
static DEVICE_ATTR_RW(enable);
static ssize_t status_show(struct device *device,
struct device_attribute *attr, char *buf)
{
int status = HDMIRX_HDCP_DISABLED;
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
u32 val;
if (hdcp)
status = hdcp->status;
if (status == HDMIRX_HDCP_AUTH_START) {
val = hdmirx_hdcp_read(hdcp, HDCP14_STATUS);
if ((val & 0x3) == 0)
status = HDMIRX_HDCP_DISABLED;
else if ((val & 0x3) == 0x1)
status = HDMIRX_HDCP_AUTH_START;
else if (val & BIT(8))
status = HDMIRX_HDCP_AUTH_SUCCESS;
else
status = HDMIRX_HDCP_AUTH_FAIL;
}
if (status == HDMIRX_HDCP_DISABLED)
return snprintf(buf, PAGE_SIZE, "hdcp disable\n");
else if (status == HDMIRX_HDCP_AUTH_START)
return snprintf(buf, PAGE_SIZE, "hdcp_auth_start\n");
else if (status == HDMIRX_HDCP_AUTH_SUCCESS)
return snprintf(buf, PAGE_SIZE, "hdcp_auth_success\n");
else if (status == HDMIRX_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);
struct rk_hdmirx_hdcp *rk_hdmirx_hdcp_register(struct rk_hdmirx_hdcp *hdcp_data)
{
int ret = 0;
struct rk_hdmirx_hdcp *hdcp;
if (!hdcp_data)
return NULL;
hdcp = devm_kzalloc(hdcp_data->dev, sizeof(*hdcp), GFP_KERNEL);
if (!hdcp)
return NULL;
hdcp->hdmirx = hdcp_data->hdmirx;
hdcp->write = hdcp_data->write;
hdcp->read = hdcp_data->read;
hdcp->enable = hdcp_data->enable;
hdcp->dev = hdcp_data->dev;
g_hdmirx_hdcp = hdcp;
hdcp->mdev.minor = MISC_DYNAMIC_MINOR;
hdcp->mdev.name = "hdmirx_hdcp1x";
hdcp->mdev.mode = 0666;
if (misc_register(&hdcp->mdev)) {
dev_err(hdcp->dev, "HDCP: Could not add character driver\n");
return NULL;
}
ret = device_create_file(hdcp->mdev.this_device, &dev_attr_enable);
if (ret) {
dev_err(hdcp->dev, "HDCP: Could not add sys file enable\n");
goto error0;
}
ret = device_create_file(hdcp->mdev.this_device, &dev_attr_status);
if (ret) {
dev_err(hdcp->dev, "HDCP: Could not add sys file status\n");
goto error1;
}
hdcp->hdcp_start = rk_hdmirx_hdcp_start;
hdcp->hdcp_stop = rk_hdmirx_hdcp_stop;
dev_info(hdcp->dev, "%s success\n", __func__);
return hdcp;
error1:
device_remove_file(hdcp->mdev.this_device, &dev_attr_enable);
error0:
misc_deregister(&hdcp->mdev);
return NULL;
}
void rk_hdmirx_hdcp_unregister(struct rk_hdmirx_hdcp *hdcp)
{
device_remove_file(hdcp->mdev.this_device, &dev_attr_enable);
device_remove_file(hdcp->mdev.this_device, &dev_attr_status);
misc_deregister(&hdcp->mdev);
}

View File

@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
*
* Author: Shunqing Chen <csq@rock-chips.com>
*/
#ifndef __RK_HDMIRX_HDCP_H__
#define __RK_HDMIRX_HDCP_H__
#include <linux/miscdevice.h>
#define HDCP_KEY_SIZE 308
#define HDCP_KEY_SEED_SIZE 2
#define KSV_LEN 5
#define HEADER 10
#define SHAMAX 20
#define PRIVATE_KEY_SIZE 280
#define KEY_SHA_SIZE 20
#define KEY_DATA_SIZE 314
#define VENDOR_DATA_SIZE (KEY_DATA_SIZE + 16)
#define HDMIRX_HDCP1X_ID 13
#define HDCP_SIG_MAGIC 0x4B534541 /* "AESK" */
#define HDCP_FLG_AES 1
struct hdcp_key_data_t {
unsigned int signature;
unsigned int length;
unsigned int crc;
unsigned int flags;
unsigned char data[0];
};
struct rk_hdmirx_hdcp2 {
int enable;
void (*start)(void);
void (*stop)(void);
struct device *dev;
int wait_hdcp2_reset;
int hot_plug;
struct miscdevice mdev;
int auth_success;
};
struct rk_hdmirx_hdcp {
bool enable;
int hdcp2_enable;
int status;
struct rk_hdmirx_hdcp2 *hdcp2;
struct miscdevice mdev;
bool keys_is_load;
bool aes_encrypt;
struct device *dev;
struct rk_hdmirx_dev *hdmirx;
void (*write)(struct rk_hdmirx_dev *hdmirx, int reg, u32 val);
u32 (*read)(struct rk_hdmirx_dev *hdmirx, int reg);
int (*hdcp_start)(struct rk_hdmirx_hdcp *hdcp);
int (*hdcp_stop)(struct rk_hdmirx_hdcp *hdcp);
};
struct rk_hdmirx_hdcp *rk_hdmirx_hdcp_register(struct rk_hdmirx_hdcp *hdcp);
void rk_hdmirx_hdcp_unregister(struct rk_hdmirx_hdcp *hdcp);
#endif /* __RK_HDMIRX_HDCP_H__ */