Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 954dc040 authored by Jin Li's avatar Jin Li
Browse files

drm/sde: add support for enabling scrambling feature



In order to support 4K@60Hz through HDMI display, driver needs to
enable scrambling feature from HDMI controller and communicate
it with sink device through DDC.

Change-Id: I0b08b6462d19044c4cffed10b0429e7f5d7285f6
Signed-off-by: default avatarJin Li <jinl@codeaurora.org>
parent d0ad9edd
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -104,7 +104,8 @@ msm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \
				dsi-staging/dsi_display_test.o

msm-$(CONFIG_DRM_SDE_HDMI) += \
	hdmi-staging/sde_hdmi.o
	hdmi-staging/sde_hdmi.o \
	hdmi-staging/sde_hdmi_bridge.o

msm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \
				dsi/pll/dsi_pll_28nm.o
+286 −4
Original line number Diff line number Diff line
@@ -30,6 +30,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"},
	{}
@@ -328,9 +344,9 @@ static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi)
		}
	}

	hdmi_set_mode(hdmi, false);
	sde_hdmi_set_mode(hdmi, false);
	_sde_hdmi_phy_reset(hdmi);
	hdmi_set_mode(hdmi, true);
	sde_hdmi_set_mode(hdmi, true);

	hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);

@@ -367,7 +383,7 @@ static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi)
	/* Disable HPD interrupt */
	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);

	hdmi_set_mode(hdmi, false);
	sde_hdmi_set_mode(hdmi, false);

	for (i = 0; i < config->hpd_clk_cnt; i++)
		clk_disable_unprepare(hdmi->hpd_clks[i]);
@@ -464,6 +480,272 @@ static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
	return IRQ_HANDLED;
}

void sde_hdmi_set_mode(struct hdmi *hdmi, bool power_on)
{
	uint32_t ctrl = 0;
	unsigned long flags;

	spin_lock_irqsave(&hdmi->reg_lock, flags);
	ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
	if (power_on) {
		ctrl |= HDMI_CTRL_ENABLE;
		if (!hdmi->hdmi_mode) {
			ctrl |= HDMI_CTRL_HDMI;
			hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
			ctrl &= ~HDMI_CTRL_HDMI;
		} else {
			ctrl |= HDMI_CTRL_HDMI;
		}
	} else {
		ctrl &= ~HDMI_CTRL_HDMI;
	}

	hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
	DRM_DEBUG("HDMI Core: %s, HDMI_CTRL=0x%08x\n",
			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)
{
@@ -1058,7 +1340,7 @@ int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)

	hdmi_audio_infoframe_init(&hdmi->audio.infoframe);

	hdmi->bridge = hdmi_bridge_init(hdmi);
	hdmi->bridge = sde_hdmi_bridge_init(hdmi);
	if (IS_ERR(hdmi->bridge)) {
		rc = PTR_ERR(hdmi->bridge);
		SDE_ERROR("failed to create HDMI bridge: %d\n", rc);
+93 −0
Original line number Diff line number Diff line
@@ -97,6 +97,36 @@ 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_register() - register hdmi display platform driver
@@ -249,6 +279,69 @@ int sde_hdmi_drm_deinit(struct sde_hdmi *display);
int sde_hdmi_get_info(struct msm_display_info *info,
				void *display);

/**
 * sde_hdmi_bridge_init() - init sde hdmi bridge
 * @hdmi:          Handle to the hdmi.
 *
 * Return: struct drm_bridge *.
 */
struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi);

/**
 * sde_hdmi_set_mode() - Set HDMI mode API.
 * @hdmi:          Handle to the hdmi.
 * @power_on:      Power on/off request.
 *
 * Return: void.
 */
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);

#else /*#ifdef CONFIG_DRM_SDE_HDMI*/
static inline void sde_hdmi_register(void)
{
+517 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2016, The Linux Foundation. All rights reserved.
 * Copyright (C) 2013 Red Hat
 * Author: Rob Clark <robdclark@gmail.com>
 *
 * 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 "sde_kms.h"
#include "sde_hdmi.h"
#include "hdmi.h"

struct sde_hdmi_bridge {
	struct drm_bridge base;
	struct hdmi *hdmi;
};
#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

void _sde_hdmi_bridge_destroy(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_power_on(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	const struct hdmi_platform_config *config = hdmi->config;
	int i, ret;

	for (i = 0; i < config->pwr_reg_cnt; i++) {
		ret = regulator_enable(hdmi->pwr_regs[i]);
		if (ret) {
			SDE_ERROR("failed to enable pwr regulator: %s (%d)\n",
					config->pwr_reg_names[i], ret);
		}
	}

	if (config->pwr_clk_cnt > 0) {
		DRM_DEBUG("pixclock: %lu", hdmi->pixclock);
		ret = clk_set_rate(hdmi->pwr_clks[0], hdmi->pixclock);
		if (ret) {
			SDE_ERROR("failed to set pixel clk: %s (%d)\n",
					config->pwr_clk_names[0], ret);
		}
	}

	for (i = 0; i < config->pwr_clk_cnt; i++) {
		ret = clk_prepare_enable(hdmi->pwr_clks[i]);
		if (ret) {
			SDE_ERROR("failed to enable pwr clk: %s (%d)\n",
					config->pwr_clk_names[i], ret);
		}
	}
}

static void _sde_hdmi_bridge_power_off(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	const struct hdmi_platform_config *config = hdmi->config;
	int i, ret;

	/* Wait for vsync */
	msleep(20);

	for (i = 0; i < config->pwr_clk_cnt; i++)
		clk_disable_unprepare(hdmi->pwr_clks[i]);

	for (i = 0; i < config->pwr_reg_cnt; i++) {
		ret = regulator_disable(hdmi->pwr_regs[i]);
		if (ret) {
			SDE_ERROR("failed to disable pwr regulator: %s (%d)\n",
					config->pwr_reg_names[i], ret);
		}
	}
}

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);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	struct hdmi_phy *phy = hdmi->phy;

	DRM_DEBUG("power up");

	if (!hdmi->power_on) {
		_sde_hdmi_bridge_power_on(bridge);
		hdmi->power_on = true;
		hdmi_audio_update(hdmi);
	}

	if (phy)
		phy->funcs->powerup(phy, hdmi->pixclock);

	sde_hdmi_set_mode(hdmi, true);

#ifdef CONFIG_DRM_MSM_HDCP
	if (hdmi->hdcp_ctrl)
		hdmi_hdcp_ctrl_on(hdmi->hdcp_ctrl);
#endif
}

