blob: f7d907782498822bd517060edbe0e06441d323f9 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/spinlock_types.h>
#include <linux/extcon-provider.h>
#include <linux/amlogic/media/vout/hdmi_tx21/hdmi_info_global.h>
#include <linux/amlogic/media/vout/hdmi_tx21/hdmi_tx_module.h>
#include "hw/hdmi_tx_reg.h"
#include "hdmi_tx.h"
static int hdmi21_authenticated;
static struct hdcp_t *p_hdcp;
static int hdcp_verbose;
module_param(hdcp_verbose, int, 0644);
MODULE_PARM_DESC(hdcp_verbose, "for hdcp debug");
MODULE_PARM_DESC(hdmi21_authenticated, "\n hdmi21_authenticated\n");
module_param(hdmi21_authenticated, int, 0444);
static bool hdcp_schedule_work(struct hdcp_work *work, u32 delay_ms, u32 period_ms);
static bool hdcp_stop_work(struct hdcp_work *work);
static void hdcptx_update_failures(struct hdcp_t *p_hdcp, enum hdcp_fail_types_t types);
static bool hdcp1x_ds_ksv_fifo_ready(struct hdcp_t *p_hdcp, u8 int_reg[]);
static void hdcp2x_auth_stop(struct hdcp_t *p_hdcp);
static void hdcptx_send_csm_msg(struct hdcp_t *p_hdcp);
static void hdcptx_reset(struct hdcp_t *p_hdcp);
static void pr_hdcp_info(const char *fmt, ...)
{
va_list args;
int len;
char temp[128] = {0};
if (!hdcp_verbose)
return;
va_start(args, fmt);
len = vsnprintf(temp, sizeof(temp), fmt, args);
va_end(args);
if (len)
pr_info("%s", temp);
}
void hdcp_mode_set(unsigned int mode)
{
pr_hdcp_info("%s[%d] %d\n", __func__, __LINE__, mode);
if (mode == 0) {
hdcptx_reset(p_hdcp);
p_hdcp->hdcptx_enabled = 0;
return;
}
if (mode != 1 && mode != 2)
return;
p_hdcp->hdcptx_enabled = 1;
if (mode == 1)
p_hdcp->req_hdcp_ver = HDCP_VER_HDCP1X;
if (mode == 2)
p_hdcp->req_hdcp_ver = HDCP_VER_HDCP2X;
hdcp_schedule_work(&p_hdcp->timer_hdcp_start, 250, 0);
}
static bool hdcp1x_ds_ksv_fifo_ready(struct hdcp_t *p_hdcp, u8 int_reg[])
{
u8 bcaps_status;
hdcptx1_bcaps_get(&bcaps_status);
if ((int_reg[0] & 0x08) || (bcaps_status & 0x20))
return true;
else
return false;
}
static void update_hdcp_state(struct hdcp_t *p_hdcp, enum hdcp_stat_t state)
{
if (p_hdcp->hdcp_state != state || state == HDCP_STAT_NONE) {
p_hdcp->hdcp_state = state;
pr_hdcp_info("%s[%d] %d\n", __func__, __LINE__, state);
}
}
static void hdcp2x_reauth_start(struct hdcp_t *p_hdcp)
{
if (!p_hdcp->reauth_ignored)
hdcptx2_reauth_send();
}
static void hdcp_topology_update(struct hdcp_t *p_hdcp)
{
struct hdcp_topo_t *topo = &p_hdcp->hdcp_topology;
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
if (p_hdcp->ds_repeater) {
u8 topoval;
topoval = hdcptx2_topology_get();
topo->rp_depth = hdcptx2_rpt_depth_get();
topo->dev_count = hdcptx2_rpt_dev_cnt_get();
if (topoval & 0x08 || topo->dev_count > HDCP2X_MAX_DEV)
topo->max_devs_exceed = true;
else
topo->max_devs_exceed = false;
if (topoval & 0x04 || topo->rp_depth > HDCP2X_MAX_DEPTH)
topo->max_cas_exceed = true;
else
topo->max_cas_exceed = false;
topo->ds_hdcp2x_dev = (topoval & 0x02) ? true : false;
topo->ds_hdcp1x_dev = (topoval & 0x01) ? true : false;
if (topo->max_devs_exceed)
topo->dev_count = HDCP2X_MAX_DEV;
if (p_hdcp->hdcp_topology.max_cas_exceed)
topo->rp_depth = HDCP2X_MAX_DEPTH;
} else {
topo->rp_depth = 1;
topo->dev_count = 1;
topo->max_devs_exceed = false;
topo->max_cas_exceed = false;
topo->ds_hdcp2x_dev = false;
topo->ds_hdcp1x_dev = false;
}
} else if (p_hdcp->hdcp_type == HDCP_VER_HDCP1X) {
if (p_hdcp->ds_repeater) {
u8 bstatus[2];
u8 max_devices = HDCP1X_MAX_TX_DEV;
hdcptx1_bstatus_get(bstatus);
topo->rp_depth = (bstatus[1] & 0x07);
topo->dev_count = (bstatus[0] & 0x7F);
if (bstatus[0] & 0x80 || topo->dev_count > max_devices)
topo->max_devs_exceed = true;
else
topo->max_devs_exceed = false;
if (bstatus[1] & 0x08 || topo->rp_depth > HDCP1X_MAX_DEPTH)
topo->max_cas_exceed = true;
else
topo->max_cas_exceed = false;
topo->ds_hdcp2x_dev = false;
topo->ds_hdcp1x_dev = true;
if (topo->max_devs_exceed)
topo->dev_count = max_devices;
if (topo->max_cas_exceed)
topo->rp_depth = HDCP1X_MAX_DEPTH;
} else {
topo->rp_depth = 1;
topo->dev_count = 1;
topo->max_devs_exceed = false;
topo->max_cas_exceed = false;
topo->ds_hdcp2x_dev = false;
topo->ds_hdcp1x_dev = true;
}
}
}
static void hdcptx_encryption_update(struct hdcp_t *p_hdcp, bool en)
{
if (p_hdcp->ds_auth) {
p_hdcp->encryption_enabled = en;
if (p_hdcp->hdcp_type == HDCP_VER_HDCP1X)
hdcptx1_encryption_update(en);
else if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X)
hdcptx2_encryption_update(en);
}
}
static void hdcp_check_ds_csm_status(struct hdcp_t *p_hdcp)
{
pr_hdcp_info("%s[%d] ds_repeater %d hdcp_type %d csm_valid %d content_type %d\n",
__func__, __LINE__, p_hdcp->ds_repeater, p_hdcp->hdcp_type,
p_hdcp->csm_valid, p_hdcp->content_type);
if (p_hdcp->ds_repeater) {
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
if (p_hdcp->csm_valid)
hdcptx_send_csm_msg(p_hdcp);
} else {
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
}
} else {
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
} else {
if (p_hdcp->csm_valid) {
if (p_hdcp->content_type == HDCP_CONTENT_TYPE_1)
hdcptx_update_failures(p_hdcp, HDCP_FAIL_CONTENT_TYPE);
else if (p_hdcp->content_type == HDCP_CONTENT_TYPE_0)
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
else
hdcptx_update_failures(p_hdcp, HDCP_FAIL_CONTENT_TYPE);
} else {
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
}
}
}
}
static void hdcp_authenticated_handle(struct hdcp_t *p_hdcp)
{
pr_hdcp_info("hdcptx: part 1 done\n");
if (p_hdcp->hdcp_type == HDCP_VER_HDCP1X) {
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
p_hdcp->ds_auth = true;
pr_hdcp_info("hdcptx: 1x AuthDone\n");
p_hdcp->fail_type = HDCP_FAIL_NONE;
hdcptx_encryption_update(p_hdcp, true);
hdcp_topology_update(p_hdcp);
hdcp_check_ds_csm_status(p_hdcp);
} else if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
p_hdcp->ds_auth = true;
pr_hdcp_info("hdcptx: 2x AuthDone\n");
p_hdcp->fail_type = HDCP_FAIL_NONE;
hdcptx_encryption_update(p_hdcp, true);
p_hdcp->ds_repeater = false;
hdcp_topology_update(p_hdcp);
hdcp_check_ds_csm_status(p_hdcp);
}
}
static bool hdcptx_query_ds_repeater(struct hdcp_t *p_hdcp)
{
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X)
return hdcptx2_ds_rptr_capability();
else
return hdcptx1_ds_rptr_capability();
}
static bool is_bksv_valid(struct hdcp_t *p_hdcp)
{
u8 bksv[KSV_SIZE];
u8 i, count = 0;
hdcptx1_ds_bksv_read(bksv, KSV_SIZE);
for (i = 0; i < KSV_SIZE; i++) {
while (bksv[i] != 0) {
if (bksv[i] & 1)
count++;
bksv[i] >>= 1;
}
}
return count == 20;
}
static void hdcp1x_check_bksv_done(struct hdcp_t *p_hdcp)
{
u8 copp_data1;
copp_data1 = hdcptx1_ds_cap_status_get();
pr_hdcp_info("%s[%d] copp_data1 0x%02x\n", __func__, __LINE__,
copp_data1);
if (copp_data1 & 0x02) {
hdcp_stop_work(&p_hdcp->timer_bksv_poll_done);
if (is_bksv_valid(p_hdcp)) {
hdcptx1_protection_enable(true);
hdcptx1_intermed_ri_check_enable(1);
} else {
hdcptx_update_failures(p_hdcp, HDCP_FAIL_BKSV_RXID);
}
}
}
static void ksv_reset_fifo(struct hdcp_t *p_hdcp)
{
p_hdcp->p_ksv_next = p_hdcp->p_ksv_lists;
}
static bool is_topology_correct(struct hdcp_t *p_hdcp)
{
u8 max_depth = HDCP1X_MAX_DEPTH;
u8 max_device_count = HDCP1X_MAX_TX_DEV;
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
max_device_count = HDCP2X_MAX_DEV;
max_depth = HDCP2X_MAX_DEPTH;
}
if (p_hdcp->hdcp_topology.max_devs_exceed || p_hdcp->hdcp_topology.max_cas_exceed)
return false;
return true;
}
static void get_ds_rcvid_lists(struct hdcp_t *p_hdcp, u8 count)
{
hdcptx2_ds_rpt_rcvid_list_read(p_hdcp->p_ksv_next, count, KSV_SIZE);
p_hdcp->p_ksv_next += count * KSV_SIZE;
}
static void get_ds_rcv_id(struct hdcp_t *p_hdcp)
{
hdcptx2_ds_rcv_id_read(p_hdcp->p_ksv_next);
p_hdcp->p_ksv_next += KSV_SIZE;
}
static void bksv_get_ds_list(struct hdcp_t *p_hdcp)
{
hdcptx1_ds_bksv_read(p_hdcp->p_ksv_next, KSV_SIZE);
p_hdcp->p_ksv_next += KSV_SIZE;
}
static void assemble_ds_ksv_lists(struct hdcp_t *p_hdcp)
{
if (p_hdcp->hdcp_type == HDCP_VER_HDCP1X) {
u8 ds_bstatus[2];
u8 ds_count;
u8 ds_depth;
hdcptx1_bstatus_get(ds_bstatus);
ds_count = ds_bstatus[0] & 0x7F;
ds_depth = ds_bstatus[1] & 0x07;
hdcptx1_get_ds_ksvlists(&p_hdcp->p_ksv_next, ds_count);
bksv_get_ds_list(p_hdcp);
} else if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
u8 dev_cnt = hdcptx2_rpt_dev_cnt_get();
get_ds_rcvid_lists(p_hdcp, dev_cnt);
get_ds_rcv_id(p_hdcp);
}
}
static bool hdcp_process_repeater_fail(struct hdcp_t *p_hdcp)
{
if (p_hdcp->hdcp_type == HDCP_VER_HDCP1X) {
u8 ds_bstatus[2];
bksv_get_ds_list(p_hdcp);
hdcptx1_bstatus_get(ds_bstatus);
if ((ds_bstatus[0] & 0x80) || (ds_bstatus[1] & 0x08)) {
hdcptx_update_failures(p_hdcp, HDCP_FAIL_TOPOLOGY);
return false;
}
hdcptx_update_failures(p_hdcp, HDCP_FAIL_V);
} else if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
u8 hdcp_topology;
hdcp_topology = hdcptx2_topology_get();
if ((hdcp_topology & 0x04) || (hdcp_topology & 0x08)) {
hdcptx_update_failures(p_hdcp, HDCP_FAIL_TOPOLOGY);
return false;
}
hdcptx_update_failures(p_hdcp, HDCP_FAIL_READY_TO);
}
return true;
}
static void hdcp_rpt_ready_process(struct hdcp_t *p_hdcp, bool ksv_read_status)
{
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
if (!(p_hdcp->hdcp_state == HDCP_STAT_FAILED ||
p_hdcp->hdcp_state == HDCP_STAT_AUTH)) {
if (!(p_hdcp->hdcp_state == HDCP_STAT_SUCCESS ||
p_hdcp->hdcp_state == HDCP_STAT_CSM)) {
hdcptx_update_failures(p_hdcp, HDCP_FAIL_UNKNOWN);
return;
}
}
if (!ksv_read_status) {
hdcp_topology_update(p_hdcp);
ksv_reset_fifo(p_hdcp);
if (!hdcp_process_repeater_fail(p_hdcp))
return;
} else {
hdcp_topology_update(p_hdcp);
if (is_topology_correct(p_hdcp)) {
assemble_ds_ksv_lists(p_hdcp);
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
if (p_hdcp->csm_msg_sent) {
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
} else {
update_hdcp_state(p_hdcp, HDCP_STAT_CSM);
if (p_hdcp->csm_valid)
hdcptx_send_csm_msg(p_hdcp);
}
}
} else {
assemble_ds_ksv_lists(p_hdcp);
hdcptx_update_failures(p_hdcp, HDCP_FAIL_TOPOLOGY);
return;
}
}
p_hdcp->rpt_ready = true;
pr_hdcp_info("%s[%d] rpt_ready %d\n", __func__, __LINE__,
p_hdcp->rpt_ready);
}
static void hdcp1x_process_intr(struct hdcp_t *p_hdcp, u8 int_reg[])
{
u8 hdcp1xcoppst, hdcp1xauthintst;
hdcp1xauthintst = int_reg[0]; // 0x63d
hdcp1xcoppst = hdcptx1_copp_status_get(); // 0x629
pr_hdcp_info("%s[%d] hdcp1xauthintst 0x%x hdcp1xcoppst 0x%x\n",
__func__, __LINE__, hdcp1xauthintst, hdcp1xcoppst);
if (hdcp1xauthintst & BIT_TPI_INTR_ST0_BKSV_BCAPS_DONE) {
p_hdcp->update_topology = false;
p_hdcp->update_topo_state = false;
hdcp1x_check_bksv_done(p_hdcp);
}
if (hdcp1xauthintst & BIT_TPI_INTR_ST0_REAUTH_RI_MISMATCH) {
p_hdcp->ds_repeater = false;
switch (hdcp1xcoppst & (0x80 | 0x40)) {
case 0x00:
break;
case 0x40:
if (!hdcptx_query_ds_repeater(p_hdcp)) {
p_hdcp->ds_repeater = false;
/* R0 == R0' */
hdcp_authenticated_handle(p_hdcp);
} else {
p_hdcp->ds_repeater = true;
p_hdcp->ds_auth = true;
p_hdcp->update_topology = true;
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
hdcp_schedule_work(&p_hdcp->timer_hdcp_rpt_auth,
HDCP_DS_KSVLIST_RETRY_TIMER, 0);
}
break;
case(0x80 | 0x40):
hdcptx_encryption_update(p_hdcp, true);
/* g_prot secure -- downstream is repeater */
p_hdcp->ds_repeater = true;
pr_hdcp_info("%s[%d] ds_repeater %d rpt_ready %d\n",
__func__, __LINE__,
p_hdcp->ds_repeater, p_hdcp->rpt_ready);
if (p_hdcp->rpt_ready) {
p_hdcp->update_topo_state = false;
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
} else {
p_hdcp->update_topo_state = true;
}
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
break;
default:
break;
}
switch (hdcptx1_ksv_v_get() & 0xc0) {
case 0xc0:
pr_hdcp_info("hdcptx1: the rx auth is pass and rx repeter auth is begin\n");
break;
case 0x80:
pr_hdcp_info("hdcptx1: the rx all auth is pass\n");
break;
case 0x40:
pr_hdcp_info("hdcptx1: the rx auth is error\n");
break;
case 0x00:
default:
pr_hdcp_info("hdcptx1: the rx auth start again or ddc_gpu_tpi_granted\n");
break;
}
}
if (hdcp1xauthintst & BIT_TPI_INTR_ST0_REESTABLISH) {
switch (hdcp1xcoppst & 0x30) {
case 0x00:
case 0x30:
pr_hdcp_info("hdcptx1: link normal or rsvd\n");
break;
case 0x10:
case 0x20:
pr_hdcp_info("hdcptx1: link lost or reneg\n");
hdcptx_update_failures(p_hdcp, (hdcp1xcoppst & 0x08));
break;
default:
break;
}
}
if (hdcp1x_ds_ksv_fifo_ready(p_hdcp, int_reg)) {
if (p_hdcp->update_topology) {
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
hdcp_rpt_ready_process(p_hdcp, true);
p_hdcp->update_topology = false;
p_hdcp->update_topo_state = true;
pr_hdcp_info("%s[%d] update_topo_state %d\n", __func__,
__LINE__, p_hdcp->update_topo_state);
if (p_hdcp->update_topo_state) {
p_hdcp->update_topo_state = false;
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
}
}
}
}
void hdcp1x_intr_handler(struct intr_t *intr)
{
u8 hdcp14_intreg[1];
pr_hdcp_info("%s[%d]\n", __func__, __LINE__);
hdcp14_intreg[0] = intr->st_data;
hdcp1x_process_intr(p_hdcp, hdcp14_intreg);
}
const char *msg1_info[] = {
"ro_rpt_rcvid_changed",
"ro_rpt_smng_changed",
"ro_ake_sent_rcvd",
"ro_ske_sent_rcvd",
"ro_rpt_rcvid_xfer_done",
"ro_rpt_smng_xfer_done",
"ro_cert_sent_rcvd",
"ro_gp3",
};
const char *msg2_info[] = {
"ro_km_sent_rcvd",
"ro_ekhkm_sent_rcvd",
"ro_h_sent_rcvd",
"ro_pair_sent_rcvd",
"ro_lc_sent_rcvd",
"ro_l_sent_rcvd",
"ro_vack_sent_rcvd",
"ro_mack_sent_rcvd",
};
static void hdcp2x_process_intr(u8 int_reg[])
{
int i;
u8 cp2tx_intr0_st = int_reg[0]; // 0x803 auth status
u8 cp2tx_intr1_st = int_reg[1]; // 0x804 msg status
u8 cp2tx_intr2_st = int_reg[2]; // 0x805
u8 cp2tx_intr3_st = int_reg[3]; // 0x806
pr_hdcp_info("%s[%d] 0x%x 0x%x 0x%x 0x%x\n", __func__, __LINE__,
int_reg[0], int_reg[1], int_reg[2], int_reg[3]);
if (cp2tx_intr0_st & BIT_CP2TX_INTR0_AUTH_DONE) {
if (hdcptx2_auth_status()) {
if (!hdcptx_query_ds_repeater(p_hdcp)) {
hdcp_authenticated_handle(p_hdcp);
} else {
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
pr_hdcp_info("hdcptx2: auth done\n");
}
}
}
if (cp2tx_intr0_st & BIT_CP2TX_INTR0_AUTH_FAIL) {
hdcp2x_auth_stop(p_hdcp);
hdcptx_update_failures(p_hdcp, p_hdcp->ds_repeater);
}
if (cp2tx_intr0_st & BIT_CP2TX_INTR0_RPT_READY_CHANGE) {
p_hdcp->ds_repeater = true;
pr_hdcp_info("hdcptx2: repeater ready\n");
}
if (cp2tx_intr0_st & BIT_CP2TX_INTR0_REAUTH_REQ) {
pr_hdcp_info("hdcptx2: reauth req from ds device\n");
hdcp2x_reauth_start(p_hdcp);
}
for (i = 0; i < 8; i++) {
if (cp2tx_intr1_st & (1 << i))
pr_hdcp_info("%s", msg1_info[i]);
}
for (i = 0; i < 8; i++) {
if (cp2tx_intr2_st & (1 << i))
pr_hdcp_info("%s", msg2_info[i]);
}
if (cp2tx_intr1_st & BIT_CP2TX_INTR1_RPT_RCVID_CHANGED) {
pr_hdcp_info("hdcptx2: rcv_id changed");
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
hdcp_rpt_ready_process(p_hdcp, true);
}
if (cp2tx_intr1_st & BIT(2))
p_hdcp->reauth_ignored = true;
if (cp2tx_intr1_st & BIT(5))
update_hdcp_state(p_hdcp, HDCP_STAT_SUCCESS);
if (cp2tx_intr1_st & BIT(6))
p_hdcp->reauth_ignored = false;
if (cp2tx_intr1_st & BIT(3)) {
p_hdcp->reauth_ignored = false;
if (hdcptx_query_ds_repeater(p_hdcp)) {
/*
* down stream is a hdcp2.2 repeater
*/
p_hdcp->ds_repeater = true;
p_hdcp->ds_auth = true;
hdcptx_encryption_update(p_hdcp, true);
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
hdcp_schedule_work(&p_hdcp->timer_hdcp_rpt_auth,
HDCP_RCVIDLIST_CHECK_TIMER, 0);
hdcp_check_ds_csm_status(p_hdcp);
} else {
p_hdcp->ds_repeater = false;
}
}
if (cp2tx_intr3_st & BIT(4))
;
}
void hdcp2x_intr_handler(struct intr_t *intr)
{
u8 hdcp2_intreg[4];
intr_status_save_clr_cp2txs(hdcp2_intreg);
hdcp2x_process_intr(hdcp2_intreg);
}
static void hdcp1x_auth_start(struct hdcp_t *p_hdcp)
{
pr_hdcp_info("%s[%d]\n", __func__, __LINE__);
p_hdcp->hdcp_type = HDCP_VER_HDCP1X;
update_hdcp_state(p_hdcp, HDCP_STAT_AUTH);
hdcptx1_encryption_update(false);
hdcptx1_auth_start();
hdcp_schedule_work(&p_hdcp->timer_bksv_poll_done, HDCP_BSKV_CHECK_TIMER, 0);
}
static void hdcp2x_auth_start(struct hdcp_t *p_hdcp)
{
u8 content_type;
p_hdcp->hdcp_type = HDCP_VER_HDCP2X;
if (p_hdcp->content_type == HDCP_CONTENT_TYPE_0)
content_type = 0;
else if (p_hdcp->content_type == HDCP_CONTENT_TYPE_1)
content_type = 1;
else
content_type = 0xFF;
pr_hdcp_info("%s[%d] content_type %d %d\n", __func__, __LINE__,
p_hdcp->content_type, content_type);
hdcptx2_src_auth_start(content_type);
hdcp2x_reauth_start(p_hdcp);
hdcp_schedule_work(&p_hdcp->timer_ddc_check_nak, 100, 200);
update_hdcp_state(p_hdcp, HDCP_STAT_AUTH);
}
static enum hdcp_ver_t hdcp_check_ds_hdcp2ver(struct hdcp_t *p_hdcp)
{
enum hdcp_ver_t hdcp_type = HDCP_VER_NONE;
enum ddc_err_t ddc_err = DDC_ERR_NONE;
u8 cap_val = 0;
ddc_err = hdmitx_ddcm_read(0, DDC_HDCP_DEVICE_ADDR, REG_DDC_HDCP_VERSION, &cap_val, 1);
if (ddc_err == DDC_ERR_NONE) {
if (cap_val == 0x04)
hdcp_type = HDCP_VER_HDCP2X;
else
hdcp_type = HDCP_VER_HDCP1X;
} else {
hdcptx_update_failures(p_hdcp, HDCP_FAIL_DDC_NACK);
}
return hdcp_type;
}
static void hdcp1x_auth_stop(struct hdcp_t *p_hdcp)
{
hdcptx1_auth_stop();
hdcptx1_protection_enable(false);
ddc_toggle_sw_tpi();
}
static bool ddc_bus_wait_free(void)
{
u8 tmo1 = 10;
u8 tmo2 = 2;
while (tmo2--) {
while (tmo1--) {
if (!hdmi_ddc_busy_check())
return true;
usleep_range(1000, 2000);
}
hdmi_ddc_error_reset();
usleep_range(1000, 2000);
}
return false;
}
static bool ddc_check_busy(struct hdcp_t *p_hdcp)
{
enum ddc_err_t error = DDC_ERR_NONE;
u8 time_out_ms = 7;
usleep_range(2000, 3000);
do {
if (!ddc_bus_wait_free()) {
error = DDC_ERR_BUSY;
time_out_ms--;
if (!time_out_ms)
return false;
usleep_range(2000, 3000);
} else {
error = DDC_ERR_NONE;
}
} while (error != DDC_ERR_NONE);
return true;
}
static void hdcp2x_auth_stop(struct hdcp_t *p_hdcp)
{
hdcp_stop_work(&p_hdcp->timer_ddc_check_nak);
if (ddc_check_busy(p_hdcp))
hdcptx2_auth_stop();
}
static void hdcp_reset_hw(struct hdcp_t *p_hdcp)
{
hdcptx_init_reg();
hdcp1x_auth_stop(p_hdcp);
hdcp2x_auth_stop(p_hdcp);
}
static void hdcptx_send_csm_msg(struct hdcp_t *p_hdcp)
{
if (p_hdcp->ds_repeater && p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
p_hdcp->csm_msg_sent = true;
hdcptx2_csm_send(&p_hdcp->csm_message);
p_hdcp->csm_message.seq_num_m++;
if (p_hdcp->csm_message.seq_num_m > 0xFFFFFFUL)
p_hdcp->csm_message.seq_num_m = 0;
memcpy(&p_hdcp->prev_csm_message, &p_hdcp->csm_message, sizeof(struct hdcp_csm_t));
}
}
static void hdcp2x_validate_csm_msg(struct hdcp_t *p_hdcp)
{
u8 content_type = (u8)(p_hdcp->csm_message.streamid_type & 0x00FF);
if (content_type == 0x00)
p_hdcp->content_type = HDCP_CONTENT_TYPE_0;
else if (content_type == 0x01)
p_hdcp->content_type = HDCP_CONTENT_TYPE_1;
else
p_hdcp->content_type = HDCP_CONTENT_TYPE_NONE;
if (p_hdcp->hdcp_type == HDCP_VER_HDCP2X) {
if (!p_hdcp->rpt_ready)
return;
if (p_hdcp->ds_repeater)
hdcptx_send_csm_msg(p_hdcp);
}
}
static void hdcp_update_csm(struct hdcp_t *p_hdcp)
{
hdcp2x_validate_csm_msg(p_hdcp);
}
static void hdcptx_reset(struct hdcp_t *p_hdcp)
{
p_hdcp->ds_repeater = false; /*!< attached is a repeater or not */
p_hdcp->update_topology = false;
p_hdcp->update_topo_state = false;
p_hdcp->rpt_ready = false;
p_hdcp->reauth_ignored = false;
p_hdcp->csm_message.seq_num_m = 0;
p_hdcp->hdcp_type = HDCP_VER_NONE;
p_hdcp->hdcp_state = HDCP_STAT_NONE;
p_hdcp->csm_valid = true;
p_hdcp->csm_msg_sent = false;
hdcp_stop_work(&p_hdcp->timer_hdcp_start);
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
hdcp_stop_work(&p_hdcp->timer_hdcp_auth_fail_retry);
hdcp_stop_work(&p_hdcp->timer_bksv_poll_done);
hdcp_stop_work(&p_hdcp->timer_ddc_check_nak);
hdcp_stop_work(&p_hdcp->timer_update_csm);
ksv_reset_fifo(p_hdcp);
p_hdcp->ds_auth = false;
hdcp_reset_hw(p_hdcp);
hdcptx2_encryption_update(false);
}
static bool hdcp_schedule_work(struct hdcp_work *work, u32 delay_ms, u32 period_ms)
{
pr_hdcp_info("hdcptx: schedule %s: delay %d ms period %d ms\n",
work->name, delay_ms, period_ms);
delay_ms = (delay_ms + 3) / 4;
period_ms = (period_ms + 3) / 4;
work->delay_ms = 0;
work->period_ms = period_ms;
if (delay_ms == 0 && period_ms == 0) {
cancel_delayed_work(&work->dwork);
return 1;
}
if (delay_ms)
return queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, delay_ms);
else
return queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, period_ms);
}
static bool hdcp_stop_work(struct hdcp_work *work)
{
cancel_delayed_work(&work->dwork);
pr_hdcp_info("hdcptx: stop %s\n", work->name);
return 0;
}
static void hdcptx_auth_start(struct hdcp_t *p_hdcp)
{
enum hdcp_ver_t hdcp_mode;
hdcp_mode = p_hdcp->req_hdcp_ver;
if (hdcp_mode != HDCP_VER_NONE) {
hdcp_mode = p_hdcp->req_hdcp_ver;
if (p_hdcp->hdcptx_enabled) {
ksv_reset_fifo(p_hdcp);
hdcp_enable_intrs(1);
hdcp_schedule_work(&p_hdcp->timer_hdcp_rcv_auth,
HDCP_STAGE1_RETRY_TIMER, 0);
if (hdcp_mode == HDCP_VER_HDCP1X)
hdcp1x_auth_start(p_hdcp);
if (hdcp_mode == HDCP_VER_HDCP2X)
hdcp2x_auth_start(p_hdcp);
hdcp_schedule_work(&p_hdcp->timer_ddc_check_nak, 100, 200);
}
}
}
const static char *fail_string[] = {
"none",
"ddc_nack",
"bksv_rxid",
"auth_fail",
"ready_to",
"v",
"topology",
"ri",
"reauth_req",
"content_type",
"auth_time_out",
"hash",
"unknown",
};
static void hdcptx_update_failures(struct hdcp_t *p_hdcp, enum hdcp_fail_types_t types)
{
pr_hdcp_info("%s[%d] types: %s\n", __func__, __LINE__,
fail_string[types]);
hdcp_stop_work(&p_hdcp->timer_hdcp_rcv_auth);
hdcp_stop_work(&p_hdcp->timer_hdcp_rpt_auth);
if (p_hdcp->hdcptx_enabled) {
if (types == HDCP_FAIL_CONTENT_TYPE) {
p_hdcp->fail_type = types;
update_hdcp_state(p_hdcp, HDCP_STAT_FAILED);
} else {
p_hdcp->reauth_ignored = false;
p_hdcp->fail_type = types;
update_hdcp_state(p_hdcp, HDCP_STAT_FAILED);
ksv_reset_fifo(p_hdcp);
hdcp_schedule_work(&p_hdcp->timer_hdcp_auth_fail_retry,
HDCP_FAILED_RETRY_TIMER, 0);
}
}
}
static void hdcp_check_ds_auth_whandler(struct work_struct *w)
{
struct hdcp_work *work = &p_hdcp->timer_hdcp_rcv_auth;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
hdcptx_update_failures(p_hdcp, HDCP_FAIL_AUTH_TIME_OUT);
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void hdcp_check_bksv_done_whandler(struct work_struct *w)
{
u8 copp_data1;
struct hdcp_work *work = &p_hdcp->timer_bksv_poll_done;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
copp_data1 = hdcptx1_ds_cap_status_get();
if (copp_data1 & 0x02) {
hdcp_stop_work(work);
if (is_bksv_valid(p_hdcp))
hdcptx1_protection_enable(true);
else
hdcptx_update_failures(p_hdcp, HDCP_FAIL_BKSV_RXID);
}
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void hdcp_check_update_whandler(struct work_struct *w)
{
struct hdcp_work *work = &p_hdcp->timer_hdcp_start;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
hdcptx_reset(p_hdcp);
if (p_hdcp->hdcptx_enabled) {
p_hdcp->hdcp_cap_ds = hdcp_check_ds_hdcp2ver(p_hdcp);
if (p_hdcp->hdcp_cap_ds != HDCP_VER_NONE) {
update_hdcp_state(p_hdcp, HDCP_STAT_AUTH);
hdcptx_auth_start(p_hdcp);
}
} else {
update_hdcp_state(p_hdcp, HDCP_STAT_NONE);
p_hdcp->hdcp_cap_ds = HDCP_VER_NONE;
p_hdcp->fail_type = HDCP_FAIL_NONE;
}
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void hdcp_ddc_check_nack_whandler(struct work_struct *w)
{
static int cnt;
struct hdcp_work *work = &p_hdcp->timer_ddc_check_nak;
if (cnt % 128 == 0) {
cnt++;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
}
if (hdmi_ddc_status_check()) {
p_hdcp->hdcp_cap_ds = HDCP_VER_NONE;
hdcp2x_auth_stop(p_hdcp);
hdcptx_update_failures(p_hdcp, HDCP_FAIL_DDC_NACK);
}
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void hdcp_check_auth_failretry_whandler(struct work_struct *w)
{
struct hdcp_work *work = &p_hdcp->timer_hdcp_auth_fail_retry;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
hdcptx_reset(p_hdcp);
hdcptx_auth_start(p_hdcp);
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void hdcp_update_csm_whandler(struct work_struct *w)
{
struct hdcp_work *work = &p_hdcp->timer_update_csm;
pr_hdcp_info("%s[%d] period %d ms\n", __func__, __LINE__,
work->period_ms);
hdcp_update_csm(p_hdcp);
if (work->period_ms)
queue_delayed_work(p_hdcp->hdcp_wq, &work->dwork, work->period_ms);
}
static void init_hdcp_works(struct hdcp_work *work,
void (*workfunc)(struct work_struct *), const char *name)
{
INIT_DELAYED_WORK(&work->dwork, workfunc);
work->name = name;
}
static struct hdcp_t hdcp_hdcp;
int hdmitx21_hdcp_init(void)
{
struct hdmitx_dev *hdev = get_hdmitx21_device();
pr_hdcp_info("%s\n", __func__);
hdev->am_hdcp = &hdcp_hdcp;
p_hdcp = &hdcp_hdcp;
p_hdcp->hdcp_state = HDCP_STAT_NONE;
p_hdcp->hdcp_type = HDCP_VER_NONE;
p_hdcp->hdcp_cap_ds = HDCP_VER_NONE;
p_hdcp->ds_auth = false;
p_hdcp->ds_repeater = false;
p_hdcp->fail_type = HDCP_FAIL_NONE;
p_hdcp->req_hdcp_ver = HDCP_VER_NONE;
p_hdcp->reauth_ignored = false;
p_hdcp->encryption_enabled = false;
p_hdcp->content_type = HDCP_CONTENT_TYPE_0;
p_hdcp->p_ksv_lists =
kmalloc((HDCP1X_MAX_DEPTH + 1) * sizeof(struct hdcp_ksv_t), GFP_KERNEL);
p_hdcp->hdcp_wq = alloc_workqueue(DEVICE_NAME, WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0);
init_hdcp_works(&p_hdcp->timer_hdcp_rcv_auth, hdcp_check_ds_auth_whandler, "hdcp_rcv_auth");
init_hdcp_works(&p_hdcp->timer_hdcp_rpt_auth, hdcp_check_ds_auth_whandler, "hdcp_rpt_auth");
init_hdcp_works(&p_hdcp->timer_bksv_poll_done,
hdcp_check_bksv_done_whandler, "bksv_poll_done");
init_hdcp_works(&p_hdcp->timer_hdcp_start, hdcp_check_update_whandler, "hdcp_start");
init_hdcp_works(&p_hdcp->timer_ddc_check_nak,
hdcp_ddc_check_nack_whandler, "ddc_check_nak");
init_hdcp_works(&p_hdcp->timer_hdcp_auth_fail_retry,
hdcp_check_auth_failretry_whandler, "hdcp_auth_fail_retry");
init_hdcp_works(&p_hdcp->timer_update_csm, hdcp_update_csm_whandler, "update_csm");
return 0;
}
void __exit hdmitx21_hdcp_exit(void)
{
hdcptx_reset(p_hdcp);
}