Skip to contentSkip to conversation

ASoC: tda998x: add a codec to the HDMI transmitter

PATCH Superseded Jean-Francois Moine 2 participants 5 messages Apr 29, 2015 – May 5, 2015

Subsystems (5)

v11 of 13 Superseded Latest is v13 →

lore
Share & tools
lore
#0 lore raw

v11 cover ASoC: tda998x: add a codec to the HDMI transmitter

Based on 4.1-rc1

v11:
- reduce the patch series to adding the tda998x codec only
v10:
- add the generic dt-card
- define the audio ports from a DT graph of ports (Russell King)
- reuse HDMI constants (Andrew Jackson - Jyri Sarha)
- alloc rate_constraints in codec (Jyri Sarha)
- fix bad number of channels (Jyri Sarha)
- correct codec generation from config (Russell King - Jyri Sarha)
- no module init/exit (Russell King)
v9:
- back to a TDA998x specific CODEC
- more comments
- change magic values to constants
v8:
- change some comments about the patches
v7:
- remove the change of the K predivider (Jyri Sarha)
- add S24_3LE and S32_LE as possible audio formats (Jyri Sarha)
- don't move the struct priv2 definition and use the slave encoder private data as the device private data (Russell King)
- remove the useless request_module (Russell King/Mark Brown)
- don't lock the HDMI module (Russell King)
- use platform_device_unregister to remove the codec (Russell King)
v6:
- extend the HDMI CODEC instead of using a specific CODEC
v5:
- use the TDA998x private data instead of a specific area for the CODEC interface
- the CODEC is TDA998x specific (Mark Brown)
v4:
- remove all the TDA998x specific stuff from the CODEC
- move the EDID scan from the CODEC to the TDA998x
- move the CODEC to sound/soc (Mark Brown)
- update the audio_sample_rate from the EDID (Andrew Jackson)
v3: fix bad rate (Andrew Jackson)
v2: check double stream start (Mark Brown)

Jean-Francois Moine (3):
drm/i2c: tda998x: Add support of a DT graph of ports drm/i2c: tda998x: Change drvdata for audio extension
ASoC: tda998x: add a codec to the HDMI transmitter

 .../devicetree/bindings/drm/i2c/tda998x.txt        |  51 +++++
 drivers/gpu/drm/i2c/tda998x_drv.c                  | 220 +++++++++++++++++++--
 include/sound/tda998x.h                            |  30 +++
 sound/soc/codecs/Kconfig                           |   5 +
 sound/soc/codecs/Makefile                          |   2 +
 sound/soc/codecs/tda998x.c                         | 174 ++++++++++++++++
 6 files changed, 471 insertions(+), 11 deletions(-)
 create mode 100644 include/sound/tda998x.h
 create mode 100644 sound/soc/codecs/tda998x.c
Signature · 1 line
2.1.4
#1 lore raw

PATCH v11 1/3 drm/i2c: tda998x: Add support of a DT graph of ports

Two kinds of ports may be declared in a DT graph of ports: video and audio. This patch accepts the port value from a video port as an alternative to the video-ports property.
It also accepts audio ports in the case the transmitter is not used as a slave encoder.
The new file include/sound/tda998x.h prepares to the definition of a tda998x CODEC.

---
 .../devicetree/bindings/drm/i2c/tda998x.txt        | 51 +++++++++++++
 drivers/gpu/drm/i2c/tda998x_drv.c                  | 88 +++++++++++++++++++---
 include/sound/tda998x.h                            |  8 ++
 3 files changed, 138 insertions(+), 9 deletions(-)
 create mode 100644 include/sound/tda998x.h
Documentation/devicetree/bindings/drm/i2c/tda998x.txt +51 -0
index e9e4bce..35f6a80 100644
@@ -16,6 +16,35 @@ Optional properties:
 
   - video-ports: 24 bits value which defines how the video controller
 	output is wired to the TDA998x input - default: <0x230145>
+	This property is not used when ports are defined.
+
+Optional nodes:
+
+  - port: up to three ports.
+	The ports are defined according to [1].
+
+    Video port.
+	There may be only one video port.
+	This one must contain the following property:
+
+	- port-type: must be "rgb"
+
+	and may contain the optional property:
+
+	- reg: 24 bits value which defines how the video controller
+		output is wired to the TDA998x input (video pins)
+		When absent, the default value is <0x230145>.
+
+    Audio ports.
+	There may be one or two audio ports.
+	These ones must contain the following properties:
+
+	- port-type: must be "i2s" or "spdif"
+
+	- reg: 8 bits value which defines how the audio controller
+		output is wired to the TDA998x input (audio pins)
+
+[1] Documentation/devicetree/bindings/graph.txt
 
 Example:
 
