mirror of
https://github.com/armbian/build
synced 2025-09-24 19:47:06 +07:00
RK3588 add HDMI sound, add support for OPi5 Max #7884
This commit is contained in:
@@ -0,0 +1,586 @@
|
||||
From: Detlev Casanova <detlev.casanova@collabora.com>
|
||||
Subject: [PATCH v7 1/3] drm/bridge: synopsys: Add audio support for dw-hdmi-qp
|
||||
Date: Mon, 17 Feb 2025 16:47:40 -0500
|
||||
|
||||
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 <sugar.zhang@rock-chips.com>
|
||||
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Tested-by: Quentin Schulz <quentin.schulz@cherry.de>
|
||||
Reviewed-by: Robert Foss <rfoss@kernel.org>
|
||||
Signed-off-by: Detlev Casanova <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 b281cabfe992e..7bbe39546163d 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_bridge_state *old_state)
|
||||
{
|
||||
@@ -382,6 +851,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;
|
||||
@@ -400,6 +870,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);
|
||||
}
|
||||
|
||||
@@ -455,6 +927,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);
|
||||
}
|
||||
@@ -477,6 +956,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;
|
||||
@@ -494,6 +976,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)
|
||||
@@ -603,6 +1088,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.48.1
|
||||
@@ -0,0 +1,96 @@
|
||||
From: Detlev Casanova <detlev.casanova@collabora.com>
|
||||
Subject: [PATCH v7 2/3] arm64: dts: rockchip: Add HDMI audio outputs for rk3588 SoC
|
||||
Date: Mon, 17 Feb 2025 16:47:41 -0500 [thread overview]
|
||||
|
||||
For hdmi0_sound, use the simple-audio-card driver with the hdmi0 QP node
|
||||
as CODEC and the i2s5 device as CPU.
|
||||
|
||||
Similarly for hdmi1_sound, the CODEC is the hdmi1 node and the CPU is
|
||||
i2s6, but only added in the rk3588-extra.dtsi device tree as the second
|
||||
TX HDMI port is not available on base versions of the SoC.
|
||||
|
||||
The simple-audio-card,mclk-fs value is set to 128 as it is done in
|
||||
the downstream driver.
|
||||
|
||||
The #sound-dai-cells value is set to 0 in the hdmi0 and hdmi1 nodes so
|
||||
that they can be used as audio codec nodes.
|
||||
|
||||
Tested-by: Quentin Schulz <quentin.schulz@cherry.de> # RK3588 Tiger Haikou
|
||||
Signed-off-by: Detlev Casanova <detlev.casanova@collabora.com>
|
||||
---
|
||||
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 17 +++++++++++++++++
|
||||
arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi | 17 +++++++++++++++++
|
||||
2 files changed, 34 insertions(+)
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
||||
index 8cfa30837ce72..f9f888dedd8f0 100644
|
||||
--- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
||||
+++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi
|
||||
@@ -382,6 +382,22 @@ scmi_reset: protocol@16 {
|
||||
};
|
||||
};
|
||||
|
||||
+ hdmi0_sound: hdmi0-sound {
|
||||
+ compatible = "simple-audio-card";
|
||||
+ simple-audio-card,format = "i2s";
|
||||
+ simple-audio-card,mclk-fs = <128>;
|
||||
+ simple-audio-card,name = "hdmi0";
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ simple-audio-card,codec {
|
||||
+ sound-dai = <&hdmi0>;
|
||||
+ };
|
||||
+
|
||||
+ simple-audio-card,cpu {
|
||||
+ sound-dai = <&i2s5_8ch>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
pmu-a55 {
|
||||
compatible = "arm,cortex-a55-pmu";
|
||||
interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_HIGH &ppi_partition0>;
|
||||
@@ -1394,6 +1410,7 @@ hdmi0: hdmi@fde80000 {
|
||||
reset-names = "ref", "hdp";
|
||||
rockchip,grf = <&sys_grf>;
|
||||
rockchip,vo-grf = <&vo1_grf>;
|
||||
+ #sound-dai-cells = <0>;
|
||||
status = "disabled";
|
||||
|
||||
ports {
|
||||
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
|
||||
index 111111111111..222222222222 100644
|
||||
--- a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
|
||||
+++ b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi
|
||||
@@ -27,6 +27,22 @@ hdmi_receiver_cma: hdmi-receiver-cma {
|
||||
};
|
||||
};
|
||||
|
||||
+ hdmi1_sound: hdmi1-sound {
|
||||
+ compatible = "simple-audio-card";
|
||||
+ simple-audio-card,format = "i2s";
|
||||
+ simple-audio-card,mclk-fs = <128>;
|
||||
+ simple-audio-card,name = "hdmi1";
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ simple-audio-card,codec {
|
||||
+ sound-dai = <&hdmi1>;
|
||||
+ };
|
||||
+
|
||||
+ simple-audio-card,cpu {
|
||||
+ sound-dai = <&i2s6_8ch>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
usb_host1_xhci: usb@fc400000 {
|
||||
compatible = "rockchip,rk3588-dwc3", "snps,dwc3";
|
||||
reg = <0x0 0xfc400000 0x0 0x400000>;
|
||||
@@ -165,6 +181,7 @@ hdmi1: hdmi@fdea0000 {
|
||||
reset-names = "ref", "hdp";
|
||||
rockchip,grf = <&sys_grf>;
|
||||
rockchip,vo-grf = <&vo1_grf>;
|
||||
+ #sound-dai-cells = <0>;
|
||||
status = "disabled";
|
||||
|
||||
ports {
|
||||
--
|
||||
2.48.1
|
||||
@@ -0,0 +1,62 @@
|
||||
From: Pavel Novikov <palachzzz.wl@gmail.com>
|
||||
Subject: arm64: dts: orange pi 5 max: Add HDMI second port
|
||||
|
||||
Adding second HDMI port support for Orange Pi 5 Max
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
index ce44549babf4..0afd4ec8e367 100644
|
||||
--- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
+++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
@@ -21,6 +21,17 @@ hdmi0_con_in: endpoint {
|
||||
};
|
||||
};
|
||||
};
|
||||
+
|
||||
+ hdmi1-con {
|
||||
+ compatible = "hdmi-connector";
|
||||
+ type = "a";
|
||||
+
|
||||
+ port {
|
||||
+ hdmi1_con_in: endpoint {
|
||||
+ remote-endpoint = <&hdmi1_out_con>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
};
|
||||
|
||||
&hdmi0 {
|
||||
@@ -58,3 +69,34 @@ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
|
||||
remote-endpoint = <&hdmi0_in_vp0>;
|
||||
};
|
||||
};
|
||||
+
|
||||
+&hdmi1 {
|
||||
+ pinctrl-names = "default";
|
||||
+ pinctrl-0 = <&hdmim0_tx1_cec &hdmim0_tx1_hpd
|
||||
+ &hdmim1_tx1_scl &hdmim1_tx1_sda>;
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&hdmi1_in {
|
||||
+ hdmi1_in_vp1: endpoint {
|
||||
+ remote-endpoint = <&vp1_out_hdmi1>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&hdmi1_out {
|
||||
+ hdmi1_out_con: endpoint {
|
||||
+ remote-endpoint = <&hdmi1_con_in>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&hdptxphy1 {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+
|
||||
+&vp1 {
|
||||
+ vp1_out_hdmi1: endpoint@ROCKCHIP_VOP2_EP_HDMI1 {
|
||||
+ reg = <ROCKCHIP_VOP2_EP_HDMI1>;
|
||||
+ remote-endpoint = <&hdmi1_in_vp1>;
|
||||
+ };
|
||||
+};
|
||||
@@ -0,0 +1,32 @@
|
||||
From: Pavel Novikov <palachzzz.wl@gmail.com>
|
||||
Subject: arm64: dts: orange pi 5 max: Add HDMI sound for both ports
|
||||
|
||||
Based on [PATCH v7 3/3] arm64: dts: rockchip: Enable HDMI audio outputs for Rock 5B by Detlev Casanova <detlev.casanova@collabora.com>
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
index ce44549babf4..c8e32488ebe5 100644
|
||||
--- a/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
+++ b/arch/arm64/boot/dts/rockchip/rk3588-orangepi-5-max.dts
|
||||
@@ -27,6 +27,22 @@ &hdmi0 {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
+&hdmi0_sound {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&hdmi1_sound {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&i2s5_8ch {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&i2s6_8ch {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
&hdmi0_in {
|
||||
hdmi0_in_vp0: endpoint {
|
||||
remote-endpoint = <&vp0_out_hdmi0>;
|
||||
Reference in New Issue
Block a user