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

Commit 495c91e3 authored by Ajay Singh Parmar's avatar Ajay Singh Parmar
Browse files

msm: mdss: hdmi: add dynamic fps support



Add dynamic fps support for hdmi to update the resolution
timings on the interface by either changing the clock rate
or by modifying the porch values or both. Dynamic fps feature
can by used for hdmi to support different use cases like
matching input stream fps with hdmi out.

Change-Id: Ia305e1eb5d3da1dfbf868650e5ee84018255476b
Signed-off-by: default avatarAjay Singh Parmar <aparmar@codeaurora.org>
parent 93157275
Loading
Loading
Loading
Loading
+243 −1
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@
#define HDMI_DEFAULT_MAX_PCLK_RATE         148500
#define HDMI_TX_3_MAX_PCLK_RATE            297000
#define HDMI_TX_4_MAX_PCLK_RATE            600000
#define HDMI_TX_KHZ_TO_HZ                  1000U

/* Enable HDCP by default */
static bool hdcp_feature_on = true;
@@ -90,6 +91,9 @@ static bool hdcp_feature_on = true;
#define VENDOR_IFRAME_LINE_NUMBER 3
#define MAX_EDID_READ_RETRY	5

#define HDMI_TX_MIN_FPS 20000
#define HDMI_TX_MAX_FPS 120000

enum {
	DATA_BYTE_1,
	DATA_BYTE_2,
@@ -162,6 +166,7 @@ static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
static int hdmi_tx_setup_tmds_clk_rate(struct hdmi_tx_ctrl *hdmi_ctrl);
static void hdmi_tx_set_vendor_specific_infoframe(
	struct hdmi_tx_ctrl *hdmi_ctrl);
static void hdmi_tx_fps_work(struct work_struct *work);

static struct mdss_hw hdmi_tx_hw = {
	.hw_ndx = MDSS_HW_HDMI,
@@ -390,12 +395,15 @@ static int hdmi_tx_get_vic_from_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl,
			new_vic = pinfo->vic;
			DEV_DBG("%s: %s is supported\n", __func__,
				msm_hdmi_mode_2string(new_vic));
			pinfo->lcdc.frame_rate = info.refresh_rate;
		} else {
			DEV_ERR("%s: invalid or not supported vic %d\n",
				__func__, pinfo->vic);
			return -EPERM;
		}
	} else {
		u64 pclk;

		timing.active_h = pinfo->xres;
		timing.back_porch_h = pinfo->lcdc.h_back_porch;
		timing.front_porch_h = pinfo->lcdc.h_front_porch;
@@ -416,7 +424,10 @@ static int hdmi_tx_get_vic_from_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl,
			timing.active_v, timing.back_porch_v,
			timing.front_porch_v, timing.pulse_width_v, v_total);

		timing.pixel_freq = ((unsigned long int)pinfo->clk_rate / 1000);
		pclk = pinfo->clk_rate;
		do_div(pclk, HDMI_TX_KHZ_TO_HZ);

		timing.pixel_freq = (unsigned long) pclk;
		if (h_total && v_total) {
			timing.refresh_rate = ((timing.pixel_freq * 1000) /
				(h_total * v_total)) * 1000;
@@ -742,6 +753,41 @@ end:
	return ret;
}

static int hdmi_tx_update_pixel_clk(struct hdmi_tx_ctrl *hdmi_ctrl, int fps)
{
	struct dss_module_power *power_data = NULL;
	struct mdss_panel_info *pinfo;
	int rc = 0;

	if (!hdmi_ctrl) {
		DEV_ERR("%s: invalid input\n", __func__);
		rc = -EINVAL;
		goto end;
	}

	pinfo = &hdmi_ctrl->panel_data.panel_info;

	power_data = &hdmi_ctrl->pdata.power_data[HDMI_TX_CORE_PM];
	if (!power_data) {
		DEV_ERR("%s: Error: invalid power data\n", __func__);
		rc = -EINVAL;
		goto end;
	}

	if (power_data->clk_config->rate == pinfo->clk_rate) {
		rc = -EINVAL;
		goto end;
	}

	power_data->clk_config->rate = pinfo->clk_rate;

	DEV_DBG("%s: rate %ld\n", __func__, power_data->clk_config->rate);

	msm_dss_clk_set_rate(power_data->clk_config, power_data->num_clk);
end:
	return rc;
}

