The TDA998x HDMI transmitter accepts audio input from either I2S or
S/PDIF.
Theses inputs have different intrinsic constraints and these constraints
may be modified by the audio parameters of the connected video device.
The choice of I2S or S/PDIF may be the done by the user or by automatic
processing (DPCM?) at each audio starting time. This asks for a dynamic
audio input switch in the HDMI driver.
This patch series implements the TDA998x specific CODEC.
A simple function call mechanism is used for exchanges between the
CODEC and the HDMI driver.
Note: the changes in the TDA998x I2C driver are based on my previous
patch series:
http://lists.freedesktop.org/archives/dri-devel/2014-January/052837.html
- v2
- add ACLK setting and code optimization in patch 1
- from Mark Brown's remarks in patch 2:
- don't compile the codec when CONFIG_ALL_CODECS
- simplify the code about start/stop audio
- fix coding style errors
- add audio-port-names associated to audio-ports
- add audio-port-names in patch 4
- add patch 5 'adjust the audio CTS_N pre-divider from audio format'
for the Beaglebone-Black board (Jyri Sarha)
Jean-Francois Moine (5):
drm/i2c: tda998x: add a function for dynamic audio input switch
ASoC: tda998x: add a codec driver for TDA998x
ASoC: tda998x: add DT documentation
ASoC: tda998x: adjust the audio hw parameters from EDID
ASoC: tda998x: adjust the audio CTS_N pre-divider from audio format
.../devicetree/bindings/sound/tda998x.txt | 16 +
drivers/gpu/drm/i2c/tda998x_drv.c | 82 +++++-
include/drm/i2c/tda998x.h | 11 +-
sound/soc/codecs/Kconfig | 6 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tda998x.c | 325 +++++++++++++++++++++
6 files changed, 436 insertions(+), 6 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/tda998x.txt
create mode 100644 sound/soc/codecs/tda998x.c
--
1.9.rc1
When both I2S and S/PDIF are wired from the audio device to the
TDA998x, the user or some internal mechanism may choose to do audio
streaming via either inputs.
This patch adds an exported function in the TDA998x driver which
initializes the audio input parameters according to the audio
subsystem.
The audio format values in the encoder configuration interface are
changed to non null values so that the value 0 is used in the audio
function to indicate that audio streaming is stopped.
As the audio clock depends on the input type, it is set so.
Then, the configuration value audio_clk_cfg is now ignored.
Signed-off-by: Jean-Francois Moine <[email protected]>
---
drivers/gpu/drm/i2c/tda998x_drv.c | 48 ++++++++++++++++++++++++++++++++++++---
include/drm/i2c/tda998x.h | 7 ++++--
2 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index 2f97290..2643be4 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -35,6 +35,7 @@ struct tda998x_priv {
struct i2c_client *hdmi;
uint16_t rev;
uint8_t current_page;
+ u8 audio_active;
int dpms;
bool is_hdmi_sink;
u8 vip_cntrl_0;
@@ -640,12 +641,11 @@ static void
tda998x_configure_audio(struct tda998x_priv *priv,
struct drm_display_mode *mode, struct tda998x_encoder_params *p)
{
- uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv;
+ uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv, aclk;
uint32_t n;
/* Enable audio ports */
reg_write(priv, REG_ENA_AP, p->audio_cfg);
- reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg);
/* Set audio input source */
switch (p->audio_format) {
@@ -654,6 +654,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
clksel_aip = AIP_CLKSEL_AIP_SPDIF;
clksel_fs = AIP_CLKSEL_FS_FS64SPDIF;
cts_n = CTS_N_M(3) | CTS_N_K(3);
+ aclk = 0; /* no clock */
break;
case AFMT_I2S:
@@ -661,6 +662,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
clksel_aip = AIP_CLKSEL_AIP_I2S;
clksel_fs = AIP_CLKSEL_FS_ACLK;
cts_n = CTS_N_M(3) | CTS_N_K(3);
+ aclk = 1; /* clock enable */
break;
default:
@@ -672,6 +674,7 @@ tda998x_configure_audio(struct tda998x_priv *priv,
reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT |
AIP_CNTRL_0_ACR_MAN); /* auto CTS */
reg_write(priv, REG_CTS_N, cts_n);
+ reg_write(priv, REG_ENA_ACLK, aclk);
/*
* Audio input somehow depends on HDMI line rate which is
@@ -728,6 +731,37 @@ tda998x_configure_audio(struct tda998x_priv *priv,
tda998x_write_aif(priv, p);
}
+/* tda998x codec interface */
+void tda998x_audio_update(struct i2c_client *client,
+ int format,
+ int port)
+{
+ struct tda998x_priv *priv = i2c_get_clientdata(client);
+ struct tda998x_encoder_params *p = &priv->params;
+
+ /* if the audio output is active, it may be a second start or a stop */
+ if (format == 0 || priv->audio_active) {
+ if (format == 0) {
+ priv->audio_active = 0;
+ reg_write(priv, REG_ENA_AP, 0);
+ }
+ return;
+ }
+ priv->audio_active = 1;
+
+ p->audio_cfg = port;
+
+ /* don't restart audio if same input format */
+ if (format == p->audio_format) {
+ reg_write(priv, REG_ENA_AP, p->audio_cfg);
+ return;
+ }
+ p->audio_format = format;
+
+ tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p);
+}
+EXPORT_SYMBOL_GPL(tda998x_audio_update);
+
/* DRM encoder functions */
static void
@@ -750,6 +784,9 @@ tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
(p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
priv->params = *p;
+
+ if (p->audio_cfg)
+ priv->audio_active = 1;
}
static void
@@ -999,7 +1036,7 @@ tda998x_encoder_mode_set(struct drm_encoder *encoder,
tda998x_write_avi(priv, adjusted_mode);
- if (priv->params.audio_cfg)
+ if (priv->audio_active)
tda998x_configure_audio(priv, adjusted_mode,
&priv->params);
}
@@ -1239,10 +1276,15 @@ tda998x_encoder_init(struct i2c_client *client,
if (!priv)
return -ENOMEM;
+ i2c_set_clientdata(client, priv);
+
priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3);
priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1);
priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5);
+ priv->params.audio_frame[1] = 1; /* channels - 1 */
+ priv->params.audio_sample_rate = 48000; /* 48kHz */
+
priv->current_page = 0xff;
priv->hdmi = client;
priv->cec = i2c_new_dummy(client->adapter, 0x34);
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
index 3e419d9..7e4806d 100644
--- a/include/drm/i2c/tda998x.h
+++ b/include/drm/i2c/tda998x.h
@@ -20,11 +20,14 @@ struct tda998x_encoder_params {
u8 audio_frame[6];
enum {
- AFMT_SPDIF,
- AFMT_I2S
+ AFMT_I2S = 1,
+ AFMT_SPDIF
} audio_format;
unsigned audio_sample_rate;
};
+void tda998x_audio_update(struct i2c_client *client,
+ int format,
+ int port);
#endif
--
1.9.rc1
This patch adds a CODEC driver for the NXP TDA998x HDMI transmitter.
The CODEC handles both I2S and S/PDIF input and does dynamic input
switch in the TDA998x I2C driver on audio streaming start/stop.
Signed-off-by: Jean-Francois Moine <[email protected]>
---
sound/soc/codecs/Kconfig | 6 ++
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tda998x.c | 237 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 245 insertions(+)
create mode 100644 sound/soc/codecs/tda998x.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b33b45d..747e387 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -352,6 +352,12 @@ config SND_SOC_STAC9766
config SND_SOC_TAS5086
tristate
+config SND_SOC_TDA998X
+ tristate
+ depends on OF
+ default y if DRM_I2C_NXP_TDA998X=y
+ default m if DRM_I2C_NXP_TDA998X=m
+
config SND_SOC_TLV320AIC23
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index bc12676..a53d09e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -62,6 +62,7 @@ snd-soc-sta32x-objs := sta32x.o
snd-soc-sta529-objs := sta529.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-tas5086-objs := tas5086.o
+snd-soc-tda998x-objs := tda998x.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
@@ -192,6 +193,7 @@ obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o
obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o
+obj-$(CONFIG_SND_SOC_TDA998X) += snd-soc-tda998x.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
diff --git a/sound/soc/codecs/tda998x.c b/sound/soc/codecs/tda998x.c
new file mode 100644
index 0000000..585cdb6
--- /dev/null
+++ b/sound/soc/codecs/tda998x.c
@@ -0,0 +1,237 @@
+/*
+ * ALSA SoC TDA998X driver
+ *
+ * This driver is used by the NXP TDA998x HDMI transmitter.
+ *
+ * Copyright (C) 2014 Jean-Francois Moine
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/tda998x.h>
+
+#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+struct tda_priv {
+ struct i2c_client *i2c_client;
+ struct snd_soc_codec *codec;
+ u8 ports[2];
+ int dai_id;
+ u8 *eld;
+};
+
+static void tda_get_encoder(struct tda_priv *priv)
+{
+ struct snd_soc_codec *codec = priv->codec;
+ struct device_node *np;
+ struct i2c_client *i2c_client;
+ static const struct of_device_id tda_dt[] = {
+ { .compatible = "nxp,tda998x" },
+ { },
+ };
+
+ /* search the tda998x device */
+ np = of_find_matching_node_and_match(NULL, tda_dt, NULL);
+ if (!np || !of_device_is_available(np)) {
+ dev_err(codec->dev, "No tda998x in DT\n");
+ return;
+ }
+ i2c_client = of_find_i2c_device_by_node(np);
+ of_node_put(np);
+ if (!i2c_client) {
+ dev_err(codec->dev, "no tda998x i2c client\n");
+ return;
+ }
+ if (!i2c_get_clientdata(i2c_client)) {
+ dev_err(codec->dev, "tda998x not initialized\n");
+ return;
+ }
+
+ priv->i2c_client = i2c_client;
+}
+
+static int tda_start_stop(struct tda_priv *priv)
+{
+ int port;
+
+ if (!priv->i2c_client) {
+ tda_get_encoder(priv);
+ if (!priv->i2c_client)
+ return -EINVAL;
+ }
+
+ /* give the audio parameters to the HDMI encoder */
+ if (priv->dai_id == AFMT_I2S)
+ port = priv->ports[0];
+ else
+ port = priv->ports[1];
+ tda998x_audio_update(priv->i2c_client, priv->dai_id, port);
+ return 0;
+}
+
+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);
+
+ /* memorize the used DAI */
+ priv->dai_id = dai->id;
+
+ /* start the TDA998x audio */
+ return tda_start_stop(priv);
+}
+
+static void tda_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
+
+ priv->dai_id = 0; /* streaming stop */
+ tda_start_stop(priv);
+}
+
+static const struct snd_soc_dai_ops tda_ops = {
+ .startup = tda_startup,
+ .shutdown = tda_shutdown,
+};
+
+static const struct snd_soc_dai_driver tda998x_dai[] = {
+ {
+ .name = "i2s-hifi",
+ .id = AFMT_I2S,
+ .playback = {
+ .stream_name = "HDMI I2S Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .formats = TDA998X_FORMATS,
+
+ },
+ .ops = &tda_ops,
+ },
+ {
+ .name = "spdif-hifi",
+ .id = AFMT_SPDIF,
+ .playback = {
+ .stream_name = "HDMI SPDIF Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 22050,
+ .rate_max = 192000,
+ .formats = TDA998X_FORMATS,
+ },
+ .ops = &tda_ops,
+ },
+};
+
+static const struct snd_soc_dapm_widget tda_widgets[] = {
+ SND_SOC_DAPM_OUTPUT("hdmi-out"),
+};
+static const struct snd_soc_dapm_route tda_routes[] = {
+ { "hdmi-out", NULL, "HDMI I2S Playback" },
+ { "hdmi-out", NULL, "HDMI SPDIF Playback" },
+};
+
+static int tda_probe(struct snd_soc_codec *codec)
+{
+ struct tda_priv *priv;
+ struct device_node *np;
+ int i, j, ret;
+ const char *p;
+
+ priv = devm_kzalloc(codec->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ snd_soc_codec_set_drvdata(codec, priv);
+ priv->codec = codec;
+
+ /* get the audio input ports (I2s and S/PDIF) */
+ np = codec->dev->of_node;
+ for (i = 0; i < 2; i++) {
+ u32 port;
+
+ ret = of_property_read_u32_index(np, "audio-ports", i, &port);
+ if (ret) {
+ if (i == 0)
+ dev_err(codec->dev,
+ "bad or missing audio-ports\n");
+ break;
+ }
+ ret = of_property_read_string_index(np, "audio-port-names",
+ i, &p);
+ if (ret) {
+ dev_err(codec->dev,
+ "missing audio-port-names[%d]\n", i);
+ break;
+ }
+ if (strcmp(p, "i2s") == 0) {
+ j = 0;
+ } else if (strcmp(p, "spdif") == 0) {
+ j = 1;
+ } else {
+ dev_err(codec->dev,
+ "bad audio-port-names '%s'\n", p);
+ break;
+ }
+ priv->ports[j] = port;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_codec_driver soc_codec_tda998x = {
+ .probe = tda_probe,
+ .dapm_widgets = tda_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tda_widgets),
+ .dapm_routes = tda_routes,
+ .num_dapm_routes = ARRAY_SIZE(tda_routes),
+};
+
+static int tda998x_dev_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_tda998x,
+ tda998x_dai, ARRAY_SIZE(tda998x_dai));
+}
+
+static int tda998x_dev_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id tda998x_codec_ids[] = {
+ { .compatible = "nxp,tda998x-codec", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tda998x_codec_ids);
+
+static struct platform_driver tda998x_driver = {
+ .probe = tda998x_dev_probe,
+ .remove = tda998x_dev_remove,
+ .driver = {
+ .name = "tda998x-codec",
+ .owner = THIS_MODULE,
+ .of_match_table = tda998x_codec_ids,
+ },
+};
+
+module_platform_driver(tda998x_driver);
+
+MODULE_AUTHOR("Jean-Francois Moine");
+MODULE_DESCRIPTION("TDA998x codec driver");
+MODULE_LICENSE("GPL");
--
1.9.rc1
This patch adds the DT documentation of the NXP TDA998x CODEC.
Signed-off-by: Jean-Francois Moine <[email protected]>
---
Documentation/devicetree/bindings/sound/tda998x.txt | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/tda998x.txt
diff --git a/Documentation/devicetree/bindings/sound/tda998x.txt b/Documentation/devicetree/bindings/sound/tda998x.txt
new file mode 100644
index 0000000..13b73ca
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/tda998x.txt
@@ -0,0 +1,16 @@
+Device-Tree bindings for the NXP TDA998x HDMI transmitter
+
+Required properties:
+ - compatible: must be "nxp,tda998x-codec".
+ - audio-ports: one or two values corresponding to entries in
+ the audio-port-names property.
+ - audio-port-names: must contain "i2s", "spdif" entries
+ matching entries in the audio-ports property.
+
+Example node:
+
+ hdmi_codec: hdmi-codec {
+ compatible = "nxp,tda998x-codec";
+ audio-ports = <0x03>, <0x04>;
+ audio-port-names = "i2s", "spdif";
+ };
--
1.9.rc1
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 <[email protected]>
---
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(-)
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index 2643be4..92cbc40 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -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);
}
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
index 7e4806d..99387ae 100644
--- a/include/drm/i2c/tda998x.h
+++ b/include/drm/i2c/tda998x.h
@@ -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);
diff --git a/sound/soc/codecs/tda998x.c b/sound/soc/codecs/tda998x.c
index 585cdb6..7f21749 100644
--- a/sound/soc/codecs/tda998x.c
+++ b/sound/soc/codecs/tda998x.c
@@ -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)
--
1.9.rc1
In some boards, with I2S input, the NXP TDA998x HDMI transmitter did
not play audio streams with a sample width lower than S16_32.
This patch adjusts the CTS_N predivider according to the used sample
width.
Signed-off-by: Jean-Francois Moine <[email protected]>
---
drivers/gpu/drm/i2c/tda998x_drv.c | 25 +++++++++++++++++++++----
include/drm/i2c/tda998x.h | 5 ++++-
sound/soc/codecs/tda998x.c | 19 +++++++++++++++----
3 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index 92cbc40..ee17d42 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -21,6 +21,7 @@
#include <linux/module.h>
#include <linux/irq.h>
#include <sound/asoundef.h>
+#include <sound/pcm_params.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
@@ -47,6 +48,8 @@ struct tda998x_priv {
volatile int wq_edid_wait;
struct drm_encoder *encoder;
+ int audio_sample_format;
+
u8 *eld;
};
@@ -663,7 +666,18 @@ tda998x_configure_audio(struct tda998x_priv *priv,
reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
clksel_aip = AIP_CLKSEL_AIP_I2S;
clksel_fs = AIP_CLKSEL_FS_ACLK;
- cts_n = CTS_N_M(3) | CTS_N_K(3);
+ switch (priv->audio_sample_format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(1);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(2);
+ break;
+ default:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ cts_n = CTS_N_M(3) | CTS_N_K(3);
+ break;
+ }
aclk = 1; /* clock enable */
break;
@@ -744,13 +758,14 @@ EXPORT_SYMBOL_GPL(tda998x_audio_get_eld);
void tda998x_audio_update(struct i2c_client *client,
int format,
- int port)
+ int port,
+ struct snd_pcm_hw_params *params)
{
struct tda998x_priv *priv = i2c_get_clientdata(client);
struct tda998x_encoder_params *p = &priv->params;
/* if the audio output is active, it may be a second start or a stop */
- if (format == 0 || priv->audio_active) {
+ if (format == 0 || !params || priv->audio_active) {
if (format == 0) {
priv->audio_active = 0;
reg_write(priv, REG_ENA_AP, 0);
@@ -762,11 +777,13 @@ void tda998x_audio_update(struct i2c_client *client,
p->audio_cfg = port;
/* don't restart audio if same input format */
- if (format == p->audio_format) {
+ if (format == p->audio_format &&
+ params_format(params) == priv->audio_sample_format) {
reg_write(priv, REG_ENA_AP, p->audio_cfg);
return;
}
p->audio_format = format;
+ priv->audio_sample_format = params_format(params);
tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p);
}
diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h
index 99387ae..62b838f 100644
--- a/include/drm/i2c/tda998x.h
+++ b/include/drm/i2c/tda998x.h
@@ -27,8 +27,11 @@ struct tda998x_encoder_params {
unsigned audio_sample_rate;
};
+struct snd_pcm_hw_params;
+
u8 *tda998x_audio_get_eld(struct i2c_client *client);
void tda998x_audio_update(struct i2c_client *client,
int format,
- int port);
+ int port,
+ struct snd_pcm_hw_params *params);
#endif
diff --git a/sound/soc/codecs/tda998x.c b/sound/soc/codecs/tda998x.c
index 7f21749..181388d 100644
--- a/sound/soc/codecs/tda998x.c
+++ b/sound/soc/codecs/tda998x.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <sound/soc.h>
#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include <linux/of.h>
#include <linux/i2c.h>
#include <drm/drm_encoder_slave.h>
@@ -61,7 +62,8 @@ static void tda_get_encoder(struct tda_priv *priv)
priv->i2c_client = i2c_client;
}
-static int tda_start_stop(struct tda_priv *priv)
+static int tda_start_stop(struct tda_priv *priv,
+ struct snd_pcm_hw_params *params)
{
int port;
@@ -76,7 +78,7 @@ static int tda_start_stop(struct tda_priv *priv)
port = priv->ports[0];
else
port = priv->ports[1];
- tda998x_audio_update(priv->i2c_client, priv->dai_id, port);
+ tda998x_audio_update(priv->i2c_client, priv->dai_id, port, params);
return 0;
}
@@ -156,9 +158,17 @@ static int tda_startup(struct snd_pcm_substream *substream,
stream->channels_max = max_channels;
stream->formats = formats;
}
+ return 0;
+}
+
+static int tda_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
/* start the TDA998x audio */
- return tda_start_stop(priv);
+ return tda_start_stop(priv, params);
}
static void tda_shutdown(struct snd_pcm_substream *substream,
@@ -167,11 +177,12 @@ static void tda_shutdown(struct snd_pcm_substream *substream,
struct tda_priv *priv = snd_soc_codec_get_drvdata(dai->codec);
priv->dai_id = 0; /* streaming stop */
- tda_start_stop(priv);
+ tda_start_stop(priv, NULL);
}
static const struct snd_soc_dai_ops tda_ops = {
.startup = tda_startup,
+ .hw_params = tda_hw_params,
.shutdown = tda_shutdown,
};
--
1.9.rc1
On Sun, Jan 26, 2014 at 07:45:36PM +0100, Jean-Francois Moine wrote:
> +static void tda_get_encoder(struct tda_priv *priv)
> +{
> + struct snd_soc_codec *codec = priv->codec;
> + struct device_node *np;
> + struct i2c_client *i2c_client;
> + static const struct of_device_id tda_dt[] = {
> + { .compatible = "nxp,tda998x" },
> + { },
> + };
> +
> + /* search the tda998x device */
> + np = of_find_matching_node_and_match(NULL, tda_dt, NULL);
> + if (!np || !of_device_is_available(np)) {
> + dev_err(codec->dev, "No tda998x in DT\n");
> + return;
> + }
> + i2c_client = of_find_i2c_device_by_node(np);
Like I said last time I'd really expect this to look like a MFD (or
generally a proper CODEC driver) so we don't get this weird probe
ordering stuff. If you don't think that's sensible for some reason it'd
be helpful to understand why.