static void _sde_hdmi_bridge_enable(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_disable(struct drm_bridge *bridge)
{
}

static void _sde_hdmi_bridge_post_disable(struct drm_bridge *bridge)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	struct hdmi_phy *phy = hdmi->phy;

#ifdef CONFIG_DRM_MSM_HDCP
	if (hdmi->hdcp_ctrl)
		hdmi_hdcp_ctrl_off(hdmi->hdcp_ctrl);
#endif

	DRM_DEBUG("power down");
	sde_hdmi_set_mode(hdmi, false);

	if (phy)
		phy->funcs->powerdown(phy);

	if (hdmi->power_on) {
		_sde_hdmi_bridge_power_off(bridge);
		hdmi->power_on = false;
		hdmi_audio_update(hdmi);
	}
}

static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
		 struct drm_display_mode *mode,
		 struct drm_display_mode *adjusted_mode)
{
	struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
	struct hdmi *hdmi = sde_hdmi_bridge->hdmi;
	int hstart, hend, vstart, vend;
	uint32_t frame_ctrl;

	mode = adjusted_mode;

	hdmi->pixclock = mode->clock * 1000;

	hstart = mode->htotal - mode->hsync_start;
	hend   = mode->htotal - mode->hsync_start + mode->hdisplay;

	vstart = mode->vtotal - mode->vsync_start - 1;
	vend   = mode->vtotal - mode->vsync_start + mode->vdisplay - 1;

	DRM_DEBUG(
		"htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d",
		mode->htotal, mode->vtotal, hstart, hend, vstart, vend);

	hdmi_write(hdmi, REG_HDMI_TOTAL,
			HDMI_TOTAL_H_TOTAL(mode->htotal - 1) |
			HDMI_TOTAL_V_TOTAL(mode->vtotal - 1));

	hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC,
			HDMI_ACTIVE_HSYNC_START(hstart) |
			HDMI_ACTIVE_HSYNC_END(hend));
	hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC,
			HDMI_ACTIVE_VSYNC_START(vstart) |
			HDMI_ACTIVE_VSYNC_END(vend));

	if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
		hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
				HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal));
		hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
				HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) |
				HDMI_VSYNC_ACTIVE_F2_END(vend + 1));
	} else {
		hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
				HDMI_VSYNC_TOTAL_F2_V_TOTAL(0));
		hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
				HDMI_VSYNC_ACTIVE_F2_START(0) |
				HDMI_VSYNC_ACTIVE_F2_END(0));
	}

	frame_ctrl = 0;
	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
		frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW;
	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
		frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW;
	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
		frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN;
	DRM_DEBUG("frame_ctrl=%08x\n", frame_ctrl);
	hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl);

	_sde_hdmi_bridge_setup_scrambler(hdmi, mode);

	hdmi_audio_update(hdmi);
}

static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
		.pre_enable = _sde_hdmi_bridge_pre_enable,
		.enable = _sde_hdmi_bridge_enable,
		.disable = _sde_hdmi_bridge_disable,
		.post_disable = _sde_hdmi_bridge_post_disable,
		.mode_set = _sde_hdmi_bridge_mode_set,
};


/* initialize bridge */
struct drm_bridge *sde_hdmi_bridge_init(struct hdmi *hdmi)
{
	struct drm_bridge *bridge = NULL;
	struct sde_hdmi_bridge *sde_hdmi_bridge;
	int ret;

	sde_hdmi_bridge = devm_kzalloc(hdmi->dev->dev,
			sizeof(*sde_hdmi_bridge), GFP_KERNEL);
	if (!sde_hdmi_bridge) {
		ret = -ENOMEM;
		goto fail;
	}

	sde_hdmi_bridge->hdmi = hdmi;

	bridge = &sde_hdmi_bridge->base;
	bridge->funcs = &_sde_hdmi_bridge_funcs;

	ret = drm_bridge_attach(hdmi->dev, bridge);
	if (ret)
		goto fail;

	return bridge;

fail:
	if (bridge)
		_sde_hdmi_bridge_destroy(bridge);

	return ERR_PTR(ret);
}
+14 −0
Original line number Diff line number Diff line
@@ -559,6 +559,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