diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index 4e788dbdcfbe..757f5526fd64 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -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 diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.c new file mode 100644 index 000000000000..7f55f7201ff8 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.c @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: + * Algea Cao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.h new file mode 100644 index 000000000000..48c3a4843ac0 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-hdcp.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: + * Algea Cao + */ +#ifndef DW_HDMI_QP_HDCP_H +#define DW_HDMI_QP_HDCP_H + +#include + +#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 diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index e234228adf88..5f0958fbd8cd 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -4,6 +4,7 @@ * Author: * Algea Cao */ +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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 @@ -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 diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index 225bfaa69701..e9b5e19a3be5 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -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__ */ diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 3a36cbecc256..c996343c35ad 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -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); diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 84020553cbf8..aca7ae836796 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -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;