Skip to content

Interrupt-based HDMI Hotplug Detection #4313

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

Merged
merged 9 commits into from
May 7, 2021
2 changes: 2 additions & 0 deletions drivers/clk/clk.c
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,8 @@ static int clk_core_determine_round_nolock(struct clk_core *core,
if (!core)
return 0;

req->rate = clamp(req->rate, req->min_rate, req->max_rate);

/*
* At this point, core protection will be disabled if
* - if the provider is not protected at all
Expand Down
80 changes: 50 additions & 30 deletions drivers/gpu/drm/vc4/vc4_crtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -272,44 +272,31 @@ 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.
*
* VC4 can only scan out to one encoder at a time, while the DRM core
* allows drivers to push pixels to more than one encoder from the
* same CRTC.
*/
static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc)
static struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
struct drm_atomic_state *state,
struct drm_connector_state *(*get_state)(struct drm_atomic_state *state,
struct drm_connector *connector))
{
struct drm_connector *connector;
struct drm_connector_list_iter conn_iter;

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

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

vc4_encoder = to_vc4_encoder(encoder);
if (vc4_encoder->crtc == crtc) {
if (conn_state->crtc == crtc) {
drm_connector_list_iter_end(&conn_iter);
return encoder;
return connector->encoder;
}
}
drm_connector_list_iter_end(&conn_iter);
Expand All @@ -326,16 +313,17 @@ static void vc4_crtc_pixelvalve_reset(struct drm_crtc *crtc)
CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_FIFO_CLR);
}

static void vc4_crtc_config_pv(struct drm_crtc *crtc)
static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_atomic_state *state)
{
struct drm_device *dev = crtc->dev;
struct vc4_dev *vc4 = to_vc4_dev(dev);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
drm_atomic_get_new_connector_state);
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
struct drm_crtc_state *state = crtc->state;
struct drm_display_mode *mode = &state->adjusted_mode;
struct drm_crtc_state *crtc_state = crtc->state;
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1;
bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 ||
Expand Down Expand Up @@ -443,10 +431,10 @@ static void require_hvs_enabled(struct drm_device *dev)
}

static int vc4_crtc_disable(struct drm_crtc *crtc,
struct drm_encoder *encoder,
struct drm_atomic_state *state,
unsigned int channel)
{
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc);
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct drm_device *dev = crtc->dev;
Expand Down Expand Up @@ -487,10 +475,29 @@ static int vc4_crtc_disable(struct drm_crtc *crtc,
return 0;
}

static struct drm_encoder *vc4_crtc_get_encoder_by_type(struct drm_crtc *crtc,
enum vc4_encoder_type type)
{
struct drm_encoder *encoder;

drm_for_each_encoder(encoder, crtc->dev) {
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);

if (vc4_encoder->type == type)
return encoder;
}

return NULL;
}

int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
{
struct drm_device *drm = crtc->dev;
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
enum vc4_encoder_type encoder_type;
const struct vc4_pv_data *pv_data;
struct drm_encoder *encoder;
unsigned encoder_sel;
int channel;

if (!(of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
Expand All @@ -509,7 +516,17 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
if (channel < 0)
return 0;

return vc4_crtc_disable(crtc, NULL, channel);
encoder_sel = VC4_GET_FIELD(CRTC_READ(PV_CONTROL), PV_CONTROL_CLK_SELECT);
if (WARN_ON(encoder_sel != 0))
return 0;

pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
encoder_type = pv_data->encoder_types[encoder_sel];
encoder = vc4_crtc_get_encoder_by_type(crtc, encoder_type);
if (WARN_ON(!encoder))
return 0;

return vc4_crtc_disable(crtc, encoder, NULL, channel);
}

static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
Expand All @@ -518,14 +535,16 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
crtc);
struct vc4_crtc_state *old_vc4_state = to_vc4_crtc_state(old_state);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
drm_atomic_get_old_connector_state);
struct drm_device *dev = crtc->dev;

require_hvs_enabled(dev);

/* Disable vblank irq handling before crtc is disabled. */
drm_crtc_vblank_off(crtc);

vc4_crtc_disable(crtc, state, old_vc4_state->assigned_channel);
vc4_crtc_disable(crtc, encoder, state, old_vc4_state->assigned_channel);

