From fd0141d1a8a2a26675ee88df75615c05a55044de Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Mon, 17 Feb 2025 16:47:40 -0500 Subject: [PATCH 01/16] drm/bridge: synopsys: Add audio support for dw-hdmi-qp Register the dw-hdmi-qp bridge driver as an HDMI audio codec. The register values computation functions (for n) are based on the downstream driver, as well as the register writing functions. The driver uses the generic HDMI Codec framework in order to implement the HDMI audio support. Signed-off-by: Sugar Zhang Reviewed-by: Dmitry Baryshkov Tested-by: Quentin Schulz Reviewed-by: Robert Foss Signed-off-by: Detlev Casanova Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250217215641.372723-2-detlev.casanova@collabora.com --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 489 +++++++++++++++++++ 1 file changed, 489 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 1d39015f1533..6166f197e37b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -36,6 +36,88 @@ #define SCRAMB_POLL_DELAY_MS 3000 +/* + * Unless otherwise noted, entries in this table are 100% optimization. + * Values can be obtained from dw_hdmi_qp_compute_n() but that function is + * slow so we pre-compute values we expect to see. + * + * The values for TMDS 25175, 25200, 27000, 54000, 74250 and 148500 kHz are + * the recommended N values specified in the Audio chapter of the HDMI + * specification. + */ +static const struct dw_hdmi_audio_tmds_n { + unsigned long tmds; + unsigned int n_32k; + unsigned int n_44k1; + unsigned int n_48k; +} common_tmds_n_table[] = { + { .tmds = 25175000, .n_32k = 4576, .n_44k1 = 7007, .n_48k = 6864, }, + { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, }, + { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 6272, .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 = 6272, .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 = 11648, .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 = 11648, .n_44k1 = 6272, .n_48k = 6144, }, + { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 6272, .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, }, +}; + +/* + * These are the CTS values as recommended in the Audio chapter of the HDMI + * specification. + */ +static const struct dw_hdmi_audio_tmds_cts { + unsigned long tmds; + unsigned int cts_32k; + unsigned int cts_44k1; + unsigned int cts_48k; +} common_tmds_cts_table[] = { + { .tmds = 25175000, .cts_32k = 28125, .cts_44k1 = 31250, .cts_48k = 28125, }, + { .tmds = 25200000, .cts_32k = 25200, .cts_44k1 = 28000, .cts_48k = 25200, }, + { .tmds = 27000000, .cts_32k = 27000, .cts_44k1 = 30000, .cts_48k = 27000, }, + { .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, }, + { .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, }, + { .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, }, + + /* End of table */ + { .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, }, +}; + struct dw_hdmi_qp_i2c { struct i2c_adapter adap; @@ -60,6 +142,8 @@ struct dw_hdmi_qp { } phy; struct regmap *regm; + + unsigned long tmds_char_rate; }; static void dw_hdmi_qp_write(struct dw_hdmi_qp *hdmi, unsigned int val, @@ -83,6 +167,346 @@ static void dw_hdmi_qp_mod(struct dw_hdmi_qp *hdmi, unsigned int data, regmap_update_bits(hdmi->regm, reg, mask, data); } +static struct dw_hdmi_qp *dw_hdmi_qp_from_bridge(struct drm_bridge *bridge) +{ + return container_of(bridge, struct dw_hdmi_qp, bridge); +} + +static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, + unsigned int n) +{ + /* Set N */ + dw_hdmi_qp_mod(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0); + + /* Set CTS */ + if (cts) + dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + else + dw_hdmi_qp_mod(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + + dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK, + AUDPKT_ACR_CONTROL1); +} + +static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi, + unsigned long pixel_clk, + unsigned long freq) +{ + const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; + int i; + + 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) + 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 u32 dw_hdmi_qp_audio_math_diff(unsigned int freq, unsigned int n, + unsigned int pixel_clk) +{ + u64 cts = mul_u32_u32(pixel_clk, n); + + return do_div(cts, 128 * freq); +} + +static unsigned int dw_hdmi_qp_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 (dw_hdmi_qp_audio_math_diff(freq, ideal_n, pixel_clk) == 0) + return ideal_n; + + for (n = min_n; n <= max_n; n++) { + u64 diff = dw_hdmi_qp_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 dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, + unsigned long sample_rate) +{ + int n = dw_hdmi_qp_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 dw_hdmi_qp_compute_n(hdmi, pixel_clk, sample_rate); +} + +static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, + unsigned long sample_rate) +{ + const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL; + int i; + + for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) { + if (pixel_clk == common_tmds_cts_table[i].tmds) { + tmds_cts = &common_tmds_cts_table[i]; + break; + } + } + + if (!tmds_cts) + return 0; + + switch (sample_rate) { + case 32000: + return tmds_cts->cts_32k; + case 44100: + case 88200: + case 176400: + return tmds_cts->cts_44k1; + case 48000: + case 96000: + case 192000: + return tmds_cts->cts_48k; + default: + return -ENOENT; + } +} + +static void dw_hdmi_qp_set_audio_interface(struct dw_hdmi_qp *hdmi, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + u32 conf0 = 0; + + /* Reset the audio data path of the AVP */ + dw_hdmi_qp_write(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST); + + /* Disable AUDS, ACR, AUDI */ + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); + + /* Clear the audio FIFO */ + dw_hdmi_qp_write(hdmi, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0); + + /* Select I2S interface as the audio source */ + dw_hdmi_qp_mod(hdmi, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0); + + /* Enable the active i2s lanes */ + switch (hparms->channels) { + case 7 ... 8: + conf0 |= I2S_LINES_EN(3); + fallthrough; + case 5 ... 6: + conf0 |= I2S_LINES_EN(2); + fallthrough; + case 3 ... 4: + conf0 |= I2S_LINES_EN(1); + fallthrough; + default: + conf0 |= I2S_LINES_EN(0); + break; + } + + dw_hdmi_qp_mod(hdmi, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0); + + /* + * Enable bpcuv generated internally for L-PCM, or received + * from stream for NLPCM/HBR. + */ + switch (fmt->bit_fmt) { + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: + conf0 = (hparms->channels == 8) ? AUD_HBR : AUD_ASP; + conf0 |= I2S_BPCUV_RCV_EN; + break; + default: + conf0 = AUD_ASP | I2S_BPCUV_RCV_DIS; + break; + } + + dw_hdmi_qp_mod(hdmi, conf0, I2S_BPCUV_RCV_MSK | AUD_FORMAT_MSK, + AUDIO_INTERFACE_CONFIG0); + + /* Enable audio FIFO auto clear when overflow */ + dw_hdmi_qp_mod(hdmi, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK, + AUDIO_INTERFACE_CONFIG0); +} + +/* + * 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). + */ +static void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, + u8 *channel_status, bool ref2stream) +{ + /* + * AUDPKT_CHSTATUS_OVR0: { RSV, RSV, CS1, CS0 } + * AUDPKT_CHSTATUS_OVR1: { CS6, CS5, CS4, CS3 } + * + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * CS0: | Mode | d | c | b | a | + * CS1: | Category Code | + * CS2: | Channel Number | Source Number | + * CS3: | Clock Accuracy | Sample Freq | + * CS4: | Ori Sample Freq | Word Length | + * CS5: | | CGMS-A | + * CS6~CS23: Reserved + * + * a: use of channel status block + * b: linear PCM identification: 0 for lpcm, 1 for nlpcm + * c: copyright information + * d: additional format information + */ + + if (ref2stream) + channel_status[0] |= IEC958_AES0_NONAUDIO; + + if ((dw_hdmi_qp_read(hdmi, AUDIO_INTERFACE_CONFIG0) & GENMASK(25, 24)) == AUD_HBR) { + /* fixup cs for HBR */ + channel_status[3] = (channel_status[3] & 0xf0) | IEC958_AES3_CON_FS_768000; + channel_status[4] = (channel_status[4] & 0x0f) | IEC958_AES4_CON_ORIGFS_NOTID; + } + + dw_hdmi_qp_write(hdmi, channel_status[0] | (channel_status[1] << 8), + AUDPKT_CHSTATUS_OVR0); + + regmap_bulk_write(hdmi->regm, AUDPKT_CHSTATUS_OVR1, &channel_status[3], 1); + + if (ref2stream) + dw_hdmi_qp_mod(hdmi, 0, + AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, + AUDPKT_CONTROL0); + else + dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN, + AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, + AUDPKT_CONTROL0); +} + +static void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned long long tmds_char_rate, + unsigned int sample_rate) +{ + unsigned int n, cts; + + n = dw_hdmi_qp_find_n(hdmi, tmds_char_rate, sample_rate); + cts = dw_hdmi_qp_find_cts(hdmi, tmds_char_rate, sample_rate); + + dw_hdmi_qp_set_cts_n(hdmi, cts, n); +} + +static int dw_hdmi_qp_audio_enable(struct drm_connector *connector, + struct drm_bridge *bridge) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + + if (hdmi->tmds_char_rate) + dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); + + return 0; +} + +static int dw_hdmi_qp_audio_prepare(struct drm_connector *connector, + struct drm_bridge *bridge, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + bool ref2stream = false; + + if (!hdmi->tmds_char_rate) + return -ENODEV; + + if (fmt->bit_clk_provider | fmt->frame_clk_provider) { + dev_err(hdmi->dev, "unsupported clock settings\n"); + return -EINVAL; + } + + if (fmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) + ref2stream = true; + + dw_hdmi_qp_set_audio_interface(hdmi, fmt, hparms); + dw_hdmi_qp_set_sample_rate(hdmi, hdmi->tmds_char_rate, hparms->sample_rate); + dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status, ref2stream); + drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, &hparms->cea); + + return 0; +} + +static void dw_hdmi_qp_audio_disable_regs(struct dw_hdmi_qp *hdmi) +{ + /* + * Keep ACR, AUDI, AUDS packet always on to make SINK device + * active for better compatibility and user experience. + * + * This also fix POP sound on some SINK devices which wakeup + * from suspend to active. + */ + dw_hdmi_qp_mod(hdmi, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK, + AUDIO_INTERFACE_CONFIG0); + dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN, + AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK, + AUDPKT_CONTROL0); + + dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, + AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); +} + +static void dw_hdmi_qp_audio_disable(struct drm_connector *connector, + struct drm_bridge *bridge) +{ + struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge); + + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); + + if (hdmi->tmds_char_rate) + dw_hdmi_qp_audio_disable_regs(hdmi); +} + static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi, unsigned char *buf, unsigned int length) { @@ -361,6 +785,51 @@ static int dw_hdmi_qp_config_drm_infoframe(struct dw_hdmi_qp *hdmi, return 0; } +/* + * Static values documented in the TRM + * Different values are only used for debug purposes + */ +#define DW_HDMI_QP_AUDIO_INFOFRAME_HB1 0x1 +#define DW_HDMI_QP_AUDIO_INFOFRAME_HB2 0xa + +static int dw_hdmi_qp_config_audio_infoframe(struct dw_hdmi_qp *hdmi, + const u8 *buffer, size_t len) +{ + /* + * AUDI_CONTENTS0: { RSV, HB2, HB1, RSV } + * AUDI_CONTENTS1: { PB3, PB2, PB1, PB0 } + * AUDI_CONTENTS2: { PB7, PB6, PB5, PB4 } + * + * PB0: CheckSum + * PB1: | CT3 | CT2 | CT1 | CT0 | F13 | CC2 | CC1 | CC0 | + * PB2: | F27 | F26 | F25 | SF2 | SF1 | SF0 | SS1 | SS0 | + * PB3: | F37 | F36 | F35 | F34 | F33 | F32 | F31 | F30 | + * PB4: | CA7 | CA6 | CA5 | CA4 | CA3 | CA2 | CA1 | CA0 | + * PB5: | DM_INH | LSV3 | LSV2 | LSV1 | LSV0 | F52 | F51 | F50 | + * PB6~PB10: Reserved + * + * AUDI_CONTENTS0 default value defined by HDMI specification, + * and shall only be changed for debug purposes. + */ + u32 header_bytes = (DW_HDMI_QP_AUDIO_INFOFRAME_HB1 << 8) | + (DW_HDMI_QP_AUDIO_INFOFRAME_HB2 << 16); + + regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS0, &header_bytes, 1); + regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &buffer[3], 1); + regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS2, &buffer[4], 1); + + /* Enable ACR, AUDI, AMD */ + dw_hdmi_qp_mod(hdmi, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, + PKTSCHED_PKT_EN); + + /* Enable AUDS */ + dw_hdmi_qp_mod(hdmi, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN); + + return 0; +} + static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_atomic_state *state) { @@ -381,6 +850,7 @@ static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge, dev_dbg(hdmi->dev, "%s mode=HDMI rate=%llu\n", __func__, conn_state->hdmi.tmds_char_rate); op_mode = 0; + hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate; } else { dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__); op_mode = OPMODE_DVI; @@ -399,6 +869,8 @@ static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge, { struct dw_hdmi_qp *hdmi = bridge->driver_private; + hdmi->tmds_char_rate = 0; + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); } @@ -454,6 +926,13 @@ static int dw_hdmi_qp_bridge_clear_infoframe(struct drm_bridge *bridge, dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN); break; + case HDMI_INFOFRAME_TYPE_AUDIO: + dw_hdmi_qp_mod(hdmi, 0, + PKTSCHED_ACR_TX_EN | + PKTSCHED_AUDS_TX_EN | + PKTSCHED_AUDI_TX_EN, + PKTSCHED_PKT_EN); + break; default: dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); } @@ -476,6 +955,9 @@ static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge, case HDMI_INFOFRAME_TYPE_DRM: return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len); + case HDMI_INFOFRAME_TYPE_AUDIO: + return dw_hdmi_qp_config_audio_infoframe(hdmi, buffer, len); + default: dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type); return 0; @@ -493,6 +975,9 @@ static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = { .hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid, .hdmi_clear_infoframe = dw_hdmi_qp_bridge_clear_infoframe, .hdmi_write_infoframe = dw_hdmi_qp_bridge_write_infoframe, + .hdmi_audio_startup = dw_hdmi_qp_audio_enable, + .hdmi_audio_shutdown = dw_hdmi_qp_audio_disable, + .hdmi_audio_prepare = dw_hdmi_qp_audio_prepare, }; static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id) @@ -602,6 +1087,10 @@ struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev, if (IS_ERR(hdmi->bridge.ddc)) return ERR_CAST(hdmi->bridge.ddc); + hdmi->bridge.hdmi_audio_max_i2s_playback_channels = 8; + hdmi->bridge.hdmi_audio_dev = dev; + hdmi->bridge.hdmi_audio_dai_port = 1; + ret = devm_drm_bridge_add(dev, &hdmi->bridge); if (ret) return ERR_PTR(ret); -- 2.51.0 From 2bf9f610494d75cfaf3c8a0cef93135ce83f7254 Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 24 Feb 2025 16:13:11 +0800 Subject: [PATCH 02/16] drm/rockchip: analogix_dp: Use formalized struct definition for grf field The formalized struct definition will makes grf field operations more concise and easier to extend. Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250224081325.96724-2-damon.ding@rock-chips.com --- .../gpu/drm/rockchip/analogix_dp-rockchip.c | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 0844175c37c5..0d93df6b5144 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -32,26 +32,29 @@ #include "rockchip_drm_drv.h" -#define RK3288_GRF_SOC_CON6 0x25c -#define RK3288_EDP_LCDC_SEL BIT(5) -#define RK3399_GRF_SOC_CON20 0x6250 -#define RK3399_EDP_LCDC_SEL BIT(5) - -#define HIWORD_UPDATE(val, mask) (val | (mask) << 16) - #define PSR_WAIT_LINE_FLAG_TIMEOUT_MS 100 +#define GRF_REG_FIELD(_reg, _lsb, _msb) { \ + .reg = _reg, \ + .lsb = _lsb, \ + .msb = _msb, \ + .valid = true, \ + } + +struct rockchip_grf_reg_field { + u32 reg; + u32 lsb; + u32 msb; + bool valid; +}; + /** * struct rockchip_dp_chip_data - splite the grf setting of kind of chips - * @lcdsel_grf_reg: grf register offset of lcdc select - * @lcdsel_big: reg value of selecting vop big for eDP - * @lcdsel_lit: reg value of selecting vop little for eDP + * @lcdc_sel: grf register field of lcdc_sel * @chip_type: specific chip type */ struct rockchip_dp_chip_data { - u32 lcdsel_grf_reg; - u32 lcdsel_big; - u32 lcdsel_lit; + const struct rockchip_grf_reg_field lcdc_sel; u32 chip_type; }; @@ -84,6 +87,26 @@ static struct rockchip_dp_device *pdata_encoder_to_dp(struct analogix_dp_plat_da return container_of(plat_data, struct rockchip_dp_device, plat_data); } +static int rockchip_grf_write(struct regmap *grf, u32 reg, u32 mask, u32 val) +{ + return regmap_write(grf, reg, (mask << 16) | (val & mask)); +} + +static int rockchip_grf_field_write(struct regmap *grf, + const struct rockchip_grf_reg_field *field, + u32 val) +{ + u32 mask; + + if (!field->valid) + return 0; + + mask = GENMASK(field->msb, field->lsb); + val <<= field->lsb; + + return rockchip_grf_write(grf, field->reg, mask, val); +} + static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) { reset_control_assert(dp->rst); @@ -181,7 +204,6 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; int ret; - u32 val; crtc = rockchip_dp_drm_get_new_crtc(encoder, state); if (!crtc) @@ -192,24 +214,19 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, if (old_crtc_state && old_crtc_state->self_refresh_active) return; - ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder); - if (ret < 0) - return; - - if (ret) - val = dp->data->lcdsel_lit; - else - val = dp->data->lcdsel_big; - - DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); - ret = clk_prepare_enable(dp->grfclk); if (ret < 0) { DRM_DEV_ERROR(dp->dev, "failed to enable grfclk %d\n", ret); return; } - ret = regmap_write(dp->grf, dp->data->lcdsel_grf_reg, val); + ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder); + if (ret < 0) + return; + + DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); + + ret = rockchip_grf_field_write(dp->grf, &dp->data->lcdc_sel, ret); if (ret != 0) DRM_DEV_ERROR(dp->dev, "Could not write to GRF: %d\n", ret); @@ -448,16 +465,12 @@ static DEFINE_RUNTIME_DEV_PM_OPS(rockchip_dp_pm_ops, rockchip_dp_suspend, rockchip_dp_resume, NULL); static const struct rockchip_dp_chip_data rk3399_edp = { - .lcdsel_grf_reg = RK3399_GRF_SOC_CON20, - .lcdsel_big = HIWORD_UPDATE(0, RK3399_EDP_LCDC_SEL), - .lcdsel_lit = HIWORD_UPDATE(RK3399_EDP_LCDC_SEL, RK3399_EDP_LCDC_SEL), + .lcdc_sel = GRF_REG_FIELD(0x6250, 5, 5), .chip_type = RK3399_EDP, }; static const struct rockchip_dp_chip_data rk3288_dp = { - .lcdsel_grf_reg = RK3288_GRF_SOC_CON6, - .lcdsel_big = HIWORD_UPDATE(0, RK3288_EDP_LCDC_SEL), - .lcdsel_lit = HIWORD_UPDATE(RK3288_EDP_LCDC_SEL, RK3288_EDP_LCDC_SEL), + .lcdc_sel = GRF_REG_FIELD(0x025c, 5, 5), .chip_type = RK3288_DP, }; -- 2.51.0 From 718b3bb9c0ab87bc90914799e6999bf4b1ecc67b Mon Sep 17 00:00:00 2001 From: Damon Ding Date: Mon, 24 Feb 2025 16:13:12 +0800 Subject: [PATCH 03/16] drm/rockchip: analogix_dp: Expand device data to support multiple edp display There are two main modifications: one is expanding struct rockchip_dp_chip_data to an array, and the other is adding &rockchip_dp_chip_data.reg to separate different edp devices. Signed-off-by: Damon Ding Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250224081325.96724-3-damon.ding@rock-chips.com --- .../gpu/drm/rockchip/analogix_dp-rockchip.c | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index 0d93df6b5144..a8265a1bf9ff 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -52,10 +52,12 @@ struct rockchip_grf_reg_field { * struct rockchip_dp_chip_data - splite the grf setting of kind of chips * @lcdc_sel: grf register field of lcdc_sel * @chip_type: specific chip type + * @reg: register base address */ struct rockchip_dp_chip_data { const struct rockchip_grf_reg_field lcdc_sel; u32 chip_type; + u32 reg; }; struct rockchip_dp_device { @@ -396,6 +398,8 @@ static int rockchip_dp_probe(struct platform_device *pdev) const struct rockchip_dp_chip_data *dp_data; struct drm_panel *panel = NULL; struct rockchip_dp_device *dp; + struct resource *res; + int i; int ret; dp_data = of_device_get_match_data(dev); @@ -410,9 +414,24 @@ static int rockchip_dp_probe(struct platform_device *pdev) if (!dp) return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + i = 0; + while (dp_data[i].reg) { + if (dp_data[i].reg == res->start) { + dp->data = &dp_data[i]; + break; + } + + i++; + } + + if (!dp->data) + return dev_err_probe(dev, -EINVAL, "no chip-data for %s node\n", + dev->of_node->name); + dp->dev = dev; dp->adp = ERR_PTR(-ENODEV); - dp->data = dp_data; dp->plat_data.panel = panel; dp->plat_data.dev_type = dp->data->chip_type; dp->plat_data.power_on = rockchip_dp_poweron; @@ -464,14 +483,22 @@ static int rockchip_dp_resume(struct device *dev) static DEFINE_RUNTIME_DEV_PM_OPS(rockchip_dp_pm_ops, rockchip_dp_suspend, rockchip_dp_resume, NULL); -static const struct rockchip_dp_chip_data rk3399_edp = { - .lcdc_sel = GRF_REG_FIELD(0x6250, 5, 5), - .chip_type = RK3399_EDP, +static const struct rockchip_dp_chip_data rk3399_edp[] = { + { + .lcdc_sel = GRF_REG_FIELD(0x6250, 5, 5), + .chip_type = RK3399_EDP, + .reg = 0xff970000, + }, + { /* sentinel */ } }; -static const struct rockchip_dp_chip_data rk3288_dp = { - .lcdc_sel = GRF_REG_FIELD(0x025c, 5, 5), - .chip_type = RK3288_DP, +static const struct rockchip_dp_chip_data rk3288_dp[] = { + { + .lcdc_sel = GRF_REG_FIELD(0x025c, 5, 5), + .chip_type = RK3288_DP, + .reg = 0xff970000, + }, + { /* sentinel */ } }; static const struct of_device_id rockchip_dp_dt_ids[] = { -- 2.51.0 From f8dd7fc9ba88bc4a6ea85269287a51fb756440e2 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sun, 23 Feb 2025 11:31:37 +0200 Subject: [PATCH 04/16] drm/rockchip: vop2: Improve display modes handling on RK3588 HDMI1 The RK3588 specific implementation is currently quite limited in terms of handling the full range of display modes supported by the connected screens, e.g. 2560x1440@75Hz, 2048x1152@60Hz, 1024x768@60Hz are just a few of them. Additionally, it doesn't cope well with non-integer refresh rates like 59.94, 29.97, 23.98, etc. Make use of HDMI1 PHY PLL as a more accurate DCLK source to handle all display modes up to 4K@60Hz. Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250223-vop2-hdmi1-disp-modes-v2-1-f4cec5e06fbe@collabora.com --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index afc946ead870..14e039e9143a 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -216,6 +216,7 @@ struct vop2 { struct clk *aclk; struct clk *pclk; struct clk *pll_hdmiphy0; + struct clk *pll_hdmiphy1; /* optional internal rgb encoder */ struct rockchip_rgb *rgb; @@ -2270,11 +2271,14 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, * Switch to HDMI PHY PLL as DCLK source for display modes up * to 4K@60Hz, if available, otherwise keep using the system CRU. */ - if (vop2->pll_hdmiphy0 && clock <= VOP2_MAX_DCLK_RATE) { + if ((vop2->pll_hdmiphy0 || vop2->pll_hdmiphy1) && clock <= VOP2_MAX_DCLK_RATE) { drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI0) { + if (!vop2->pll_hdmiphy0) + break; + if (!vp->dclk_src) vp->dclk_src = clk_get_parent(vp->dclk); @@ -2284,6 +2288,20 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, "Could not switch to HDMI0 PHY PLL: %d\n", ret); break; } + + if (rkencoder->crtc_endpoint_id == ROCKCHIP_VOP2_EP_HDMI1) { + if (!vop2->pll_hdmiphy1) + break; + + if (!vp->dclk_src) + vp->dclk_src = clk_get_parent(vp->dclk); + + ret = clk_set_parent(vp->dclk, vop2->pll_hdmiphy1); + if (ret < 0) + drm_warn(vop2->drm, + "Could not switch to HDMI1 PHY PLL: %d\n", ret); + break; + } } } @@ -3733,6 +3751,11 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) return PTR_ERR(vop2->pll_hdmiphy0); } + vop2->pll_hdmiphy1 = devm_clk_get_optional(vop2->dev, "pll_hdmiphy1"); + if (IS_ERR(vop2->pll_hdmiphy1)) + return dev_err_probe(drm->dev, PTR_ERR(vop2->pll_hdmiphy1), + "failed to get pll_hdmiphy1\n"); + vop2->irq = platform_get_irq(pdev, 0); if (vop2->irq < 0) { drm_err(vop2->drm, "cannot find irq for vop2\n"); -- 2.51.0 From b06d1ef3355571383cdb463cf0195b7a02efdfbf Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Sun, 23 Feb 2025 11:31:38 +0200 Subject: [PATCH 05/16] drm/rockchip: vop2: Consistently use dev_err_probe() Replace drm_err() calls in vop2_bind() and vop2_create_crtcs() with dev_err_probe(), to simplify error handling and improve consistency. Additionally, ensure the already existing dev_err_probe() invocations pass drm->dev instead of dev as their first argument, so that we get the actual reason in case of -EPROBE_DEFER errors: platform display-subsystem: deferred probe pending: (reason unknown) vs. platform display-subsystem: deferred probe pending: rockchip-drm: While at it, add the missing '\n' to some of the message strings. Signed-off-by: Cristian Ciocaltea Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250223-vop2-hdmi1-disp-modes-v2-2-f4cec5e06fbe@collabora.com --- drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 83 +++++++++----------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c index 14e039e9143a..7b893b4447b6 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -3273,10 +3273,9 @@ static int vop2_create_crtcs(struct vop2 *vop2) snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id); vp->dclk = devm_clk_get(vop2->dev, dclk_name); - if (IS_ERR(vp->dclk)) { - drm_err(vop2->drm, "failed to get %s\n", dclk_name); - return PTR_ERR(vp->dclk); - } + if (IS_ERR(vp->dclk)) + return dev_err_probe(drm->dev, PTR_ERR(vp->dclk), + "failed to get %s\n", dclk_name); np = of_graph_get_remote_node(dev->of_node, i, -1); if (!np) { @@ -3286,11 +3285,9 @@ static int vop2_create_crtcs(struct vop2 *vop2) of_node_put(np); port = of_graph_get_port_by_id(dev->of_node, i); - if (!port) { - drm_err(vop2->drm, "no port node found for video_port%d\n", i); - return -ENOENT; - } - + if (!port) + return dev_err_probe(drm->dev, -ENOENT, + "no port node found for video_port%d\n", i); vp->crtc.port = port; nvps++; } @@ -3330,11 +3327,9 @@ static int vop2_create_crtcs(struct vop2 *vop2) possible_crtcs = (1 << nvps) - 1; ret = vop2_plane_init(vop2, win, possible_crtcs); - if (ret) { - drm_err(vop2->drm, "failed to init plane %s: %d\n", - win->data->name, ret); - return ret; - } + if (ret) + return dev_err_probe(drm->dev, ret, "failed to init plane %s\n", + win->data->name); } for (i = 0; i < vop2_data->nr_vps; i++) { @@ -3348,10 +3343,9 @@ static int vop2_create_crtcs(struct vop2 *vop2) ret = drm_crtc_init_with_planes(drm, &vp->crtc, plane, NULL, &vop2_crtc_funcs, "video_port%d", vp->id); - if (ret) { - drm_err(vop2->drm, "crtc init for video_port%d failed\n", i); - return ret; - } + if (ret) + return dev_err_probe(drm->dev, ret, + "crtc init for video_port%d failed\n", i); drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs); if (vop2->lut_regs) { @@ -3678,10 +3672,9 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) dev_set_drvdata(dev, vop2); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vop"); - if (!res) { - drm_err(vop2->drm, "failed to get vop2 register byname\n"); - return -EINVAL; - } + if (!res) + return dev_err_probe(drm->dev, -EINVAL, + "failed to get vop2 register byname\n"); vop2->res = res; vop2->regs = devm_ioremap_resource(dev, res); @@ -3706,50 +3699,50 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) if (vop2_data->feature & VOP2_FEATURE_HAS_SYS_GRF) { vop2->sys_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); if (IS_ERR(vop2->sys_grf)) - return dev_err_probe(dev, PTR_ERR(vop2->sys_grf), "cannot get sys_grf"); + return dev_err_probe(drm->dev, PTR_ERR(vop2->sys_grf), + "cannot get sys_grf\n"); } if (vop2_data->feature & VOP2_FEATURE_HAS_VOP_GRF) { vop2->vop_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vop-grf"); if (IS_ERR(vop2->vop_grf)) - return dev_err_probe(dev, PTR_ERR(vop2->vop_grf), "cannot get vop_grf"); + return dev_err_probe(drm->dev, PTR_ERR(vop2->vop_grf), + "cannot get vop_grf\n"); } if (vop2_data->feature & VOP2_FEATURE_HAS_VO1_GRF) { vop2->vo1_grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,vo1-grf"); if (IS_ERR(vop2->vo1_grf)) - return dev_err_probe(dev, PTR_ERR(vop2->vo1_grf), "cannot get vo1_grf"); + return dev_err_probe(drm->dev, PTR_ERR(vop2->vo1_grf), + "cannot get vo1_grf\n"); } if (vop2_data->feature & VOP2_FEATURE_HAS_SYS_PMU) { vop2->sys_pmu = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,pmu"); if (IS_ERR(vop2->sys_pmu)) - return dev_err_probe(dev, PTR_ERR(vop2->sys_pmu), "cannot get sys_pmu"); + return dev_err_probe(drm->dev, PTR_ERR(vop2->sys_pmu), + "cannot get sys_pmu\n"); } vop2->hclk = devm_clk_get(vop2->dev, "hclk"); - if (IS_ERR(vop2->hclk)) { - drm_err(vop2->drm, "failed to get hclk source\n"); - return PTR_ERR(vop2->hclk); - } + if (IS_ERR(vop2->hclk)) + return dev_err_probe(drm->dev, PTR_ERR(vop2->hclk), + "failed to get hclk source\n"); vop2->aclk = devm_clk_get(vop2->dev, "aclk"); - if (IS_ERR(vop2->aclk)) { - drm_err(vop2->drm, "failed to get aclk source\n"); - return PTR_ERR(vop2->aclk); - } + if (IS_ERR(vop2->aclk)) + return dev_err_probe(drm->dev, PTR_ERR(vop2->aclk), + "failed to get aclk source\n"); vop2->pclk = devm_clk_get_optional(vop2->dev, "pclk_vop"); - if (IS_ERR(vop2->pclk)) { - drm_err(vop2->drm, "failed to get pclk source\n"); - return PTR_ERR(vop2->pclk); - } + if (IS_ERR(vop2->pclk)) + return dev_err_probe(drm->dev, PTR_ERR(vop2->pclk), + "failed to get pclk source\n"); vop2->pll_hdmiphy0 = devm_clk_get_optional(vop2->dev, "pll_hdmiphy0"); - if (IS_ERR(vop2->pll_hdmiphy0)) { - drm_err(vop2->drm, "failed to get pll_hdmiphy0\n"); - return PTR_ERR(vop2->pll_hdmiphy0); - } + if (IS_ERR(vop2->pll_hdmiphy0)) + return dev_err_probe(drm->dev, PTR_ERR(vop2->pll_hdmiphy0), + "failed to get pll_hdmiphy0\n"); vop2->pll_hdmiphy1 = devm_clk_get_optional(vop2->dev, "pll_hdmiphy1"); if (IS_ERR(vop2->pll_hdmiphy1)) @@ -3757,10 +3750,8 @@ static int vop2_bind(struct device *dev, struct device *master, void *data) "failed to get pll_hdmiphy1\n"); vop2->irq = platform_get_irq(pdev, 0); - if (vop2->irq < 0) { - drm_err(vop2->drm, "cannot find irq for vop2\n"); - return vop2->irq; - } + if (vop2->irq < 0) + return dev_err_probe(drm->dev, vop2->irq, "cannot find irq for vop2\n"); mutex_init(&vop2->vop2_lock); -- 2.51.0 From a6ba2dad0aa4f623ab0def8b6e6888ac00639055 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Fri, 21 Feb 2025 00:41:40 +0100 Subject: [PATCH 06/16] drivers: base: component: add function to query the bound status The component helpers already expose the bound status in debugfs, but at times it might be necessary to also check that state in the kernel and act differently depending on the result. For example the shutdown handler of a drm-driver might need to stop a whole output pipeline if the drm device is up and running, but may run into problems if that drm-device has never been set up before, for example because the binding deferred. So add a little helper that returns the bound status for a componet device. Acked-by: Greg Kroah-Hartman Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250220234141.2788785-2-heiko@sntech.de --- drivers/base/component.c | 14 ++++++++++++++ include/linux/component.h | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/base/component.c b/drivers/base/component.c index 741497324d78..d63e01f4851d 100644 --- a/drivers/base/component.c +++ b/drivers/base/component.c @@ -569,6 +569,20 @@ void component_master_del(struct device *parent, } EXPORT_SYMBOL_GPL(component_master_del); +bool component_master_is_bound(struct device *parent, + const struct component_master_ops *ops) +{ + struct aggregate_device *adev; + + guard(mutex)(&component_mutex); + adev = __aggregate_find(parent, ops); + if (!adev) + return 0; + + return adev->bound; +} +EXPORT_SYMBOL_GPL(component_master_is_bound); + static void component_unbind(struct component *component, struct aggregate_device *adev, void *data) { diff --git a/include/linux/component.h b/include/linux/component.h index df4aa75c9e7c..9d6c66401280 100644 --- a/include/linux/component.h +++ b/include/linux/component.h @@ -3,7 +3,7 @@ #define COMPONENT_H #include - +#include struct device; @@ -90,6 +90,8 @@ int component_compare_dev_name(struct device *dev, void *data); void component_master_del(struct device *, const struct component_master_ops *); +bool component_master_is_bound(struct device *parent, + const struct component_master_ops *ops); struct component_match; -- 2.51.0 From 4444e4d789d64f053435713e5984f0ef31a7633b Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Fri, 21 Feb 2025 00:41:41 +0100 Subject: [PATCH 07/16] drm/rockchip: Fix shutdown when no drm-device is set up When the drm-driver probes, it mainly creates the component device, where all the sub-drivers (vops, hdmi, etc) hook into. This will cause the shutdown handler to get called on shutdown, even though the drm-device might not have been set up, or the component bind might have failed. So use the new component helper to check whether the drm-device is up and only then call the drm-atomic helper to release all the drm magic. This prevents failures when the drm-device is never set, or has been freed up already for example by a probe-defer during the component bind. Reviewed-by: Nicolas Frattaroli Tested-by: Nicolas Frattaroli Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250220234141.2788785-3-heiko@sntech.de --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 439edc165ff6..180fad5d49ad 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -484,9 +484,11 @@ static void rockchip_drm_platform_remove(struct platform_device *pdev) static void rockchip_drm_platform_shutdown(struct platform_device *pdev) { - struct drm_device *drm = platform_get_drvdata(pdev); + if (component_master_is_bound(&pdev->dev, &rockchip_drm_ops)) { + struct drm_device *drm = platform_get_drvdata(pdev); - drm_atomic_helper_shutdown(drm); + drm_atomic_helper_shutdown(drm); + } } static const struct of_device_id rockchip_drm_dt_ids[] = { -- 2.51.0 From cd740b873f8f6f5f4558723241ba9c09eb36d0ba Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 26 Feb 2025 08:18:10 -0800 Subject: [PATCH 08/16] accel/amdxdna: Check interrupt register before mailbox_rx_worker exits There is a timeout failure been found during stress tests. If the firmware generates a mailbox response right after driver clears the mailbox channel interrupt register, the hardware will not generate an interrupt for the response. This causes the unexpected mailbox command timeout. To handle this failure, driver checks the interrupt register before exiting mailbox_rx_worker(). If there is a new response, driver goes back to process it. Signed-off-by: Lizhi Hou Reviewed-by: Jacek Lawrynowicz Signed-off-by: Mario Limonciello Link: https://patchwork.freedesktop.org/patch/msgid/20250226161810.4188334-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_mailbox.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index aa07e67400ef..da1ac89bb78f 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -349,8 +349,6 @@ static irqreturn_t mailbox_irq_handler(int irq, void *p) trace_mbox_irq_handle(MAILBOX_NAME, irq); /* Schedule a rx_work to call the callback functions */ queue_work(mb_chann->work_q, &mb_chann->rx_work); - /* Clear IOHUB register */ - mailbox_reg_write(mb_chann, mb_chann->iohub_int_addr, 0); return IRQ_HANDLED; } @@ -367,6 +365,9 @@ static void mailbox_rx_worker(struct work_struct *rx_work) return; } +again: + mailbox_reg_write(mb_chann, mb_chann->iohub_int_addr, 0); + while (1) { /* * If return is 0, keep consuming next message, until there is @@ -380,10 +381,18 @@ static void mailbox_rx_worker(struct work_struct *rx_work) if (unlikely(ret)) { MB_ERR(mb_chann, "Unexpected ret %d, disable irq", ret); WRITE_ONCE(mb_chann->bad_state, true); - disable_irq(mb_chann->msix_irq); - break; + return; } } + + /* + * The hardware will not generate interrupt if firmware creates a new + * response right after driver clears interrupt register. Check + * the interrupt register to make sure there is not any new response + * before exiting. + */ + if (mailbox_reg_read(mb_chann, mb_chann->iohub_int_addr)) + goto again; } int xdna_mailbox_send_msg(struct mailbox_channel *mb_chann, -- 2.51.0 From 77f183d151bda791ac26ee479b6542ccd35acdac Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Wed, 12 Feb 2025 19:31:26 +1030 Subject: [PATCH 09/16] drm/nouveau: Avoid multiple -Wflex-array-member-not-at-end warnings -Wflex-array-member-not-at-end was introduced in GCC-14, and we are getting ready to enable it, globally. So, in order to avoid ending up with flexible-array members in the middle of other structs, we use the `struct_group_tagged()` helper to separate the flexible arrays from the rest of the members in the flexible structures. We then use the newly created tagged `struct nvif_ioctl_v0_hdr` and `struct nvif_ioctl_mthd_v0_hdr` to replace the type of the objects causing trouble in multiple structures. We also want to ensure that when new members need to be added to the flexible structures, they are always included within the newly created tagged structs. For this, we use `static_assert()`. This ensures that the memory layout for both the flexible structure and the new tagged struct is the same after any changes. So, with these changes, fix the following warnings: drivers/gpu/drm/nouveau/nvif/object.c:60:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nvif/object.c:233:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nvif/object.c:214:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nvif/object.c:152:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nvif/object.c:138:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nvif/object.c:104:38: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nouveau_svm.c:83:35: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] drivers/gpu/drm/nouveau/nouveau_svm.c:82:30: warning: structure containing a flexible array member is not at the end of another structure [-Wflex-array-member-not-at-end] Signed-off-by: Gustavo A. R. Silva Acked-by: Dave Airlie Signed-off-by: Danilo Krummrich Link: https://patchwork.freedesktop.org/patch/msgid/Z6xjZhHxRp4Bu_SX@kspp --- drivers/gpu/drm/nouveau/include/nvif/ioctl.h | 32 +++++++++++++------- drivers/gpu/drm/nouveau/nouveau_svm.c | 4 +-- drivers/gpu/drm/nouveau/nvif/object.c | 12 ++++---- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvif/ioctl.h b/drivers/gpu/drm/nouveau/include/nvif/ioctl.h index e825c8a1d9ca..00015412cb3e 100644 --- a/drivers/gpu/drm/nouveau/include/nvif/ioctl.h +++ b/drivers/gpu/drm/nouveau/include/nvif/ioctl.h @@ -3,25 +3,30 @@ #define __NVIF_IOCTL_H__ struct nvif_ioctl_v0 { - __u8 version; + /* New members MUST be added within the struct_group() macro below. */ + struct_group_tagged(nvif_ioctl_v0_hdr, __hdr, + __u8 version; #define NVIF_IOCTL_V0_SCLASS 0x01 #define NVIF_IOCTL_V0_NEW 0x02 #define NVIF_IOCTL_V0_DEL 0x03 #define NVIF_IOCTL_V0_MTHD 0x04 #define NVIF_IOCTL_V0_MAP 0x07 #define NVIF_IOCTL_V0_UNMAP 0x08 - __u8 type; - __u8 pad02[4]; + __u8 type; + __u8 pad02[4]; #define NVIF_IOCTL_V0_OWNER_NVIF 0x00 #define NVIF_IOCTL_V0_OWNER_ANY 0xff - __u8 owner; + __u8 owner; #define NVIF_IOCTL_V0_ROUTE_NVIF 0x00 #define NVIF_IOCTL_V0_ROUTE_HIDDEN 0xff - __u8 route; - __u64 token; - __u64 object; + __u8 route; + __u64 token; + __u64 object; + ); __u8 data[]; /* ioctl data (below) */ }; +static_assert(offsetof(struct nvif_ioctl_v0, data) == sizeof(struct nvif_ioctl_v0_hdr), + "struct member likely outside of struct_group()"); struct nvif_ioctl_sclass_v0 { /* nvif_ioctl ... */ @@ -51,12 +56,17 @@ struct nvif_ioctl_del { }; struct nvif_ioctl_mthd_v0 { - /* nvif_ioctl ... */ - __u8 version; - __u8 method; - __u8 pad02[6]; + /* New members MUST be added within the struct_group() macro below. */ + struct_group_tagged(nvif_ioctl_mthd_v0_hdr, __hdr, + /* nvif_ioctl ... */ + __u8 version; + __u8 method; + __u8 pad02[6]; + ); __u8 data[]; /* method data (class.h) */ }; +static_assert(offsetof(struct nvif_ioctl_mthd_v0, data) == sizeof(struct nvif_ioctl_mthd_v0_hdr), + "struct member likely outside of struct_group()"); struct nvif_ioctl_map_v0 { /* nvif_ioctl ... */ diff --git a/drivers/gpu/drm/nouveau/nouveau_svm.c b/drivers/gpu/drm/nouveau/nouveau_svm.c index 8ea98f06d39a..825c867eba7c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_svm.c +++ b/drivers/gpu/drm/nouveau/nouveau_svm.c @@ -79,8 +79,8 @@ struct nouveau_svm { #define SVM_ERR(s,f,a...) NV_WARN((s)->drm, "svm: "f"\n", ##a) struct nouveau_pfnmap_args { - struct nvif_ioctl_v0 i; - struct nvif_ioctl_mthd_v0 m; + struct nvif_ioctl_v0_hdr i; + struct nvif_ioctl_mthd_v0_hdr m; struct nvif_vmm_pfnmap_v0 p; }; diff --git a/drivers/gpu/drm/nouveau/nvif/object.c b/drivers/gpu/drm/nouveau/nvif/object.c index 0b87278ac0f8..70af63d70976 100644 --- a/drivers/gpu/drm/nouveau/nvif/object.c +++ b/drivers/gpu/drm/nouveau/nvif/object.c @@ -57,7 +57,7 @@ int nvif_object_sclass_get(struct nvif_object *object, struct nvif_sclass **psclass) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_sclass_v0 sclass; } *args = NULL; int ret, cnt = 0, i; @@ -101,7 +101,7 @@ int nvif_object_mthd(struct nvif_object *object, u32 mthd, void *data, u32 size) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_mthd_v0 mthd; } *args; u32 args_size; @@ -135,7 +135,7 @@ void nvif_object_unmap_handle(struct nvif_object *object) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_unmap unmap; } args = { .ioctl.type = NVIF_IOCTL_V0_UNMAP, @@ -149,7 +149,7 @@ nvif_object_map_handle(struct nvif_object *object, void *argv, u32 argc, u64 *handle, u64 *length) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_map_v0 map; } *args; u32 argn = sizeof(*args) + argc; @@ -211,7 +211,7 @@ void nvif_object_dtor(struct nvif_object *object) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_del del; } args = { .ioctl.type = NVIF_IOCTL_V0_DEL, @@ -230,7 +230,7 @@ nvif_object_ctor(struct nvif_object *parent, const char *name, u32 handle, s32 oclass, void *data, u32 size, struct nvif_object *object) { struct { - struct nvif_ioctl_v0 ioctl; + struct nvif_ioctl_v0_hdr ioctl; struct nvif_ioctl_new_v0 new; } *args; int ret = 0; -- 2.51.0 From 7a4c93cadd58728cd2d2d1c63093860e4af7b160 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 15 Dec 2024 22:00:14 +0000 Subject: [PATCH 10/16] drm/vboxvideo: Remove unused hgsmi_cursor_position hgsmi_cursor_position() has been unused since 2018's commit 35f3288c453e ("staging: vboxvideo: Atomic phase 1: convert cursor to universal plane") Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Hans de Goede Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20241215220014.452537-1-linux@treblig.org --- drivers/gpu/drm/vboxvideo/hgsmi_base.c | 37 --------------------- drivers/gpu/drm/vboxvideo/vboxvideo_guest.h | 2 -- 2 files changed, 39 deletions(-) diff --git a/drivers/gpu/drm/vboxvideo/hgsmi_base.c b/drivers/gpu/drm/vboxvideo/hgsmi_base.c index 87dccaecc3e5..db994aeaa0f9 100644 --- a/drivers/gpu/drm/vboxvideo/hgsmi_base.c +++ b/drivers/gpu/drm/vboxvideo/hgsmi_base.c @@ -181,40 +181,3 @@ int hgsmi_update_pointer_shape(struct gen_pool *ctx, u32 flags, return rc; } - -/** - * hgsmi_cursor_position - Report the guest cursor position. The host may - * wish to use this information to re-position its - * own cursor (though this is currently unlikely). - * The current host cursor position is returned. - * Return: 0 or negative errno value. - * @ctx: The context containing the heap used. - * @report_position: Are we reporting a position? - * @x: Guest cursor X position. - * @y: Guest cursor Y position. - * @x_host: Host cursor X position is stored here. Optional. - * @y_host: Host cursor Y position is stored here. Optional. - */ -int hgsmi_cursor_position(struct gen_pool *ctx, bool report_position, - u32 x, u32 y, u32 *x_host, u32 *y_host) -{ - struct vbva_cursor_position *p; - - p = hgsmi_buffer_alloc(ctx, sizeof(*p), HGSMI_CH_VBVA, - VBVA_CURSOR_POSITION); - if (!p) - return -ENOMEM; - - p->report_position = report_position; - p->x = x; - p->y = y; - - hgsmi_buffer_submit(ctx, p); - - *x_host = p->x; - *y_host = p->y; - - hgsmi_buffer_free(ctx, p); - - return 0; -} diff --git a/drivers/gpu/drm/vboxvideo/vboxvideo_guest.h b/drivers/gpu/drm/vboxvideo/vboxvideo_guest.h index 55fcee3a6470..643c4448bdcb 100644 --- a/drivers/gpu/drm/vboxvideo/vboxvideo_guest.h +++ b/drivers/gpu/drm/vboxvideo/vboxvideo_guest.h @@ -34,8 +34,6 @@ int hgsmi_query_conf(struct gen_pool *ctx, u32 index, u32 *value_ret); int hgsmi_update_pointer_shape(struct gen_pool *ctx, u32 flags, u32 hot_x, u32 hot_y, u32 width, u32 height, u8 *pixels, u32 len); -int hgsmi_cursor_position(struct gen_pool *ctx, bool report_position, - u32 x, u32 y, u32 *x_host, u32 *y_host); bool vbva_enable(struct vbva_buf_ctx *vbva_ctx, struct gen_pool *ctx, struct vbva_buffer *vbva, s32 screen); -- 2.51.0 From 3cfae15302b398fd2d9884dcea0a1d2188e6513d Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 15 Dec 2024 21:47:50 +0000 Subject: [PATCH 11/16] gpu: host1x: Remove unused host1x_debug_dump_syncpts host1x_debug_dump_syncpts() has been unused since commit f0fb260a0cdb ("gpu: host1x: Implement syncpoint wait using DMA fences") Remove it. Signed-off-by: Dr. David Alan Gilbert Acked-by: Mikko Perttunen Signed-off-by: Thierry Reding Link: https://patchwork.freedesktop.org/patch/msgid/20241215214750.448209-1-linux@treblig.org --- drivers/gpu/host1x/debug.c | 9 --------- drivers/gpu/host1x/debug.h | 1 - 2 files changed, 10 deletions(-) diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index a18cc8d8caf5..6433c00d5d7e 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -216,12 +216,3 @@ void host1x_debug_dump(struct host1x *host1x) show_all(host1x, &o, true); } - -void host1x_debug_dump_syncpts(struct host1x *host1x) -{ - struct output o = { - .fn = write_to_printk - }; - - show_syncpts(host1x, &o, false); -} diff --git a/drivers/gpu/host1x/debug.h b/drivers/gpu/host1x/debug.h index 62bd8a091fa7..c43c61d876a9 100644 --- a/drivers/gpu/host1x/debug.h +++ b/drivers/gpu/host1x/debug.h @@ -41,6 +41,5 @@ extern unsigned int host1x_debug_trace_cmdbuf; void host1x_debug_init(struct host1x *host1x); void host1x_debug_deinit(struct host1x *host1x); void host1x_debug_dump(struct host1x *host1x); -void host1x_debug_dump_syncpts(struct host1x *host1x); #endif -- 2.51.0 From 049e7ac203d51fdc3a739f5f28906788e8eeea03 Mon Sep 17 00:00:00 2001 From: Kever Yang Date: Thu, 27 Feb 2025 19:19:03 +0800 Subject: [PATCH 12/16] dt-bindings: gpu: Add rockchip,rk3562-mali compatible The Rockchip RK3562 GPU is ARM Mali-G52, use the same driver with "arm,mali-bifrost". Extend the binding accordingly to allow compatible = "rockchip,rk3562-mali", "arm,mali-bifrost"; Signed-off-by: Kever Yang Acked-by: "Rob Herring (Arm)" Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20250227111913.2344207-6-kever.yang@rock-chips.com --- Documentation/devicetree/bindings/gpu/arm,mali-bifrost.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/gpu/arm,mali-bifrost.yaml b/Documentation/devicetree/bindings/gpu/arm,mali-bifrost.yaml index 735c7f06c24e..fc8e82cb28a9 100644 --- a/Documentation/devicetree/bindings/gpu/arm,mali-bifrost.yaml +++ b/Documentation/devicetree/bindings/gpu/arm,mali-bifrost.yaml @@ -25,6 +25,7 @@ properties: - renesas,r9a07g044-mali - renesas,r9a07g054-mali - rockchip,px30-mali + - rockchip,rk3562-mali - rockchip,rk3568-mali - rockchip,rk3576-mali - const: arm,mali-bifrost # Mali Bifrost GPU model/revision is fully discoverable -- 2.51.0 From 16e3bf497fb2d379f3d461fa0c85d14de0a3d183 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Thu, 26 Dec 2024 02:27:46 +0000 Subject: [PATCH 13/16] gpu: ipu-v3: ipu-ic: Remove unused ipu_ic_task_graphics_init ipu_ic_task_graphics_init() was added in 2014 by commit 1aa8ea0d2bd5 ("gpu: ipu-v3: Add Image Converter unit") but has been unused. Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20241226022752.219399-2-linux@treblig.org Signed-off-by: Dmitry Baryshkov --- drivers/gpu/ipu-v3/ipu-ic.c | 73 ------------------------------------- include/video/imx-ipu-v3.h | 4 -- 2 files changed, 77 deletions(-) diff --git a/drivers/gpu/ipu-v3/ipu-ic.c b/drivers/gpu/ipu-v3/ipu-ic.c index 846461bac70d..acd76ecc5221 100644 --- a/drivers/gpu/ipu-v3/ipu-ic.c +++ b/drivers/gpu/ipu-v3/ipu-ic.c @@ -321,79 +321,6 @@ void ipu_ic_task_disable(struct ipu_ic *ic) } EXPORT_SYMBOL_GPL(ipu_ic_task_disable); -int ipu_ic_task_graphics_init(struct ipu_ic *ic, - const struct ipu_ic_colorspace *g_in_cs, - bool galpha_en, u32 galpha, - bool colorkey_en, u32 colorkey) -{ - struct ipu_ic_priv *priv = ic->priv; - struct ipu_ic_csc csc2; - unsigned long flags; - u32 reg, ic_conf; - int ret = 0; - - if (ic->task == IC_TASK_ENCODER) - return -EINVAL; - - spin_lock_irqsave(&priv->lock, flags); - - ic_conf = ipu_ic_read(ic, IC_CONF); - - if (!(ic_conf & ic->bit->ic_conf_csc1_en)) { - struct ipu_ic_csc csc1; - - ret = ipu_ic_calc_csc(&csc1, - V4L2_YCBCR_ENC_601, - V4L2_QUANTIZATION_FULL_RANGE, - IPUV3_COLORSPACE_RGB, - V4L2_YCBCR_ENC_601, - V4L2_QUANTIZATION_FULL_RANGE, - IPUV3_COLORSPACE_RGB); - if (ret) - goto unlock; - - /* need transparent CSC1 conversion */ - ret = init_csc(ic, &csc1, 0); - if (ret) - goto unlock; - } - - ic->g_in_cs = *g_in_cs; - csc2.in_cs = ic->g_in_cs; - csc2.out_cs = ic->out_cs; - - ret = __ipu_ic_calc_csc(&csc2); - if (ret) - goto unlock; - - ret = init_csc(ic, &csc2, 1); - if (ret) - goto unlock; - - if (galpha_en) { - ic_conf |= IC_CONF_IC_GLB_LOC_A; - reg = ipu_ic_read(ic, IC_CMBP_1); - reg &= ~(0xff << ic->bit->ic_cmb_galpha_bit); - reg |= (galpha << ic->bit->ic_cmb_galpha_bit); - ipu_ic_write(ic, reg, IC_CMBP_1); - } else - ic_conf &= ~IC_CONF_IC_GLB_LOC_A; - - if (colorkey_en) { - ic_conf |= IC_CONF_KEY_COLOR_EN; - ipu_ic_write(ic, colorkey, IC_CMBP_2); - } else - ic_conf &= ~IC_CONF_KEY_COLOR_EN; - - ipu_ic_write(ic, ic_conf, IC_CONF); - - ic->graphics = true; -unlock: - spin_unlock_irqrestore(&priv->lock, flags); - return ret; -} -EXPORT_SYMBOL_GPL(ipu_ic_task_graphics_init); - int ipu_ic_task_init_rsc(struct ipu_ic *ic, const struct ipu_ic_csc *csc, int in_width, int in_height, diff --git a/include/video/imx-ipu-v3.h b/include/video/imx-ipu-v3.h index c422a403c099..0bb1d714cbf5 100644 --- a/include/video/imx-ipu-v3.h +++ b/include/video/imx-ipu-v3.h @@ -445,10 +445,6 @@ int ipu_ic_task_init_rsc(struct ipu_ic *ic, int in_width, int in_height, int out_width, int out_height, u32 rsc); -int ipu_ic_task_graphics_init(struct ipu_ic *ic, - const struct ipu_ic_colorspace *g_in_cs, - bool galpha_en, u32 galpha, - bool colorkey_en, u32 colorkey); void ipu_ic_task_enable(struct ipu_ic *ic); void ipu_ic_task_disable(struct ipu_ic *ic); int ipu_ic_task_idma_init(struct ipu_ic *ic, struct ipuv3_channel *channel, -- 2.51.0 From a52ba18c254c0a3819e632e6371554f1c6f5bd16 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Thu, 26 Dec 2024 02:27:47 +0000 Subject: [PATCH 14/16] gpu: ipu-v3: Remove unused ipu_rot_mode_to_degrees ipu_rot_mode_to_degrees() was added in 2014 by commit f835f386a119 ("gpu: ipu-v3: Add rotation mode conversion utilities") but has remained unused. Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20241226022752.219399-3-linux@treblig.org Signed-off-by: Dmitry Baryshkov --- drivers/gpu/ipu-v3/ipu-common.c | 32 -------------------------------- include/video/imx-ipu-v3.h | 2 -- 2 files changed, 34 deletions(-) diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c index 947323f4a234..a8570e1bdf28 100644 --- a/drivers/gpu/ipu-v3/ipu-common.c +++ b/drivers/gpu/ipu-v3/ipu-common.c @@ -165,38 +165,6 @@ int ipu_degrees_to_rot_mode(enum ipu_rotate_mode *mode, int degrees, } EXPORT_SYMBOL_GPL(ipu_degrees_to_rot_mode); -int ipu_rot_mode_to_degrees(int *degrees, enum ipu_rotate_mode mode, - bool hflip, bool vflip) -{ - u32 r90, vf, hf; - - r90 = ((u32)mode >> 2) & 0x1; - hf = ((u32)mode >> 1) & 0x1; - vf = ((u32)mode >> 0) & 0x1; - hf ^= (u32)hflip; - vf ^= (u32)vflip; - - switch ((enum ipu_rotate_mode)((r90 << 2) | (hf << 1) | vf)) { - case IPU_ROTATE_NONE: - *degrees = 0; - break; - case IPU_ROTATE_90_RIGHT: - *degrees = 90; - break; - case IPU_ROTATE_180: - *degrees = 180; - break; - case IPU_ROTATE_90_LEFT: - *degrees = 270; - break; - default: - return -EINVAL; - } - - return 0; -} -EXPORT_SYMBOL_GPL(ipu_rot_mode_to_degrees); - struct ipuv3_channel *ipu_idmac_get(struct ipu_soc *ipu, unsigned num) { struct ipuv3_channel *channel; diff --git a/include/video/imx-ipu-v3.h b/include/video/imx-ipu-v3.h index 0bb1d714cbf5..8870f65c9a8b 100644 --- a/include/video/imx-ipu-v3.h +++ b/include/video/imx-ipu-v3.h @@ -484,8 +484,6 @@ enum ipu_color_space ipu_drm_fourcc_to_colorspace(u32 drm_fourcc); enum ipu_color_space ipu_pixelformat_to_colorspace(u32 pixelformat); int ipu_degrees_to_rot_mode(enum ipu_rotate_mode *mode, int degrees, bool hflip, bool vflip); -int ipu_rot_mode_to_degrees(int *degrees, enum ipu_rotate_mode mode, - bool hflip, bool vflip); struct ipu_client_platformdata { int csi; -- 2.51.0 From 4f9c64e95c3510f4a5192bd401de5611c1dd5637 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Thu, 26 Dec 2024 02:27:48 +0000 Subject: [PATCH 15/16] gpu: ipu-v3: Remove unused ipu_idmac_channel_busy The last use of ipu_idmac_channel_busy() was removed in 2017 by commit eb8c88808c83 ("drm/imx: add deferred plane disabling") Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20241226022752.219399-4-linux@treblig.org Signed-off-by: Dmitry Baryshkov --- drivers/gpu/ipu-v3/ipu-common.c | 6 ------ drivers/gpu/ipu-v3/ipu-prv.h | 2 -- 2 files changed, 8 deletions(-) diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/ipu-v3/ipu-common.c index a8570e1bdf28..fa77e4e64f12 100644 --- a/drivers/gpu/ipu-v3/ipu-common.c +++ b/drivers/gpu/ipu-v3/ipu-common.c @@ -484,12 +484,6 @@ int ipu_idmac_enable_channel(struct ipuv3_channel *channel) } EXPORT_SYMBOL_GPL(ipu_idmac_enable_channel); -bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno) -{ - return (ipu_idmac_read(ipu, IDMAC_CHA_BUSY(chno)) & idma_mask(chno)); -} -EXPORT_SYMBOL_GPL(ipu_idmac_channel_busy); - int ipu_idmac_wait_busy(struct ipuv3_channel *channel, int ms) { struct ipu_soc *ipu = channel->ipu; diff --git a/drivers/gpu/ipu-v3/ipu-prv.h b/drivers/gpu/ipu-v3/ipu-prv.h index 3884acb7995a..16322b2137f8 100644 --- a/drivers/gpu/ipu-v3/ipu-prv.h +++ b/drivers/gpu/ipu-v3/ipu-prv.h @@ -216,8 +216,6 @@ void ipu_srm_dp_update(struct ipu_soc *ipu, bool sync); int ipu_module_enable(struct ipu_soc *ipu, u32 mask); int ipu_module_disable(struct ipu_soc *ipu, u32 mask); -bool ipu_idmac_channel_busy(struct ipu_soc *ipu, unsigned int chno); - int ipu_csi_init(struct ipu_soc *ipu, struct device *dev, int id, unsigned long base, u32 module, struct clk *clk_ipu); void ipu_csi_exit(struct ipu_soc *ipu, int id); -- 2.51.0 From 96e9d754b35e87a5be2de7dce3c810ffdd769c84 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Thu, 26 Dec 2024 02:27:49 +0000 Subject: [PATCH 16/16] gpu: ipu-v3: Remove unused ipu_image_convert_* functions ipu_image_convert_enum_format() and ipu_image_convert_sync() were both added in 2016 by commit cd98e85a6b78 ("gpu: ipu-v3: Add queued image conversion support") but have remained unused. Remove them. ipu_image_convert_sync() was the last user of image_convert_sync_complete(). Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20241226022752.219399-5-linux@treblig.org Signed-off-by: Dmitry Baryshkov --- drivers/gpu/ipu-v3/ipu-image-convert.c | 48 -------------------------- include/video/imx-ipu-image-convert.h | 32 ----------------- 2 files changed, 80 deletions(-) diff --git a/drivers/gpu/ipu-v3/ipu-image-convert.c b/drivers/gpu/ipu-v3/ipu-image-convert.c index 841316582ea9..3c33b4defab5 100644 --- a/drivers/gpu/ipu-v3/ipu-image-convert.c +++ b/drivers/gpu/ipu-v3/ipu-image-convert.c @@ -355,20 +355,6 @@ static void dump_format(struct ipu_image_convert_ctx *ctx, (ic_image->fmt->fourcc >> 24) & 0xff); } -int ipu_image_convert_enum_format(int index, u32 *fourcc) -{ - const struct ipu_image_pixfmt *fmt; - - if (index >= (int)ARRAY_SIZE(image_convert_formats)) - return -EINVAL; - - /* Format found */ - fmt = &image_convert_formats[index]; - *fourcc = fmt->fourcc; - return 0; -} -EXPORT_SYMBOL_GPL(ipu_image_convert_enum_format); - static void free_dma_buf(struct ipu_image_convert_priv *priv, struct ipu_image_convert_dma_buf *buf) { @@ -2437,40 +2423,6 @@ ipu_image_convert(struct ipu_soc *ipu, enum ipu_ic_task ic_task, } EXPORT_SYMBOL_GPL(ipu_image_convert); -/* "Canned" synchronous single image conversion */ -static void image_convert_sync_complete(struct ipu_image_convert_run *run, - void *data) -{ - struct completion *comp = data; - - complete(comp); -} - -int ipu_image_convert_sync(struct ipu_soc *ipu, enum ipu_ic_task ic_task, - struct ipu_image *in, struct ipu_image *out, - enum ipu_rotate_mode rot_mode) -{ - struct ipu_image_convert_run *run; - struct completion comp; - int ret; - - init_completion(&comp); - - run = ipu_image_convert(ipu, ic_task, in, out, rot_mode, - image_convert_sync_complete, &comp); - if (IS_ERR(run)) - return PTR_ERR(run); - - ret = wait_for_completion_timeout(&comp, msecs_to_jiffies(10000)); - ret = (ret == 0) ? -ETIMEDOUT : 0; - - ipu_image_convert_unprepare(run->ctx); - kfree(run); - - return ret; -} -EXPORT_SYMBOL_GPL(ipu_image_convert_sync); - int ipu_image_convert_init(struct ipu_soc *ipu, struct device *dev) { struct ipu_image_convert_priv *priv; diff --git a/include/video/imx-ipu-image-convert.h b/include/video/imx-ipu-image-convert.h index 3c71b8b94b33..003b3927ede5 100644 --- a/include/video/imx-ipu-image-convert.h +++ b/include/video/imx-ipu-image-convert.h @@ -40,19 +40,6 @@ struct ipu_image_convert_run { typedef void (*ipu_image_convert_cb_t)(struct ipu_image_convert_run *run, void *ctx); -/** - * ipu_image_convert_enum_format() - enumerate the image converter's - * supported input and output pixel formats. - * - * @index: pixel format index - * @fourcc: v4l2 fourcc for this index - * - * Returns 0 with a valid index and fills in v4l2 fourcc, -EINVAL otherwise. - * - * In V4L2, drivers can call ipu_image_enum_format() in .enum_fmt. - */ -int ipu_image_convert_enum_format(int index, u32 *fourcc); - /** * ipu_image_convert_adjust() - adjust input/output images to IPU restrictions. * @@ -176,23 +163,4 @@ ipu_image_convert(struct ipu_soc *ipu, enum ipu_ic_task ic_task, ipu_image_convert_cb_t complete, void *complete_context); -/** - * ipu_image_convert_sync() - synchronous single image conversion request - * - * @ipu: the IPU handle to use for the conversion - * @ic_task: the IC task to use for the conversion - * @in: input image format - * @out: output image format - * @rot_mode: rotation mode - * - * Carry out a single image conversion. Returns when the conversion - * completes. The input/output formats and rotation mode must already - * meet IPU retrictions. The created context is automatically unprepared - * and the run freed on return. - */ -int ipu_image_convert_sync(struct ipu_soc *ipu, enum ipu_ic_task ic_task, - struct ipu_image *in, struct ipu_image *out, - enum ipu_rotate_mode rot_mode); - - #endif /* __IMX_IPU_IMAGE_CONVERT_H__ */ -- 2.51.0