summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAbhinav Kumar <abhinavk@codeaurora.org>2017-03-13 13:40:30 -0700
committerAbhinav Kumar <abhinavk@codeaurora.org>2017-04-10 23:43:16 -0700
commit7a5cee889a02065c1dd3df1b7795cc2e4d726be9 (patch)
tree7250f8d4fcfce83a3fdb1208eb4afc7f32951f4d
parent31604f1c6f99f726111c21f28fe0c84120a9a3eb (diff)
drm/msm: add support for enabling scrambling feature
To support 4k@60fps resolution through HDMI, enable scrambler feature from HDMI controller and communicate it with sink device through DDC. Change-Id: I17750db358df58499303ef9d735bf3301b02a7c1 Signed-off-by: Abhinav Kumar <abhinavk@codeaurora.org>
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c255
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h77
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c262
-rw-r--r--drivers/gpu/drm/msm/hdmi/hdmi.xml.h14
4 files changed, 608 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
index 347b78886b24..6a8b3ccd6478 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
@@ -32,6 +32,22 @@
static DEFINE_MUTEX(sde_hdmi_list_lock);
static LIST_HEAD(sde_hdmi_list);
+/* HDMI SCDC register offsets */
+#define HDMI_SCDC_UPDATE_0 0x10
+#define HDMI_SCDC_UPDATE_1 0x11
+#define HDMI_SCDC_TMDS_CONFIG 0x20
+#define HDMI_SCDC_SCRAMBLER_STATUS 0x21
+#define HDMI_SCDC_CONFIG_0 0x30
+#define HDMI_SCDC_STATUS_FLAGS_0 0x40
+#define HDMI_SCDC_STATUS_FLAGS_1 0x41
+#define HDMI_SCDC_ERR_DET_0_L 0x50
+#define HDMI_SCDC_ERR_DET_0_H 0x51
+#define HDMI_SCDC_ERR_DET_1_L 0x52
+#define HDMI_SCDC_ERR_DET_1_H 0x53
+#define HDMI_SCDC_ERR_DET_2_L 0x54
+#define HDMI_SCDC_ERR_DET_2_H 0x55
+#define HDMI_SCDC_ERR_DET_CHECKSUM 0x56
+
static const struct of_device_id sde_hdmi_dt_match[] = {
{.compatible = "qcom,hdmi-display"},
{}
@@ -638,6 +654,245 @@ 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,
+ }
+ };
+
+ DRM_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;
+
+ DRM_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,
+ }
+ };
+
+ DRM_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;
+
+ DRM_DEBUG("End DDC write %d", rc);
+
+ return rc;
+}
+
+int sde_hdmi_scdc_read(struct hdmi *hdmi, u32 data_type, u32 *val)
+{
+ int rc = 0;
+ u8 data_buf[2] = {0};
+ u16 dev_addr, data_len;
+ u8 offset;
+
+ if (!hdmi || !hdmi->i2c || !val) {
+ SDE_ERROR("Bad Parameters\n");
+ return -EINVAL;
+ }
+
+ if (data_type >= HDMI_TX_SCDC_MAX) {
+ SDE_ERROR("Unsupported data type\n");
+ return -EINVAL;
+ }
+
+ dev_addr = 0xA8;
+
+ switch (data_type) {
+ case HDMI_TX_SCDC_SCRAMBLING_STATUS:
+ data_len = 1;
+ offset = HDMI_SCDC_SCRAMBLER_STATUS;
+ break;
+ case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
+ case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
+ data_len = 1;
+ offset = HDMI_SCDC_TMDS_CONFIG;
+ break;
+ case HDMI_TX_SCDC_CLOCK_DET_STATUS:
+ case HDMI_TX_SCDC_CH0_LOCK_STATUS:
+ case HDMI_TX_SCDC_CH1_LOCK_STATUS:
+ case HDMI_TX_SCDC_CH2_LOCK_STATUS:
+ data_len = 1;
+ offset = HDMI_SCDC_STATUS_FLAGS_0;
+ break;
+ case HDMI_TX_SCDC_CH0_ERROR_COUNT:
+ data_len = 2;
+ offset = HDMI_SCDC_ERR_DET_0_L;
+ break;
+ case HDMI_TX_SCDC_CH1_ERROR_COUNT:
+ data_len = 2;
+ offset = HDMI_SCDC_ERR_DET_1_L;
+ break;
+ case HDMI_TX_SCDC_CH2_ERROR_COUNT:
+ data_len = 2;
+ offset = HDMI_SCDC_ERR_DET_2_L;
+ break;
+ case HDMI_TX_SCDC_READ_ENABLE:
+ data_len = 1;
+ offset = HDMI_SCDC_CONFIG_0;
+ break;
+ default:
+ break;
+ }
+
+ rc = sde_hdmi_ddc_read(hdmi, dev_addr, offset, data_buf, data_len);
+ if (rc) {
+ SDE_ERROR("DDC Read failed for %d\n", data_type);
+ return rc;
+ }
+
+ switch (data_type) {
+ case HDMI_TX_SCDC_SCRAMBLING_STATUS:
+ *val = (data_buf[0] & BIT(0)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
+ *val = (data_buf[0] & BIT(0)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
+ *val = (data_buf[0] & BIT(1)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_CLOCK_DET_STATUS:
+ *val = (data_buf[0] & BIT(0)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_CH0_LOCK_STATUS:
+ *val = (data_buf[0] & BIT(1)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_CH1_LOCK_STATUS:
+ *val = (data_buf[0] & BIT(2)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_CH2_LOCK_STATUS:
+ *val = (data_buf[0] & BIT(3)) ? 1 : 0;
+ break;
+ case HDMI_TX_SCDC_CH0_ERROR_COUNT:
+ case HDMI_TX_SCDC_CH1_ERROR_COUNT:
+ case HDMI_TX_SCDC_CH2_ERROR_COUNT:
+ if (data_buf[1] & BIT(7))
+ *val = (data_buf[0] | ((data_buf[1] & 0x7F) << 8));
+ else
+ *val = 0;
+ break;
+ case HDMI_TX_SCDC_READ_ENABLE:
+ *val = (data_buf[0] & BIT(0)) ? 1 : 0;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int sde_hdmi_scdc_write(struct hdmi *hdmi, u32 data_type, u32 val)
+{
+ int rc = 0;
+ u8 data_buf[2] = {0};
+ u8 read_val = 0;
+ u16 dev_addr, data_len;
+ u8 offset;
+
+ if (!hdmi || !hdmi->i2c) {
+ SDE_ERROR("Bad Parameters\n");
+ return -EINVAL;
+ }
+
+ if (data_type >= HDMI_TX_SCDC_MAX) {
+ SDE_ERROR("Unsupported data type\n");
+ return -EINVAL;
+ }
+
+ dev_addr = 0xA8;
+
+ switch (data_type) {
+ case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
+ case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
+ dev_addr = 0xA8;
+ data_len = 1;
+ offset = HDMI_SCDC_TMDS_CONFIG;
+ rc = sde_hdmi_ddc_read(hdmi, dev_addr, offset, &read_val,
+ data_len);
+ if (rc) {
+ SDE_ERROR("scdc read failed\n");
+ return rc;
+ }
+ if (data_type == HDMI_TX_SCDC_SCRAMBLING_ENABLE) {
+ data_buf[0] = ((((u8)(read_val & 0xFF)) & (~BIT(0))) |
+ ((u8)(val & BIT(0))));
+ } else {
+ data_buf[0] = ((((u8)(read_val & 0xFF)) & (~BIT(1))) |
+ (((u8)(val & BIT(0))) << 1));
+ }
+ break;
+ case HDMI_TX_SCDC_READ_ENABLE:
+ data_len = 1;
+ offset = HDMI_SCDC_CONFIG_0;
+ data_buf[0] = (u8)(val & 0x1);
+ break;
+ default:
+ SDE_ERROR("Cannot write to read only reg (%d)\n",
+ data_type);
+ return -EINVAL;
+ }
+
+ rc = sde_hdmi_ddc_write(hdmi, dev_addr, offset, data_buf, data_len);
+ if (rc) {
+ SDE_ERROR("DDC Read failed for %d\n", data_type);
+ return rc;
+ }
+ return 0;
+}
+
int sde_hdmi_get_info(struct msm_display_info *info,
void *display)
{
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
index 869d1bebf9db..c2b685111c65 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
@@ -116,6 +116,37 @@ struct sde_hdmi {
struct dentry *root;
};
+/**
+ * hdmi_tx_scdc_access_type() - hdmi 2.0 DDC functionalities.
+ */
+enum hdmi_tx_scdc_access_type {
+ HDMI_TX_SCDC_SCRAMBLING_STATUS,
+ HDMI_TX_SCDC_SCRAMBLING_ENABLE,
+ HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE,
+ HDMI_TX_SCDC_CLOCK_DET_STATUS,
+ HDMI_TX_SCDC_CH0_LOCK_STATUS,
+ HDMI_TX_SCDC_CH1_LOCK_STATUS,
+ HDMI_TX_SCDC_CH2_LOCK_STATUS,
+ HDMI_TX_SCDC_CH0_ERROR_COUNT,
+ HDMI_TX_SCDC_CH1_ERROR_COUNT,
+ HDMI_TX_SCDC_CH2_ERROR_COUNT,
+ HDMI_TX_SCDC_READ_ENABLE,
+ HDMI_TX_SCDC_MAX,
+};
+
+/**
+ * hdmi_tx_ddc_timer_type() - hdmi DDC timer functionalities.
+ */
+enum hdmi_tx_ddc_timer_type {
+ HDMI_TX_DDC_TIMER_HDCP2P2_RD_MSG,
+ HDMI_TX_DDC_TIMER_SCRAMBLER_STATUS,
+ HDMI_TX_DDC_TIMER_UPDATE_FLAGS,
+ HDMI_TX_DDC_TIMER_STATUS_FLAGS,
+ HDMI_TX_DDC_TIMER_CED,
+ HDMI_TX_DDC_TIMER_MAX,
+ };
+
+
#ifdef CONFIG_DRM_SDE_HDMI
/**
* sde_hdmi_get_num_of_displays() - returns number of display devices
@@ -259,6 +290,52 @@ 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.
+ * @val: Read back value.
+ *
+ * Return: error code.
+ */
+int sde_hdmi_scdc_read(struct hdmi *hdmi, u32 data_type, u32 *val);
+
+/**
+ * sde_hdmi_scdc_write() - hdmi 2.0 ddc write API.
+ * @hdmi: Handle to the hdmi.
+ * @data_type: DDC data type, refer to enum hdmi_tx_scdc_access_type.
+ * @val: Value write through DDC.
+ *
+ * Return: error code.
+ */
+int sde_hdmi_scdc_write(struct hdmi *hdmi, u32 data_type, u32 val);
+
+/**
* sde_hdmi_audio_on() - enable hdmi audio.
* @hdmi: Handle to the hdmi.
* @params: audio setup parameters from codec.
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 681dca501f9b..d649458fcb7c 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
@@ -28,6 +28,13 @@ struct sde_hdmi_bridge {
};
#define to_hdmi_bridge(x) container_of(x, struct sde_hdmi_bridge, base)
+/* TX major version that supports scrambling */
+#define HDMI_TX_SCRAMBLER_MIN_TX_VERSION 0x04
+#define HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ 340000
+#define HDMI_TX_SCRAMBLER_TIMEOUT_MSEC 200
+/* default hsyncs for 4k@60 for 200ms */
+#define HDMI_DEFAULT_TIMEOUT_HSYNC 28571
+
/* for AVI program */
#define HDMI_AVI_INFOFRAME_BUFFER_SIZE \
(HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE)
@@ -101,6 +108,260 @@ static void _sde_hdmi_bridge_power_off(struct drm_bridge *bridge)
}
}
+static int _sde_hdmi_bridge_ddc_clear_irq(struct hdmi *hdmi,
+ char *what)
+{
+ u32 ddc_int_ctrl, ddc_status, in_use, timeout;
+ u32 sw_done_mask = BIT(2);
+ u32 sw_done_ack = BIT(1);
+ u32 in_use_by_sw = BIT(0);
+ u32 in_use_by_hw = BIT(1);
+
+ /* clear and enable interrutps */
+ ddc_int_ctrl = sw_done_mask | sw_done_ack;
+
+ hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL, ddc_int_ctrl);
+
+ /* wait until DDC HW is free */
+ timeout = 100;
+ do {
+ ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS);
+ in_use = ddc_status & (in_use_by_sw | in_use_by_hw);
+ if (in_use) {
+ SDE_DEBUG("ddc is in use by %s, timeout(%d)\n",
+ ddc_status & in_use_by_sw ? "sw" : "hw",
+ timeout);
+ udelay(100);
+ }
+ } while (in_use && --timeout);
+
+ if (!timeout) {
+ SDE_ERROR("%s: timedout\n", what);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int _sde_hdmi_bridge_scrambler_ddc_check_status(struct hdmi *hdmi)
+{
+ int rc = 0;
+ u32 reg_val;
+
+ /* check for errors and clear status */
+ reg_val = hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_STATUS);
+ if (reg_val & BIT(4)) {
+ SDE_ERROR("ddc aborted\n");
+ reg_val |= BIT(5);
+ rc = -ECONNABORTED;
+ }
+
+ if (reg_val & BIT(8)) {
+ SDE_ERROR("timed out\n");
+ reg_val |= BIT(9);
+ rc = -ETIMEDOUT;
+ }
+
+ if (reg_val & BIT(12)) {
+ SDE_ERROR("NACK0\n");
+ reg_val |= BIT(13);
+ rc = -EIO;
+ }
+ if (reg_val & BIT(14)) {
+ SDE_ERROR("NACK1\n");
+ reg_val |= BIT(15);
+ rc = -EIO;
+ }
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_STATUS, reg_val);
+
+ return rc;
+}
+
+static void _sde_hdmi_bridge_scrambler_ddc_reset(struct hdmi *hdmi)
+{
+ u32 reg_val;
+
+ /* clear ack and disable interrupts */
+ reg_val = BIT(14) | BIT(9) | BIT(5) | BIT(1);
+ hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL2, reg_val);
+
+ /* Reset DDC timers */
+ reg_val = BIT(0) | hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
+
+ reg_val = hdmi_read(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL);
+ reg_val &= ~BIT(0);
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
+}
+
+static void _sde_hdmi_bridge_scrambler_ddc_disable(struct hdmi *hdmi)
+{
+ u32 reg_val;
+
+ _sde_hdmi_bridge_scrambler_ddc_reset(hdmi);
+ /* Disable HW DDC access to RxStatus register */
+ reg_val = hdmi_read(hdmi, REG_HDMI_HW_DDC_CTRL);
+ reg_val &= ~(BIT(8) | BIT(9));
+ hdmi_write(hdmi, REG_HDMI_HW_DDC_CTRL, reg_val);
+}
+
+static int _sde_hdmi_bridge_scrambler_status_timer_setup(struct hdmi *hdmi,
+ u32 timeout_hsync)
+{
+ u32 reg_val;
+ int rc;
+
+ _sde_hdmi_bridge_ddc_clear_irq(hdmi, "scrambler");
+
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL,
+ timeout_hsync);
+ hdmi_write(hdmi, REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL2,
+ timeout_hsync);
+ reg_val = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL5);
+ reg_val |= BIT(10);
+ hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL5, reg_val);
+
+ reg_val = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL2);
+ /* Trigger interrupt if scrambler status is 0 or DDC failure */
+ reg_val |= BIT(10);
+ reg_val &= ~(BIT(15) | BIT(16));
+ reg_val |= BIT(16);
+ hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL2, reg_val);
+
+ /* Enable DDC access */
+ reg_val = hdmi_read(hdmi, REG_HDMI_HW_DDC_CTRL);
+
+ reg_val &= ~(BIT(8) | BIT(9));
+ reg_val |= BIT(8);
+ hdmi_write(hdmi, REG_HDMI_HW_DDC_CTRL, reg_val);
+
+ /* WAIT for 200ms as per HDMI 2.0 standard for sink to respond */
+ msleep(200);
+
+ /* clear the scrambler status */
+ rc = _sde_hdmi_bridge_scrambler_ddc_check_status(hdmi);
+ if (rc)
+ SDE_ERROR("scrambling ddc error %d\n", rc);
+
+ _sde_hdmi_bridge_scrambler_ddc_disable(hdmi);
+
+ return rc;
+}
+
+static int _sde_hdmi_bridge_setup_ddc_timers(struct hdmi *hdmi,
+ u32 type, u32 to_in_num_lines)
+{
+ if (type >= HDMI_TX_DDC_TIMER_MAX) {
+ SDE_ERROR("Invalid timer type %d\n", type);
+ return -EINVAL;
+ }
+
+ switch (type) {
+ case HDMI_TX_DDC_TIMER_SCRAMBLER_STATUS:
+ _sde_hdmi_bridge_scrambler_status_timer_setup(hdmi,
+ to_in_num_lines);
+ break;
+ default:
+ SDE_ERROR("%d type not supported\n", type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static inline int _sde_hdmi_bridge_get_timeout_in_hysnc(
+ struct drm_display_mode *mode, u32 timeout_ms)
+{
+ /*
+ * pixel clock = h_total * v_total * fps
+ * 1 sec = pixel clock number of pixels are transmitted.
+ * time taken by one line (h_total) = 1s / (v_total * fps).
+ * lines for give time = (time_ms * 1000) / (1000000 / (v_total * fps))
+ * = (time_ms * clock) / h_total
+ */
+
+ return (timeout_ms * mode->clock / mode->htotal);
+}
+
+static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ int rc = 0;
+ int timeout_hsync;
+ u32 reg_val = 0;
+ u32 tmds_clock_ratio = 0;
+ bool scrambler_on = false;
+
+ if (!hdmi || !mode) {
+ SDE_ERROR("invalid input\n");
+ return -EINVAL;
+ }
+
+ /* Read HDMI version */
+ reg_val = hdmi_read(hdmi, REG_HDMI_VERSION);
+ reg_val = (reg_val & 0xF0000000) >> 28;
+ /* Scrambling is supported from HDMI TX 4.0 */
+ if (reg_val < HDMI_TX_SCRAMBLER_MIN_TX_VERSION) {
+ DRM_INFO("scrambling not supported by tx\n");
+ return 0;
+ }
+
+ if (mode->clock > HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ) {
+ scrambler_on = true;
+ tmds_clock_ratio = 1;
+ }
+
+ DRM_INFO("scrambler %s\n", scrambler_on ? "on" : "off");
+
+ if (scrambler_on) {
+ rc = sde_hdmi_scdc_write(hdmi,
+ HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE,
+ tmds_clock_ratio);
+ if (rc) {
+ SDE_ERROR("TMDS CLK RATIO ERR\n");
+ return rc;
+ }
+
+ reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+ reg_val |= BIT(31); /* Enable Update DATAPATH_MODE */
+ reg_val |= BIT(28); /* Set SCRAMBLER_EN bit */
+
+ hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+
+ rc = sde_hdmi_scdc_write(hdmi,
+ HDMI_TX_SCDC_SCRAMBLING_ENABLE, 0x1);
+ if (rc) {
+ SDE_ERROR("failed to enable scrambling\n");
+ return rc;
+ }
+
+ /*
+ * Setup hardware to periodically check for scrambler
+ * status bit on the sink. Sink should set this bit
+ * with in 200ms after scrambler is enabled.
+ */
+ timeout_hsync = _sde_hdmi_bridge_get_timeout_in_hysnc(
+ mode,
+ HDMI_TX_SCRAMBLER_TIMEOUT_MSEC);
+ if (timeout_hsync <= 0) {
+ SDE_ERROR("err in timeout hsync calc\n");
+ timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
+ }
+ SDE_DEBUG("timeout for scrambling en: %d hsyncs\n",
+ timeout_hsync);
+
+ rc = _sde_hdmi_bridge_setup_ddc_timers(hdmi,
+ HDMI_TX_DDC_TIMER_SCRAMBLER_STATUS, timeout_hsync);
+ } else {
+ sde_hdmi_scdc_write(hdmi, HDMI_TX_SCDC_SCRAMBLING_ENABLE, 0x0);
+ reg_val = hdmi_read(hdmi, REG_HDMI_CTRL);
+ reg_val &= ~BIT(31); /* Disable Update DATAPATH_MODE */
+ reg_val &= ~BIT(28); /* Unset SCRAMBLER_EN bit */
+ hdmi_write(hdmi, REG_HDMI_CTRL, reg_val);
+ }
+ return rc;
+}
+
static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
{
struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
@@ -373,6 +634,7 @@ static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
_sde_hdmi_bridge_set_spd_infoframe(hdmi, mode);
DRM_DEBUG("hdmi setup info frame\n");
}
+ _sde_hdmi_bridge_setup_scrambler(hdmi, mode);
}
static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.xml.h b/drivers/gpu/drm/msm/hdmi/hdmi.xml.h
index aef20f76bf02..0956617442af 100644
--- a/drivers/gpu/drm/msm/hdmi/hdmi.xml.h
+++ b/drivers/gpu/drm/msm/hdmi/hdmi.xml.h
@@ -563,6 +563,20 @@ static inline uint32_t HDMI_VSYNC_TOTAL_F2_V_TOTAL(uint32_t val)
#define REG_HDMI_CEC_WR_CHECK_CONFIG 0x00000370
+#define REG_HDMI_DDC_INT_CTRL0 0x00000430
+#define REG_HDMI_DDC_INT_CTRL1 0x00000434
+#define REG_HDMI_DDC_INT_CTRL2 0x00000438
+#define REG_HDMI_DDC_INT_CTRL3 0x0000043C
+#define REG_HDMI_DDC_INT_CTRL4 0x00000440
+#define REG_HDMI_DDC_INT_CTRL5 0x00000444
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_CTRL 0x00000464
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL 0x00000468
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL2 0x0000046C
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_STATUS 0x00000470
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_STATUS 0x00000474
+#define REG_HDMI_SCRAMBLER_STATUS_DDC_TIMER_STATUS2 0x00000478
+#define REG_HDMI_HW_DDC_CTRL 0x000004CC
+
#define REG_HDMI_8x60_PHY_REG0 0x00000300
#define HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__MASK 0x0000001c
#define HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__SHIFT 2