// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) Rockchip Electronics Co.Ltd * Author: * Algea Cao */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dw-hdmi-qp-audio.h" #include "dw-hdmi-qp.h" #include #define DDC_CI_ADDR 0x37 #define DDC_SEGMENT_ADDR 0x30 #define HDMI_EDID_LEN 512 /* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */ #define SCDC_MIN_SOURCE_VERSION 0x1 #define HDMI14_MAX_TMDSCLK 340000000 static const unsigned int dw_hdmi_cable[] = { EXTCON_DISP_HDMI, EXTCON_NONE, }; /* * Unless otherwise noted, entries in this table are 100% optimization. * Values can be obtained from hdmi_compute_n() but that function is * slow so we pre-compute values we expect to see. * * All 32k and 48k values are expected to be the same (due to the way * the math works) for any rate that's an exact kHz. */ static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = { { .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, }, { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, }, { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, }, { .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, }, { .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, { .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, { .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, { .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, }, { .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, { .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, { .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, }, { .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, }, { .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, }, { .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, }, { .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, }, { .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, { .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, }, { .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, }, { .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, }, { .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, { .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, }, { .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, { .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, }, { .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, }, { .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, }, /* For 297 MHz+ HDMI spec have some other rule for setting N */ { .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, }, { .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, }, /* End of table */ { .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, }, }; static const struct drm_display_mode dw_hdmi_default_modes[] = { /* 16 - 1920x1080@60Hz 16:9 */ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, /* 2 - 720x480@60Hz 4:3 */ { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, 798, 858, 0, 480, 489, 495, 525, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, /* 4 - 1280x720@60Hz 16:9 */ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, 1430, 1650, 0, 720, 725, 730, 750, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, /* 31 - 1920x1080@50Hz 16:9 */ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448, 2492, 2640, 0, 1080, 1084, 1089, 1125, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, /* 19 - 1280x720@50Hz 16:9 */ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720, 1760, 1980, 0, 720, 725, 730, 750, 0, DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, /* 17 - 720x576@50Hz 4:3 */ { DRM_MODE("720x576", DRM_MODE_TYPE_DRIVER, 27000, 720, 732, 796, 864, 0, 576, 581, 586, 625, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, /* 2 - 720x480@60Hz 4:3 */ { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, 798, 858, 0, 480, 489, 495, 525, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), .picture_aspect_ratio = HDMI_PICTURE_ASPECT_4_3, }, }; enum frl_mask { FRL_3GBPS_3LANE = 1, FRL_6GBPS_3LANE, FRL_6GBPS_4LANE, FRL_8GBPS_4LANE, FRL_10GBPS_4LANE, FRL_12GBPS_4LANE, }; struct hdmi_vmode_qp { bool mdataenablepolarity; unsigned int previous_pixelclock; unsigned long mpixelclock; unsigned int mpixelrepetitioninput; unsigned int mpixelrepetitionoutput; unsigned long previous_tmdsclock; unsigned int mtmdsclock; }; struct hdmi_qp_data_info { unsigned int enc_in_bus_format; unsigned int enc_out_bus_format; unsigned int enc_in_encoding; unsigned int enc_out_encoding; unsigned int quant_range; unsigned int pix_repet_factor; struct hdmi_vmode_qp video_mode; bool update; }; struct dw_hdmi_qp_i2c { struct i2c_adapter adap; struct mutex lock; /* used to serialize data transfers */ struct completion cmp; u32 stat; u8 slave_reg; bool is_regaddr; bool is_segment; unsigned int scl_high_ns; unsigned int scl_low_ns; }; struct dw_hdmi_phy_data { enum dw_hdmi_phy_type type; const char *name; unsigned int gen; bool has_svsret; int (*configure)(struct dw_hdmi_qp *hdmi, const struct dw_hdmi_plat_data *pdata, unsigned long mpixelclock); }; struct dw_hdmi_qp { struct drm_connector connector; struct drm_bridge bridge; struct platform_device *hdcp_dev; struct platform_device *audio; struct device *dev; struct dw_hdmi_qp_i2c *i2c; struct hdmi_qp_data_info hdmi_data; const struct dw_hdmi_plat_data *plat_data; int vic; int main_irq; int avp_irq; int earc_irq; u8 edid[HDMI_EDID_LEN]; struct { const struct dw_hdmi_qp_phy_ops *ops; const char *name; void *data; bool enabled; } phy; struct drm_display_mode previous_mode; struct i2c_adapter *ddc; void __iomem *regs; bool sink_is_hdmi; bool sink_has_audio; bool hpd_state; struct mutex mutex; /* for state below and previous_mode */ struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */ enum drm_connector_force force; /* mutex-protected force state */ bool disabled; /* DRM has disabled our bridge */ bool bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ u32 scdc_intr; u32 flt_intr; u32 earc_intr; spinlock_t audio_lock; struct mutex audio_mutex; unsigned int sample_rate; unsigned int audio_cts; unsigned int audio_n; bool audio_enable; void (*enable_audio)(struct dw_hdmi_qp *hdmi); void (*disable_audio)(struct dw_hdmi_qp *hdmi); struct dentry *debugfs_dir; bool scramble_low_rates; struct extcon_dev *extcon; struct regmap *regm; bool initialized; /* hdmi is enabled before bind */ struct completion flt_cmp; struct completion earc_cmp; struct cec_notifier *cec_notifier; struct cec_adapter *cec_adap; struct mutex cec_notifier_mutex; hdmi_codec_plugged_cb plugged_cb; struct device *codec_dev; enum drm_connector_status last_connector_result; }; static inline void hdmi_writel(struct dw_hdmi_qp *hdmi, u32 val, int offset) { regmap_write(hdmi->regm, offset, val); } static inline u32 hdmi_readl(struct dw_hdmi_qp *hdmi, int offset) { unsigned int val = 0; regmap_read(hdmi->regm, offset, &val); return val; } static void handle_plugged_change(struct dw_hdmi_qp *hdmi, bool plugged) { if (hdmi->plugged_cb && hdmi->codec_dev) hdmi->plugged_cb(hdmi->codec_dev, plugged); } int dw_hdmi_qp_set_plugged_cb(struct dw_hdmi_qp *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_qp_set_plugged_cb); static void hdmi_modb(struct dw_hdmi_qp *hdmi, u32 data, u32 mask, u32 reg) { regmap_update_bits(hdmi->regm, reg, mask, data); } static void hdmi_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, unsigned int n) { /* Set N */ hdmi_modb(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0); /* Set CTS */ if (cts) hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK, AUDPKT_ACR_CONTROL1); else hdmi_modb(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK, AUDPKT_ACR_CONTROL1); hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK, AUDPKT_ACR_CONTROL1); } static int hdmi_match_tmds_n_table(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) { const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; int i; if (plat_data->tmds_n_table) { for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) { if (pixel_clk == plat_data->tmds_n_table[i].tmds) { tmds_n = &plat_data->tmds_n_table[i]; break; } } } if (tmds_n == NULL) { for (i = 0; common_tmds_n_table[i].tmds != 0; i++) { if (pixel_clk == common_tmds_n_table[i].tmds) { tmds_n = &common_tmds_n_table[i]; break; } } } if (tmds_n == NULL) return -ENOENT; switch (freq) { case 32000: return tmds_n->n_32k; case 44100: case 88200: case 176400: return (freq / 44100) * tmds_n->n_44k1; case 48000: case 96000: case 192000: return (freq / 48000) * tmds_n->n_48k; default: return -ENOENT; } } static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n, unsigned int pixel_clk) { u64 final, diff; u64 cts; final = (u64)pixel_clk * n; cts = final; do_div(cts, 128 * freq); diff = final - (u64)cts * (128 * freq); return diff; } static unsigned int hdmi_compute_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long freq) { unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500); unsigned int max_n = (128 * freq) / 300; unsigned int ideal_n = (128 * freq) / 1000; unsigned int best_n_distance = ideal_n; unsigned int best_n = 0; u64 best_diff = U64_MAX; int n; /* If the ideal N could satisfy the audio math, then just take it */ if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0) return ideal_n; for (n = min_n; n <= max_n; n++) { u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk); if (diff < best_diff || (diff == best_diff && abs(n - ideal_n) < best_n_distance)) { best_n = n; best_diff = diff; best_n_distance = abs(best_n - ideal_n); } /* * The best N already satisfy the audio math, and also be * the closest value to ideal N, so just cut the loop. */ if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance)) break; } return best_n; } static unsigned int hdmi_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned long sample_rate) { int n; n = hdmi_match_tmds_n_table(hdmi, pixel_clk, sample_rate); if (n > 0) return n; dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n", pixel_clk); return hdmi_compute_n(hdmi, pixel_clk, sample_rate); } /* * 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_qp_set_channel_status(struct dw_hdmi_qp *hdmi, u8 *channel_status) { /* Set channel status */ hdmi_writel(hdmi, channel_status[3] | (channel_status[4] << 8), AUDPKT_CHSTATUS_OVR1); hdmi_modb(hdmi, AUDPKT_CHSTATUS_OVR_EN, AUDPKT_CHSTATUS_OVR_EN_MASK, AUDPKT_CONTROL0); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_status); static void hdmi_set_clk_regenerator(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, unsigned int sample_rate) { unsigned int n = 0, cts = 0; n = hdmi_find_n(hdmi, pixel_clk, sample_rate); spin_lock_irq(&hdmi->audio_lock); hdmi->audio_n = n; hdmi->audio_cts = cts; hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); spin_unlock_irq(&hdmi->audio_lock); } static void hdmi_init_clk_regenerator(struct dw_hdmi_qp *hdmi) { mutex_lock(&hdmi->audio_mutex); hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); mutex_unlock(&hdmi->audio_mutex); } static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi_qp *hdmi) { mutex_lock(&hdmi->audio_mutex); hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, hdmi->sample_rate); mutex_unlock(&hdmi->audio_mutex); } void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate) { mutex_lock(&hdmi->audio_mutex); hdmi->sample_rate = rate; hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, hdmi->sample_rate); mutex_unlock(&hdmi->audio_mutex); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_sample_rate); void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt) { } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_count); void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca) { } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_allocation); static void hdmi_enable_audio_clk(struct dw_hdmi_qp *hdmi, bool enable) { if (enable) hdmi_modb(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); else hdmi_modb(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); } static void dw_hdmi_i2s_audio_enable(struct dw_hdmi_qp *hdmi) { hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); hdmi_enable_audio_clk(hdmi, true); } static void dw_hdmi_i2s_audio_disable(struct dw_hdmi_qp *hdmi) { hdmi_enable_audio_clk(hdmi, false); } void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi) { unsigned long flags; spin_lock_irqsave(&hdmi->audio_lock, flags); hdmi->audio_enable = true; if (hdmi->enable_audio) hdmi->enable_audio(hdmi); spin_unlock_irqrestore(&hdmi->audio_lock, flags); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_enable); void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi) { unsigned long flags; spin_lock_irqsave(&hdmi->audio_lock, flags); hdmi->audio_enable = false; if (hdmi->disable_audio) hdmi->disable_audio(hdmi); spin_unlock_irqrestore(&hdmi->audio_lock, flags); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_disable); static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) { switch (bus_format) { case MEDIA_BUS_FMT_RGB888_1X24: case MEDIA_BUS_FMT_RGB101010_1X30: case MEDIA_BUS_FMT_RGB121212_1X36: case MEDIA_BUS_FMT_RGB161616_1X48: return true; default: return false; } } static bool hdmi_bus_fmt_is_yuv444(unsigned int bus_format) { switch (bus_format) { case MEDIA_BUS_FMT_YUV8_1X24: case MEDIA_BUS_FMT_YUV10_1X30: case MEDIA_BUS_FMT_YUV12_1X36: case MEDIA_BUS_FMT_YUV16_1X48: return true; default: return false; } } static bool hdmi_bus_fmt_is_yuv422(unsigned int bus_format) { switch (bus_format) { case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_UYVY10_1X20: case MEDIA_BUS_FMT_UYVY12_1X24: return true; default: return false; } } static bool hdmi_bus_fmt_is_yuv420(unsigned int bus_format) { switch (bus_format) { 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: return true; default: return false; } } static int hdmi_bus_fmt_color_depth(unsigned int bus_format) { switch (bus_format) { case MEDIA_BUS_FMT_RGB888_1X24: case MEDIA_BUS_FMT_YUV8_1X24: case MEDIA_BUS_FMT_UYVY8_1X16: case MEDIA_BUS_FMT_UYYVYY8_0_5X24: return 8; case MEDIA_BUS_FMT_RGB101010_1X30: case MEDIA_BUS_FMT_YUV10_1X30: case MEDIA_BUS_FMT_UYVY10_1X20: case MEDIA_BUS_FMT_UYYVYY10_0_5X30: return 10; case MEDIA_BUS_FMT_RGB121212_1X36: case MEDIA_BUS_FMT_YUV12_1X36: case MEDIA_BUS_FMT_UYVY12_1X24: case MEDIA_BUS_FMT_UYYVYY12_0_5X36: return 12; case MEDIA_BUS_FMT_RGB161616_1X48: case MEDIA_BUS_FMT_YUV16_1X48: case MEDIA_BUS_FMT_UYYVYY16_0_5X48: return 16; default: return 0; } } static void dw_hdmi_i2c_init(struct dw_hdmi_qp *hdmi) { /* Software reset */ hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); hdmi_writel(hdmi, 0x00, I2CM_CONTROL0); hdmi_writel(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0); hdmi_modb(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0); /* Clear DONE and ERROR interrupts */ hdmi_writel(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR, MAINUNIT_1_INT_CLEAR); } static int dw_hdmi_i2c_read(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; int stat; if (!i2c->is_regaddr) { dev_dbg(hdmi->dev, "set read register address to 0\n"); i2c->slave_reg = 0x00; i2c->is_regaddr = true; } while (length--) { reinit_completion(&i2c->cmp); hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, I2CM_INTERFACE_CONTROL0); hdmi_modb(hdmi, I2CM_FM_READ, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); if (!stat) { dev_err(hdmi->dev, "i2c read time out!\n"); hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } /* Check for error condition on the bus */ if (i2c->stat & I2CM_NACK_RCVD_IRQ) { dev_err(hdmi->dev, "i2c read err!\n"); hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } *buf++ = hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff; dev_dbg(hdmi->dev, "i2c read done! i2c->stat:%02x 0x%02x\n", i2c->stat, hdmi_readl(hdmi, I2CM_INTERFACE_RDDATA_0_3)); hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); } i2c->is_segment = false; return 0; } static int dw_hdmi_i2c_write(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; int stat; if (!i2c->is_regaddr) { /* Use the first write byte as register address */ i2c->slave_reg = buf[0]; length--; buf++; i2c->is_regaddr = true; } while (length--) { reinit_completion(&i2c->cmp); hdmi_writel(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3); hdmi_modb(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR, I2CM_INTERFACE_CONTROL0); hdmi_modb(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10); if (!stat) { dev_err(hdmi->dev, "i2c write time out!\n"); hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); return -EAGAIN; } /* Check for error condition on the bus */ if (i2c->stat & I2CM_NACK_RCVD_IRQ) { dev_err(hdmi->dev, "i2c write nack!\n"); hdmi_writel(hdmi, 0x01, I2CM_CONTROL0); return -EIO; } hdmi_modb(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0); } dev_dbg(hdmi->dev, "i2c write done!\n"); return 0; } static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap); struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; 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, "i2c xfer: num: %d, addr: %#x\n", num, addr); for (i = 0; i < num; i++) { if (msgs[i].len == 0) { dev_err(hdmi->dev, "unsupported transfer %d/%d, no data\n", i + 1, num); return -EOPNOTSUPP; } } mutex_lock(&i2c->lock); /* Unmute DONE and ERROR interrupts */ hdmi_modb(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N, MAINUNIT_1_INT_MASK_N); /* Set slave device address taken from the first I2C message */ if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1) addr = DDC_ADDR; hdmi_modb(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0); /* Set slave device register address on transfer */ i2c->is_regaddr = false; /* Set segment pointer for I2C extended read mode operation */ i2c->is_segment = false; for (i = 0; i < num; i++) { dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n", i + 1, num, msgs[i].len, msgs[i].flags); if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) { i2c->is_segment = true; hdmi_modb(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR, I2CM_INTERFACE_CONTROL1); hdmi_modb(hdmi, *msgs[i].buf, I2CM_SEG_PTR, I2CM_INTERFACE_CONTROL1); } else { if (msgs[i].flags & I2C_M_RD) ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, msgs[i].len); else ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, msgs[i].len); } if (ret < 0) break; } if (!ret) ret = num; /* Mute DONE and ERROR interrupts */ hdmi_modb(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N, MAINUNIT_1_INT_MASK_N); mutex_unlock(&i2c->lock); return ret; } static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm dw_hdmi_algorithm = { .master_xfer = dw_hdmi_i2c_xfer, .functionality = dw_hdmi_i2c_func, }; static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi_qp *hdmi) { struct i2c_adapter *adap; struct dw_hdmi_qp_i2c *i2c; int ret; i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL); if (!i2c) return ERR_PTR(-ENOMEM); mutex_init(&i2c->lock); init_completion(&i2c->cmp); adap = &i2c->adap; adap->class = I2C_CLASS_DDC; adap->owner = THIS_MODULE; adap->dev.parent = hdmi->dev; adap->algo = &dw_hdmi_algorithm; strscpy(adap->name, "ddc", sizeof(adap->name)); i2c_set_adapdata(adap, hdmi); ret = i2c_add_adapter(adap); if (ret) { dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name); devm_kfree(hdmi->dev, i2c); return ERR_PTR(ret); } hdmi->i2c = i2c; dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name); return adap; } #define HDMI_PHY_EARC_MASK BIT(29) int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi) { u32 stat, ret; /* set hdmi phy earc mode */ hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_PHY_EARC_MASK, true); ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode); if (ret) return ret; reinit_completion(&hdmi->earc_cmp); hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ | EARCRX_CMDC_DISCOVERY_DONE_IRQ, EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ | EARCRX_CMDC_DISCOVERY_DONE_IRQ, EARCRX_0_INT_MASK_N); /* start discovery */ hdmi_modb(hdmi, EARCRX_CMDC_DISCOVERY_EN, EARCRX_CMDC_DISCOVERY_EN, EARCRX_CMDC_CONTROL); /* * The eARC TX device drives a logic-high-voltage-level * pulse on the physical HPD connector pin, after * at least 100 ms of low voltage level to start the * eARC Discovery process. */ hdmi_modb(hdmi, EARCRX_CONNECTOR_HPD, EARCRX_CONNECTOR_HPD, EARCRX_CMDC_CONTROL); stat = wait_for_completion_timeout(&hdmi->earc_cmp, HZ / 10); if (!stat) return -EAGAIN; if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_TIMEOUT_IRQ) { dev_err(hdmi->dev, "discovery timeout\n"); return -ETIMEDOUT; } else if (hdmi->earc_intr & EARCRX_CMDC_DISCOVERY_DONE_IRQ) { dev_info(hdmi->dev, "discovery done\n"); } else { dev_err(hdmi->dev, "discovery failed\n"); return -EINVAL; } hdmi_writel(hdmi, 1, EARCRX_DMAC_PHY_CONTROL); hdmi_modb(hdmi, EARCRX_CMDC_SWINIT_P, EARCRX_CMDC_SWINIT_P, EARCRX_CMDC_CONFIG0); hdmi_writel(hdmi, 0xf3, EARCRX_DMAC_CONFIG); hdmi_writel(hdmi, 0x63, EARCRX_DMAC_CONTROL0); hdmi_writel(hdmi, 0xff, EARCRX_DMAC_CONTROL1); hdmi_modb(hdmi, EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG | EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN, EARCRX_XACTREAD_STOP_CFG | EARCRX_XACTREAD_RETRY_CFG | EARCRX_CMDC_DSCVR_EARCVALID0_TO_DISC1 | EARCRX_CMDC_XACT_RESTART_EN, EARCRX_CMDC_CONFIG0); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER0); hdmi_writel(hdmi, 0x1b0e, EARCRX_DMAC_CHSTATUS_STREAMER1); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER2); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER3); hdmi_writel(hdmi, 0xf2000000, EARCRX_DMAC_CHSTATUS_STREAMER4); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER5); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER6); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER7); hdmi_writel(hdmi, 0, EARCRX_DMAC_CHSTATUS_STREAMER8); return 0; } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_earc); /* ----------------------------------------------------------------------------- * HDMI TX Setup */ static void hdmi_config_AVI(struct dw_hdmi_qp *hdmi, const struct drm_connector *connector, const struct drm_display_mode *mode) { struct hdmi_avi_infoframe frame; u32 val, i, j; u8 buff[17]; enum hdmi_quantization_range rgb_quant_range = hdmi->hdmi_data.quant_range; /* Initialise info frame from DRM mode */ drm_hdmi_avi_infoframe_from_display_mode(&frame, connector, mode); /* * Ignore monitor selectable quantization, use quantization set * by the user */ drm_hdmi_avi_infoframe_quant_range(&frame, connector, mode, rgb_quant_range); if (hdmi_bus_fmt_is_yuv444(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV444; else if (hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV422; else if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) frame.colorspace = HDMI_COLORSPACE_YUV420; else frame.colorspace = HDMI_COLORSPACE_RGB; /* Set up colorimetry */ 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; case V4L2_YCBCR_ENC_BT2020: if (hdmi->hdmi_data.enc_in_encoding == V4L2_YCBCR_ENC_BT2020) frame.colorimetry = HDMI_COLORIMETRY_EXTENDED; else frame.colorimetry = HDMI_COLORIMETRY_ITU_709; frame.extended_colorimetry = HDMI_EXTENDED_COLORIMETRY_BT2020; 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; } frame.scan_mode = HDMI_SCAN_MODE_NONE; hdmi_avi_infoframe_pack_only(&frame, buff, 17); /* * The Designware IP uses a different byte format from standard * AVI info frames, though generally the bits are in the correct * bytes. */ val = (frame.version << 8) | (frame.length << 16); hdmi_writel(hdmi, val, PKT_AVI_CONTENTS0); for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { if (i * 4 + j >= 14) break; if (!j) val = buff[i * 4 + j + 3]; val |= buff[i * 4 + j + 3] << (8 * j); } hdmi_writel(hdmi, val, PKT_AVI_CONTENTS1 + i * 4); } hdmi_modb(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN); } static void hdmi_config_CVTEM(struct dw_hdmi_qp *hdmi) { u8 ds_type = 0; u8 sync = 1; u8 vfr = 1; u8 afr = 0; u8 new = 1; u8 end = 0; u8 data_set_length = 136; u8 hb1[6] = { 0x80, 0, 0, 0, 0, 0x40 }; u8 *pps_body; u32 val, i, reg; struct drm_display_mode *mode = &hdmi->previous_mode; int hsync, hfront, hback; struct dw_hdmi_link_config *link_cfg; void *data = hdmi->plat_data->phy_data; hdmi_modb(hdmi, 0, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_PKT_EN); if (hdmi->plat_data->get_link_cfg) { link_cfg = hdmi->plat_data->get_link_cfg(data); } else { dev_err(hdmi->dev, "can't get frl link cfg\n"); return; } if (!link_cfg->dsc_mode) { dev_info(hdmi->dev, "don't use dsc mode\n"); return; } pps_body = link_cfg->pps_payload; hsync = mode->hsync_end - mode->hsync_start; hback = mode->htotal - mode->hsync_end; hfront = mode->hsync_start - mode->hdisplay; for (i = 0; i < 6; i++) { val = i << 16 | hb1[i] << 8; hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS0 + i * 0x20); } val = new << 7 | end << 6 | ds_type << 4 | afr << 3 | vfr << 2 | sync << 1; hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS1); val = data_set_length << 16 | pps_body[0] << 24; hdmi_writel(hdmi, val, PKT0_EMP_CVTEM_CONTENTS2); reg = PKT0_EMP_CVTEM_CONTENTS3; for (i = 1; i < 125; i++) { if (reg == PKT1_EMP_CVTEM_CONTENTS0 || reg == PKT2_EMP_CVTEM_CONTENTS0 || reg == PKT3_EMP_CVTEM_CONTENTS0 || reg == PKT4_EMP_CVTEM_CONTENTS0 || reg == PKT5_EMP_CVTEM_CONTENTS0) { reg += 4; i--; continue; } if (i % 4 == 1) val = pps_body[i]; if (i % 4 == 2) val |= pps_body[i] << 8; if (i % 4 == 3) val |= pps_body[i] << 16; if (!(i % 4)) { val |= pps_body[i] << 24; hdmi_writel(hdmi, val, reg); reg += 4; } } val = (hfront & 0xff) << 24 | pps_body[127] << 16 | pps_body[126] << 8 | pps_body[125]; hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS6); val = (hback & 0xff) << 24 | ((hsync >> 8) & 0xff) << 16 | (hsync & 0xff) << 8 | ((hfront >> 8) & 0xff); hdmi_writel(hdmi, val, PKT4_EMP_CVTEM_CONTENTS7); val = link_cfg->hcactive << 8 | ((hback >> 8) & 0xff); hdmi_writel(hdmi, val, PKT5_EMP_CVTEM_CONTENTS1); for (i = PKT5_EMP_CVTEM_CONTENTS2; i <= PKT5_EMP_CVTEM_CONTENTS7; i += 4) hdmi_writel(hdmi, 0, i); hdmi_modb(hdmi, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_EMP_CVTEM_TX_EN, PKTSCHED_PKT_EN); } static void hdmi_config_drm_infoframe(struct dw_hdmi_qp *hdmi, const struct drm_connector *connector) { const struct drm_connector_state *conn_state = connector->state; struct hdr_output_metadata *hdr_metadata; struct hdmi_drm_infoframe frame; u8 buffer[30]; ssize_t err; int i; u32 val; if (!hdmi->plat_data->use_drm_infoframe) return; hdmi_modb(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); if (!hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf) { DRM_DEBUG("No need to set HDR metadata in infoframe\n"); return; } if (!conn_state->hdr_output_metadata) { DRM_DEBUG("source metadata not set yet\n"); return; } hdr_metadata = (struct hdr_output_metadata *) conn_state->hdr_output_metadata->data; if (!(hdmi->connector.hdr_sink_metadata.hdmi_type1.eotf & BIT(hdr_metadata->hdmi_metadata_type1.eotf))) { DRM_ERROR("Not support EOTF %d\n", hdr_metadata->hdmi_metadata_type1.eotf); return; } 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; } val = (frame.version << 8) | (frame.length << 16); hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS0); for (i = 0; i <= frame.length; i++) { if (i % 4 == 0) val = buffer[3 + i]; val |= buffer[3 + i] << ((i % 4) * 8); if (i % 4 == 3 || (i == (frame.length))) hdmi_writel(hdmi, val, PKT_DRMI_CONTENTS1 + ((i / 4) * 4)); } hdmi_modb(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); DRM_DEBUG("%s eotf %d end\n", __func__, hdr_metadata->hdmi_metadata_type1.eotf); } /* Filter out invalid setups to avoid configuring SCDC and scrambling */ static bool dw_hdmi_support_scdc(struct dw_hdmi_qp *hdmi, const struct drm_display_info *display) { /* Disable if no DDC bus */ if (!hdmi->ddc) return false; /* Disable if SCDC is not supported, or if an HF-VSDB block is absent */ if (!display->hdmi.scdc.supported || !display->hdmi.scdc.scrambling.supported) return false; /* * Disable if display only support low TMDS rates and scrambling * for low rates is not supported either */ if (!display->hdmi.scdc.scrambling.low_rates && display->max_tmds_clock <= 340000) return false; return true; } static int hdmi_set_frl_mask(int frl_rate) { switch (frl_rate) { case 48: return FRL_12GBPS_4LANE; case 40: return FRL_10GBPS_4LANE; case 32: return FRL_8GBPS_4LANE; case 24: return FRL_6GBPS_4LANE; case 18: return FRL_6GBPS_3LANE; case 9: return FRL_3GBPS_3LANE; } return 0; } static int hdmi_start_flt(struct dw_hdmi_qp *hdmi, u8 rate) { u8 val; u8 ffe_lv = 0; int i = 0, stat; /* FLT_READY & FFE_LEVELS read */ for (i = 0; i < 20; i++) { drm_scdc_readb(hdmi->ddc, SCDC_STATUS_FLAGS_0, &val); if (val & BIT(6)) break; msleep(20); } if (i == 20) { dev_err(hdmi->dev, "sink flt isn't ready\n"); return -EINVAL; } hdmi_modb(hdmi, SCDC_UPD_FLAGS_RD_IRQ, SCDC_UPD_FLAGS_RD_IRQ, MAINUNIT_1_INT_MASK_N); hdmi_modb(hdmi, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); /* max ffe level 3 */ val = 3 << 4 | hdmi_set_frl_mask(rate); drm_scdc_writeb(hdmi->ddc, 0x31, val); /* select FRL_RATE & FFE_LEVELS */ hdmi_writel(hdmi, ffe_lv, FLT_CONFIG0); /* Start LTS_3 state in source DUT */ reinit_completion(&hdmi->flt_cmp); hdmi_modb(hdmi, FLT_EXIT_TO_LTSP_IRQ, FLT_EXIT_TO_LTSP_IRQ, MAINUNIT_1_INT_MASK_N); hdmi_writel(hdmi, 1, FLT_CONTROL0); /* wait for completed link training at source side */ stat = wait_for_completion_timeout(&hdmi->flt_cmp, HZ * 2); if (!stat) { dev_err(hdmi->dev, "wait lts3 finish time out\n"); hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, MAINUNIT_1_INT_MASK_N); return -EAGAIN; } if (!(hdmi->flt_intr & FLT_EXIT_TO_LTSP_IRQ)) { dev_err(hdmi->dev, "not to ltsp\n"); hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, MAINUNIT_1_INT_MASK_N); return -EINVAL; } return 0; } #define HDMI_MODE_FRL_MASK BIT(30) static void hdmi_set_op_mode(struct dw_hdmi_qp *hdmi, struct dw_hdmi_link_config *link_cfg, const struct drm_connector *connector) { int frl_rate; /* set sink frl mode disable and wait sink ready */ hdmi_writel(hdmi, 0, FLT_CONFIG0); if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) drm_scdc_writeb(hdmi->ddc, 0x31, 0); /* * some TVs must wait a while before switching frl mode resolution, * or the signal may not be recognized. */ msleep(200); if (!link_cfg->frl_mode) { dev_info(hdmi->dev, "dw hdmi qp use tmds mode\n"); hdmi_modb(hdmi, 0, OPMODE_FRL, LINK_CONFIG0); hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0); return; } if (link_cfg->frl_lanes == 4) hdmi_modb(hdmi, OPMODE_FRL_4LANES, OPMODE_FRL_4LANES, LINK_CONFIG0); else hdmi_modb(hdmi, 0, OPMODE_FRL_4LANES, LINK_CONFIG0); hdmi_modb(hdmi, 1, OPMODE_FRL, LINK_CONFIG0); frl_rate = link_cfg->frl_lanes * link_cfg->rate_per_lane; hdmi_start_flt(hdmi, frl_rate); } static unsigned long hdmi_get_tmdsclock(struct dw_hdmi_qp *hdmi, unsigned long mpixelclock) { unsigned long tmdsclock = mpixelclock; unsigned int depth = hdmi_bus_fmt_color_depth(hdmi->hdmi_data.enc_out_bus_format); if (!hdmi_bus_fmt_is_yuv422(hdmi->hdmi_data.enc_out_bus_format)) { switch (depth) { case 16: tmdsclock = mpixelclock * 2; break; case 12: tmdsclock = mpixelclock * 3 / 2; break; case 10: tmdsclock = mpixelclock * 5 / 4; break; default: break; } } return tmdsclock; } static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi, const struct drm_connector *connector, const struct drm_display_mode *mode) { int ret; void *data = hdmi->plat_data->phy_data; struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode; struct dw_hdmi_link_config *link_cfg; u8 bytes = 0; hdmi->vic = drm_match_cea_mode(mode); if (!hdmi->vic) dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n"); else dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic); if (hdmi->plat_data->get_enc_out_encoding) hdmi->hdmi_data.enc_out_encoding = hdmi->plat_data->get_enc_out_encoding(data); else if ((hdmi->vic == 6) || (hdmi->vic == 7) || (hdmi->vic == 21) || (hdmi->vic == 22) || (hdmi->vic == 2) || (hdmi->vic == 3) || (hdmi->vic == 17) || (hdmi->vic == 18)) hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_601; else hdmi->hdmi_data.enc_out_encoding = V4L2_YCBCR_ENC_709; if (mode->flags & DRM_MODE_FLAG_DBLCLK) { hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 1; hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 1; } else { hdmi->hdmi_data.video_mode.mpixelrepetitionoutput = 0; hdmi->hdmi_data.video_mode.mpixelrepetitioninput = 0; } /* Get input format from plat data or fallback to RGB888 */ if (hdmi->plat_data->get_input_bus_format) hdmi->hdmi_data.enc_in_bus_format = hdmi->plat_data->get_input_bus_format(data); else if (hdmi->plat_data->input_bus_format) hdmi->hdmi_data.enc_in_bus_format = hdmi->plat_data->input_bus_format; else hdmi->hdmi_data.enc_in_bus_format = MEDIA_BUS_FMT_RGB888_1X24; /* Default to RGB888 output format */ if (hdmi->plat_data->get_output_bus_format) hdmi->hdmi_data.enc_out_bus_format = hdmi->plat_data->get_output_bus_format(data); else hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24; /* Get input encoding from plat data or fallback to none */ if (hdmi->plat_data->get_enc_in_encoding) hdmi->hdmi_data.enc_in_encoding = hdmi->plat_data->get_enc_in_encoding(data); else if (hdmi->plat_data->input_bus_encoding) hdmi->hdmi_data.enc_in_encoding = hdmi->plat_data->input_bus_encoding; else hdmi->hdmi_data.enc_in_encoding = V4L2_YCBCR_ENC_DEFAULT; if (hdmi->plat_data->get_quant_range) hdmi->hdmi_data.quant_range = hdmi->plat_data->get_quant_range(data); else hdmi->hdmi_data.quant_range = HDMI_QUANTIZATION_RANGE_DEFAULT; if (hdmi->plat_data->get_link_cfg) link_cfg = hdmi->plat_data->get_link_cfg(data); else return -EINVAL; hdmi->phy.ops->set_mode(hdmi, hdmi->phy.data, HDMI_MODE_FRL_MASK, link_cfg->frl_mode); /* * According to the dw-hdmi specification 6.4.2 * vp_pr_cd[3:0]: * 0000b: No pixel repetition (pixel sent only once) * 0001b: Pixel sent two times (pixel repeated once) */ hdmi->hdmi_data.pix_repet_factor = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 1 : 0; hdmi->hdmi_data.video_mode.mdataenablepolarity = true; vmode->previous_pixelclock = vmode->mpixelclock; vmode->mpixelclock = mode->crtc_clock * 1000; if ((mode->flags & DRM_MODE_FLAG_3D_MASK) == DRM_MODE_FLAG_3D_FRAME_PACKING) vmode->mpixelclock *= 2; dev_dbg(hdmi->dev, "final pixclk = %ld\n", vmode->mpixelclock); vmode->previous_tmdsclock = vmode->mtmdsclock; vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi, vmode->mpixelclock); if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) vmode->mtmdsclock /= 2; dev_info(hdmi->dev, "final tmdsclk = %d\n", vmode->mtmdsclock); ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode); if (ret) return ret; if (hdmi->plat_data->set_grf_cfg) hdmi->plat_data->set_grf_cfg(data); if (hdmi->sink_has_audio) { dev_dbg(hdmi->dev, "sink has audio support\n"); /* HDMI Initialization Step E - Configure audio */ hdmi_clk_regenerator_update_pixel_clock(hdmi); hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); } /* not for DVI mode */ if (hdmi->sink_is_hdmi) { dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__); hdmi_modb(hdmi, 0, OPMODE_DVI, LINK_CONFIG0); hdmi_modb(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0); if (!link_cfg->frl_mode) { if (vmode->mtmdsclock > HDMI14_MAX_TMDSCLK) { drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION, &bytes); drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION, min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION)); drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 1); drm_scdc_set_scrambling(hdmi->ddc, 1); hdmi_writel(hdmi, 1, SCRAMB_CONFIG0); } else { if (dw_hdmi_support_scdc(hdmi, &connector->display_info)) { drm_scdc_set_high_tmds_clock_ratio(hdmi->ddc, 0); drm_scdc_set_scrambling(hdmi->ddc, 0); } hdmi_writel(hdmi, 0, SCRAMB_CONFIG0); } } /* HDMI Initialization Step F - Configure AVI InfoFrame */ hdmi_config_AVI(hdmi, connector, mode); hdmi_config_CVTEM(hdmi); hdmi_config_drm_infoframe(hdmi, connector); hdmi_set_op_mode(hdmi, link_cfg, connector); } else { hdmi_modb(hdmi, OPMODE_DVI, OPMODE_DVI, LINK_CONFIG0); dev_info(hdmi->dev, "%s DVI mode\n", __func__); } return 0; } static enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); mutex_lock(&hdmi->mutex); hdmi->force = DRM_FORCE_UNSPECIFIED; mutex_unlock(&hdmi->mutex); return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); } static int dw_hdmi_update_hdr_property(struct drm_connector *connector) { struct drm_device *dev = connector->dev; struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); void *data = hdmi->plat_data->phy_data; const struct hdr_static_metadata *metadata = &connector->hdr_sink_metadata.hdmi_type1; size_t size = sizeof(*metadata); struct drm_property *property; struct drm_property_blob *blob; int ret; if (hdmi->plat_data->get_hdr_property) property = hdmi->plat_data->get_hdr_property(data); else return -EINVAL; if (hdmi->plat_data->get_hdr_blob) blob = hdmi->plat_data->get_hdr_blob(data); else return -EINVAL; ret = drm_property_replace_global_blob(dev, &blob, size, metadata, &connector->base, property); return ret; } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); struct hdr_static_metadata *metedata = &connector->hdr_sink_metadata.hdmi_type1; struct edid *edid; struct drm_display_mode *mode; struct drm_display_info *info = &connector->display_info; void *data = hdmi->plat_data->phy_data; int i, ret = 0; if (!hdmi->ddc) return 0; memset(metedata, 0, sizeof(*metedata)); 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); if (hdmi->plat_data->get_edid_dsc_info) hdmi->plat_data->get_edid_dsc_info(data, edid); ret = drm_add_edid_modes(connector, edid); dw_hdmi_update_hdr_property(connector); kfree(edid); } else { hdmi->sink_is_hdmi = true; hdmi->sink_has_audio = true; for (i = 0; i < ARRAY_SIZE(dw_hdmi_default_modes); i++) { const struct drm_display_mode *ptr = &dw_hdmi_default_modes[i]; mode = drm_mode_duplicate(connector->dev, ptr); if (mode) { if (!i) { mode->type = DRM_MODE_TYPE_PREFERRED; mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_NONE; } drm_mode_probed_add(connector, mode); ret++; } } info->edid_hdmi_dc_modes = 0; info->hdmi.y420_dc_modes = 0; info->color_formats = 0; dev_info(hdmi->dev, "failed to get edid\n"); } return ret; } static int dw_hdmi_atomic_connector_set_property(struct drm_connector *connector, struct drm_connector_state *state, struct drm_property *property, uint64_t val) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; if (ops && ops->set_property) return ops->set_property(connector, state, property, val, hdmi->plat_data->phy_data); else return -EINVAL; } static int dw_hdmi_atomic_connector_get_property(struct drm_connector *connector, const struct drm_connector_state *state, struct drm_property *property, uint64_t *val) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; if (ops && ops->get_property) return ops->get_property(connector, state, property, val, hdmi->plat_data->phy_data); else return -EINVAL; } static int dw_hdmi_connector_set_property(struct drm_connector *connector, struct drm_property *property, uint64_t val) { return dw_hdmi_atomic_connector_set_property(connector, NULL, property, val); } static void dw_hdmi_attach_properties(struct dw_hdmi_qp *hdmi) { unsigned int color = MEDIA_BUS_FMT_RGB888_1X24; const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; if (ops && ops->attach_properties) return ops->attach_properties(&hdmi->connector, color, 0, hdmi->plat_data->phy_data); } static void dw_hdmi_destroy_properties(struct dw_hdmi_qp *hdmi) { const struct dw_hdmi_property_ops *ops = hdmi->plat_data->property_ops; if (ops && ops->destroy_properties) return ops->destroy_properties(&hdmi->connector, hdmi->plat_data->phy_data); } static struct drm_encoder * dw_hdmi_connector_best_encoder(struct drm_connector *connector) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); return hdmi->bridge.encoder; } static bool dw_hdmi_color_changed(struct drm_connector *connector) { struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); void *data = hdmi->plat_data->phy_data; bool ret = false; if (hdmi->plat_data->get_color_changed) ret = hdmi->plat_data->get_color_changed(data); return ret; } static bool hdr_metadata_equal(const struct drm_connector_state *old_state, const struct drm_connector_state *new_state) { struct drm_property_blob *old_blob = old_state->hdr_output_metadata; struct drm_property_blob *new_blob = new_state->hdr_output_metadata; if (!old_blob || !new_blob) return old_blob == new_blob; if (old_blob->length != new_blob->length) return false; return !memcmp(old_blob->data, new_blob->data, old_blob->length); } 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; struct dw_hdmi_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); struct drm_display_mode *mode = NULL; void *data = hdmi->plat_data->phy_data; struct hdmi_vmode_qp *vmode = &hdmi->hdmi_data.video_mode; unsigned int in_bus_format = hdmi->hdmi_data.enc_in_bus_format; unsigned int out_bus_format = hdmi->hdmi_data.enc_out_bus_format; bool color_changed = false; if (!crtc) return 0; crtc_state = drm_atomic_get_crtc_state(state, crtc); if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); /* * If HDMI is enabled in uboot, it's need to record * drm_display_mode and set phy status to enabled. */ if (!vmode->mpixelclock) { crtc_state = drm_atomic_get_crtc_state(state, crtc); if (hdmi->plat_data->get_enc_in_encoding) hdmi->hdmi_data.enc_in_encoding = hdmi->plat_data->get_enc_in_encoding(data); if (hdmi->plat_data->get_enc_out_encoding) hdmi->hdmi_data.enc_out_encoding = hdmi->plat_data->get_enc_out_encoding(data); if (hdmi->plat_data->get_input_bus_format) hdmi->hdmi_data.enc_in_bus_format = hdmi->plat_data->get_input_bus_format(data); if (hdmi->plat_data->get_output_bus_format) hdmi->hdmi_data.enc_out_bus_format = hdmi->plat_data->get_output_bus_format(data); mode = &crtc_state->mode; memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); vmode->mpixelclock = mode->crtc_clock * 1000; vmode->previous_pixelclock = mode->clock; vmode->previous_tmdsclock = mode->clock; vmode->mtmdsclock = hdmi_get_tmdsclock(hdmi, vmode->mpixelclock); if (hdmi_bus_fmt_is_yuv420(hdmi->hdmi_data.enc_out_bus_format)) vmode->mtmdsclock /= 2; if (in_bus_format != hdmi->hdmi_data.enc_in_bus_format || out_bus_format != hdmi->hdmi_data.enc_out_bus_format) color_changed = true; } if (!hdr_metadata_equal(old_state, new_state) || dw_hdmi_color_changed(connector) || color_changed) { 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_qp *hdmi = container_of(connector, struct dw_hdmi_qp, connector); mutex_lock(&hdmi->mutex); if (hdmi->force != connector->force) { if (!hdmi->disabled && connector->force == DRM_FORCE_OFF) extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false); else if (hdmi->disabled && connector->force == DRM_FORCE_ON) extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true); } hdmi->force = connector->force; mutex_unlock(&hdmi->mutex); } static int dw_hdmi_qp_fill_modes(struct drm_connector *connector, u32 max_x, u32 max_y) { return drm_helper_probe_single_connector_modes(connector, 9000, 9000); } static const struct drm_connector_funcs dw_hdmi_connector_funcs = { .fill_modes = dw_hdmi_qp_fill_modes, .detect = dw_hdmi_connector_detect, .destroy = drm_connector_cleanup, .force = dw_hdmi_connector_force, .reset = drm_atomic_helper_connector_reset, .set_property = dw_hdmi_connector_set_property, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .atomic_set_property = dw_hdmi_atomic_connector_set_property, .atomic_get_property = dw_hdmi_atomic_connector_get_property, }; static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = { .get_modes = dw_hdmi_connector_get_modes, .best_encoder = dw_hdmi_connector_best_encoder, .atomic_check = dw_hdmi_connector_atomic_check, }; static int dw_hdmi_qp_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { struct dw_hdmi_qp *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_attach_encoder(connector, encoder); dw_hdmi_attach_properties(hdmi); 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_qp_bridge_detach(struct drm_bridge *bridge) { struct dw_hdmi_qp *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_qp_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, const struct drm_display_mode *mode) { struct dw_hdmi_qp *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; if (pdata->mode_valid) mode_status = pdata->mode_valid(connector, pdata->priv_data, info, mode); return mode_status; } static void dw_hdmi_qp_bridge_mode_set(struct drm_bridge *bridge, const struct drm_display_mode *orig_mode, const struct drm_display_mode *mode) { struct dw_hdmi_qp *hdmi = bridge->driver_private; mutex_lock(&hdmi->mutex); /* Store the display mode for plugin/DKMS poweron events */ memcpy(&hdmi->previous_mode, mode, sizeof(hdmi->previous_mode)); mutex_unlock(&hdmi->mutex); } static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_bridge_state *old_state) { struct dw_hdmi_qp *hdmi = bridge->driver_private; extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false); handle_plugged_change(hdmi, false); mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; if (hdmi->phy.ops->disable) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); mutex_unlock(&hdmi->mutex); } static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_state) { struct dw_hdmi_qp *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_qp_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode); mutex_unlock(&hdmi->mutex); extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true); handle_plugged_change(hdmi, true); } 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_qp_bridge_attach, .detach = dw_hdmi_qp_bridge_detach, .mode_set = dw_hdmi_qp_bridge_mode_set, .mode_valid = dw_hdmi_qp_bridge_mode_valid, .atomic_enable = dw_hdmi_qp_bridge_atomic_enable, .atomic_disable = dw_hdmi_qp_bridge_atomic_disable, }; void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap) { hdmi->cec_adap = adap; } EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_cec_adap); static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; struct dw_hdmi_qp_i2c *i2c = hdmi->i2c; u32 stat; stat = hdmi_readl(hdmi, MAINUNIT_1_INT_STATUS); i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ | I2CM_NACK_RCVD_IRQ); hdmi->scdc_intr = stat & (SCDC_UPD_FLAGS_RD_IRQ | SCDC_UPD_FLAGS_CHG_IRQ | SCDC_UPD_FLAGS_CLR_IRQ | SCDC_RR_REPLY_STOP_IRQ | SCDC_NACK_RCVD_IRQ); hdmi->flt_intr = stat & (FLT_EXIT_TO_LTSP_IRQ | FLT_EXIT_TO_LTS4_IRQ | FLT_EXIT_TO_LTSL_IRQ); dev_dbg(hdmi->dev, "i2c main unit irq:%#x\n", stat); if (i2c->stat) { hdmi_writel(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR); complete(&i2c->cmp); } if (hdmi->flt_intr) { dev_dbg(hdmi->dev, "i2c flt irq:%#x\n", hdmi->flt_intr); hdmi_writel(hdmi, hdmi->flt_intr, MAINUNIT_1_INT_CLEAR); complete(&hdmi->flt_cmp); } if (hdmi->scdc_intr) { u8 val; dev_dbg(hdmi->dev, "i2c scdc irq:%#x\n", hdmi->scdc_intr); hdmi_writel(hdmi, hdmi->scdc_intr, MAINUNIT_1_INT_CLEAR); val = hdmi_readl(hdmi, SCDC_STATUS0); /* frl start */ if (val & BIT(4)) { hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_POLL_EN | SCDC_UPD_FLAGS_AUTO_CLR, SCDC_CONFIG0); hdmi_modb(hdmi, 0, SCDC_UPD_FLAGS_RD_IRQ, MAINUNIT_1_INT_MASK_N); dev_info(hdmi->dev, "frl start\n"); } } if (stat) return IRQ_HANDLED; return IRQ_NONE; } static irqreturn_t dw_hdmi_qp_avp_hardirq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; u32 stat; stat = hdmi_readl(hdmi, AVP_1_INT_STATUS); if (stat) { dev_dbg(hdmi->dev, "HDCP irq %#x\n", stat); stat &= ~stat; hdmi_writel(hdmi, stat, AVP_1_INT_MASK_N); return IRQ_WAKE_THREAD; } return IRQ_NONE; } static irqreturn_t dw_hdmi_qp_earc_hardirq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; u32 stat; stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS); if (stat) { dev_dbg(hdmi->dev, "earc irq %#x\n", stat); stat &= ~stat; hdmi_writel(hdmi, stat, EARCRX_0_INT_MASK_N); return IRQ_WAKE_THREAD; } return IRQ_NONE; } static irqreturn_t dw_hdmi_qp_avp_irq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; u32 stat; stat = hdmi_readl(hdmi, AVP_1_INT_STATUS); if (!stat) return IRQ_NONE; hdmi_writel(hdmi, stat, AVP_1_INT_CLEAR); return IRQ_HANDLED; } static irqreturn_t dw_hdmi_qp_earc_irq(int irq, void *dev_id) { struct dw_hdmi_qp *hdmi = dev_id; u32 stat; stat = hdmi_readl(hdmi, EARCRX_0_INT_STATUS); if (!stat) return IRQ_NONE; hdmi_writel(hdmi, stat, EARCRX_0_INT_CLEAR); hdmi->earc_intr = stat; complete(&hdmi->earc_cmp); return IRQ_HANDLED; } static int dw_hdmi_detect_phy(struct dw_hdmi_qp *hdmi) { u8 phy_type; phy_type = hdmi->plat_data->phy_force_vendor ? DW_HDMI_PHY_VENDOR_PHY : 0; if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { /* Vendor PHYs require support from the glue layer. */ if (!hdmi->plat_data->qp_phy_ops || !hdmi->plat_data->phy_name) { dev_err(hdmi->dev, "Vendor HDMI PHY not supported by glue layer\n"); return -ENODEV; } hdmi->phy.ops = hdmi->plat_data->qp_phy_ops; hdmi->phy.data = hdmi->plat_data->phy_data; hdmi->phy.name = hdmi->plat_data->phy_name; } return 0; } void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change) { enum drm_connector_status status = plug_in ? connector_status_connected : connector_status_disconnected; if (!plug_in) cec_notifier_set_phys_addr(hdmi->cec_notifier, CEC_PHYS_ADDR_INVALID); if (hdmi->bridge.dev) { if (change && hdmi->cec_adap && hdmi->cec_adap->devnode.registered) cec_queue_pin_hpd_event(hdmi->cec_adap, hdmi->hpd_state, ktime_get()); drm_bridge_hpd_notify(&hdmi->bridge, status); } } EXPORT_SYMBOL_GPL(dw_hdmi_qp_cec_set_hpd); static const struct regmap_config hdmi_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = EARCRX_1_INT_FORCE, }; static struct dw_hdmi_qp * __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; struct device_node *ddc_node; struct dw_hdmi_qp *hdmi; struct dw_hdmi_qp_i2s_audio_data audio; struct platform_device_info pdevinfo; struct resource *iores = NULL; int irq; int ret; hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); if (!hdmi) return ERR_PTR(-ENOMEM); hdmi->connector.stereo_allowed = 1; hdmi->plat_data = plat_data; hdmi->dev = dev; hdmi->sample_rate = 48000; hdmi->disabled = true; 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); if (ddc_node) { hdmi->ddc = of_get_i2c_adapter_by_node(ddc_node); of_node_put(ddc_node); if (!hdmi->ddc) { dev_dbg(hdmi->dev, "failed to read ddc node\n"); return ERR_PTR(-EPROBE_DEFER); } } else { dev_dbg(hdmi->dev, "no ddc property found\n"); } if (!plat_data->regm) { const struct regmap_config *reg_config; reg_config = &hdmi_regmap_config; iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); hdmi->regs = devm_ioremap_resource(dev, iores); if (IS_ERR(hdmi->regs)) { ret = PTR_ERR(hdmi->regs); goto err_res; } hdmi->regm = devm_regmap_init_mmio(dev, hdmi->regs, reg_config); if (IS_ERR(hdmi->regm)) { dev_err(dev, "Failed to configure regmap\n"); ret = PTR_ERR(hdmi->regm); goto err_res; } } else { hdmi->regm = plat_data->regm; } ret = dw_hdmi_detect_phy(hdmi); if (ret < 0) goto err_res; hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N); hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N); hdmi_writel(hdmi, 428571429, TIMER_BASE_CONFIG0); irq = platform_get_irq(pdev, 0); if (irq < 0) { ret = irq; goto err_res; } hdmi->avp_irq = irq; ret = devm_request_threaded_irq(dev, hdmi->avp_irq, dw_hdmi_qp_avp_hardirq, dw_hdmi_qp_avp_irq, IRQF_SHARED, dev_name(dev), hdmi); if (ret) goto err_res; irq = platform_get_irq(pdev, 2); if (irq < 0) { ret = irq; goto err_res; } hdmi->earc_irq = irq; ret = devm_request_threaded_irq(dev, hdmi->earc_irq, dw_hdmi_qp_earc_hardirq, dw_hdmi_qp_earc_irq, IRQF_SHARED, dev_name(dev), hdmi); if (ret) goto err_res; irq = platform_get_irq(pdev, 3); if (irq < 0) { ret = irq; goto err_res; } hdmi->main_irq = irq; ret = devm_request_threaded_irq(dev, hdmi->main_irq, dw_hdmi_qp_main_hardirq, NULL, IRQF_SHARED, dev_name(dev), hdmi); if (ret) goto err_res; hdmi_init_clk_regenerator(hdmi); /* If DDC bus is not specified, try to register HDMI I2C bus */ if (!hdmi->ddc) { hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); if (IS_ERR(hdmi->ddc)) hdmi->ddc = NULL; /* * Read high and low time from device tree. If not available use * the default timing scl clock rate is about 99.6KHz. */ if (of_property_read_u32(np, "ddc-i2c-scl-high-time-ns", &hdmi->i2c->scl_high_ns)) hdmi->i2c->scl_high_ns = 4708; if (of_property_read_u32(np, "ddc-i2c-scl-low-time-ns", &hdmi->i2c->scl_low_ns)) hdmi->i2c->scl_low_ns = 4916; } hdmi->bridge.driver_private = hdmi; hdmi->bridge.funcs = &dw_hdmi_bridge_funcs; #ifdef CONFIG_OF hdmi->bridge.of_node = pdev->dev.of_node; #endif if (hdmi->phy.ops->setup_hpd) hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data); hdmi->connector.ycbcr_420_allowed = hdmi->plat_data->ycbcr_420_allowed; audio.hdmi = hdmi; audio.eld = hdmi->connector.eld; audio.write = hdmi_writel; audio.read = hdmi_readl; audio.mod = hdmi_modb; hdmi->enable_audio = dw_hdmi_i2s_audio_enable; hdmi->disable_audio = dw_hdmi_i2s_audio_disable; memset(&pdevinfo, 0, sizeof(pdevinfo)); pdevinfo.parent = dev; pdevinfo.id = PLATFORM_DEVID_AUTO; pdevinfo.name = "dw-hdmi-qp-i2s-audio"; pdevinfo.data = &audio; pdevinfo.size_data = sizeof(audio); pdevinfo.dma_mask = DMA_BIT_MASK(32); hdmi->audio = platform_device_register_full(&pdevinfo); hdmi->extcon = devm_extcon_dev_allocate(hdmi->dev, dw_hdmi_cable); if (IS_ERR(hdmi->extcon)) { dev_err(hdmi->dev, "allocate extcon failed\n"); ret = PTR_ERR(hdmi->extcon); goto err_res; } ret = devm_extcon_dev_register(hdmi->dev, hdmi->extcon); if (ret) { dev_err(hdmi->dev, "failed to register extcon: %d\n", ret); goto err_res; } ret = extcon_set_property_capability(hdmi->extcon, EXTCON_DISP_HDMI, EXTCON_PROP_DISP_HPD); if (ret) { dev_err(hdmi->dev, "failed to set USB property capability: %d\n", ret); goto err_res; } /* Reset HDMI DDC I2C master controller and mute I2CM interrupts */ if (hdmi->i2c) dw_hdmi_i2c_init(hdmi); init_completion(&hdmi->flt_cmp); init_completion(&hdmi->earc_cmp); if (of_property_read_bool(np, "scramble-low-rates")) hdmi->scramble_low_rates = true; return hdmi; err_res: if (hdmi->i2c) i2c_del_adapter(&hdmi->i2c->adap); else i2c_put_adapter(hdmi->ddc); return ERR_PTR(ret); } static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi) { if (hdmi->avp_irq) disable_irq(hdmi->avp_irq); if (hdmi->main_irq) disable_irq(hdmi->main_irq); if (hdmi->earc_irq) disable_irq(hdmi->earc_irq); dw_hdmi_destroy_properties(hdmi); hdmi->connector.funcs->destroy(&hdmi->connector); if (hdmi->audio && !IS_ERR(hdmi->audio)) platform_device_unregister(hdmi->audio); if (hdmi->bridge.encoder) hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder); if (hdmi->i2c) i2c_del_adapter(&hdmi->i2c->adap); else i2c_put_adapter(hdmi->ddc); } /* ----------------------------------------------------------------------------- * Bind/unbind API, used from platforms based on the component framework. */ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, struct drm_encoder *encoder, struct dw_hdmi_plat_data *plat_data) { struct dw_hdmi_qp *hdmi; int ret; hdmi = __dw_hdmi_probe(pdev, plat_data); if (IS_ERR(hdmi)) return hdmi; ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL, 0); if (ret) { __dw_hdmi_remove(hdmi); dev_err(hdmi->dev, "Failed to initialize bridge with drm\n"); return ERR_PTR(ret); } plat_data->connector = &hdmi->connector; return hdmi; } EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind); void dw_hdmi_qp_unbind(struct dw_hdmi_qp *hdmi) { __dw_hdmi_remove(hdmi); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_unbind); void dw_hdmi_qp_suspend(struct device *dev, struct dw_hdmi_qp *hdmi) { if (!hdmi) { dev_warn(dev, "Hdmi has not been initialized\n"); return; } mutex_lock(&hdmi->mutex); /* * When system shutdown, hdmi should be disabled. * When system suspend, dw_hdmi_qp_bridge_disable will disable hdmi first. * To prevent duplicate operation, we should determine whether hdmi * has been disabled. */ if (!hdmi->disabled) hdmi->disabled = true; mutex_unlock(&hdmi->mutex); if (hdmi->avp_irq) disable_irq(hdmi->avp_irq); if (hdmi->main_irq) disable_irq(hdmi->main_irq); if (hdmi->earc_irq) disable_irq(hdmi->earc_irq); pinctrl_pm_select_sleep_state(dev); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_suspend); void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi) { if (!hdmi) { dev_warn(dev, "Hdmi has not been initialized\n"); return; } hdmi_writel(hdmi, 0, MAINUNIT_0_INT_MASK_N); hdmi_writel(hdmi, 0, MAINUNIT_1_INT_MASK_N); hdmi_writel(hdmi, 428571429, TIMER_BASE_CONFIG0); pinctrl_pm_select_default_state(dev); mutex_lock(&hdmi->mutex); if (hdmi->i2c) dw_hdmi_i2c_init(hdmi); if (hdmi->avp_irq) enable_irq(hdmi->avp_irq); if (hdmi->main_irq) enable_irq(hdmi->main_irq); if (hdmi->earc_irq) enable_irq(hdmi->earc_irq); mutex_unlock(&hdmi->mutex); } EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume); MODULE_AUTHOR("Algea Cao "); MODULE_DESCRIPTION("DW HDMI QP transmitter driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:dw-hdmi-qp");