Skip to content

drm/vc4: hdmi: Support the 4k @ 60Hz modes #4284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions drivers/gpu/drm/vc4/vc4_crtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,19 @@ static u32 vc4_crtc_get_fifo_full_level_bits(struct vc4_crtc *vc4_crtc,
PV_CONTROL_FIFO_LEVEL);
}

static struct drm_encoder *vc4_get_connector_encoder(struct drm_connector *connector)
{
struct drm_encoder *encoder;

if (drm_WARN_ON(connector->dev, hweight32(connector->possible_encoders) != 1))
return NULL;

drm_connector_for_each_possible_encoder(connector, encoder)
return encoder;

return NULL;
}

/*
* Returns the encoder attached to the CRTC.
*
Expand All @@ -277,9 +290,17 @@ static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc)

drm_connector_list_iter_begin(crtc->dev, &conn_iter);
drm_for_each_connector_iter(connector, &conn_iter) {
if (connector->state->crtc == crtc) {
struct drm_encoder *encoder;
struct vc4_encoder *vc4_encoder;

encoder = vc4_get_connector_encoder(connector);
if (!encoder)
continue;

vc4_encoder = to_vc4_encoder(encoder);
if (vc4_encoder->crtc == crtc) {
drm_connector_list_iter_end(&conn_iter);
return connector->encoder;
return encoder;
}
}
drm_connector_list_iter_end(&conn_iter);
Expand Down Expand Up @@ -1040,6 +1061,9 @@ static void vc4_set_crtc_possible_masks(struct drm_device *drm,
struct vc4_encoder *vc4_encoder;
int i;

if (encoder->encoder_type == DRM_MODE_ENCODER_VIRTUAL)
continue;

vc4_encoder = to_vc4_encoder(encoder);
for (i = 0; i < ARRAY_SIZE(pv_data->encoder_types); i++) {
if (vc4_encoder->type == encoder_types[i]) {
Expand Down
48 changes: 47 additions & 1 deletion drivers/gpu/drm/vc4/vc4_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,41 @@ static int compare_dev(struct device *dev, void *data)
return dev == data;
}

static struct drm_crtc *vc4_drv_find_crtc(struct drm_device *drm,
struct drm_encoder *encoder)
{
struct drm_crtc *crtc;

if (WARN_ON(hweight32(encoder->possible_crtcs) != 1))
return NULL;

drm_for_each_crtc(crtc, drm) {
if (!drm_encoder_crtc_ok(encoder, crtc))
continue;

return crtc;
}

return NULL;
}

static void vc4_drv_set_encoder_data(struct drm_device *drm)
{
struct drm_encoder *encoder;

drm_for_each_encoder(encoder, drm) {
struct vc4_encoder *vc4_encoder;
struct drm_crtc *crtc;

crtc = vc4_drv_find_crtc(drm, encoder);
if (WARN_ON(!crtc))
return;

vc4_encoder = to_vc4_encoder(encoder);
vc4_encoder->crtc = crtc;
}
}

static void vc4_match_add_drivers(struct device *dev,
struct component_match **match,
struct platform_driver *const *drivers,
Expand Down Expand Up @@ -309,6 +344,8 @@ static int vc4_drm_bind(struct device *dev)
if (ret)
return ret;

vc4_drv_set_encoder_data(drm);

if (!vc4->firmware_kms) {
ret = vc4_plane_create_additional_planes(drm);
if (ret)
Expand Down Expand Up @@ -354,12 +391,21 @@ static const struct component_master_ops vc4_drm_ops = {
.unbind = vc4_drm_unbind,
};

/*
* This list determines the binding order of our components, and we have
* a few constraints:
* - The TXP driver needs to be bound before the PixelValves (CRTC)
* but after the HVS to set the possible_crtc field properly
* - The HDMI driver needs to be bound after the HVS so that we can
* lookup the HVS maximum core clock rate and figure out if we
* support 4kp60 or not.
*/
static struct platform_driver *const component_drivers[] = {
&vc4_hvs_driver,
&vc4_hdmi_driver,
&vc4_vec_driver,
&vc4_dpi_driver,
&vc4_dsi_driver,
&vc4_hvs_driver,
&vc4_txp_driver,
&vc4_crtc_driver,
&vc4_firmware_kms_driver,
Expand Down
10 changes: 10 additions & 0 deletions drivers/gpu/drm/vc4/vc4_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,16 @@ enum vc4_encoder_type {

struct vc4_encoder {
struct drm_encoder base;

/*
* At boot time, we need to be able to retrieve the CRTC for a given
* connector in order to run the disable hooks below to avoid the stuck
* pixel issue. Unfortunately the drm_connector->encoder pointer is
* NULL at that time so we can't move up the chain, so we'll store it
* ourselves here.
*/
struct drm_crtc *crtc;

enum vc4_encoder_type type;
u32 clock_select;

Expand Down
122 changes: 113 additions & 9 deletions drivers/gpu/drm/vc4/vc4_hdmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <drm/drm_edid.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_scdc_helper.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/i2c.h>
Expand Down Expand Up @@ -78,6 +79,8 @@
#define VC5_HDMI_VERTB_VSPO_SHIFT 16
#define VC5_HDMI_VERTB_VSPO_MASK VC4_MASK(29, 16)

#define VC5_HDMI_SCRAMBLER_CTL_ENABLE BIT(0)

#define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_SHIFT 8
#define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_MASK VC4_MASK(10, 8)

Expand All @@ -93,7 +96,6 @@
# define VC4_HD_M_ENABLE BIT(0)

#define CEC_CLOCK_FREQ 40000
#define VC4_HSM_MID_CLOCK 149985000

#define HDMI_CODEC_CHMAP_IDX_UNKNOWN -1

Expand Down Expand Up @@ -402,6 +404,11 @@ static void hdmi_codec_eld_chmap(struct vc4_hdmi *vc4_hdmi)

#define HDMI_14_MAX_TMDS_CLK (340 * 1000 * 1000)

static bool vc4_hdmi_mode_needs_scrambling(const struct drm_display_mode *mode)
{
return (mode->clock * 1000) > HDMI_14_MAX_TMDS_CLK;
}

static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
Expand Down Expand Up @@ -524,6 +531,18 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
ret = drm_add_edid_modes(connector, edid);
kfree(edid);

if (vc4_hdmi->disable_4kp60) {
struct drm_device *drm = connector->dev;
struct drm_display_mode *mode;

list_for_each_entry(mode, &connector->probed_modes, head) {
if (vc4_hdmi_mode_needs_scrambling(mode)) {
drm_warn_once(drm, "The core clock cannot reach frequencies high enough to support 4k @ 60Hz.");
drm_warn_once(drm, "Please change your config.txt file to add hdmi_enable_4kp60.");
}
}
}

return ret;
}

Expand Down Expand Up @@ -825,6 +844,64 @@ static void vc4_hdmi_set_infoframes(struct drm_encoder *encoder)
vc4_hdmi_set_hdr_infoframe(encoder);
}

static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder,
struct drm_display_mode *mode)
{
struct vc4_hdmi_encoder *vc4_encoder = to_vc4_hdmi_encoder(encoder);
struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_display_info *display = &vc4_hdmi->connector.display_info;

if (!vc4_encoder->hdmi_monitor)
return false;

if (!display->hdmi.scdc.supported ||
!display->hdmi.scdc.scrambling.supported)
return false;

return true;
}

