summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c15
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h9
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c87
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c73
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h6
-rw-r--r--drivers/gpu/drm/msm/msm_kms.h18
6 files changed, 200 insertions, 8 deletions
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
index b30e894ebc4d..1b295949122b 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
@@ -556,13 +556,13 @@ static const struct file_operations sde_hdmi_hdcp_state_fops = {
.read = _sde_hdmi_hdcp_state_read,
};
-static u64 _sde_hdmi_clip_valid_pclk(struct drm_display_mode *mode, u64 pclk_in)
+static u64 _sde_hdmi_clip_valid_pclk(struct hdmi *hdmi, u64 pclk_in)
{
u32 pclk_delta, pclk;
u64 pclk_clip = pclk_in;
/* as per standard, 0.5% of deviation is allowed */
- pclk = mode->clock * HDMI_KHZ_TO_HZ;
+ pclk = hdmi->pixclock;
pclk_delta = pclk * 5 / 1000;
if (pclk_in < (pclk - pclk_delta))
@@ -700,7 +700,6 @@ static void sde_hdmi_tx_hdcp_cb_work(struct work_struct *work)
static int _sde_hdmi_update_pll_delta(struct sde_hdmi *display, s32 ppm)
{
struct hdmi *hdmi = display->ctrl.ctrl;
- struct drm_display_mode *current_mode = &display->mode;
u64 cur_pclk, dst_pclk;
u64 clip_pclk;
int rc = 0;
@@ -725,7 +724,7 @@ static int _sde_hdmi_update_pll_delta(struct sde_hdmi *display, s32 ppm)
dst_pclk = cur_pclk * (1000000000 + ppm);
do_div(dst_pclk, 1000000000);
- clip_pclk = _sde_hdmi_clip_valid_pclk(current_mode, dst_pclk);
+ clip_pclk = _sde_hdmi_clip_valid_pclk(hdmi, dst_pclk);
/* update pclk */
if (clip_pclk != cur_pclk) {
@@ -2126,9 +2125,13 @@ static int sde_hdmi_tx_check_capability(struct sde_hdmi *sde_hdmi)
}
}
- SDE_DEBUG("%s: Features <HDMI:%s, HDCP:%s>\n", __func__,
+ if (sde_hdmi->hdmi_tx_major_version >= HDMI_TX_VERSION_4)
+ sde_hdmi->dc_feature_supported = true;
+
+ SDE_DEBUG("%s: Features <HDMI:%s, HDCP:%s, Deep Color:%s>\n", __func__,
hdmi_disabled ? "OFF" : "ON",
- hdcp_disabled ? "OFF" : "ON");
+ hdcp_disabled ? "OFF" : "ON",
+ sde_hdmi->dc_feature_supported ? "ON" : "OFF");
if (hdmi_disabled) {
DEV_ERR("%s: HDMI disabled\n", __func__);
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
index 6b9bcfec031b..743d34d05e57 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
@@ -33,6 +33,9 @@
#include "sde_hdmi_util.h"
#include "sde_hdcp.h"
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
#ifdef HDMI_DEBUG_ENABLE
#define SDE_HDMI_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args)
#else
@@ -110,6 +113,8 @@ enum hdmi_tx_feature_type {
* @client_notify_pending: If there is client notification pending.
* @irq_domain: IRQ domain structure.
* @pll_update_enable: if it's allowed to update HDMI PLL ppm.
+ * @dc_enable: If deep color is enabled. Only DC_30 so far.
+ * @dc_feature_supported: If deep color feature is supported.
* @notifier: CEC notifider to convey physical address information.
* @root: Debug fs root entry.
*/
@@ -161,6 +166,8 @@ struct sde_hdmi {
struct irq_domain *irq_domain;
struct cec_notifier *notifier;
bool pll_update_enable;
+ bool dc_enable;
+ bool dc_feature_supported;
struct delayed_work hdcp_cb_work;
struct dss_io_data io[HDMI_TX_MAX_IO];
@@ -188,6 +195,8 @@ enum hdmi_tx_scdc_access_type {
#define HDMI_KHZ_TO_HZ 1000
#define HDMI_MHZ_TO_HZ 1000000
+#define HDMI_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO 2
+#define HDMI_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
/* Maximum pixel clock rates for hdmi tx */
#define HDMI_DEFAULT_MAX_PCLK_RATE 148500
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 24d2320683e4..35b237e5eafe 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_bridge.c
@@ -345,7 +345,9 @@ static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
return 0;
}
- if (mode->clock > HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ) {
+ /* use actual clock instead of mode clock */
+ if (hdmi->pixclock >
+ HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ * HDMI_KHZ_TO_HZ) {
scrambler_on = true;
tmds_clock_ratio = 1;
} else {
@@ -402,6 +404,38 @@ static int _sde_hdmi_bridge_setup_scrambler(struct hdmi *hdmi,
return rc;
}
+static void _sde_hdmi_bridge_setup_deep_color(struct hdmi *hdmi)
+{
+ struct drm_connector *connector = hdmi->connector;
+ struct sde_connector *c_conn = to_sde_connector(connector);
+ struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display;
+ u32 hdmi_ctrl_reg, vbi_pkt_reg;
+
+ SDE_DEBUG("Deep Color: %s\n", display->dc_enable ? "On" : "Off");
+
+ if (display->dc_enable) {
+ hdmi_ctrl_reg = hdmi_read(hdmi, REG_HDMI_CTRL);
+
+ /* GC CD override */
+ hdmi_ctrl_reg |= BIT(27);
+
+ /* enable deep color for RGB888/YUV444/YUV420 30 bits */
+ hdmi_ctrl_reg |= BIT(24);
+ hdmi_write(hdmi, REG_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 = hdmi_read(hdmi, REG_HDMI_VBI_PKT_CTRL);
+ vbi_pkt_reg |= BIT(5) | BIT(4);
+ hdmi_write(hdmi, REG_HDMI_VBI_PKT_CTRL, vbi_pkt_reg);
+ }
+}
+
static void _sde_hdmi_bridge_pre_enable(struct drm_bridge *bridge)
{
struct sde_hdmi_bridge *sde_hdmi_bridge = to_hdmi_bridge(bridge);
@@ -660,18 +694,52 @@ static inline void _sde_hdmi_save_mode(struct hdmi *hdmi,
drm_mode_copy(&display->mode, mode);
}
+static u32 _sde_hdmi_choose_best_format(struct hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ /*
+ * choose priority:
+ * 1. DC + RGB
+ * 2. DC + YUV
+ * 3. RGB
+ * 4. YUV
+ */
+ int dc_format;
+ struct drm_connector *connector = hdmi->connector;
+
+ dc_format = sde_hdmi_sink_dc_support(connector, mode);
+ if (dc_format & MSM_MODE_FLAG_RGB444_DC_ENABLE)
+ return (MSM_MODE_FLAG_COLOR_FORMAT_RGB444
+ | MSM_MODE_FLAG_RGB444_DC_ENABLE);
+
+ return MSM_MODE_FLAG_COLOR_FORMAT_RGB444;
+}
+
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;
+ struct drm_connector *connector = hdmi->connector;
+ struct sde_connector *c_conn = to_sde_connector(connector);
+ struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display;
int hstart, hend, vstart, vend;
uint32_t frame_ctrl;
mode = adjusted_mode;
- hdmi->pixclock = mode->clock * 1000;
+ display->dc_enable = mode->private_flags &
+ (MSM_MODE_FLAG_RGB444_DC_ENABLE |
+ MSM_MODE_FLAG_YUV420_DC_ENABLE);
+
+ /* compute pixclock as per color format and bit depth */
+ hdmi->pixclock = sde_hdmi_calc_pixclk(
+ mode->clock * HDMI_KHZ_TO_HZ,
+ mode->private_flags,
+ display->dc_enable);
+ SDE_DEBUG("Actual PCLK: %lu, Mode PCLK: %d\n",
+ hdmi->pixclock, mode->clock);
hstart = mode->htotal - mode->hsync_start;
hend = mode->htotal - mode->hsync_start + mode->hdisplay;
@@ -734,6 +802,20 @@ static void _sde_hdmi_bridge_mode_set(struct drm_bridge *bridge,
_sde_hdmi_save_mode(hdmi, mode);
_sde_hdmi_bridge_setup_scrambler(hdmi, mode);
+ _sde_hdmi_bridge_setup_deep_color(hdmi);
+}
+
+static bool _sde_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
+ const 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;
+
+ adjusted_mode->private_flags |=
+ _sde_hdmi_choose_best_format(hdmi, adjusted_mode);
+
+ return true;
}
static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
@@ -742,6 +824,7 @@ static const struct drm_bridge_funcs _sde_hdmi_bridge_funcs = {
.disable = _sde_hdmi_bridge_disable,
.post_disable = _sde_hdmi_bridge_post_disable,
.mode_set = _sde_hdmi_bridge_mode_set,
+ .mode_fixup = _sde_hdmi_bridge_mode_fixup,
};
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
index a7887d2c84b0..ba47b7702efd 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.c
@@ -825,3 +825,76 @@ int sde_hdmi_hdcp2p2_read_rxstatus(void *hdmi_display)
}
return rc;
}
+
+unsigned long sde_hdmi_calc_pixclk(unsigned long pixel_freq,
+ u32 out_format, bool dc_enable)
+{
+ u32 rate_ratio = HDMI_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO;
+
+ if (out_format & MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420)
+ rate_ratio = HDMI_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO;
+
+ pixel_freq /= rate_ratio;
+
+ if (dc_enable)
+ pixel_freq += pixel_freq >> 2;
+
+ return pixel_freq;
+
+}
+
+bool sde_hdmi_validate_pixclk(struct drm_connector *connector,
+ unsigned long pclk)
+{
+ struct sde_connector *c_conn = to_sde_connector(connector);
+ struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display;
+ unsigned long max_pclk = display->max_pclk_khz * HDMI_KHZ_TO_HZ;
+
+ if (connector->max_tmds_char)
+ max_pclk = MIN(max_pclk,
+ connector->max_tmds_char * HDMI_MHZ_TO_HZ);
+ else if (connector->max_tmds_clock)
+ max_pclk = MIN(max_pclk,
+ connector->max_tmds_clock * HDMI_MHZ_TO_HZ);
+
+ SDE_DEBUG("MAX PCLK = %ld, PCLK = %ld\n", max_pclk, pclk);
+
+ return pclk < max_pclk;
+}
+
+static bool sde_hdmi_check_dc_clock(struct drm_connector *connector,
+ struct drm_display_mode *mode, u32 format)
+{
+ struct sde_connector *c_conn = to_sde_connector(connector);
+ struct sde_hdmi *display = (struct sde_hdmi *)c_conn->display;
+
+ u32 tmds_clk_with_dc = sde_hdmi_calc_pixclk(
+ mode->clock * HDMI_KHZ_TO_HZ,
+ format,
+ true);
+
+ return (display->dc_feature_supported &&
+ sde_hdmi_validate_pixclk(connector, tmds_clk_with_dc));
+}
+
+int sde_hdmi_sink_dc_support(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ int dc_format = 0;
+
+ if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV) &&
+ (connector->display_info.edid_hdmi_dc_modes
+ & DRM_EDID_YCBCR420_DC_30))
+ if (sde_hdmi_check_dc_clock(connector, mode,
+ MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420))
+ dc_format |= MSM_MODE_FLAG_YUV420_DC_ENABLE;
+
+ if ((mode->flags & DRM_MODE_FLAG_SUPPORTS_RGB) &&
+ (connector->display_info.edid_hdmi_dc_modes
+ & DRM_EDID_HDMI_DC_30))
+ if (sde_hdmi_check_dc_clock(connector, mode,
+ MSM_MODE_FLAG_COLOR_FORMAT_RGB444))
+ dc_format |= MSM_MODE_FLAG_RGB444_DC_ENABLE;
+
+ return dc_format;
+}
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
index 8b69dd31f637..10effed54a14 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi_util.h
@@ -164,4 +164,10 @@ int sde_hdmi_hdcp2p2_read_rxstatus(void *hdmi_display);
void sde_hdmi_ddc_config(void *hdmi_display);
int sde_hdmi_ddc_hdcp2p2_isr(void *hdmi_display);
void sde_hdmi_dump_regs(void *hdmi_display);
+unsigned long sde_hdmi_calc_pixclk(unsigned long pixel_freq,
+ u32 out_format, bool dc_enable);
+bool sde_hdmi_validate_pixclk(struct drm_connector *connector,
+ unsigned long pclk);
+int sde_hdmi_sink_dc_support(struct drm_connector *connector,
+ struct drm_display_mode *mode);
#endif /* _SDE_HDMI_UTIL_H_ */
diff --git a/drivers/gpu/drm/msm/msm_kms.h b/drivers/gpu/drm/msm/msm_kms.h
index 2ab50919f514..ed0ba928f170 100644
--- a/drivers/gpu/drm/msm/msm_kms.h
+++ b/drivers/gpu/drm/msm/msm_kms.h
@@ -34,6 +34,24 @@
#define MSM_MODE_FLAG_SEAMLESS_DYNAMIC_FPS (1<<0)
/* Transition to new mode requires a wait-for-vblank before the modeset */
#define MSM_MODE_FLAG_VBLANK_PRE_MODESET (1<<1)
+/*
+ * We need setting some flags in bridge, and using them in encoder. Add them in
+ * private_flags would be better for use. DRM_MODE_FLAG_SUPPORTS_RGB/YUV are
+ * flags that indicating the SINK supported color formats read from EDID. While,
+ * these flags defined here indicate the best color/bit depth foramt we choosed
+ * that would be better for display. For example the best mode display like:
+ * RGB+RGB_DC,YUV+YUV_DC, RGB,YUV. And we could not set RGB and YUV format at
+ * the same time. And also RGB_DC only set when RGB format is set,the same for
+ * YUV_DC.
+ */
+/* Enable RGB444 30 bit deep color */
+#define MSM_MODE_FLAG_RGB444_DC_ENABLE (1<<2)
+/* Enable YUV420 30 bit deep color */
+#define MSM_MODE_FLAG_YUV420_DC_ENABLE (1<<3)
+/* Choose RGB444 format to display */
+#define MSM_MODE_FLAG_COLOR_FORMAT_RGB444 (1<<4)
+/* Choose YUV420 format to display */
+#define MSM_MODE_FLAG_COLOR_FORMAT_YCBCR420 (1<<5)
/* As there are different display controller blocks depending on the
* snapdragon version, the kms support is split out and the appropriate