@@ -46,6 +46,8 @@ struct tda998x_priv {
wait_queue_head_t wq_edid;
volatile int wq_edid_wait;
struct drm_encoder *encoder;
+
+ u8 *eld;
};
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
@@ -732,6 +734,14 @@ tda998x_configure_audio(struct tda998x_priv *priv,
}
/* tda998x codec interface */
+u8 *tda998x_audio_get_eld(struct i2c_client *client)
+{
+ struct tda998x_priv *priv = i2c_get_clientdata(client);
+
+ return priv->eld;
+}
+EXPORT_SYMBOL_GPL(tda998x_audio_get_eld);
+
void tda998x_audio_update(struct i2c_client *client,
int format,
int port)
@@ -1186,6 +1196,11 @@ tda998x_encoder_get_modes(struct drm_encoder *encoder,
drm_mode_connector_update_edid_property(connector, edid);
n = drm_add_edid_modes(connector, edid);
priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+
+ /* keep the EDID as ELD for the audio subsystem */
+ drm_edid_to_eld(connector, edid);
+ priv->eld = connector->eld;
+
kfree(edid);
}
@@ -27,6 +27,7 @@ struct tda998x_encoder_params {
unsigned audio_sample_rate;
};
+u8 *tda998x_audio_get_eld(struct i2c_client *client);
void tda998x_audio_update(struct i2c_client *client,
int format,
int port);
@@ -84,10 +84,79 @@ static int tda_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
+ u8 *eld = NULL;
+ static unsigned rates_mask[] = {
+ SNDRV_PCM_RATE_32000,
+ SNDRV_PCM_RATE_44100,
+ SNDRV_PCM_RATE_48000,
+ SNDRV_PCM_RATE_88200,
+ SNDRV_PCM_RATE_96000,
+ SNDRV_PCM_RATE_176400,
+ SNDRV_PCM_RATE_192000,
+ };
/* memorize the used DAI */
priv->dai_id = dai->id;
+ /* get the ELD from the tda998x driver */
+ if (!priv->i2c_client)
+ tda_get_encoder(priv);
+ if (priv->i2c_client)
+ eld = tda998x_audio_get_eld(priv->i2c_client);
+
+ /* adjust the hw params from the ELD (EDID) */
+ if (eld) {
+ struct snd_soc_dai_driver *dai_drv = dai->driver;
+ struct snd_soc_pcm_stream *stream = &dai_drv->playback;
+ u8 *sad;
+ int sad_count;
+ unsigned eld_ver, mnl, rates, rate_mask, i;
+ unsigned max_channels, fmt;
+ u64 formats;
+
+ eld_ver = eld[0] >> 3;
+ if (eld_ver != 2 && eld_ver != 31)
+ return 0;
+
+ mnl = eld[4] & 0x1f;
+ if (mnl > 16)
+ return 0;
+
+ sad_count = eld[5] >> 4;
+ sad = eld + 20 + mnl;
+
+ /* Start from the basic audio settings */
+ max_channels = 2;
+ rates = 0;
+ fmt = 0;
+ while (sad_count--) {
+ switch (sad[0] & 0x78) {
+ case 0x08: /* PCM */
+ max_channels = max(max_channels, (sad[0] & 7) + 1u);
+ rates |= sad[1];
+ fmt |= sad[2] & 0x07;
+ break;
+ }
+ sad += 3;
+ }
+
+ for (rate_mask = i = 0; i < ARRAY_SIZE(rates_mask); i++)
+ if (rates & 1 << i)
+ rate_mask |= rates_mask[i];
+ formats = 0;
+ if (fmt & 1)
+ formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ if (fmt & 2)
+ formats |= SNDRV_PCM_FMTBIT_S20_3LE;
+ if (fmt & 4)
+ formats |= SNDRV_PCM_FMTBIT_S24_LE;
+
+ /* change the snd_soc_pcm_stream values of the driver */
+ stream->rates = rate_mask;
+ stream->channels_max = max_channels;
+ stream->formats = formats;
+ }
+
/* start the TDA998x audio */
return tda_start_stop(priv);
}
@@ -203,9 +272,17 @@ static const struct snd_soc_codec_driver soc_codec_tda998x = {
static int tda998x_dev_probe(struct platform_device *pdev)
{
+ struct snd_soc_dai_driver *dai_drv;
+
+ /* copy the DAI driver to a writable area */
+ dai_drv = devm_kzalloc(&pdev->dev, sizeof(tda998x_dai), GFP_KERNEL);
+ if (!dai_drv)
+ return -ENOMEM;
+ memcpy(dai_drv, tda998x_dai, sizeof(tda998x_dai));
+
return snd_soc_register_codec(&pdev->dev,
&soc_codec_tda998x,
- tda998x_dai, ARRAY_SIZE(tda998x_dai));
+ dai_drv, ARRAY_SIZE(tda998x_dai));
}
static int tda998x_dev_remove(struct platform_device *pdev)
The supported audio parameters are described in the EDID which is received by the HDMI transmitter from the connected screen. Use these ones to adjust the audio stream parameters. Signed-off-by: Jean-Francois Moine <moinejf@free.fr> --- drivers/gpu/drm/i2c/tda998x_drv.c | 15 ++++++++ include/drm/i2c/tda998x.h | 1 + sound/soc/codecs/tda998x.c | 79 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 1 deletion(-)