@@ -26,4 +55,26 @@ Example:
 		interrupts = <27 2>;		/* falling edge */
 		pinctrl-0 = <&pmx_camera>;
 		pinctrl-names = "default";
+
+		port@230145 {
+			port-type = "rgb";
+			reg = <0x230145>;
+			hdmi_0: endpoint {
+				remote-endpoint = <&lcd0_0>;
+			};
+		};
+		port@3 {			/* AP1 = I2S */
+			port-type = "i2s";
+			reg = <0x03>;
+			tda998x_i2s: endpoint {
+				remote-endpoint = <&audio1_i2s>;
+			};
+		};
+		port@4 {			 /* AP2 = S/PDIF */
+			port-type = "spdif";
+			reg = <0x04>;
+			tda998x_spdif: endpoint {
+				remote-endpoint = <&audio1_spdif1>;
+			};
+		};
 	};
drivers/gpu/drm/i2c/tda998x_drv.c +79 -9

115 lines not on this page · raw message

include/sound/tda998x.h new file +8 -0
new file mode 100644
index 0000000..487a809
@@ -0,0 +1,8 @@
+#ifndef SND_TDA998X_H
+#define SND_TDA998X_H
+
+struct tda998x_audio_s {
+	u8 ports[2];			/* AP value */
+	u8 port_types[2];		/* AFMT_xxx */
+};
+#endif
Signature · 1 line
2.1.4
#2 lore raw

PATCH v11 2/3 drm/i2c: tda998x: Change drvdata for audio extension

In reply to Jean-Francois Moine

The device drvdata is used for component bind, but points to the encoder/connector structure which is hidden from the slave encoder. For audio extension, the slave encoder private data must be accessible, so, this patch changes drvdata to the slave encoder private data and sets it in case of slave encoder use.

---
 drivers/gpu/drm/i2c/tda998x_drv.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
drivers/gpu/drm/i2c/tda998x_drv.c +6 -2
index a2ed391..e9b497e 100644
@@ -1448,6 +1448,8 @@ static int tda998x_encoder_init(struct i2c_client *client,
 	encoder_slave->slave_priv = priv;
 	encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;
 
+	dev_set_drvdata(&client->dev, priv);
+
 	return 0;
 }
 
@@ -1575,7 +1577,7 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data)
 	if (!priv)
 		return -ENOMEM;
 
-	dev_set_drvdata(dev, priv);
+	dev_set_drvdata(dev, &priv->base);
 
 	if (dev->of_node)
 		crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
