diff options
author | Thomas Zimmermann <tzimmermann@suse.de> | 2022-07-27 09:41:27 +0200 |
---|---|---|
committer | Thomas Zimmermann <tzimmermann@suse.de> | 2022-07-27 09:41:27 +0200 |
commit | 9c7f5cf088789957dcfb460cca1ab0fb578f2376 (patch) | |
tree | 6f5f305e5021fe5babb17feaf67be99d0f33e2ba /drivers/gpu/drm/vc4/vc4_hdmi.c | |
parent | 592c718e422de34b4c56ee66d2f02d3726b244ab (diff) | |
parent | 5727dcfd8486399c40e39d2c08fe36fedab29d99 (diff) |
Merge remote-tracking branch 'drm-misc/drm-misc-next' into drm-tip
# Conflicts:
# drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
Diffstat (limited to 'drivers/gpu/drm/vc4/vc4_hdmi.c')
-rw-r--r-- | drivers/gpu/drm/vc4/vc4_hdmi.c | 683 |
1 files changed, 500 insertions, 183 deletions
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 6ab83296b0e4..6f61a1b8a1a3 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -34,6 +34,7 @@ #include <drm/display/drm_hdmi_helper.h> #include <drm/display/drm_scdc_helper.h> #include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h> #include <linux/clk.h> @@ -146,7 +147,12 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) { struct drm_info_node *node = (struct drm_info_node *)m->private; struct vc4_hdmi *vc4_hdmi = node->info_ent->data; + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_printer p = drm_seq_file_printer(m); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV; drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); drm_print_regset32(&p, &vc4_hdmi->hd_regset); @@ -157,12 +163,23 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) drm_print_regset32(&p, &vc4_hdmi->ram_regset); drm_print_regset32(&p, &vc4_hdmi->rm_regset); + drm_dev_exit(idx); + return 0; } static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + /* + * We can be called by our bind callback, when the + * connector->dev pointer might not be initialised yet. + */ + if (drm && !drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -179,11 +196,23 @@ static void vc4_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_SW_RESET_CONTROL, 0); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + if (drm) + drm_dev_exit(idx); } static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + /* + * We can be called by our bind callback, when the + * connector->dev pointer might not be initialised yet. + */ + if (drm && !drm_dev_enter(drm, &idx)) + return; reset_control_reset(vc4_hdmi->reset); @@ -195,15 +224,31 @@ static void vc5_hdmi_reset(struct vc4_hdmi *vc4_hdmi) HDMI_READ(HDMI_CLOCK_STOP) | VC4_DVP_HT_CLOCK_STOP_PIXEL); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + if (drm) + drm_dev_exit(idx); } #ifdef CONFIG_DRM_VC4_HDMI_CEC static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) { - unsigned long cec_rate = clk_get_rate(vc4_hdmi->cec_clock); + struct drm_device *drm = vc4_hdmi->connector.dev; + unsigned long cec_rate; unsigned long flags; u16 clk_cnt; u32 value; + int idx; + + /* + * This function is called by our runtime_resume implementation + * and thus at bind time, when we haven't registered our + * connector yet and thus don't have a pointer to the DRM + * device. + */ + if (drm && !drm_dev_enter(drm, &idx)) + return; + + cec_rate = clk_get_rate(vc4_hdmi->cec_clock); spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -219,6 +264,9 @@ static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) HDMI_WRITE(HDMI_CEC_CNTRL_1, value); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + if (drm) + drm_dev_exit(idx); } #else static void vc4_hdmi_cec_update_clk_div(struct vc4_hdmi *vc4_hdmi) {} @@ -267,12 +315,6 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force) return connector_status_disconnected; } -static void vc4_hdmi_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - static int vc4_hdmi_connector_get_modes(struct drm_connector *connector) { struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector); @@ -380,7 +422,6 @@ vc4_hdmi_connector_duplicate_state(struct drm_connector *connector) static const struct drm_connector_funcs vc4_hdmi_connector_funcs = { .detect = vc4_hdmi_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = vc4_hdmi_connector_destroy, .reset = vc4_hdmi_connector_reset, .atomic_duplicate_state = vc4_hdmi_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, @@ -398,10 +439,13 @@ static int vc4_hdmi_connector_init(struct drm_device *dev, struct drm_encoder *encoder = &vc4_hdmi->encoder.base; int ret; - drm_connector_init_with_ddc(dev, connector, - &vc4_hdmi_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA, - vc4_hdmi->ddc); + ret = drmm_connector_init(dev, connector, + &vc4_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + vc4_hdmi->ddc); + if (ret) + return ret; + drm_connector_helper_add(connector, &vc4_hdmi_connector_helper_funcs); /* @@ -444,25 +488,34 @@ static int vc4_hdmi_stop_packet(struct drm_encoder *encoder, bool poll) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = type - 0x80; unsigned long flags; + int ret = 0; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return -ENODEV; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, HDMI_READ(HDMI_RAM_PACKET_CONFIG) & ~BIT(packet_id)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); - if (!poll) - return 0; + if (poll) { + ret = wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & + BIT(packet_id)), 100); + } - return wait_for(!(HDMI_READ(HDMI_RAM_PACKET_STATUS) & - BIT(packet_id)), 100); + drm_dev_exit(idx); + return ret; } static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, union hdmi_infoframe *frame) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; u32 packet_id = frame->any.type - 0x80; const struct vc4_hdmi_register *ram_packet_start = &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START]; @@ -475,6 +528,10 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, unsigned long flags; ssize_t len, i; int ret; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; WARN_ONCE(!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE), @@ -482,12 +539,12 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); if (len < 0) - return; + goto out; ret = vc4_hdmi_stop_packet(encoder, frame->any.type, true); if (ret) { DRM_ERROR("Failed to wait for infoframe to go idle: %d\n", ret); - return; + goto out; } spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -523,6 +580,9 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, BIT(packet_id)), 100); if (ret) DRM_ERROR("Failed to wait for infoframe to start: %d\n", ret); + +out: + drm_dev_exit(idx); } static void vc4_hdmi_avi_infoframe_colorspace(struct hdmi_avi_infoframe *frame, @@ -672,8 +732,10 @@ static bool vc4_hdmi_supports_scrambling(struct drm_encoder *encoder, static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; unsigned long flags; + int idx; lockdep_assert_held(&vc4_hdmi->mutex); @@ -685,6 +747,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) vc4_hdmi->output_format)) return; + if (!drm_dev_enter(drm, &idx)) + return; + drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, true); drm_scdc_set_scrambling(vc4_hdmi->ddc, true); @@ -693,6 +758,8 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) VC5_HDMI_SCRAMBLER_CTL_ENABLE); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + vc4_hdmi->scdc_enabled = true; queue_delayed_work(system_wq, &vc4_hdmi->scrambling_work, @@ -702,7 +769,9 @@ static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder) static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; lockdep_assert_held(&vc4_hdmi->mutex); @@ -714,6 +783,9 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) if (delayed_work_pending(&vc4_hdmi->scrambling_work)) cancel_delayed_work_sync(&vc4_hdmi->scrambling_work); + if (!drm_dev_enter(drm, &idx)) + return; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_SCRAMBLER_CTL, HDMI_READ(HDMI_SCRAMBLER_CTL) & ~VC5_HDMI_SCRAMBLER_CTL_ENABLE); @@ -721,6 +793,8 @@ static void vc4_hdmi_disable_scrambling(struct drm_encoder *encoder) drm_scdc_set_scrambling(vc4_hdmi->ddc, false); drm_scdc_set_high_tmds_clock_ratio(vc4_hdmi->ddc, false); + + drm_dev_exit(idx); } static void vc4_hdmi_scrambling_wq(struct work_struct *work) @@ -743,12 +817,17 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; mutex_lock(&vc4_hdmi->mutex); vc4_hdmi->packet_ram_enabled = false; + if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0); @@ -766,6 +845,9 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, vc4_hdmi_disable_scrambling(encoder); + drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); } @@ -773,11 +855,16 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; int ret; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_BLANKPIX); @@ -793,6 +880,9 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, if (ret < 0) DRM_ERROR("Failed to release power domain: %d\n", ret); + drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); } @@ -800,8 +890,13 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 csc_ctl; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -836,6 +931,8 @@ static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } /* @@ -920,6 +1017,7 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); unsigned long flags; @@ -928,6 +1026,10 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, u32 csc_chan_ctl = 0; u32 csc_ctl = VC5_MT_CP_CSC_CTL_ENABLE | VC4_SET_FIELD(VC4_HD_CSC_CTL_MODE_CUSTOM, VC5_MT_CP_CSC_CTL_MODE); + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -970,12 +1072,15 @@ static void vc5_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CSC_CTL, csc_ctl); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; bool interlaced = mode->flags & DRM_MODE_FLAG_INTERLACE; @@ -995,6 +1100,10 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, VC4_HDMI_VERTB_VBP)); unsigned long flags; u32 reg; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -1027,12 +1136,15 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_MISC_CONTROL, reg); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, struct drm_display_mode *mode) { + struct drm_device *drm = vc4_hdmi->connector.dev; const struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(state); bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; @@ -1056,6 +1168,10 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, unsigned char gcp; bool gcp_en; u32 reg; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -1132,13 +1248,20 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_CLOCK_STOP, 0); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 drift; int ret; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -1167,12 +1290,15 @@ static void vc4_hdmi_recenter_fifo(struct vc4_hdmi *vc4_hdmi) VC4_HDMI_FIFO_CTL_RECENTER_DONE, 1); WARN_ONCE(ret, "Timeout waiting for " "VC4_HDMI_FIFO_CTL_RECENTER_DONE"); + + drm_dev_exit(idx); } 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_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); @@ -1183,9 +1309,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, unsigned long bvb_rate, hsm_rate; unsigned long flags; int ret; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) + goto out; + /* * As stated in RPi's vc4 firmware "HDMI state machine (HSM) clock must * be faster than pixel clock, infinitesimally faster, tested in @@ -1206,13 +1336,13 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, ret = clk_set_min_rate(vc4_hdmi->hsm_clock, hsm_rate); if (ret) { DRM_ERROR("Failed to set HSM clock rate: %d\n", ret); - goto out; + goto err_dev_exit; } ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); if (ret < 0) { DRM_ERROR("Failed to retain power domain: %d\n", ret); - goto out; + goto err_dev_exit; } ret = clk_set_rate(vc4_hdmi->pixel_clock, tmds_char_rate); @@ -1264,6 +1394,8 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder, if (vc4_hdmi->variant->set_timings) vc4_hdmi->variant->set_timings(vc4_hdmi, conn_state, mode); + drm_dev_exit(idx); + mutex_unlock(&vc4_hdmi->mutex); return; @@ -1272,6 +1404,8 @@ err_disable_pixel_clock: clk_disable_unprepare(vc4_hdmi->pixel_clock); err_put_runtime_pm: pm_runtime_put(&vc4_hdmi->pdev->dev); +err_dev_exit: + drm_dev_exit(idx); out: mutex_unlock(&vc4_hdmi->mutex); return; @@ -1281,14 +1415,19 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_connector *connector = &vc4_hdmi->connector; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state, connector); unsigned long flags; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) + return; + if (vc4_hdmi->variant->csc_setup) vc4_hdmi->variant->csc_setup(vc4_hdmi, conn_state, mode); @@ -1296,6 +1435,8 @@ static void vc4_hdmi_encoder_pre_crtc_enable(struct drm_encoder *encoder, HDMI_WRITE(HDMI_FIFO_CTL, VC4_HDMI_FIFO_CTL_MASTER_SLAVE_N); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + mutex_unlock(&vc4_hdmi->mutex); } @@ -1303,15 +1444,20 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_display_mode *mode = &vc4_hdmi->saved_adjusted_mode; struct drm_display_info *display = &vc4_hdmi->connector.display_info; bool hsync_pos = mode->flags & DRM_MODE_FLAG_PHSYNC; bool vsync_pos = mode->flags & DRM_MODE_FLAG_PVSYNC; unsigned long flags; int ret; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) + return; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_VID_CTL, @@ -1370,6 +1516,7 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, vc4_hdmi_recenter_fifo(vc4_hdmi); vc4_hdmi_enable_scrambling(encoder); + drm_dev_exit(idx); mutex_unlock(&vc4_hdmi->mutex); } @@ -1692,6 +1839,26 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { .mode_valid = vc4_hdmi_encoder_mode_valid, }; +static int vc4_hdmi_late_register(struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + const struct vc4_hdmi_variant *variant = vc4_hdmi->variant; + int ret; + + ret = vc4_debugfs_add_file(drm->primary, variant->debugfs_name, + vc4_hdmi_debugfs_regs, + vc4_hdmi); + if (ret) + return ret; + + return 0; +} + +static const struct drm_encoder_funcs vc4_hdmi_encoder_funcs = { + .late_register = vc4_hdmi_late_register, +}; + static u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) { int i; @@ -1718,13 +1885,20 @@ static u32 vc5_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) { + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; u32 hotplug; + int idx; + + if (!drm_dev_enter(drm, &idx)) + return false; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); hotplug = HDMI_READ(HDMI_HOTPLUG); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + return !!(hotplug & VC4_HDMI_HOTPLUG_CONNECTED); } @@ -1732,10 +1906,16 @@ static bool vc5_hdmi_hp_detect(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate) { - u32 hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); + struct drm_device *drm = vc4_hdmi->connector.dev; + u32 hsm_clock; unsigned long flags; unsigned long n, m; + int idx; + if (!drm_dev_enter(drm, &idx)) + return; + + hsm_clock = clk_get_rate(vc4_hdmi->audio_clock); rational_best_approximation(hsm_clock, samplerate, VC4_HD_MAI_SMP_N_MASK >> VC4_HD_MAI_SMP_N_SHIFT, @@ -1748,6 +1928,8 @@ static void vc4_hdmi_audio_set_mai_clock(struct vc4_hdmi *vc4_hdmi, VC4_SET_FIELD(n, VC4_HD_MAI_SMP_N) | VC4_SET_FIELD(m - 1, VC4_HD_MAI_SMP_M)); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + drm_dev_exit(idx); } static void vc4_hdmi_set_n_cts(struct vc4_hdmi *vc4_hdmi, unsigned int samplerate) @@ -1803,13 +1985,21 @@ static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi) static int vc4_hdmi_audio_startup(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int ret = 0; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) { + ret = -ENODEV; + goto out; + } + if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) { - mutex_unlock(&vc4_hdmi->mutex); - return -ENODEV; + ret = -ENODEV; + goto out_dev_exit; } vc4_hdmi->audio.streaming = true; @@ -1826,9 +2016,12 @@ static int vc4_hdmi_audio_startup(struct device *dev, void *data) if (vc4_hdmi->variant->phy_rng_enable) vc4_hdmi->variant->phy_rng_enable(vc4_hdmi); +out_dev_exit: + drm_dev_exit(idx); +out: mutex_unlock(&vc4_hdmi->mutex); - return 0; + return ret; } static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) @@ -1857,10 +2050,15 @@ static void vc4_hdmi_audio_reset(struct vc4_hdmi *vc4_hdmi) static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) + goto out; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_MAI_CTL, @@ -1876,6 +2074,9 @@ static void vc4_hdmi_audio_shutdown(struct device *dev, void *data) vc4_hdmi->audio.streaming = false; vc4_hdmi_audio_reset(vc4_hdmi); + drm_dev_exit(idx); + +out: mutex_unlock(&vc4_hdmi->mutex); } @@ -1923,6 +2124,7 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, struct hdmi_codec_params *params) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + struct drm_device *drm = vc4_hdmi->connector.dev; struct drm_encoder *encoder = &vc4_hdmi->encoder.base; unsigned int sample_rate = params->sample_rate; unsigned int channels = params->channels; @@ -1931,15 +2133,22 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, u32 channel_map; u32 mai_audio_format; u32 mai_sample_rate; + int ret = 0; + int idx; dev_dbg(dev, "%s: %u Hz, %d bit, %d channels\n", __func__, sample_rate, params->sample_width, channels); mutex_lock(&vc4_hdmi->mutex); + if (!drm_dev_enter(drm, &idx)) { + ret = -ENODEV; + goto out; + } + if (!vc4_hdmi_audio_can_stream(vc4_hdmi)) { - mutex_unlock(&vc4_hdmi->mutex); - return -EINVAL; + ret = -EINVAL; + goto out_dev_exit; } vc4_hdmi_audio_set_mai_clock(vc4_hdmi, sample_rate); @@ -1996,9 +2205,12 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, memcpy(&vc4_hdmi->audio.infoframe, ¶ms->cea, sizeof(params->cea)); vc4_hdmi_set_audio_infoframe(encoder); +out_dev_exit: + drm_dev_exit(idx); +out: mutex_unlock(&vc4_hdmi->mutex); - return 0; + return ret; } static const struct snd_soc_component_driver vc4_hdmi_audio_cpu_dai_comp = { @@ -2060,6 +2272,14 @@ static struct hdmi_codec_pdata vc4_hdmi_codec_pdata = { .i2s = 1, }; +static void vc4_hdmi_audio_codec_release(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + platform_device_unregister(vc4_hdmi->audio.codec_pdev); + vc4_hdmi->audio.codec_pdev = NULL; +} + static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) { const struct vc4_hdmi_register *mai_data = @@ -2072,6 +2292,26 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) int index, len; int ret; + /* + * ASoC makes it a bit hard to retrieve a pointer to the + * vc4_hdmi structure. Registering the card will overwrite our + * device drvdata with a pointer to the snd_soc_card structure, + * which can then be used to retrieve whatever drvdata we want + * to associate. + * + * However, that doesn't fly in the case where we wouldn't + * register an ASoC card (because of an old DT that is missing + * the dmas properties for example), then the card isn't + * registered and the device drvdata wouldn't be set. + * + * We can deal with both cases by making sure a snd_soc_card + * pointer and a vc4_hdmi structure are pointing to the same + * memory address, so we can treat them indistinctly without any + * issue. + */ + BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); + BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); + if (!of_find_property(dev->of_node, "dmas", &len) || !len) { dev_warn(dev, "'dmas' DT property is missing or empty, no HDMI audio\n"); @@ -2101,6 +2341,30 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; vc4_hdmi->audio.dma_data.maxburst = 2; + /* + * NOTE: Strictly speaking, we should probably use a DRM-managed + * registration there to avoid removing all the audio components + * by the time the driver doesn't have any user anymore. + * + * However, the ASoC core uses a number of devm_kzalloc calls + * when registering, even when using non-device-managed + * functions (such as in snd_soc_register_component()). + * + * If we call snd_soc_unregister_component() in a DRM-managed + * action, the device-managed actions have already been executed + * and thus we would access memory that has been freed. + * + * Using device-managed hooks here probably leaves us open to a + * bunch of issues if userspace still has a handle on the ALSA + * device when the device is removed. However, this is mitigated + * by the use of drm_dev_enter()/drm_dev_exit() in the audio + * path to prevent the access to the device resources if it + * isn't there anymore. + * + * Then, the vc4_hdmi structure is DRM-managed and thus only + * freed whenever the last user has closed the DRM device file. + * It should thus outlive ALSA in most situations. + */ ret = devm_snd_dmaengine_pcm_register(dev, &pcm_conf, 0); if (ret) { dev_err(dev, "Could not register PCM component: %d\n", ret); @@ -2124,6 +2388,10 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) } vc4_hdmi->audio.codec_pdev = codec_pdev; + ret = devm_add_action_or_reset(dev, vc4_hdmi_audio_codec_release, vc4_hdmi); + if (ret) + return ret; + dai_link->cpus = &vc4_hdmi->audio.cpu; dai_link->codecs = &vc4_hdmi->audio.codec; dai_link->platforms = &vc4_hdmi->audio.platform; @@ -2162,12 +2430,6 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) } -static void vc4_hdmi_audio_exit(struct vc4_hdmi *vc4_hdmi) -{ - platform_device_unregister(vc4_hdmi->audio.codec_pdev); - vc4_hdmi->audio.codec_pdev = NULL; -} - static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv) { struct vc4_hdmi *vc4_hdmi = priv; @@ -2190,21 +2452,19 @@ static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi) unsigned int hpd_con = platform_get_irq_byname(pdev, "hpd-connected"); unsigned int hpd_rm = platform_get_irq_byname(pdev, "hpd-removed"); - ret = request_threaded_irq(hpd_con, - NULL, - vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, - "vc4 hdmi hpd connected", vc4_hdmi); + ret = devm_request_threaded_irq(&pdev->dev, hpd_con, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd connected", vc4_hdmi); if (ret) return ret; - ret = request_threaded_irq(hpd_rm, - NULL, - vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, - "vc4 hdmi hpd disconnected", vc4_hdmi); - if (ret) { - free_irq(hpd_con, vc4_hdmi); + ret = devm_request_threaded_irq(&pdev->dev, hpd_rm, + NULL, + vc4_hdmi_hpd_irq_thread, IRQF_ONESHOT, + "vc4 hdmi hpd disconnected", vc4_hdmi); + if (ret) return ret; - } connector->polled = DRM_CONNECTOR_POLL_HPD; } @@ -2212,16 +2472,6 @@ static int vc4_hdmi_hotplug_init(struct vc4_hdmi *vc4_hdmi) return 0; } -static void vc4_hdmi_hotplug_exit(struct vc4_hdmi *vc4_hdmi) -{ - struct platform_device *pdev = vc4_hdmi->pdev; - - if (vc4_hdmi->variant->external_irq_controller) { - free_irq(platform_get_irq_byname(pdev, "hpd-connected"), vc4_hdmi); - free_irq(platform_get_irq_byname(pdev, "hpd-removed"), vc4_hdmi); - } -} - #ifdef CONFIG_DRM_VC4_HDMI_CEC static irqreturn_t vc4_cec_irq_handler_rx_thread(int irq, void *priv) { @@ -2295,6 +2545,17 @@ static irqreturn_t vc4_cec_irq_handler_tx_bare_locked(struct vc4_hdmi *vc4_hdmi) { u32 cntrl1; + /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + lockdep_assert_held(&vc4_hdmi->hw_lock); cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); @@ -2323,6 +2584,17 @@ static irqreturn_t vc4_cec_irq_handler_rx_bare_locked(struct vc4_hdmi *vc4_hdmi) lockdep_assert_held(&vc4_hdmi->hw_lock); + /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + vc4_hdmi->cec_rx_msg.len = 0; cntrl1 = HDMI_READ(HDMI_CEC_CNTRL_1); vc4_cec_read_msg(vc4_hdmi, cntrl1); @@ -2354,6 +2626,17 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) irqreturn_t ret; u32 cntrl5; + /* + * We don't need to protect the register access using + * drm_dev_enter() there because the interrupt handler lifetime + * is tied to the device itself, and not to the DRM device. + * + * So when the device will be gone, one of the first thing we + * will be doing will be to unregister the interrupt handler, + * and then unregister the DRM device. drm_dev_enter() would + * thus always succeed if we are here. + */ + if (!(stat & VC4_HDMI_CPU_CEC)) return IRQ_NONE; @@ -2374,11 +2657,13 @@ static irqreturn_t vc4_cec_irq_handler(int irq, void *priv) static int vc4_hdmi_cec_enable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; /* clock period in microseconds */ const u32 usecs = 1000000 / CEC_CLOCK_FREQ; unsigned long flags; u32 val; int ret; + int idx; /* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2391,9 +2676,19 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap) * keep it in mind if we were to change that assumption. */ + if (!drm_dev_enter(drm, &idx)) + /* + * We can't return an error code, because the CEC + * framework will emit WARN_ON messages at unbind + * otherwise. + */ + return 0; + ret = pm_runtime_resume_and_get(&vc4_hdmi->pdev->dev); - if (ret) + if (ret) { + drm_dev_exit(idx); return ret; + } spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -2429,13 +2724,25 @@ static int vc4_hdmi_cec_enable(struct cec_adapter *adap) spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + return 0; } static int vc4_hdmi_cec_disable(struct cec_adapter *adap) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; + + if (!drm_dev_enter(drm, &idx)) + /* + * We can't return an error code, because the CEC + * framework will emit WARN_ON messages at unbind + * otherwise. + */ + return 0; /* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2460,6 +2767,8 @@ static int vc4_hdmi_cec_disable(struct cec_adapter *adap) pm_runtime_put(&vc4_hdmi->pdev->dev); + drm_dev_exit(idx); + return 0; } @@ -2474,7 +2783,9 @@ static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable) static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) { struct vc4_hdmi *vc4_hdmi = cec_get_drvdata(adap); + struct drm_device *drm = vc4_hdmi->connector.dev; unsigned long flags; + int idx; /* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2487,12 +2798,22 @@ static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) * keep it in mind if we were to change that assumption. */ + if (!drm_dev_enter(drm, &idx)) + /* + * We can't return an error code, because the CEC + * framework will emit WARN_ON messages at unbind + * otherwise. + */ + return 0; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_CEC_CNTRL_1, (HDMI_READ(HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) | (log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + return 0; } @@ -2504,6 +2825,7 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, unsigned long flags; u32 val; unsigned int i; + int idx; /* * NOTE: This function should really take vc4_hdmi->mutex, but doing so @@ -2516,8 +2838,12 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, * keep it in mind if we were to change that assumption. */ + if (!drm_dev_enter(dev, &idx)) + return -ENODEV; + if (msg->len > 16) { drm_err(dev, "Attempting to transmit too much data (%d)\n", msg->len); + drm_dev_exit(idx); return -ENOMEM; } @@ -2541,6 +2867,8 @@ static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + drm_dev_exit(idx); + return 0; } @@ -2550,6 +2878,14 @@ static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = { .adap_transmit = vc4_hdmi_cec_adap_transmit, }; +static void vc4_hdmi_cec_release(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + cec_unregister_adapter(vc4_hdmi->cec_adap); + vc4_hdmi->cec_adap = NULL; +} + static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) { struct cec_connector_info conn_info; @@ -2574,73 +2910,82 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info); if (vc4_hdmi->variant->external_irq_controller) { - ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-rx"), - vc4_cec_irq_handler_rx_bare, - vc4_cec_irq_handler_rx_thread, 0, - "vc4 hdmi cec rx", vc4_hdmi); + ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-rx"), + vc4_cec_irq_handler_rx_bare, + vc4_cec_irq_handler_rx_thread, 0, + "vc4 hdmi cec rx", vc4_hdmi); if (ret) goto err_delete_cec_adap; - ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-tx"), - vc4_cec_irq_handler_tx_bare, - vc4_cec_irq_handler_tx_thread, 0, - "vc4 hdmi cec tx", vc4_hdmi); + ret = devm_request_threaded_irq(dev, platform_get_irq_byname(pdev, "cec-tx"), + vc4_cec_irq_handler_tx_bare, + vc4_cec_irq_handler_tx_thread, 0, + "vc4 hdmi cec tx", vc4_hdmi); if (ret) - goto err_remove_cec_rx_handler; + goto err_delete_cec_adap; } else { - ret = request_threaded_irq(platform_get_irq(pdev, 0), - vc4_cec_irq_handler, - vc4_cec_irq_handler_thread, 0, - "vc4 hdmi cec", vc4_hdmi); + ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0), + vc4_cec_irq_handler, + vc4_cec_irq_handler_thread, 0, + "vc4 hdmi cec", vc4_hdmi); if (ret) goto err_delete_cec_adap; } ret = cec_register_adapter(vc4_hdmi->cec_adap, &pdev->dev); if (ret < 0) - goto err_remove_handlers; + goto err_delete_cec_adap; - return 0; - -err_remove_handlers: - if (vc4_hdmi->variant->external_irq_controller) - free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi); - else - free_irq(platform_get_irq(pdev, 0), vc4_hdmi); + /* + * NOTE: Strictly speaking, we should probably use a DRM-managed + * registration there to avoid removing the CEC adapter by the + * time the DRM driver doesn't have any user anymore. + * + * However, the CEC framework already cleans up the CEC adapter + * only when the last user has closed its file descriptor, so we + * don't need to handle it in DRM. + * + * By the time the device-managed hook is executed, we will give + * up our reference to the CEC adapter and therefore don't + * really care when it's actually freed. + * + * There's still a problematic sequence: if we unregister our + * CEC adapter, but the userspace keeps a handle on the CEC + * adapter but not the DRM device for some reason. In such a + * case, our vc4_hdmi structure will be freed, but the + * cec_adapter structure will have a dangling pointer to what + * used to be our HDMI controller. If we get a CEC call at that + * moment, we could end up with a use-after-free. Fortunately, + * the CEC framework already handles this too, by calling + * cec_is_registered() in cec_ioctl() and cec_poll(). + */ + ret = devm_add_action_or_reset(dev, vc4_hdmi_cec_release, vc4_hdmi); + if (ret) + return ret; -err_remove_cec_rx_handler: - if (vc4_hdmi->variant->external_irq_controller) - free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi); + return 0; err_delete_cec_adap: cec_delete_adapter(vc4_hdmi->cec_adap); return ret; } - -static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) -{ - struct platform_device *pdev = vc4_hdmi->pdev; - - if (vc4_hdmi->variant->external_irq_controller) { - free_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_hdmi); - free_irq(platform_get_irq_byname(pdev, "cec-tx"), vc4_hdmi); - } else { - free_irq(platform_get_irq(pdev, 0), vc4_hdmi); - } - - cec_unregister_adapter(vc4_hdmi->cec_adap); -} #else static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) { return 0; } - -static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {}; #endif -static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, +static void vc4_hdmi_free_regset(struct drm_device *drm, void *ptr) +{ + struct debugfs_reg32 *regs = ptr; + + kfree(regs); +} + +static int vc4_hdmi_build_regset(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi, struct debugfs_regset32 *regset, enum vc4_hdmi_regs reg) { @@ -2648,6 +2993,7 @@ static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, struct debugfs_reg32 *regs, *new_regs; unsigned int count = 0; unsigned int i; + int ret; regs = kcalloc(variant->num_registers, sizeof(*regs), GFP_KERNEL); @@ -2673,10 +3019,15 @@ static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, regset->regs = new_regs; regset->nregs = count; + ret = drmm_add_action_or_reset(drm, vc4_hdmi_free_regset, new_regs); + if (ret) + return ret; + return 0; } -static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +static int vc4_hdmi_init_resources(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi) { struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; @@ -2690,11 +3041,11 @@ static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) if (IS_ERR(vc4_hdmi->hd_regs)) return PTR_ERR(vc4_hdmi->hd_regs); - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); if (ret) return ret; @@ -2717,7 +3068,8 @@ static int vc4_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) return 0; } -static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) +static int vc5_hdmi_init_resources(struct drm_device *drm, + struct vc4_hdmi *vc4_hdmi) { struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; @@ -2819,42 +3171,42 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) return PTR_ERR(vc4_hdmi->reset); } - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->cec_regset, VC5_CEC); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->cec_regset, VC5_CEC); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->csc_regset, VC5_CSC); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->csc_regset, VC5_CSC); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->dvp_regset, VC5_DVP); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->dvp_regset, VC5_DVP); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->phy_regset, VC5_PHY); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->phy_regset, VC5_PHY); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->ram_regset, VC5_RAM); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->ram_regset, VC5_RAM); if (ret) return ret; - ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->rm_regset, VC5_RM); + ret = vc4_hdmi_build_regset(drm, vc4_hdmi, &vc4_hdmi->rm_regset, VC5_RM); if (ret) return ret; return 0; } -static int __maybe_unused vc4_hdmi_runtime_suspend(struct device *dev) +static int vc4_hdmi_runtime_suspend(struct device *dev) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); @@ -2897,6 +3249,13 @@ static int vc4_hdmi_runtime_resume(struct device *dev) return 0; } +static void vc4_hdmi_put_ddc_device(void *ptr) +{ + struct vc4_hdmi *vc4_hdmi = ptr; + + put_device(&vc4_hdmi->ddc->dev); +} + static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) { const struct vc4_hdmi_variant *variant = of_device_get_match_data(dev); @@ -2907,10 +3266,14 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) struct device_node *ddc_node; int ret; - vc4_hdmi = devm_kzalloc(dev, sizeof(*vc4_hdmi), GFP_KERNEL); + vc4_hdmi = drmm_kzalloc(drm, sizeof(*vc4_hdmi), GFP_KERNEL); if (!vc4_hdmi) return -ENOMEM; - mutex_init(&vc4_hdmi->mutex); + + ret = drmm_mutex_init(drm, &vc4_hdmi->mutex); + if (ret) + return ret; + spin_lock_init(&vc4_hdmi->hw_lock); INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq); @@ -2934,7 +3297,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) if (variant->max_pixel_clock > HDMI_14_MAX_TMDS_CLK) vc4_hdmi->scdc_enabled = true; - ret = variant->init_resources(vc4_hdmi); + ret = variant->init_resources(drm, vc4_hdmi); if (ret) return ret; @@ -2951,13 +3314,16 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) return -EPROBE_DEFER; } + ret = devm_add_action_or_reset(dev, vc4_hdmi_put_ddc_device, vc4_hdmi); + if (ret) + return ret; + /* Only use the GPIO HPD pin if present in the DT, otherwise * we'll use the HDMI core's register. */ vc4_hdmi->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); if (IS_ERR(vc4_hdmi->hpd_gpio)) { - ret = PTR_ERR(vc4_hdmi->hpd_gpio); - goto err_put_ddc; + return PTR_ERR(vc4_hdmi->hpd_gpio); } vc4_hdmi->disable_wifi_frequencies = @@ -2971,17 +3337,17 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) vc4_hdmi->disable_4kp60 = true; } + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + /* - * We need to have the device powered up at this point to call - * our reset hook and for the CEC init. + * We need to have the device powered up at this point to call + * our reset hook and for the CEC init. */ - ret = vc4_hdmi_runtime_resume(dev); + ret = pm_runtime_resume_and_get(dev); if (ret) - goto err_put_ddc; - - pm_runtime_get_noresume(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); + return ret; if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") || of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) && @@ -2991,92 +3357,43 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) clk_prepare_enable(vc4_hdmi->pixel_bvb_clock); } - drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); + ret = drmm_encoder_init(drm, encoder, + &vc4_hdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS, + NULL); + if (ret) + goto err_put_runtime_pm; + drm_encoder_helper_add(encoder, &vc4_hdmi_encoder_helper_funcs); ret = vc4_hdmi_connector_init(drm, vc4_hdmi); if (ret) - goto err_destroy_encoder; + goto err_put_runtime_pm; ret = vc4_hdmi_hotplug_init(vc4_hdmi); if (ret) - goto err_destroy_conn; + goto err_put_runtime_pm; ret = vc4_hdmi_cec_init(vc4_hdmi); if (ret) - goto err_free_hotplug; + goto err_put_runtime_pm; ret = vc4_hdmi_audio_init(vc4_hdmi); if (ret) - goto err_free_cec; - - vc4_debugfs_add_file(drm, variant->debugfs_name, - vc4_hdmi_debugfs_regs, - vc4_hdmi); + goto err_put_runtime_pm; pm_runtime_put_sync(dev); return 0; -err_free_cec: - vc4_hdmi_cec_exit(vc4_hdmi); -err_free_hotplug: - vc4_hdmi_hotplug_exit(vc4_hdmi); -err_destroy_conn: - vc4_hdmi_connector_destroy(&vc4_hdmi->connector); -err_destroy_encoder: - drm_encoder_cleanup(encoder); +err_put_runtime_pm: pm_runtime_put_sync(dev); - pm_runtime_disable(dev); -err_put_ddc: - put_device(&vc4_hdmi->ddc->dev); return ret; } -static void vc4_hdmi_unbind(struct device *dev, struct device *master, - void *data) -{ - struct vc4_hdmi *vc4_hdmi; - - /* - * ASoC makes it a bit hard to retrieve a pointer to the - * vc4_hdmi structure. Registering the card will overwrite our - * device drvdata with a pointer to the snd_soc_card structure, - * which can then be used to retrieve whatever drvdata we want - * to associate. - * - * However, that doesn't fly in the case where we wouldn't - * register an ASoC card (because of an old DT that is missing - * the dmas properties for example), then the card isn't - * registered and the device drvdata wouldn't be set. - * - * We can deal with both cases by making sure a snd_soc_card - * pointer and a vc4_hdmi structure are pointing to the same - * memory address, so we can treat them indistinctly without any - * issue. - */ - BUILD_BUG_ON(offsetof(struct vc4_hdmi_audio, card) != 0); - BUILD_BUG_ON(offsetof(struct vc4_hdmi, audio) != 0); - vc4_hdmi = dev_get_drvdata(dev); - - kfree(vc4_hdmi->hdmi_regset.regs); - kfree(vc4_hdmi->hd_regset.regs); - - vc4_hdmi_audio_exit(vc4_hdmi); - vc4_hdmi_cec_exit(vc4_hdmi); - vc4_hdmi_hotplug_exit(vc4_hdmi); - vc4_hdmi_connector_destroy(&vc4_hdmi->connector); - drm_encoder_cleanup(&vc4_hdmi->encoder.base); - - pm_runtime_disable(dev); - - put_device(&vc4_hdmi->ddc->dev); -} - static const struct component_ops vc4_hdmi_ops = { .bind = vc4_hdmi_bind, - .unbind = vc4_hdmi_unbind, }; static int vc4_hdmi_dev_probe(struct platform_device *pdev) |