651 lines
15 KiB
C
651 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) Rockchip Electronics Co.Ltd
|
|
* Author:
|
|
* Algea Cao <algea.cao@rock-chips.com>
|
|
*/
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/hdmi.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/soc/rockchip/rk_vendor_storage.h>
|
|
#include <crypto/sha.h>
|
|
#include <drm/bridge/dw_hdmi.h>
|
|
|
|
#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");
|