From 5068a5df4bad5a060fbbca0f36c1a3201b6f38fb Mon Sep 17 00:00:00 2001 From: Ajay Singh Parmar Date: Wed, 17 Aug 2016 12:19:04 -0700 Subject: [PATCH] mdss: display-port: add support for hdcp 2.2 Add support for HDCP (High-Bandwidth Digital Content Protect) version 2.2 for DisplayPort. Define interfaces to interact with Trust Zone and DisplayPort drivers. Hookup with TZ's kernel module and send-receive HDCP 2.2 messages to-from sink using DP's aux channel. Change-Id: Id77e77ee628667dacc7a714c553b5ce5beafa9bb Signed-off-by: Ajay Singh Parmar --- drivers/video/fbdev/msm/Makefile | 1 + drivers/video/fbdev/msm/mdss_dp.c | 132 ++- drivers/video/fbdev/msm/mdss_dp.h | 20 +- drivers/video/fbdev/msm/mdss_dp_aux.c | 45 +- drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c | 861 ++++++++++++++++++ drivers/video/fbdev/msm/mdss_dp_util.h | 4 + .../fbdev/msm/{mdss_hdcp_1x.h => mdss_hdcp.h} | 10 +- drivers/video/fbdev/msm/mdss_hdcp_1x.c | 2 +- drivers/video/fbdev/msm/mdss_hdmi_hdcp2p2.c | 2 +- drivers/video/fbdev/msm/mdss_hdmi_tx.c | 2 +- 10 files changed, 1044 insertions(+), 35 deletions(-) create mode 100644 drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c rename drivers/video/fbdev/msm/{mdss_hdcp_1x.h => mdss_hdcp.h} (90%) diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile index 6009a9b97d1c..b905c0e855dd 100644 --- a/drivers/video/fbdev/msm/Makefile +++ b/drivers/video/fbdev/msm/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_FB_MSM_MDSS) += mdss_dba_utils.o obj-$(CONFIG_FB_MSM_MDSS) += mdss_hdcp_1x.o obj-$(CONFIG_FB_MSM_MDSS_DP_PANEL) += mdss_dp.o mdss_dp_util.o obj-$(CONFIG_FB_MSM_MDSS_DP_PANEL) += mdss_dp_aux.o +obj-$(CONFIG_FB_MSM_MDSS_DP_PANEL) += mdss_dp_hdcp2p2.o obj-$(CONFIG_FB_MSM_MDSS) += mdss_io_util.o obj-$(CONFIG_FB_MSM_MDSS) += msm_ext_display.o diff --git a/drivers/video/fbdev/msm/mdss_dp.c b/drivers/video/fbdev/msm/mdss_dp.c index dbf6f16d735f..516cbdc9192b 100644 --- a/drivers/video/fbdev/msm/mdss_dp.c +++ b/drivers/video/fbdev/msm/mdss_dp.c @@ -36,7 +36,7 @@ #include "mdss_dp_util.h" #include "mdss_hdmi_panel.h" #include -#include "mdss_hdcp_1x.h" +#include "mdss_hdcp.h" #include "mdss_debug.h" #define RGB_COMPONENTS 3 @@ -1621,22 +1621,24 @@ static void mdss_dp_hdcp_cb(void *ptr, enum hdcp_states status) return; } - ops = dp->hdcp_ops; + ops = dp->hdcp.ops; mutex_lock(&dp->train_mutex); switch (status) { case HDCP_STATE_AUTHENTICATED: - pr_debug("hdcp 1.3 authenticated\n"); + pr_debug("hdcp authenticated\n"); + dp->hdcp.auth_state = true; break; case HDCP_STATE_AUTH_FAIL: + dp->hdcp.auth_state = false; + if (dp->power_on) { pr_debug("Reauthenticating\n"); if (ops && ops->reauthenticate) { - rc = ops->reauthenticate(dp->hdcp_data); + rc = ops->reauthenticate(dp->hdcp.data); if (rc) - pr_err("HDCP reauth failed. rc=%d\n", - rc); + pr_err("reauth failed rc=%d\n", rc); } } else { pr_debug("not reauthenticating, cable disconnected\n"); @@ -1685,19 +1687,22 @@ static int mdss_dp_hdcp_init(struct mdss_panel_data *pdata) hdcp_init_data.sec_access = true; hdcp_init_data.client_id = HDCP_CLIENT_DP; - dp_drv->hdcp_data = hdcp_1x_init(&hdcp_init_data); - if (IS_ERR_OR_NULL(dp_drv->hdcp_data)) { + dp_drv->hdcp.data = hdcp_1x_init(&hdcp_init_data); + if (IS_ERR_OR_NULL(dp_drv->hdcp.data)) { pr_err("Error hdcp init\n"); rc = -EINVAL; goto error; } - dp_drv->panel_data.panel_info.hdcp_1x_data = dp_drv->hdcp_data; + dp_drv->panel_data.panel_info.hdcp_1x_data = dp_drv->hdcp.data; pr_debug("HDCP 1.3 initialized\n"); - dp_drv->hdcp_ops = hdcp_1x_start(dp_drv->hdcp_data); + dp_drv->hdcp.hdcp2 = dp_hdcp2p2_init(&hdcp_init_data); + if (!IS_ERR_OR_NULL(dp_drv->hdcp.data)) + pr_debug("HDCP 2.2 initialized\n"); + dp_drv->hdcp.feature_enabled = true; return 0; error: return rc; @@ -1839,13 +1844,46 @@ static void mdss_dp_mainlink_push_idle(struct mdss_panel_data *pdata) pr_debug("mainlink off done\n"); } +static void mdss_dp_update_hdcp_info(struct mdss_dp_drv_pdata *dp) +{ + void *fd = NULL; + struct hdcp_ops *ops = NULL; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + /* check first if hdcp2p2 is supported */ + fd = dp->hdcp.hdcp2; + if (fd) + ops = dp_hdcp2p2_start(fd); + + if (ops && ops->feature_supported) + dp->hdcp.hdcp2_present = ops->feature_supported(fd); + else + dp->hdcp.hdcp2_present = false; + + if (!dp->hdcp.hdcp2_present) { + dp->hdcp.hdcp1_present = hdcp1_check_if_supported_load_app(); + + if (dp->hdcp.hdcp1_present) { + fd = dp->hdcp.hdcp1; + ops = hdcp_1x_start(fd); + } + } + + /* update internal data about hdcp */ + dp->hdcp.data = fd; + dp->hdcp.ops = ops; +} + static int mdss_dp_event_handler(struct mdss_panel_data *pdata, int event, void *arg) { int rc = 0; struct fb_info *fbi; struct mdss_dp_drv_pdata *dp = NULL; - struct hdcp_ops *ops; if (!pdata) { pr_err("%s: Invalid input data\n", __func__); @@ -1857,24 +1895,23 @@ static int mdss_dp_event_handler(struct mdss_panel_data *pdata, dp = container_of(pdata, struct mdss_dp_drv_pdata, panel_data); - ops = dp->hdcp_ops; - switch (event) { case MDSS_EVENT_UNBLANK: rc = mdss_dp_on(pdata); break; case MDSS_EVENT_PANEL_ON: - if (hdcp1_check_if_supported_load_app()) { - if (ops && ops->authenticate) - rc = ops->authenticate(dp->hdcp_data); - } + mdss_dp_update_hdcp_info(dp); + + if (dp->hdcp.ops && dp->hdcp.ops->authenticate) + rc = dp->hdcp.ops->authenticate(dp->hdcp.data); break; case MDSS_EVENT_PANEL_OFF: rc = mdss_dp_off(pdata); break; case MDSS_EVENT_BLANK: - if (ops && ops->off) - ops->off(dp->hdcp_data); + if (dp->hdcp.ops && dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + mdss_dp_mainlink_push_idle(pdata); break; case MDSS_EVENT_FB_REGISTERED: @@ -1909,6 +1946,7 @@ static int mdss_dp_remove(struct platform_device *pdev) struct mdss_dp_drv_pdata *dp_drv = NULL; dp_drv = platform_get_drvdata(pdev); + dp_hdcp2p2_deinit(dp_drv->hdcp.data); iounmap(dp_drv->ctrl_io.base); dp_drv->ctrl_io.base = NULL; @@ -2149,11 +2187,6 @@ irqreturn_t dp_isr(int irq, void *ptr) dp_aux_native_handler(dp, isr1); } - if (dp->hdcp_ops && dp->hdcp_ops->isr) { - if (dp->hdcp_ops->isr(dp->hdcp_data)) - pr_err("dp_hdcp_isr failed\n"); - } - return IRQ_HANDLED; } @@ -2410,6 +2443,10 @@ static void usbpd_response_callback(struct usbpd_svid_handler *hdlr, u8 cmd, mdss_dp_host_init(&dp_drv->panel_data); else dp_send_events(dp_drv, EV_USBPD_DP_CONFIGURE); + + if (dp_drv->alt_mode.dp_status.hpd_irq && dp_drv->power_on && + dp_drv->hdcp.ops && dp_drv->hdcp.ops->isr) + dp_drv->hdcp.ops->isr(dp_drv->hdcp.data); break; case DP_VDM_STATUS: dp_drv->alt_mode.dp_status.response = *vdos; @@ -2609,6 +2646,53 @@ probe_err: } +void *mdss_dp_get_hdcp_data(struct device *dev) +{ + struct mdss_dp_drv_pdata *dp_drv = NULL; + + if (!dev) { + pr_err("%s:Invalid input\n", __func__); + return NULL; + } + dp_drv = dev_get_drvdata(dev); + if (!dp_drv) { + pr_err("%s:Invalid dp driver\n", __func__); + return NULL; + } + return dp_drv->hdcp.data; +} + +static inline bool dp_is_hdcp_enabled(struct mdss_dp_drv_pdata *dp_drv) +{ + return dp_drv->hdcp.feature_enabled && + (dp_drv->hdcp.hdcp1_present || dp_drv->hdcp.hdcp2_present) && + dp_drv->hdcp.ops; +} + +static inline bool dp_is_stream_shareable(struct mdss_dp_drv_pdata *dp_drv) +{ + bool ret = 0; + + switch (dp_drv->hdcp.enc_lvl) { + case HDCP_STATE_AUTH_ENC_NONE: + ret = true; + break; + case HDCP_STATE_AUTH_ENC_1X: + ret = dp_is_hdcp_enabled(dp_drv) && + dp_drv->hdcp.auth_state; + break; + case HDCP_STATE_AUTH_ENC_2P2: + ret = dp_drv->hdcp.feature_enabled && + dp_drv->hdcp.hdcp2_present && + dp_drv->hdcp.auth_state; + break; + default: + ret = false; + } + + return ret; +} + static const struct of_device_id msm_mdss_dp_dt_match[] = { {.compatible = "qcom,mdss-dp"}, {} diff --git a/drivers/video/fbdev/msm/mdss_dp.h b/drivers/video/fbdev/msm/mdss_dp.h index f6af1f011f7b..beeb4d4b1a91 100644 --- a/drivers/video/fbdev/msm/mdss_dp.h +++ b/drivers/video/fbdev/msm/mdss_dp.h @@ -350,6 +350,21 @@ struct dp_pinctrl_res { irqreturn_t dp_isr(int irq, void *ptr); +struct dp_hdcp { + void *data; + struct hdcp_ops *ops; + + void *hdcp1; + void *hdcp2; + + int enc_lvl; + + bool auth_state; + bool hdcp1_present; + bool hdcp2_present; + bool feature_enabled; +}; + struct mdss_dp_drv_pdata { /* device driver */ int (*on) (struct mdss_panel_data *pdata); @@ -358,6 +373,7 @@ struct mdss_dp_drv_pdata { struct platform_device *ext_pdev; struct usbpd *pd; + struct dp_hdcp hdcp; struct usbpd_svid_handler svid_handler; struct dp_alt_mode alt_mode; bool dp_initialized; @@ -465,8 +481,6 @@ struct mdss_dp_drv_pdata { u32 new_vic; int fb_node; - void *hdcp_data; - struct hdcp_ops *hdcp_ops; struct dpcd_test_request test_data; struct dpcd_sink_count sink_count; }; @@ -569,5 +583,7 @@ void mdss_dp_config_ctrl(struct mdss_dp_drv_pdata *ep); char mdss_dp_gen_link_clk(struct mdss_panel_info *pinfo, char lane_cnt); int mdss_dp_aux_set_sink_power_state(struct mdss_dp_drv_pdata *ep, char state); void mdss_dp_aux_send_test_response(struct mdss_dp_drv_pdata *ep); +void *mdss_dp_get_hdcp_data(struct device *dev); +int mdss_dp_hdcp2p2_init(struct mdss_dp_drv_pdata *dp_drv); #endif /* MDSS_DP_H */ diff --git a/drivers/video/fbdev/msm/mdss_dp_aux.c b/drivers/video/fbdev/msm/mdss_dp_aux.c index a1668ea0d3f8..4d9a110cf6af 100644 --- a/drivers/video/fbdev/msm/mdss_dp_aux.c +++ b/drivers/video/fbdev/msm/mdss_dp_aux.c @@ -277,13 +277,15 @@ static int dp_aux_read_cmds(struct mdss_dp_drv_pdata *ep, wait_for_completion(&ep->aux_comp); - if (ep->aux_error_num == EDP_AUX_ERR_NONE) + if (ep->aux_error_num == EDP_AUX_ERR_NONE) { ret = dp_cmd_fifo_rx(rp, len, ep->base); - else - ret = ep->aux_error_num; - if (cmds->out_buf) - memcpy(cmds->out_buf, rp->data, cmds->len); + if (cmds->out_buf) + memcpy(cmds->out_buf, rp->data, cmds->len); + + } else { + ret = ep->aux_error_num; + } ep->aux_cmd_busy = 0; mutex_unlock(&ep->aux_mutex); @@ -1694,3 +1696,36 @@ void mdss_dp_aux_init(struct mdss_dp_drv_pdata *ep) dp_buf_init(&ep->txp, ep->txbuf, sizeof(ep->txbuf)); dp_buf_init(&ep->rxp, ep->rxbuf, sizeof(ep->rxbuf)); } + +int mdss_dp_aux_read_rx_status(struct mdss_dp_drv_pdata *dp, u8 *rx_status) +{ + bool cp_irq; + int rc = 0; + + if (!dp) { + pr_err("%s Invalid input\n", __func__); + return -EINVAL; + } + + *rx_status = 0; + + rc = dp_aux_read_buf(dp, DP_DPCD_CP_IRQ, 1, 0); + if (!rc) { + pr_err("Error reading CP_IRQ\n"); + return -EINVAL; + } + + cp_irq = *dp->rxp.data & BIT(2); + + if (cp_irq) { + rc = dp_aux_read_buf(dp, DP_DPCD_RXSTATUS, 1, 0); + if (!rc) { + pr_err("Error reading RxStatus\n"); + return -EINVAL; + } + + *rx_status = *dp->rxp.data; + } + + return 0; +} diff --git a/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c b/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c new file mode 100644 index 000000000000..3891806b09bb --- /dev/null +++ b/drivers/video/fbdev/msm/mdss_dp_hdcp2p2.c @@ -0,0 +1,861 @@ +/* Copyright (c) 2016, 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 +#include +#include +#include +#include +#include + +#include +#include "mdss_hdcp.h" +#include "mdss_dp_util.h" + +enum dp_hdcp2p2_sink_status { + SINK_DISCONNECTED, + SINK_CONNECTED +}; + +enum dp_auth_status { + DP_HDCP_AUTH_STATUS_FAILURE, + DP_HDCP_AUTH_STATUS_SUCCESS +}; + +struct dp_hdcp2p2_ctrl { + atomic_t auth_state; + enum dp_hdcp2p2_sink_status sink_status; /* Is sink connected */ + struct hdcp_init_data init_data; + 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 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 dp_auth_status auth_status; + + 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; + char *msg_buf; + uint32_t send_msg_len; /* length of all parameters in msg */ + uint32_t timeout; + uint32_t num_messages; + struct hdcp_msg_part msg_part[HDCP_MAX_MESSAGE_PARTS]; + u8 sink_rx_status; + u8 rx_status; + char abort_mask; + + bool cp_irq_done; + bool polling; +}; + +static inline char *dp_hdcp_cmd_to_str(uint32_t cmd) +{ + switch (cmd) { + case HDMI_HDCP_WKUP_CMD_SEND_MESSAGE: + return "HDMI_HDCP_WKUP_CMD_SEND_MESSAGE"; + case HDMI_HDCP_WKUP_CMD_RECV_MESSAGE: + return "HDMI_HDCP_WKUP_CMD_RECV_MESSAGE"; + case HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS: + return "HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS"; + case HDMI_HDCP_WKUP_CMD_STATUS_FAILED: + return "DP_HDCP_WKUP_CMD_STATUS_FAIL"; + case HDMI_HDCP_WKUP_CMD_LINK_POLL: + return "HDMI_HDCP_WKUP_CMD_LINK_POLL"; + case HDMI_HDCP_WKUP_CMD_AUTHENTICATE: + return "HDMI_HDCP_WKUP_CMD_AUTHENTICATE"; + default: + return "???"; + } +} + +static inline bool dp_hdcp2p2_is_valid_state(struct dp_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 dp_hdcp2p2_copy_buf(struct dp_hdcp2p2_ctrl *ctrl, + struct hdmi_hdcp_wakeup_data *data) +{ + int i = 0; + + if (!data || !data->message_data) + return 0; + + mutex_lock(&ctrl->msg_lock); + + ctrl->timeout = data->timeout; + ctrl->num_messages = data->message_data->num_messages; + ctrl->send_msg_len = 0; /* Total len of all messages */ + + for (i = 0; i < ctrl->num_messages ; i++) + ctrl->send_msg_len += data->message_data->messages[i].length; + + memcpy(ctrl->msg_part, data->message_data->messages, + sizeof(data->message_data->messages)); + + ctrl->rx_status = data->message_data->rx_status; + ctrl->abort_mask = data->abort_mask; + + if (!data->send_msg_len) { + mutex_unlock(&ctrl->msg_lock); + return 0; + } + + kzfree(ctrl->msg_buf); + + ctrl->msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + + if (!ctrl->msg_buf) { + mutex_unlock(&ctrl->msg_lock); + return -ENOMEM; + } + + /* ignore first byte as it contains message id */ + memcpy(ctrl->msg_buf, data->send_msg_buf + 1, ctrl->send_msg_len); + + mutex_unlock(&ctrl->msg_lock); + + return 0; +} + +static int dp_hdcp2p2_wakeup(struct hdmi_hdcp_wakeup_data *data) +{ + struct dp_hdcp2p2_ctrl *ctrl; + u32 const default_timeout_us = 500; + + if (!data) { + pr_err("invalid input\n"); + return -EINVAL; + } + + ctrl = data->context; + if (!ctrl) { + pr_err("invalid ctrl\n"); + return -EINVAL; + } + + mutex_lock(&ctrl->wakeup_mutex); + + ctrl->wakeup_cmd = data->cmd; + + if (data->timeout) + ctrl->timeout = (data->timeout) * 2; + else + ctrl->timeout = default_timeout_us; + + if (!dp_hdcp2p2_is_valid_state(ctrl)) { + pr_err("invalid state\n"); + goto exit; + } + + if (dp_hdcp2p2_copy_buf(ctrl, data)) + goto exit; + + if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_SUCCESS) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_SUCCESS; + else if (ctrl->wakeup_cmd == HDMI_HDCP_WKUP_CMD_STATUS_FAILED) + ctrl->auth_status = DP_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: + pr_err("invalid wakeup command %d\n", ctrl->wakeup_cmd); + } +exit: + mutex_unlock(&ctrl->wakeup_mutex); + return 0; +} + +static inline int dp_hdcp2p2_wakeup_lib(struct dp_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) + pr_err("error sending %s to lib\n", + hdcp_lib_cmd_to_str(data->cmd)); + } + + return rc; +} + +static void dp_hdcp2p2_reset(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + ctrl->sink_status = SINK_DISCONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); +} + +static void dp_hdcp2p2_off(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdmi_hdcp_wakeup_data cdata = {HDMI_HDCP_WKUP_CMD_AUTHENTICATE}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_hdcp2p2_reset(ctrl); + + flush_kthread_worker(&ctrl->worker); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); +} + +static int dp_hdcp2p2_authenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdmi_hdcp_wakeup_data cdata = {HDMI_HDCP_WKUP_CMD_AUTHENTICATE}; + int rc = 0; + + flush_kthread_worker(&ctrl->worker); + + ctrl->sink_status = SINK_CONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATING); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); + + return rc; +} + +static int dp_hdcp2p2_reauthenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_hdcp2p2_reset((struct dp_hdcp2p2_ctrl *)input); + + return dp_hdcp2p2_authenticate(input); +} + +static ssize_t dp_hdcp2p2_sysfs_wta_min_level_change(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dp_hdcp2p2_ctrl *ctrl = mdss_dp_get_hdcp_data(dev); + struct hdcp_lib_wakeup_data cdata = { + HDCP_LIB_WKUP_CMD_QUERY_STREAM_TYPE}; + bool enc_notify = true; + int enc_lvl; + int min_enc_lvl; + int rc; + + if (!ctrl) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto exit; + } + + rc = kstrtoint(buf, 10, &min_enc_lvl); + if (rc) { + pr_err("kstrtoint failed. rc=%d\n", rc); + goto exit; + } + + switch (min_enc_lvl) { + case 0: + enc_lvl = HDCP_STATE_AUTH_ENC_NONE; + break; + case 1: + enc_lvl = HDCP_STATE_AUTH_ENC_1X; + break; + case 2: + enc_lvl = HDCP_STATE_AUTH_ENC_2P2; + break; + default: + enc_notify = false; + } + + pr_debug("enc level changed %d\n", min_enc_lvl); + + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + if (enc_notify && ctrl->init_data.notify_status) + ctrl->init_data.notify_status(ctrl->init_data.cb_data, enc_lvl); + + rc = count; +exit: + return rc; +} + +static void dp_hdcp2p2_auth_failed(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + + /* notify DP about HDCP failure */ + ctrl->init_data.notify_status(ctrl->init_data.cb_data, + HDCP_STATE_AUTH_FAIL); +} + +static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, int offset, u32 timeout) +{ + int rc, max_size = 16, read_size, len = size; + u8 *buf_start = buf; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return -EINVAL; + } + + do { + struct edp_cmd cmd = {0}; + + read_size = min(size, max_size); + + cmd.read = 1; + cmd.addr = offset; + cmd.len = read_size; + cmd.out_buf = buf; + + rc = dp_aux_read(ctrl->init_data.cb_data, &cmd); + if (rc) { + pr_err("Aux read failed\n"); + break; + } + + buf += read_size; + offset += read_size; + size -= read_size; + } while (size > 0); + + print_hex_dump(KERN_DEBUG, "hdcp2p2: ", DUMP_PREFIX_NONE, + 16, 1, buf_start, len, false); + return rc; +} + +static int dp_hdcp2p2_aux_write_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, uint offset, uint timeout) +{ + int rc, max_size = 16, write_size; + + do { + struct edp_cmd cmd = {0}; + + write_size = min(size, max_size); + + cmd.read = 0; + cmd.addr = offset; + cmd.len = write_size; + cmd.datap = buf; + + rc = dp_aux_write(ctrl->init_data.cb_data, &cmd); + if (rc) { + pr_err("Aux write failed\n"); + break; + } + + buf += write_size; + offset += write_size; + size -= write_size; + } while (size > 0); + + return rc; +} + +static DEVICE_ATTR(min_level_change, S_IWUSR, NULL, + dp_hdcp2p2_sysfs_wta_min_level_change); + +static struct attribute *dp_hdcp2p2_fs_attrs[] = { + &dev_attr_min_level_change.attr, + NULL, +}; + +static struct attribute_group dp_hdcp2p2_fs_attr_group = { + .name = "dp_hdcp2p2", + .attrs = dp_hdcp2p2_fs_attrs, +}; + +static bool dp_hdcp2p2_feature_supported(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdcp_txmtr_ops *lib = NULL; + bool supported = false; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + lib = ctrl->lib; + if (!lib) { + pr_err("invalid lib ops data\n"); + goto end; + } + + if (lib->feature_supported) + supported = lib->feature_supported( + ctrl->lib_ctx); +end: + return supported; +} + +static void dp_hdcp2p2_send_msg_work(struct kthread_work *work) +{ + int rc = 0; + int i; + int sent_bytes = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, send_msg); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + char *buf = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto exit; + } + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + goto exit; + } + + mutex_lock(&ctrl->msg_lock); + + /* Loop through number of parameters in the messages. */ + for (i = 0; i < ctrl->num_messages; i++) { + buf = ctrl->msg_buf + sent_bytes; + + /* Forward the message to the sink */ + rc = dp_hdcp2p2_aux_write_message(ctrl, buf, + (size_t)ctrl->msg_part[i].length, + ctrl->msg_part[i].offset, ctrl->timeout); + if (rc) { + pr_err("Error sending msg to sink %d\n", rc); + mutex_unlock(&ctrl->msg_lock); + goto exit; + } + sent_bytes += ctrl->msg_part[i].length; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_SEND_SUCCESS; + cdata.timeout = ctrl->timeout; + mutex_unlock(&ctrl->msg_lock); + +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; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_get_msg_from_sink(struct dp_hdcp2p2_ctrl *ctrl) +{ + int i, rc = 0; + char *recvd_msg_buf = NULL; + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + int bytes_read = 0; + + cdata.context = ctrl->lib_ctx; + + recvd_msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + if (!recvd_msg_buf) { + rc = -ENOMEM; + goto exit; + } + + for (i = 0; i < ctrl->num_messages; i++) { + rc = dp_hdcp2p2_aux_read_message( + ctrl, recvd_msg_buf + bytes_read, + ctrl->msg_part[i].length, + ctrl->msg_part[i].offset, + ctrl->timeout); + if (rc) { + pr_err("error reading message %d\n", rc); + goto exit; + } + bytes_read += ctrl->msg_part[i].length; + } + + cdata.recvd_msg_buf = recvd_msg_buf; + cdata.recvd_msg_len = ctrl->send_msg_len; + cdata.timeout = ctrl->timeout; +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; + else + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_SUCCESS; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + kfree(recvd_msg_buf); + + return rc; +} + +static void dp_hdcp2p2_recv_msg_work(struct kthread_work *work) +{ + int rc = 0; + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, recv_msg); + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + goto exit; + } + + if (ctrl->sink_rx_status & ctrl->abort_mask) { + pr_err("reauth or Link fail triggered by sink\n"); + + ctrl->sink_rx_status = 0; + rc = -ENOLINK; + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + + goto exit; + } + + if (ctrl->rx_status && !ctrl->sink_rx_status) { + pr_debug("Recv msg for RxStatus, but no CP_IRQ yet\n"); + ctrl->polling = true; + goto exit; + } + + dp_hdcp2p2_get_msg_from_sink(ctrl); + + return; +exit: + if (rc) + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static void dp_hdcp2p2_poll_work(struct kthread_work *work) +{ + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, poll); + + if (ctrl->cp_irq_done) { + ctrl->cp_irq_done = false; + dp_hdcp2p2_get_msg_from_sink(ctrl); + } else { + ctrl->polling = true; + } +} + +static void dp_hdcp2p2_auth_status_work(struct kthread_work *work) +{ + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, status); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + if (ctrl->auth_status == DP_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 { + dp_hdcp2p2_auth_failed(ctrl); + } +} + +static void dp_hdcp2p2_link_work(struct kthread_work *work) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, link); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + cdata.context = ctrl->lib_ctx; + + ctrl->sink_rx_status = 0; + rc = mdss_dp_aux_read_rx_status(ctrl->init_data.cb_data, + &ctrl->sink_rx_status); + + if (rc) { + pr_err("failed to read rx status\n"); + + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + goto exit; + } + + if (ctrl->sink_rx_status & ctrl->abort_mask) { + pr_err("reauth or Link fail triggered by sink\n"); + + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + rc = -ENOLINK; + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + goto exit; + } + + /* if polling, get message from sink else let polling start */ + if (ctrl->polling && (ctrl->sink_rx_status & ctrl->rx_status)) { + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + rc = dp_hdcp2p2_get_msg_from_sink(ctrl); + + ctrl->polling = false; + } else { + ctrl->cp_irq_done = true; + } +exit: + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + if (rc) { + dp_hdcp2p2_auth_failed(ctrl); + return; + } +} + +static void dp_hdcp2p2_auth_work(struct kthread_work *work) +{ + int rc = 0; + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, auth); + + 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 = dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + if (rc) + dp_hdcp2p2_auth_failed(ctrl); +} + +static int dp_hdcp2p2_isr(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + queue_kthread_work(&ctrl->worker, &ctrl->link); + + return 0; +} + +void dp_hdcp2p2_deinit(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + kthread_stop(ctrl->thread); + + sysfs_remove_group(ctrl->init_data.sysfs_kobj, + &dp_hdcp2p2_fs_attr_group); + + mutex_destroy(&ctrl->mutex); + mutex_destroy(&ctrl->msg_lock); + mutex_destroy(&ctrl->wakeup_mutex); + kzfree(ctrl->msg_buf); + kfree(ctrl); +} + +void *dp_hdcp2p2_init(struct hdcp_init_data *init_data) +{ + int rc; + struct dp_hdcp2p2_ctrl *ctrl; + static struct hdcp_ops ops = { + .reauthenticate = dp_hdcp2p2_reauthenticate, + .authenticate = dp_hdcp2p2_authenticate, + .feature_supported = dp_hdcp2p2_feature_supported, + .off = dp_hdcp2p2_off, + .isr = dp_hdcp2p2_isr + }; + + static struct hdcp_client_ops client_ops = { + .wakeup = dp_hdcp2p2_wakeup, + }; + + static struct hdcp_txmtr_ops txmtr_ops; + struct hdcp_register_data register_data = {0}; + + if (!init_data || !init_data->cb_data || + !init_data->notify_status) { + pr_err("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return ERR_PTR(-ENOMEM); + + ctrl->init_data = *init_data; + ctrl->lib = &txmtr_ops; + ctrl->msg_buf = NULL; + rc = sysfs_create_group(init_data->sysfs_kobj, + &dp_hdcp2p2_fs_attr_group); + if (rc) { + pr_err("dp_hdcp2p2 sysfs group creation failed\n"); + goto error; + } + + ctrl->sink_status = SINK_DISCONNECTED; + + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); + + ctrl->ops = &ops; + mutex_init(&ctrl->mutex); + mutex_init(&ctrl->msg_lock); + mutex_init(&ctrl->wakeup_mutex); + + register_data.hdcp_ctx = &ctrl->lib_ctx; + register_data.client_ops = &client_ops; + register_data.txmtr_ops = &txmtr_ops; + register_data.device_type = HDCP_TXMTR_DP; + register_data.client_ctx = ctrl; + + rc = hdcp_library_register(®ister_data); + if (rc) { + pr_err("Unable to register with HDCP 2.2 library\n"); + goto error; + } + + init_kthread_worker(&ctrl->worker); + + init_kthread_work(&ctrl->auth, dp_hdcp2p2_auth_work); + init_kthread_work(&ctrl->send_msg, dp_hdcp2p2_send_msg_work); + init_kthread_work(&ctrl->recv_msg, dp_hdcp2p2_recv_msg_work); + init_kthread_work(&ctrl->status, dp_hdcp2p2_auth_status_work); + init_kthread_work(&ctrl->link, dp_hdcp2p2_link_work); + init_kthread_work(&ctrl->poll, dp_hdcp2p2_poll_work); + + ctrl->thread = kthread_run(kthread_worker_fn, + &ctrl->worker, "dp_hdcp2p2"); + + if (IS_ERR(ctrl->thread)) { + pr_err("unable to start DP hdcp2p2 thread\n"); + rc = PTR_ERR(ctrl->thread); + ctrl->thread = NULL; + goto error; + } + + return ctrl; +error: + kfree(ctrl); + return ERR_PTR(rc); +} + +static bool dp_hdcp2p2_supported(struct dp_hdcp2p2_ctrl *ctrl) +{ + struct edp_cmd cmd = {0}; + const u32 offset = 0x6921d; + u8 buf; + + cmd.read = 1; + cmd.addr = offset; + cmd.len = sizeof(buf); + cmd.out_buf = &buf; + + if (dp_aux_read(ctrl->init_data.cb_data, &cmd)) { + pr_err("RxCaps read failed\n"); + goto error; + } + + pr_debug("rxcaps 0x%x\n", buf); + + if (buf & BIT(1)) + return true; +error: + return false; +} + +struct hdcp_ops *dp_hdcp2p2_start(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + + pr_debug("Checking sink capability\n"); + if (dp_hdcp2p2_supported(ctrl)) + return ctrl->ops; + else + return NULL; +} + diff --git a/drivers/video/fbdev/msm/mdss_dp_util.h b/drivers/video/fbdev/msm/mdss_dp_util.h index cf2286f9b58a..334c0071050d 100644 --- a/drivers/video/fbdev/msm/mdss_dp_util.h +++ b/drivers/video/fbdev/msm/mdss_dp_util.h @@ -37,6 +37,9 @@ #define DP_AUX_TRANS_CTRL (0x00000238) #define DP_AUX_STATUS (0x00000244) +#define DP_DPCD_CP_IRQ (0x201) +#define DP_DPCD_RXSTATUS (0x69493) + #define DP_INTERRUPT_TRANS_NUM (0x000002A0) #define DP_MAINLINK_CTRL (0x00000400) @@ -299,5 +302,6 @@ void mdss_dp_audio_set_sample_rate(struct dss_io_data *ctrl_io, char dp_link_rate, uint32_t audio_freq); void mdss_dp_set_safe_to_exit_level(struct dss_io_data *ctrl_io, uint32_t lane_cnt); +int mdss_dp_aux_read_rx_status(struct mdss_dp_drv_pdata *dp, u8 *rx_status); #endif /* __DP_UTIL_H__ */ diff --git a/drivers/video/fbdev/msm/mdss_hdcp_1x.h b/drivers/video/fbdev/msm/mdss_hdcp.h similarity index 90% rename from drivers/video/fbdev/msm/mdss_hdcp_1x.h rename to drivers/video/fbdev/msm/mdss_hdcp.h index 426b13a340f4..d373d22384e8 100644 --- a/drivers/video/fbdev/msm/mdss_hdcp_1x.h +++ b/drivers/video/fbdev/msm/mdss_hdcp.h @@ -14,6 +14,7 @@ #define __MDSS_HDMI_HDCP_H__ #include "mdss_hdmi_util.h" +#include "mdss_dp.h" #include