From 4c9ea35ec996ebb05b77b184be91d01fda30d7be Mon Sep 17 00:00:00 2001 From: Chen Shunqing Date: Wed, 26 Jan 2022 11:26:02 +0800 Subject: [PATCH] media: rockchip: hdmirx: add hdcp1.4 support Signed-off-by: Chen Shunqing Change-Id: I8c6e055b35d8873a6b0d1703591513ada452a0f4 --- .../media/platform/rockchip/hdmirx/Makefile | 2 +- .../platform/rockchip/hdmirx/rk_hdmirx.c | 32 +- .../platform/rockchip/hdmirx/rk_hdmirx.h | 2 + .../platform/rockchip/hdmirx/rk_hdmirx_hdcp.c | 277 ++++++++++++++++++ .../platform/rockchip/hdmirx/rk_hdmirx_hdcp.h | 71 +++++ 5 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c create mode 100644 drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h diff --git a/drivers/media/platform/rockchip/hdmirx/Makefile b/drivers/media/platform/rockchip/hdmirx/Makefile index d989c89c2379..280fc1994c5b 100644 --- a/drivers/media/platform/rockchip/hdmirx/Makefile +++ b/drivers/media/platform/rockchip/hdmirx/Makefile @@ -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 diff --git a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.c b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.c index 051dea9c68a2..2ad511d94d3c 100644 --- a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.c +++ b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.c @@ -43,6 +43,7 @@ #include #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); diff --git a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.h b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.h index 5a387e7ab6af..ec7925dcc9e6 100644 --- a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.h +++ b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx.h @@ -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) diff --git a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c new file mode 100644 index 000000000000..eea58538fe7e --- /dev/null +++ b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * + * Author: Shunqing Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h new file mode 100644 index 000000000000..361c62f43bf8 --- /dev/null +++ b/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * + * Author: Shunqing Chen + */ + +#ifndef __RK_HDMIRX_HDCP_H__ +#define __RK_HDMIRX_HDCP_H__ + +#include + +#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__ */