diff options
| author | Linux Build Service Account <lnxbuild@quicinc.com> | 2017-06-15 22:59:09 -0700 |
|---|---|---|
| committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2017-06-15 22:59:09 -0700 |
| commit | 16a6dc2a061446e4feac988d5fed128677e58c21 (patch) | |
| tree | 1bfa4822a7a5ee4d31cd2db75bccded97b0245ce /drivers/gpu | |
| parent | 92e373a732709efc5b633752d7fe17cb79690f50 (diff) | |
| parent | 02b2b76fb0d3eeab8bfe7fb43f8413c8ccb4690d (diff) | |
Merge "drm/msm: add HDCP 1x module for MSM DRM driver"
Diffstat (limited to 'drivers/gpu')
| -rw-r--r-- | drivers/gpu/drm/msm/Makefile | 5 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c | 295 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h | 86 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c | 34 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c | 41 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c | 313 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h | 63 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi.h | 30 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c | 78 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi_i2c.c | 81 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi_util.c | 143 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde_hdcp.h | 83 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde_hdcp_1x.c | 1721 |
13 files changed, 2687 insertions, 286 deletions
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index c916626c8329..a0ac535d50d1 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -13,6 +13,7 @@ msm_drm-y := \ hdmi/hdmi_connector.o \ hdmi/hdmi_hdcp.o \ hdmi/hdmi_i2c.o \ + hdmi/hdmi_util.o \ hdmi/hdmi_phy_8960.o \ hdmi/hdmi_phy_8x60.o \ hdmi/hdmi_phy_8x74.o \ @@ -50,7 +51,8 @@ msm_drm-y := \ sde_dbg_evtlog.o \ sde_io_util.o \ dba_bridge.o \ - sde_edid_parser.o + sde_edid_parser.o \ + sde_hdcp_1x.o # use drm gpu driver only if qcom_kgsl driver not available ifneq ($(CONFIG_QCOM_KGSL),y) @@ -101,6 +103,7 @@ msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \ dsi-staging/dsi_display_test.o msm_drm-$(CONFIG_DRM_SDE_HDMI) += \ + hdmi-staging/sde_hdmi_util.o \ hdmi-staging/sde_hdmi.o \ hdmi-staging/sde_hdmi_bridge.o \ hdmi-staging/sde_hdmi_audio.o \ diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c index 0b5c7c889219..443e0111516c 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c @@ -30,6 +30,7 @@ #include "msm_drv.h" #include "sde_hdmi.h" #include "sde_hdmi_regs.h" +#include "hdmi.h" static DEFINE_MUTEX(sde_hdmi_list_lock); static LIST_HEAD(sde_hdmi_list); @@ -426,6 +427,118 @@ static u64 _sde_hdmi_clip_valid_pclk(struct drm_display_mode *mode, u64 pclk_in) return pclk_clip; } +static void sde_hdmi_tx_hdcp_cb(void *ptr, enum sde_hdcp_states status) +{ + struct sde_hdmi *hdmi_ctrl = (struct sde_hdmi *)ptr; + struct hdmi *hdmi; + + if (!hdmi_ctrl) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + hdmi = hdmi_ctrl->ctrl.ctrl; + hdmi_ctrl->hdcp_status = status; + queue_delayed_work(hdmi->workq, &hdmi_ctrl->hdcp_cb_work, HZ/4); +} + +void sde_hdmi_hdcp_off(struct sde_hdmi *hdmi_ctrl) +{ + + if (!hdmi_ctrl) { + SDE_ERROR("%s: invalid input\n", __func__); + return; + } + + if (hdmi_ctrl->hdcp_ops) + hdmi_ctrl->hdcp_ops->off(hdmi_ctrl->hdcp_data); + + flush_delayed_work(&hdmi_ctrl->hdcp_cb_work); + + hdmi_ctrl->hdcp_ops = NULL; +} + +static void sde_hdmi_tx_hdcp_cb_work(struct work_struct *work) +{ + struct sde_hdmi *hdmi_ctrl = NULL; + struct delayed_work *dw = to_delayed_work(work); + int rc = 0; + struct hdmi *hdmi; + + hdmi_ctrl = container_of(dw, struct sde_hdmi, hdcp_cb_work); + if (!hdmi_ctrl) { + DEV_DBG("%s: invalid input\n", __func__); + return; + } + + hdmi = hdmi_ctrl->ctrl.ctrl; + + switch (hdmi_ctrl->hdcp_status) { + case HDCP_STATE_AUTHENTICATED: + hdmi_ctrl->auth_state = true; + + if (sde_hdmi_tx_is_panel_on(hdmi_ctrl) && + sde_hdmi_tx_is_stream_shareable(hdmi_ctrl)) { + rc = sde_hdmi_config_avmute(hdmi, false); + } + + if (hdmi_ctrl->hdcp1_use_sw_keys && + hdmi_ctrl->hdcp14_present) { + if (!hdmi_ctrl->hdcp22_present) + hdcp1_set_enc(true); + } + break; + case HDCP_STATE_AUTH_FAIL: + if (hdmi_ctrl->hdcp1_use_sw_keys && hdmi_ctrl->hdcp14_present) { + if (hdmi_ctrl->auth_state && !hdmi_ctrl->hdcp22_present) + hdcp1_set_enc(false); + } + + hdmi_ctrl->auth_state = false; + + if (sde_hdmi_tx_is_encryption_set(hdmi_ctrl) || + !sde_hdmi_tx_is_stream_shareable(hdmi_ctrl)) + rc = sde_hdmi_config_avmute(hdmi, true); + + if (sde_hdmi_tx_is_panel_on(hdmi_ctrl)) { + pr_debug("%s: Reauthenticating\n", __func__); + if (hdmi_ctrl->hdcp_ops && hdmi_ctrl->hdcp_data) { + rc = hdmi_ctrl->hdcp_ops->reauthenticate( + hdmi_ctrl->hdcp_data); + if (rc) + pr_err("%s: HDCP reauth failed. rc=%d\n", + __func__, rc); + } else + pr_err("%s: NULL HDCP Ops and Data\n", + __func__); + } else { + pr_debug("%s: Not reauthenticating. Cable not conn\n", + __func__); + } + + break; + case HDCP_STATE_AUTH_ENC_NONE: + hdmi_ctrl->enc_lvl = HDCP_STATE_AUTH_ENC_NONE; + if (sde_hdmi_tx_is_panel_on(hdmi_ctrl)) + rc = sde_hdmi_config_avmute(hdmi, false); + break; + case HDCP_STATE_AUTH_ENC_1X: + case HDCP_STATE_AUTH_ENC_2P2: + hdmi_ctrl->enc_lvl = hdmi_ctrl->hdcp_status; + + if (sde_hdmi_tx_is_panel_on(hdmi_ctrl) && + sde_hdmi_tx_is_stream_shareable(hdmi_ctrl)) { + rc = sde_hdmi_config_avmute(hdmi, false); + } else { + rc = sde_hdmi_config_avmute(hdmi, true); + } + break; + default: + break; + /* do nothing */ + } +} + /** * _sde_hdmi_update_pll_delta() - Update the HDMI pixel clock as per input ppm * @@ -920,6 +1033,12 @@ static void _sde_hdmi_cec_update_phys_addr(struct sde_hdmi *display) else cec_notifier_set_phys_addr(display->notifier, CEC_PHYS_ADDR_INVALID); + +} + +static void _sde_hdmi_init_ddc(struct sde_hdmi *display, struct hdmi *hdmi) +{ + display->ddc_ctrl.io = &display->io[HDMI_TX_CORE_IO]; } static void _sde_hdmi_map_regs(struct sde_hdmi *display, struct hdmi *hdmi) @@ -1023,8 +1142,14 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id) hdmi_i2c_irq(hdmi->i2c); /* Process HDCP: */ - if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) - hdmi_hdcp_ctrl_irq(hdmi->hdcp_ctrl); + 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)) + DEV_ERR("%s: hdcp_1x_isr failed\n", + __func__); + } + } /* Process CEC: */ _sde_hdmi_cec_irq(sde_hdmi); @@ -1203,84 +1328,8 @@ void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on) power_on ? "Enable" : "Disable", ctrl); } -int sde_hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len) -{ - int rc; - int retry = 5; - struct i2c_msg msgs[] = { - { - .addr = addr >> 1, - .flags = 0, - .len = 1, - .buf = &offset, - }, { - .addr = addr >> 1, - .flags = I2C_M_RD, - .len = data_len, - .buf = data, - } - }; - - SDE_HDMI_DEBUG("Start DDC read"); - retry: - rc = i2c_transfer(hdmi->i2c, msgs, 2); - - retry--; - if (rc == 2) - rc = 0; - else if (retry > 0) - goto retry; - else - rc = -EIO; - - SDE_HDMI_DEBUG("End DDC read %d", rc); - - return rc; -} - #define DDC_WRITE_MAX_BYTE_NUM 32 -int sde_hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len) -{ - int rc; - int retry = 10; - u8 buf[DDC_WRITE_MAX_BYTE_NUM]; - struct i2c_msg msgs[] = { - { - .addr = addr >> 1, - .flags = 0, - .len = 1, - } - }; - - SDE_HDMI_DEBUG("Start DDC write"); - if (data_len > (DDC_WRITE_MAX_BYTE_NUM - 1)) { - SDE_ERROR("%s: write size too big\n", __func__); - return -ERANGE; - } - - buf[0] = offset; - memcpy(&buf[1], data, data_len); - msgs[0].buf = buf; - msgs[0].len = data_len + 1; - retry: - rc = i2c_transfer(hdmi->i2c, msgs, 1); - - retry--; - if (rc == 1) - rc = 0; - else if (retry > 0) - goto retry; - else - rc = -EIO; - - SDE_HDMI_DEBUG("End DDC write %d", rc); - - return rc; -} - int sde_hdmi_scdc_read(struct hdmi *hdmi, u32 data_type, u32 *val) { int rc = 0; @@ -1337,7 +1386,8 @@ int sde_hdmi_scdc_read(struct hdmi *hdmi, u32 data_type, u32 *val) break; } - rc = sde_hdmi_ddc_read(hdmi, dev_addr, offset, data_buf, data_len); + rc = hdmi_ddc_read(hdmi, dev_addr, offset, data_buf, + data_len, true); if (rc) { SDE_ERROR("DDC Read failed for %d\n", data_type); return rc; @@ -1409,8 +1459,8 @@ int sde_hdmi_scdc_write(struct hdmi *hdmi, u32 data_type, u32 val) dev_addr = 0xA8; data_len = 1; offset = HDMI_SCDC_TMDS_CONFIG; - rc = sde_hdmi_ddc_read(hdmi, dev_addr, offset, &read_val, - data_len); + rc = hdmi_ddc_read(hdmi, dev_addr, offset, &read_val, + data_len, true); if (rc) { SDE_ERROR("scdc read failed\n"); return rc; @@ -1434,7 +1484,8 @@ int sde_hdmi_scdc_write(struct hdmi *hdmi, u32 data_type, u32 val) return -EINVAL; } - rc = sde_hdmi_ddc_write(hdmi, dev_addr, offset, data_buf, data_len); + rc = hdmi_ddc_write(hdmi, dev_addr, offset, data_buf, + data_len, true); if (rc) { SDE_ERROR("DDC Read failed for %d\n", data_type); return rc; @@ -1631,6 +1682,50 @@ static int sde_hdmi_tx_check_capability(struct sde_hdmi *sde_hdmi) return ret; } /* hdmi_tx_check_capability */ +static int _sde_hdmi_init_hdcp(struct sde_hdmi *hdmi_ctrl) +{ + struct sde_hdcp_init_data hdcp_init_data; + void *hdcp_data; + int rc = 0; + struct hdmi *hdmi; + + if (!hdmi_ctrl) { + SDE_ERROR("sde_hdmi is NULL\n"); + return -EINVAL; + } + + hdmi = hdmi_ctrl->ctrl.ctrl; + hdcp_init_data.phy_addr = hdmi->mmio_phy_addr; + hdcp_init_data.core_io = &hdmi_ctrl->io[HDMI_TX_CORE_IO]; + hdcp_init_data.qfprom_io = &hdmi_ctrl->io[HDMI_TX_QFPROM_IO]; + hdcp_init_data.hdcp_io = &hdmi_ctrl->io[HDMI_TX_HDCP_IO]; + hdcp_init_data.mutex = &hdmi_ctrl->hdcp_mutex; + hdcp_init_data.workq = hdmi->workq; + hdcp_init_data.notify_status = sde_hdmi_tx_hdcp_cb; + hdcp_init_data.cb_data = (void *)hdmi_ctrl; + hdcp_init_data.hdmi_tx_ver = hdmi_ctrl->hdmi_tx_major_version; + hdcp_init_data.sec_access = true; + hdcp_init_data.client_id = HDCP_CLIENT_HDMI; + hdcp_init_data.ddc_ctrl = &hdmi_ctrl->ddc_ctrl; + + if (hdmi_ctrl->hdcp14_present) { + hdcp_data = sde_hdcp_1x_init(&hdcp_init_data); + + if (IS_ERR_OR_NULL(hdcp_data)) { + DEV_ERR("%s: hdcp 1.4 init failed\n", __func__); + rc = -EINVAL; + kfree(hdcp_data); + goto end; + } else { + hdmi_ctrl->hdcp_feature_data[SDE_HDCP_1x] = hdcp_data; + SDE_HDMI_DEBUG("%s: HDCP 1.4 initialized\n", __func__); + } + } + +end: + return rc; +} + int sde_hdmi_connector_post_init(struct drm_connector *connector, void *info, void *display) @@ -1664,7 +1759,36 @@ int sde_hdmi_connector_post_init(struct drm_connector *connector, SDE_ERROR("failed to enable HPD: %d\n", rc); _sde_hdmi_get_tx_version(sde_hdmi); + sde_hdmi_tx_check_capability(sde_hdmi); + + _sde_hdmi_init_hdcp(sde_hdmi); + + return rc; +} + +int sde_hdmi_start_hdcp(struct drm_connector *connector) +{ + int rc; + struct sde_connector *c_conn = to_sde_connector(connector); + struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display; + struct hdmi *hdmi = display->ctrl.ctrl; + + if (!hdmi) { + SDE_ERROR("%s: invalid input\n", __func__); + return -EINVAL; + } + + if (!sde_hdmi_tx_is_hdcp_enabled(display)) + return 0; + + if (sde_hdmi_tx_is_encryption_set(display)) + sde_hdmi_config_avmute(hdmi, true); + + rc = display->hdcp_ops->authenticate(display->hdcp_data); + if (rc) + SDE_ERROR("%s: hdcp auth failed. rc=%d\n", __func__, rc); + return rc; } @@ -1781,6 +1905,9 @@ 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]); + return 0; } @@ -1864,7 +1991,11 @@ static int sde_hdmi_bind(struct device *dev, struct device *master, void *data) display->drm_dev = drm; _sde_hdmi_map_regs(display, priv->hdmi); + _sde_hdmi_init_ddc(display, priv->hdmi); + INIT_DELAYED_WORK(&display->hdcp_cb_work, + sde_hdmi_tx_hdcp_cb_work); + mutex_init(&display->hdcp_mutex); mutex_unlock(&display->display_lock); return rc; diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h index 38ab1da08196..dd61fa794526 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h @@ -20,6 +20,7 @@ #include <linux/debugfs.h> #include <linux/of_device.h> #include <linux/msm_ext_display.h> +#include <linux/hdcp_qseecom.h> #include <drm/drmP.h> #include <drm/drm_crtc.h> @@ -29,6 +30,8 @@ #include "sde_connector.h" #include "msm_drv.h" #include "sde_edid_parser.h" +#include "sde_hdmi_util.h" +#include "sde_hdcp.h" #ifdef HDMI_DEBUG_ENABLE #define SDE_HDMI_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args) @@ -82,6 +85,11 @@ enum hdmi_tx_io_type { HDMI_TX_MAX_IO }; +enum hdmi_tx_feature_type { + SDE_HDCP_1x, + SDE_HDCP_2P2 +}; + /** * struct sde_hdmi - hdmi display information * @pdev: Pointer to platform device. @@ -112,7 +120,7 @@ struct sde_hdmi { const char *display_type; struct list_head list; struct mutex display_lock; - + struct mutex hdcp_mutex; struct sde_hdmi_ctrl ctrl; struct platform_device *ext_pdev; @@ -130,6 +138,18 @@ struct sde_hdmi { u32 max_pclk_khz; bool hdcp1_use_sw_keys; u32 hdcp14_present; + u32 hdcp22_present; + u8 hdcp_status; + u32 enc_lvl; + bool auth_state; + /*hold final data + *based on hdcp support + */ + void *hdcp_data; + /*hold hdcp init data*/ + void *hdcp_feature_data[2]; + struct sde_hdcp_ops *hdcp_ops; + struct sde_hdmi_tx_ddc_ctrl ddc_ctrl; struct work_struct hpd_work; bool codec_ready; bool client_notify_pending; @@ -137,6 +157,7 @@ struct sde_hdmi { struct irq_domain *irq_domain; struct cec_notifier *notifier; + struct delayed_work hdcp_cb_work; struct dss_io_data io[HDMI_TX_MAX_IO]; /* DEBUG FS */ struct dentry *root; @@ -338,32 +359,6 @@ struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi); void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on); /** - * sde_hdmi_ddc_read() - common hdmi ddc read API. - * @hdmi: Handle to the hdmi. - * @addr: Command address. - * @offset: Command offset. - * @data: Data buffer for read back. - * @data_len: Data buffer length. - * - * Return: error code. - */ -int sde_hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len); - -/** - * sde_hdmi_ddc_write() - common hdmi ddc write API. - * @hdmi: Handle to the hdmi. - * @addr: Command address. - * @offset: Command offset. - * @data: Data buffer for write. - * @data_len: Data buffer length. - * - * Return: error code. - */ -int sde_hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len); - -/** * sde_hdmi_scdc_read() - hdmi 2.0 ddc read API. * @hdmi: Handle to the hdmi. * @data_type: DDC data type, refer to enum hdmi_tx_scdc_access_type. @@ -429,6 +424,13 @@ void sde_hdmi_notify_clients(struct sde_hdmi *display, bool connected); void sde_hdmi_ack_state(struct drm_connector *connector, enum drm_connector_status status); +bool sde_hdmi_tx_is_hdcp_enabled(struct sde_hdmi *hdmi_ctrl); +bool sde_hdmi_tx_is_encryption_set(struct sde_hdmi *hdmi_ctrl); +bool sde_hdmi_tx_is_stream_shareable(struct sde_hdmi *hdmi_ctrl); +bool sde_hdmi_tx_is_panel_on(struct sde_hdmi *hdmi_ctrl); +int sde_hdmi_start_hdcp(struct drm_connector *connector); +void sde_hdmi_hdcp_off(struct sde_hdmi *hdmi_ctrl); + #else /*#ifdef CONFIG_DRM_SDE_HDMI*/ static inline u32 sde_hdmi_get_num_of_displays(void) @@ -487,12 +489,42 @@ static inline int sde_hdmi_dev_deinit(struct sde_hdmi *display) return 0; } +bool hdmi_tx_is_hdcp_enabled(struct sde_hdmi *hdmi_ctrl) +{ + return false; +} + +bool sde_hdmi_tx_is_encryption_set(struct sde_hdmi *hdmi_ctrl) +{ + return false; +} + +bool sde_hdmi_tx_is_stream_shareable(struct sde_hdmi *hdmi_ctrl) +{ + return false; +} + +bool sde_hdmi_tx_is_panel_on(struct sde_hdmi *hdmi_ctrl) +{ + return false; +} + static inline int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc) { return 0; } +int sde_hdmi_start_hdcp(struct drm_connector *connector) +{ + return 0; +} + +void sde_hdmi_hdcp_off(struct sde_hdmi *hdmi_ctrl) +{ + +} + static inline int sde_hdmi_drm_deinit(struct sde_hdmi *display) { return 0; diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c index 48a3a9316a41..d6213dc0a4aa 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c @@ -355,37 +355,3 @@ void sde_hdmi_audio_off(struct hdmi *hdmi) SDE_DEBUG("HDMI Audio: Disabled\n"); } -int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set) -{ - u32 av_mute_status; - bool av_pkt_en = false; - - if (!hdmi) { - SDE_ERROR("invalid HDMI Ctrl\n"); - return -ENODEV; - } - - av_mute_status = hdmi_read(hdmi, HDMI_GC); - - if (set) { - if (!(av_mute_status & BIT(0))) { - hdmi_write(hdmi, HDMI_GC, av_mute_status | BIT(0)); - av_pkt_en = true; - } - } else { - if (av_mute_status & BIT(0)) { - hdmi_write(hdmi, HDMI_GC, av_mute_status & ~BIT(0)); - av_pkt_en = true; - } - } - - /* Enable AV Mute tranmission here */ - if (av_pkt_en) - hdmi_write(hdmi, HDMI_VBI_PKT_CTRL, - hdmi_read(hdmi, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5))); - - SDE_DEBUG("AVMUTE %s\n", set ? "set" : "cleared"); - - return 0; -} - 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 26a0638f7792..a9673d152cb7 100644 --- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c @@ -396,8 +396,45 @@ static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge) mutex_unlock(&display->display_lock); } +static void sde_hdmi_update_hdcp_info(struct drm_connector *connector) +{ + void *fd = NULL; + struct sde_hdcp_ops *ops = NULL; + struct sde_connector *c_conn = to_sde_connector(connector); + struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display; + + if (!display) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + 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]; + if (fd) + ops = sde_hdcp_1x_start(fd); + } + } + + /* update internal data about hdcp */ + display->hdcp_data = fd; + display->hdcp_ops = ops; +} + static void _sde_hdmi_bridge_enable(struct drm_bridge *bridge) { + struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge); + struct hdmi *hdmi = sde_hdmi_bridge->hdmi; + + /* need to update hdcp info here to ensure right HDCP support*/ + sde_hdmi_update_hdcp_info(hdmi->connector); + + /* start HDCP authentication */ + sde_hdmi_start_hdcp(hdmi->connector); } static void _sde_hdmi_bridge_disable(struct drm_bridge *bridge) @@ -414,8 +451,8 @@ static void _sde_hdmi_bridge_post_disable(struct drm_bridge *bridge) sde_hdmi_notify_clients(display, display->connected); - if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported) - hdmi_hdcp_ctrl_off(hdmi->hdcp_ctrl); + if (sde_hdmi_tx_is_hdcp_enabled(display)) + sde_hdmi_hdcp_off(display); sde_hdmi_audio_off(hdmi); diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c new file mode 100644 index 000000000000..10fc89545440 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c @@ -0,0 +1,313 @@ +/* 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. + */ + +#include <linux/slab.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/iopoll.h> +#include <linux/types.h> +#include <linux/switch.h> +#include <linux/gcd.h> + +#include "drm_edid.h" +#include "sde_kms.h" +#include "sde_hdmi.h" +#include "sde_hdmi_regs.h" +#include "hdmi.h" + +static int sde_hdmi_ddc_read_retry(struct sde_hdmi *display) +{ + int status; + int busy_wait_us; + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + struct sde_hdmi_tx_ddc_data *ddc_data; + struct hdmi *hdmi; + + if (!display) { + SDE_ERROR("invalid input\n"); + return -EINVAL; + } + + hdmi = display->ctrl.ctrl; + ddc_ctrl = &display->ddc_ctrl; + ddc_data = &ddc_ctrl->ddc_data; + + if (!ddc_data) { + SDE_ERROR("invalid input\n"); + return -EINVAL; + } + + if (!ddc_data->data_buf) { + status = -EINVAL; + SDE_ERROR("%s: invalid buf\n", ddc_data->what); + goto error; + } + + if (ddc_data->retry < 0) { + SDE_ERROR("invalid no. of retries %d\n", ddc_data->retry); + status = -EINVAL; + goto error; + } + + do { + if (ddc_data->hard_timeout) { + HDMI_UTIL_DEBUG("using hard_timeout %dms\n", + ddc_data->hard_timeout); + + busy_wait_us = ddc_data->hard_timeout * HDMI_MS_TO_US; + hdmi->use_hard_timeout = true; + hdmi->busy_wait_us = busy_wait_us; + } + + /* Calling upstream ddc read method */ + status = hdmi_ddc_read(hdmi, ddc_data->dev_addr, + ddc_data->offset, + ddc_data->data_buf, ddc_data->request_len, + false); + + if (ddc_data->hard_timeout) + ddc_data->timeout_left = hdmi->timeout_count; + + + if (ddc_data->hard_timeout && !hdmi->timeout_count) { + HDMI_UTIL_DEBUG("%s: timedout\n", ddc_data->what); + status = -ETIMEDOUT; + } + + } while (status && ddc_data->retry--); + + if (status) { + HDMI_UTIL_ERROR("%s: failed status = %d\n", + ddc_data->what, status); + goto error; + } + + HDMI_UTIL_DEBUG("%s: success\n", ddc_data->what); + +error: + return status; +} /* sde_hdmi_ddc_read_retry */ + +int sde_hdmi_ddc_read(void *cb_data) +{ + int rc = 0; + int retry; + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + struct sde_hdmi_tx_ddc_data *ddc_data; + struct sde_hdmi *display = (struct sde_hdmi *)cb_data; + + if (!display) { + SDE_ERROR("invalid ddc ctrl\n"); + return -EINVAL; + } + + ddc_ctrl = &display->ddc_ctrl; + ddc_data = &ddc_ctrl->ddc_data; + retry = ddc_data->retry; + + rc = sde_hdmi_ddc_read_retry(display); + if (!rc) + return rc; + + if (ddc_data->retry_align) { + ddc_data->retry = retry; + + ddc_data->request_len = 32 * ((ddc_data->data_len + 31) / 32); + rc = sde_hdmi_ddc_read_retry(display); + } + + return rc; +} /* hdmi_ddc_read */ + +int sde_hdmi_ddc_write(void *cb_data) +{ + int status; + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + struct sde_hdmi_tx_ddc_data *ddc_data; + int busy_wait_us; + struct hdmi *hdmi; + struct sde_hdmi *display = (struct sde_hdmi *)cb_data; + + if (!display) { + SDE_ERROR("invalid input\n"); + return -EINVAL; + } + + hdmi = display->ctrl.ctrl; + ddc_ctrl = &display->ddc_ctrl; + + ddc_data = &ddc_ctrl->ddc_data; + + if (!ddc_data) { + SDE_ERROR("invalid input\n"); + return -EINVAL; + } + + if (!ddc_data->data_buf) { + status = -EINVAL; + SDE_ERROR("%s: invalid buf\n", ddc_data->what); + goto error; + } + + if (ddc_data->retry < 0) { + SDE_ERROR("invalid no. of retries %d\n", ddc_data->retry); + status = -EINVAL; + goto error; + } + + do { + if (ddc_data->hard_timeout) { + busy_wait_us = ddc_data->hard_timeout * HDMI_MS_TO_US; + hdmi->use_hard_timeout = true; + hdmi->busy_wait_us = busy_wait_us; + } + + status = hdmi_ddc_write(hdmi, + ddc_data->dev_addr, ddc_data->offset, + ddc_data->data_buf, ddc_data->data_len, + false); + + if (ddc_data->hard_timeout) + ddc_data->timeout_left = hdmi->timeout_count; + + if (ddc_data->hard_timeout && !hdmi->timeout_count) { + HDMI_UTIL_ERROR("%s timout\n", ddc_data->what); + status = -ETIMEDOUT; + } + + } while (status && ddc_data->retry--); + + if (status) { + HDMI_UTIL_ERROR("%s: failed status = %d\n", + ddc_data->what, status); + goto error; + } + + HDMI_UTIL_DEBUG("%s: success\n", ddc_data->what); +error: + return status; +} /* hdmi_ddc_write */ + +bool sde_hdmi_tx_is_hdcp_enabled(struct sde_hdmi *hdmi_ctrl) +{ + if (!hdmi_ctrl) { + SDE_ERROR("%s: invalid input\n", __func__); + return false; + } + + return (hdmi_ctrl->hdcp14_present || hdmi_ctrl->hdcp22_present) && + hdmi_ctrl->hdcp_ops; +} + +bool sde_hdmi_tx_is_encryption_set(struct sde_hdmi *hdmi_ctrl) +{ + bool enc_en = true; + u32 reg_val; + struct hdmi *hdmi; + + if (!hdmi_ctrl) { + SDE_ERROR("%s: invalid input\n", __func__); + goto end; + } + + hdmi = hdmi_ctrl->ctrl.ctrl; + + reg_val = hdmi_read(hdmi, HDMI_HDCP_CTRL2); + if ((reg_val & BIT(0)) && (reg_val & BIT(1))) + goto end; + + if (hdmi_read(hdmi, HDMI_CTRL) & BIT(2)) + goto end; + + return false; + +end: + return enc_en; +} /* sde_hdmi_tx_is_encryption_set */ + +bool sde_hdmi_tx_is_stream_shareable(struct sde_hdmi *hdmi_ctrl) +{ + bool ret; + + if (!hdmi_ctrl) { + SDE_ERROR("%s: invalid input\n", __func__); + return false; + } + + switch (hdmi_ctrl->enc_lvl) { + case HDCP_STATE_AUTH_ENC_NONE: + ret = true; + break; + case HDCP_STATE_AUTH_ENC_1X: + ret = sde_hdmi_tx_is_hdcp_enabled(hdmi_ctrl) && + hdmi_ctrl->auth_state; + break; + case HDCP_STATE_AUTH_ENC_2P2: + ret = hdmi_ctrl->hdcp22_present && + hdmi_ctrl->auth_state; + break; + default: + ret = false; + } + + return ret; +} + +bool sde_hdmi_tx_is_panel_on(struct sde_hdmi *hdmi_ctrl) +{ + struct hdmi *hdmi; + + if (!hdmi_ctrl) { + SDE_ERROR("%s: invalid input\n", __func__); + return false; + } + + hdmi = hdmi_ctrl->ctrl.ctrl; + + return hdmi_ctrl->connected && hdmi->power_on; +} + +int sde_hdmi_config_avmute(struct hdmi *hdmi, bool set) +{ + u32 av_mute_status; + bool av_pkt_en = false; + + if (!hdmi) { + SDE_ERROR("invalid HDMI Ctrl\n"); + return -ENODEV; + } + + av_mute_status = hdmi_read(hdmi, HDMI_GC); + + if (set) { + if (!(av_mute_status & BIT(0))) { + hdmi_write(hdmi, HDMI_GC, av_mute_status | BIT(0)); + av_pkt_en = true; + } + } else { + if (av_mute_status & BIT(0)) { + hdmi_write(hdmi, HDMI_GC, av_mute_status & ~BIT(0)); + av_pkt_en = true; + } + } + + /* Enable AV Mute tranmission here */ + if (av_pkt_en) + hdmi_write(hdmi, HDMI_VBI_PKT_CTRL, + hdmi_read(hdmi, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5))); + + pr_info("AVMUTE %s\n", set ? "set" : "cleared"); + + return 0; +} diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h new file mode 100644 index 000000000000..6becf1efc9d8 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h @@ -0,0 +1,63 @@ +/* + * 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. + * + */ + +#ifndef _SDE_HDMI_UTIL_H_ +#define _SDE_HDMI_UTIL_H_ + +#include <linux/types.h> +#include <linux/bitops.h> +#include <linux/debugfs.h> +#include <linux/of_device.h> +#include <linux/msm_ext_display.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include "hdmi.h" +#include "sde_kms.h" +#include "sde_connector.h" +#include "msm_drv.h" +#include "sde_hdmi_regs.h" + +#ifdef HDMI_UTIL_DEBUG_ENABLE +#define HDMI_UTIL_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args) +#else +#define HDMI_UTIL_DEBUG(fmt, args...) SDE_DEBUG(fmt, ##args) +#endif + +#define HDMI_UTIL_ERROR(fmt, args...) SDE_ERROR(fmt, ##args) + +struct sde_hdmi_tx_ddc_data { + char *what; + u8 *data_buf; + u32 data_len; + u32 dev_addr; + u32 offset; + u32 request_len; + u32 retry_align; + u32 hard_timeout; + u32 timeout_left; + int retry; +}; + +struct sde_hdmi_tx_ddc_ctrl { + atomic_t rxstatus_busy_wait_done; + struct dss_io_data *io; + struct sde_hdmi_tx_ddc_data ddc_data; +}; + +/* DDC */ +int sde_hdmi_ddc_write(void *cb_data); +int sde_hdmi_ddc_read(void *cb_data); + +#endif /* _SDE_HDMI_UTIL_H_ */ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index 5bf87ae20255..84b578eaad47 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -27,6 +27,11 @@ #include "msm_drv.h" #include "hdmi.xml.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 struct hdmi_phy; struct hdmi_platform_config; @@ -76,10 +81,14 @@ struct hdmi { bool hdmi_mode; /* are we in hdmi mode? */ bool is_hdcp_supported; int irq; + void (*ddc_sw_done_cb)(void *data); + void *sw_done_cb_data; struct workqueue_struct *workq; struct hdmi_hdcp_ctrl *hdcp_ctrl; - + bool use_hard_timeout; + int busy_wait_us; + u32 timeout_count; /* * spinlock to protect registers shared by different execution * REG_HDMI_CTRL @@ -120,8 +129,20 @@ struct hdmi_platform_config { int mux_lpm_gpio; }; +struct hdmi_i2c_adapter { + struct i2c_adapter base; + struct hdmi *hdmi; + bool sw_done; + wait_queue_head_t ddc_event; +}; + void hdmi_set_mode(struct hdmi *hdmi, bool power_on); +#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base) + +int ddc_clear_irq(struct hdmi *hdmi); +void init_ddc(struct hdmi *hdmi); + static inline void hdmi_write(struct hdmi *hdmi, u32 reg, u32 data) { msm_writel(data, hdmi->mmio + reg); @@ -191,6 +212,13 @@ void hdmi_i2c_destroy(struct i2c_adapter *i2c); struct i2c_adapter *hdmi_i2c_init(struct hdmi *hdmi); /* + * DDC utility functions + */ +int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset, + u8 *data, u16 data_len, bool self_retry); +int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset, + u8 *data, u16 data_len, bool self_retry); +/* * hdcp */ struct hdmi_hdcp_ctrl *hdmi_hdcp_ctrl_init(struct hdmi *hdmi); diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c index cdd89f892df6..66be37bea4f6 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c @@ -85,84 +85,6 @@ struct hdmi_hdcp_ctrl { bool max_dev_exceeded; }; -static int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len) -{ - int rc; - int retry = 5; - struct i2c_msg msgs[] = { - { - .addr = addr >> 1, - .flags = 0, - .len = 1, - .buf = &offset, - }, { - .addr = addr >> 1, - .flags = I2C_M_RD, - .len = data_len, - .buf = data, - } - }; - - DBG("Start DDC read"); -retry: - rc = i2c_transfer(hdmi->i2c, msgs, 2); - - retry--; - if (rc == 2) - rc = 0; - else if (retry > 0) - goto retry; - else - rc = -EIO; - - DBG("End DDC read %d", rc); - - return rc; -} - -#define HDCP_DDC_WRITE_MAX_BYTE_NUM 32 - -static int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset, - u8 *data, u16 data_len) -{ - int rc; - int retry = 10; - u8 buf[HDCP_DDC_WRITE_MAX_BYTE_NUM]; - struct i2c_msg msgs[] = { - { - .addr = addr >> 1, - .flags = 0, - .len = 1, - } - }; - - DBG("Start DDC write"); - if (data_len > (HDCP_DDC_WRITE_MAX_BYTE_NUM - 1)) { - pr_err("%s: write size too big\n", __func__); - return -ERANGE; - } - - buf[0] = offset; - memcpy(&buf[1], data, data_len); - msgs[0].buf = buf; - msgs[0].len = data_len + 1; -retry: - rc = i2c_transfer(hdmi->i2c, msgs, 1); - - retry--; - if (rc == 1) - rc = 0; - else if (retry > 0) - goto retry; - else - rc = -EIO; - - DBG("End DDC write %d", rc); - - return rc; -} - static int hdmi_hdcp_scm_wr(struct hdmi_hdcp_ctrl *hdcp_ctrl, u32 *preg, u32 *pdata, u32 count) { diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c b/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c index f4ab7f70fed1..c65cc908b882 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi_i2c.c @@ -17,66 +17,16 @@ #include "hdmi.h" -struct hdmi_i2c_adapter { - struct i2c_adapter base; - struct hdmi *hdmi; - bool sw_done; - wait_queue_head_t ddc_event; -}; -#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base) - -static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c) -{ - struct hdmi *hdmi = hdmi_i2c->hdmi; - - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, - HDMI_DDC_CTRL_SW_STATUS_RESET); - hdmi_write(hdmi, REG_HDMI_DDC_CTRL, - HDMI_DDC_CTRL_SOFT_RESET); - - 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)); +#define MAX_TRANSACTIONS 4 - /* enable reference timer for 27us */ - hdmi_write(hdmi, REG_HDMI_DDC_REF, - HDMI_DDC_REF_REFTIMER_ENABLE | - HDMI_DDC_REF_REFTIMER(27)); -} +#define SDE_DDC_TXN_CNT_MASK 0x07ff0000 +#define SDE_DDC_TXN_CNT_SHIFT 16 -static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c) +static inline uint32_t SDE_HDMI_I2C_TRANSACTION_REG_CNT(uint32_t val) { - struct hdmi *hdmi = hdmi_i2c->hdmi; - struct drm_device *dev = hdmi->dev; - uint32_t retry = 0xffff; - uint32_t ddc_int_ctrl; - - do { - --retry; - - hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL, - HDMI_DDC_INT_CTRL_SW_DONE_ACK | - HDMI_DDC_INT_CTRL_SW_DONE_MASK); - - ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL); - - } while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry); - - if (!retry) { - dev_err(dev->dev, "timeout waiting for DDC\n"); - return -ETIMEDOUT; - } - - hdmi_i2c->sw_done = false; - - return 0; + return ((val) << SDE_DDC_TXN_CNT_SHIFT) & SDE_DDC_TXN_CNT_MASK; } -#define MAX_TRANSACTIONS 4 - static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c) { struct hdmi *hdmi = hdmi_i2c->hdmi; @@ -115,12 +65,13 @@ static int hdmi_i2c_xfer(struct i2c_adapter *i2c, WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE)); + if (num == 0) return num; - init_ddc(hdmi_i2c); + init_ddc(hdmi); - ret = ddc_clear_irq(hdmi_i2c); + ret = ddc_clear_irq(hdmi); if (ret) return ret; @@ -155,7 +106,7 @@ static int hdmi_i2c_xfer(struct i2c_adapter *i2c, } } - i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) | + i2c_trans = SDE_HDMI_I2C_TRANSACTION_REG_CNT(p->len) | HDMI_I2C_TRANSACTION_REG_RW( (p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) | HDMI_I2C_TRANSACTION_REG_START; @@ -177,9 +128,13 @@ static int hdmi_i2c_xfer(struct i2c_adapter *i2c, ret = -ETIMEDOUT; dev_warn(dev->dev, "DDC timeout: %d\n", ret); DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x", - hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS), - hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS), - hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL)); + hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS), + hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS), + hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL)); + if (hdmi->use_hard_timeout) { + hdmi->use_hard_timeout = false; + hdmi->timeout_count = 0; + } return ret; } @@ -213,6 +168,10 @@ static int hdmi_i2c_xfer(struct i2c_adapter *i2c, } } + if (hdmi->use_hard_timeout) { + hdmi->use_hard_timeout = false; + hdmi->timeout_count = jiffies_to_msecs(ret); + } return i; } diff --git a/drivers/gpu/drm/msm/hdmi/hdmi_util.c b/drivers/gpu/drm/msm/hdmi/hdmi_util.c new file mode 100644 index 000000000000..c7cfa38ed3ad --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi/hdmi_util.c @@ -0,0 +1,143 @@ +/* + * 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 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/of_irq.h> +#include "hdmi.h" + +void init_ddc(struct hdmi *hdmi) +{ + hdmi_write(hdmi, REG_HDMI_DDC_CTRL, + HDMI_DDC_CTRL_SW_STATUS_RESET); + hdmi_write(hdmi, REG_HDMI_DDC_CTRL, + HDMI_DDC_CTRL_SOFT_RESET); + + 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 ddc_clear_irq(struct hdmi *hdmi) +{ + struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(hdmi->i2c); + struct drm_device *dev = hdmi->dev; + uint32_t retry = 0xffff; + uint32_t ddc_int_ctrl; + + do { + --retry; + + hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL, + HDMI_DDC_INT_CTRL_SW_DONE_ACK | + HDMI_DDC_INT_CTRL_SW_DONE_MASK); + + ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL); + + } while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry); + + if (!retry) { + dev_err(dev->dev, "timeout waiting for DDC\n"); + return -ETIMEDOUT; + } + + hdmi_i2c->sw_done = false; + + return 0; +} + +int hdmi_ddc_read(struct hdmi *hdmi, u16 addr, u8 offset, +u8 *data, u16 data_len, bool self_retry) +{ + int rc; + int retry = 10; + struct i2c_msg msgs[] = { + { + .addr = addr >> 1, + .flags = 0, + .len = 1, + .buf = &offset, + }, { + .addr = addr >> 1, + .flags = I2C_M_RD, + .len = data_len, + .buf = data, + } + }; + + DBG("Start DDC read"); +retry: + rc = i2c_transfer(hdmi->i2c, msgs, 2); + retry--; + + if (rc == 2) + rc = 0; + else if (self_retry && (retry > 0)) + goto retry; + else + rc = -EIO; + + DBG("End DDC read %d", rc); + + return rc; +} + +#define HDCP_DDC_WRITE_MAX_BYTE_NUM 1024 + +int hdmi_ddc_write(struct hdmi *hdmi, u16 addr, u8 offset, + u8 *data, u16 data_len, bool self_retry) +{ + int rc; + int retry = 10; + u8 buf[HDCP_DDC_WRITE_MAX_BYTE_NUM]; + struct i2c_msg msgs[] = { + { + .addr = addr >> 1, + .flags = 0, + .len = 1, + } + }; + + pr_debug("TESTING ! REMOVE RETRY Start DDC write"); + if (data_len > (HDCP_DDC_WRITE_MAX_BYTE_NUM - 1)) { + pr_err("%s: write size too big\n", __func__); + return -ERANGE; + } + + buf[0] = offset; + memcpy(&buf[1], data, data_len); + msgs[0].buf = buf; + msgs[0].len = data_len + 1; +retry: + rc = i2c_transfer(hdmi->i2c, msgs, 1); + retry--; + if (rc == 1) + rc = 0; + else if (self_retry && (retry > 0)) + goto retry; + else + rc = -EIO; + + DBG("End DDC write %d", rc); + + return rc; +} diff --git a/drivers/gpu/drm/msm/sde_hdcp.h b/drivers/gpu/drm/msm/sde_hdcp.h new file mode 100644 index 000000000000..443bd5790551 --- /dev/null +++ b/drivers/gpu/drm/msm/sde_hdcp.h @@ -0,0 +1,83 @@ +/* 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. + */ + +#ifndef __SDE_HDCP_H__ +#define __SDE_HDCP_H__ + +#include <soc/qcom/scm.h> + +#include <linux/types.h> +#include <linux/bitops.h> +#include <linux/debugfs.h> +#include <linux/of_device.h> +#include <linux/i2c.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include "hdmi.h" +#include "sde_kms.h" + +#ifdef SDE_HDCP_DEBUG_ENABLE +#define SDE_HDCP_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args) +#else +#define SDE_HDCP_DEBUG(fmt, args...) SDE_DEBUG(fmt, ##args) +#endif + +enum sde_hdcp_client_id { + HDCP_CLIENT_HDMI, + HDCP_CLIENT_DP, +}; + +enum sde_hdcp_states { + HDCP_STATE_INACTIVE, + HDCP_STATE_AUTHENTICATING, + HDCP_STATE_AUTHENTICATED, + HDCP_STATE_AUTH_FAIL, + HDCP_STATE_AUTH_ENC_NONE, + HDCP_STATE_AUTH_ENC_1X, + HDCP_STATE_AUTH_ENC_2P2 +}; + +struct sde_hdcp_init_data { + struct dss_io_data *core_io; + struct dss_io_data *qfprom_io; + struct dss_io_data *hdcp_io; + struct mutex *mutex; + struct workqueue_struct *workq; + void *cb_data; + void (*notify_status)(void *cb_data, enum sde_hdcp_states status); + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + u8 sink_rx_status; + u16 *version; + u32 phy_addr; + u32 hdmi_tx_ver; + bool sec_access; + enum sde_hdcp_client_id client_id; +}; + +struct sde_hdcp_ops { + int (*isr)(void *ptr); + int (*cp_irq)(void *ptr); + int (*reauthenticate)(void *input); + int (*authenticate)(void *hdcp_ctrl); + bool (*feature_supported)(void *input); + void (*off)(void *hdcp_ctrl); +}; + +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); + +const char *sde_hdcp_state_name(enum sde_hdcp_states hdcp_state); + +#endif /* __SDE_HDCP_H__ */ diff --git a/drivers/gpu/drm/msm/sde_hdcp_1x.c b/drivers/gpu/drm/msm/sde_hdcp_1x.c new file mode 100644 index 000000000000..e650098ec96d --- /dev/null +++ b/drivers/gpu/drm/msm/sde_hdcp_1x.c @@ -0,0 +1,1721 @@ +/* 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/io.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/iopoll.h> +#include <linux/hdcp_qseecom.h> +#include "sde_hdcp.h" +#include "sde_hdmi_util.h" +#include "video/msm_hdmi_hdcp_mgr.h" + +#define SDE_HDCP_STATE_NAME (sde_hdcp_state_name(hdcp->hdcp_state)) + +/* HDCP Keys state based on HDMI_HDCP_LINK0_STATUS:KEYS_STATE */ +#define HDCP_KEYS_STATE_NO_KEYS 0 +#define HDCP_KEYS_STATE_NOT_CHECKED 1 +#define HDCP_KEYS_STATE_CHECKING 2 +#define HDCP_KEYS_STATE_VALID 3 +#define HDCP_KEYS_STATE_AKSV_NOT_VALID 4 +#define HDCP_KEYS_STATE_CHKSUM_MISMATCH 5 +#define HDCP_KEYS_STATE_PROD_AKSV 6 +#define HDCP_KEYS_STATE_RESERVED 7 + +#define TZ_HDCP_CMD_ID 0x00004401 + +#define HDCP_INT_CLR (isr->auth_success_ack | isr->auth_fail_ack | \ + isr->auth_fail_info_ack | isr->tx_req_ack | \ + isr->encryption_ready_ack | \ + isr->encryption_not_ready_ack | isr->tx_req_done_ack) + +#define HDCP_INT_EN (isr->auth_success_mask | isr->auth_fail_mask | \ + isr->encryption_ready_mask | \ + isr->encryption_not_ready_mask) + +#define HDCP_POLL_SLEEP_US (20 * 1000) +#define HDCP_POLL_TIMEOUT_US (HDCP_POLL_SLEEP_US * 100) + +#define sde_hdcp_1x_state(x) (hdcp->hdcp_state == x) + +struct sde_hdcp_sink_addr { + char *name; + u32 addr; + u32 len; +}; + +struct sde_hdcp_1x_reg_data { + u32 reg_id; + struct sde_hdcp_sink_addr *sink; +}; + +struct sde_hdcp_skaddr_map { + /* addresses to read from sink */ + struct sde_hdcp_sink_addr bcaps; + struct sde_hdcp_sink_addr bksv; + struct sde_hdcp_sink_addr r0; + struct sde_hdcp_sink_addr bstatus; + struct sde_hdcp_sink_addr cp_irq_status; + struct sde_hdcp_sink_addr ksv_fifo; + struct sde_hdcp_sink_addr v_h0; + struct sde_hdcp_sink_addr v_h1; + struct sde_hdcp_sink_addr v_h2; + struct sde_hdcp_sink_addr v_h3; + struct sde_hdcp_sink_addr v_h4; + + /* addresses to write to sink */ + struct sde_hdcp_sink_addr an; + struct sde_hdcp_sink_addr aksv; + struct sde_hdcp_sink_addr ainfo; +}; + +struct sde_hdcp_int_set { + /* interrupt register */ + u32 int_reg; + + /* interrupt enable/disable masks */ + u32 auth_success_mask; + u32 auth_fail_mask; + u32 encryption_ready_mask; + u32 encryption_not_ready_mask; + u32 tx_req_mask; + u32 tx_req_done_mask; + + /* interrupt acknowledgment */ + u32 auth_success_ack; + u32 auth_fail_ack; + u32 auth_fail_info_ack; + u32 encryption_ready_ack; + u32 encryption_not_ready_ack; + u32 tx_req_ack; + u32 tx_req_done_ack; + + /* interrupt status */ + u32 auth_success_int; + u32 auth_fail_int; + u32 encryption_ready; + u32 encryption_not_ready; + u32 tx_req_int; + u32 tx_req_done_int; +}; + +struct sde_hdcp_reg_set { + u32 status; + u32 keys_offset; + u32 r0_offset; + u32 v_offset; + u32 ctrl; + u32 aksv_lsb; + u32 aksv_msb; + u32 entropy_ctrl0; + u32 entropy_ctrl1; + u32 sec_sha_ctrl; + u32 sec_sha_data; + u32 sha_status; + + u32 data2_0; + u32 data3; + u32 data4; + u32 data5; + u32 data6; + + u32 sec_data0; + u32 sec_data1; + u32 sec_data7; + u32 sec_data8; + u32 sec_data9; + u32 sec_data10; + u32 sec_data11; + u32 sec_data12; + + u32 reset; + u32 reset_bit; + + u32 repeater; +}; + +#define HDCP_REG_SET_CLIENT_HDMI \ + {HDMI_HDCP_LINK0_STATUS, 28, 24, 20, HDMI_HDCP_CTRL, \ + HDMI_HDCP_SW_LOWER_AKSV, HDMI_HDCP_SW_UPPER_AKSV, \ + HDMI_HDCP_ENTROPY_CTRL0, HDMI_HDCP_ENTROPY_CTRL1, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_SHA_CTRL, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_SHA_DATA, \ + HDMI_HDCP_SHA_STATUS, HDMI_HDCP_RCVPORT_DATA2_0, \ + HDMI_HDCP_RCVPORT_DATA3, HDMI_HDCP_RCVPORT_DATA4, \ + HDMI_HDCP_RCVPORT_DATA5, HDMI_HDCP_RCVPORT_DATA6, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA0, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA1, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA7, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA8, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA9, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA10, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA11, \ + HDCP_SEC_TZ_HV_HLOS_HDCP_RCVPORT_DATA12, \ + HDMI_HDCP_RESET, BIT(0), BIT(6)} + +/* To do for DP */ +#define HDCP_REG_SET_CLIENT_DP \ + {0} + +#define HDCP_HDMI_SINK_ADDR_MAP \ + {{"bcaps", 0x40, 1}, {"bksv", 0x00, 5}, {"r0'", 0x08, 2}, \ + {"bstatus", 0x41, 2}, {"??", 0x0, 0}, {"ksv-fifo", 0x43, 0}, \ + {"v_h0", 0x20, 4}, {"v_h1", 0x24, 4}, {"v_h2", 0x28, 4}, \ + {"v_h3", 0x2c, 4}, {"v_h4", 0x30, 4}, {"an", 0x18, 8}, \ + {"aksv", 0x10, 5}, {"ainfo", 0x00, 0},} + +#define HDCP_DP_SINK_ADDR_MAP \ + {{"bcaps", 0x68028, 1}, {"bksv", 0x68000, 5}, {"r0'", 0x68005, 2}, \ + {"binfo", 0x6802A, 2}, {"cp_irq_status", 0x68029, 1}, \ + {"ksv-fifo", 0x6802C, 0}, {"v_h0", 0x68014, 4}, {"v_h1", 0x68018, 4}, \ + {"v_h2", 0x6801C, 4}, {"v_h3", 0x68020, 4}, {"v_h4", 0x68024, 4}, \ + {"an", 0x6800C, 8}, {"aksv", 0x68007, 5}, {"ainfo", 0x6803B, 1} } + +#define HDCP_HDMI_INT_SET \ + {HDMI_HDCP_INT_CTRL, \ + BIT(2), BIT(6), 0, 0, 0, 0, \ + BIT(1), BIT(5), BIT(7), 0, 0, 0, 0, \ + BIT(0), BIT(4), 0, 0, 0, 0} + +#define HDCP_DP_INT_SET \ + {DP_INTR_STATUS2, \ + BIT(17), BIT(20), BIT(24), BIT(27), 0, 0, \ + BIT(16), BIT(19), BIT(21), BIT(23), BIT(26), 0, 0, \ + BIT(15), BIT(18), BIT(22), BIT(25), 0, 0} + +struct sde_hdcp_1x { + u8 bcaps; + u32 tp_msgid; + u32 an_0, an_1, aksv_0, aksv_1; + bool sink_r0_ready; + bool reauth; + bool ksv_ready; + enum sde_hdcp_states hdcp_state; + struct HDCP_V2V1_MSG_TOPOLOGY cached_tp; + struct HDCP_V2V1_MSG_TOPOLOGY current_tp; + struct delayed_work hdcp_auth_work; + struct completion r0_checked; + struct completion sink_r0_available; + struct sde_hdcp_init_data init_data; + struct sde_hdcp_ops *ops; + struct sde_hdcp_reg_set reg_set; + struct sde_hdcp_int_set int_set; + struct sde_hdcp_skaddr_map sink_addr; + struct workqueue_struct *workq; +}; + +const char *sde_hdcp_state_name(enum sde_hdcp_states hdcp_state) +{ + switch (hdcp_state) { + case HDCP_STATE_INACTIVE: return "HDCP_STATE_INACTIVE"; + case HDCP_STATE_AUTHENTICATING: return "HDCP_STATE_AUTHENTICATING"; + case HDCP_STATE_AUTHENTICATED: return "HDCP_STATE_AUTHENTICATED"; + case HDCP_STATE_AUTH_FAIL: return "HDCP_STATE_AUTH_FAIL"; + default: return "???"; + } +} + +static int sde_hdcp_1x_count_one(u8 *array, u8 len) +{ + int i, j, count = 0; + + for (i = 0; i < len; i++) + for (j = 0; j < 8; j++) + count += (((array[i] >> j) & 0x1) ? 1 : 0); + return count; +} + +static void reset_hdcp_ddc_failures(struct sde_hdcp_1x *hdcp) +{ + int hdcp_ddc_ctrl1_reg; + int hdcp_ddc_status; + int failure; + int nack0; + struct dss_io_data *io; + + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + return; + } + + io = hdcp->init_data.core_io; + + /* Check for any DDC transfer failures */ + hdcp_ddc_status = DSS_REG_R(io, HDMI_HDCP_DDC_STATUS); + failure = (hdcp_ddc_status >> 16) & BIT(0); + nack0 = (hdcp_ddc_status >> 14) & BIT(0); + SDE_HDCP_DEBUG("%s: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d\n", + SDE_HDCP_STATE_NAME, hdcp_ddc_status, failure, nack0); + + if (failure) { + /* + * Indicates that the last HDCP HW DDC transfer failed. + * This occurs when a transfer is attempted with HDCP DDC + * disabled (HDCP_DDC_DISABLE=1) or the number of retries + * matches HDCP_DDC_RETRY_CNT. + * Failure occurred, let's clear it. + */ + SDE_HDCP_DEBUG("%s: DDC failure HDCP_DDC_STATUS=0x%08x\n", + SDE_HDCP_STATE_NAME, hdcp_ddc_status); + + /* First, Disable DDC */ + DSS_REG_W(io, HDMI_HDCP_DDC_CTRL_0, BIT(0)); + + /* ACK the Failure to Clear it */ + hdcp_ddc_ctrl1_reg = DSS_REG_R(io, HDMI_HDCP_DDC_CTRL_1); + DSS_REG_W(io, HDMI_HDCP_DDC_CTRL_1, + hdcp_ddc_ctrl1_reg | BIT(0)); + + /* Check if the FAILURE got Cleared */ + hdcp_ddc_status = DSS_REG_R(io, HDMI_HDCP_DDC_STATUS); + hdcp_ddc_status = (hdcp_ddc_status >> 16) & BIT(0); + if (hdcp_ddc_status == 0x0) + SDE_HDCP_DEBUG("%s: HDCP DDC Failure cleared\n", + SDE_HDCP_STATE_NAME); + else + SDE_ERROR("%s: Unable to clear HDCP DDC Failure", + SDE_HDCP_STATE_NAME); + + /* Re-Enable HDCP DDC */ + DSS_REG_W(io, HDMI_HDCP_DDC_CTRL_0, 0); + } + + if (nack0) { + SDE_HDCP_DEBUG("%s: Before: HDMI_DDC_SW_STATUS=0x%08x\n", + SDE_HDCP_STATE_NAME, DSS_REG_R(io, HDMI_DDC_SW_STATUS)); + /* Reset HDMI DDC software status */ + DSS_REG_W_ND(io, HDMI_DDC_CTRL, + DSS_REG_R(io, HDMI_DDC_CTRL) | BIT(3)); + msleep(20); + DSS_REG_W_ND(io, HDMI_DDC_CTRL, + DSS_REG_R(io, HDMI_DDC_CTRL) & ~(BIT(3))); + + /* Reset HDMI DDC Controller */ + DSS_REG_W_ND(io, HDMI_DDC_CTRL, + DSS_REG_R(io, HDMI_DDC_CTRL) | BIT(1)); + msleep(20); + DSS_REG_W_ND(io, HDMI_DDC_CTRL, + DSS_REG_R(io, HDMI_DDC_CTRL) & ~BIT(1)); + SDE_HDCP_DEBUG("%s: After: HDMI_DDC_SW_STATUS=0x%08x\n", + SDE_HDCP_STATE_NAME, DSS_REG_R(io, HDMI_DDC_SW_STATUS)); + } + + hdcp_ddc_status = DSS_REG_R(io, HDMI_HDCP_DDC_STATUS); + + failure = (hdcp_ddc_status >> 16) & BIT(0); + nack0 = (hdcp_ddc_status >> 14) & BIT(0); + SDE_HDCP_DEBUG("%s: On Exit: HDCP_DDC_STATUS=0x%x, FAIL=%d, NACK0=%d\n", + SDE_HDCP_STATE_NAME, hdcp_ddc_status, failure, nack0); +} /* reset_hdcp_ddc_failures */ + +static void sde_hdcp_1x_hw_ddc_clean(struct sde_hdcp_1x *hdcp) +{ + struct dss_io_data *io = NULL; + u32 hdcp_ddc_status, ddc_hw_status; + u32 ddc_xfer_done, ddc_xfer_req; + u32 ddc_hw_req, ddc_hw_not_idle; + bool ddc_hw_not_ready, xfer_not_done, hw_not_done; + u32 timeout_count; + + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + return; + } + + io = hdcp->init_data.core_io; + if (!io->base) { + pr_err("core io not inititalized\n"); + return; + } + + /* Wait to be clean on DDC HW engine */ + timeout_count = 100; + do { + hdcp_ddc_status = DSS_REG_R(io, HDMI_HDCP_DDC_STATUS); + ddc_xfer_req = hdcp_ddc_status & BIT(4); + ddc_xfer_done = hdcp_ddc_status & BIT(10); + + ddc_hw_status = DSS_REG_R(io, HDMI_DDC_HW_STATUS); + ddc_hw_req = ddc_hw_status & BIT(16); + ddc_hw_not_idle = ddc_hw_status & (BIT(0) | BIT(1)); + + /* ddc transfer was requested but not completed */ + xfer_not_done = ddc_xfer_req && !ddc_xfer_done; + + /* ddc status is not idle or a hw request pending */ + hw_not_done = ddc_hw_not_idle || ddc_hw_req; + + ddc_hw_not_ready = xfer_not_done || hw_not_done; + + SDE_HDCP_DEBUG("%s: timeout count(%d): ddc hw%sready\n", + SDE_HDCP_STATE_NAME, timeout_count, + ddc_hw_not_ready ? " not " : " "); + SDE_HDCP_DEBUG("hdcp_ddc_status[0x%x], ddc_hw_status[0x%x]\n", + hdcp_ddc_status, ddc_hw_status); + if (ddc_hw_not_ready) + msleep(20); + } while (ddc_hw_not_ready && --timeout_count); +} /* hdcp_1x_hw_ddc_clean */ + +static int sde_hdcp_1x_load_keys(void *input) +{ + int rc = 0; + bool use_sw_keys = false; + u32 reg_val; + u32 ksv_lsb_addr, ksv_msb_addr; + u32 aksv_lsb, aksv_msb; + u8 aksv[5]; + struct dss_io_data *io; + struct dss_io_data *qfprom_io; + struct sde_hdcp_1x *hdcp = input; + struct sde_hdcp_reg_set *reg_set; + + if (!hdcp || !hdcp->init_data.core_io || + !hdcp->init_data.qfprom_io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + if (!sde_hdcp_1x_state(HDCP_STATE_INACTIVE) && + !sde_hdcp_1x_state(HDCP_STATE_AUTH_FAIL)) { + pr_err("%s: invalid state. returning\n", + SDE_HDCP_STATE_NAME); + rc = -EINVAL; + goto end; + } + + io = hdcp->init_data.core_io; + qfprom_io = hdcp->init_data.qfprom_io; + reg_set = &hdcp->reg_set; + + /* On compatible hardware, use SW keys */ + reg_val = DSS_REG_R(qfprom_io, SEC_CTRL_HW_VERSION); + if (reg_val >= HDCP_SEL_MIN_SEC_VERSION) { + reg_val = DSS_REG_R(qfprom_io, + QFPROM_RAW_FEAT_CONFIG_ROW0_MSB + + QFPROM_RAW_VERSION_4); + + if (!(reg_val & BIT(23))) + use_sw_keys = true; + } + + if (use_sw_keys) { + if (hdcp1_set_keys(&aksv_msb, &aksv_lsb)) { + pr_err("setting hdcp SW keys failed\n"); + rc = -EINVAL; + goto end; + } + } else { + /* Fetch aksv from QFPROM, this info should be public. */ + ksv_lsb_addr = HDCP_KSV_LSB; + ksv_msb_addr = HDCP_KSV_MSB; + + if (hdcp->init_data.sec_access) { + ksv_lsb_addr += HDCP_KSV_VERSION_4_OFFSET; + ksv_msb_addr += HDCP_KSV_VERSION_4_OFFSET; + } + + aksv_lsb = DSS_REG_R(qfprom_io, ksv_lsb_addr); + aksv_msb = DSS_REG_R(qfprom_io, ksv_msb_addr); + } + + SDE_HDCP_DEBUG("%s: AKSV=%02x%08x\n", SDE_HDCP_STATE_NAME, + aksv_msb, aksv_lsb); + + aksv[0] = aksv_lsb & 0xFF; + aksv[1] = (aksv_lsb >> 8) & 0xFF; + aksv[2] = (aksv_lsb >> 16) & 0xFF; + aksv[3] = (aksv_lsb >> 24) & 0xFF; + aksv[4] = aksv_msb & 0xFF; + + /* check there are 20 ones in AKSV */ + if (sde_hdcp_1x_count_one(aksv, 5) != 20) { + pr_err("AKSV bit count failed\n"); + rc = -EINVAL; + goto end; + } + + DSS_REG_W(io, reg_set->aksv_lsb, aksv_lsb); + DSS_REG_W(io, reg_set->aksv_msb, aksv_msb); + + /* Setup seed values for random number An */ + DSS_REG_W(io, reg_set->entropy_ctrl0, 0xB1FFB0FF); + DSS_REG_W(io, reg_set->entropy_ctrl1, 0xF00DFACE); + + /* make sure hw is programmed */ + wmb(); + + /* enable hdcp engine */ + DSS_REG_W(io, reg_set->ctrl, 0x1); + + hdcp->hdcp_state = HDCP_STATE_AUTHENTICATING; +end: + return rc; +} + +static int sde_hdcp_1x_read(struct sde_hdcp_1x *hdcp, + struct sde_hdcp_sink_addr *sink, + u8 *buf, bool realign) +{ + u32 rc = 0; + struct sde_hdmi_tx_ddc_data *ddc_data; + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + reset_hdcp_ddc_failures(hdcp); + + ddc_ctrl = hdcp->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 = 0x74; + ddc_data->offset = sink->addr; + ddc_data->data_buf = buf; + ddc_data->data_len = sink->len; + ddc_data->request_len = sink->len; + ddc_data->retry = 5; + ddc_data->what = sink->name; + ddc_data->retry_align = realign; + + rc = sde_hdmi_ddc_read((void *)hdcp->init_data.cb_data); + if (rc) + SDE_ERROR("%s: %s read failed\n", + SDE_HDCP_STATE_NAME, sink->name); + } else if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + /* To-do DP APIs go here */ + } + + return rc; +} + +static int sde_hdcp_1x_write(struct sde_hdcp_1x *hdcp, + struct sde_hdcp_sink_addr *sink, u8 *buf) +{ + int rc = 0; + struct sde_hdmi_tx_ddc_data *ddc_data; + struct sde_hdmi_tx_ddc_ctrl *ddc_ctrl; + + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + ddc_ctrl = hdcp->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 = 0x74; + ddc_data->offset = sink->addr; + ddc_data->data_buf = buf; + ddc_data->data_len = sink->len; + ddc_data->what = sink->name; + + rc = sde_hdmi_ddc_write((void *)hdcp->init_data.cb_data); + if (rc) + SDE_ERROR("%s: %s write failed\n", + SDE_HDCP_STATE_NAME, sink->name); + } else if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + /* To-do DP APIs go here */ + } + + return rc; +} + +static void sde_hdcp_1x_enable_interrupts(struct sde_hdcp_1x *hdcp) +{ + u32 intr_reg; + struct dss_io_data *io; + struct sde_hdcp_int_set *isr; + + io = hdcp->init_data.core_io; + isr = &hdcp->int_set; + + intr_reg = DSS_REG_R(io, isr->int_reg); + + intr_reg |= HDCP_INT_CLR | HDCP_INT_EN; + + DSS_REG_W(io, isr->int_reg, intr_reg); +} + +static int sde_hdcp_1x_read_bcaps(struct sde_hdcp_1x *hdcp) +{ + int rc; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + struct dss_io_data *hdcp_io = hdcp->init_data.hdcp_io; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.bcaps, + &hdcp->bcaps, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading bcaps\n"); + goto error; + } + + SDE_HDCP_DEBUG("bcaps read: 0x%x\n", hdcp->bcaps); + + hdcp->current_tp.ds_type = hdcp->bcaps & reg_set->repeater ? + DS_REPEATER : DS_RECEIVER; + + SDE_HDCP_DEBUG("ds: %s\n", hdcp->current_tp.ds_type == DS_REPEATER ? + "repeater" : "receiver"); + + /* Write BCAPS to the hardware */ + DSS_REG_W(hdcp_io, reg_set->sec_data12, hdcp->bcaps); +error: + return rc; +} + +static int sde_hdcp_1x_wait_for_hw_ready(struct sde_hdcp_1x *hdcp) +{ + int rc; + u32 link0_status; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + struct dss_io_data *io = hdcp->init_data.core_io; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + /* Wait for HDCP keys to be checked and validated */ + rc = readl_poll_timeout(io->base + reg_set->status, link0_status, + ((link0_status >> reg_set->keys_offset) & 0x7) + == HDCP_KEYS_STATE_VALID || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("key not ready\n"); + goto error; + } + + /* + * 1.1_Features turned off by default. + * No need to write AInfo since 1.1_Features is disabled. + */ + DSS_REG_W(io, reg_set->data4, 0); + + /* Wait for An0 and An1 bit to be ready */ + rc = readl_poll_timeout(io->base + reg_set->status, link0_status, + (link0_status & (BIT(8) | BIT(9))) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("An not ready\n"); + goto error; + } + + /* As per hardware recommendations, wait before reading An */ + msleep(20); +error: + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + + return rc; +} + +static int sde_hdcp_1x_send_an_aksv_to_sink(struct sde_hdcp_1x *hdcp) +{ + int rc; + u8 an[8], aksv[5]; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + an[0] = hdcp->an_0 & 0xFF; + an[1] = (hdcp->an_0 >> 8) & 0xFF; + an[2] = (hdcp->an_0 >> 16) & 0xFF; + an[3] = (hdcp->an_0 >> 24) & 0xFF; + an[4] = hdcp->an_1 & 0xFF; + an[5] = (hdcp->an_1 >> 8) & 0xFF; + an[6] = (hdcp->an_1 >> 16) & 0xFF; + an[7] = (hdcp->an_1 >> 24) & 0xFF; + + SDE_HDCP_DEBUG("an read: 0x%2x%2x%2x%2x%2x%2x%2x%2x\n", + an[7], an[6], an[5], an[4], an[3], an[2], an[1], an[0]); + + rc = sde_hdcp_1x_write(hdcp, &hdcp->sink_addr.an, an); + if (IS_ERR_VALUE(rc)) { + pr_err("error writing an to sink\n"); + goto error; + } + + /* Copy An and AKSV to byte arrays for transmission */ + aksv[0] = hdcp->aksv_0 & 0xFF; + aksv[1] = (hdcp->aksv_0 >> 8) & 0xFF; + aksv[2] = (hdcp->aksv_0 >> 16) & 0xFF; + aksv[3] = (hdcp->aksv_0 >> 24) & 0xFF; + aksv[4] = hdcp->aksv_1 & 0xFF; + + SDE_HDCP_DEBUG("aksv read: 0x%2x%2x%2x%2x%2x\n", + aksv[4], aksv[3], aksv[2], aksv[1], aksv[0]); + + rc = sde_hdcp_1x_write(hdcp, &hdcp->sink_addr.aksv, aksv); + if (IS_ERR_VALUE(rc)) { + pr_err("error writing aksv to sink\n"); + goto error; + } +error: + return rc; +} + +static int sde_hdcp_1x_read_an_aksv_from_hw(struct sde_hdcp_1x *hdcp) +{ + struct dss_io_data *io = hdcp->init_data.core_io; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + hdcp->an_0 = DSS_REG_R(io, reg_set->data5); + if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + udelay(1); + hdcp->an_0 = DSS_REG_R(io, reg_set->data5); + } + + hdcp->an_1 = DSS_REG_R(io, reg_set->data6); + if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + udelay(1); + hdcp->an_1 = DSS_REG_R(io, reg_set->data6); + } + + /* Read AKSV */ + hdcp->aksv_0 = DSS_REG_R(io, reg_set->data3); + hdcp->aksv_1 = DSS_REG_R(io, reg_set->data4); + + return 0; +} + +static int sde_hdcp_1x_get_bksv_from_sink(struct sde_hdcp_1x *hdcp) +{ + int rc; + u8 *bksv = hdcp->current_tp.bksv; + u32 link0_bksv_0, link0_bksv_1; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + struct dss_io_data *hdcp_io = hdcp->init_data.hdcp_io; + + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.bksv, bksv, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading bksv from sink\n"); + goto error; + } + + SDE_HDCP_DEBUG("bksv read: 0x%2x%2x%2x%2x%2x\n", + bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]); + + /* check there are 20 ones in BKSV */ + if (sde_hdcp_1x_count_one(bksv, 5) != 20) { + pr_err("%s: BKSV doesn't have 20 1's and 20 0's\n", + SDE_HDCP_STATE_NAME); + rc = -EINVAL; + goto error; + } + + link0_bksv_0 = bksv[3]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[2]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[1]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[0]; + link0_bksv_1 = bksv[4]; + + DSS_REG_W(hdcp_io, reg_set->sec_data0, link0_bksv_0); + DSS_REG_W(hdcp_io, reg_set->sec_data1, link0_bksv_1); +error: + return rc; +} + +static void sde_hdcp_1x_enable_sink_irq_hpd(struct sde_hdcp_1x *hdcp) +{ + int rc; + u8 enable_hpd_irq = 0x1; + + if (hdcp->current_tp.ds_type != DS_REPEATER) + return; + + rc = sde_hdcp_1x_write(hdcp, &hdcp->sink_addr.ainfo, &enable_hpd_irq); + if (IS_ERR_VALUE(rc)) + SDE_HDCP_DEBUG("error writing ainfo to sink\n"); +} + +static int sde_hdcp_1x_verify_r0(struct sde_hdcp_1x *hdcp) +{ + int rc, r0_retry = 3; + u8 buf[2]; + u32 link0_status, timeout_count; + u32 const r0_read_delay_us = 1; + u32 const r0_read_timeout_us = r0_read_delay_us * 10; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + struct dss_io_data *io = hdcp->init_data.core_io; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + /* Wait for HDCP R0 computation to be completed */ + rc = readl_poll_timeout(io->base + reg_set->status, link0_status, + (link0_status & BIT(reg_set->r0_offset)) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("R0 not ready\n"); + goto error; + } + + /* + * HDCP Compliace Test case 1A-01: + * Wait here at least 100ms before reading R0' + */ + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + msleep(100); + } else { + if (!hdcp->sink_r0_ready) { + reinit_completion(&hdcp->sink_r0_available); + timeout_count = wait_for_completion_timeout( + &hdcp->sink_r0_available, HZ / 2); + + if (hdcp->reauth) { + pr_err("sink R0 not ready\n"); + rc = -EINVAL; + goto error; + } + } + } + + do { + memset(buf, 0, sizeof(buf)); + + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.r0, + buf, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading R0' from sink\n"); + goto error; + } + + SDE_HDCP_DEBUG("sink R0'read: %2x%2x\n", buf[1], buf[0]); + + DSS_REG_W(io, reg_set->data2_0, (((u32)buf[1]) << 8) | buf[0]); + + rc = readl_poll_timeout(io->base + reg_set->status, + link0_status, (link0_status & BIT(12)) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + r0_read_delay_us, r0_read_timeout_us); + } while (rc && --r0_retry); +error: + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + + return rc; +} + +static int sde_hdcp_1x_authentication_part1(struct sde_hdcp_1x *hdcp) +{ + int rc; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + sde_hdcp_1x_enable_interrupts(hdcp); + + rc = sde_hdcp_1x_read_bcaps(hdcp); + if (rc) + goto error; + + rc = sde_hdcp_1x_wait_for_hw_ready(hdcp); + if (rc) + goto error; + + rc = sde_hdcp_1x_read_an_aksv_from_hw(hdcp); + if (rc) + goto error; + + rc = sde_hdcp_1x_get_bksv_from_sink(hdcp); + if (rc) + goto error; + + rc = sde_hdcp_1x_send_an_aksv_to_sink(hdcp); + if (rc) + goto error; + + sde_hdcp_1x_enable_sink_irq_hpd(hdcp); + + rc = sde_hdcp_1x_verify_r0(hdcp); + if (rc) + goto error; + + pr_info("SUCCESSFUL\n"); + + return 0; +error: + pr_err("%s: FAILED\n", SDE_HDCP_STATE_NAME); + + return rc; +} + +static int sde_hdcp_1x_transfer_v_h(struct sde_hdcp_1x *hdcp) +{ + int rc = 0; + struct dss_io_data *io = hdcp->init_data.hdcp_io; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + struct sde_hdcp_1x_reg_data reg_data[] = { + {reg_set->sec_data7, &hdcp->sink_addr.v_h0}, + {reg_set->sec_data8, &hdcp->sink_addr.v_h1}, + {reg_set->sec_data9, &hdcp->sink_addr.v_h2}, + {reg_set->sec_data10, &hdcp->sink_addr.v_h3}, + {reg_set->sec_data11, &hdcp->sink_addr.v_h4}, + }; + struct sde_hdcp_sink_addr sink = {"V", reg_data->sink->addr}; + u32 size = ARRAY_SIZE(reg_data); + u8 buf[0xFF] = {0}; + u32 i = 0, len = 0; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + for (i = 0; i < size; i++) { + struct sde_hdcp_1x_reg_data *rd = reg_data + i; + + len += rd->sink->len; + } + + sink.len = len; + + rc = sde_hdcp_1x_read(hdcp, &sink, buf, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading %s\n", sink.name); + goto end; + } + + + for (i = 0; i < size; i++) { + struct sde_hdcp_1x_reg_data *rd = reg_data + i; + u32 reg_data; + + memcpy(®_data, buf + (sizeof(u32) * i), sizeof(u32)); + DSS_REG_W(io, rd->reg_id, reg_data); + } +end: + return rc; +} + +static int sde_hdcp_1x_validate_downstream(struct sde_hdcp_1x *hdcp) +{ + int rc; + u8 buf[2] = {0, 0}; + u8 device_count, depth; + u8 max_cascade_exceeded, max_devs_exceeded; + u16 bstatus; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.bstatus, + buf, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading bstatus\n"); + goto end; + } + + bstatus = buf[1]; + bstatus = (bstatus << 8) | buf[0]; + + device_count = bstatus & 0x7F; + + SDE_HDCP_DEBUG("device count %d\n", device_count); + + /* Cascaded repeater depth */ + depth = (bstatus >> 8) & 0x7; + SDE_HDCP_DEBUG("depth %d\n", depth); + + /* + * HDCP Compliance 1B-05: + * Check if no. of devices connected to repeater + * exceed max_devices_connected from bit 7 of Bstatus. + */ + max_devs_exceeded = (bstatus & BIT(7)) >> 7; + if (max_devs_exceeded == 0x01) { + pr_err("no. of devs connected exceed max allowed\n"); + rc = -EINVAL; + goto end; + } + + /* + * HDCP Compliance 1B-06: + * Check if no. of cascade connected to repeater + * exceed max_cascade_connected from bit 11 of Bstatus. + */ + max_cascade_exceeded = (bstatus & BIT(11)) >> 11; + if (max_cascade_exceeded == 0x01) { + pr_err("no. of cascade connections exceed max allowed\n"); + rc = -EINVAL; + goto end; + } + + /* Update topology information */ + hdcp->current_tp.dev_count = device_count; + hdcp->current_tp.max_cascade_exceeded = max_cascade_exceeded; + hdcp->current_tp.max_dev_exceeded = max_devs_exceeded; + hdcp->current_tp.depth = depth; + + DSS_REG_W(hdcp->init_data.hdcp_io, + reg_set->sec_data12, hdcp->bcaps | (bstatus << 8)); +end: + return rc; +} + +static int sde_hdcp_1x_read_ksv_fifo(struct sde_hdcp_1x *hdcp) +{ + u32 ksv_read_retry = 20, ksv_bytes, rc = 0; + u8 *ksv_fifo = hdcp->current_tp.ksv_list; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + memset(ksv_fifo, 0, sizeof(hdcp->current_tp.ksv_list)); + + /* each KSV is 5 bytes long */ + ksv_bytes = 5 * hdcp->current_tp.dev_count; + hdcp->sink_addr.ksv_fifo.len = ksv_bytes; + + while (ksv_bytes && --ksv_read_retry) { + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.ksv_fifo, + ksv_fifo, true); + if (IS_ERR_VALUE(rc)) + pr_err("could not read ksv fifo (%d)\n", + ksv_read_retry); + else + break; + } + + if (rc) + pr_err("error reading ksv_fifo\n"); + + return rc; +} + +static int sde_hdcp_1x_write_ksv_fifo(struct sde_hdcp_1x *hdcp) +{ + int i, rc = 0; + u8 *ksv_fifo = hdcp->current_tp.ksv_list; + u32 ksv_bytes = hdcp->sink_addr.ksv_fifo.len; + struct dss_io_data *io = hdcp->init_data.core_io; + struct dss_io_data *sec_io = hdcp->init_data.hdcp_io; + struct sde_hdcp_reg_set *reg_set = &hdcp->reg_set; + u32 sha_status = 0, status; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + /* reset SHA Controller */ + DSS_REG_W(sec_io, reg_set->sec_sha_ctrl, 0x1); + DSS_REG_W(sec_io, reg_set->sec_sha_ctrl, 0x0); + + for (i = 0; i < ksv_bytes - 1; i++) { + /* Write KSV byte and do not set DONE bit[0] */ + DSS_REG_W_ND(sec_io, reg_set->sec_sha_data, ksv_fifo[i] << 16); + + /* + * Once 64 bytes have been written, we need to poll for + * HDCP_SHA_BLOCK_DONE before writing any further + */ + if (i && !((i + 1) % 64)) { + rc = readl_poll_timeout(io->base + reg_set->sha_status, + sha_status, (sha_status & BIT(0)) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("block not done\n"); + goto error; + } + } + } + + /* Write l to DONE bit[0] */ + DSS_REG_W_ND(sec_io, reg_set->sec_sha_data, + (ksv_fifo[ksv_bytes - 1] << 16) | 0x1); + + /* Now wait for HDCP_SHA_COMP_DONE */ + rc = readl_poll_timeout(io->base + reg_set->sha_status, sha_status, + (sha_status & BIT(4)) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("V computation not done\n"); + goto error; + } + + /* Wait for V_MATCHES */ + rc = readl_poll_timeout(io->base + reg_set->status, status, + (status & BIT(reg_set->v_offset)) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING), + HDCP_POLL_SLEEP_US, HDCP_POLL_TIMEOUT_US); + if (IS_ERR_VALUE(rc)) { + pr_err("V mismatch\n"); + rc = -EINVAL; + } +error: + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + rc = -EINVAL; + + return rc; +} + +static int sde_hdcp_1x_wait_for_ksv_ready(struct sde_hdcp_1x *hdcp) +{ + int rc, timeout; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + /* + * Wait until READY bit is set in BCAPS, as per HDCP specifications + * maximum permitted time to check for READY bit is five seconds. + */ + rc = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.bcaps, + &hdcp->bcaps, false); + if (IS_ERR_VALUE(rc)) { + pr_err("error reading bcaps\n"); + goto error; + } + + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + timeout = 50; + + while (!(hdcp->bcaps & BIT(5)) && --timeout) { + rc = sde_hdcp_1x_read(hdcp, + &hdcp->sink_addr.bcaps, + &hdcp->bcaps, false); + if (IS_ERR_VALUE(rc) || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("error reading bcaps\n"); + goto error; + } + msleep(100); + } + } else { + u8 cp_buf = 0; + struct sde_hdcp_sink_addr *sink = + &hdcp->sink_addr.cp_irq_status; + + timeout = jiffies_to_msecs(jiffies); + + while (1) { + rc = sde_hdcp_1x_read(hdcp, sink, &cp_buf, false); + if (rc) + goto error; + + if (cp_buf & BIT(0)) + break; + + /* max timeout of 5 sec as per hdcp 1.x spec */ + if (abs(timeout - jiffies_to_msecs(jiffies)) > 5000) { + timeout = 0; + break; + } + + if (hdcp->ksv_ready || hdcp->reauth || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + break; + + /* re-read after a minimum delay */ + msleep(20); + } + } + + if (!timeout || hdcp->reauth || + !sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("DS KSV not ready\n"); + rc = -EINVAL; + } else { + hdcp->ksv_ready = true; + } +error: + return rc; +} + +static int sde_hdcp_1x_authentication_part2(struct sde_hdcp_1x *hdcp) +{ + int rc; + int v_retry = 3; + + rc = sde_hdcp_1x_validate_downstream(hdcp); + if (rc) + goto error; + + rc = sde_hdcp_1x_read_ksv_fifo(hdcp); + if (rc) + goto error; + + do { + rc = sde_hdcp_1x_transfer_v_h(hdcp); + if (rc) + goto error; + + /* do not proceed further if no device connected */ + if (!hdcp->current_tp.dev_count) + goto error; + + rc = sde_hdcp_1x_write_ksv_fifo(hdcp); + } while (--v_retry && rc); +error: + if (rc) { + pr_err("%s: FAILED\n", SDE_HDCP_STATE_NAME); + } else { + hdcp->hdcp_state = HDCP_STATE_AUTHENTICATED; + + pr_info("SUCCESSFUL\n"); + } + + return rc; +} + +static void sde_hdcp_1x_cache_topology(struct sde_hdcp_1x *hdcp) +{ + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + return; + } + + memcpy((void *)&hdcp->cached_tp, + (void *) &hdcp->current_tp, + sizeof(hdcp->cached_tp)); +} + +static void sde_hdcp_1x_notify_topology(struct sde_hdcp_1x *hdcp) +{ + /* TO DO : something here for HDCP 1x*/ +} + +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); + } + + if (hdcp->init_data.notify_status && + !sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) { + hdcp->init_data.notify_status( + hdcp->init_data.cb_data, + hdcp->hdcp_state); + } +} + +static void sde_hdcp_1x_auth_work(struct work_struct *work) +{ + int rc; + struct delayed_work *dw = to_delayed_work(work); + struct sde_hdcp_1x *hdcp = container_of(dw, + struct sde_hdcp_1x, hdcp_auth_work); + struct dss_io_data *io; + + if (!hdcp) { + pr_err("invalid input\n"); + return; + } + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + pr_err("invalid state\n"); + return; + } + + hdcp->sink_r0_ready = false; + hdcp->reauth = false; + hdcp->ksv_ready = false; + + io = hdcp->init_data.core_io; + /* Enabling Software DDC for HDMI and REF timer for DP */ + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) + DSS_REG_W_ND(io, HDMI_DDC_ARBITRATION, DSS_REG_R(io, + HDMI_DDC_ARBITRATION) & ~(BIT(4))); + else if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + /* To do for DP */ + } + + /* + * program hw to enable encryption as soon as + * authentication is successful. + */ + hdcp1_set_enc(true); + + rc = sde_hdcp_1x_authentication_part1(hdcp); + if (rc) + goto end; + + if (hdcp->current_tp.ds_type == DS_REPEATER) { + rc = sde_hdcp_1x_wait_for_ksv_ready(hdcp); + if (rc) + goto end; + } else { + hdcp->hdcp_state = HDCP_STATE_AUTHENTICATED; + goto end; + } + + hdcp->ksv_ready = false; + + rc = sde_hdcp_1x_authentication_part2(hdcp); + if (rc) + goto end; + + /* + * Disabling software DDC before going into part3 to make sure + * there is no Arbitration between software and hardware for DDC + */ + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) + DSS_REG_W_ND(io, HDMI_DDC_ARBITRATION, DSS_REG_R(io, + HDMI_DDC_ARBITRATION) | (BIT(4))); +end: + if (rc && !sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) + hdcp->hdcp_state = HDCP_STATE_AUTH_FAIL; + + sde_hdcp_1x_update_auth_status(hdcp); +} + +static int sde_hdcp_1x_authenticate(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + + if (!hdcp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + flush_delayed_work(&hdcp->hdcp_auth_work); + + if (!sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + if (!sde_hdcp_1x_load_keys(input)) { + + queue_delayed_work(hdcp->workq, + &hdcp->hdcp_auth_work, HZ/2); + } else { + hdcp->hdcp_state = HDCP_STATE_AUTH_FAIL; + sde_hdcp_1x_update_auth_status(hdcp); + } + + return 0; +} /* hdcp_1x_authenticate */ + +static int sde_hdcp_1x_reauthenticate(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + struct dss_io_data *io; + struct sde_hdcp_reg_set *reg_set; + struct sde_hdcp_int_set *isr; + u32 hdmi_hw_version; + u32 ret = 0, reg; + + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + return -EINVAL; + } + + io = hdcp->init_data.core_io; + reg_set = &hdcp->reg_set; + isr = &hdcp->int_set; + + if (!sde_hdcp_1x_state(HDCP_STATE_AUTH_FAIL)) { + pr_err("invalid state\n"); + return -EINVAL; + } + + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + hdmi_hw_version = DSS_REG_R(io, HDMI_VERSION); + if (hdmi_hw_version >= 0x30030000) { + DSS_REG_W(io, HDMI_CTRL_SW_RESET, BIT(1)); + DSS_REG_W(io, HDMI_CTRL_SW_RESET, 0); + } + + /* Wait to be clean on DDC HW engine */ + sde_hdcp_1x_hw_ddc_clean(hdcp); + } + + /* Disable HDCP interrupts */ + DSS_REG_W(io, isr->int_reg, DSS_REG_R(io, isr->int_reg) & ~HDCP_INT_EN); + + reg = DSS_REG_R(io, reg_set->reset); + DSS_REG_W(io, reg_set->reset, reg | reg_set->reset_bit); + + /* Disable encryption and disable the HDCP block */ + DSS_REG_W(io, reg_set->ctrl, 0); + + DSS_REG_W(io, reg_set->reset, reg & ~reg_set->reset_bit); + + hdcp->hdcp_state = HDCP_STATE_INACTIVE; + sde_hdcp_1x_authenticate(hdcp); + + return ret; +} /* hdcp_1x_reauthenticate */ + +static void sde_hdcp_1x_off(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + struct dss_io_data *io; + struct sde_hdcp_reg_set *reg_set; + struct sde_hdcp_int_set *isr; + int rc = 0; + u32 reg; + + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + return; + } + + io = hdcp->init_data.core_io; + reg_set = &hdcp->reg_set; + isr = &hdcp->int_set; + + if (sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) { + pr_err("invalid state\n"); + return; + } + + /* + * Disable HDCP interrupts. + * Also, need to set the state to inactive here so that any ongoing + * reauth works will know that the HDCP session has been turned off. + */ + mutex_lock(hdcp->init_data.mutex); + DSS_REG_W(io, isr->int_reg, + DSS_REG_R(io, isr->int_reg) & ~HDCP_INT_EN); + hdcp->hdcp_state = HDCP_STATE_INACTIVE; + mutex_unlock(hdcp->init_data.mutex); + + /* complete any wait pending */ + complete_all(&hdcp->sink_r0_available); + complete_all(&hdcp->r0_checked); + /* + * Cancel any pending auth/reauth attempts. + * If one is ongoing, this will wait for it to finish. + * No more reauthentiaction attempts will be scheduled since we + * set the currect state to inactive. + */ + rc = cancel_delayed_work_sync(&hdcp->hdcp_auth_work); + if (rc) + SDE_HDCP_DEBUG("%s: Deleted hdcp auth work\n", + SDE_HDCP_STATE_NAME); + + hdcp1_set_enc(false); + + reg = DSS_REG_R(io, reg_set->reset); + DSS_REG_W(io, reg_set->reset, reg | reg_set->reset_bit); + + /* Disable encryption and disable the HDCP block */ + DSS_REG_W(io, reg_set->ctrl, 0); + + DSS_REG_W(io, reg_set->reset, reg & ~reg_set->reset_bit); + + hdcp->sink_r0_ready = false; + + SDE_HDCP_DEBUG("%s: HDCP: Off\n", SDE_HDCP_STATE_NAME); +} /* hdcp_1x_off */ + +static int sde_hdcp_1x_isr(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + int rc = 0; + struct dss_io_data *io; + u32 hdcp_int_val; + struct sde_hdcp_reg_set *reg_set; + struct sde_hdcp_int_set *isr; + + if (!hdcp || !hdcp->init_data.core_io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + io = hdcp->init_data.core_io; + reg_set = &hdcp->reg_set; + isr = &hdcp->int_set; + + hdcp_int_val = DSS_REG_R(io, isr->int_reg); + + /* Ignore HDCP interrupts if HDCP is disabled */ + if (sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) { + DSS_REG_W(io, isr->int_reg, hdcp_int_val | HDCP_INT_CLR); + return 0; + } + + if (hdcp_int_val & isr->auth_success_int) { + /* AUTH_SUCCESS_INT */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->auth_success_ack)); + SDE_HDCP_DEBUG("%s: AUTH SUCCESS\n", SDE_HDCP_STATE_NAME); + + if (sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) + complete_all(&hdcp->r0_checked); + } + + if (hdcp_int_val & isr->auth_fail_int) { + /* AUTH_FAIL_INT */ + u32 link_status = DSS_REG_R(io, reg_set->status); + + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->auth_fail_ack)); + + SDE_HDCP_DEBUG("%s: AUTH FAIL, LINK0_STATUS=0x%08x\n", + SDE_HDCP_STATE_NAME, link_status); + + if (sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATED)) { + hdcp->hdcp_state = HDCP_STATE_AUTH_FAIL; + sde_hdcp_1x_update_auth_status(hdcp); + } else if (sde_hdcp_1x_state(HDCP_STATE_AUTHENTICATING)) { + complete_all(&hdcp->r0_checked); + } + + /* Clear AUTH_FAIL_INFO as well */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->auth_fail_info_ack)); + } + + if (hdcp_int_val & isr->tx_req_int) { + /* DDC_XFER_REQ_INT */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->tx_req_ack)); + SDE_HDCP_DEBUG("%s: DDC_XFER_REQ_INT received\n", + SDE_HDCP_STATE_NAME); + } + + if (hdcp_int_val & isr->tx_req_done_int) { + /* DDC_XFER_DONE_INT */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->tx_req_done_ack)); + SDE_HDCP_DEBUG("%s: DDC_XFER_DONE received\n", + SDE_HDCP_STATE_NAME); + } + + if (hdcp_int_val & isr->encryption_ready) { + /* Encryption enabled */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->encryption_ready_ack)); + SDE_HDCP_DEBUG("%s: encryption ready received\n", + SDE_HDCP_STATE_NAME); + } + + if (hdcp_int_val & isr->encryption_not_ready) { + /* Encryption enabled */ + DSS_REG_W(io, isr->int_reg, + (hdcp_int_val | isr->encryption_not_ready_ack)); + SDE_HDCP_DEBUG("%s: encryption not ready received\n", + SDE_HDCP_STATE_NAME); + } + +error: + return rc; +} + +void sde_hdcp_1x_deinit(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + + if (!hdcp) { + pr_err("invalid input\n"); + return; + } + + if (hdcp->workq) + destroy_workqueue(hdcp->workq); + + kfree(hdcp); +} /* hdcp_1x_deinit */ + +static void sde_hdcp_1x_update_client_reg_set(struct sde_hdcp_1x *hdcp) +{ + + if (hdcp->init_data.client_id == HDCP_CLIENT_HDMI) { + struct sde_hdcp_reg_set reg_set = HDCP_REG_SET_CLIENT_HDMI; + struct sde_hdcp_skaddr_map sink_addr = HDCP_HDMI_SINK_ADDR_MAP; + struct sde_hdcp_int_set isr = HDCP_HDMI_INT_SET; + + hdcp->reg_set = reg_set; + hdcp->sink_addr = sink_addr; + hdcp->int_set = isr; + } else if (hdcp->init_data.client_id == HDCP_CLIENT_DP) { + /* TO DO for DP + * Will be filled later + */ + } +} + +static bool sde_hdcp_1x_is_cp_irq_raised(struct sde_hdcp_1x *hdcp) +{ + int ret; + u8 buf = 0; + struct sde_hdcp_sink_addr sink = {"irq", 0x201, 1}; + + ret = sde_hdcp_1x_read(hdcp, &sink, &buf, false); + if (IS_ERR_VALUE(ret)) + pr_err("error reading irq_vector\n"); + + return buf & BIT(2) ? true : false; +} + +static void sde_hdcp_1x_clear_cp_irq(struct sde_hdcp_1x *hdcp) +{ + int ret; + u8 buf = BIT(2); + struct sde_hdcp_sink_addr sink = {"irq", 0x201, 1}; + + ret = sde_hdcp_1x_write(hdcp, &sink, &buf); + if (IS_ERR_VALUE(ret)) + pr_err("error clearing irq_vector\n"); +} + +static int sde_hdcp_1x_cp_irq(void *input) +{ + struct sde_hdcp_1x *hdcp = (struct sde_hdcp_1x *)input; + u8 buf = 0; + int ret; + + if (!hdcp) { + pr_err("invalid input\n"); + goto irq_not_handled; + } + + if (!sde_hdcp_1x_is_cp_irq_raised(hdcp)) { + SDE_HDCP_DEBUG("cp_irq not raised\n"); + goto irq_not_handled; + } + + ret = sde_hdcp_1x_read(hdcp, &hdcp->sink_addr.cp_irq_status, + &buf, false); + if (IS_ERR_VALUE(ret)) { + pr_err("error reading cp_irq_status\n"); + goto irq_not_handled; + } + + if ((buf & BIT(2)) || (buf & BIT(3))) { + pr_err("%s\n", + buf & BIT(2) ? "LINK_INTEGRITY_FAILURE" : + "REAUTHENTICATION_REQUEST"); + + hdcp->reauth = true; + + if (!sde_hdcp_1x_state(HDCP_STATE_INACTIVE)) + hdcp->hdcp_state = HDCP_STATE_AUTH_FAIL; + + complete_all(&hdcp->sink_r0_available); + sde_hdcp_1x_update_auth_status(hdcp); + } else if (buf & BIT(1)) { + SDE_HDCP_DEBUG("R0' AVAILABLE\n"); + hdcp->sink_r0_ready = true; + complete_all(&hdcp->sink_r0_available); + } else if ((buf & BIT(0))) { + SDE_HDCP_DEBUG("KSVs READY\n"); + + hdcp->ksv_ready = true; + } else { + SDE_HDCP_DEBUG("spurious interrupt\n"); + } + + sde_hdcp_1x_clear_cp_irq(hdcp); + return 0; + +irq_not_handled: + return -EINVAL; +} + +void *sde_hdcp_1x_init(struct sde_hdcp_init_data *init_data) +{ + struct sde_hdcp_1x *hdcp = NULL; + char name[20]; + static struct sde_hdcp_ops ops = { + .isr = sde_hdcp_1x_isr, + .cp_irq = sde_hdcp_1x_cp_irq, + .reauthenticate = sde_hdcp_1x_reauthenticate, + .authenticate = sde_hdcp_1x_authenticate, + .off = sde_hdcp_1x_off + }; + + if (!init_data || !init_data->core_io || !init_data->qfprom_io || + !init_data->mutex || !init_data->notify_status || + !init_data->workq || !init_data->cb_data) { + pr_err("invalid input\n"); + goto error; + } + + if (init_data->sec_access && !init_data->hdcp_io) { + pr_err("hdcp_io required\n"); + goto error; + } + + hdcp = kzalloc(sizeof(*hdcp), GFP_KERNEL); + if (!hdcp) + goto error; + + hdcp->init_data = *init_data; + hdcp->ops = &ops; + + snprintf(name, sizeof(name), "hdcp_1x_%d", + hdcp->init_data.client_id); + + hdcp->workq = create_workqueue(name); + if (!hdcp->workq) { + pr_err("Error creating workqueue\n"); + kfree(hdcp); + goto error; + } + + sde_hdcp_1x_update_client_reg_set(hdcp); + + INIT_DELAYED_WORK(&hdcp->hdcp_auth_work, sde_hdcp_1x_auth_work); + + hdcp->hdcp_state = HDCP_STATE_INACTIVE; + init_completion(&hdcp->r0_checked); + init_completion(&hdcp->sink_r0_available); + + SDE_HDCP_DEBUG("HDCP module initialized. HDCP_STATE=%s\n", + SDE_HDCP_STATE_NAME); + + return (void *)hdcp; + +error: + return NULL; +} /* hdcp_1x_init */ + +struct sde_hdcp_ops *sde_hdcp_1x_start(void *input) +{ + return ((struct sde_hdcp_1x *)input)->ops; +} + |