@@ -1634,7 +1636,9 @@ err_encoder:
 static void tda998x_unbind(struct device *dev, struct device *master,
 			   void *data)
 {
-	struct tda998x_priv2 *priv = dev_get_drvdata(dev);
+	struct tda998x_priv *priv_s = dev_get_drvdata(dev);
+	struct tda998x_priv2 *priv =
+			container_of(priv_s, struct tda998x_priv2, base);
 
 	drm_connector_cleanup(&priv->connector);
 	drm_encoder_cleanup(&priv->encoder);
Signature · 1 line
2.1.4
#3 lore raw

PATCH v11 3/3 ASoC: tda998x: add a codec to the HDMI transmitter

In reply to Jean-Francois Moine

The tda998x CODEC maintains the audio format and rate constraints according to the HDMI device parameters (EDID) and sets dynamically the input ports in the TDA998x I2C driver on start/stop audio streaming.

---
 drivers/gpu/drm/i2c/tda998x_drv.c | 124 +++++++++++++++++++++++++++
 include/sound/tda998x.h           |  22 +++++
 sound/soc/codecs/Kconfig          |   5 ++
 sound/soc/codecs/Makefile         |   2 +
 sound/soc/codecs/tda998x.c        | 174 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 327 insertions(+)
 create mode 100644 sound/soc/codecs/tda998x.c
drivers/gpu/drm/i2c/tda998x_drv.c +124 -0
index e9b497e..c40b7e5 100644
@@ -755,6 +755,104 @@ tda998x_configure_audio(struct tda998x_priv *priv,
 	tda998x_write_aif(priv, p);
 }
 
+#if IS_ENABLED(CONFIG_SND_SOC_TDA998X)
+/* tda998x audio codec interface */
+
+/* return the audio parameters */
+static int tda998x_get_audio_var(struct device *dev,
+				struct tda998x_audio_s **tda998x_audio)
+{
+	struct tda998x_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->encoder->crtc
+	 || !priv->is_hdmi_sink)
+		return -ENODEV;
+
+	*tda998x_audio = &priv->audio;
+	return 0;
+}
+
+/* switch the audio port and initialize the audio parameters for streaming */
+static int tda998x_set_audio_input(struct device *dev,
+				int port_index,
+				unsigned sample_rate)
+{
+	struct tda998x_priv *priv = dev_get_drvdata(dev);
+	struct tda998x_encoder_params *p = &priv->params;
+
+	if (!priv->encoder->crtc)
+		return -ENODEV;
+
+	/* if no port, just disable the audio port */
+	if (port_index == PORT_NONE) {
+		reg_write(priv, REG_ENA_AP, 0);
+		return 0;
+	}
+
+	/* if same audio parameters, just enable the audio port */
+	if (p->audio_cfg == priv->audio.ports[port_index] &&
+	    p->audio_sample_rate == sample_rate) {
+		reg_write(priv, REG_ENA_AP, p->audio_cfg);
+		return 0;
+	}
+
+	p->audio_format = priv->audio.port_types[port_index];
+	p->audio_clk_cfg = p->audio_format == AFMT_SPDIF ? 0 : 1;
+	p->audio_cfg = priv->audio.ports[port_index];
+	p->audio_sample_rate = sample_rate;
+	tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p);
+	return 0;
+}
+
+/* get the audio capabilities from the EDID */
+static void tda998x_get_audio_caps(struct tda998x_priv *priv,
+				struct drm_connector *connector)
+{
+	u8 *eld = connector->eld;
+	u8 *sad;
+	int sad_count;
+	unsigned eld_ver, mnl;
+	u8 max_channels, rate_mask, fmt;
+
+	/* adjust the hw params from the ELD (EDID) */
+	eld_ver = eld[DRM_ELD_VER] >> DRM_ELD_VER_SHIFT;
+	if (eld_ver != 2 && eld_ver != 31)
+		return;
+
+	mnl = drm_eld_mnl(eld);
+	if (mnl > 16)
+		return;
+
+	sad_count = drm_eld_sad_count(eld);
+	sad = eld + DRM_ELD_CEA_SAD(mnl, 0);
+
+	/* Start from the basic audio settings */
+	max_channels = 1;
+	rate_mask = 0;
+	fmt = 0;
+	while (sad_count--) {
+		switch (sad[0] & 0x78) {
+		case 0x08:			/* SAD uncompressed audio */
+			if ((sad[0] & 7) > max_channels)
+				max_channels = sad[0] & 7;
+			rate_mask |= sad[1];
+			fmt |= sad[2] & 0x07;
+			break;
+		}
+		sad += 3;
+	}
+
+	priv->audio.cea_sad.channels = max_channels;
+	priv->audio.cea_sad.freq = rate_mask;
+	priv->audio.cea_sad.format = fmt;
+}
+
+static struct tda998x_ops_s tda998x_codec_ops = {
+	.get_audio_var = tda998x_get_audio_var,
+	.set_audio_input = tda998x_set_audio_input,
+};
+#endif /* SND_SOC */
+
 /* DRM encoder functions */
 
 static void tda998x_encoder_set_config(struct tda998x_priv *priv,
@@ -774,6 +872,8 @@ static void tda998x_encoder_set_config(struct tda998x_priv *priv,
 			    (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
 
 	priv->params = *p;
+	priv->audio.port_types[0] = p->audio_format;
+	priv->audio.ports[0] = p->audio_cfg;
 }
 
 static void tda998x_encoder_dpms(struct tda998x_priv *priv, int mode)
@@ -1118,6 +1218,15 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv,
 	drm_mode_connector_update_edid_property(connector, edid);
 	n = drm_add_edid_modes(connector, edid);
 	priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid);
+
+#if IS_ENABLED(CONFIG_SND_SOC_TDA998X)
+	/* set the audio parameters from the EDID */
+	if (priv->is_hdmi_sink) {
+		drm_edid_to_eld(connector, edid);
+		tda998x_get_audio_caps(priv, connector);
+	}
+#endif
+
 	kfree(edid);
 
 	return n;
@@ -1153,6 +1262,11 @@ static void tda998x_destroy(struct tda998x_priv *priv)
 		cancel_delayed_work_sync(&priv->dwork);
 	}
 