/*
* Make sure we issue a vblank event after disabling the CRTC if
Expand All @@ -546,7 +565,8 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
{
struct drm_device *dev = crtc->dev;
struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc);
struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, state,
drm_atomic_get_new_connector_state);
struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);

require_hvs_enabled(dev);
Expand All @@ -561,7 +581,7 @@ static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
if (vc4_encoder->pre_crtc_configure)
vc4_encoder->pre_crtc_configure(encoder, state);

vc4_crtc_config_pv(crtc);
vc4_crtc_config_pv(crtc, state);

CRTC_WRITE(PV_CONTROL, CRTC_READ(PV_CONTROL) | PV_CONTROL_EN);

Expand Down
36 changes: 0 additions & 36 deletions drivers/gpu/drm/vc4/vc4_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,41 +226,6 @@ 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 @@ -343,7 +308,6 @@ static int vc4_drm_bind(struct device *dev)
ret = component_bind_all(dev, drm);
if (ret)
return ret;
vc4_drv_set_encoder_data(drm);

if (!vc4->firmware_kms) {
ret = vc4_plane_create_additional_planes(drm);
Expand Down
10 changes: 0 additions & 10 deletions drivers/gpu/drm/vc4/vc4_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -445,16 +445,6 @@ 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
82 changes: 60 additions & 22 deletions drivers/gpu/drm/vc4/vc4_hdmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,12 @@ static enum drm_connector_status
vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
enum drm_connector_status ret = connector_status_disconnected;
bool connected = false;

WARN_ON(pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev));
WARN_ON(clk_prepare_enable(vc4_hdmi->hsm_clock));

if (vc4_hdmi->hpd_gpio) {
if (gpio_get_value_cansleep(vc4_hdmi->hpd_gpio) ^
vc4_hdmi->hpd_active_low)
Expand All @@ -500,11 +504,16 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
}
}

return connector_status_connected;
ret = connector_status_connected;
goto out;
}

cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
return connector_status_disconnected;

out:
clk_disable_unprepare(vc4_hdmi->hsm_clock);
pm_runtime_put(&vc4_hdmi->pdev->dev);
return ret;
}

static void vc4_hdmi_connector_destroy(struct drm_connector *connector)
Expand Down Expand Up @@ -1175,36 +1184,21 @@ static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi)
"VC4_HDMI_FIFO_CTL_RECENTER_DONE");
}

static struct drm_connector_state *
vc4_hdmi_encoder_get_connector_state(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct drm_connector_state *conn_state;
struct drm_connector *connector;
unsigned int i;

for_each_new_connector_in_state(state, connector, conn_state, i) {
if (conn_state->best_encoder == encoder)
return conn_state;
}

return NULL;
}

static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
struct drm_connector *connector = &vc4_hdmi->connector;
struct drm_connector_state *conn_state =
vc4_hdmi_encoder_get_connector_state(encoder, state);
drm_atomic_get_new_connector_state(state, connector);
struct vc4_hdmi_connector_state *vc4_conn_state =
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 bvb_rate, pixel_rate, hsm_rate;
int ret;

ret = pm_runtime_get_sync(&vc4_hdmi->pdev->dev);
if (ret < 0) {
ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev);
if (ret) {
DRM_ERROR("Failed to retain power domain: %d\n", ret);
return;
}
Expand Down Expand Up @@ -2145,6 +2139,46 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)

}

static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv)
{
struct vc4_hdmi *vc4_hdmi = priv;
struct drm_device *dev = vc4_hdmi->connector.dev;

if (dev)
drm_kms_helper_hotplug_event(dev);

return IRQ_HANDLED;
}

static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi)
{
struct platform_device *pdev = vc4_hdmi->pdev;
struct device *dev = &pdev->dev;
int ret;

if (vc4_hdmi->variant->external_irq_controller) {
ret = devm_request_threaded_irq(dev,
platform_get_irq_byname(pdev, "hpd-connected"),
NULL,
vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT,
"vc4 hdmi hpd connected", vc4_hdmi);
if (ret)
return ret;

ret = devm_request_threaded_irq(dev,
platform_get_irq_byname(pdev, "hpd-removed"),
NULL,
vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT,
"vc4 hdmi hpd disconnected", vc4_hdmi);
if (ret)
return ret;

connector->polled = DRM_CONNECTOR_POLL_HPD;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't compile (error: ‘connector’ undeclared). @mripard was there something lost in a rebase?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it needs:

diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
index f84c8257bd7e..f1930549de1f 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
@@ -2177,6 +2177,7 @@ static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv)
 static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi)
 {
        struct platform_device *pdev = vc4_hdmi->pdev;
+       struct drm_connector *connector = &vc4_hdmi->connector;
        struct device *dev = &pdev->dev;
        int ret;

}

return 0;
}

#ifdef CONFIG_DRM_VC4_HDMI_CEC
static irqreturn_t vc4_cec_irq_handler_rx_thread(int irq, void *priv)
{
Expand Down Expand Up @@ -2744,6 +2778,10 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
if (ret)
goto err_destroy_encoder;

ret = vc4_hdmi_hotplug_init(vc4_hdmi);
if (ret)
goto err_destroy_conn;

ret = vc4_hdmi_cec_init(vc4_hdmi);
if (ret)
goto err_destroy_conn;
Expand Down