1167 lines
27 KiB
C
1167 lines
27 KiB
C
/* Copyright (c) 2015 The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/hdcp_qseecom.h>
|
|
#include "mdss_hdmi_hdcp.h"
|
|
#include "video/msm_hdmi_hdcp_mgr.h"
|
|
#include "mdss_hdmi_util.h"
|
|
|
|
/*
|
|
* Defined addresses and offsets of standard HDCP 2.2 sink registers
|
|
* for DDC, as defined in HDCP 2.2 spec section 2.14 table 2.7
|
|
*/
|
|
#define HDCP_SINK_DDC_SLAVE_ADDR 0x74 /* Sink DDC slave address */
|
|
#define HDCP_SINK_DDC_HDCP2_VERSION 0x50 /* Does sink support HDCP2.2 */
|
|
#define HDCP_SINK_DDC_HDCP2_WRITE_MESSAGE 0x60 /* HDCP Tx writes here */
|
|
#define HDCP_SINK_DDC_HDCP2_RXSTATUS 0x70 /* RxStatus, 2 bytes */
|
|
#define HDCP_SINK_DDC_HDCP2_READ_MESSAGE 0x80 /* HDCP Tx reads here */
|
|
|
|
#define HDCP2P2_LINK_CHECK_TIME_MS 900 /* link check within 1 sec */
|
|
|
|
#define HDCP2P2_DEFAULT_TIMEOUT 500
|
|
|
|
/*
|
|
* HDCP 2.2 encryption requires the data encryption block that is present in
|
|
* HDMI controller version 4.0.0 and above
|
|
*/
|
|
#define MIN_HDMI_TX_MAJOR_VERSION 4
|
|
|
|
enum hdmi_hdcp2p2_sink_status {
|
|
SINK_DISCONNECTED,
|
|
SINK_CONNECTED
|
|
};
|
|
|
|
struct hdmi_hdcp2p2_ctrl {
|
|
atomic_t auth_state;
|
|
bool tethered;
|
|
enum hdmi_hdcp2p2_sink_status sink_status; /* Is sink connected */
|
|
struct hdmi_hdcp_init_data init_data; /* Feature data from HDMI drv */
|
|
struct mutex mutex; /* mutex to protect access to ctrl */
|
|
struct mutex msg_lock; /* mutex to protect access to msg buffer */
|
|
struct mutex wakeup_mutex; /* mutex to protect access to wakeup call*/
|
|
struct hdmi_hdcp_ops *ops;
|
|
void *lib_ctx; /* Handle to HDCP 2.2 Trustzone library */
|
|
struct hdcp_txmtr_ops *lib; /* Ops for driver to call into TZ */
|
|
|
|
enum hdmi_hdcp_wakeup_cmd wakeup_cmd;
|
|
char *send_msg_buf;
|
|
uint32_t send_msg_len;
|
|
uint32_t timeout;
|
|
uint32_t timeout_left;
|
|
|
|
struct task_struct *thread;
|
|
struct kthread_worker worker;
|
|
struct kthread_work status;
|
|
struct kthread_work auth;
|
|
struct kthread_work send_msg;
|
|
struct kthread_work recv_msg;
|
|
struct kthread_work link;
|
|
};
|
|
|
|
static int hdmi_hdcp2p2_auth(struct hdmi_hdcp2p2_ctrl *ctrl);
|
|
static void hdmi_hdcp2p2_send_msg(struct hdmi_hdcp2p2_ctrl *ctrl);
|
|
static void hdmi_hdcp2p2_recv_msg(struct hdmi_hdcp2p2_ctrl *ctrl);
|
|
static void hdmi_hdcp2p2_auth_status(struct hdmi_hdcp2p2_ctrl *ctrl);
|
|
|
|
static inline bool hdmi_hdcp2p2_is_valid_state(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_AUTHENTICATE)
|
|
return true;
|
|
|
|
if (atomic_read(&ctrl->auth_state) != HDCP_STATE_INACTIVE)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_copy_buf(struct hdmi_hdcp2p2_ctrl *ctrl,
|
|
struct hdmi_hdcp_wakeup_data *data)
|
|
{
|
|
mutex_lock(&ctrl->msg_lock);
|
|
|
|
if (!data->send_msg_len) {
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
return 0;
|
|
}
|
|
|
|
ctrl->send_msg_len = data->send_msg_len;
|
|
|
|
kzfree(ctrl->send_msg_buf);
|
|
|
|
ctrl->send_msg_buf = kzalloc(data->send_msg_len, GFP_KERNEL);
|
|
|
|
if (!ctrl->send_msg_buf) {
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
memcpy(ctrl->send_msg_buf, data->send_msg_buf, ctrl->send_msg_len);
|
|
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_wakeup(struct hdmi_hdcp_wakeup_data *data)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl;
|
|
|
|
if (!data) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctrl = data->context;
|
|
if (!ctrl) {
|
|
pr_err("invalid ctrl\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->wakeup_mutex);
|
|
|
|
pr_debug("cmd: %s, timeout %dms, tethered %d\n",
|
|
hdmi_hdcp_cmd_to_str(data->cmd),
|
|
data->timeout, ctrl->tethered);
|
|
|
|
ctrl->wakeup_cmd = data->cmd;
|
|
|
|
if (data->timeout)
|
|
ctrl->timeout = data->timeout;
|
|
else
|
|
ctrl->timeout = HDCP2P2_DEFAULT_TIMEOUT;
|
|
|
|
if (!hdmi_hdcp2p2_is_valid_state(ctrl)) {
|
|
pr_err("invalid state\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (hdmi_hdcp2p2_copy_buf(ctrl, data))
|
|
goto exit;
|
|
|
|
if (ctrl->tethered)
|
|
goto exit;
|
|
|
|
switch (ctrl->wakeup_cmd) {
|
|
case HDMI_HDCP_WKUP_CMD_SEND_MESSAGE:
|
|
queue_kthread_work(&ctrl->worker, &ctrl->send_msg);
|
|
break;
|
|
case HDMI_HDCP_WKUP_CMD_RECV_MESSAGE:
|
|
queue_kthread_work(&ctrl->worker, &ctrl->recv_msg);
|
|
break;
|
|
case HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS:
|
|
case HDMI_HDCP_WKUP_CMD_STATUS_FAILED:
|
|
queue_kthread_work(&ctrl->worker, &ctrl->status);
|
|
break;
|
|
case HDMI_HDCP_WKUP_CMD_AUTHENTICATE:
|
|
queue_kthread_work(&ctrl->worker, &ctrl->auth);
|
|
break;
|
|
default:
|
|
pr_err("invalid wakeup command %d\n", ctrl->wakeup_cmd);
|
|
}
|
|
exit:
|
|
mutex_unlock(&ctrl->wakeup_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static inline int hdmi_hdcp2p2_wakeup_lib(struct hdmi_hdcp2p2_ctrl *ctrl,
|
|
struct hdcp_lib_wakeup_data *data)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (ctrl)
|
|
ctrl->wakeup_cmd = HDMI_HDCP_WKUP_CMD_INVALID;
|
|
|
|
if (ctrl && ctrl->lib && ctrl->lib->wakeup &&
|
|
data && (data->cmd != HDCP_LIB_WKUP_CMD_INVALID)) {
|
|
rc = ctrl->lib->wakeup(data);
|
|
if (rc)
|
|
pr_err("error sending %s to lib\n",
|
|
hdcp_lib_cmd_to_str(data->cmd));
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_run(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
while (1) {
|
|
switch (ctrl->wakeup_cmd) {
|
|
case HDMI_HDCP_WKUP_CMD_SEND_MESSAGE:
|
|
hdmi_hdcp2p2_send_msg(ctrl);
|
|
break;
|
|
|
|
case HDMI_HDCP_WKUP_CMD_RECV_MESSAGE:
|
|
hdmi_hdcp2p2_recv_msg(ctrl);
|
|
break;
|
|
|
|
case HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS:
|
|
case HDMI_HDCP_WKUP_CMD_STATUS_FAILED:
|
|
hdmi_hdcp2p2_auth_status(ctrl);
|
|
default:
|
|
goto exit;
|
|
}
|
|
}
|
|
exit:
|
|
ctrl->wakeup_cmd = HDMI_HDCP_WKUP_CMD_INVALID;
|
|
}
|
|
|
|
int hdmi_hdcp2p2_authenticate_tethered(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = hdmi_hdcp2p2_auth(ctrl);
|
|
if (rc) {
|
|
pr_err("auth failed %d\n", rc);
|
|
goto exit;
|
|
}
|
|
|
|
hdmi_hdcp2p2_run(ctrl);
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_reset(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
ctrl->sink_status = SINK_DISCONNECTED;
|
|
atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_off(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = (struct hdmi_hdcp2p2_ctrl *)input;
|
|
struct hdmi_hdcp_wakeup_data cdata = {HDMI_HDCP_WKUP_CMD_AUTHENTICATE};
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
hdmi_hdcp2p2_reset(ctrl);
|
|
|
|
flush_kthread_worker(&ctrl->worker);
|
|
|
|
hdmi_hdcp2p2_ddc_disable(ctrl->init_data.ddc_ctrl);
|
|
|
|
if (ctrl->tethered) {
|
|
hdmi_hdcp2p2_auth(ctrl);
|
|
} else {
|
|
cdata.context = input;
|
|
hdmi_hdcp2p2_wakeup(&cdata);
|
|
}
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_authenticate(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = input;
|
|
struct hdmi_hdcp_wakeup_data cdata = {HDMI_HDCP_WKUP_CMD_AUTHENTICATE};
|
|
u32 regval;
|
|
int rc = 0;
|
|
|
|
/* Enable authentication success interrupt */
|
|
regval = DSS_REG_R(ctrl->init_data.core_io, HDMI_HDCP_INT_CTRL2);
|
|
regval |= BIT(1) | BIT(2);
|
|
|
|
DSS_REG_W(ctrl->init_data.core_io, HDMI_HDCP_INT_CTRL2, regval);
|
|
|
|
flush_kthread_worker(&ctrl->worker);
|
|
|
|
ctrl->sink_status = SINK_CONNECTED;
|
|
atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATING);
|
|
|
|
/* make sure ddc is idle before starting hdcp 2.2 authentication */
|
|
hdmi_scrambler_ddc_disable(ctrl->init_data.ddc_ctrl);
|
|
hdmi_hdcp2p2_ddc_disable(ctrl->init_data.ddc_ctrl);
|
|
|
|
if (ctrl->tethered) {
|
|
hdmi_hdcp2p2_authenticate_tethered(ctrl);
|
|
} else {
|
|
cdata.context = input;
|
|
hdmi_hdcp2p2_wakeup(&cdata);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_reauthenticate(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = (struct hdmi_hdcp2p2_ctrl *)input;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdmi_hdcp2p2_reset((struct hdmi_hdcp2p2_ctrl *)input);
|
|
|
|
return hdmi_hdcp2p2_authenticate(input);
|
|
}
|
|
static ssize_t hdmi_hdcp2p2_sysfs_rda_sink_status(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
ssize_t ret;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->mutex);
|
|
if (ctrl->sink_status == SINK_CONNECTED)
|
|
ret = scnprintf(buf, PAGE_SIZE, "Connected\n");
|
|
else
|
|
ret = scnprintf(buf, PAGE_SIZE, "Disconnected\n");
|
|
mutex_unlock(&ctrl->mutex);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_rda_tethered(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t ret;
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->mutex);
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", ctrl->tethered);
|
|
mutex_unlock(&ctrl->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_wta_tethered(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
int rc, tethered;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->mutex);
|
|
rc = kstrtoint(buf, 10, &tethered);
|
|
if (rc) {
|
|
pr_err("kstrtoint failed. rc=%d\n", rc);
|
|
goto exit;
|
|
}
|
|
|
|
ctrl->tethered = !!tethered;
|
|
|
|
if (ctrl->lib && ctrl->lib->update_exec_type && ctrl->lib_ctx)
|
|
ctrl->lib->update_exec_type(ctrl->lib_ctx, ctrl->tethered);
|
|
exit:
|
|
mutex_unlock(&ctrl->mutex);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_rda_trigger(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
ssize_t ret;
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->mutex);
|
|
if (ctrl->sink_status == SINK_CONNECTED)
|
|
ret = scnprintf(buf, PAGE_SIZE, "Triggered\n");
|
|
else
|
|
ret = scnprintf(buf, PAGE_SIZE, "Not triggered\n");
|
|
mutex_unlock(&ctrl->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_wta_trigger(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&ctrl->mutex);
|
|
ctrl->sink_status = SINK_CONNECTED;
|
|
mutex_unlock(&ctrl->mutex);
|
|
|
|
pr_debug("HDCP 2.2 authentication triggered\n");
|
|
hdmi_hdcp2p2_authenticate(ctrl);
|
|
return count;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_wta_min_level_change(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
struct hdcp_lib_wakeup_data cdata = {
|
|
HDCP_LIB_WKUP_CMD_QUERY_STREAM_TYPE};
|
|
bool enc_notify = true;
|
|
enum hdmi_hdcp_state enc_lvl;
|
|
int min_enc_lvl;
|
|
int rc;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = kstrtoint(buf, 10, &min_enc_lvl);
|
|
if (rc) {
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
goto exit;
|
|
}
|
|
|
|
switch (min_enc_lvl) {
|
|
case 0:
|
|
enc_lvl = HDCP_STATE_AUTH_ENC_NONE;
|
|
break;
|
|
case 1:
|
|
enc_lvl = HDCP_STATE_AUTH_ENC_1X;
|
|
break;
|
|
case 2:
|
|
enc_lvl = HDCP_STATE_AUTH_ENC_2P2;
|
|
break;
|
|
default:
|
|
enc_notify = false;
|
|
}
|
|
|
|
pr_debug("enc level changed %d\n", min_enc_lvl);
|
|
|
|
cdata.context = ctrl->lib_ctx;
|
|
hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
|
|
if (ctrl->tethered)
|
|
hdmi_hdcp2p2_run(ctrl);
|
|
|
|
if (enc_notify && ctrl->init_data.notify_status)
|
|
ctrl->init_data.notify_status(ctrl->init_data.cb_data, enc_lvl);
|
|
|
|
rc = count;
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_auth_failed(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL);
|
|
|
|
hdmi_hdcp2p2_ddc_disable(ctrl->init_data.ddc_ctrl);
|
|
|
|
/* notify hdmi tx about HDCP failure */
|
|
ctrl->init_data.notify_status(ctrl->init_data.cb_data,
|
|
HDCP_STATE_AUTH_FAIL);
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_ddc_read_message(struct hdmi_hdcp2p2_ctrl *ctrl,
|
|
u8 *buf, int size, u32 timeout)
|
|
{
|
|
struct hdmi_tx_ddc_data ddc_data;
|
|
int rc;
|
|
|
|
if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
|
|
pr_err("hdcp is off\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&ddc_data, 0, sizeof(ddc_data));
|
|
ddc_data.dev_addr = HDCP_SINK_DDC_SLAVE_ADDR;
|
|
ddc_data.offset = HDCP_SINK_DDC_HDCP2_READ_MESSAGE;
|
|
ddc_data.data_buf = buf;
|
|
ddc_data.data_len = size;
|
|
ddc_data.request_len = size;
|
|
ddc_data.retry = 0;
|
|
ddc_data.hard_timeout = timeout;
|
|
ddc_data.what = "HDCP2ReadMessage";
|
|
|
|
ctrl->init_data.ddc_ctrl->ddc_data = ddc_data;
|
|
|
|
pr_debug("read msg timeout %dms\n", timeout);
|
|
|
|
rc = hdmi_ddc_read(ctrl->init_data.ddc_ctrl);
|
|
if (rc)
|
|
pr_err("Cannot read HDCP message register\n");
|
|
|
|
ctrl->timeout_left = ctrl->init_data.ddc_ctrl->ddc_data.timeout_left;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int hdmi_hdcp2p2_ddc_write_message(struct hdmi_hdcp2p2_ctrl *ctrl,
|
|
u8 *buf, size_t size)
|
|
{
|
|
struct hdmi_tx_ddc_data ddc_data;
|
|
int rc;
|
|
|
|
memset(&ddc_data, 0, sizeof(ddc_data));
|
|
ddc_data.dev_addr = HDCP_SINK_DDC_SLAVE_ADDR;
|
|
ddc_data.offset = HDCP_SINK_DDC_HDCP2_WRITE_MESSAGE;
|
|
ddc_data.data_buf = buf;
|
|
ddc_data.data_len = size;
|
|
ddc_data.hard_timeout = ctrl->timeout;
|
|
ddc_data.what = "HDCP2WriteMessage";
|
|
|
|
ctrl->init_data.ddc_ctrl->ddc_data = ddc_data;
|
|
|
|
rc = hdmi_ddc_write(ctrl->init_data.ddc_ctrl);
|
|
if (rc)
|
|
pr_err("Cannot write HDCP message register\n");
|
|
|
|
ctrl->timeout_left = ctrl->init_data.ddc_ctrl->ddc_data.timeout_left;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_read_version(struct hdmi_hdcp2p2_ctrl *ctrl,
|
|
u8 *hdcp2version)
|
|
{
|
|
struct hdmi_tx_ddc_data ddc_data;
|
|
int rc;
|
|
|
|
memset(&ddc_data, 0, sizeof(ddc_data));
|
|
ddc_data.dev_addr = HDCP_SINK_DDC_SLAVE_ADDR;
|
|
ddc_data.offset = HDCP_SINK_DDC_HDCP2_VERSION;
|
|
ddc_data.data_buf = hdcp2version;
|
|
ddc_data.data_len = 1;
|
|
ddc_data.request_len = 1;
|
|
ddc_data.retry = 1;
|
|
ddc_data.what = "HDCP2Version";
|
|
|
|
ctrl->init_data.ddc_ctrl->ddc_data = ddc_data;
|
|
|
|
rc = hdmi_ddc_read(ctrl->init_data.ddc_ctrl);
|
|
if (rc) {
|
|
pr_err("Cannot read HDCP2Version register");
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("Read HDCP2Version as %u\n", *hdcp2version);
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t hdmi_hdcp2p2_sysfs_rda_hdcp2_version(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
u8 hdcp2version;
|
|
ssize_t ret;
|
|
struct hdmi_hdcp2p2_ctrl *ctrl =
|
|
hdmi_get_featuredata_from_sysfs_dev(dev, HDMI_TX_FEAT_HDCP2P2);
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
ret = hdmi_hdcp2p2_read_version(ctrl, &hdcp2version);
|
|
if (ret < 0)
|
|
return ret;
|
|
return snprintf(buf, PAGE_SIZE, "%u\n", hdcp2version);
|
|
}
|
|
|
|
|
|
static DEVICE_ATTR(trigger, S_IRUGO | S_IWUSR, hdmi_hdcp2p2_sysfs_rda_trigger,
|
|
hdmi_hdcp2p2_sysfs_wta_trigger);
|
|
static DEVICE_ATTR(min_level_change, S_IWUSR, NULL,
|
|
hdmi_hdcp2p2_sysfs_wta_min_level_change);
|
|
static DEVICE_ATTR(sink_status, S_IRUGO, hdmi_hdcp2p2_sysfs_rda_sink_status,
|
|
NULL);
|
|
static DEVICE_ATTR(hdcp2_version, S_IRUGO, hdmi_hdcp2p2_sysfs_rda_hdcp2_version,
|
|
NULL);
|
|
static DEVICE_ATTR(tethered, S_IRUGO | S_IWUSR, hdmi_hdcp2p2_sysfs_rda_tethered,
|
|
hdmi_hdcp2p2_sysfs_wta_tethered);
|
|
|
|
static struct attribute *hdmi_hdcp2p2_fs_attrs[] = {
|
|
&dev_attr_trigger.attr,
|
|
&dev_attr_min_level_change.attr,
|
|
&dev_attr_sink_status.attr,
|
|
&dev_attr_hdcp2_version.attr,
|
|
&dev_attr_tethered.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group hdmi_hdcp2p2_fs_attr_group = {
|
|
.name = "hdcp2p2",
|
|
.attrs = hdmi_hdcp2p2_fs_attrs,
|
|
};
|
|
|
|
static bool hdmi_hdcp2p2_feature_supported(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = input;
|
|
struct hdcp_txmtr_ops *lib = NULL;
|
|
bool supported = false;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
goto end;
|
|
}
|
|
|
|
lib = ctrl->lib;
|
|
if (!lib) {
|
|
pr_err("invalid lib ops data\n");
|
|
goto end;
|
|
}
|
|
|
|
if (lib->feature_supported)
|
|
supported = lib->feature_supported(
|
|
ctrl->lib_ctx);
|
|
end:
|
|
return supported;
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_send_msg(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
int rc = 0;
|
|
struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
|
|
uint32_t msglen;
|
|
char *msg = NULL;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
cdata.context = ctrl->lib_ctx;
|
|
|
|
if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
|
|
pr_err("hdcp is off\n");
|
|
goto exit;
|
|
}
|
|
|
|
mutex_lock(&ctrl->msg_lock);
|
|
msglen = ctrl->send_msg_len;
|
|
|
|
if (!msglen) {
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
msg = kzalloc(msglen, GFP_KERNEL);
|
|
if (!msg) {
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
rc = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
memcpy(msg, ctrl->send_msg_buf, msglen);
|
|
mutex_unlock(&ctrl->msg_lock);
|
|
|
|
/* Forward the message to the sink */
|
|
rc = hdmi_hdcp2p2_ddc_write_message(ctrl, msg, (size_t)msglen);
|
|
if (rc) {
|
|
pr_err("Error sending msg to sink %d\n", rc);
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_SEND_FAILED;
|
|
} else {
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_SEND_SUCCESS;
|
|
cdata.timeout = ctrl->timeout_left;
|
|
}
|
|
exit:
|
|
kfree(msg);
|
|
|
|
hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_send_msg_work(struct kthread_work *work)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
|
|
struct hdmi_hdcp2p2_ctrl, send_msg);
|
|
|
|
hdmi_hdcp2p2_send_msg(ctrl);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_recv_msg(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
int rc, timeout_hsync;
|
|
char *recvd_msg_buf = NULL;
|
|
struct hdmi_tx_hdcp2p2_ddc_data *ddc_data;
|
|
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
|
|
struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
cdata.context = ctrl->lib_ctx;
|
|
|
|
ddc_ctrl = ctrl->init_data.ddc_ctrl;
|
|
if (!ddc_ctrl) {
|
|
pr_err("invalid ddc ctrl\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
|
|
pr_err("hdcp is off\n");
|
|
goto exit;
|
|
}
|
|
hdmi_ddc_config(ddc_ctrl);
|
|
|
|
ddc_data = &ddc_ctrl->hdcp2p2_ddc_data;
|
|
|
|
memset(ddc_data, 0, sizeof(*ddc_data));
|
|
|
|
timeout_hsync = hdmi_utils_get_timeout_in_hysnc(
|
|
ctrl->init_data.timing, ctrl->timeout);
|
|
|
|
if (timeout_hsync <= 0) {
|
|
pr_err("err in timeout hsync calc\n");
|
|
timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
|
|
}
|
|
|
|
pr_debug("timeout for rxstatus %dms, %d hsync\n",
|
|
ctrl->timeout, timeout_hsync);
|
|
|
|
ddc_data->intr_mask = RXSTATUS_MESSAGE_SIZE;
|
|
ddc_data->timeout_ms = ctrl->timeout;
|
|
ddc_data->timeout_hsync = timeout_hsync;
|
|
ddc_data->periodic_timer_hsync = timeout_hsync / 20;
|
|
ddc_data->read_method = HDCP2P2_RXSTATUS_HW_DDC_SW_TRIGGER;
|
|
ddc_data->wait = true;
|
|
|
|
rc = hdmi_hdcp2p2_ddc_read_rxstatus(ddc_ctrl);
|
|
if (rc) {
|
|
pr_err("error reading rxstatus %d\n", rc);
|
|
goto exit;
|
|
}
|
|
|
|
ctrl->timeout_left = ddc_data->timeout_left;
|
|
|
|
pr_debug("timeout left after rxstatus %dms, msg size %d\n",
|
|
ctrl->timeout_left, ddc_data->message_size);
|
|
|
|
if (!ddc_data->message_size) {
|
|
pr_err("recvd invalid message size\n");
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
recvd_msg_buf = kzalloc(ddc_data->message_size, GFP_KERNEL);
|
|
if (!recvd_msg_buf) {
|
|
rc = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
rc = hdmi_hdcp2p2_ddc_read_message(ctrl, recvd_msg_buf,
|
|
ddc_data->message_size, ctrl->timeout_left);
|
|
if (rc) {
|
|
pr_err("error reading message %d\n", rc);
|
|
goto exit;
|
|
}
|
|
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_SUCCESS;
|
|
cdata.recvd_msg_buf = recvd_msg_buf;
|
|
cdata.recvd_msg_len = ddc_data->message_size;
|
|
cdata.timeout = ctrl->timeout_left;
|
|
exit:
|
|
if (rc == -ETIMEDOUT)
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_TIMEOUT;
|
|
else if (rc)
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_FAILED;
|
|
|
|
|
|
hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
kfree(recvd_msg_buf);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_recv_msg_work(struct kthread_work *work)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
|
|
struct hdmi_hdcp2p2_ctrl, recv_msg);
|
|
|
|
hdmi_hdcp2p2_recv_msg(ctrl);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_link_cb(void *data)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = data;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
if (atomic_read(&ctrl->auth_state) != HDCP_STATE_INACTIVE)
|
|
queue_kthread_work(&ctrl->worker, &ctrl->link);
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_link_check(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
|
|
struct hdmi_tx_hdcp2p2_ddc_data *ddc_data;
|
|
int timeout_hsync;
|
|
|
|
ddc_ctrl = ctrl->init_data.ddc_ctrl;
|
|
if (!ddc_ctrl)
|
|
return -EINVAL;
|
|
|
|
hdmi_ddc_config(ddc_ctrl);
|
|
|
|
ddc_data = &ddc_ctrl->hdcp2p2_ddc_data;
|
|
|
|
memset(ddc_data, 0, sizeof(*ddc_data));
|
|
|
|
timeout_hsync = hdmi_utils_get_timeout_in_hysnc(
|
|
ctrl->init_data.timing,
|
|
jiffies_to_msecs((HZ / 2) + (HZ / 4)));
|
|
|
|
if (timeout_hsync <= 0) {
|
|
pr_err("err in timeout hsync calc\n");
|
|
timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
|
|
}
|
|
pr_debug("timeout for rxstatus %d hsyncs\n", timeout_hsync);
|
|
|
|
ddc_data->intr_mask = RXSTATUS_READY | RXSTATUS_MESSAGE_SIZE |
|
|
RXSTATUS_REAUTH_REQ;
|
|
ddc_data->timeout_hsync = timeout_hsync;
|
|
ddc_data->periodic_timer_hsync = timeout_hsync;
|
|
ddc_data->read_method = HDCP2P2_RXSTATUS_HW_DDC_SW_TRIGGER;
|
|
ddc_data->link_cb = hdmi_hdcp2p2_link_cb;
|
|
ddc_data->link_data = ctrl;
|
|
|
|
return hdmi_hdcp2p2_ddc_read_rxstatus(ddc_ctrl);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_auth_status(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
|
|
pr_err("hdcp is off\n");
|
|
return;
|
|
}
|
|
|
|
if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_FAILED) {
|
|
hdmi_hdcp2p2_auth_failed(ctrl);
|
|
} else if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS) {
|
|
ctrl->init_data.notify_status(ctrl->init_data.cb_data,
|
|
HDCP_STATE_AUTHENTICATED);
|
|
|
|
hdmi_hdcp2p2_link_check(ctrl);
|
|
}
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_auth_status_work(struct kthread_work *work)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
|
|
struct hdmi_hdcp2p2_ctrl, status);
|
|
|
|
hdmi_hdcp2p2_auth_status(ctrl);
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_link_work(struct kthread_work *work)
|
|
{
|
|
int rc = 0;
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
|
|
struct hdmi_hdcp2p2_ctrl, link);
|
|
struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
|
|
char *recvd_msg_buf = NULL;
|
|
struct hdmi_tx_hdcp2p2_ddc_data *ddc_data;
|
|
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
cdata.context = ctrl->lib_ctx;
|
|
|
|
ddc_ctrl = ctrl->init_data.ddc_ctrl;
|
|
if (!ddc_ctrl) {
|
|
rc = -EINVAL;
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
goto exit;
|
|
}
|
|
|
|
rc = hdmi_hdcp2p2_ddc_check_status(ddc_ctrl);
|
|
if (rc) {
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
goto exit;
|
|
}
|
|
|
|
ddc_data = &ddc_ctrl->hdcp2p2_ddc_data;
|
|
|
|
if (ddc_data->reauth_req) {
|
|
pr_debug("sync reported loss of synchronization, reauth\n");
|
|
rc = -ENOLINK;
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
goto exit;
|
|
}
|
|
|
|
if (ddc_data->ready && ddc_data->message_size) {
|
|
pr_debug("topology changed. rxstatus msg size %d\n",
|
|
ddc_data->message_size);
|
|
|
|
recvd_msg_buf = kzalloc(ddc_data->message_size, GFP_KERNEL);
|
|
if (!recvd_msg_buf) {
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
goto exit;
|
|
}
|
|
|
|
rc = hdmi_hdcp2p2_ddc_read_message(ctrl, recvd_msg_buf,
|
|
ddc_data->message_size, HDCP2P2_DEFAULT_TIMEOUT);
|
|
if (rc) {
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
pr_err("error reading message %d\n", rc);
|
|
} else {
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_SUCCESS;
|
|
cdata.recvd_msg_buf = recvd_msg_buf;
|
|
cdata.recvd_msg_len = ddc_data->message_size;
|
|
|
|
hdmi_hdcp2p2_link_check(ctrl);
|
|
}
|
|
}
|
|
exit:
|
|
hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
kfree(recvd_msg_buf);
|
|
|
|
if (ctrl->tethered)
|
|
hdmi_hdcp2p2_run(ctrl);
|
|
|
|
if (rc) {
|
|
hdmi_hdcp2p2_auth_failed(ctrl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int hdmi_hdcp2p2_auth(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
|
|
int rc = 0;
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cdata.context = ctrl->lib_ctx;
|
|
|
|
if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTHENTICATING)
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_START;
|
|
else
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
|
|
rc = hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
if (rc)
|
|
hdmi_hdcp2p2_auth_failed(ctrl);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void hdmi_hdcp2p2_auth_work(struct kthread_work *work)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
|
|
struct hdmi_hdcp2p2_ctrl, auth);
|
|
|
|
hdmi_hdcp2p2_auth(ctrl);
|
|
}
|
|
|
|
void hdmi_hdcp2p2_deinit(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = (struct hdmi_hdcp2p2_ctrl *)input;
|
|
struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
|
|
|
|
if (!ctrl) {
|
|
pr_err("invalid input\n");
|
|
return;
|
|
}
|
|
|
|
cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
|
|
cdata.context = ctrl->lib_ctx;
|
|
hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
|
|
|
|
kthread_stop(ctrl->thread);
|
|
|
|
sysfs_remove_group(ctrl->init_data.sysfs_kobj,
|
|
&hdmi_hdcp2p2_fs_attr_group);
|
|
|
|
mutex_destroy(&ctrl->mutex);
|
|
mutex_destroy(&ctrl->msg_lock);
|
|
mutex_destroy(&ctrl->wakeup_mutex);
|
|
kfree(ctrl);
|
|
}
|
|
|
|
void *hdmi_hdcp2p2_init(struct hdmi_hdcp_init_data *init_data)
|
|
{
|
|
int rc;
|
|
struct hdmi_hdcp2p2_ctrl *ctrl;
|
|
static struct hdmi_hdcp_ops ops = {
|
|
.hdmi_hdcp_reauthenticate = hdmi_hdcp2p2_reauthenticate,
|
|
.hdmi_hdcp_authenticate = hdmi_hdcp2p2_authenticate,
|
|
.feature_supported = hdmi_hdcp2p2_feature_supported,
|
|
.hdmi_hdcp_off = hdmi_hdcp2p2_off
|
|
};
|
|
|
|
static struct hdcp_client_ops client_ops = {
|
|
.wakeup = hdmi_hdcp2p2_wakeup,
|
|
};
|
|
|
|
static struct hdcp_txmtr_ops txmtr_ops;
|
|
struct hdcp_register_data register_data;
|
|
|
|
pr_debug("HDCP2P2 feature initialization\n");
|
|
|
|
if (!init_data || !init_data->core_io || !init_data->mutex ||
|
|
!init_data->ddc_ctrl || !init_data->notify_status ||
|
|
!init_data->workq || !init_data->cb_data) {
|
|
pr_err("invalid input\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (init_data->hdmi_tx_ver < MIN_HDMI_TX_MAJOR_VERSION) {
|
|
pr_err("HDMI Tx does not support HDCP 2.2\n");
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
|
|
if (!ctrl)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ctrl->init_data = *init_data;
|
|
ctrl->lib = &txmtr_ops;
|
|
ctrl->tethered = init_data->tethered;
|
|
|
|
rc = sysfs_create_group(init_data->sysfs_kobj,
|
|
&hdmi_hdcp2p2_fs_attr_group);
|
|
if (rc) {
|
|
pr_err("hdcp2p2 sysfs group creation failed\n");
|
|
goto error;
|
|
}
|
|
|
|
ctrl->sink_status = SINK_DISCONNECTED;
|
|
|
|
atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE);
|
|
|
|
ctrl->ops = &ops;
|
|
mutex_init(&ctrl->mutex);
|
|
mutex_init(&ctrl->msg_lock);
|
|
mutex_init(&ctrl->wakeup_mutex);
|
|
|
|
register_data.hdcp_ctx = &ctrl->lib_ctx;
|
|
register_data.client_ops = &client_ops;
|
|
register_data.txmtr_ops = &txmtr_ops;
|
|
register_data.client_ctx = ctrl;
|
|
register_data.tethered = ctrl->tethered;
|
|
|
|
rc = hdcp_library_register(®ister_data);
|
|
if (rc) {
|
|
pr_err("Unable to register with HDCP 2.2 library\n");
|
|
goto error;
|
|
}
|
|
|
|
init_kthread_worker(&ctrl->worker);
|
|
|
|
init_kthread_work(&ctrl->auth, hdmi_hdcp2p2_auth_work);
|
|
init_kthread_work(&ctrl->send_msg, hdmi_hdcp2p2_send_msg_work);
|
|
init_kthread_work(&ctrl->recv_msg, hdmi_hdcp2p2_recv_msg_work);
|
|
init_kthread_work(&ctrl->status, hdmi_hdcp2p2_auth_status_work);
|
|
init_kthread_work(&ctrl->link, hdmi_hdcp2p2_link_work);
|
|
|
|
ctrl->thread = kthread_run(kthread_worker_fn,
|
|
&ctrl->worker, "hdmi_hdcp2p2");
|
|
|
|
if (IS_ERR(ctrl->thread)) {
|
|
pr_err("unable to start hdcp2p2 thread\n");
|
|
rc = PTR_ERR(ctrl->thread);
|
|
ctrl->thread = NULL;
|
|
goto error;
|
|
}
|
|
|
|
return ctrl;
|
|
error:
|
|
kfree(ctrl);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
static bool hdmi_hdcp2p2_supported(struct hdmi_hdcp2p2_ctrl *ctrl)
|
|
{
|
|
u8 hdcp2version;
|
|
|
|
int rc = hdmi_hdcp2p2_read_version(ctrl, &hdcp2version);
|
|
if (rc)
|
|
goto error;
|
|
|
|
if (hdcp2version & BIT(2)) {
|
|
pr_debug("Sink is HDCP 2.2 capable\n");
|
|
return true;
|
|
}
|
|
|
|
error:
|
|
pr_debug("Sink is not HDCP 2.2 capable\n");
|
|
return false;
|
|
}
|
|
|
|
struct hdmi_hdcp_ops *hdmi_hdcp2p2_start(void *input)
|
|
{
|
|
struct hdmi_hdcp2p2_ctrl *ctrl = input;
|
|
|
|
pr_debug("Checking sink capability\n");
|
|
if (hdmi_hdcp2p2_supported(ctrl))
|
|
return ctrl->ops;
|
|
else
|
|
return NULL;
|
|
|
|
}
|
|
|