summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt14
-rw-r--r--arch/arm/boot/dts/qcom/msm8998.dtsi4
-rw-r--r--drivers/gpu/drm/msm/Makefile1
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c50
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h2
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c82
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_hdcp2p2.c955
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c461
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h103
-rw-r--r--drivers/gpu/drm/msm/sde_hdcp.h7
-rw-r--r--drivers/gpu/drm/msm/sde_hdcp_1x.c7
-rw-r--r--drivers/misc/hdcp.c257
-rw-r--r--include/linux/hdcp_qseecom.h5
13 files changed, 1875 insertions, 73 deletions
diff --git a/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt
new file mode 100644
index 000000000000..8d5f55d7a8ca
--- /dev/null
+++ b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt
@@ -0,0 +1,14 @@
+MSM HDCP driver
+
+Standalone driver managing HDCP related communications
+between TZ and HLOS for MSM chipset.
+
+Required properties:
+
+compatible = "qcom,msm-hdcp";
+
+Example:
+
+qcom_msmhdcp: qcom,msm_hdcp {
+ compatible = "qcom,msm-hdcp";
+};
diff --git a/arch/arm/boot/dts/qcom/msm8998.dtsi b/arch/arm/boot/dts/qcom/msm8998.dtsi
index 5218a1d86e6d..fc546512992d 100644
--- a/arch/arm/boot/dts/qcom/msm8998.dtsi
+++ b/arch/arm/boot/dts/qcom/msm8998.dtsi
@@ -2366,6 +2366,10 @@
hyplog-size-offset = <0x414>; /* 0x066BFB34 */
};
+ qcom_msmhdcp: qcom,msm_hdcp {
+ compatible = "qcom,msm-hdcp";
+ };
+
qcom_crypto: qcrypto@1DE0000 {
compatible = "qcom,qcrypto";
reg = <0x1DE0000 0x20000>,
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index a0ac535d50d1..999d5e45e5c5 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -107,6 +107,7 @@ msm_drm-$(CONFIG_DRM_SDE_HDMI) += \
hdmi-staging/sde_hdmi.o \
hdmi-staging/sde_hdmi_bridge.o \
hdmi-staging/sde_hdmi_audio.o \
+ hdmi-staging/sde_hdmi_hdcp2p2.o \
msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \
dsi/pll/dsi_pll_28nm.o
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
index 443e0111516c..47bee42c59fa 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
@@ -1039,6 +1039,7 @@ static void _sde_hdmi_cec_update_phys_addr(struct sde_hdmi *display)
static void _sde_hdmi_init_ddc(struct sde_hdmi *display, struct hdmi *hdmi)
{
display->ddc_ctrl.io = &display->io[HDMI_TX_CORE_IO];
+ init_completion(&display->ddc_ctrl.rx_status_done);
}
static void _sde_hdmi_map_regs(struct sde_hdmi *display, struct hdmi *hdmi)
@@ -1127,32 +1128,39 @@ static void _sde_hdmi_cec_irq(struct sde_hdmi *sde_hdmi)
static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
{
- struct sde_hdmi *sde_hdmi = dev_id;
+ struct sde_hdmi *display = dev_id;
struct hdmi *hdmi;
- if (!sde_hdmi || !sde_hdmi->ctrl.ctrl) {
- SDE_ERROR("sde_hdmi=%p or hdmi is NULL\n", sde_hdmi);
+ if (!display || !display->ctrl.ctrl) {
+ SDE_ERROR("sde_hdmi=%pK or hdmi is NULL\n", display);
return IRQ_NONE;
}
- hdmi = sde_hdmi->ctrl.ctrl;
+
+ hdmi = display->ctrl.ctrl;
/* Process HPD: */
- _sde_hdmi_connector_irq(sde_hdmi);
+ _sde_hdmi_connector_irq(display);
+
+ /* Process Scrambling ISR */
+ sde_hdmi_ddc_scrambling_isr((void *)display);
+
+ /* Process DDC2 */
+ sde_hdmi_ddc_hdcp2p2_isr((void *)display);
/* Process DDC: */
hdmi_i2c_irq(hdmi->i2c);
/* Process HDCP: */
- if (sde_hdmi->hdcp_ops && sde_hdmi->hdcp_data) {
- if (sde_hdmi->hdcp_ops->isr) {
- if (sde_hdmi->hdcp_ops->isr(
- sde_hdmi->hdcp_data))
+ if (display->hdcp_ops && display->hdcp_data) {
+ if (display->hdcp_ops->isr) {
+ if (display->hdcp_ops->isr(
+ display->hdcp_data))
DEV_ERR("%s: hdcp_1x_isr failed\n",
__func__);
}
}
/* Process CEC: */
- _sde_hdmi_cec_irq(sde_hdmi);
+ _sde_hdmi_cec_irq(display);
return IRQ_HANDLED;
}
@@ -1717,11 +1725,22 @@ static int _sde_hdmi_init_hdcp(struct sde_hdmi *hdmi_ctrl)
kfree(hdcp_data);
goto end;
} else {
- hdmi_ctrl->hdcp_feature_data[SDE_HDCP_1x] = hdcp_data;
+ hdmi_ctrl->hdcp_feat_data[SDE_HDCP_1x] = hdcp_data;
SDE_HDMI_DEBUG("%s: HDCP 1.4 initialized\n", __func__);
}
}
+ hdcp_data = sde_hdmi_hdcp2p2_init(&hdcp_init_data);
+
+ if (IS_ERR_OR_NULL(hdcp_data)) {
+ DEV_ERR("%s: hdcp 2.2 init failed\n", __func__);
+ rc = -EINVAL;
+ goto end;
+ } else {
+ hdmi_ctrl->hdcp_feat_data[SDE_HDCP_2P2] = hdcp_data;
+ SDE_HDMI_DEBUG("%s: HDCP 2.2 initialized\n", __func__);
+ }
+
end:
return rc;
}
@@ -1905,8 +1924,11 @@ int sde_hdmi_dev_deinit(struct sde_hdmi *display)
SDE_ERROR("Invalid params\n");
return -EINVAL;
}
- if (display->hdcp_feature_data[SDE_HDCP_1x])
- sde_hdcp_1x_deinit(display->hdcp_feature_data[SDE_HDCP_1x]);
+ if (display->hdcp_feat_data[SDE_HDCP_1x])
+ sde_hdcp_1x_deinit(display->hdcp_feat_data[SDE_HDCP_1x]);
+
+ if (display->hdcp_feat_data[SDE_HDCP_2P2])
+ sde_hdmi_hdcp2p2_deinit(display->hdcp_feat_data[SDE_HDCP_2P2]);
return 0;
}
@@ -1993,6 +2015,8 @@ static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
_sde_hdmi_map_regs(display, priv->hdmi);
_sde_hdmi_init_ddc(display, priv->hdmi);
+ display->enc_lvl = HDCP_STATE_AUTH_ENC_NONE;
+
INIT_DELAYED_WORK(&display->hdcp_cb_work,
sde_hdmi_tx_hdcp_cb_work);
mutex_init(&display->hdcp_mutex);
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
index dd61fa794526..6b6518287028 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
@@ -147,7 +147,7 @@ struct sde_hdmi {
*/
void *hdcp_data;
/*hold hdcp init data*/
- void *hdcp_feature_data[2];
+ void *hdcp_feat_data[2];
struct sde_hdcp_ops *hdcp_ops;
struct sde_hdmi_tx_ddc_ctrl ddc_ctrl;
struct work_struct hpd_work;
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
index a9673d152cb7..6b01d02930f8 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
@@ -32,8 +32,7 @@ struct sde_hdmi_bridge {
#define HDMI_TX_SCRAMBLER_MIN_TX_VERSION 0x04
#define HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ 340000
#define HDMI_TX_SCRAMBLER_TIMEOUT_MSEC 200
-/* default hsyncs for 4k@60 for 200ms */
-#define HDMI_DEFAULT_TIMEOUT_HSYNC 28571
+
/* for AVI program */
#define HDMI_AVI_INFOFRAME_BUFFER_SIZE \
@@ -177,39 +176,22 @@ static int _sde_hdmi_bridge_scrambler_ddc_check_status(struct hdmi *hdmi)
return rc;
}
-static void _sde_hdmi_bridge_scrambler_ddc_reset(struct hdmi *hdmi)
-{
- u32 reg_val;
-
- /* clear ack and disable interrupts */
- reg_val = BIT(14) | BIT(9) | BIT(5) | BIT(1);
- hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL2, reg_val);
-
- /* Reset DDC timers */
- reg_val = BIT(0) | hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
- hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
-
- reg_val = hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
- reg_val &= ~BIT(0);
- hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
-}
-
-static void _sde_hdmi_bridge_scrambler_ddc_disable(struct hdmi *hdmi)
-{
- u32 reg_val;
-
- _sde_hdmi_bridge_scrambler_ddc_reset(hdmi);
- /* Disable HW DDC access to RxStatus register */
- reg_val = hdmi_read(hdmi, REG_HDMI_HW_DDC_CTRL);
- reg_val &= ~(BIT(8) | BIT(9));
- hdmi_write(hdmi, REG_HDMI_HW_DDC_CTRL, reg_val);
-}
-
static int _sde_hdmi_bridge_scrambler_status_timer_setup(struct hdmi *hdmi,
u32 timeout_hsync)
{
u32 reg_val;
int rc;
+ struct sde_connector *c_conn;
+ struct drm_connector *connector = NULL;
+ struct sde_hdmi *display;
+
+ if (!hdmi) {
+ SDE_ERROR("invalid input\n");
+ return -EINVAL;
+ }
+ connector = hdmi->connector;
+ c_conn = to_sde_connector(hdmi->connector);
+ display = (struct sde_hdmi *)c_conn->display;
_sde_hdmi_bridge_ddc_clear_irq(hdmi, "scrambler");
@@ -243,7 +225,7 @@ static int _sde_hdmi_bridge_scrambler_status_timer_setup(struct hdmi *hdmi,
if (rc)
SDE_ERROR("scrambling ddc error %d\n", rc);
- _sde_hdmi_bridge_scrambler_ddc_disable(hdmi);
+ _sde_hdmi_scrambler_ddc_disable((void *)display);
return rc;
}
@@ -269,20 +251,6 @@ static int _sde_hdmi_bridge_setup_ddc_timers(struct hdmi *hdmi,
return 0;
}
-static inline int _sde_hdmi_bridge_get_timeout_in_hysnc(
- struct drm_display_mode *mode, u32 timeout_ms)
-{
- /*
- * pixel clock = h_total * v_total * fps
- * 1 sec = pixel clock number of pixels are transmitted.
- * time taken by one line (h_total) = 1s / (v_total * fps).
- * lines for give time = (time_ms * 1000) / (1000000 / (v_total * fps))
- * = (time_ms * clock) / h_total
- */
-
- return (timeout_ms * mode->clock / mode->htotal);
-}
-
static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
struct drm_display_mode *mode)
{
@@ -291,14 +259,17 @@ static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
u32 reg_val = 0;
u32 tmds_clock_ratio = 0;
bool scrambler_on = false;
-
+ struct sde_connector *c_conn;
struct drm_connector *connector = NULL;
+ struct sde_hdmi *display;
if (!hdmi || !mode) {
SDE_ERROR("invalid input\n");
return -EINVAL;
}
connector = hdmi->connector;
+ c_conn = to_sde_connector(hdmi->connector);
+ display = (struct sde_hdmi *)c_conn->display;
/* Read HDMI version */
reg_val = hdmi_read(hdmi, REG_HDMI_VERSION);
@@ -344,9 +315,10 @@ static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
* status bit on the sink. Sink should set this bit
* with in 200ms after scrambler is enabled.
*/
- timeout_hsync = _sde_hdmi_bridge_get_timeout_in_hysnc(
- mode,
+ timeout_hsync = _sde_hdmi_get_timeout_in_hysnc(
+ (void *)display,
HDMI_TX_SCRAMBLER_TIMEOUT_MSEC);
+
if (timeout_hsync <= 0) {
SDE_ERROR("err in timeout hsync calc\n");
timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
@@ -408,13 +380,23 @@ static void sde_hdmi_update_hdcp_info(struct drm_connector *connector)
return;
}
+ /* check first if hdcp2p2 is supported */
+ fd = display->hdcp_feat_data[SDE_HDCP_2P2];
+ if (fd)
+ ops = sde_hdmi_hdcp2p2_start(fd);
+
+ if (ops && ops->feature_supported)
+ display->hdcp22_present = ops->feature_supported(fd);
+ else
+ display->hdcp22_present = false;
+
if (!display->hdcp22_present) {
if (display->hdcp1_use_sw_keys) {
display->hdcp14_present =
hdcp1_check_if_supported_load_app();
}
if (display->hdcp14_present) {
- fd = display->hdcp_feature_data[SDE_HDCP_1x];
+ fd = display->hdcp_feat_data[SDE_HDCP_1x];
if (fd)
ops = sde_hdcp_1x_start(fd);
}
@@ -666,9 +648,9 @@ static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
_sde_hdmi_bridge_set_spd_infoframe(hdmi, mode);
DRM_DEBUG("hdmi setup info frame\n");
}
- _sde_hdmi_bridge_setup_scrambler(hdmi, mode);
_sde_hdmi_save_mode(hdmi, mode);
+ _sde_hdmi_bridge_setup_scrambler(hdmi, mode);
}
static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_hdcp2p2.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_hdcp2p2.c
new file mode 100644
index 000000000000..986b33fe1016
--- /dev/null
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_hdcp2p2.c
@@ -0,0 +1,955 @@
+/* Copyright (c) 2017, 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 "sde_hdcp.h"
+#include "video/msm_hdmi_hdcp_mgr.h"
+#include "sde_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_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 sde_hdmi_hdcp2p2_sink_status {
+ SINK_DISCONNECTED,
+ SINK_CONNECTED
+};
+
+enum sde_hdmi_auth_status {
+ HDMI_HDCP_AUTH_STATUS_FAILURE,
+ HDMI_HDCP_AUTH_STATUS_SUCCESS
+};
+
+struct sde_hdmi_hdcp2p2_ctrl {
+ atomic_t auth_state;
+ enum sde_hdmi_hdcp2p2_sink_status sink_status; /* Is sink connected */
+ struct sde_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 sde_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;
+ enum sde_hdmi_auth_status auth_status;
+ 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;
+ struct kthread_work poll;
+};
+
+static int sde_hdmi_hdcp2p2_auth(struct sde_hdmi_hdcp2p2_ctrl *ctrl);
+static void sde_hdmi_hdcp2p2_send_msg(struct sde_hdmi_hdcp2p2_ctrl *ctrl);
+static void sde_hdmi_hdcp2p2_recv_msg(struct sde_hdmi_hdcp2p2_ctrl *ctrl);
+static void sde_hdmi_hdcp2p2_auth_status(struct sde_hdmi_hdcp2p2_ctrl *ctrl);
+static int sde_hdmi_hdcp2p2_link_check(struct sde_hdmi_hdcp2p2_ctrl *ctrl);
+
+static bool sde_hdcp2p2_is_valid_state(struct sde_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 sde_hdmi_hdcp2p2_copy_buf(struct sde_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 sde_hdmi_hdcp2p2_wakeup(struct hdmi_hdcp_wakeup_data *data)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+
+ if (!data) {
+ SDE_ERROR("invalid input\n");
+ return -EINVAL;
+ }
+
+ ctrl = data->context;
+ if (!ctrl) {
+ SDE_ERROR("invalid ctrl\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&ctrl->wakeup_mutex);
+
+ SDE_HDCP_DEBUG("cmd: %s, timeout %dms\n",
+ hdmi_hdcp_cmd_to_str(data->cmd),
+ data->timeout);
+
+ ctrl->wakeup_cmd = data->cmd;
+
+ if (data->timeout)
+ ctrl->timeout = data->timeout * 2;
+ else
+ ctrl->timeout = HDCP2P2_DEFAULT_TIMEOUT;
+
+ if (!sde_hdcp2p2_is_valid_state(ctrl)) {
+ SDE_ERROR("invalid state\n");
+ goto exit;
+ }
+
+ if (sde_hdmi_hdcp2p2_copy_buf(ctrl, data))
+ goto exit;
+
+ if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS)
+ ctrl->auth_status = HDMI_HDCP_AUTH_STATUS_SUCCESS;
+ else if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_FAILED)
+ ctrl->auth_status = HDMI_HDCP_AUTH_STATUS_FAILURE;
+
+ 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_LINK_POLL:
+ queue_kthread_work(&ctrl->worker, &ctrl->poll);
+ break;
+ case HDMI_HDCP_WKUP_CMD_AUTHENTICATE:
+ queue_kthread_work(&ctrl->worker, &ctrl->auth);
+ break;
+ default:
+ SDE_ERROR("invalid wakeup command %d\n", ctrl->wakeup_cmd);
+ }
+exit:
+ mutex_unlock(&ctrl->wakeup_mutex);
+ return 0;
+}
+
+static int sde_hdmi_hdcp2p2_wakeup_lib(struct sde_hdmi_hdcp2p2_ctrl *ctrl,
+ struct hdcp_lib_wakeup_data *data)
+{
+ int rc = 0;
+
+ if (ctrl && ctrl->lib && ctrl->lib->wakeup &&
+ data && (data->cmd != HDCP_LIB_WKUP_CMD_INVALID)) {
+ rc = ctrl->lib->wakeup(data);
+ if (rc)
+ SDE_ERROR("error sending %s to lib\n",
+ hdcp_lib_cmd_to_str(data->cmd));
+ }
+
+ return rc;
+}
+
+static void sde_hdmi_hdcp2p2_reset(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return;
+ }
+
+ ctrl->sink_status = SINK_DISCONNECTED;
+ atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE);
+}
+
+static void sde_hdmi_hdcp2p2_off(void *input)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+ struct hdmi_hdcp_wakeup_data cdata = {HDMI_HDCP_WKUP_CMD_AUTHENTICATE};
+
+ ctrl = (struct sde_hdmi_hdcp2p2_ctrl *)input;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return;
+ }
+
+ sde_hdmi_hdcp2p2_reset(ctrl);
+
+ flush_kthread_worker(&ctrl->worker);
+
+ sde_hdmi_hdcp2p2_ddc_disable((void *)ctrl->init_data.cb_data);
+
+ cdata.context = input;
+ sde_hdmi_hdcp2p2_wakeup(&cdata);
+}
+
+static int sde_hdmi_hdcp2p2_authenticate(void *input)
+{
+ struct sde_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 */
+ _sde_hdmi_scrambler_ddc_disable((void *)ctrl->init_data.cb_data);
+ sde_hdmi_hdcp2p2_ddc_disable((void *)ctrl->init_data.cb_data);
+
+ cdata.context = input;
+ sde_hdmi_hdcp2p2_wakeup(&cdata);
+
+ return rc;
+}
+
+static int sde_hdmi_hdcp2p2_reauthenticate(void *input)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+
+ ctrl = (struct sde_hdmi_hdcp2p2_ctrl *)input;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return -EINVAL;
+ }
+
+ sde_hdmi_hdcp2p2_reset(ctrl);
+
+ return sde_hdmi_hdcp2p2_authenticate(input);
+}
+
+static void sde_hdmi_hdcp2p2_auth_failed(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return;
+ }
+
+ atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL);
+
+ sde_hdmi_hdcp2p2_ddc_disable(ctrl->init_data.cb_data);
+
+ /* notify hdmi tx about HDCP failure */
+ ctrl->init_data.notify_status(ctrl->init_data.cb_data,
+ HDCP_STATE_AUTH_FAIL);
+}
+
+static int sde_hdmi_hdcp2p2_ddc_rd_message(struct sde_hdmi_hdcp2p2_ctrl *ctrl,
+ u8 *buf, int size, u32 timeout)
+{
+ struct sde_hdmi_tx_ddc_data *ddc_data;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+
+ int rc;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid ctrl\n");
+ return -EINVAL;
+ }
+
+ ddc_ctrl = ctrl->init_data.ddc_ctrl;
+ ddc_data = &ddc_ctrl->ddc_data;
+
+ if (!ddc_data) {
+ SDE_ERROR("invalid ddc data\n");
+ return -EINVAL;
+ }
+
+ if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
+ SDE_ERROR("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";
+
+ rc = sde_hdmi_ddc_read(ctrl->init_data.cb_data);
+ if (rc)
+ SDE_ERROR("Cannot read HDCP message register\n");
+
+ ctrl->timeout_left = ddc_data->timeout_left;
+
+ return rc;
+}
+
+static int sde_hdmi_hdcp2p2_ddc_wt_message(struct sde_hdmi_hdcp2p2_ctrl *ctrl,
+ u8 *buf, size_t size)
+{
+ struct sde_hdmi_tx_ddc_data *ddc_data;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+
+ int rc;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid ctrl\n");
+ return -EINVAL;
+ }
+
+ ddc_ctrl = ctrl->init_data.ddc_ctrl;
+ ddc_data = &ddc_ctrl->ddc_data;
+
+ if (!ddc_data) {
+ SDE_ERROR("invalid ddc data\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_WRITE_MESSAGE;
+ ddc_data->data_buf = buf;
+ ddc_data->data_len = size;
+ ddc_data->hard_timeout = ctrl->timeout;
+ ddc_data->what = "HDCP2WriteMessage";
+
+ rc = sde_hdmi_ddc_write((void *)ctrl->init_data.cb_data);
+ if (rc)
+ SDE_ERROR("Cannot write HDCP message register\n");
+
+ ctrl->timeout_left = ddc_data->timeout_left;
+
+ return rc;
+}
+
+static int sde_hdmi_hdcp2p2_read_version(struct sde_hdmi_hdcp2p2_ctrl *ctrl,
+ u8 *hdcp2version)
+{
+ struct sde_hdmi_tx_ddc_data *ddc_data;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+ int rc;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid ctrl\n");
+ return -EINVAL;
+ }
+
+ ddc_ctrl = ctrl->init_data.ddc_ctrl;
+ ddc_data = &ddc_ctrl->ddc_data;
+
+ if (!ddc_data) {
+ SDE_ERROR("invalid ddc data\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_VERSION;
+ ddc_data->data_buf = hdcp2version;
+ ddc_data->data_len = 1;
+ ddc_data->request_len = 1;
+ ddc_data->retry = 1;
+ ddc_data->what = "HDCP2Version";
+
+ rc = sde_hdmi_ddc_read((void *)ctrl->init_data.cb_data);
+ if (rc) {
+ SDE_ERROR("Cannot read HDCP2Version register");
+ return rc;
+ }
+
+ SDE_HDCP_DEBUG("Read HDCP2Version as %u\n", *hdcp2version);
+ return rc;
+}
+
+static bool sde_hdmi_hdcp2p2_feature_supported(void *input)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = input;
+ struct hdcp_txmtr_ops *lib = NULL;
+ bool supported = false;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ goto end;
+ }
+
+ lib = ctrl->lib;
+ if (!lib) {
+ SDE_ERROR("invalid lib ops data\n");
+ goto end;
+ }
+
+ if (lib->feature_supported) {
+ supported = lib->feature_supported(
+ ctrl->lib_ctx);
+ }
+
+end:
+ return supported;
+}
+
+static void sde_hdmi_hdcp2p2_send_msg(struct sde_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) {
+ SDE_ERROR("invalid input\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ cdata.context = ctrl->lib_ctx;
+
+ if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
+ SDE_ERROR("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 = sde_hdmi_hdcp2p2_ddc_wt_message(ctrl,
+ msg, (size_t)msglen);
+ if (rc) {
+ SDE_ERROR("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);
+
+ sde_hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
+}
+
+static void sde_hdmi_hdcp2p2_send_msg_work(struct kthread_work *work)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, send_msg);
+
+ sde_hdmi_hdcp2p2_send_msg(ctrl);
+}
+
+static void sde_hdmi_hdcp2p2_link_cb(void *data)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = data;
+
+ if (!ctrl) {
+ SDE_HDCP_DEBUG("invalid input\n");
+ return;
+ }
+
+ if (atomic_read(&ctrl->auth_state) != HDCP_STATE_INACTIVE)
+ queue_kthread_work(&ctrl->worker, &ctrl->link);
+}
+
+static void sde_hdmi_hdcp2p2_recv_msg(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ int timeout_hsync = 0, rc = 0;
+ char *recvd_msg_buf = NULL;
+ struct sde_hdmi_tx_hdcp2p2_ddc_data *ddc_data;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+ struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
+
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ cdata.context = ctrl->lib_ctx;
+
+ if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
+ SDE_ERROR("hdcp is off\n");
+ goto exit;
+ }
+
+ ddc_ctrl = ctrl->init_data.ddc_ctrl;
+ if (!ddc_ctrl) {
+ pr_err("invalid ddc ctrl\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ ddc_data = &ddc_ctrl->sde_hdcp2p2_ddc_data;
+ memset(ddc_data, 0, sizeof(*ddc_data));
+
+ timeout_hsync = _sde_hdmi_get_timeout_in_hysnc(
+ (void *)ctrl->init_data.cb_data, ctrl->timeout);
+
+ if (timeout_hsync <= 0) {
+ SDE_ERROR("err in timeout hsync calc\n");
+ timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
+ }
+
+ SDE_HDCP_DEBUG("timeout for rxstatus %dms, %d hsync\n",
+ ctrl->timeout, timeout_hsync);
+
+ ddc_data->intr_mask = RXSTATUS_MESSAGE_SIZE | RXSTATUS_REAUTH_REQ;
+ 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 = sde_hdmi_hdcp2p2_read_rxstatus(ctrl->init_data.cb_data);
+ if (rc) {
+ SDE_ERROR("error reading rxstatus %d\n", rc);
+ goto exit;
+ }
+
+ if (ddc_data->reauth_req) {
+ ddc_data->reauth_req = false;
+
+ SDE_HDCP_DEBUG("reauth triggered by sink\n");
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ ctrl->timeout_left = ddc_data->timeout_left;
+
+ SDE_HDCP_DEBUG("timeout left after rxstatus %dms, msg size %d\n",
+ ctrl->timeout_left, ddc_data->message_size);
+
+ if (!ddc_data->message_size) {
+ SDE_ERROR("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 = sde_hdmi_hdcp2p2_ddc_rd_message(ctrl, recvd_msg_buf,
+ ddc_data->message_size, ctrl->timeout_left);
+ if (rc) {
+ SDE_ERROR("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;
+
+ sde_hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
+ kfree(recvd_msg_buf);
+}
+
+static void sde_hdmi_hdcp2p2_recv_msg_work(struct kthread_work *work)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, recv_msg);
+
+ sde_hdmi_hdcp2p2_recv_msg(ctrl);
+}
+
+static int sde_hdmi_hdcp2p2_link_check(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+ struct sde_hdmi_tx_hdcp2p2_ddc_data *ddc_data;
+ int timeout_hsync;
+ int ret;
+
+ ddc_ctrl = ctrl->init_data.ddc_ctrl;
+
+ if (!ddc_ctrl)
+ return -EINVAL;
+
+ sde_hdmi_ddc_config(ctrl->init_data.cb_data);
+
+ ddc_data = &ddc_ctrl->sde_hdcp2p2_ddc_data;
+
+ memset(ddc_data, 0, sizeof(*ddc_data));
+
+ timeout_hsync = _sde_hdmi_get_timeout_in_hysnc(
+ (void *)ctrl->init_data.cb_data,
+ jiffies_to_msecs(HZ / 2));
+
+ if (timeout_hsync <= 0) {
+ SDE_ERROR("err in timeout hsync calc\n");
+ timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
+ }
+ SDE_HDCP_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 = sde_hdmi_hdcp2p2_link_cb;
+ ddc_data->link_data = ctrl;
+
+ ret = sde_hdmi_hdcp2p2_read_rxstatus((void *)ctrl->init_data.cb_data);
+ return ret;
+}
+
+static void sde_hdmi_hdcp2p2_poll_work(struct kthread_work *work)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, poll);
+
+ sde_hdmi_hdcp2p2_link_check(ctrl);
+}
+
+static void sde_hdmi_hdcp2p2_auth_status(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return;
+ }
+
+ if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) {
+ SDE_ERROR("hdcp is off\n");
+ return;
+ }
+
+ if (ctrl->auth_status == HDMI_HDCP_AUTH_STATUS_SUCCESS) {
+ ctrl->init_data.notify_status(ctrl->init_data.cb_data,
+ HDCP_STATE_AUTHENTICATED);
+
+ atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATED);
+ } else {
+ sde_hdmi_hdcp2p2_auth_failed(ctrl);
+ }
+}
+
+static void sde_hdmi_hdcp2p2_auth_status_work(struct kthread_work *work)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, status);
+
+ sde_hdmi_hdcp2p2_auth_status(ctrl);
+}
+
+static void sde_hdmi_hdcp2p2_link_work(struct kthread_work *work)
+{
+ int rc = 0;
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, link);
+ struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
+ char *recvd_msg_buf = NULL;
+ struct sde_hdmi_tx_hdcp2p2_ddc_data *ddc_data;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+
+ if (!ctrl) {
+ SDE_ERROR("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;
+ }
+
+ ddc_data = &ddc_ctrl->sde_hdcp2p2_ddc_data;
+
+ if (ddc_data->reauth_req) {
+ SDE_HDCP_DEBUG("reauth triggered by sink\n");
+
+ ddc_data->reauth_req = false;
+ rc = -ENOLINK;
+ cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
+ goto exit;
+ }
+
+ if (ddc_data->ready && ddc_data->message_size) {
+ SDE_HDCP_DEBUG("topology changed. rxstatus msg size %d\n",
+ ddc_data->message_size);
+
+ ddc_data->ready = false;
+
+ recvd_msg_buf = kzalloc(ddc_data->message_size, GFP_KERNEL);
+ if (!recvd_msg_buf) {
+ cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
+ goto exit;
+ }
+
+ rc = sde_hdmi_hdcp2p2_ddc_rd_message(ctrl, recvd_msg_buf,
+ ddc_data->message_size, HDCP2P2_DEFAULT_TIMEOUT);
+ if (rc) {
+ cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
+ SDE_ERROR("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;
+ }
+
+ ddc_data->message_size = 0;
+ }
+exit:
+ sde_hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
+ kfree(recvd_msg_buf);
+
+ if (rc) {
+ sde_hdmi_hdcp2p2_auth_failed(ctrl);
+ return;
+ }
+}
+
+static int sde_hdmi_hdcp2p2_auth(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
+ int rc = 0;
+
+ if (!ctrl) {
+ SDE_ERROR("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 = sde_hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
+ if (rc)
+ sde_hdmi_hdcp2p2_auth_failed(ctrl);
+
+ return rc;
+}
+
+static void sde_hdmi_hdcp2p2_auth_work(struct kthread_work *work)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl = container_of(work,
+ struct sde_hdmi_hdcp2p2_ctrl, auth);
+
+ sde_hdmi_hdcp2p2_auth(ctrl);
+}
+
+void sde_hdmi_hdcp2p2_deinit(void *input)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+ struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID};
+
+ ctrl = (struct sde_hdmi_hdcp2p2_ctrl *)input;
+
+ if (!ctrl) {
+ SDE_ERROR("invalid input\n");
+ return;
+ }
+
+ cdata.cmd = HDCP_LIB_WKUP_CMD_STOP;
+ cdata.context = ctrl->lib_ctx;
+ sde_hdmi_hdcp2p2_wakeup_lib(ctrl, &cdata);
+
+ kthread_stop(ctrl->thread);
+
+ mutex_destroy(&ctrl->mutex);
+ mutex_destroy(&ctrl->msg_lock);
+ mutex_destroy(&ctrl->wakeup_mutex);
+ kfree(ctrl);
+}
+
+void *sde_hdmi_hdcp2p2_init(struct sde_hdcp_init_data *init_data)
+{
+ int rc;
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+ static struct sde_hdcp_ops ops = {
+ .reauthenticate = sde_hdmi_hdcp2p2_reauthenticate,
+ .authenticate = sde_hdmi_hdcp2p2_authenticate,
+ .feature_supported = sde_hdmi_hdcp2p2_feature_supported,
+ .off = sde_hdmi_hdcp2p2_off
+ };
+
+ static struct hdcp_client_ops client_ops = {
+ .wakeup = sde_hdmi_hdcp2p2_wakeup,
+ };
+
+ static struct hdcp_txmtr_ops txmtr_ops;
+ struct hdcp_register_data register_data;
+
+ SDE_HDCP_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) {
+ SDE_ERROR("invalid input\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (init_data->hdmi_tx_ver < MIN_HDMI_TX_MAJOR_VERSION) {
+ SDE_ERROR("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->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.device_type = HDCP_TXMTR_HDMI;
+ register_data.client_ctx = ctrl;
+
+ rc = hdcp_library_register(&register_data);
+ if (rc) {
+ SDE_ERROR("Unable to register with HDCP 2.2 library\n");
+ goto error;
+ }
+
+ init_kthread_worker(&ctrl->worker);
+
+ init_kthread_work(&ctrl->auth, sde_hdmi_hdcp2p2_auth_work);
+ init_kthread_work(&ctrl->send_msg, sde_hdmi_hdcp2p2_send_msg_work);
+ init_kthread_work(&ctrl->recv_msg, sde_hdmi_hdcp2p2_recv_msg_work);
+ init_kthread_work(&ctrl->status, sde_hdmi_hdcp2p2_auth_status_work);
+ init_kthread_work(&ctrl->link, sde_hdmi_hdcp2p2_link_work);
+ init_kthread_work(&ctrl->poll, sde_hdmi_hdcp2p2_poll_work);
+
+ ctrl->thread = kthread_run(kthread_worker_fn,
+ &ctrl->worker, "hdmi_hdcp2p2");
+
+ if (IS_ERR(ctrl->thread)) {
+ SDE_ERROR("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 sde_hdmi_hdcp2p2_supported(struct sde_hdmi_hdcp2p2_ctrl *ctrl)
+{
+ u8 hdcp2version = 0;
+ int rc = sde_hdmi_hdcp2p2_read_version(ctrl, &hdcp2version);
+
+ if (rc)
+ goto error;
+
+ if (hdcp2version & BIT(2)) {
+ SDE_HDCP_DEBUG("Sink is HDCP 2.2 capable\n");
+ return true;
+ }
+
+error:
+ SDE_HDCP_DEBUG("Sink is not HDCP 2.2 capable\n");
+ return false;
+}
+
+struct sde_hdcp_ops *sde_hdmi_hdcp2p2_start(void *input)
+{
+ struct sde_hdmi_hdcp2p2_ctrl *ctrl;
+
+ ctrl = (struct sde_hdmi_hdcp2p2_ctrl *)input;
+
+ SDE_HDCP_DEBUG("Checking sink capability\n");
+ if (sde_hdmi_hdcp2p2_supported(ctrl))
+ return ctrl->ops;
+ else
+ return NULL;
+
+}
+
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
index 10fc89545440..35ee5ae56d6a 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
@@ -26,6 +26,237 @@
#include "sde_hdmi_regs.h"
#include "hdmi.h"
+#define HDMI_SEC_TO_MS 1000
+#define HDMI_MS_TO_US 1000
+#define HDMI_SEC_TO_US (HDMI_SEC_TO_MS * HDMI_MS_TO_US)
+#define HDMI_KHZ_TO_HZ 1000
+#define HDMI_BUSY_WAIT_DELAY_US 100
+
+static void sde_hdmi_hdcp2p2_ddc_clear_status(struct sde_hdmi *display)
+{
+ u32 reg_val;
+ struct hdmi *hdmi;
+
+ if (!display) {
+ pr_err("invalid ddc ctrl\n");
+ return;
+ }
+ hdmi = display->ctrl.ctrl;
+ /* check for errors and clear status */
+ reg_val = hdmi_read(hdmi, HDMI_HDCP2P2_DDC_STATUS);
+
+ if (reg_val & BIT(4)) {
+ pr_debug("ddc aborted\n");
+ reg_val |= BIT(5);
+ }
+
+ if (reg_val & BIT(8)) {
+ pr_debug("timed out\n");
+ reg_val |= BIT(9);
+ }
+
+ if (reg_val & BIT(12)) {
+ pr_debug("NACK0\n");
+ reg_val |= BIT(13);
+ }
+
+ if (reg_val & BIT(14)) {
+ pr_debug("NACK1\n");
+ reg_val |= BIT(15);
+ }
+
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_STATUS, reg_val);
+}
+
+int sde_hdmi_ddc_hdcp2p2_isr(void *hdmi_display)
+{
+ struct sde_hdmi_tx_hdcp2p2_ddc_data *data;
+ u32 intr0, intr2, intr5;
+ u32 msg_size;
+ int rc = 0;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ struct hdmi *hdmi;
+
+ ddc_ctrl = &display->ddc_ctrl;
+ data = &ddc_ctrl->sde_hdcp2p2_ddc_data;
+ hdmi = display->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ intr0 = hdmi_read(hdmi, HDMI_DDC_INT_CTRL0);
+ intr2 = hdmi_read(hdmi, HDMI_HDCP_INT_CTRL2);
+ intr5 = hdmi_read(hdmi, HDMI_DDC_INT_CTRL5);
+
+ pr_debug("intr0: 0x%x, intr2: 0x%x, intr5: 0x%x\n",
+ intr0, intr2, intr5);
+
+ /* check if encryption is enabled */
+ if (intr2 & BIT(0)) {
+ /*
+ * ack encryption ready interrupt.
+ * disable encryption ready interrupt.
+ * enable encryption not ready interrupt.
+ */
+ intr2 &= ~BIT(2);
+ intr2 |= BIT(1) | BIT(6);
+
+ pr_info("HDCP 2.2 Encryption enabled\n");
+ data->encryption_ready = true;
+ }
+
+ /* check if encryption is disabled */
+ if (intr2 & BIT(4)) {
+ /*
+ * ack encryption not ready interrupt.
+ * disable encryption not ready interrupt.
+ * enable encryption ready interrupt.
+ */
+ intr2 &= ~BIT(6);
+ intr2 |= BIT(5) | BIT(2);
+
+ pr_info("HDCP 2.2 Encryption disabled\n");
+ data->encryption_ready = false;
+ }
+
+ hdmi_write(hdmi, HDMI_HDCP_INT_CTRL2, intr2);
+
+ /* get the message size bits 29:20 */
+ msg_size = (intr0 & (0x3FF << 20)) >> 20;
+
+ if (msg_size) {
+ /* ack and disable message size interrupt */
+ intr0 |= BIT(30);
+ intr0 &= ~BIT(31);
+
+ data->message_size = msg_size;
+ }
+
+ /* check and disable ready interrupt */
+ if (intr0 & BIT(16)) {
+ /* ack ready/not ready interrupt */
+ intr0 |= BIT(17);
+ intr0 &= ~BIT(18);
+ pr_debug("got ready interrupt\n");
+ data->ready = true;
+ }
+
+ /* check for reauth req interrupt */
+ if (intr0 & BIT(12)) {
+ /* ack and disable reauth req interrupt */
+ intr0 |= BIT(13);
+ intr0 &= ~BIT(14);
+ pr_err("got reauth interrupt\n");
+ data->reauth_req = true;
+ }
+
+ /* check for ddc fail interrupt */
+ if (intr0 & BIT(8)) {
+ /* ack ddc fail interrupt */
+ intr0 |= BIT(9);
+ pr_err("got ddc fail interrupt\n");
+ data->ddc_max_retries_fail = true;
+ }
+
+ /* check for ddc done interrupt */
+ if (intr0 & BIT(4)) {
+ /* ack ddc done interrupt */
+ intr0 |= BIT(5);
+ pr_debug("got ddc done interrupt\n");
+ data->ddc_done = true;
+ }
+
+ /* check for ddc read req interrupt */
+ if (intr0 & BIT(0)) {
+ /* ack read req interrupt */
+ intr0 |= BIT(1);
+
+ data->ddc_read_req = true;
+ }
+
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL0, intr0);
+
+ if (intr5 & BIT(0)) {
+ pr_err("RXSTATUS_DDC_REQ_TIMEOUT\n");
+
+ /* ack and disable timeout interrupt */
+ intr5 |= BIT(1);
+ intr5 &= ~BIT(2);
+
+ data->ddc_timeout = true;
+ }
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL5, intr5);
+
+ if (data->message_size || data->ready || data->reauth_req) {
+ if (data->wait) {
+ complete(&ddc_ctrl->rx_status_done);
+ } else if (data->link_cb && data->link_data) {
+ data->link_cb(data->link_data);
+ } else {
+ pr_err("new msg/reauth not handled\n");
+ rc = -EINVAL;
+ }
+ }
+
+ sde_hdmi_hdcp2p2_ddc_clear_status(display);
+
+ return rc;
+}
+
+int sde_hdmi_ddc_scrambling_isr(void *hdmi_display)
+{
+
+ bool scrambler_timer_off = false;
+ u32 intr2, intr5;
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ struct hdmi *hdmi;
+
+
+ hdmi = display->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ intr2 = hdmi_read(hdmi, HDMI_DDC_INT_CTRL2);
+ intr5 = hdmi_read(hdmi, HDMI_DDC_INT_CTRL5);
+
+ pr_debug("intr2: 0x%x, intr5: 0x%x\n", intr2, intr5);
+
+ if (intr2 & BIT(12)) {
+ pr_err("SCRAMBLER_STATUS_NOT\n");
+
+ intr2 |= BIT(14);
+ scrambler_timer_off = true;
+ }
+
+ if (intr2 & BIT(8)) {
+ pr_err("SCRAMBLER_STATUS_DDC_FAILED\n");
+
+ intr2 |= BIT(9);
+
+ scrambler_timer_off = true;
+ }
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL2, intr2);
+
+ if (intr5 & BIT(8)) {
+ pr_err("SCRAMBLER_STATUS_DDC_REQ_TIMEOUT\n");
+ intr5 |= BIT(9);
+ intr5 &= ~BIT(10);
+ scrambler_timer_off = true;
+ }
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL5, intr5);
+
+ if (scrambler_timer_off)
+ _sde_hdmi_scrambler_ddc_disable((void *)display);
+
+ return 0;
+}
+
static int sde_hdmi_ddc_read_retry(struct sde_hdmi *display)
{
int status;
@@ -311,3 +542,233 @@ int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set)
return 0;
}
+
+int _sde_hdmi_get_timeout_in_hysnc(void *hdmi_display, u32 timeout_ms)
+{
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ struct drm_display_mode mode = display->mode;
+ /*
+ * pixel clock = h_total * v_total * fps
+ * 1 sec = pixel clock number of pixels are transmitted.
+ * time taken by one line (h_total) = 1s / (v_total * fps).
+ * lines for give time = (time_ms * 1000) / (1000000 / (v_total * fps))
+ * = (time_ms * clock) / h_total
+ */
+
+ return (timeout_ms * mode.clock / mode.htotal);
+}
+
+static void sde_hdmi_hdcp2p2_ddc_reset(struct sde_hdmi *hdmi_ctrl)
+{
+ u32 reg_val;
+ struct hdmi *hdmi = hdmi_ctrl->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("Invalid parameters\n");
+ return;
+ }
+
+ /*
+ * Clear acks for DDC_REQ, DDC_DONE, DDC_FAILED, RXSTATUS_READY,
+ * RXSTATUS_MSG_SIZE
+ */
+ reg_val = BIT(30) | BIT(17) | BIT(13) | BIT(9) | BIT(5) | BIT(1);
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL0, reg_val);
+ /* Reset DDC timers */
+ reg_val = BIT(0) | hdmi_read(hdmi, HDMI_HDCP2P2_DDC_CTRL);
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_CTRL, reg_val);
+ reg_val = hdmi_read(hdmi, HDMI_HDCP2P2_DDC_CTRL);
+ reg_val &= ~BIT(0);
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_CTRL, reg_val);
+}
+
+void sde_hdmi_hdcp2p2_ddc_disable(void *hdmi_display)
+{
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ u32 reg_val;
+ struct hdmi *hdmi = display->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("Invalid parameters\n");
+ return;
+ }
+
+ sde_hdmi_hdcp2p2_ddc_reset(display);
+
+ /* Disable HW DDC access to RxStatus register */
+ reg_val = hdmi_read(hdmi, HDMI_HW_DDC_CTRL);
+ reg_val &= ~(BIT(1) | BIT(0));
+
+ hdmi_write(hdmi, HDMI_HW_DDC_CTRL, reg_val);
+}
+
+static void _sde_hdmi_scrambler_ddc_reset(struct hdmi *hdmi)
+{
+ u32 reg_val;
+
+ /* clear ack and disable interrupts */
+ reg_val = BIT(14) | BIT(9) | BIT(5) | BIT(1);
+ hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL2, reg_val);
+
+ /* Reset DDC timers */
+ reg_val = BIT(0) | hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
+
+ reg_val = hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
+ reg_val &= ~BIT(0);
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
+}
+
+void _sde_hdmi_scrambler_ddc_disable(void *hdmi_display)
+{
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ u32 reg_val;
+
+ struct hdmi *hdmi = display->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("Invalid parameters\n");
+ return;
+ }
+
+ _sde_hdmi_scrambler_ddc_reset(hdmi);
+ /* Disable HW DDC access to RxStatus register */
+ reg_val = hdmi_read(hdmi, REG_HDMI_HW_DDC_CTRL);
+ reg_val &= ~(BIT(8) | BIT(9));
+ hdmi_write(hdmi, REG_HDMI_HW_DDC_CTRL, reg_val);
+}
+
+void sde_hdmi_ddc_config(void *hdmi_display)
+{
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ struct hdmi *hdmi = display->ctrl.ctrl;
+
+ if (!hdmi) {
+ pr_err("Invalid parameters\n");
+ return;
+ }
+ hdmi_write(hdmi, REG_HDMI_DDC_SPEED,
+ HDMI_DDC_SPEED_THRESHOLD(2) |
+ HDMI_DDC_SPEED_PRESCALE(10));
+
+ hdmi_write(hdmi, REG_HDMI_DDC_SETUP,
+ HDMI_DDC_SETUP_TIMEOUT(0xff));
+
+ /* enable reference timer for 19us */
+ hdmi_write(hdmi, REG_HDMI_DDC_REF,
+ HDMI_DDC_REF_REFTIMER_ENABLE |
+ HDMI_DDC_REF_REFTIMER(19));
+}
+
+int sde_hdmi_hdcp2p2_read_rxstatus(void *hdmi_display)
+{
+ u32 reg_val;
+ u32 intr_en_mask;
+ u32 timeout;
+ u32 timer;
+ int rc = 0;
+ int busy_wait_us;
+ struct sde_hdmi_tx_hdcp2p2_ddc_data *data;
+ struct sde_hdmi *display = (struct sde_hdmi *)hdmi_display;
+ struct hdmi *hdmi = display->ctrl.ctrl;
+ struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl;
+ u32 rem;
+
+ if (!hdmi) {
+ pr_err("Invalid ddc data\n");
+ return -EINVAL;
+ }
+
+ ddc_ctrl = &display->ddc_ctrl;
+ data = &ddc_ctrl->sde_hdcp2p2_ddc_data;
+ if (!data) {
+ pr_err("Invalid ddc data\n");
+ return -EINVAL;
+ }
+
+ rc = ddc_clear_irq(hdmi);
+ if (rc) {
+ pr_err("DDC clear irq failed\n");
+ return rc;
+ }
+ intr_en_mask = data->intr_mask;
+ intr_en_mask |= BIT(HDCP2P2_RXSTATUS_DDC_FAILED_INTR_MASK);
+
+ /* Disable short read for now, sinks don't support it */
+ reg_val = hdmi_read(hdmi, HDMI_HDCP2P2_DDC_CTRL);
+ reg_val |= BIT(4);
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_CTRL, reg_val);
+ /*
+ * Setup the DDC timers for HDMI_HDCP2P2_DDC_TIMER_CTRL1 and
+ * HDMI_HDCP2P2_DDC_TIMER_CTRL2.
+ * Following are the timers:
+ * 1. DDC_REQUEST_TIMER: Timeout in hsyncs in which to wait for the
+ * HDCP 2.2 sink to respond to an RxStatus request
+ * 2. DDC_URGENT_TIMER: Time period in hsyncs to issue an urgent flag
+ * when an RxStatus DDC request is made but not accepted by I2C
+ * engine
+ * 3. DDC_TIMEOUT_TIMER: Timeout in hsyncs which starts counting when
+ * a request is made and stops when it is accepted by DDC arbiter
+ */
+
+ timeout = data->timeout_hsync;
+ timer = data->periodic_timer_hsync;
+
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_TIMER_CTRL, timer);
+ /* Set both urgent and hw-timeout fields to the same value */
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_TIMER_CTRL2,
+ (timeout << 16 | timeout));
+ /* enable interrupts */
+ reg_val = intr_en_mask;
+ /* Clear interrupt status bits */
+ reg_val |= intr_en_mask >> 1;
+
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL0, reg_val);
+ reg_val = hdmi_read(hdmi, HDMI_DDC_INT_CTRL5);
+ /* clear and enable RxStatus read timeout */
+ reg_val |= BIT(2) | BIT(1);
+
+ hdmi_write(hdmi, HDMI_DDC_INT_CTRL5, reg_val);
+ /*
+ * Enable hardware DDC access to RxStatus register
+ *
+ * HDMI_HW_DDC_CTRL:Bits 1:0 (RXSTATUS_DDC_ENABLE) read like this:
+ *
+ * 0 = disable HW controlled DDC access to RxStatus
+ * 1 = automatic on when HDCP 2.2 is authenticated and loop based on
+ * request timer (i.e. the hardware will loop automatically)
+ * 2 = force on and loop based on request timer (hardware will loop)
+ * 3 = enable by sw trigger and loop until interrupt is generated for
+ * RxStatus.reauth_req, RxStatus.ready or RxStatus.message_Size.
+ *
+ * Depending on the value of ddc_data::poll_sink, we make the decision
+ * to use either SW_TRIGGER(3) (poll_sink = false) which means that the
+ * hardware will poll sink and generate interrupt when sink responds,
+ * or use AUTOMATIC_LOOP(1) (poll_sink = true) which will poll the sink
+ * based on request timer
+ */
+
+ reg_val = hdmi_read(hdmi, HDMI_HW_DDC_CTRL);
+ reg_val &= ~(BIT(1) | BIT(0));
+
+ busy_wait_us = data->timeout_ms * HDMI_MS_TO_US;
+
+ /* read method: HDCP2P2_RXSTATUS_HW_DDC_SW_TRIGGER */
+ reg_val |= BIT(1) | BIT(0);
+ hdmi_write(hdmi, HDMI_HW_DDC_CTRL, reg_val);
+
+ hdmi_write(hdmi, HDMI_HDCP2P2_DDC_SW_TRIGGER, 1);
+ if (data->wait) {
+ reinit_completion(&ddc_ctrl->rx_status_done);
+ rem = wait_for_completion_timeout(&ddc_ctrl->rx_status_done,
+ HZ);
+ data->timeout_left = jiffies_to_msecs(rem);
+
+ if (!data->timeout_left) {
+ pr_err("sw ddc rxstatus timeout\n");
+ rc = -ETIMEDOUT;
+ }
+ sde_hdmi_hdcp2p2_ddc_disable((void *)display);
+ }
+ return rc;
+}
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
index 6becf1efc9d8..697cb0c40754 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
@@ -37,6 +37,72 @@
#define HDMI_UTIL_ERROR(fmt, args...) SDE_ERROR(fmt, ##args)
+/*
+ * Offsets in HDMI_DDC_INT_CTRL0 register
+ *
+ * The HDMI_DDC_INT_CTRL0 register is intended for HDCP 2.2 RxStatus
+ * register manipulation. It reads like this:
+ *
+ * Bit 31: RXSTATUS_MESSAGE_SIZE_MASK (1 = generate interrupt when size > 0)
+ * Bit 30: RXSTATUS_MESSAGE_SIZE_ACK (1 = Acknowledge message size intr)
+ * Bits 29-20: RXSTATUS_MESSAGE_SIZE (Actual size of message available)
+ * Bits 19-18: RXSTATUS_READY_MASK (1 = generate interrupt when ready = 1
+ * 2 = generate interrupt when ready = 0)
+ * Bit 17: RXSTATUS_READY_ACK (1 = Acknowledge ready bit interrupt)
+ * Bit 16: RXSTATUS_READY (1 = Rxstatus ready bit read is 1)
+ * Bit 15: RXSTATUS_READY_NOT (1 = Rxstatus ready bit read is 0)
+ * Bit 14: RXSTATUS_REAUTH_REQ_MASK (1 = generate interrupt when reauth is
+ * requested by sink)
+ * Bit 13: RXSTATUS_REAUTH_REQ_ACK (1 = Acknowledge Reauth req interrupt)
+ * Bit 12: RXSTATUS_REAUTH_REQ (1 = Rxstatus reauth req bit read is 1)
+ * Bit 10: RXSTATUS_DDC_FAILED_MASK (1 = generate interrupt when DDC
+ * tranasaction fails)
+ * Bit 9: RXSTATUS_DDC_FAILED_ACK (1 = Acknowledge ddc failure interrupt)
+ * Bit 8: RXSTATUS_DDC_FAILED (1 = DDC transaction failed)
+ * Bit 6: RXSTATUS_DDC_DONE_MASK (1 = generate interrupt when DDC
+ * transaction completes)
+ * Bit 5: RXSTATUS_DDC_DONE_ACK (1 = Acknowledge ddc done interrupt)
+ * Bit 4: RXSTATUS_DDC_DONE (1 = DDC transaction is done)
+ * Bit 2: RXSTATUS_DDC_REQ_MASK (1 = generate interrupt when DDC Read
+ * request for RXstatus is made)
+ * Bit 1: RXSTATUS_DDC_REQ_ACK (1 = Acknowledge Rxstatus read interrupt)
+ * Bit 0: RXSTATUS_DDC_REQ (1 = RXStatus DDC read request is made)
+ *
+ */
+
+#define HDCP2P2_RXSTATUS_MESSAGE_SIZE_SHIFT 20
+#define HDCP2P2_RXSTATUS_MESSAGE_SIZE_MASK 0x3ff00000
+#define HDCP2P2_RXSTATUS_MESSAGE_SIZE_ACK_SHIFT 30
+#define HDCP2P2_RXSTATUS_MESSAGE_SIZE_INTR_SHIFT 31
+
+#define HDCP2P2_RXSTATUS_REAUTH_REQ_SHIFT 12
+#define HDCP2P2_RXSTATUS_REAUTH_REQ_MASK 1
+#define HDCP2P2_RXSTATUS_REAUTH_REQ_ACK_SHIFT 13
+#define HDCP2P2_RXSTATUS_REAUTH_REQ_INTR_SHIFT 14
+
+#define HDCP2P2_RXSTATUS_READY_SHIFT 16
+#define HDCP2P2_RXSTATUS_READY_MASK 1
+#define HDCP2P2_RXSTATUS_READY_ACK_SHIFT 17
+#define HDCP2P2_RXSTATUS_READY_INTR_SHIFT 18
+#define HDCP2P2_RXSTATUS_READY_INTR_MASK 18
+
+#define HDCP2P2_RXSTATUS_DDC_FAILED_SHIFT 8
+#define HDCP2P2_RXSTATUS_DDC_FAILED_ACKSHIFT 9
+#define HDCP2P2_RXSTATUS_DDC_FAILED_INTR_MASK 10
+#define HDCP2P2_RXSTATUS_DDC_DONE 6
+
+/* default hsyncs for 4k@60 for 200ms */
+#define HDMI_DEFAULT_TIMEOUT_HSYNC 28571
+
+/*
+ * Bits 1:0 in HDMI_HW_DDC_CTRL that dictate how the HDCP 2.2 RxStatus will be
+ * read by the hardware
+ */
+#define HDCP2P2_RXSTATUS_HW_DDC_DISABLE 0
+#define HDCP2P2_RXSTATUS_HW_DDC_AUTOMATIC_LOOP 1
+#define HDCP2P2_RXSTATUS_HW_DDC_FORCE_LOOP 2
+#define HDCP2P2_RXSTATUS_HW_DDC_SW_TRIGGER 3
+
struct sde_hdmi_tx_ddc_data {
char *what;
u8 *data_buf;
@@ -50,14 +116,49 @@ struct sde_hdmi_tx_ddc_data {
int retry;
};
+enum sde_hdmi_tx_hdcp2p2_rxstatus_intr_mask {
+ RXSTATUS_MESSAGE_SIZE = BIT(31),
+ RXSTATUS_READY = BIT(18),
+ RXSTATUS_REAUTH_REQ = BIT(14),
+};
+
+struct sde_hdmi_tx_hdcp2p2_ddc_data {
+ enum sde_hdmi_tx_hdcp2p2_rxstatus_intr_mask intr_mask;
+ u32 timeout_ms;
+ u32 timeout_hsync;
+ u32 periodic_timer_hsync;
+ u32 timeout_left;
+ u32 read_method;
+ u32 message_size;
+ bool encryption_ready;
+ bool ready;
+ bool reauth_req;
+ bool ddc_max_retries_fail;
+ bool ddc_done;
+ bool ddc_read_req;
+ bool ddc_timeout;
+ bool wait;
+ int irq_wait_count;
+ void (*link_cb)(void *data);
+ void *link_data;
+};
+
struct sde_hdmi_tx_ddc_ctrl {
- atomic_t rxstatus_busy_wait_done;
+ struct completion rx_status_done;
struct dss_io_data *io;
struct sde_hdmi_tx_ddc_data ddc_data;
+ struct sde_hdmi_tx_hdcp2p2_ddc_data sde_hdcp2p2_ddc_data;
};
/* DDC */
int sde_hdmi_ddc_write(void *cb_data);
int sde_hdmi_ddc_read(void *cb_data);
+int sde_hdmi_ddc_scrambling_isr(void *hdmi_display);
+int _sde_hdmi_get_timeout_in_hysnc(void *hdmi_display, u32 timeout_ms);
+void _sde_hdmi_scrambler_ddc_disable(void *hdmi_display);
+void sde_hdmi_hdcp2p2_ddc_disable(void *hdmi_display);
+int sde_hdmi_hdcp2p2_read_rxstatus(void *hdmi_display);
+void sde_hdmi_ddc_config(void *hdmi_display);
+int sde_hdmi_ddc_hdcp2p2_isr(void *hdmi_display);
#endif /* _SDE_HDMI_UTIL_H_ */
diff --git a/drivers/gpu/drm/msm/sde_hdcp.h b/drivers/gpu/drm/msm/sde_hdcp.h
index 443bd5790551..49cca9399cb0 100644
--- a/drivers/gpu/drm/msm/sde_hdcp.h
+++ b/drivers/gpu/drm/msm/sde_hdcp.h
@@ -25,6 +25,7 @@
#include <drm/drm_edid.h>
#include "hdmi.h"
#include "sde_kms.h"
+#include "sde_hdmi_util.h"
#ifdef SDE_HDCP_DEBUG_ENABLE
#define SDE_HDCP_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args)
@@ -75,9 +76,9 @@ struct sde_hdcp_ops {
void *sde_hdcp_1x_init(struct sde_hdcp_init_data *init_data);
void sde_hdcp_1x_deinit(void *input);
-
struct sde_hdcp_ops *sde_hdcp_1x_start(void *input);
-
+void *sde_hdmi_hdcp2p2_init(struct sde_hdcp_init_data *init_data);
+void sde_hdmi_hdcp2p2_deinit(void *input);
const char *sde_hdcp_state_name(enum sde_hdcp_states hdcp_state);
-
+struct sde_hdcp_ops *sde_hdmi_hdcp2p2_start(void *input);
#endif /* __SDE_HDCP_H__ */
diff --git a/drivers/gpu/drm/msm/sde_hdcp_1x.c b/drivers/gpu/drm/msm/sde_hdcp_1x.c
index e650098ec96d..3aba9e307732 100644
--- a/drivers/gpu/drm/msm/sde_hdcp_1x.c
+++ b/drivers/gpu/drm/msm/sde_hdcp_1x.c
@@ -1216,18 +1216,19 @@ static void sde_hdcp_1x_cache_topology(struct sde_hdcp_1x *hdcp)
memcpy((void *)&hdcp->cached_tp,
(void *) &hdcp->current_tp,
sizeof(hdcp->cached_tp));
+ hdcp1_cache_repeater_topology((void *)&hdcp->cached_tp);
}
-static void sde_hdcp_1x_notify_topology(struct sde_hdcp_1x *hdcp)
+static void sde_hdcp_1x_notify_topology(void)
{
- /* TO DO : something here for HDCP 1x*/
+ hdcp1_notify_topology();
}
static void sde_hdcp_1x_update_auth_status(struct sde_hdcp_1x *hdcp)
{
if (sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATED)) {
sde_hdcp_1x_cache_topology(hdcp);
- sde_hdcp_1x_notify_topology(hdcp);
+ sde_hdcp_1x_notify_topology();
}
if (hdcp->init_data.notify_status &&
diff --git a/drivers/misc/hdcp.c b/drivers/misc/hdcp.c
index 6dabb92bc8ce..19950b4e8059 100644
--- a/drivers/misc/hdcp.c
+++ b/drivers/misc/hdcp.c
@@ -12,10 +12,13 @@
#define pr_fmt(fmt) "%s: " fmt, __func__
+#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/list.h>
@@ -30,9 +33,26 @@
#include <linux/errno.h>
#include <linux/hdcp_qseecom.h>
#include <linux/kthread.h>
+#include <linux/of.h>
+#include <video/msm_hdmi_hdcp_mgr.h>
#include "qseecom_kernel.h"
+struct msm_hdcp_mgr {
+ struct platform_device *pdev;
+ dev_t dev_num;
+ struct cdev cdev;
+ struct class *class;
+ struct device *device;
+ struct HDCP_V2V1_MSG_TOPOLOGY cached_tp;
+ u32 tp_msgid;
+};
+
+#define CLASS_NAME "hdcp"
+#define DRIVER_NAME "msm_hdcp"
+
+static struct msm_hdcp_mgr *hdcp_drv_mgr;
+
#define TZAPP_NAME "hdcp2p2"
#define HDCP1_APP_NAME "hdcp1"
#define QSEECOM_SBUFF_SIZE 0x1000
@@ -2433,3 +2453,240 @@ void hdcp_library_deregister(void *phdcpcontext)
kzfree(handle);
}
EXPORT_SYMBOL(hdcp_library_deregister);
+
+void hdcp1_notify_topology(void)
+{
+ char *envp[4];
+ char *a;
+ char *b;
+
+ a = kzalloc(SZ_16, GFP_KERNEL);
+
+ if (!a)
+ return;
+
+ b = kzalloc(SZ_16, GFP_KERNEL);
+
+ if (!b) {
+ kfree(a);
+ return;
+ }
+
+ envp[0] = "HDCP_MGR_EVENT=MSG_READY";
+ envp[1] = a;
+ envp[2] = b;
+ envp[3] = NULL;
+
+ snprintf(envp[1], 16, "%d", (int)DOWN_CHECK_TOPOLOGY);
+ snprintf(envp[2], 16, "%d", (int)HDCP_V1_TX);
+
+ kobject_uevent_env(&hdcp_drv_mgr->device->kobj, KOBJ_CHANGE, envp);
+ kfree(a);
+ kfree(b);
+}
+
+static ssize_t msm_hdcp_1x_sysfs_rda_tp(struct device *dev,
+struct device_attribute *attr, char *buf)
+{
+ ssize_t ret = 0;
+
+ if (!hdcp_drv_mgr) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ switch (hdcp_drv_mgr->tp_msgid) {
+ case DOWN_CHECK_TOPOLOGY:
+ case DOWN_REQUEST_TOPOLOGY:
+ buf[MSG_ID_IDX] = hdcp_drv_mgr->tp_msgid;
+ buf[RET_CODE_IDX] = HDCP_AUTHED;
+ ret = HEADER_LEN;
+
+ memcpy(buf + HEADER_LEN, &hdcp_drv_mgr->cached_tp,
+ sizeof(struct HDCP_V2V1_MSG_TOPOLOGY));
+
+ ret += sizeof(struct HDCP_V2V1_MSG_TOPOLOGY);
+
+ /* clear the flag once data is read back to user space*/
+ hdcp_drv_mgr->tp_msgid = -1;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+} /* hdcp_1x_sysfs_rda_tp*/
+
+static ssize_t msm_hdcp_1x_sysfs_wta_tp(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int msgid = 0;
+ ssize_t ret = count;
+
+ if (!hdcp_drv_mgr || !buf) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ msgid = buf[0];
+
+ switch (msgid) {
+ case DOWN_CHECK_TOPOLOGY:
+ case DOWN_REQUEST_TOPOLOGY:
+ hdcp_drv_mgr->tp_msgid = msgid;
+ break;
+ /* more cases added here */
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+} /* hdmi_tx_sysfs_wta_hpd */
+
+static DEVICE_ATTR(tp, S_IRUGO | S_IWUSR, msm_hdcp_1x_sysfs_rda_tp,
+ msm_hdcp_1x_sysfs_wta_tp);
+
+void hdcp1_cache_repeater_topology(void *hdcp1_cached_tp)
+{
+ memcpy((void *)&hdcp_drv_mgr->cached_tp,
+ hdcp1_cached_tp,
+ sizeof(struct HDCP_V2V1_MSG_TOPOLOGY));
+}
+
+static struct attribute *msm_hdcp_fs_attrs[] = {
+ &dev_attr_tp.attr,
+ NULL
+};
+
+static struct attribute_group msm_hdcp_fs_attr_group = {
+ .attrs = msm_hdcp_fs_attrs
+};
+
+static int msm_hdcp_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int msm_hdcp_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations msm_hdcp_fops = {
+ .owner = THIS_MODULE,
+ .open = msm_hdcp_open,
+ .release = msm_hdcp_close,
+};
+
+static const struct of_device_id msm_hdcp_dt_match[] = {
+ { .compatible = "qcom,msm-hdcp",},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, msm_hdcp_dt_match);
+
+static int msm_hdcp_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ hdcp_drv_mgr = devm_kzalloc(&pdev->dev, sizeof(struct msm_hdcp_mgr),
+ GFP_KERNEL);
+ if (!hdcp_drv_mgr)
+ return -ENOMEM;
+
+ hdcp_drv_mgr->pdev = pdev;
+
+ platform_set_drvdata(pdev, hdcp_drv_mgr);
+
+ ret = alloc_chrdev_region(&hdcp_drv_mgr->dev_num, 0, 1, DRIVER_NAME);
+ if (ret < 0) {
+ pr_err("alloc_chrdev_region failed ret = %d\n", ret);
+ goto error_get_dev_num;
+ }
+
+ hdcp_drv_mgr->class = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(hdcp_drv_mgr->class)) {
+ ret = PTR_ERR(hdcp_drv_mgr->class);
+ pr_err("couldn't create class rc = %d\n", ret);
+ goto error_class_create;
+ }
+
+ hdcp_drv_mgr->device = device_create(hdcp_drv_mgr->class, NULL,
+ hdcp_drv_mgr->dev_num, NULL, DRIVER_NAME);
+ if (IS_ERR(hdcp_drv_mgr->device)) {
+ ret = PTR_ERR(hdcp_drv_mgr->device);
+ pr_err("device_create failed %d\n", ret);
+ goto error_class_device_create;
+ }
+
+ cdev_init(&hdcp_drv_mgr->cdev, &msm_hdcp_fops);
+ ret = cdev_add(&hdcp_drv_mgr->cdev,
+ MKDEV(MAJOR(hdcp_drv_mgr->dev_num), 0), 1);
+ if (ret < 0) {
+ pr_err("cdev_add failed %d\n", ret);
+ goto error_cdev_add;
+ }
+
+ ret = sysfs_create_group(&hdcp_drv_mgr->device->kobj,
+ &msm_hdcp_fs_attr_group);
+ if (ret)
+ pr_err("unable to register rotator sysfs nodes\n");
+
+ return 0;
+error_cdev_add:
+ device_destroy(hdcp_drv_mgr->class, hdcp_drv_mgr->dev_num);
+error_class_device_create:
+ class_destroy(hdcp_drv_mgr->class);
+error_class_create:
+ unregister_chrdev_region(hdcp_drv_mgr->dev_num, 1);
+error_get_dev_num:
+ devm_kfree(&pdev->dev, hdcp_drv_mgr);
+ hdcp_drv_mgr = NULL;
+ return ret;
+}
+
+static int msm_hdcp_remove(struct platform_device *pdev)
+{
+ struct msm_hdcp_mgr *mgr;
+
+ mgr = (struct msm_hdcp_mgr *)platform_get_drvdata(pdev);
+ if (!mgr)
+ return -ENODEV;
+
+ sysfs_remove_group(&hdcp_drv_mgr->device->kobj,
+ &msm_hdcp_fs_attr_group);
+ cdev_del(&hdcp_drv_mgr->cdev);
+ device_destroy(hdcp_drv_mgr->class, hdcp_drv_mgr->dev_num);
+ class_destroy(hdcp_drv_mgr->class);
+ unregister_chrdev_region(hdcp_drv_mgr->dev_num, 1);
+
+ devm_kfree(&pdev->dev, hdcp_drv_mgr);
+ hdcp_drv_mgr = NULL;
+ return 0;
+}
+
+static struct platform_driver msm_hdcp_driver = {
+ .probe = msm_hdcp_probe,
+ .remove = msm_hdcp_remove,
+ .driver = {
+ .name = "msm_hdcp",
+ .of_match_table = msm_hdcp_dt_match,
+ .pm = NULL,
+ }
+};
+
+static int __init msm_hdcp_init(void)
+{
+ return platform_driver_register(&msm_hdcp_driver);
+}
+
+static void __exit msm_hdcp_exit(void)
+{
+ return platform_driver_unregister(&msm_hdcp_driver);
+}
+
+module_init(msm_hdcp_init);
+module_exit(msm_hdcp_exit);
+
+MODULE_DESCRIPTION("MSM HDCP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/hdcp_qseecom.h b/include/linux/hdcp_qseecom.h
index 68f2dd993170..dc4af867a3bc 100644
--- a/include/linux/hdcp_qseecom.h
+++ b/include/linux/hdcp_qseecom.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2017, 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
@@ -146,5 +146,6 @@ void hdcp_library_deregister(void *phdcpcontext);
bool hdcp1_check_if_supported_load_app(void);
int hdcp1_set_keys(uint32_t *aksv_msb, uint32_t *aksv_lsb);
int hdcp1_set_enc(bool enable);
-
+void hdcp1_cache_repeater_topology(void *hdcp1_cached_tp);
+void hdcp1_notify_topology(void);
#endif /* __HDCP_QSEECOM_H */