diff options
| -rw-r--r-- | Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt | 14 | ||||
| -rw-r--r-- | arch/arm/boot/dts/qcom/msm8998.dtsi | 4 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c | 50 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c | 82 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_hdcp2p2.c | 955 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c | 461 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h | 103 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde_hdcp.h | 7 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde_hdcp_1x.c | 7 | ||||
| -rw-r--r-- | drivers/misc/hdcp.c | 257 | ||||
| -rw-r--r-- | include/linux/hdcp_qseecom.h | 5 |
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(®ister_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 */ |