static ssize_t hdmi_tx_sysfs_wta_hot_plug(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t count)
{
@@ -1905,6 +1951,7 @@ static int hdmi_tx_init_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl)
	pinfo->lcdc.v_back_porch = timing.back_porch_v;
	pinfo->lcdc.v_front_porch = timing.front_porch_v;
	pinfo->lcdc.v_pulse_width = timing.pulse_width_v;
	pinfo->lcdc.frame_rate = timing.refresh_rate;

	pinfo->type = DTV_PANEL;
	pinfo->pdest = DISPLAY_3;
@@ -1912,6 +1959,9 @@ static int hdmi_tx_init_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl)
	pinfo->bpp = 24;
	pinfo->fb_num = 1;

	pinfo->min_fps = HDMI_TX_MIN_FPS;
	pinfo->max_fps = HDMI_TX_MAX_FPS;

	pinfo->lcdc.border_clr = 0; /* blk */
	pinfo->lcdc.underflow_clr = 0xff; /* blue */
	pinfo->lcdc.hsync_skew = 0;
@@ -2105,6 +2155,18 @@ end:
	return ret;
} /* hdmi_tx_check_capability */

static void hdmi_tx_update_panel_data(struct hdmi_tx_ctrl *hdmi_ctrl)
{
	struct mdss_panel_info *pinfo = &hdmi_ctrl->panel_data.panel_info;

	pinfo->saved_total = mdss_panel_get_htotal(pinfo, true);
	pinfo->saved_fporch = hdmi_ctrl->vid_cfg.timing.front_porch_h;

	pinfo->current_fps = hdmi_ctrl->vid_cfg.timing.refresh_rate;
	pinfo->default_fps = hdmi_ctrl->vid_cfg.timing.refresh_rate;
	pinfo->lcdc.frame_rate = hdmi_ctrl->vid_cfg.timing.refresh_rate;
}

static int hdmi_tx_set_video_fmt(struct hdmi_tx_ctrl *hdmi_ctrl,
	struct mdss_panel_info *pinfo)
{
@@ -2183,9 +2245,27 @@ static int hdmi_tx_set_video_fmt(struct hdmi_tx_ctrl *hdmi_ctrl,
		hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
		vid_cfg->vic, false);

	hdmi_tx_update_panel_data(hdmi_ctrl);

	return res_changed;
} /* hdmi_tx_set_video_fmt */

static bool hdmi_tx_check_for_video_update(struct hdmi_tx_ctrl *hdmi_ctrl)
{
	struct msm_hdmi_mode_timing_info *timing = &hdmi_ctrl->vid_cfg.timing;
	struct mdss_panel_info *pinfo = &hdmi_ctrl->panel_data.panel_info;

	if (timing->back_porch_h != pinfo->lcdc.h_back_porch ||
		timing->front_porch_h != pinfo->lcdc.h_front_porch ||
		timing->pulse_width_h != pinfo->lcdc.h_pulse_width ||
		timing->back_porch_v != pinfo->lcdc.v_back_porch ||
		timing->front_porch_v != pinfo->lcdc.v_front_porch ||
		timing->pulse_width_v != pinfo->lcdc.v_pulse_width)
		return true;

	return false;
}