static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
{
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);

if (!vc4_hdmi_supports_scrambling(encoder, mode))
return;

if (!vc4_hdmi_mode_needs_scrambling(mode))
return;

drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true);
drm_scdc_set_scrambling(vc4_hdmi->ddc, true);

HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) |
VC5_HDMI_SCRAMBLER_CTL_ENABLE);
}

static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder)
{
struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_crtc *crtc = encoder->crtc;

/*
* At boot, encoder->crtc will be NULL. Since we don't know the
* state of the scrambler and in order to avoid any
* inconsistency, let's disable it all the time.
*/
if (crtc && !vc4_hdmi_supports_scrambling(encoder, &crtc->mode))
return;

if (crtc && !vc4_hdmi_mode_needs_scrambling(&crtc->mode))
return;

HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) &
~VC5_HDMI_SCRAMBLER_CTL_ENABLE);

drm_scdc_set_scrambling(vc4_hdmi->ddc, false);
drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, false);
}

static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
Expand All @@ -837,6 +914,8 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,

HDMI_WRITE(HDMI_VID_CTL,
HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX);

vc4_hdmi_disable_scrambling(encoder);
}

static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
Expand Down Expand Up @@ -1118,7 +1197,7 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
conn_state_to_vc4_hdmi_conn_state(conn_state);
struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
unsigned long pixel_rate, hsm_rate;
unsigned long bvb_rate, pixel_rate, hsm_rate;
int ret;

