diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/synopsys/dw-hdmi.c')
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 134 |
1 files changed, 101 insertions, 33 deletions
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index c6490949d9db..521d689413c8 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -27,7 +27,6 @@ #include <drm/bridge/dw_hdmi.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h> #include <drm/drm_of.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> @@ -190,6 +189,7 @@ struct dw_hdmi { void (*enable_audio)(struct dw_hdmi *hdmi); void (*disable_audio)(struct dw_hdmi *hdmi); + struct mutex cec_notifier_mutex; struct cec_notifier *cec_notifier; }; @@ -508,8 +508,14 @@ static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts, /* nshift factor = 0 */ hdmi_modb(hdmi, 0, HDMI_AUD_CTS3_N_SHIFT_MASK, HDMI_AUD_CTS3); - hdmi_writeb(hdmi, ((cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK) | - HDMI_AUD_CTS3_CTS_MANUAL, HDMI_AUD_CTS3); + /* Use automatic CTS generation mode when CTS is not set */ + if (cts) + hdmi_writeb(hdmi, ((cts >> 16) & + HDMI_AUD_CTS3_AUDCTS19_16_MASK) | + HDMI_AUD_CTS3_CTS_MANUAL, + HDMI_AUD_CTS3); + else + hdmi_writeb(hdmi, 0, HDMI_AUD_CTS3); hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2); hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1); @@ -579,24 +585,33 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, { unsigned long ftdms = pixel_clk; unsigned int n, cts; + u8 config3; u64 tmp; n = hdmi_compute_n(sample_rate, pixel_clk); - /* - * Compute the CTS value from the N value. Note that CTS and N - * can be up to 20 bits in total, so we need 64-bit math. Also - * note that our TDMS clock is not fully accurate; it is accurate - * to kHz. This can introduce an unnecessary remainder in the - * calculation below, so we don't try to warn about that. - */ - tmp = (u64)ftdms * n; - do_div(tmp, 128 * sample_rate); - cts = tmp; + config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID); - dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", - __func__, sample_rate, ftdms / 1000000, (ftdms / 1000) % 1000, - n, cts); + /* Only compute CTS when using internal AHB audio */ + if (config3 & HDMI_CONFIG3_AHBAUDDMA) { + /* + * Compute the CTS value from the N value. Note that CTS and N + * can be up to 20 bits in total, so we need 64-bit math. Also + * note that our TDMS clock is not fully accurate; it is + * accurate to kHz. This can introduce an unnecessary remainder + * in the calculation below, so we don't try to warn about that. + */ + tmp = (u64)ftdms * n; + do_div(tmp, 128 * sample_rate); + cts = tmp; + + dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n", + __func__, sample_rate, + ftdms / 1000000, (ftdms / 1000) % 1000, + n, cts); + } else { + cts = 0; + } spin_lock_irq(&hdmi->audio_lock); hdmi->audio_n = n; @@ -630,6 +645,42 @@ void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) } EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_rate); +void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt) +{ + u8 layout; + + mutex_lock(&hdmi->audio_mutex); + + /* + * For >2 channel PCM audio, we need to select layout 1 + * and set an appropriate channel map. + */ + if (cnt > 2) + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1; + else + layout = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0; + + hdmi_modb(hdmi, layout, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK, + HDMI_FC_AUDSCONF); + + /* Set the audio infoframes channel count */ + hdmi_modb(hdmi, (cnt - 1) << HDMI_FC_AUDICONF0_CC_OFFSET, + HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_count); + +void dw_hdmi_set_channel_allocation(struct dw_hdmi *hdmi, unsigned int ca) +{ + mutex_lock(&hdmi->audio_mutex); + + hdmi_writeb(hdmi, ca, HDMI_FC_AUDICONF2); + + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_allocation); + static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) { if (enable) @@ -2179,20 +2230,44 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge) struct dw_hdmi *hdmi = bridge->driver_private; struct drm_encoder *encoder = bridge->encoder; struct drm_connector *connector = &hdmi->connector; + struct cec_connector_info conn_info; + struct cec_notifier *notifier; connector->interlace_allowed = 1; connector->polled = DRM_CONNECTOR_POLL_HPD; drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs); - drm_connector_init(bridge->dev, connector, &dw_hdmi_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); + drm_connector_init_with_ddc(bridge->dev, connector, + &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + hdmi->ddc); drm_connector_attach_encoder(connector, encoder); + cec_fill_conn_info_from_drm(&conn_info, connector); + + notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info); + if (!notifier) + return -ENOMEM; + + mutex_lock(&hdmi->cec_notifier_mutex); + hdmi->cec_notifier = notifier; + mutex_unlock(&hdmi->cec_notifier_mutex); + return 0; } +static void dw_hdmi_bridge_detach(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_conn_unregister(hdmi->cec_notifier); + hdmi->cec_notifier = NULL; + mutex_unlock(&hdmi->cec_notifier_mutex); +} + static enum drm_mode_status dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_mode *mode) @@ -2249,6 +2324,7 @@ static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .attach = dw_hdmi_bridge_attach, + .detach = dw_hdmi_bridge_detach, .enable = dw_hdmi_bridge_enable, .disable = dw_hdmi_bridge_disable, .mode_set = dw_hdmi_bridge_mode_set, @@ -2356,9 +2432,11 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id) phy_stat & HDMI_PHY_HPD, phy_stat & HDMI_PHY_RX_SENSE); - if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) - cec_notifier_set_phys_addr(hdmi->cec_notifier, - CEC_PHYS_ADDR_INVALID); + if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) { + mutex_lock(&hdmi->cec_notifier_mutex); + cec_notifier_phys_addr_invalidate(hdmi->cec_notifier); + mutex_unlock(&hdmi->cec_notifier_mutex); + } } if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { @@ -2544,6 +2622,7 @@ __dw_hdmi_probe(struct platform_device *pdev, mutex_init(&hdmi->mutex); mutex_init(&hdmi->audio_mutex); + mutex_init(&hdmi->cec_notifier_mutex); spin_lock_init(&hdmi->audio_lock); ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); @@ -2676,12 +2755,6 @@ __dw_hdmi_probe(struct platform_device *pdev, if (ret) goto err_iahb; - hdmi->cec_notifier = cec_notifier_get(dev); - if (!hdmi->cec_notifier) { - ret = -ENOMEM; - goto err_iahb; - } - /* * To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator * N and cts values before enabling phy @@ -2746,6 +2819,7 @@ __dw_hdmi_probe(struct platform_device *pdev, struct dw_hdmi_i2s_audio_data audio; audio.hdmi = hdmi; + audio.eld = hdmi->connector.eld; audio.write = hdmi_writeb; audio.read = hdmi_readb; hdmi->enable_audio = dw_hdmi_i2s_audio_enable; @@ -2779,9 +2853,6 @@ err_iahb: hdmi->ddc = NULL; } - if (hdmi->cec_notifier) - cec_notifier_put(hdmi->cec_notifier); - clk_disable_unprepare(hdmi->iahb_clk); if (hdmi->cec_clk) clk_disable_unprepare(hdmi->cec_clk); @@ -2803,9 +2874,6 @@ static void __dw_hdmi_remove(struct dw_hdmi *hdmi) /* Disable all interrupts */ hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); - if (hdmi->cec_notifier) - cec_notifier_put(hdmi->cec_notifier); - clk_disable_unprepare(hdmi->iahb_clk); clk_disable_unprepare(hdmi->isfr_clk); if (hdmi->cec_clk) |