linux/drivers/gpu/drm/msm/hdmi/hdmi_bridge.c
Andy Yan 5d156a9c3d drm/bridge: Pass down connector to drm bridge detect hook
In some application scenarios, we hope to get the corresponding
connector when the bridge's detect hook is invoked.

In most cases, we can get the connector by drm_atomic_get_connector_for_encoder
if the encoder attached to the bridge is enabled, however there will
still be some scenarios where the detect hook of the bridge is called
but the corresponding encoder has not been enabled yet. For instance,
this occurs when the device is hot plug in for the first time.

Since the call to bridge's detect is initiated by the connector, passing
down the corresponding connector directly will make things simpler.

Signed-off-by: Andy Yan <andy.yan@rock-chips.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20250703125027.311109-3-andyshrk@163.com
[DB: added the chunk to the cdn-dp driver]
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-07-14 18:23:18 +03:00

521 lines
15 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_edid.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include "msm_kms.h"
#include "hdmi.h"
static void msm_hdmi_power_on(struct drm_bridge *bridge)
{
struct drm_device *dev = bridge->dev;
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
int ret;
pm_runtime_resume_and_get(&hdmi->pdev->dev);
if (hdmi->extp_clk) {
DBG("pixclock: %lu", hdmi->pixclock);
ret = clk_set_rate(hdmi->extp_clk, hdmi->pixclock);
if (ret)
DRM_DEV_ERROR(dev->dev, "failed to set extp clk rate: %d\n", ret);
ret = clk_prepare_enable(hdmi->extp_clk);
if (ret)
DRM_DEV_ERROR(dev->dev, "failed to enable extp clk: %d\n", ret);
}
}
static void power_off(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
/* TODO do we need to wait for final vblank somewhere before
* cutting the clocks?
*/
mdelay(16 + 4);
if (hdmi->extp_clk)
clk_disable_unprepare(hdmi->extp_clk);
pm_runtime_put(&hdmi->pdev->dev);
}
#define AVI_IFRAME_LINE_NUMBER 1
#define SPD_IFRAME_LINE_NUMBER 1
#define VENSPEC_IFRAME_LINE_NUMBER 3
static int msm_hdmi_config_avi_infoframe(struct hdmi *hdmi,
const u8 *buffer, size_t len)
{
u32 buf[4] = {};
u32 val;
int i;
if (len != HDMI_INFOFRAME_SIZE(AVI) || len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure avi infoframe\n");
return -EINVAL;
}
/*
* the AVI_INFOx registers don't map exactly to how the AVI infoframes
* are packed according to the spec. The checksum from the header is
* written to the LSB byte of AVI_INFO0 and the version is written to
* the third byte from the LSB of AVI_INFO3
*/
memcpy(buf, &buffer[3], len - 3);
buf[3] |= buffer[1] << 24;
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_AVI_INFO(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val |= HDMI_INFOFRAME_CTRL0_AVI_SEND |
HDMI_INFOFRAME_CTRL0_AVI_CONT;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK;
val |= HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
return 0;
}
static int msm_hdmi_config_audio_infoframe(struct hdmi *hdmi,
const u8 *buffer, size_t len)
{
u32 val;
if (len != HDMI_INFOFRAME_SIZE(AUDIO)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure audio infoframe\n");
return -EINVAL;
}
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO0,
buffer[3] |
buffer[4] << 8 |
buffer[5] << 16 |
buffer[6] << 24);
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO1,
buffer[7] |
buffer[8] << 8 |
buffer[9] << 16 |
buffer[10] << 24);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
return 0;
}
static int msm_hdmi_config_spd_infoframe(struct hdmi *hdmi,
const u8 *buffer, size_t len)
{
u32 buf[7] = {};
u32 val;
int i;
if (len != HDMI_INFOFRAME_SIZE(SPD) || len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure SPD infoframe\n");
return -EINVAL;
}
/* checksum gets written together with the body of the frame */
hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR,
buffer[0] |
buffer[1] << 8 |
buffer[2] << 16);
memcpy(buf, &buffer[3], len - 3);
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_GENERIC1(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val |= HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static int msm_hdmi_config_hdmi_infoframe(struct hdmi *hdmi,
const u8 *buffer, size_t len)
{
u32 buf[7] = {};
u32 val;
int i;
if (len < HDMI_INFOFRAME_HEADER_SIZE + HDMI_VENDOR_INFOFRAME_SIZE ||
len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure HDMI infoframe\n");
return -EINVAL;
}
/* checksum gets written together with the body of the frame */
hdmi_write(hdmi, REG_HDMI_GENERIC0_HDR,
buffer[0] |
buffer[1] << 8 |
buffer[2] << 16);
memcpy(buf, &buffer[3], len - 3);
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_GENERIC0(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val |= HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static int msm_hdmi_bridge_clear_infoframe(struct drm_bridge *bridge,
enum hdmi_infoframe_type type)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
switch (type) {
case HDMI_INFOFRAME_TYPE_AVI:
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND |
HDMI_INFOFRAME_CTRL0_AVI_CONT);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
break;
case HDMI_INFOFRAME_TYPE_AUDIO:
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
break;
case HDMI_INFOFRAME_TYPE_SPD:
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
break;
case HDMI_INFOFRAME_TYPE_VENDOR:
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
break;
default:
drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe type %x\n", type);
}
return 0;
}
static int msm_hdmi_bridge_write_infoframe(struct drm_bridge *bridge,
enum hdmi_infoframe_type type,
const u8 *buffer, size_t len)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
msm_hdmi_bridge_clear_infoframe(bridge, type);
switch (type) {
case HDMI_INFOFRAME_TYPE_AVI:
return msm_hdmi_config_avi_infoframe(hdmi, buffer, len);
case HDMI_INFOFRAME_TYPE_AUDIO:
return msm_hdmi_config_audio_infoframe(hdmi, buffer, len);
case HDMI_INFOFRAME_TYPE_SPD:
return msm_hdmi_config_spd_infoframe(hdmi, buffer, len);
case HDMI_INFOFRAME_TYPE_VENDOR:
return msm_hdmi_config_hdmi_infoframe(hdmi, buffer, len);
default:
drm_dbg_driver(hdmi_bridge->base.dev, "Unsupported infoframe type %x\n", type);
return 0;
}
}
static void msm_hdmi_set_timings(struct hdmi *hdmi,
const struct drm_display_mode *mode);
static void msm_hdmi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct hdmi_phy *phy = hdmi->phy;
struct drm_encoder *encoder = bridge->encoder;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
DBG("power up");
connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
conn_state = drm_atomic_get_new_connector_state(state, connector);
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
hdmi->pixclock = conn_state->hdmi.tmds_char_rate;
msm_hdmi_set_timings(hdmi, &crtc_state->adjusted_mode);
mutex_lock(&hdmi->state_mutex);
if (!hdmi->power_on) {
msm_hdmi_phy_resource_enable(phy);
msm_hdmi_power_on(bridge);
hdmi->power_on = true;
}
mutex_unlock(&hdmi->state_mutex);
if (connector->display_info.is_hdmi)
msm_hdmi_audio_update(hdmi);
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
msm_hdmi_phy_powerup(phy, hdmi->pixclock);
msm_hdmi_set_mode(hdmi, true);
if (hdmi->hdcp_ctrl)
msm_hdmi_hdcp_on(hdmi->hdcp_ctrl);
}
static void msm_hdmi_bridge_atomic_post_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct hdmi_phy *phy = hdmi->phy;
if (hdmi->hdcp_ctrl)
msm_hdmi_hdcp_off(hdmi->hdcp_ctrl);
DBG("power down");
/* Keep the HDMI enabled if the HPD is enabled */
mutex_lock(&hdmi->state_mutex);
msm_hdmi_set_mode(hdmi, hdmi->hpd_enabled);
msm_hdmi_phy_powerdown(phy);
if (hdmi->power_on) {
power_off(bridge);
hdmi->power_on = false;
if (hdmi->connector->display_info.is_hdmi)
msm_hdmi_audio_update(hdmi);
msm_hdmi_phy_resource_disable(phy);
}
mutex_unlock(&hdmi->state_mutex);
}
static void msm_hdmi_set_timings(struct hdmi *hdmi,
const struct drm_display_mode *mode)
{
int hstart, hend, vstart, vend;
uint32_t frame_ctrl;
hstart = mode->htotal - mode->hsync_start;
hend = mode->htotal - mode->hsync_start + mode->hdisplay;
vstart = mode->vtotal - mode->vsync_start - 1;
vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1;
DBG("htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d",
mode->htotal, mode->vtotal, hstart, hend, vstart, vend);
hdmi_write(hdmi, REG_HDMI_TOTAL,
HDMI_TOTAL_H_TOTAL(mode->htotal - 1) |
HDMI_TOTAL_V_TOTAL(mode->vtotal - 1));
hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC,
HDMI_ACTIVE_HSYNC_START(hstart) |
HDMI_ACTIVE_HSYNC_END(hend));
hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC,
HDMI_ACTIVE_VSYNC_START(vstart) |
HDMI_ACTIVE_VSYNC_END(vend));
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal));
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) |
HDMI_VSYNC_ACTIVE_F2_END(vend + 1));
} else {
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
HDMI_VSYNC_TOTAL_F2_V_TOTAL(0));
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
HDMI_VSYNC_ACTIVE_F2_START(0) |
HDMI_VSYNC_ACTIVE_F2_END(0));
}
frame_ctrl = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN;
DBG("frame_ctrl=%08x", frame_ctrl);
hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl);
}
static const struct drm_edid *msm_hdmi_bridge_edid_read(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
const struct drm_edid *drm_edid;
uint32_t hdmi_ctrl;
hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE);
drm_edid = drm_edid_read_ddc(connector, hdmi->i2c);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
return drm_edid;
}
static enum drm_mode_status msm_hdmi_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge,
const struct drm_display_mode *mode,
unsigned long long tmds_rate)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct msm_drm_private *priv = bridge->dev->dev_private;
struct msm_kms *kms = priv->kms;
long actual;
/* for mdp5/apq8074, we manage our own pixel clk (as opposed to
* mdp4/dtv stuff where pixel clk is assigned to mdp/encoder
* instead):
*/
if (kms->funcs->round_pixclk)
actual = kms->funcs->round_pixclk(kms,
tmds_rate,
hdmi_bridge->hdmi->encoder);
else if (hdmi->extp_clk)
actual = clk_round_rate(hdmi->extp_clk, tmds_rate);
else
actual = tmds_rate;
DBG("requested=%lld, actual=%ld", tmds_rate, actual);
if (actual != tmds_rate)
return MODE_CLOCK_RANGE;
return 0;
}
static const struct drm_bridge_funcs msm_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_pre_enable = msm_hdmi_bridge_atomic_pre_enable,
.atomic_post_disable = msm_hdmi_bridge_atomic_post_disable,
.edid_read = msm_hdmi_bridge_edid_read,
.detect = msm_hdmi_bridge_detect,
.hpd_enable = msm_hdmi_hpd_enable,
.hpd_disable = msm_hdmi_hpd_disable,
.hdmi_tmds_char_rate_valid = msm_hdmi_bridge_tmds_char_rate_valid,
.hdmi_clear_infoframe = msm_hdmi_bridge_clear_infoframe,
.hdmi_write_infoframe = msm_hdmi_bridge_write_infoframe,
.hdmi_audio_prepare = msm_hdmi_bridge_audio_prepare,
.hdmi_audio_shutdown = msm_hdmi_bridge_audio_shutdown,
};
static void
msm_hdmi_hotplug_work(struct work_struct *work)
{
struct hdmi_bridge *hdmi_bridge =
container_of(work, struct hdmi_bridge, hpd_work);
struct drm_bridge *bridge = &hdmi_bridge->base;
drm_bridge_hpd_notify(bridge, drm_bridge_detect(bridge, hdmi_bridge->hdmi->connector));
}
/* initialize bridge */
int msm_hdmi_bridge_init(struct hdmi *hdmi)
{
struct drm_bridge *bridge = NULL;
struct hdmi_bridge *hdmi_bridge;
int ret;
hdmi_bridge = devm_drm_bridge_alloc(hdmi->dev->dev, struct hdmi_bridge, base,
&msm_hdmi_bridge_funcs);
if (IS_ERR(hdmi_bridge))
return PTR_ERR(hdmi_bridge);
hdmi_bridge->hdmi = hdmi;
INIT_WORK(&hdmi_bridge->hpd_work, msm_hdmi_hotplug_work);
bridge = &hdmi_bridge->base;
bridge->ddc = hdmi->i2c;
bridge->type = DRM_MODE_CONNECTOR_HDMIA;
bridge->vendor = "Qualcomm";
bridge->product = "Snapdragon";
bridge->ops = DRM_BRIDGE_OP_HPD |
DRM_BRIDGE_OP_DETECT |
DRM_BRIDGE_OP_HDMI |
DRM_BRIDGE_OP_HDMI_AUDIO |
DRM_BRIDGE_OP_EDID;
bridge->hdmi_audio_max_i2s_playback_channels = 8;
bridge->hdmi_audio_dev = &hdmi->pdev->dev;
bridge->hdmi_audio_dai_port = -1;
ret = devm_drm_bridge_add(hdmi->dev->dev, bridge);
if (ret)
return ret;
ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
return ret;
hdmi->bridge = bridge;
return 0;
}