Skip to content

Commit

Permalink
drm/rockchip: hdmi: support modify color format
Browse files Browse the repository at this point in the history
This patch is based on https://patchwork.kernel.org/patch/9801533,
add the drm property "hdmi_output_format", the possible value
could be:
     - RGB
     - YCBCR 444
     - YCBCR 422

To handle various subsampling of YCBCR output types, this property
allows two special automatic cases:
     - DRM_HDMI_OUTPUT_YCBCR_HQ
       This indicates preferred output should be YCBCR output,
       with highest subsampling rate by the source/sink, which
       can be typically:
	- ycbcr444
	- ycbcr422
	- ycbcr420
     - DRM_HDMI_OUTPUT_YCBCR_LQ
       This indicates preferred output should be YCBCR output, with
       lowest subsampling rate supported by source/sink, which can be:
	- ycbcr420
	- ycbcr422
	- ycbcr444

Default value of the property is set to 0 = RGB, so no changes if you
don't set the property.

Change-Id: Ie4a98ba91c8285a2e8f1ec7832d73183ad57665e
Signed-off-by: Zheng Yang <zhengyang@rock-chips.com>
  • Loading branch information
Zheng Yang committed Nov 1, 2017
1 parent 199377c commit da59743
Showing 1 changed file with 151 additions and 20 deletions.
171 changes: 151 additions & 20 deletions drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@

#define HIWORD_UPDATE(val, mask) (val | (mask) << 16)

/* HDMI output pixel format */
enum drm_hdmi_output_type {
DRM_HDMI_OUTPUT_DEFAULT_RGB, /* default RGB */
DRM_HDMI_OUTPUT_YCBCR444, /* YCBCR 444 */
DRM_HDMI_OUTPUT_YCBCR422, /* YCBCR 422 */
DRM_HDMI_OUTPUT_YCBCR420, /* YCBCR 420 */
DRM_HDMI_OUTPUT_YCBCR_HQ, /* Highest subsampled YUV */
DRM_HDMI_OUTPUT_YCBCR_LQ, /* Lowest subsampled YUV */
DRM_HDMI_OUTPUT_INVALID, /* Guess what ? */
};

struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
Expand All @@ -67,9 +78,13 @@ struct rockchip_hdmi {
struct phy *phy;

unsigned long bus_format;
unsigned long output_bus_format;

struct drm_property *color_depth_property;
struct drm_property *hdmi_output_property;

unsigned int colordepth;
enum drm_hdmi_output_type hdmi_output;
};

#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x)
Expand Down Expand Up @@ -540,12 +555,16 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)

