msm: mdss: refine the calculation of tmds clock rate

TMDS clock rate should be concerned in case of deep color
and scrambler. In deep color case TMDS clock should be
increased in order to transmit more bits, so this clock
rate should be checked against the maximum clock rate
allowed by sink. In scrambler case correct pixel clock
should be computed depending on video format and deep
color.

CRs-Fixed: 1086894
Change-Id: I78770eea7fdd57f12d92ff40ed8043987e742024
Signed-off-by: Ray Zhang <rayz@codeaurora.org>
This commit is contained in:
Ray Zhang 2016-11-05 03:52:17 +08:00
parent 906ef866d0
commit c006d555a1
7 changed files with 204 additions and 132 deletions

View file

@ -134,6 +134,7 @@ struct hdmi_edid_ctrl {
u8 it_scan_info;
u8 ce_scan_info;
u8 cea_blks;
/* DC: MSB -> LSB: Y420_48|Y420_36|Y420_30|RGB48|RGB36|RGB30|Y444 */
u8 deep_color;
u16 physical_address;
u32 video_resolution; /* selected by user */
@ -858,6 +859,43 @@ static const u8 *hdmi_edid_find_block(const u8 *in_buf, u32 start_offset,
return NULL;
} /* hdmi_edid_find_block */
static const u8 *hdmi_edid_find_hfvsdb(const u8 *in_buf)
{
u8 len = 0, i = 0;
const u8 *vsd = NULL;
u32 vsd_offset = DBC_START_OFFSET;
u32 hf_ieee_oui = 0;
/* Find HF-VSDB with HF-OUI */
do {
vsd = hdmi_edid_find_block(in_buf, vsd_offset,
VENDOR_SPECIFIC_DATA_BLOCK, &len);
if (!vsd || !len || len > MAX_DATA_BLOCK_SIZE) {
if (i == 0)
pr_debug("%s: VSDB not found\n", __func__);
else
pr_debug("%s: no more VSDB found\n", __func__);
return NULL;
}
hf_ieee_oui = (vsd[1] << 16) | (vsd[2] << 8) | vsd[3];
if (hf_ieee_oui == HDMI_FORUM_IEEE_OUI) {
pr_debug("%s: found HF-VSDB\n", __func__);
break;
}
pr_debug("%s: Not a HF OUI 0x%x\n", __func__, hf_ieee_oui);
i++;
vsd_offset = vsd - in_buf + len + 1;
} while (1);
return vsd;
}
static void hdmi_edid_set_y420_support(struct hdmi_edid_ctrl *edid_ctrl,
u32 video_format)
{
@ -1251,62 +1289,32 @@ static void hdmi_edid_extract_speaker_allocation_data(
static void hdmi_edid_extract_sink_caps(struct hdmi_edid_ctrl *edid_ctrl,
const u8 *in_buf)
{
u8 len = 0, i = 0;
const u8 *vsd = NULL;
u32 vsd_offset = DBC_START_OFFSET;
u32 hf_ieee_oui = 0;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
pr_err("%s: invalid input\n", __func__);
return;
}
/* Find HF-VSDB with HF-OUI */
do {
vsd = hdmi_edid_find_block(in_buf, vsd_offset,
VENDOR_SPECIFIC_DATA_BLOCK, &len);
vsd = hdmi_edid_find_hfvsdb(in_buf);
if (!vsd || !len || len > MAX_DATA_BLOCK_SIZE) {
if (i == 0)
DEV_ERR("%s: VSDB not found\n", __func__);
else
DEV_DBG("%s: no more VSDB found\n", __func__);
break;
}
hf_ieee_oui = (vsd[1] << 16) | (vsd[2] << 8) | vsd[3];
if (hf_ieee_oui == HDMI_FORUM_IEEE_OUI) {
DEV_DBG("%s: found HF-VSDB\n", __func__);
break;
}
DEV_DBG("%s: Not a HF OUI 0x%x\n", __func__, hf_ieee_oui);
i++;
vsd_offset = vsd - in_buf + len + 1;
} while (1);
if (!vsd) {
DEV_DBG("%s: HF-VSDB not found\n", __func__);
return;
if (vsd) {
/* Max pixel clock is in multiples of 5Mhz. */
edid_ctrl->sink_caps.max_pclk_in_hz =
vsd[5]*5000000;
edid_ctrl->sink_caps.scdc_present =
(vsd[6] & 0x80) ? true : false;
edid_ctrl->sink_caps.scramble_support =
(vsd[6] & 0x08) ? true : false;
edid_ctrl->sink_caps.read_req_support =
(vsd[6] & 0x40) ? true : false;
edid_ctrl->sink_caps.osd_disparity =
(vsd[6] & 0x01) ? true : false;
edid_ctrl->sink_caps.dual_view_support =
(vsd[6] & 0x02) ? true : false;
edid_ctrl->sink_caps.ind_view_support =
(vsd[6] & 0x04) ? true : false;
}
/* Max pixel clock is in multiples of 5Mhz. */
edid_ctrl->sink_caps.max_pclk_in_hz =
vsd[5]*5000000;
edid_ctrl->sink_caps.scdc_present =
(vsd[6] & 0x80) ? true : false;
edid_ctrl->sink_caps.scramble_support =
(vsd[6] & 0x08) ? true : false;
edid_ctrl->sink_caps.read_req_support =
(vsd[6] & 0x40) ? true : false;
edid_ctrl->sink_caps.osd_disparity =
(vsd[6] & 0x01) ? true : false;
edid_ctrl->sink_caps.dual_view_support =
(vsd[6] & 0x02) ? true : false;
edid_ctrl->sink_caps.ind_view_support =
(vsd[6] & 0x04) ? true : false;
}
static void hdmi_edid_extract_latency_fields(struct hdmi_edid_ctrl *edid_ctrl,
@ -1404,12 +1412,19 @@ static void hdmi_edid_extract_dc(struct hdmi_edid_ctrl *edid_ctrl,
edid_ctrl->deep_color = (vsd[6] >> 0x3) & 0xF;
DEV_DBG("%s: deep color: Y444|RGB30|RGB36|RGB48: (%d|%d|%d|%d)\n",
__func__,
vsd = hdmi_edid_find_hfvsdb(in_buf);
if (vsd)
edid_ctrl->deep_color |= (vsd[7] & 0x07) << 4;
pr_debug("deep color: Y444|RGB30|RGB36|RGB48|Y420_30|Y420_36|Y420_48: (%d|%d|%d|%d|%d|%d|%d)\n",
(int) (edid_ctrl->deep_color & BIT(0)) >> 0,
(int) (edid_ctrl->deep_color & BIT(1)) >> 1,
(int) (edid_ctrl->deep_color & BIT(2)) >> 2,
(int) (edid_ctrl->deep_color & BIT(3)) >> 3);
(int) (edid_ctrl->deep_color & BIT(3)) >> 3,
(int) (edid_ctrl->deep_color & BIT(4)) >> 4,
(int) (edid_ctrl->deep_color & BIT(5)) >> 5,
(int) (edid_ctrl->deep_color & BIT(6)) >> 6);
}
static u32 hdmi_edid_check_header(const u8 *edid_buf)
@ -2398,8 +2413,8 @@ u32 hdmi_edid_get_sink_mode(void *input)
*
* This API returns deep color for different formats supported by sink.
* Deep color support for Y444 (BIT(0)), RGB30 (BIT(1)), RGB36 (BIT(2),
* RGB 48 (BIT(3)) is provided in a 8 bit integer. The MSB 8 bits are
* not used.
* RGB 48 (BIT(3)), Y420_30 (BIT(4)), Y420_36 (BIT(5)), Y420_48 (BIT(6))
* is provided in a 8 bit integer. The MSB 8 bits are not used.
*
* Return: deep color data.
*/
@ -2415,6 +2430,25 @@ u8 hdmi_edid_get_deep_color(void *input)
return edid_ctrl->deep_color;
}
/**
* hdmi_edid_get_max_pclk() - get max pclk supported. Sink side's limitation
* should be concerned as well.
* @input: edid parser data
*
* Return: max pclk rate
*/
u32 hdmi_edid_get_max_pclk(void *input)
{
struct hdmi_edid_ctrl *edid_ctrl = (struct hdmi_edid_ctrl *)input;
if (!edid_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
return 0;
}
return edid_ctrl->init_data.max_pclk_khz;
}
/**
* hdmi_edid_get_hdr_data() - get the HDR capabiliies of the sink
* @input: edid parser data

View file

@ -60,6 +60,7 @@ void *hdmi_edid_init(struct hdmi_edid_init_data *init_data);
bool hdmi_edid_is_s3d_mode_supported(void *input,
u32 video_mode, u32 s3d_mode);
u8 hdmi_edid_get_deep_color(void *edid_ctrl);
u32 hdmi_edid_get_max_pclk(void *edid_ctrl);
void hdmi_edid_get_hdr_data(void *edid_ctrl,
struct hdmi_edid_hdr_data **hdr_data);

View file

@ -602,14 +602,51 @@ end:
return rc;
}
static int hdmi_panel_setup_dc(struct hdmi_panel *panel)
{
u32 hdmi_ctrl_reg;
u32 vbi_pkt_reg;
int rc = 0;
pr_debug("Deep Color: %s\n", panel->data->dc_enable ? "ON" : "OFF");
/* enable deep color if supported */
if (panel->data->dc_enable) {
hdmi_ctrl_reg = DSS_REG_R(panel->io, HDMI_CTRL);
/* GC CD override */
hdmi_ctrl_reg |= BIT(27);
/* enable deep color for RGB888 30 bits */
hdmi_ctrl_reg |= BIT(24);
DSS_REG_W(panel->io, HDMI_CTRL, hdmi_ctrl_reg);
/* Enable GC_CONT and GC_SEND in General Control Packet
* (GCP) register so that deep color data is
* transmitted to the sink on every frame, allowing
* the sink to decode the data correctly.
*
* GC_CONT: 0x1 - Send GCP on every frame
* GC_SEND: 0x1 - Enable GCP Transmission
*/
vbi_pkt_reg = DSS_REG_R(panel->io, HDMI_VBI_PKT_CTRL);
vbi_pkt_reg |= BIT(5) | BIT(4);
DSS_REG_W(panel->io, HDMI_VBI_PKT_CTRL, vbi_pkt_reg);
}
return rc;
}
static int hdmi_panel_setup_scrambler(struct hdmi_panel *panel)
{
int rc = 0;
int timeout_hsync;
u32 reg_val = 0;
u32 tmds_clock_ratio = 0;
u32 tmds_clock = 0;
bool scrambler_on = false;
struct msm_hdmi_mode_timing_info *timing = NULL;
struct mdss_panel_info *pinfo = NULL;
if (!panel) {
pr_err("invalid input\n");
@ -622,13 +659,22 @@ static int hdmi_panel_setup_scrambler(struct hdmi_panel *panel)
return -EINVAL;
}
pinfo = panel->data->pinfo;
if (!pinfo) {
pr_err("invalid panel data\n");
return -EINVAL;
}
/* Scrambling is supported from HDMI TX 4.0 */
if (panel->version < HDMI_TX_SCRAMBLER_MIN_TX_VERSION) {
pr_debug("scrambling not supported by tx\n");
return 0;
}
if (timing->pixel_freq > HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ) {
tmds_clock = hdmi_tx_setup_tmds_clk_rate(timing->pixel_freq,
pinfo->out_format, panel->data->dc_enable);
if (tmds_clock > HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ) {
scrambler_on = true;
tmds_clock_ratio = 1;
} else {
@ -798,6 +844,12 @@ static int hdmi_panel_power_on(void *input)
pr_err("scrambler setup failed. rc=%d\n", rc);
goto err;
}
rc = hdmi_panel_setup_dc(panel);
if (rc) {
pr_err("Deep Color setup failed. rc=%d\n", rc);
goto err;
}
end:
panel->on = true;

View file

@ -28,6 +28,7 @@
* @infoframe: set to true if infoframes should be sent to sink
* @is_it_content: set to true if content is IT
* @scrambler: set to true if scrambler needs to be enabled
* @dc_enable: set to true if deep color is enabled
*/
struct hdmi_panel_data {
struct mdss_panel_info *pinfo;
@ -39,6 +40,7 @@ struct hdmi_panel_data {
bool infoframe;
bool is_it_content;
bool scrambler;
bool dc_enable;
};
/**

View file

@ -35,6 +35,7 @@
#include "mdss.h"
#include "mdss_panel.h"
#include "mdss_hdmi_mhl.h"
#include "mdss_hdmi_util.h"
#define DRV_NAME "hdmi-tx"
#define COMPATIBLE_NAME "qcom,hdmi-tx"
@ -58,13 +59,6 @@
#define AUDIO_POLL_SLEEP_US (5 * 1000)
#define AUDIO_POLL_TIMEOUT_US (AUDIO_POLL_SLEEP_US * 1000)
#define HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO 2
#define HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
#define HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
#define HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ 340000
#define HDMI_TX_SCRAMBLER_TIMEOUT_MSEC 200
/* Maximum pixel clock rates for hdmi tx */
#define HDMI_DEFAULT_MAX_PCLK_RATE 148500
#define HDMI_TX_3_MAX_PCLK_RATE 297000
@ -111,7 +105,6 @@ static irqreturn_t hdmi_tx_isr(int irq, void *data);
static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl);
static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
enum hdmi_tx_power_module_type module, int enable);
static int hdmi_tx_setup_tmds_clk_rate(struct hdmi_tx_ctrl *hdmi_ctrl);
static void hdmi_tx_fps_work(struct work_struct *work);
static int hdmi_tx_pinctrl_set_state(struct hdmi_tx_ctrl *hdmi_ctrl,
enum hdmi_tx_power_module_type module, bool active);
@ -318,11 +311,29 @@ static inline bool hdmi_tx_metadata_type_one(struct hdmi_tx_ctrl *hdmi_ctrl)
return hdr_data->metadata_type_one;
}
static inline bool hdmix_tx_sink_dc_support(struct hdmi_tx_ctrl *hdmi_ctrl)
{
void *edid_fd = hdmi_tx_get_fd(HDMI_TX_FEAT_EDID);
if (hdmi_ctrl->panel_data.panel_info.out_format == MDP_Y_CBCR_H2V2)
return (hdmi_edid_get_deep_color(edid_fd) & BIT(4));
else
return (hdmi_edid_get_deep_color(edid_fd) & BIT(1));
}
static inline bool hdmi_tx_dc_support(struct hdmi_tx_ctrl *hdmi_ctrl)
{
return hdmi_ctrl->dc_feature_on && hdmi_ctrl->dc_support &&
(hdmi_edid_get_deep_color(
hdmi_tx_get_fd(HDMI_TX_FEAT_EDID)) & BIT(1));
/* actual pixel clock if deep color is enabled */
void *edid_fd = hdmi_tx_get_fd(HDMI_TX_FEAT_EDID);
u32 tmds_clk_with_dc = hdmi_tx_setup_tmds_clk_rate(
hdmi_ctrl->timing.pixel_freq,
hdmi_ctrl->panel.pinfo->out_format,
true);
return hdmi_ctrl->dc_feature_on &&
hdmi_ctrl->dc_support &&
hdmix_tx_sink_dc_support(hdmi_ctrl) &&
(tmds_clk_with_dc <= hdmi_edid_get_max_pclk(edid_fd));
}
static const char *hdmi_tx_pm_name(enum hdmi_tx_power_module_type module)
@ -349,7 +360,10 @@ static const char *hdmi_tx_io_name(u32 type)
static void hdmi_tx_audio_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
{
if (hdmi_ctrl && hdmi_ctrl->audio_ops.on) {
u32 pclk = hdmi_tx_setup_tmds_clk_rate(hdmi_ctrl);
u32 pclk = hdmi_tx_setup_tmds_clk_rate(
hdmi_ctrl->timing.pixel_freq,
hdmi_ctrl->panel.pinfo->out_format,
hdmi_ctrl->panel.dc_enable);
hdmi_ctrl->audio_ops.on(hdmi_ctrl->audio_data,
pclk, &hdmi_ctrl->audio_params);
@ -2345,7 +2359,6 @@ static void hdmi_tx_set_mode(struct hdmi_tx_ctrl *hdmi_ctrl, u32 power_on)
struct dss_io_data *io = NULL;
/* Defaults: Disable block, HDMI mode */
u32 hdmi_ctrl_reg = BIT(1);
u32 vbi_pkt_reg;
if (!hdmi_ctrl) {
DEV_ERR("%s: invalid input\n", __func__);
@ -2383,27 +2396,6 @@ static void hdmi_tx_set_mode(struct hdmi_tx_ctrl *hdmi_ctrl, u32 power_on)
* longer be used
*/
hdmi_ctrl_reg |= BIT(31);
/* enable deep color if supported */
if (hdmi_tx_dc_support(hdmi_ctrl)) {
/* GC CD override */
hdmi_ctrl_reg |= BIT(27);
/* enable deep color for RGB888 30 bits */
hdmi_ctrl_reg |= BIT(24);
/* Enable GC_CONT and GC_SEND in General Control Packet
* (GCP) register so that deep color data is
* transmitted to the sink on every frame, allowing
* the sink to decode the data correctly.
*
* GC_CONT: 0x1 - Send GCP on every frame
* GC_SEND: 0x1 - Enable GCP Transmission
*/
vbi_pkt_reg = DSS_REG_R(io, HDMI_VBI_PKT_CTRL);
vbi_pkt_reg |= BIT(5) | BIT(4);
DSS_REG_W(io, HDMI_VBI_PKT_CTRL, vbi_pkt_reg);
}
}
DSS_REG_W(io, HDMI_CTRL, hdmi_ctrl_reg);
@ -2973,44 +2965,6 @@ static int hdmi_tx_get_cable_status(struct platform_device *pdev, u32 vote)
return hpd;
}
static int hdmi_tx_setup_tmds_clk_rate(struct hdmi_tx_ctrl *hdmi_ctrl)
{
u32 rate = 0;
struct msm_hdmi_mode_timing_info *timing = NULL;
u32 rate_ratio;
if (!hdmi_ctrl) {
DEV_ERR("%s: Bad input parameters\n", __func__);
goto end;
}
timing = &hdmi_ctrl->timing;
if (!timing) {
DEV_ERR("%s: Invalid timing info\n", __func__);
goto end;
}
switch (hdmi_ctrl->panel_data.panel_info.out_format) {
case MDP_Y_CBCR_H2V2:
rate_ratio = HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
case MDP_Y_CBCR_H2V1:
rate_ratio = HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
default:
rate_ratio = HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
}
rate = timing->pixel_freq / rate_ratio;
if (hdmi_tx_dc_support(hdmi_ctrl))
rate += rate >> 2;
end:
return rate;
}
static inline bool hdmi_tx_hw_is_cable_connected(struct hdmi_tx_ctrl *hdmi_ctrl)
{
return DSS_REG_R(&hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO],
@ -3144,16 +3098,14 @@ static int hdmi_tx_power_on(struct hdmi_tx_ctrl *hdmi_ctrl)
hdmi_ctrl->vic);
hdmi_ctrl->panel.scrambler = hdmi_edid_get_sink_scrambler_support(
edata);
hdmi_ctrl->panel.dc_enable = hdmi_tx_dc_support(hdmi_ctrl);
if (hdmi_ctrl->panel_ops.on)
hdmi_ctrl->panel_ops.on(pdata);
pixel_clk = hdmi_ctrl->timing.pixel_freq * 1000;
if (panel_data->panel_info.out_format == MDP_Y_CBCR_H2V2)
pixel_clk >>= 1;
else if (hdmi_tx_dc_support(hdmi_ctrl))
pixel_clk += pixel_clk >> 2;
pixel_clk = hdmi_tx_setup_tmds_clk_rate(hdmi_ctrl->timing.pixel_freq,
hdmi_ctrl->panel.pinfo->out_format,
hdmi_ctrl->panel.dc_enable) * 1000;
DEV_DBG("%s: setting pixel clk %d\n", __func__, pixel_clk);

View file

@ -15,6 +15,7 @@
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/msm_mdp.h>
#include "mdss_hdmi_util.h"
#define RESOLUTION_NAME_STR_LEN 30
@ -26,6 +27,10 @@
#define HDMI_SCDC_UNKNOWN_REGISTER "Unknown register"
#define HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO 2
#define HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
#define HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
static char res_buf[RESOLUTION_NAME_STR_LEN];
enum trigger_mode {
@ -738,6 +743,30 @@ ssize_t hdmi_get_video_3d_fmt_2string(u32 format, char *buf, u32 size)
return len;
} /* hdmi_get_video_3d_fmt_2string */
int hdmi_tx_setup_tmds_clk_rate(u32 pixel_freq, u32 out_format, bool dc_enable)
{
u32 rate_ratio;
switch (out_format) {
case MDP_Y_CBCR_H2V2:
rate_ratio = HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
case MDP_Y_CBCR_H2V1:
rate_ratio = HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
default:
rate_ratio = HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO;
break;
}
pixel_freq /= rate_ratio;
if (dc_enable)
pixel_freq += pixel_freq >> 2;
return pixel_freq;
}
static void hdmi_ddc_trigger(struct hdmi_tx_ddc_ctrl *ddc_ctrl,
enum trigger_mode mode, bool seg)
{

View file

@ -16,6 +16,7 @@
#include "video/msm_hdmi_modes.h"
#include "mdss_panel.h"
#include "mdss_hdmi_panel.h"
/* HDMI_TX Registers */
#define HDMI_CTRL (0x00000000)
@ -495,6 +496,7 @@ bool hdmi_is_valid_resv_timing(int mode);
void hdmi_reset_resv_timing_info(void);
int hdmi_panel_get_vic(struct mdss_panel_info *pinfo,
struct hdmi_util_ds_data *ds_data);
int hdmi_tx_setup_tmds_clk_rate(u32 pixel_freq, u32 out_format, bool dc_enable);
/* todo: Fix this. Right now this is defined in mdss_hdmi_tx.c */
void *hdmi_get_featuredata_from_sysfs_dev(struct device *device, u32 type);