diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/synopsys/dw-hdmi.c')
-rw-r--r-- | drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 1247 |
1 files changed, 1033 insertions, 214 deletions
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index c6490949d9db..3e1be9894ed1 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -25,18 +25,20 @@ #include <uapi/linux/videodev2.h> #include <drm/bridge/dw_hdmi.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/display/drm_scdc_helper.h> +#include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> -#include <drm/drm_edid.h> -#include <drm/drm_encoder_slave.h> +#include <drm/drm_bridge.h> #include <drm/drm_of.h> #include <drm/drm_print.h> #include <drm/drm_probe_helper.h> -#include <drm/drm_scdc_helper.h> #include "dw-hdmi-audio.h" #include "dw-hdmi-cec.h" #include "dw-hdmi.h" +#define DDC_CI_ADDR 0x37 #define DDC_SEGMENT_ADDR 0x30 #define HDMI_EDID_LEN 512 @@ -90,6 +92,12 @@ static const u16 csc_coeff_rgb_in_eitu709[3][4] = { { 0x6756, 0x78ab, 0x2000, 0x0200 } }; +static const u16 csc_coeff_rgb_full_to_rgb_limited[3][4] = { + { 0x1b7c, 0x0000, 0x0000, 0x0020 }, + { 0x0000, 0x1b7c, 0x0000, 0x0020 }, + { 0x0000, 0x0000, 0x1b7c, 0x0020 } +}; + struct hdmi_vmode { bool mdataenablepolarity; @@ -107,6 +115,7 @@ struct hdmi_data_info { unsigned int pix_repet_factor; unsigned int hdcp_enable; struct hdmi_vmode video_mode; + bool rgb_limited_range; }; struct dw_hdmi_i2c { @@ -134,6 +143,7 @@ struct dw_hdmi_phy_data { struct dw_hdmi { struct drm_connector connector; struct drm_bridge bridge; + struct drm_bridge *next_bridge; unsigned int version; @@ -172,6 +182,7 @@ struct dw_hdmi { struct mutex mutex; /* for state below and previous_mode */ enum drm_connector_force force; /* mutex-protected force state */ + struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ bool disabled; /* DRM has disabled our bridge */ bool bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ @@ -180,7 +191,10 @@ struct dw_hdmi { spinlock_t audio_lock; struct mutex audio_mutex; + unsigned int sample_non_pcm; + unsigned int sample_width; unsigned int sample_rate; + unsigned int channels; unsigned int audio_cts; unsigned int audio_n; bool audio_enable; @@ -190,7 +204,12 @@ 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; + + hdmi_codec_plugged_cb plugged_cb; + struct device *codec_dev; + enum drm_connector_status last_connector_result; }; #define HDMI_IH_PHY_STAT0_RX_SENSE \ @@ -215,6 +234,28 @@ static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset) return val; } +static void handle_plugged_change(struct dw_hdmi *hdmi, bool plugged) +{ + if (hdmi->plugged_cb && hdmi->codec_dev) + hdmi->plugged_cb(hdmi->codec_dev, plugged); +} + +int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn, + struct device *codec_dev) +{ + bool plugged; + + mutex_lock(&hdmi->mutex); + hdmi->plugged_cb = fn; + hdmi->codec_dev = codec_dev; + plugged = hdmi->last_connector_result == connector_status_connected; + handle_plugged_change(hdmi, plugged); + mutex_unlock(&hdmi->mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_plugged_cb); + static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg) { regmap_update_bits(hdmi->regm, reg << hdmi->reg_shift, mask, data); @@ -398,6 +439,15 @@ static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, u8 addr = msgs[0].addr; int i, ret = 0; + if (addr == DDC_CI_ADDR) + /* + * The internal I2C controller does not support the multi-byte + * read and write operations needed for DDC/CI. + * TOFIX: Blacklist the DDC/CI address until we filter out + * unsupported I2C operations. + */ + return -EOPNOTSUPP; + dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr); for (i = 0; i < num; i++) { @@ -508,8 +558,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); @@ -536,6 +592,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 4096; else if (pixel_clk == 74176000 || pixel_clk == 148352000) n = 11648; + else if (pixel_clk == 297000000) + n = 3072; else n = 4096; n *= mult; @@ -548,6 +606,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 17836; else if (pixel_clk == 148352000) n = 8918; + else if (pixel_clk == 297000000) + n = 4704; else n = 6272; n *= mult; @@ -562,6 +622,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) n = 11648; else if (pixel_clk == 148352000) n = 5824; + else if (pixel_clk == 297000000) + n = 5120; else n = 6144; n *= mult; @@ -574,29 +636,58 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk) return n; } +/* + * When transmitting IEC60958 linear PCM audio, these registers allow to + * configure the channel status information of all the channel status + * bits in the IEC60958 frame. For the moment this configuration is only + * used when the I2S audio interface, General Purpose Audio (GPA), + * or AHB audio DMA (AHBAUDDMA) interface is active + * (for S/PDIF interface this information comes from the stream). + */ +void dw_hdmi_set_channel_status(struct dw_hdmi *hdmi, + u8 *channel_status) +{ + /* + * Set channel status register for frequency and word length. + * Use default values for other registers. + */ + hdmi_writeb(hdmi, channel_status[3], HDMI_FC_AUDSCHNLS7); + hdmi_writeb(hdmi, channel_status[4], HDMI_FC_AUDSCHNLS8); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_channel_status); + static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi, unsigned long pixel_clk, unsigned int sample_rate) { 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); + /* Compute CTS when using internal AHB audio or General Parallel audio*/ + if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) { + /* + * 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; @@ -620,6 +711,22 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi) mutex_unlock(&hdmi->audio_mutex); } +void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_width = width; + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width); + +void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_non_pcm = non_pcm; + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm); + void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate) { mutex_lock(&hdmi->audio_mutex); @@ -630,6 +737,43 @@ 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); + hdmi->channels = cnt; + + /* + * 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) @@ -639,6 +783,97 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable) hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); } +static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi) +{ + if (!hdmi->curr_conn) + return NULL; + + return hdmi->curr_conn->eld; +} + +static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + int sample_freq = 0x2, org_sample_freq = 0xD; + int ch_mask = BIT(hdmi->channels) - 1; + + switch (hdmi->sample_rate) { + case 32000: + sample_freq = 0x03; + org_sample_freq = 0x0C; + break; + case 44100: + sample_freq = 0x00; + org_sample_freq = 0x0F; + break; + case 48000: + sample_freq = 0x02; + org_sample_freq = 0x0D; + break; + case 88200: + sample_freq = 0x08; + org_sample_freq = 0x07; + break; + case 96000: + sample_freq = 0x0A; + org_sample_freq = 0x05; + break; + case 176400: + sample_freq = 0x0C; + org_sample_freq = 0x03; + break; + case 192000: + sample_freq = 0x0E; + org_sample_freq = 0x01; + break; + default: + break; + } + + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); + + hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0); + hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2); + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3); + hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4); + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5); + hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6); + hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7); + hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8); + + hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1); + hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2); + hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0); + + hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3); + + /* hbr */ + if (hdmi->sample_rate == 192000 && hdmi->channels == 8 && + hdmi->sample_width == 32 && hdmi->sample_non_pcm) + hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2); + + if (pdata->enable_audio) + pdata->enable_audio(hdmi, + hdmi->channels, + hdmi->sample_width, + hdmi->sample_rate, + hdmi->sample_non_pcm); +} + +static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi) +{ + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; + + hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0); + + hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3); + if (pdata->disable_audio) + pdata->disable_audio(hdmi); + + hdmi_enable_audio_clk(hdmi, false); +} + static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi) { hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); @@ -847,7 +1082,14 @@ static void hdmi_video_sample(struct dw_hdmi *hdmi) static int is_color_space_conversion(struct dw_hdmi *hdmi) { - return hdmi->hdmi_data.enc_in_bus_format != hdmi->hdmi_data.enc_out_bus_format; + struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; + bool is_input_rgb, is_output_rgb; + + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi_data->enc_out_bus_format); + + return (is_input_rgb != is_output_rgb) || + (is_input_rgb && is_output_rgb && hdmi_data->rgb_limited_range); } static int is_color_space_decimation(struct dw_hdmi *hdmi) @@ -874,28 +1116,37 @@ static int is_color_space_interpolation(struct dw_hdmi *hdmi) return 0; } +static bool is_csc_needed(struct dw_hdmi *hdmi) +{ + return is_color_space_conversion(hdmi) || + is_color_space_decimation(hdmi) || + is_color_space_interpolation(hdmi); +} + static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi) { const u16 (*csc_coeff)[3][4] = &csc_coeff_default; + bool is_input_rgb, is_output_rgb; unsigned i; u32 csc_scale = 1; - if (is_color_space_conversion(hdmi)) { - if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { - if (hdmi->hdmi_data.enc_out_encoding == - V4L2_YCBCR_ENC_601) - csc_coeff = &csc_coeff_rgb_out_eitu601; - else - csc_coeff = &csc_coeff_rgb_out_eitu709; - } else if (hdmi_bus_fmt_is_rgb( - hdmi->hdmi_data.enc_in_bus_format)) { - if (hdmi->hdmi_data.enc_out_encoding == - V4L2_YCBCR_ENC_601) - csc_coeff = &csc_coeff_rgb_in_eitu601; - else - csc_coeff = &csc_coeff_rgb_in_eitu709; - csc_scale = 0; - } + is_input_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_in_bus_format); + is_output_rgb = hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format); + + if (!is_input_rgb && is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_out_eitu601; + else + csc_coeff = &csc_coeff_rgb_out_eitu709; + } else if (is_input_rgb && !is_output_rgb) { + if (hdmi->hdmi_data.enc_out_encoding == V4L2_YCBCR_ENC_601) + csc_coeff = &csc_coeff_rgb_in_eitu601; + else + csc_coeff = &csc_coeff_rgb_in_eitu709; + csc_scale = 0; + } else if (is_input_rgb && is_output_rgb && + hdmi->hdmi_data.rgb_limited_range) { + csc_coeff = &csc_coeff_rgb_full_to_rgb_limited; } /* The CSC registers are sequential, alternating MSB then LSB */ @@ -966,6 +1217,8 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP; struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data; u8 val, vp_conf; + u8 clear_gcp_auto = 0; + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format) || hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format) || @@ -975,6 +1228,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) case 8: color_depth = 4; output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS; + clear_gcp_auto = 1; break; case 10: color_depth = 5; @@ -994,6 +1248,7 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) case 0: case 8: remap_size = HDMI_VP_REMAP_YCC422_16bit; + clear_gcp_auto = 1; break; case 10: remap_size = HDMI_VP_REMAP_YCC422_20bit; @@ -1018,6 +1273,19 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK); hdmi_writeb(hdmi, val, HDMI_VP_PR_CD); + /* HDMI1.4b specification section 6.5.3: + * Source shall only send GCPs with non-zero CD to sinks + * that indicate support for Deep Color. + * GCP only transmit CD and do not handle AVMUTE, PP norDefault_Phase (yet). + * Disable Auto GCP when 24-bit color for sinks that not support Deep Color. + */ + val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3); + if (clear_gcp_auto == 1) + val &= ~HDMI_FC_DATAUTO3_GCP_AUTO; + else + val |= HDMI_FC_DATAUTO3_GCP_AUTO; + hdmi_writeb(hdmi, val, HDMI_FC_DATAUTO3); + hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE, HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF); @@ -1109,10 +1377,9 @@ void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data, EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_write); /* Filter out invalid setups to avoid configuring SCDC and scrambling */ -static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi) +static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi, + const struct drm_display_info *display) { - struct drm_display_info *display = &hdmi->connector.display_info; - /* Completely disable SCDC support for older controllers */ if (hdmi->version < 0x200a) return false; @@ -1150,12 +1417,13 @@ static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi) * helper should called right before enabling the TMDS Clock and Data in * the PHY configuration callback. */ -void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi) +void dw_hdmi_set_high_tmds_clock_ratio(struct dw_hdmi *hdmi, + const struct drm_display_info *display) { unsigned long mtmdsclock = hdmi->hdmi_data.video_mode.mtmdsclock; /* Control for TMDS Bit Period/TMDS Clock-Period Ratio */ - if (dw_hdmi_support_scdc(hdmi)) { + if (dw_hdmi_support_scdc(hdmi, display)) { if (mtmdsclock > HDMI14_MAX_TMDSCLK) drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1); else @@ -1215,13 +1483,21 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable) HDMI_PHY_CONF0_SELDIPIF_MASK); } -void dw_hdmi_phy_reset(struct dw_hdmi *hdmi) +void dw_hdmi_phy_gen1_reset(struct dw_hdmi *hdmi) +{ + /* PHY reset. The reset signal is active low on Gen1 PHYs. */ + hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); + hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); +} +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen1_reset); + +void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi) { /* PHY reset. The reset signal is active high on Gen2 PHYs. */ hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ); hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ); } -EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset); +EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_reset); void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address) { @@ -1358,7 +1634,8 @@ static int hdmi_phy_configure_dwc_hdmi_3d_tx(struct dw_hdmi *hdmi, return 0; } -static int hdmi_phy_configure(struct dw_hdmi *hdmi) +static int hdmi_phy_configure(struct dw_hdmi *hdmi, + const struct drm_display_info *display) { const struct dw_hdmi_phy_data *phy = hdmi->phy.data; const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; @@ -1368,13 +1645,13 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi) dw_hdmi_phy_power_off(hdmi); - dw_hdmi_set_high_tmds_clock_ratio(hdmi); + dw_hdmi_set_high_tmds_clock_ratio(hdmi, display); /* Leave low power consumption mode by asserting SVSRET. */ if (phy->has_svsret) dw_hdmi_phy_enable_svsret(hdmi, 1); - dw_hdmi_phy_reset(hdmi); + dw_hdmi_phy_gen2_reset(hdmi); hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST); @@ -1382,7 +1659,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi) /* Write to the PHY as configured by the platform */ if (pdata->configure_phy) - ret = pdata->configure_phy(hdmi, pdata, mpixelclock); + ret = pdata->configure_phy(hdmi, pdata->priv_data, mpixelclock); else ret = phy->configure(hdmi, pdata, mpixelclock); if (ret) { @@ -1399,7 +1676,8 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi) } static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, - struct drm_display_mode *mode) + const struct drm_display_info *display, + const struct drm_display_mode *mode) { int i, ret; @@ -1408,7 +1686,7 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, dw_hdmi_phy_sel_data_en_pol(hdmi, 1); dw_hdmi_phy_sel_interface_control(hdmi, 0); - ret = hdmi_phy_configure(hdmi); + ret = hdmi_phy_configure(hdmi, display); if (ret) return ret; } @@ -1496,14 +1774,26 @@ static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1); } -static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) +static void hdmi_config_AVI(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) { struct hdmi_avi_infoframe frame; u8 val; /* Initialise info frame from DRM mode */ - drm_hdmi_avi_infoframe_from_display_mode(&frame, - &hdmi->connector, mode); + drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); + + if (hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, + hdmi->hdmi_data.rgb_limited_range ? + HDMI_QUANTIZATION_RANGE_LIMITED : + HDMI_QUANTIZATION_RANGE_FULL); + } else { + frame.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + frame.ycc_quantization_range = + HDMI_YCC_QUANTIZATION_RANGE_LIMITED; + } if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV444; @@ -1515,32 +1805,36 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) frame.colorspace = HDMI_COLORSPACE_RGB; /* Set up colorimetry */ - switch (hdmi->hdmi_data.enc_out_encoding) { - case V4L2_YCBCR_ENC_601: - if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601) - frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; - else + if (!hdmi_bus_fmt_is_rgb(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi->hdmi_data.enc_out_encoding) { + case V4L2_YCBCR_ENC_601: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV601) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + case V4L2_YCBCR_ENC_709: + if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709) + frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; + else + frame.colorimetry = HDMI_COLORIMETRY_ITU_709; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + break; + default: /* Carries no data */ frame.colorimetry = HDMI_COLORIMETRY_ITU_601; + frame.extended_colorimetry = + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + break; + } + } else { + frame.colorimetry = HDMI_COLORIMETRY_NONE; frame.extended_colorimetry = - HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; - break; - case V4L2_YCBCR_ENC_709: - if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_XV709) - frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; - else - frame.colorimetry = HDMI_COLORIMETRY_ITU_709; - frame.extended_colorimetry = - HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; - break; - default: /* Carries no data */ - frame.colorimetry = HDMI_COLORIMETRY_ITU_601; - frame.extended_colorimetry = - HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; - break; + HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; } - frame.scan_mode = HDMI_SCAN_MODE_NONE; - /* * The Designware IP uses a different byte format from standard * AVI info frames, though generally the bits are in the correct @@ -1608,14 +1902,14 @@ static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode) } static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi, - struct drm_display_mode *mode) + const struct drm_connector *connector, + const struct drm_display_mode *mode) { struct hdmi_vendor_infoframe frame; u8 buffer[10]; ssize_t err; - err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, - &hdmi->connector, + err = drm_hdmi_vendor_infoframe_from_display_mode(&frame, connector, mode); if (err < 0) /* @@ -1661,25 +1955,81 @@ static void hdmi_config_vendor_specific_infoframe(struct dw_hdmi *hdmi, HDMI_FC_DATAUTO0_VSD_MASK); } +static void hdmi_config_drm_infoframe(struct dw_hdmi *hdmi, + const struct drm_connector *connector) +{ + const struct drm_connector_state *conn_state = connector->state; + struct hdmi_drm_infoframe frame; + u8 buffer[30]; + ssize_t err; + int i; + + if (!hdmi->plat_data->use_drm_infoframe) + return; + + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_DISABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); + + err = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state); + if (err < 0) + return; + + err = hdmi_drm_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(hdmi->dev, "Failed to pack drm infoframe: %zd\n", err); + return; + } + + hdmi_writeb(hdmi, frame.version, HDMI_FC_DRM_HB0); + hdmi_writeb(hdmi, frame.length, HDMI_FC_DRM_HB1); + + for (i = 0; i < frame.length; i++) + hdmi_writeb(hdmi, buffer[4 + i], HDMI_FC_DRM_PB0 + i); + + hdmi_writeb(hdmi, 1, HDMI_FC_DRM_UP); + hdmi_modb(hdmi, HDMI_FC_PACKET_TX_EN_DRM_ENABLE, + HDMI_FC_PACKET_TX_EN_DRM_MASK, HDMI_FC_PACKET_TX_EN); +} + static void hdmi_av_composer(struct dw_hdmi *hdmi, + const struct drm_display_info *display, const struct drm_display_mode *mode) { u8 inv_val, bytes; - struct drm_hdmi_info *hdmi_info = &hdmi->connector.display_info.hdmi; + const struct drm_hdmi_info *hdmi_info = &display->hdmi; struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len; unsigned int vdisplay, hdisplay; - vmode->mtmdsclock = vmode->mpixelclock = mode->clock * 1000; + vmode->mpixelclock = mode->clock * 1000; dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock); + vmode->mtmdsclock = vmode->mpixelclock; + + if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { + switch (hdmi_bus_fmt_color_depth( + hdmi->hdmi_data.enc_out_bus_format)) { + case 16: + vmode->mtmdsclock = vmode->mpixelclock * 2; + break; + case 12: + vmode->mtmdsclock = vmode->mpixelclock * 3 / 2; + break; + case 10: + vmode->mtmdsclock = vmode->mpixelclock * 5 / 4; + break; + } + } + if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) vmode->mtmdsclock /= 2; + dev_dbg(hdmi->dev, "final tmdsclock = %d\n", vmode->mtmdsclock); + /* Set up HDMI_FC_INVIDCONF */ inv_val = (hdmi->hdmi_data.hdcp_enable || - (dw_hdmi_support_scdc(hdmi) && + (dw_hdmi_support_scdc(hdmi, display) && (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || hdmi_info->scdc.scrambling.low_rates)) ? HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE : @@ -1747,7 +2097,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, } /* Scrambling Control */ - if (dw_hdmi_support_scdc(hdmi)) { + if (dw_hdmi_support_scdc(hdmi, display)) { if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK || hdmi_info->scdc.scrambling.low_rates) { /* @@ -1841,18 +2191,19 @@ static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); /* Enable csc path */ - if (is_color_space_conversion(hdmi)) { + if (is_csc_needed(hdmi)) { hdmi->mc_clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE; hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); - } - /* Enable color space conversion if needed */ - if (is_color_space_conversion(hdmi)) hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH, HDMI_MC_FLOWCTRL); - else + } else { + hdmi->mc_clkdis |= HDMI_MC_CLKDIS_CSCCLK_DISABLE; + hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS); + hdmi_writeb(hdmi, HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS, HDMI_MC_FLOWCTRL); + } } /* Workaround to clear the overflow condition */ @@ -1869,30 +2220,21 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi) * then write one of the FC registers several times. * * The number of iterations matters and depends on the HDMI TX revision - * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL - * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified - * as needing the workaround, with 4 iterations for v1.30a and 1 - * iteration for others. - * The Amlogic Meson GX SoCs (v2.01a) have been identified as needing - * the workaround with a single iteration. - * The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have - * been identified as needing the workaround with a single iteration. + * (and possibly on the platform). + * 4 iterations for i.MX6Q(v1.30a) and 1 iteration for others. + * i.MX6DL (v1.31a), Allwinner SoCs (v1.32a), Rockchip RK3288 SoC (v2.00a), + * Amlogic Meson GX SoCs (v2.01a), RK3328/RK3399 SoCs (v2.11a) + * and i.MX8MPlus (v2.13a) have been identified as needing the workaround + * with a single iteration. */ switch (hdmi->version) { case 0x130a: count = 4; break; - case 0x131a: - case 0x132a: - case 0x200a: - case 0x201a: - case 0x211a: - case 0x212a: + default: count = 1; break; - default: - return; } /* TMDS software reset */ @@ -1909,7 +2251,9 @@ static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi) HDMI_IH_MUTE_FC_STAT2); } -static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) +static int dw_hdmi_setup(struct dw_hdmi *hdmi, + const struct drm_connector *connector, + const struct drm_display_mode *mode) { int ret; @@ -1934,11 +2278,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; - /* TOFIX: Get input format from plat data or fallback to RGB888 */ - if (hdmi->plat_data->input_bus_format) - hdmi->hdmi_data.enc_in_bus_format = - hdmi->plat_data->input_bus_format; - else + if (hdmi->hdmi_data.enc_in_bus_format == MEDIA_BUS_FMT_FIXED) hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; /* TOFIX: Get input encoding from plat data or fallback to none */ @@ -1948,18 +2288,24 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) else hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; - /* TOFIX: Default to RGB888 output format */ - hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED) + hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + hdmi->hdmi_data.rgb_limited_range = hdmi->sink_is_hdmi && + drm_default_rgb_quant_range(mode) == + HDMI_QUANTIZATION_RANGE_LIMITED; hdmi->hdmi_data.pix_repet_factor = 0; hdmi->hdmi_data.hdcp_enable = 0; hdmi->hdmi_data.video_mode.mdataenablepolarity = true; /* HDMI Initialization Step B.1 */ - hdmi_av_composer(hdmi, mode); + hdmi_av_composer(hdmi, &connector->display_info, mode); /* HDMI Initializateion Step B.2 */ - ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode); + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, + &connector->display_info, + &hdmi->previous_mode); if (ret) return ret; hdmi->phy.enabled = true; @@ -1972,7 +2318,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) /* HDMI Initialization Step E - Configure audio */ hdmi_clk_regenerator_update_pixel_clock(hdmi); - hdmi_enable_audio_clk(hdmi, true); + hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); } /* not for DVI mode */ @@ -1980,8 +2326,9 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); /* HDMI Initialization Step F - Configure AVI InfoFrame */ - hdmi_config_AVI(hdmi, mode); - hdmi_config_vendor_specific_infoframe(hdmi, mode); + hdmi_config_AVI(hdmi, connector, mode); + hdmi_config_vendor_specific_infoframe(hdmi, connector, mode); + hdmi_config_drm_infoframe(hdmi, connector); } else { dev_dbg(hdmi->dev, "%s DVI mode\n", __func__); } @@ -2050,7 +2397,12 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { hdmi->bridge_is_on = true; - dw_hdmi_setup(hdmi, &hdmi->previous_mode); + + /* + * The curr_conn field is guaranteed to be valid here, as this function + * is only be called when !hdmi->disabled. + */ + dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); } static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) @@ -2105,49 +2457,102 @@ static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi) hdmi->rxsense); } -static enum drm_connector_status -dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +static enum drm_connector_status dw_hdmi_detect(struct dw_hdmi *hdmi) { - struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, - connector); + enum drm_connector_status result; + + result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); mutex_lock(&hdmi->mutex); - hdmi->force = DRM_FORCE_UNSPECIFIED; - dw_hdmi_update_power(hdmi); - dw_hdmi_update_phy_mask(hdmi); + if (result != hdmi->last_connector_result) { + dev_dbg(hdmi->dev, "read_hpd result: %d", result); + handle_plugged_change(hdmi, + result == connector_status_connected); + hdmi->last_connector_result = result; + } mutex_unlock(&hdmi->mutex); - return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); + return result; } -static int dw_hdmi_connector_get_modes(struct drm_connector *connector) +static struct edid *dw_hdmi_get_edid(struct dw_hdmi *hdmi, + struct drm_connector *connector) { - struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, - connector); struct edid *edid; - int ret = 0; if (!hdmi->ddc) - return 0; + return NULL; edid = drm_get_edid(connector, hdmi->ddc); - if (edid) { - dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", - edid->width_cm, edid->height_cm); - - hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); - hdmi->sink_has_audio = drm_detect_monitor_audio(edid); - drm_connector_update_edid_property(connector, edid); - cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); - ret = drm_add_edid_modes(connector, edid); - kfree(edid); - } else { + if (!edid) { dev_dbg(hdmi->dev, "failed to get edid\n"); + return NULL; } + dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n", + edid->width_cm, edid->height_cm); + + hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid); + hdmi->sink_has_audio = drm_detect_monitor_audio(edid); + + return edid; +} + +/* ----------------------------------------------------------------------------- + * DRM Connector Operations + */ + +static enum drm_connector_status +dw_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + return dw_hdmi_detect(hdmi); +} + +static int dw_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, + connector); + struct edid *edid; + int ret; + + edid = dw_hdmi_get_edid(hdmi, connector); + if (!edid) + return 0; + + drm_connector_update_edid_property(connector, edid); + cec_notifier_set_phys_addr_from_edid(hdmi->cec_notifier, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + return ret; } +static int dw_hdmi_connector_atomic_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_crtc *crtc = new_state->crtc; + struct drm_crtc_state *crtc_state; + + if (!crtc) + return 0; + + if (!drm_connector_atomic_hdr_metadata_equal(old_state, new_state)) { + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + + return 0; +} + static void dw_hdmi_connector_force(struct drm_connector *connector) { struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, @@ -2172,41 +2577,371 @@ static const struct drm_connector_funcs dw_hdmi_connector_funcs = { static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { .get_modes = dw_hdmi_connector_get_modes, + .atomic_check = dw_hdmi_connector_atomic_check, }; -static int dw_hdmi_bridge_attach(struct drm_bridge *bridge) +static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) { - 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; + + if (hdmi->version >= 0x200a) + connector->ycbcr_420_allowed = + hdmi->plat_data->ycbcr_420_allowed; + else + connector->ycbcr_420_allowed = false; 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(hdmi->bridge.dev, connector, + &dw_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA, + hdmi->ddc); + + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + drm_atomic_helper_connector_reset(connector); + + drm_connector_attach_max_bpc_property(connector, 8, 16); + + if (hdmi->version >= 0x200a && hdmi->plat_data->use_drm_infoframe) + drm_connector_attach_hdr_output_metadata_property(connector); + + drm_connector_attach_encoder(connector, hdmi->bridge.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; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +/* + * Possible output formats : + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48, + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36, + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30, + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24, + * - MEDIA_BUS_FMT_YUV16_1X48, + * - MEDIA_BUS_FMT_RGB161616_1X48, + * - MEDIA_BUS_FMT_UYVY12_1X24, + * - MEDIA_BUS_FMT_YUV12_1X36, + * - MEDIA_BUS_FMT_RGB121212_1X36, + * - MEDIA_BUS_FMT_UYVY10_1X20, + * - MEDIA_BUS_FMT_YUV10_1X30, + * - MEDIA_BUS_FMT_RGB101010_1X30, + * - MEDIA_BUS_FMT_UYVY8_1X16, + * - MEDIA_BUS_FMT_YUV8_1X24, + * - MEDIA_BUS_FMT_RGB888_1X24, + */ + +/* Can return a maximum of 11 possible output formats for a mode/connector */ +#define MAX_OUTPUT_SEL_FORMATS 11 + +static u32 *dw_hdmi_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + struct drm_connector *conn = conn_state->connector; + struct drm_display_info *info = &conn->display_info; + struct drm_display_mode *mode = &crtc_state->mode; + u8 max_bpc = conn_state->max_requested_bpc; + bool is_hdmi2_sink = info->hdmi.scdc.supported || + (info->color_formats & DRM_COLOR_FORMAT_YCBCR420); + u32 *output_fmts; + unsigned int i = 0; + + *num_output_fmts = 0; + + output_fmts = kcalloc(MAX_OUTPUT_SEL_FORMATS, sizeof(*output_fmts), + GFP_KERNEL); + if (!output_fmts) + return NULL; + + /* If dw-hdmi is the first or only bridge, avoid negociating with ourselves */ + if (list_is_singular(&bridge->encoder->bridge_chain) || + list_is_first(&bridge->chain_node, &bridge->encoder->bridge_chain)) { + *num_output_fmts = 1; + output_fmts[0] = MEDIA_BUS_FMT_FIXED; + + return output_fmts; + } + + /* + * If the current mode enforces 4:2:0, force the output but format + * to 4:2:0 and do not add the YUV422/444/RGB formats + */ + if (conn->ycbcr_420_allowed && + (drm_mode_is_420_only(info, mode) || + (is_hdmi2_sink && drm_mode_is_420_also(info, mode)))) { + + /* Order bus formats from 16bit to 8bit if supported */ + if (max_bpc >= 16 && info->bpc == 16 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_48)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY16_0_5X48; + + if (max_bpc >= 12 && info->bpc >= 12 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY12_0_5X36; + + if (max_bpc >= 10 && info->bpc >= 10 && + (info->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)) + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY10_0_5X30; + + /* Default 8bit fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_UYYVYY8_0_5X24; + + *num_output_fmts = i; + + return output_fmts; + } + + /* + * Order bus formats from 16bit to 8bit and from YUV422 to RGB + * if supported. In any case the default RGB888 format is added + */ + + if (max_bpc >= 16 && info->bpc == 16) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + } + + if (max_bpc >= 12 && info->bpc >= 12) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + } + + if (max_bpc >= 10 && info->bpc >= 10) { + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + + output_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + } + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + output_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR444) + output_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + + /* Default 8bit RGB fallback */ + output_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + + *num_output_fmts = i; + + return output_fmts; +} + +/* + * Possible input formats : + * - MEDIA_BUS_FMT_RGB888_1X24 + * - MEDIA_BUS_FMT_YUV8_1X24 + * - MEDIA_BUS_FMT_UYVY8_1X16 + * - MEDIA_BUS_FMT_UYYVYY8_0_5X24 + * - MEDIA_BUS_FMT_RGB101010_1X30 + * - MEDIA_BUS_FMT_YUV10_1X30 + * - MEDIA_BUS_FMT_UYVY10_1X20 + * - MEDIA_BUS_FMT_UYYVYY10_0_5X30 + * - MEDIA_BUS_FMT_RGB121212_1X36 + * - MEDIA_BUS_FMT_YUV12_1X36 + * - MEDIA_BUS_FMT_UYVY12_1X24 + * - MEDIA_BUS_FMT_UYYVYY12_0_5X36 + * - MEDIA_BUS_FMT_RGB161616_1X48 + * - MEDIA_BUS_FMT_YUV16_1X48 + * - MEDIA_BUS_FMT_UYYVYY16_0_5X48 + */ + +/* Can return a maximum of 3 possible input formats for an output format */ +#define MAX_INPUT_SEL_FORMATS 3 + +static u32 *dw_hdmi_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + unsigned int i = 0; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + switch (output_fmt) { + /* If MEDIA_BUS_FMT_FIXED is tested, return default bus format */ + case MEDIA_BUS_FMT_FIXED: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + /* 8bit */ + case MEDIA_BUS_FMT_RGB888_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY8_1X16; + input_fmts[i++] = MEDIA_BUS_FMT_YUV8_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB888_1X24; + break; + + /* 10bit */ + case MEDIA_BUS_FMT_RGB101010_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + break; + case MEDIA_BUS_FMT_YUV10_1X30: + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + case MEDIA_BUS_FMT_UYVY10_1X20: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY10_1X20; + input_fmts[i++] = MEDIA_BUS_FMT_YUV10_1X30; + input_fmts[i++] = MEDIA_BUS_FMT_RGB101010_1X30; + break; + + /* 12bit */ + case MEDIA_BUS_FMT_RGB121212_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + break; + case MEDIA_BUS_FMT_YUV12_1X36: + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + case MEDIA_BUS_FMT_UYVY12_1X24: + input_fmts[i++] = MEDIA_BUS_FMT_UYVY12_1X24; + input_fmts[i++] = MEDIA_BUS_FMT_YUV12_1X36; + input_fmts[i++] = MEDIA_BUS_FMT_RGB121212_1X36; + break; + + /* 16bit */ + case MEDIA_BUS_FMT_RGB161616_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + break; + case MEDIA_BUS_FMT_YUV16_1X48: + input_fmts[i++] = MEDIA_BUS_FMT_YUV16_1X48; + input_fmts[i++] = MEDIA_BUS_FMT_RGB161616_1X48; + break; + + /*YUV 4:2:0 */ + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY12_0_5X36: + case MEDIA_BUS_FMT_UYYVYY16_0_5X48: + input_fmts[i++] = output_fmt; + break; + } + + *num_input_fmts = i; + + if (*num_input_fmts == 0) { + kfree(input_fmts); + input_fmts = NULL; + } + + return input_fmts; +} + +static int dw_hdmi_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + hdmi->hdmi_data.enc_out_bus_format = + bridge_state->output_bus_cfg.format; + + hdmi->hdmi_data.enc_in_bus_format = + bridge_state->input_bus_cfg.format; - drm_connector_attach_encoder(connector, encoder); + dev_dbg(hdmi->dev, "input format 0x%04x, output format 0x%04x\n", + bridge_state->input_bus_cfg.format, + bridge_state->output_bus_cfg.format); return 0; } +static int dw_hdmi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) + return drm_bridge_attach(bridge->encoder, hdmi->next_bridge, + bridge, flags); + + return dw_hdmi_connector_create(hdmi); +} + +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_info *info, const struct drm_display_mode *mode) { struct dw_hdmi *hdmi = bridge->driver_private; - struct drm_connector *connector = &hdmi->connector; + const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; enum drm_mode_status mode_status = MODE_OK; /* We don't support double-clocked modes */ if (mode->flags & DRM_MODE_FLAG_DBLCLK) return MODE_BAD; - if (hdmi->plat_data->mode_valid) - mode_status = hdmi->plat_data->mode_valid(connector, mode); + if (pdata->mode_valid) + mode_status = pdata->mode_valid(hdmi, pdata->priv_data, info, + mode); return mode_status; } @@ -2220,41 +2955,78 @@ static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); /* Store the display mode for plugin/DKMS poweron events */ - memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); + drm_mode_copy(&hdmi->previous_mode, mode); mutex_unlock(&hdmi->mutex); } -static void dw_hdmi_bridge_disable(struct drm_bridge *bridge) +static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) { struct dw_hdmi *hdmi = bridge->driver_private; mutex_lock(&hdmi->mutex); hdmi->disabled = true; + hdmi->curr_conn = NULL; dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } -static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) +static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) { struct dw_hdmi *hdmi = bridge->driver_private; + struct drm_atomic_state *state = old_state->base.state; + struct drm_connector *connector; + + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); mutex_lock(&hdmi->mutex); hdmi->disabled = false; + hdmi->curr_conn = connector; dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } +static enum drm_connector_status dw_hdmi_bridge_detect(struct drm_bridge *bridge) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_detect(hdmi); +} + +static struct edid *dw_hdmi_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_hdmi *hdmi = bridge->driver_private; + + return dw_hdmi_get_edid(hdmi, connector); +} + static const struct drm_bridge_funcs dw_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, .attach = dw_hdmi_bridge_attach, - .enable = dw_hdmi_bridge_enable, - .disable = dw_hdmi_bridge_disable, + .detach = dw_hdmi_bridge_detach, + .atomic_check = dw_hdmi_bridge_atomic_check, + .atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts, + .atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts, + .atomic_enable = dw_hdmi_bridge_atomic_enable, + .atomic_disable = dw_hdmi_bridge_atomic_disable, .mode_set = dw_hdmi_bridge_mode_set, .mode_valid = dw_hdmi_bridge_mode_valid, + .detect = dw_hdmi_bridge_detect, + .get_edid = dw_hdmi_bridge_get_edid, }; +/* ----------------------------------------------------------------------------- + * IRQ Handling + */ + static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi) { struct dw_hdmi_i2c *i2c = hdmi->i2c; @@ -2356,16 +3128,26 @@ 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) { + enum drm_connector_status status = phy_int_pol & HDMI_PHY_HPD + ? connector_status_connected + : connector_status_disconnected; + dev_dbg(hdmi->dev, "EVENT=%s\n", - phy_int_pol & HDMI_PHY_HPD ? "plugin" : "plugout"); - if (hdmi->bridge.dev) + status == connector_status_connected ? + "plugin" : "plugout"); + + if (hdmi->bridge.dev) { drm_helper_hpd_irq_event(hdmi->bridge.dev); + drm_bridge_hpd_notify(&hdmi->bridge, status); + } } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); @@ -2511,9 +3293,57 @@ static void dw_hdmi_init_hw(struct dw_hdmi *hdmi) hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); } -static struct dw_hdmi * -__dw_hdmi_probe(struct platform_device *pdev, - const struct dw_hdmi_plat_data *plat_data) +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ + +static int dw_hdmi_parse_dt(struct dw_hdmi *hdmi) +{ + struct device_node *endpoint; + struct device_node *remote; + + if (!hdmi->plat_data->output_port) + return 0; + + endpoint = of_graph_get_endpoint_by_regs(hdmi->dev->of_node, + hdmi->plat_data->output_port, + -1); + if (!endpoint) { + /* + * On platforms whose bindings don't make the output port + * mandatory (such as Rockchip) the plat_data->output_port + * field isn't set, so it's safe to make this a fatal error. + */ + dev_err(hdmi->dev, "Missing endpoint in port@%u\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + remote = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!remote) { + dev_err(hdmi->dev, "Endpoint in port@%u unconnected\n", + hdmi->plat_data->output_port); + return -ENODEV; + } + + if (!of_device_is_available(remote)) { + dev_err(hdmi->dev, "port@%u remote device is disabled\n", + hdmi->plat_data->output_port); + of_node_put(remote); + return -ENODEV; + } + + hdmi->next_bridge = of_drm_find_bridge(remote); + of_node_put(remote); + if (!hdmi->next_bridge) + return -EPROBE_DEFER; + + return 0; +} + +struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, + const struct dw_hdmi_plat_data *plat_data) { struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -2537,15 +3367,22 @@ __dw_hdmi_probe(struct platform_device *pdev, hdmi->plat_data = plat_data; hdmi->dev = dev; hdmi->sample_rate = 48000; + hdmi->channels = 2; hdmi->disabled = true; hdmi->rxsense = true; hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE); hdmi->mc_clkdis = 0x7f; + hdmi->last_connector_result = connector_status_disconnected; mutex_init(&hdmi->mutex); mutex_init(&hdmi->audio_mutex); + mutex_init(&hdmi->cec_notifier_mutex); spin_lock_init(&hdmi->audio_lock); + ret = dw_hdmi_parse_dt(hdmi); + if (ret < 0) + return ERR_PTR(ret); + ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); if (ddc_node) { hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); @@ -2676,12 +3513,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 @@ -2715,6 +3546,9 @@ __dw_hdmi_probe(struct platform_device *pdev, hdmi->bridge.driver_private = hdmi; hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; + hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID + | DRM_BRIDGE_OP_HPD; + hdmi->bridge.interlace_allowed = true; #ifdef CONFIG_OF hdmi->bridge.of_node = pdev->dev.of_node; #endif @@ -2733,7 +3567,7 @@ __dw_hdmi_probe(struct platform_device *pdev, audio.base = hdmi->regs; audio.irq = irq; audio.hdmi = hdmi; - audio.eld = hdmi->connector.eld; + audio.get_eld = hdmi_audio_get_eld; hdmi->enable_audio = dw_hdmi_ahb_audio_enable; hdmi->disable_audio = dw_hdmi_ahb_audio_disable; @@ -2746,6 +3580,7 @@ __dw_hdmi_probe(struct platform_device *pdev, struct dw_hdmi_i2s_audio_data audio; audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; audio.write = hdmi_writeb; audio.read = hdmi_readb; hdmi->enable_audio = dw_hdmi_i2s_audio_enable; @@ -2756,9 +3591,27 @@ __dw_hdmi_probe(struct platform_device *pdev, pdevinfo.size_data = sizeof(audio); pdevinfo.dma_mask = DMA_BIT_MASK(32); hdmi->audio = platform_device_register_full(&pdevinfo); + } else if (iores && config3 & HDMI_CONFIG3_GPAUD) { + struct dw_hdmi_audio_data audio; + + audio.phys = iores->start; + audio.base = hdmi->regs; + audio.irq = irq; + audio.hdmi = hdmi; + audio.get_eld = hdmi_audio_get_eld; + + hdmi->enable_audio = dw_hdmi_gp_audio_enable; + hdmi->disable_audio = dw_hdmi_gp_audio_disable; + + pdevinfo.name = "dw-hdmi-gp-audio"; + pdevinfo.id = PLATFORM_DEVID_NONE; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); } - if (config0 & HDMI_CONFIG0_CEC) { + if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) { cec.hdmi = hdmi; cec.ops = &dw_hdmi_cec_ops; cec.irq = irq; @@ -2771,20 +3624,13 @@ __dw_hdmi_probe(struct platform_device *pdev, hdmi->cec = platform_device_register_full(&pdevinfo); } + drm_bridge_add(&hdmi->bridge); + return hdmi; err_iahb: - if (hdmi->i2c) { - i2c_del_adapter(&hdmi->i2c->adap); - 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); + clk_disable_unprepare(hdmi->cec_clk); err_isfr: clk_disable_unprepare(hdmi->isfr_clk); err_res: @@ -2792,9 +3638,12 @@ err_res: return ERR_PTR(ret); } +EXPORT_SYMBOL_GPL(dw_hdmi_probe); -static void __dw_hdmi_remove(struct dw_hdmi *hdmi) +void dw_hdmi_remove(struct dw_hdmi *hdmi) { + drm_bridge_remove(&hdmi->bridge); + if (hdmi->audio && !IS_ERR(hdmi->audio)) platform_device_unregister(hdmi->audio); if (!IS_ERR(hdmi->cec)) @@ -2803,44 +3652,15 @@ 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) - clk_disable_unprepare(hdmi->cec_clk); + clk_disable_unprepare(hdmi->cec_clk); if (hdmi->i2c) i2c_del_adapter(&hdmi->i2c->adap); else i2c_put_adapter(hdmi->ddc); } - -/* ----------------------------------------------------------------------------- - * Probe/remove API, used from platforms based on the DRM bridge API. - */ -struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, - const struct dw_hdmi_plat_data *plat_data) -{ - struct dw_hdmi *hdmi; - - hdmi = __dw_hdmi_probe(pdev, plat_data); - if (IS_ERR(hdmi)) - return hdmi; - - drm_bridge_add(&hdmi->bridge); - - return hdmi; -} -EXPORT_SYMBOL_GPL(dw_hdmi_probe); - -void dw_hdmi_remove(struct dw_hdmi *hdmi) -{ - drm_bridge_remove(&hdmi->bridge); - - __dw_hdmi_remove(hdmi); -} EXPORT_SYMBOL_GPL(dw_hdmi_remove); /* ----------------------------------------------------------------------------- @@ -2853,14 +3673,13 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, struct dw_hdmi *hdmi; int ret; - hdmi = __dw_hdmi_probe(pdev, plat_data); + hdmi = dw_hdmi_probe(pdev, plat_data); if (IS_ERR(hdmi)) return hdmi; - ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL); + ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); if (ret) { dw_hdmi_remove(hdmi); - DRM_ERROR("Failed to initialize bridge with drm\n"); return ERR_PTR(ret); } @@ -2870,7 +3689,7 @@ EXPORT_SYMBOL_GPL(dw_hdmi_bind); void dw_hdmi_unbind(struct dw_hdmi *hdmi) { - __dw_hdmi_remove(hdmi); + dw_hdmi_remove(hdmi); } EXPORT_SYMBOL_GPL(dw_hdmi_unbind); |