static unsigned int
dw_hdmi_rockchip_check_depth(struct drm_display_info *info,
unsigned long pixelclock, int depth)
struct drm_display_mode *mode,
int color_format, int depth)
{
unsigned long tmdsclock;
unsigned int colordepth;
unsigned long tmdsclock, pixclock = mode->crtc_clock;
int colordepth = 8;

if (!depth)
if (color_format == DRM_HDMI_OUTPUT_YCBCR444 &&
!(info->edid_hdmi_dc_modes & DRM_EDID_HDMI_DC_Y444))
return colordepth;
else if (!depth)
colordepth = info->bpc;
else
colordepth = depth;
Expand All @@ -554,10 +573,16 @@ dw_hdmi_rockchip_check_depth(struct drm_display_info *info,
if (colordepth > 10)
colordepth = 10;

if (colordepth == 10)
tmdsclock = pixelclock * 5 / 4;
if (mode->flags & DRM_MODE_FLAG_DBLCLK)
pixclock *= 2;
if ((mode->flags & DRM_MODE_FLAG_3D_MASK) ==
DRM_MODE_FLAG_3D_FRAME_PACKING)
pixclock *= 2;

if (color_format == DRM_HDMI_OUTPUT_YCBCR422 || colordepth == 8)
tmdsclock = pixclock;
else
tmdsclock = pixelclock;
tmdsclock = pixclock * colordepth / 8;

/*
* For some display device, max_tmds_clock is 0, we think
Expand All @@ -571,23 +596,79 @@ dw_hdmi_rockchip_check_depth(struct drm_display_info *info,
return colordepth;
}

static void
dw_hdmi_rockchip_select_output(struct drm_connector_state *conn_state,
struct drm_crtc_state *crtc_state,
struct rockchip_hdmi *hdmi,
unsigned int *color_format,
unsigned int *color_depth)
{
struct drm_display_info *info = &conn_state->connector->display_info;
struct drm_display_mode *mode = &crtc_state->mode;

*color_format = DRM_HDMI_OUTPUT_DEFAULT_RGB;

switch (hdmi->hdmi_output) {
case DRM_HDMI_OUTPUT_YCBCR_HQ:
if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
*color_format = DRM_HDMI_OUTPUT_YCBCR444;
else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
*color_format = DRM_HDMI_OUTPUT_YCBCR422;
else if (conn_state->connector->ycbcr_420_allowed &&
drm_mode_is_420(info, mode))
*color_format = DRM_HDMI_OUTPUT_YCBCR420;
break;
case DRM_HDMI_OUTPUT_YCBCR_LQ:
if (conn_state->connector->ycbcr_420_allowed &&
drm_mode_is_420(info, mode))
*color_format = DRM_HDMI_OUTPUT_YCBCR420;
else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
*color_format = DRM_HDMI_OUTPUT_YCBCR422;
else if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
*color_format = DRM_HDMI_OUTPUT_YCBCR444;
break;
case DRM_HDMI_OUTPUT_YCBCR420:
if (conn_state->connector->ycbcr_420_allowed &&
drm_mode_is_420(info, mode))
*color_format = DRM_HDMI_OUTPUT_YCBCR420;
break;
case DRM_HDMI_OUTPUT_YCBCR422:
if (info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
*color_format = DRM_HDMI_OUTPUT_YCBCR422;
break;
case DRM_HDMI_OUTPUT_YCBCR444:
if (info->color_formats & DRM_COLOR_FORMAT_YCRCB444)
*color_format = DRM_HDMI_OUTPUT_YCBCR444;
break;
case DRM_HDMI_OUTPUT_DEFAULT_RGB:
default:
break;
}

/* RK3368 should limit 4K 50/60 to YCbCr420 mode */
if (hdmi->dev_type == RK3368_HDMI &&
mode->crtc_clock > 340000 &&
drm_mode_is_420(info, mode))
*color_format = DRM_HDMI_OUTPUT_YCBCR420;

*color_depth = dw_hdmi_rockchip_check_depth(info, mode,
*color_format,
hdmi->colordepth);
}

static int
dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
struct drm_display_info *info = &conn_state->connector->display_info;
unsigned long pixclock = crtc_state->adjusted_mode.crtc_clock;
unsigned int colordepth;
unsigned int colordepth, colorformat;

colordepth = dw_hdmi_rockchip_check_depth(info, pixclock,
hdmi->colordepth);
dw_hdmi_rockchip_select_output(conn_state, crtc_state, hdmi,
&colorformat, &colordepth);

if (conn_state->connector->ycbcr_420_allowed &&
crtc_state->mode.crtc_clock > 340000 &&
drm_mode_is_420(info, &crtc_state->mode)) {
if (colorformat == DRM_HDMI_OUTPUT_YCBCR420) {
s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
if (colordepth > 8)
s->bus_format = MEDIA_BUS_FMT_UYYVYY10_0_5X30;
Expand All @@ -597,10 +678,19 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
phy_set_bus_width(hdmi->phy, colordepth / 2);
} else {
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
if (colordepth > 8)
s->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
else
s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
if (colordepth > 8) {
if (colorformat != DRM_HDMI_OUTPUT_DEFAULT_RGB &&
hdmi->dev_type != RK3288_HDMI)
s->bus_format = MEDIA_BUS_FMT_YUV10_1X30;
else
s->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
} else {
if (colorformat != DRM_HDMI_OUTPUT_DEFAULT_RGB &&
hdmi->dev_type != RK3288_HDMI)
s->bus_format = MEDIA_BUS_FMT_YUV8_1X24;
else
s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
}
if (hdmi->phy)
phy_set_bus_width(hdmi->phy, colordepth);
}
Expand All @@ -609,6 +699,17 @@ dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,

hdmi->bus_format = s->bus_format;

if (colorformat == DRM_HDMI_OUTPUT_YCBCR422) {
if (colordepth == 12)
hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY12_1X24;
else if (colordepth == 10)
hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY10_1X20;
else
hdmi->output_bus_format = MEDIA_BUS_FMT_UYVY8_1X16;
} else {
hdmi->output_bus_format = s->bus_format;
}

return 0;
}

Expand All @@ -625,7 +726,7 @@ dw_hdmi_rockchip_get_output_bus_format(void *data)
{
struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data;

return hdmi->bus_format;
return hdmi->output_bus_format;
}

static const struct drm_prop_enum_list color_depth_enum_list[] = {
Expand All @@ -634,6 +735,16 @@ static const struct drm_prop_enum_list color_depth_enum_list[] = {
{ 10, "30bit" },
};

static const struct drm_prop_enum_list drm_hdmi_output_enum_list[] = {
{ DRM_HDMI_OUTPUT_DEFAULT_RGB, "output_rgb" },
{ DRM_HDMI_OUTPUT_YCBCR444, "output_ycbcr444" },
{ DRM_HDMI_OUTPUT_YCBCR422, "output_ycbcr422" },
{ DRM_HDMI_OUTPUT_YCBCR420, "output_ycbcr420" },
{ DRM_HDMI_OUTPUT_YCBCR_HQ, "output_ycbcr_high_subsampling" },
{ DRM_HDMI_OUTPUT_YCBCR_LQ, "output_ycbcr_low_subsampling" },
{ DRM_HDMI_OUTPUT_INVALID, "invalid_output" },
};

static void
dw_hdmi_rockchip_attatch_properties(struct drm_connector *connector,
int version, void *data)
Expand All @@ -653,6 +764,14 @@ dw_hdmi_rockchip_attatch_properties(struct drm_connector *connector,
hdmi->colordepth = 8;
}
}

prop = drm_property_create_enum(connector->dev, 0, "hdmi_output_format",
drm_hdmi_output_enum_list,
ARRAY_SIZE(drm_hdmi_output_enum_list));
if (prop) {
hdmi->hdmi_output_property = prop;
drm_object_attach_property(&connector->base, prop, 0);
}
}

static void
Expand All @@ -666,6 +785,12 @@ dw_hdmi_rockchip_destroy_properties(struct drm_connector *connector,
hdmi->color_depth_property);
hdmi->color_depth_property = NULL;
}

if (hdmi->hdmi_output_property) {
drm_property_destroy(connector->dev,
hdmi->hdmi_output_property);
hdmi->hdmi_output_property = NULL;
}
}

static int
Expand All @@ -680,6 +805,9 @@ dw_hdmi_rockchip_set_property(struct drm_connector *connector,
if (property == hdmi->color_depth_property) {
hdmi->colordepth = val;
return 0;
} else if (property == hdmi->hdmi_output_property) {
hdmi->hdmi_output = val;
return 0;
}

DRM_ERROR("failed to set rockchip hdmi connector property\n");
Expand All @@ -698,6 +826,9 @@ dw_hdmi_rockchip_get_property(struct drm_connector *connector,
if (property == hdmi->color_depth_property) {
*val = hdmi->colordepth;
return 0;
} else if (property == hdmi->hdmi_output_property) {
*val = hdmi->hdmi_output;
return 0;
}

DRM_ERROR("failed to get rockchip hdmi connector property\n");
Expand Down

0 comments on commit da59743

Please sign in to comment.