From 3e0cf8c18eb4e2f7ba06bfcf8dee921854ac171e Mon Sep 17 00:00:00 2001 From: Sugar Zhang Date: Fri, 15 Nov 2019 13:58:58 +0800 Subject: [PATCH] drm: bridge/dw-hdmi: add support for hdmi bitstream audio Change-Id: Ib1f6c5dba6451f3fbb029b5472dbfbf5694cff68 Signed-off-by: Sugar Zhang --- .../gpu/drm/bridge/synopsys/dw-hdmi-audio.h | 1 + .../drm/bridge/synopsys/dw-hdmi-i2s-audio.c | 135 +++++++++++++++++- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.h | 38 +++++ 4 files changed, 172 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h index 63b5756f463b..af7f39c85ba4 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h @@ -17,6 +17,7 @@ struct dw_hdmi_i2s_audio_data { void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); u8 (*read)(struct dw_hdmi *hdmi, int offset); + void (*mod)(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned int reg); }; #endif diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c index 8f9c8a6b46de..463aa3e54465 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c @@ -32,6 +32,14 @@ static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset) return audio->read(hdmi, offset); } +static inline void hdmi_update_bits(struct dw_hdmi_i2s_audio_data *audio, + u8 data, u8 mask, unsigned int reg) +{ + struct dw_hdmi *hdmi = audio->hdmi; + + audio->mod(hdmi, data, mask, reg); +} + static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, struct hdmi_codec_daifmt *fmt, struct hdmi_codec_params *hparms) @@ -41,6 +49,7 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, u8 conf0 = 0; u8 conf1 = 0; u8 inputclkfs = 0; + u8 val; /* it cares I2S only */ if ((fmt->fmt != HDMI_I2S) || @@ -49,8 +58,7 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, return -EINVAL; } - inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; - conf0 = HDMI_AUD_CONF0_I2S_ALL_ENABLE; + inputclkfs = HDMI_AUD_INPUTCLKFS_128FS; switch (hparms->sample_width) { case 16: @@ -60,6 +68,62 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, case 32: conf1 = HDMI_AUD_CONF1_WIDTH_24; break; + default: + dev_err(dev, "unsupported sample width [%d]\n", hparms->sample_width); + return -EINVAL; + } + + switch (hparms->channels) { + case 2: + conf0 = HDMI_AUD_CONF0_I2S_2CHANNEL_ENABLE; + break; + case 4: + conf0 = HDMI_AUD_CONF0_I2S_4CHANNEL_ENABLE; + break; + case 6: + conf0 = HDMI_AUD_CONF0_I2S_6CHANNEL_ENABLE; + break; + case 8: + conf0 = HDMI_AUD_CONF0_I2S_8CHANNEL_ENABLE; + break; + default: + dev_err(dev, "unsupported channels [%d]\n", hparms->channels); + return -EINVAL; + } + + /* + * dw-hdmi introduced insert_pcuv bit in version 2.10a. + * When set (1'b1), this bit enables the insertion of the PCUV + * (Parity, Channel Status, User bit and Validity) bits on the + * incoming audio stream (support limited to Linear PCM audio) + */ + val = 0; + if (hdmi_read(audio, HDMI_DESIGN_ID) >= 0x21) + val = HDMI_AUD_CONF2_INSERT_PCUV; + + /*Mask fifo empty and full int and reset fifo*/ + hdmi_update_bits(audio, + HDMI_AUD_INT_FIFO_EMPTY_MSK | + HDMI_AUD_INT_FIFO_FULL_MSK, + HDMI_AUD_INT_FIFO_EMPTY_MSK | + HDMI_AUD_INT_FIFO_FULL_MSK, HDMI_AUD_INT); + hdmi_update_bits(audio, HDMI_AUD_CONF0_SW_RESET, + HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); + hdmi_update_bits(audio, HDMI_MC_SWRSTZ_I2S_RESET_MSK, + HDMI_MC_SWRSTZ_I2S_RESET_MSK, HDMI_MC_SWRSTZ); + + switch (hparms->mode) { + case NLPCM: + hdmi_write(audio, HDMI_AUD_CONF2_NLPCM, HDMI_AUD_CONF2); + conf1 = HDMI_AUD_CONF1_WIDTH_21; + break; + case HBR: + hdmi_write(audio, HDMI_AUD_CONF2_HBR, HDMI_AUD_CONF2); + conf1 = HDMI_AUD_CONF1_WIDTH_21; + break; + default: + hdmi_write(audio, val, HDMI_AUD_CONF2); + break; } dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); @@ -68,8 +132,73 @@ static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, hdmi_write(audio, conf0, HDMI_AUD_CONF0); hdmi_write(audio, conf1, HDMI_AUD_CONF1); + val = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT0; + if (hparms->channels > 2) + val = HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_LAYOUT1; + hdmi_update_bits(audio, val, HDMI_FC_AUDSCONF_AUD_PACKET_LAYOUT_MASK, + HDMI_FC_AUDSCONF); + + switch (hparms->sample_rate) { + case 32000: + val = HDMI_FC_AUDSCHNLS_32K; + break; + case 44100: + val = HDMI_FC_AUDSCHNLS_441K; + break; + case 48000: + val = HDMI_FC_AUDSCHNLS_48K; + break; + case 88200: + val = HDMI_FC_AUDSCHNLS_882K; + break; + case 96000: + val = HDMI_FC_AUDSCHNLS_96K; + break; + case 176400: + val = HDMI_FC_AUDSCHNLS_1764K; + break; + case 192000: + val = HDMI_FC_AUDSCHNLS_192K; + break; + default: + val = HDMI_FC_AUDSCHNLS_441K; + break; + } + + /* set channel status register */ + hdmi_update_bits(audio, val, + HDMI_FC_AUDSCHNLS7_SAMPFREQ_MASK, + HDMI_FC_AUDSCHNLS7); + hdmi_write(audio, + (((u8)~val) << HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET), + HDMI_FC_AUDSCHNLS8); + + /* Refer to CEA861-E Audio infoFrame + * Set both Audio Channel Count and Audio Coding + * Type Refer to Stream Head for HDMI + */ + hdmi_update_bits(audio, + (hparms->channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET, + HDMI_FC_AUDICONF0_CC_MASK, HDMI_FC_AUDICONF0); + + /* Set both Audio Sample Size and Sample Frequency + * Refer to Stream Head for HDMI + */ + hdmi_write(audio, 0x00, HDMI_FC_AUDICONF1); + + /* Set Channel Allocation */ + hdmi_write(audio, 0x00, HDMI_FC_AUDICONF2); + + /* Set LFEPBLDOWN-MIX INH and LSV */ + hdmi_write(audio, 0x00, HDMI_FC_AUDICONF3); + dw_hdmi_audio_enable(hdmi); + hdmi_update_bits(audio, HDMI_AUD_CONF0_SW_RESET, + HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); + hdmi_update_bits(audio, HDMI_MC_SWRSTZ_I2S_RESET_MSK, + HDMI_MC_SWRSTZ_I2S_RESET_MSK, HDMI_MC_SWRSTZ); + return 0; } @@ -118,7 +247,7 @@ static int snd_dw_hdmi_probe(struct platform_device *pdev) pdata.ops = &dw_hdmi_i2s_ops; pdata.i2s = 1; - pdata.max_i2s_channels = 6; + pdata.max_i2s_channels = 8; pdata.data = audio; memset(&pdevinfo, 0, sizeof(pdevinfo)); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 26fcacf2d056..4f985ecda35c 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3816,6 +3816,7 @@ __dw_hdmi_probe(struct platform_device *pdev, audio.hdmi = hdmi; audio.write = hdmi_writeb; audio.read = hdmi_readb; + audio.mod = hdmi_modb; hdmi->enable_audio = dw_hdmi_i2s_audio_enable; hdmi->disable_audio = dw_hdmi_i2s_audio_disable; diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h index 3a8b9dc9c176..bb53a5c0d804 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h @@ -162,6 +162,15 @@ #define HDMI_FC_SPDDEVICEINF 0x1062 #define HDMI_FC_AUDSCONF 0x1063 #define HDMI_FC_AUDSSTAT 0x1064 +#define HDMI_FC_AUDSCHNLS0 0x1067 +#define HDMI_FC_AUDSCHNLS1 0x1068 +#define HDMI_FC_AUDSCHNLS2 0x1069 +#define HDMI_FC_AUDSCHNLS3 0x106a +#define HDMI_FC_AUDSCHNLS4 0x106b +#define HDMI_FC_AUDSCHNLS5 0x106c +#define HDMI_FC_AUDSCHNLS6 0x106d +#define HDMI_FC_AUDSCHNLS7 0x106e +#define HDMI_FC_AUDSCHNLS8 0x106f #define HDMI_FC_DATACH0FILL 0x1070 #define HDMI_FC_DATACH1FILL 0x1071 #define HDMI_FC_DATACH2FILL 0x1072 @@ -788,6 +797,8 @@ enum { /* HDMI_FC_AUDSCHNLS7 field values */ HDMI_FC_AUDSCHNLS7_ACCURACY_OFFSET = 4, HDMI_FC_AUDSCHNLS7_ACCURACY_MASK = 0x30, + HDMI_FC_AUDSCHNLS7_SAMPFREQ_OFFSET = 0, + HDMI_FC_AUDSCHNLS7_SAMPFREQ_MASK = 0x0f, /* HDMI_FC_AUDSCHNLS8 field values */ HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_MASK = 0xf0, @@ -795,6 +806,15 @@ enum { HDMI_FC_AUDSCHNLS8_WORDLEGNTH_MASK = 0x0f, HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET = 0, +/* HDMI_FC_AUDSCHNLS Sample Rate */ + HDMI_FC_AUDSCHNLS_32K = 0x3, + HDMI_FC_AUDSCHNLS_441K = 0x0, + HDMI_FC_AUDSCHNLS_48K = 0x2, + HDMI_FC_AUDSCHNLS_882K = 0x8, + HDMI_FC_AUDSCHNLS_96K = 0xa, + HDMI_FC_AUDSCHNLS_1764K = 0xc, + HDMI_FC_AUDSCHNLS_192K = 0xe, + /* FC_AUDSCONF field values */ HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_MASK = 0xF0, HDMI_FC_AUDSCONF_AUD_PACKET_SAMPFIT_OFFSET = 4, @@ -956,15 +976,30 @@ enum { /* AUD_CONF0 field values */ HDMI_AUD_CONF0_SW_RESET = 0x80, + HDMI_AUD_CONF0_I2S_SELECT_MASK = 0x20, + HDMI_AUD_CONF0_I2S_2CHANNEL_ENABLE = 0x21, + HDMI_AUD_CONF0_I2S_4CHANNEL_ENABLE = 0x23, + HDMI_AUD_CONF0_I2S_6CHANNEL_ENABLE = 0x27, + HDMI_AUD_CONF0_I2S_8CHANNEL_ENABLE = 0x2F, HDMI_AUD_CONF0_I2S_ALL_ENABLE = 0x2F, +/* AUD_INT field values */ + HDMI_AUD_INT_FIFO_EMPTY_MSK = BIT(3), + HDMI_AUD_INT_FIFO_FULL_MSK = BIT(2), + /* AUD_CONF1 field values */ HDMI_AUD_CONF1_MODE_I2S = 0x00, HDMI_AUD_CONF1_MODE_RIGHT_J = 0x02, HDMI_AUD_CONF1_MODE_LEFT_J = 0x04, HDMI_AUD_CONF1_WIDTH_16 = 0x10, + HDMI_AUD_CONF1_WIDTH_21 = 0x15, HDMI_AUD_CONF1_WIDTH_24 = 0x18, +/* AUD_CONF2 filed values */ + HDMI_AUD_CONF2_HBR = 0x1, + HDMI_AUD_CONF2_NLPCM = 0x2, + HDMI_AUD_CONF2_INSERT_PCUV = 0x04, + /* AUD_CTS3 field values */ HDMI_AUD_CTS3_N_SHIFT_OFFSET = 5, HDMI_AUD_CTS3_N_SHIFT_MASK = 0xe0, @@ -1135,6 +1170,9 @@ enum { HDMI_I2CM_DIV_FAST_STD_MODE = 0x8, HDMI_I2CM_DIV_FAST_MODE = 0x8, HDMI_I2CM_DIV_STD_MODE = 0, + +/* HDMI_MC_SWRSTZ filed values */ + HDMI_MC_SWRSTZ_I2S_RESET_MSK = BIT(3), }; /*