static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
{
	u32 total_v   = 0;
@@ -2197,6 +2277,7 @@ static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
	u32 div       = 0;
	struct dss_io_data *io = NULL;
	struct msm_hdmi_mode_timing_info *timing = NULL;
	struct mdss_panel_info *pinfo;

	if (!hdmi_ctrl) {
		DEV_ERR("%s: invalid input\n", __func__);
@@ -2214,6 +2295,9 @@ static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
		DEV_ERR("%s: Core io is not initialized\n", __func__);
		return -EPERM;
	}

	pinfo = &hdmi_ctrl->panel_data.panel_info;

	/*
	 * In case of YUV420 output, Horizontal timing parameters should be
	 * reduced by half
@@ -2221,6 +2305,35 @@ static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
	if (hdmi_ctrl->vid_cfg.avi_iframe.pixel_format == MDP_Y_CBCR_H2V2)
		div = 1;

	if (pinfo->dynamic_fps) {
		if (!hdmi_tx_check_for_video_update(hdmi_ctrl))
			return -EINVAL;

		if (pinfo->dfps_update ==
			DFPS_IMMEDIATE_PORCH_UPDATE_MODE_HFP ||
			pinfo->dfps_update ==
				DFPS_IMMEDIATE_MULTI_UPDATE_MODE_CLK_HFP) {
			DEV_DBG("%s: hfp=%d, hbp=%d, hpw=%d\n", __func__,
				pinfo->lcdc.h_front_porch,
				pinfo->lcdc.h_back_porch,
				pinfo->lcdc.h_pulse_width);

			timing->back_porch_h = pinfo->lcdc.h_back_porch;
			timing->front_porch_h = pinfo->lcdc.h_front_porch;
			timing->pulse_width_h = pinfo->lcdc.h_pulse_width;
		} else if (pinfo->dfps_update ==
			DFPS_IMMEDIATE_PORCH_UPDATE_MODE_VFP) {
			DEV_DBG("%s: vfp=%d, vbp=%d, vpw=%d\n", __func__,
				pinfo->lcdc.v_front_porch,
				pinfo->lcdc.v_back_porch,
				pinfo->lcdc.v_pulse_width);

			timing->back_porch_v = pinfo->lcdc.v_back_porch;
			timing->front_porch_v = pinfo->lcdc.v_front_porch;
			timing->pulse_width_v = pinfo->lcdc.v_pulse_width;
		}
	}

	total_h = (hdmi_tx_get_h_total(timing) >> div) - 1;
	total_v = hdmi_tx_get_v_total(timing) - 1;

@@ -3957,6 +4070,7 @@ static int hdmi_tx_dev_init(struct hdmi_tx_ctrl *hdmi_ctrl)
	init_completion(&hdmi_ctrl->hpd_int_done);

	INIT_WORK(&hdmi_ctrl->hpd_int_work, hdmi_tx_hpd_int_work);
	INIT_WORK(&hdmi_ctrl->fps_work, hdmi_tx_fps_work);
	INIT_WORK(&hdmi_ctrl->cable_notify_work, hdmi_tx_cable_notify_work);
	INIT_DELAYED_WORK(&hdmi_ctrl->hdcp_cb_work, hdmi_tx_hdcp_cb_work);

@@ -4088,6 +4202,127 @@ static char *hdmi_tx_get_event_name(int event)
	}
}

static void hdmi_tx_update_fps(struct hdmi_tx_ctrl *hdmi_ctrl)
{
	int rc = 0, vic = 0;
	u64 pclk;
	struct mdss_panel_data *pdata;
	struct mdss_panel_info *pinfo;
	struct msm_hdmi_mode_timing_info timing = {0};

	if (!hdmi_ctrl) {
		DEV_ERR("%s: invalid input\n", __func__);
		return;
	}

	pdata = &hdmi_ctrl->panel_data;
	pinfo = &pdata->panel_info;

	if (!pinfo->dynamic_fps) {
		DEV_DBG("%s: Dynamic fps not enabled\n", __func__);
		return;
	}

	if (hdmi_ctrl->dynamic_fps == pinfo->current_fps) {
		DEV_DBG("%s: Panel is already at this FPS: %d\n",
			__func__, hdmi_ctrl->dynamic_fps);
		return;
	}

	if (hdmi_tx_is_hdcp_enabled(hdmi_ctrl))
		hdmi_tx_hdcp_off(hdmi_ctrl);

	if (pinfo->dfps_update == DFPS_IMMEDIATE_MULTI_UPDATE_MODE_CLK_HFP) {
		if (hdmi_tx_video_setup(hdmi_ctrl)) {
			DEV_DBG("%s: no change in video timing\n", __func__);
			return;
		}

		if (hdmi_tx_update_pixel_clk(hdmi_ctrl,
			hdmi_ctrl->dynamic_fps)) {
			DEV_DBG("%s: no change in clk\n", __func__);
			return;
		}

		pinfo->saved_total = mdss_panel_get_htotal(pinfo, true);
		pinfo->saved_fporch = pinfo->lcdc.h_front_porch;
	} else if (pinfo->dfps_update == DFPS_IMMEDIATE_PORCH_UPDATE_MODE_HFP) {
		if (hdmi_tx_video_setup(hdmi_ctrl)) {
			DEV_DBG("%s: no change in video timing\n", __func__);
			return;
		}

		pinfo->saved_total = mdss_panel_get_htotal(pinfo, true);
		pinfo->saved_fporch = pinfo->lcdc.h_front_porch;
	} else if (pinfo->dfps_update == DFPS_IMMEDIATE_PORCH_UPDATE_MODE_VFP) {
		if (hdmi_tx_video_setup(hdmi_ctrl)) {
			DEV_DBG("%s: no change in video timing\n", __func__);
			return;
		}

		pinfo->saved_total = mdss_panel_get_vtotal(pinfo);
		pinfo->saved_fporch = pinfo->lcdc.v_front_porch;
	} else if (pinfo->dfps_update == DFPS_IMMEDIATE_CLK_UPDATE_MODE) {
		if (hdmi_tx_update_pixel_clk(hdmi_ctrl,
			hdmi_ctrl->dynamic_fps)) {
			DEV_DBG("%s: no change in clk\n", __func__);
			return;
		}
	}

	pinfo->current_fps = hdmi_ctrl->dynamic_fps;
	pinfo->default_fps = hdmi_ctrl->dynamic_fps;
	pinfo->lcdc.frame_rate = hdmi_ctrl->dynamic_fps;
	pinfo->dynamic_fps = false;

	rc = hdmi_get_supported_mode(&timing, &hdmi_ctrl->ds_data,
		hdmi_ctrl->vid_cfg.vic);

	if (rc || !timing.supported) {
		DEV_ERR("%s: timing details\n", __func__);
		return;
	}

	timing.back_porch_h = pinfo->lcdc.h_back_porch;
	timing.front_porch_h = pinfo->lcdc.h_front_porch;
	timing.pulse_width_h = pinfo->lcdc.h_pulse_width;

	timing.back_porch_v = pinfo->lcdc.v_back_porch;
	timing.front_porch_v = pinfo->lcdc.v_front_porch;
	timing.pulse_width_v = pinfo->lcdc.v_pulse_width;

	timing.refresh_rate = hdmi_ctrl->dynamic_fps;

	pclk = pinfo->clk_rate;
	do_div(pclk, HDMI_TX_KHZ_TO_HZ);
	timing.pixel_freq = (unsigned long) pclk;

	hdmi_ctrl->vid_cfg.timing = timing;

	vic = hdmi_get_video_id_code(&timing, &hdmi_ctrl->ds_data);

	if (vic > 0 && hdmi_ctrl->vid_cfg.vic != vic) {
		hdmi_ctrl->vid_cfg.vic = vic;
		DEV_DBG("%s: switched to new resolution id %d\n",
			__func__, vic);
	}

	hdmi_tx_start_hdcp(hdmi_ctrl);
}

static void hdmi_tx_fps_work(struct work_struct *work)
{
	struct hdmi_tx_ctrl *hdmi_ctrl = NULL;

	hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, fps_work);
	if (!hdmi_ctrl) {
		DEV_DBG("%s: invalid input\n", __func__);
		return;
	}

	hdmi_tx_update_fps(hdmi_ctrl);
}

static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
	int event, void *arg)
{
@@ -4100,6 +4335,13 @@ static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
		return -EINVAL;
	}

	/* UPDATE FPS is called from atomic context */
	if (event == MDSS_EVENT_PANEL_UPDATE_FPS) {
		hdmi_ctrl->dynamic_fps = (u32) (unsigned long)arg;
		queue_work(hdmi_ctrl->workq, &hdmi_ctrl->fps_work);
		return rc;
	}

	mutex_lock(&hdmi_ctrl->tx_lock);

	DEV_DBG("%s: event = %s suspend=%d, hpd_feature=%d\n", __func__,
+2 −0
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ struct hdmi_tx_ctrl {
	u32 hpd_feature_on;
	u32 hpd_initialized;
	u32 vote_hdmi_core_on;
	u32 dynamic_fps;
	u8  timing_gen_on;
	u8  mhl_hpd_on;
	u8  hdcp_status;
@@ -157,6 +158,7 @@ struct hdmi_tx_ctrl {
	struct hdmi_util_ds_data ds_data;
	struct completion hpd_int_done;
	struct work_struct hpd_int_work;
	struct work_struct fps_work;
	struct delayed_work hdcp_cb_work;

	struct work_struct cable_notify_work;
+4 −20
Original line number Diff line number Diff line
@@ -595,29 +595,13 @@ int hdmi_get_video_id_code(struct msm_hdmi_mode_timing_info *timing_in,
		break;
	}

	if (vic < 0) {
		for (i = 0; i < HDMI_VFRMT_MAX; i++) {
			ret = hdmi_get_supported_mode(&supported_timing,
				ds_data, i);
			if (ret || !supported_timing.supported)
				continue;
			if (timing_in->active_h != supported_timing.active_h)
				continue;
			if (timing_in->active_v != supported_timing.active_v)
				continue;
			vic = (int)supported_timing.video_format;
			break;
		}
	}

	if (vic < 0) {
	if (vic < 0)
		pr_err("timing is not supported h=%d v=%d\n",
			timing_in->active_h, timing_in->active_v);
	}

exit:
	else
		pr_debug("vic = %d timing = %s\n", vic,
			msm_hdmi_mode_2string((u32)vic));
exit:

	return vic;
} /* hdmi_get_video_id_code */