ret = pm_runtime_get_sync(&vc4_hdmi->pdev->dev);
Expand Down Expand Up @@ -1172,12 +1251,14 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,

vc4_hdmi_cec_update_clk_div(vc4_hdmi);

/*
* FIXME: When the pixel freq is 594MHz (4k60), this needs to be setup
* at 300MHz.
*/
ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock,
(hsm_rate > VC4_HSM_MID_CLOCK ? 150000000 : 75000000));
if (pixel_rate > 297000000)
bvb_rate = 300000000;
else if (pixel_rate > 148500000)
bvb_rate = 150000000;
else
bvb_rate = 75000000;

ret = clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, bvb_rate);
if (ret) {
DRM_ERROR("Failed to set pixel bvb clock rate: %d\n", ret);
clk_disable_unprepare(vc4_hdmi->hsm_clock);
Expand Down Expand Up @@ -1285,6 +1366,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,
}

vc4_hdmi_recenter_fifo(vc4_hdmi);
vc4_hdmi_enable_scrambling(encoder);
}

static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder)
Expand Down Expand Up @@ -1337,6 +1419,9 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
if (pixel_rate > vc4_hdmi->variant->max_pixel_clock)
return -EINVAL;

if (vc4_hdmi->disable_4kp60 && (pixel_rate > HDMI_14_MAX_TMDS_CLK))
return -EINVAL;

vc4_state->pixel_rate = pixel_rate;

return 0;
Expand All @@ -1356,6 +1441,9 @@ vc4_hdmi_encoder_mode_valid(struct drm_encoder *encoder,
if ((mode->clock * 1000) > vc4_hdmi->variant->max_pixel_clock)
return MODE_CLOCK_HIGH;

if (vc4_hdmi->disable_4kp60 && vc4_hdmi_mode_needs_scrambling(mode))
return MODE_CLOCK_HIGH;

return MODE_OK;
}

Expand Down Expand Up @@ -2618,9 +2706,25 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
vc4_hdmi->disable_wifi_frequencies =
of_property_read_bool(dev->of_node, "wifi-2.4ghz-coexistence");

if (variant->max_pixel_clock == 600000000) {
struct vc4_dev *vc4 = to_vc4_dev(drm);
long max_rate = clk_round_rate(vc4->hvs->core_clk, 550000000);

if (max_rate < 550000000)
vc4_hdmi->disable_4kp60 = true;
}

if (vc4_hdmi->variant->reset)
vc4_hdmi->variant->reset(vc4_hdmi);

if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") ||
of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) &&
HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) {
clk_prepare_enable(vc4_hdmi->pixel_clock);
clk_prepare_enable(vc4_hdmi->hsm_clock);
clk_prepare_enable(vc4_hdmi->pixel_bvb_clock);
}

pm_runtime_enable(dev);

drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
Expand Down Expand Up @@ -2735,7 +2839,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {
.encoder_type = VC4_ENCODER_TYPE_HDMI0,
.debugfs_name = "hdmi0_regs",
.card_name = "vc4-hdmi-0",
.max_pixel_clock = HDMI_14_MAX_TMDS_CLK,
.max_pixel_clock = 600000000,
.registers = vc5_hdmi_hdmi0_fields,
.num_registers = ARRAY_SIZE(vc5_hdmi_hdmi0_fields),
.phy_lane_mapping = {
Expand Down
8 changes: 8 additions & 0 deletions drivers/gpu/drm/vc4/vc4_hdmi.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ struct vc4_hdmi {
*/
bool disable_wifi_frequencies;

/*
* Even if HDMI0 on the RPi4 can output modes requiring a pixel
* rate higher than 297MHz, it needs some adjustments in the
* config.txt file to be able to do so and thus won't always be
* available.
*/
bool disable_4kp60;

struct cec_adapter *cec_adap;
struct cec_msg cec_rx_msg;
bool cec_tx_ok;
Expand Down
Loading