mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
media: rockchip: hdmirx: add hdcp1.4 support
Signed-off-by: Chen Shunqing <csq@rock-chips.com> Change-Id: I8c6e055b35d8873a6b0d1703591513ada452a0f4
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
277
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c
Normal file
277
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c
Normal 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);
|
||||
}
|
||||
71
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h
Normal file
71
drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h
Normal 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__ */
|
||||
Reference in New Issue
Block a user