diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 6f2e04ac6281..a98aa4fb6cb2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -3308,8 +3308,25 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_unlock(&hdmi->mutex); } +static int drm_hdmi_probe_single_connector_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + struct dw_hdmi_qp *hdmi = + container_of(connector, struct dw_hdmi_qp, connector); + struct drm_display_info *info = &connector->display_info; + void *data = hdmi->plat_data->phy_data; + int ret; + + ret = drm_helper_probe_single_connector_modes(connector, maxX, maxY); + + if (hdmi->plat_data->get_mode_color_caps) + hdmi->plat_data->get_mode_color_caps(connector, info, data); + + return ret; +} + static const struct drm_connector_funcs dw_hdmi_connector_funcs = { - .fill_modes = drm_helper_probe_single_connector_modes, + .fill_modes = drm_hdmi_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, .destroy = drm_connector_cleanup, .force = dw_hdmi_connector_force, diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index b3c6ed89e0c2..169c60893248 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3622,8 +3622,25 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_unlock(&hdmi->mutex); } +static int drm_hdmi_probe_single_connector_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + struct dw_hdmi *hdmi = + container_of(connector, struct dw_hdmi, connector); + struct drm_display_info *info = &connector->display_info; + void *data = hdmi->plat_data->phy_data; + int ret; + + ret = drm_helper_probe_single_connector_modes(connector, maxX, maxY); + + if (hdmi->plat_data->get_mode_color_caps) + hdmi->plat_data->get_mode_color_caps(connector, info, data); + + return ret; +} + static const struct drm_connector_funcs dw_hdmi_connector_funcs = { - .fill_modes = drm_helper_probe_single_connector_modes, + .fill_modes = drm_hdmi_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, .destroy = drm_connector_cleanup, .force = dw_hdmi_connector_force, diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 88cbe5788030..a3c76f8d28f1 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -238,6 +238,22 @@ enum hdmi_frl_rate_per_lane { FRL_3G_PER_LANE = 3, }; +enum mode_color_caps_mask { + RGB_8BIT = 0, + RGB_10BIT, + YUV444_8BIT, + YUV444_10BIT, + YUV422_8BIT, + YUV422_10BIT, + YUV420_8BIT, + YUV420_10BIT, +}; + +struct mode_color_caps { + struct drm_mode_modeinfo umode; + u8 color_caps; +}; + struct rockchip_hdmi { struct device *dev; struct regmap *regmap; @@ -295,9 +311,11 @@ struct rockchip_hdmi { struct drm_property *allm_capacity; struct drm_property *allm_enable; struct drm_property *hdcp_state_property; + struct drm_property *mode_color_capacity; struct drm_property_blob *hdr_panel_blob_ptr; struct drm_property_blob *next_hdr_data_ptr; + struct drm_property_blob *mode_color_caps_ptr; unsigned int colordepth; unsigned int colorimetry; @@ -324,6 +342,7 @@ struct rockchip_hdmi { struct pinctrl *p; struct pinctrl_state *idle_state; struct pinctrl_state *default_state; + struct mode_color_caps *mode_color_caps; bool timing_force_output; struct drm_display_mode force_mode; u32 force_bus_format; @@ -3063,6 +3082,100 @@ static void dw_hdmi_rockchip_force_frl_rate(void *data, u8 rate) phy_set_bus_width(hdmi->phy, hdmi->phy_bus_width); } +static u8 mode_color_caps_init(struct drm_connector *connector, struct drm_display_mode *mode, + struct drm_display_info *info) +{ + u8 color_caps_mask = RGB_8BIT; + + if (info->edid_hdmi_rgb444_dc_modes & DRM_EDID_HDMI_DC_30) + color_caps_mask |= BIT(RGB_10BIT); + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) { + color_caps_mask |= BIT(YUV444_8BIT); + if (info->edid_hdmi_ycbcr444_dc_modes & DRM_EDID_HDMI_DC_30) + color_caps_mask |= BIT(YUV444_10BIT); + } + + /* For hdmi, yuv422 8bit and 10bit are the same format */ + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) { + color_caps_mask |= BIT(YUV422_8BIT); + color_caps_mask |= BIT(YUV422_10BIT); + } + + if (connector->ycbcr_420_allowed && drm_mode_is_420(info, mode)) { + color_caps_mask |= BIT(YUV420_8BIT); + if (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30) + color_caps_mask |= BIT(YUV420_10BIT); + } + + return color_caps_mask; +} + +static void dw_hdmi_rockchip_get_mode_color_caps(struct drm_connector *connector, + struct drm_display_info *info, + void *data) +{ + struct rockchip_hdmi *hdmi = (struct rockchip_hdmi *)data; + struct drm_display_mode *mode; + u8 color_caps_mask; + u32 max_tmds_clock = info->max_tmds_clock; + u32 size = 0; + struct mode_color_caps *caps; + struct drm_property *property = hdmi->mode_color_capacity; + + if (list_empty(&connector->modes)) + return; + + list_for_each_entry(mode, &connector->modes, head) + size++; + + kfree(hdmi->mode_color_caps); + + size = sizeof(struct mode_color_caps) * size; + + hdmi->mode_color_caps = kmalloc(size, GFP_KERNEL); + if (!hdmi->mode_color_caps) + return; + caps = hdmi->mode_color_caps; + + max_tmds_clock = min(max_tmds_clock, hdmi->max_tmdsclk); + + list_for_each_entry(mode, &connector->modes, head) { + color_caps_mask = mode_color_caps_init(connector, mode, info); + + drm_mode_convert_to_umode(&caps->umode, mode); + /* hdmi 2.1 frl mode */ + if (mode->clock > 600000) { + if (mode->clock > 1188000) + color_caps_mask = BIT(YUV420_8BIT) | BIT(YUV420_10BIT); + + caps->color_caps = color_caps_mask; + caps++; + continue; + } + + /* RGB/YUV444 10BIT is out of range */ + if ((mode->clock * 10 / 8) > max_tmds_clock && mode->clock <= max_tmds_clock) { + color_caps_mask &= ~(BIT(RGB_10BIT) | BIT(YUV444_10BIT)); + /* only support YUV420 */ + } else if (mode->clock > max_tmds_clock && (mode->clock / 2) <= max_tmds_clock) { + color_caps_mask &= ~(BIT(RGB_10BIT) | BIT(RGB_8BIT) | BIT(YUV444_10BIT) | + BIT(YUV444_8BIT) | BIT(YUV422_10BIT) | + BIT(YUV422_8BIT)); + + /* YUV420 10BIT is out of range */ + if ((mode->clock / 2) * 10 / 8 > max_tmds_clock) + color_caps_mask &= ~BIT(YUV420_10BIT); + } + + caps->color_caps = color_caps_mask; + caps++; + } + + drm_property_replace_global_blob(connector->dev, &hdmi->mode_color_caps_ptr, size, + hdmi->mode_color_caps, &connector->base, property); +} + static const struct drm_prop_enum_list color_depth_enum_list[] = { { 0, "Automatic" }, /* Prefer highest color depth */ { 8, "24bit" }, @@ -3259,6 +3372,13 @@ dw_hdmi_rockchip_attach_properties(struct drm_connector *connector, } hdmi->hdcp_state_property = prop; drm_object_attach_property(&connector->base, prop, RK_IF_HDCP_ENCRYPTED_NONE); + + prop = drm_property_create(connector->dev, DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE, + "MODE_COLOR_CAPACITY", 0); + if (prop) { + hdmi->mode_color_capacity = prop; + drm_object_attach_property(&connector->base, prop, 0); + } } static void @@ -3331,6 +3451,13 @@ dw_hdmi_rockchip_destroy_properties(struct drm_connector *connector, drm_property_destroy(connector->dev, hdmi->allm_enable); hdmi->allm_enable = NULL; } + + if (hdmi->mode_color_capacity) { + kfree(hdmi->mode_color_caps); + hdmi->mode_color_caps = NULL; + drm_property_destroy(connector->dev, hdmi->mode_color_capacity); + hdmi->mode_color_capacity = NULL; + } } static int @@ -3399,6 +3526,8 @@ dw_hdmi_rockchip_set_property(struct drm_connector *connector, return 0; } else if (property == hdmi->hdcp_state_property) { return 0; + } else if (property == hdmi->mode_color_capacity) { + return 0; } DRM_ERROR("Unknown property [PROP:%d:%s]\n", @@ -3482,6 +3611,9 @@ dw_hdmi_rockchip_get_property(struct drm_connector *connector, else *val = RK_IF_HDCP_ENCRYPTED_NONE; return 0; + } else if (property == hdmi->mode_color_capacity) { + *val = hdmi->mode_color_caps_ptr ? hdmi->mode_color_caps_ptr->base.id : 0; + return 0; } DRM_ERROR("Unknown property [PROP:%d:%s]\n", @@ -4205,6 +4337,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, dw_hdmi_rockchip_get_refclk_rate; plat_data->force_frl_rate = dw_hdmi_rockchip_force_frl_rate; + plat_data->get_mode_color_caps = + dw_hdmi_rockchip_get_mode_color_caps; plat_data->property_ops = &dw_hdmi_rockchip_property_ops; secondary = rockchip_hdmi_find_by_id(dev->driver, !hdmi->id); diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 9e4e4b114914..1a4a93ded628 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -273,6 +273,8 @@ struct dw_hdmi_plat_data { struct drm_display_mode *(*get_force_timing)(void *data); u32 (*get_refclk_rate)(void *data); void (*force_frl_rate)(void *data, u8 rate); + void (*get_mode_color_caps)(struct drm_connector *connector, struct drm_display_info *info, + void *data); /* Vendor Property support */ const struct dw_hdmi_property_ops *property_ops;