KernelNanopiR5S/drivers/media/platform/rockchip/hdmirx/rk_hdmirx_hdcp.c
2024-09-10 09:59:09 +02:00

448 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 Rockchip Electronics Co. Ltd.
*
* Author: Shunqing Chen <csq@rock-chips.com>
*/
#include <linux/delay.h>
#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_toio(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 = 0;
hdcp->status = HDMIRX_HDCP_DISABLED;
if (!hdcp->keys_is_load)
ret = hdcp_load_keys_cb(hdcp);
if (ret && !hdcp->test_key_load)
return -EINVAL;
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_CONNECTED);
return 0;
}
static int rk_hdmirx_hdcp1x_start(struct rk_hdmirx_hdcp *hdcp)
{
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->status == HDMIRX_HDCP_DISABLED)
return 0;
hdmirx_hdcp_update_bits(hdcp, GLOBAL_SWENABLE, HDCP_ENABLE, 0);
hdcp->status = HDMIRX_HDCP_DISABLED;
return 0;
}
static void rk_hdmirx_hdcp2_hpd_config(struct rk_hdmirx_hdcp *hdcp, bool en)
{
if (hdcp->tx_5v_power(hdcp->hdmirx))
hdcp->hpd_config(hdcp->hdmirx, en);
}
static int rk_hdmirx_hdcp2x_start(struct rk_hdmirx_hdcp *hdcp)
{
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG,
HDCP2_SWITCH_OVR_VALUE |
HDCP2_SWITCH_LCK |
HDCP2_SWITCH_OVR_EN,
HDCP2_SWITCH_LCK);
if (hdcp->tx_5v_power(hdcp->hdmirx))
hdmirx_hdcp_write(hdcp, HDCP2_ESM_P0_GPIO_IN, 0x2);
dev_dbg(hdcp->dev, "%s success\n", __func__);
return 0;
}
static int rk_hdmirx_hdcp2x_stop(struct rk_hdmirx_hdcp *hdcp)
{
hdmirx_hdcp_write(hdcp, HDCP2_ESM_P0_GPIO_IN, 0x0);
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG,
HDCP2_SWITCH_OVR_VALUE |
HDCP2_SWITCH_LCK |
HDCP2_SWITCH_OVR_EN,
HDCP2_SWITCH_OVR_EN);
return 0;
}
static void rk_hdmirx_hdcp2_connect_ctrl(struct rk_hdmirx_hdcp *hdcp, bool en)
{
if (hdcp->enable != HDCP_2X_ENABLE)
return;
if (en) {
hdmirx_hdcp_write(hdcp, HDCP2_ESM_P0_GPIO_IN, 0x2);
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG, HDCP2_CONNECTED,
HDCP2_CONNECTED);
} else {
hdmirx_hdcp_write(hdcp, HDCP2_ESM_P0_GPIO_IN, 0x0);
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG,
HDCP2_CONNECTED, 0);
}
}
static int rk_hdmirx_hdcp_start(struct rk_hdmirx_hdcp *hdcp)
{
if (hdcp->enable == HDCP_2X_ENABLE) {
rk_hdmirx_hdcp1x_start(hdcp);
rk_hdmirx_hdcp2x_start(hdcp);
return 0;
}
hdmirx_hdcp_update_bits(hdcp, HDCP2_CONFIG,
HDCP2_SWITCH_OVR_EN,
HDCP2_SWITCH_OVR_EN);
if (hdcp->enable == HDCP_1X_ENABLE)
rk_hdmirx_hdcp1x_start(hdcp);
return 0;
}
static int rk_hdmirx_hdcp_stop(struct rk_hdmirx_hdcp *hdcp)
{
if (!hdcp->enable)
return 0;
if (hdcp->enable == HDCP_2X_ENABLE)
rk_hdmirx_hdcp2x_stop(hdcp);
rk_hdmirx_hdcp1x_stop(hdcp);
dev_dbg(hdcp->dev, "hdcp stop\n");
return 0;
}
static ssize_t enable_show(struct device *device,
struct device_attribute *attr, char *buf)
{
u8 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)
{
int enable;
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
if (!hdcp)
return -EINVAL;
if (kstrtoint(buf, 10, &enable))
return -EINVAL;
if (enable > hdcp->hdcp_support)
return count;
if (hdcp->enable != enable) {
rk_hdmirx_hdcp2_hpd_config(hdcp, false);
if (enable == HDCP_2X_ENABLE) {
rk_hdmirx_hdcp1x_start(hdcp);
rk_hdmirx_hdcp2x_start(hdcp);
} else if (enable == HDCP_1X_ENABLE) {
if (hdcp->enable == HDCP_2X_ENABLE)
rk_hdmirx_hdcp2x_stop(hdcp);
rk_hdmirx_hdcp1x_start(hdcp);
} else {
rk_hdmirx_hdcp_stop(hdcp);
}
msleep(300);
rk_hdmirx_hdcp2_hpd_config(hdcp, true);
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;
int dectypt, n = 0;
if (!hdcp)
return 0;
if (!hdcp->enable)
return snprintf(buf, PAGE_SIZE, "HDCP Disable\n");
if (hdcp->enable == HDCP_2X_ENABLE) {
dectypt = hdmirx_hdcp_read(hdcp, HDCP2_STATUS) & BIT(0);
if (dectypt) {
val = hdmirx_hdcp_read(hdcp, HDCP2_ESM_P0_GPIO_OUT);
if (val & BIT(2))
n += snprintf(buf + n, PAGE_SIZE - n,
"HDCP2.3: Authenticated success\n");
else
n += snprintf(buf + n, PAGE_SIZE - n,
"HDCP2.3: Authenticated failed\n");
return n;
}
n += snprintf(buf, PAGE_SIZE, "HDCP2.3: No dectypted\n");
}
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_AUTH_START)
n += snprintf(buf + n, PAGE_SIZE, "HDCP1.4: Authenticated start\n");
else if (status == HDMIRX_HDCP_AUTH_SUCCESS)
n += snprintf(buf + n, PAGE_SIZE, "HDCP1.4: Authenticated success\n");
else if (status == HDMIRX_HDCP_AUTH_FAIL)
n += snprintf(buf + n, PAGE_SIZE, "HDCP1.4: Authenticated failed\n");
else
n += snprintf(buf + n, PAGE_SIZE, "HDCP1.4: Unknown status\n");
return n;
}
static DEVICE_ATTR_RO(status);
static ssize_t support_show(struct device *device,
struct device_attribute *attr, char *buf)
{
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
return snprintf(buf, PAGE_SIZE, "%d\n", hdcp->hdcp_support);
}
static DEVICE_ATTR_RO(support);
static ssize_t test_key1x_store(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct rk_hdmirx_hdcp *hdcp = g_hdmirx_hdcp;
u8 hdcp_vendor_data[VENDOR_DATA_SIZE + 1];
struct hdcp_key_data_t *key_data;
void __iomem *base;
char str[4] = {0};
int ret, len, i;
len = count / 2;
if (len < (HDCP_KEY_SIZE + HDCP_KEY_SEED_SIZE))
return -EINVAL;
if (len > VENDOR_DATA_SIZE)
len = VENDOR_DATA_SIZE;
for (i = 0; i < len; i++) {
long c = -1;
memcpy(str, &buf[i * 2], 2);
ret = kstrtol(str, 16, &c);
if (ret || c > 255 || c < 0)
return -EINVAL;
hdcp_vendor_data[i] = (u8)(c & 0xff);
}
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_toio(base, hdcp_vendor_data, len);
hdcp->test_key_load = true;
if (!hdcp->enable)
return count;
rk_hdmirx_hdcp2_hpd_config(hdcp, false);
rk_hdmirx_hdcp_start(hdcp);
msleep(300);
rk_hdmirx_hdcp2_hpd_config(hdcp, true);
return count;
}
static DEVICE_ATTR_WO(test_key1x);
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->tx_5v_power = hdcp_data->tx_5v_power;
hdcp->hpd_config = hdcp_data->hpd_config;
hdcp->enable = hdcp_data->enable;
hdcp->hdcp_support = hdcp_data->enable;
hdcp->dev = hdcp_data->dev;
g_hdmirx_hdcp = hdcp;
hdcp->mdev.minor = MISC_DYNAMIC_MINOR;
hdcp->mdev.name = "hdmirx_hdcp";
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;
}
ret = device_create_file(hdcp->mdev.this_device, &dev_attr_support);
if (ret) {
dev_err(hdcp->dev, "HDCP: Could not add sys file support\n");
goto error2;
}
ret = device_create_file(hdcp->mdev.this_device, &dev_attr_test_key1x);
if (ret) {
dev_err(hdcp->dev, "HDCP: Could not add sys file test_key1x\n");
goto error2;
}
hdcp->hdcp_start = rk_hdmirx_hdcp_start;
hdcp->hdcp_stop = rk_hdmirx_hdcp_stop;
hdcp->hdcp2_connect_ctrl = rk_hdmirx_hdcp2_connect_ctrl;
dev_info(hdcp->dev, "%s success\n", __func__);
return hdcp;
error2:
device_remove_file(hdcp->mdev.this_device, &dev_attr_status);
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);
device_remove_file(hdcp->mdev.this_device, &dev_attr_support);
device_remove_file(hdcp->mdev.this_device, &dev_attr_test_key1x);
misc_deregister(&hdcp->mdev);
}