summaryrefslogtreecommitdiff
path: root/drivers/gpu
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@quicinc.com>2017-06-15 22:59:09 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2017-06-15 22:59:09 -0700
commit16a6dc2a061446e4feac988d5fed128677e58c21 (patch)
tree1bfa4822a7a5ee4d31cd2db75bccded97b0245ce /drivers/gpu
parent92e373a732709efc5b633752d7fe17cb79690f50 (diff)
parent02b2b76fb0d3eeab8bfe7fb43f8413c8ccb4690d (diff)
Merge "drm/msm: add HDCP 1x module for MSM DRM driver"
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/drm/msm/Makefile5
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c295
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h86
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_audio.c34
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c41
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c313
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h63
-rw-r--r--drivers/gpu/drm/msm/hdmi/hdmi.h30
-rw-r--r--drivers/gpu/drm/msm/hdmi/hdmi_hdcp.c78
-rw-r--r--drivers/gpu/drm/msm/hdmi/hdmi_i2c.c81
-rw-r--r--drivers/gpu/drm/msm/hdmi/hdmi_util.c143
-rw-r--r--drivers/gpu/drm/msm/sde_hdcp.h83
-rw-r--r--drivers/gpu/drm/msm/sde_hdcp_1x.c1721
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(&reg_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;
+}
+