+#if IS_ENABLED(CONFIG_SND_SOC_TDA998X)
+	if (priv->audio.ports[0])
+		tda9998x_codec_unregister(&priv->hdmi->dev);
+#endif
+
 	i2c_unregister_device(priv->cec);
 }
 
@@ -1289,6 +1403,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
 	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;
 	/* CEC I2C address bound to TDA998x I2C addr by configuration pins */
@@ -1402,6 +1519,13 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
 				priv->params.audio_clk_cfg =
 					priv->params.audio_format ==
 							AFMT_SPDIF ? 0 : 1;
+
+#if IS_ENABLED(CONFIG_SND_SOC_TDA998X)
+				/* and create the audio CODEC */
+				tda9998x_codec_register(&client->dev,
+							&priv->audio,
+							&tda998x_codec_ops);
+#endif
 			}
 		} else {
 
include/sound/tda998x.h +22 -0
index 487a809..b4b747b 100644
@@ -1,8 +1,30 @@
 #ifndef SND_TDA998X_H
 #define SND_TDA998X_H
 
+#include <drm/drm_edid.h>
+
+/* port index for audio stream stop */
+#define PORT_NONE (-1)
+
 struct tda998x_audio_s {
 	u8 ports[2];			/* AP value */
 	u8 port_types[2];		/* AFMT_xxx */
+#if IS_ENABLED(CONFIG_SND_SOC_TDA998X)
+	struct cea_sad cea_sad;		/* Short Audio Descriptor */
+	void *codec_priv;
+#endif
 };
+
+struct tda998x_ops_s {
+	int (*get_audio_var)(struct device *dev,
+			struct tda998x_audio_s **tda998x_audio);
+	int (*set_audio_input)(struct device *dev,
+			int port_index,
+			unsigned sample_rate);
+};
+
+int tda9998x_codec_register(struct device *dev,
+			struct tda998x_audio_s *tda998x_audio_i,
+			struct tda998x_ops_s *tda998x_ops);
+void tda9998x_codec_unregister(struct device *dev);
 #endif
sound/soc/codecs/Kconfig +5 -0
index 061c465..eede478 100644
@@ -104,6 +104,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
 	select SND_SOC_TAS2552 if I2C
 	select SND_SOC_TAS5086 if I2C
+	select SND_SOC_TDA998X if DRM_I2C_NXP_TDA998X
 	select SND_SOC_TFA9879 if I2C
 	select SND_SOC_TLV320AIC23_I2C if I2C
 	select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
@@ -611,6 +612,10 @@ config SND_SOC_TAS5086
 	tristate "Texas Instruments TAS5086 speaker amplifier"
 	depends on I2C
 
+config SND_SOC_TDA998X
+	def_tristate y
+	depends on DRM_I2C_NXP_TDA998X
+
 config SND_SOC_TFA9879
 	tristate "NXP Semiconductors TFA9879 amplifier"
 	depends on I2C
sound/soc/codecs/Makefile +2 -0
index abe2d7e..8183474 100644
@@ -106,6 +106,7 @@ snd-soc-sta350-objs := sta350.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-tfa9879-objs := tfa9879.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -288,6 +289,7 @@ obj-$(CONFIG_SND_SOC_STA529)   += snd-soc-sta529.o
 obj-$(CONFIG_SND_SOC_STAC9766)	+= snd-soc-stac9766.o
 obj-$(CONFIG_SND_SOC_TAS2552)	+= snd-soc-tas2552.o
 obj-$(CONFIG_SND_SOC_TAS5086)	+= snd-soc-tas5086.o
+obj-$(CONFIG_SND_SOC_TDA998X)	+= snd-soc-tda998x.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)	+= snd-soc-tlv320aic23.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C)	+= snd-soc-tlv320aic23-i2c.o
sound/soc/codecs/tda998x.c new file +174 -0

174 lines not on this page · raw message

Signature · 1 line
2.1.4
maintainer
#4 lore raw

drivers/gpu/drm/i2c/tda998x_drv.c tda998x_configure_audio(struct tda998x_priv *priv, · line 810

Quoted 4 lines · Jean-Francois Moine
drivers/gpu/drm/i2c/tda998x_drv.c +4 -0
+/* get the audio capabilities from the EDID */
+static void tda998x_get_audio_caps(struct tda998x_priv *priv,
+				struct drm_connector *connector)
+{

Russell created some generic EDID handling code (sound/core: add DRM ELD helper), if at all possible I'd really like to get those patches merged and use them rather than have custom code. If they're not suitable then let's understand why.