[alsa-devel] [PATCH v12 0/6] ASoC: tda998x: add a codec to the HDMI transmitter
Based on 4.1-rc1 As Russell King's DRM ELD helper is not yet in any repository, it is included here with a little patch.
v12: - use Russell King's DRM ELD helper (Mark Brown) 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 (6): drm/edid: add function to help find SADs sound/core: add DRM ELD helper sound/core: remove the trace from the DRM ELD helper 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 | 178 +++++++++++++++++++-- include/drm/drm_edid.h | 19 +++ include/sound/pcm_drm_eld.h | 6 + include/sound/tda998x.h | 23 +++ sound/core/Kconfig | 3 + sound/core/Makefile | 1 + sound/core/pcm_drm_eld.c | 91 +++++++++++ sound/soc/codecs/Kconfig | 6 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tda998x.c | 132 +++++++++++++++ 11 files changed, 501 insertions(+), 11 deletions(-) create mode 100644 include/sound/pcm_drm_eld.h create mode 100644 include/sound/tda998x.h create mode 100644 sound/core/pcm_drm_eld.c create mode 100644 sound/soc/codecs/tda998x.c
From: Russell King - ARM Linux linux@arm.linux.org.uk
Add a function to find the start of the SADs in the ELD. This complements the helper to retrieve the SAD count.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- include/drm/drm_edid.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index 7990501..53c53c4 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -348,6 +348,25 @@ static inline int drm_eld_mnl(const uint8_t *eld) }
/** + * drm_eld_sad - Get ELD SAD structures. + * @eld: pointer to an eld memory structure with sad_count set + */ +static inline const uint8_t *drm_eld_sad(const uint8_t *eld) +{ + unsigned int ver, mnl; + + ver = (eld[DRM_ELD_VER] & DRM_ELD_VER_MASK) >> DRM_ELD_VER_SHIFT; + if (ver != 2 && ver != 31) + return NULL; + + mnl = drm_eld_mnl(eld); + if (mnl > 16) + return NULL; + + return eld + DRM_ELD_CEA_SAD(mnl, 0); +} + +/** * drm_eld_sad_count - Get ELD SAD count. * @eld: pointer to an eld memory structure with sad_count set */
On Fri, May 08, 2015 at 09:39:33AM +0200, Jean-Francois Moine wrote:
From: Russell King - ARM Linux linux@arm.linux.org.uk
Add a function to find the start of the SADs in the ELD. This complements the helper to retrieve the SAD count.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
NAK. Where's my sign-off gone? This is unacceptable behaviour.
From: Russell King - ARM Linux linux@arm.linux.org.uk
Add a helper for the EDID like data structure, which is typically passed from a HDMI adapter to its associated audio driver. This informs the audio driver of the capabilities of the attached HDMI sink.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- include/sound/pcm_drm_eld.h | 6 +++ sound/core/Kconfig | 3 ++ sound/core/Makefile | 1 + sound/core/pcm_drm_eld.c | 92 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 include/sound/pcm_drm_eld.h create mode 100644 sound/core/pcm_drm_eld.c
diff --git a/include/sound/pcm_drm_eld.h b/include/sound/pcm_drm_eld.h new file mode 100644 index 0000000..93357b2 --- /dev/null +++ b/include/sound/pcm_drm_eld.h @@ -0,0 +1,6 @@ +#ifndef __SOUND_PCM_DRM_ELD_H +#define __SOUND_PCM_DRM_ELD_H + +int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld); + +#endif diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 313f22e..b534c8a 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -6,6 +6,9 @@ config SND_PCM tristate select SND_TIMER
+config SND_PCM_ELD + bool + config SND_DMAENGINE_PCM tristate
diff --git a/sound/core/Makefile b/sound/core/Makefile index 4daf2f5..591b491 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -13,6 +13,7 @@ snd-$(CONFIG_SND_JACK) += jack.o snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_timer.o pcm_misc.o \ pcm_memory.o memalloc.o snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o +snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o
# for trace-points CFLAGS_pcm_lib.o := -I$(src) diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c new file mode 100644 index 0000000..47d9b60 --- /dev/null +++ b/sound/core/pcm_drm_eld.c @@ -0,0 +1,92 @@ +/* + * PCM DRM helpers + * + * 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/export.h> +#include <drm/drm_edid.h> +#include <sound/pcm.h> +#include <sound/pcm_drm_eld.h> + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static int eld_limit_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + struct snd_interval *c; + unsigned int rate_mask = 7, i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) { + unsigned channels = 1 + (sad[0] & 7); + + /* + * Exclude SADs which do not include the + * requested number of channels. + */ + if (c->min <= channels) + rate_mask |= sad[1]; + } +printk("%s: r %d-%d c %d-%d m %x\n", __func__, r->min, r->max, c->min, c->max, rate_mask); + } + + return snd_interval_list(r, ARRAY_SIZE(eld_rates), eld_rates, + rate_mask); +} + +static int eld_limit_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *var = hw_param_interval(params, rule->var); + struct snd_interval t = { .min = 1, .max = 2, .integer = 1, }; + unsigned int i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) { + switch (sad[0] & 0x78) { + case 0x08: + t.max = max(t.max, (sad[0] & 7) + 1u); + break; + } + } + } + + return snd_interval_refine(var, &t); +} + +int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld) +{ + int ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + eld_limit_rates, eld, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + eld_limit_channels, eld, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, -1); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_pcm_hw_constraint_eld);
On Fri, May 08, 2015 at 10:01:05AM +0200, Jean-Francois Moine wrote:
From: Russell King - ARM Linux linux@arm.linux.org.uk
Add a helper for the EDID like data structure, which is typically passed from a HDMI adapter to its associated audio driver. This informs the audio driver of the capabilities of the attached HDMI sink.
Signed-off-by: Jean-Francois Moine moinejf@free.fr
Why are you getting rid of my Sign-off? This is just totally unacceptable behaviour. NAK.
Second reason for a NAK is that this code is not ready yet. It has issues with multiple SADs which are not trivial to resolve with ALSAs (and therefore ASoCs) current architecture for refining the parameters, and so far no one seems to be very helpful to getting that problem resolved. Hence the patch set is stalled on my side until such time that people are willing to talk about the issue.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- sound/core/pcm_drm_eld.c | 1 - 1 file changed, 1 deletion(-)
diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c index 47d9b60..a553d30 100644 --- a/sound/core/pcm_drm_eld.c +++ b/sound/core/pcm_drm_eld.c @@ -42,7 +42,6 @@ static int eld_limit_rates(struct snd_pcm_hw_params *params, if (c->min <= channels) rate_mask |= sad[1]; } -printk("%s: r %d-%d c %d-%d m %x\n", __func__, r->min, r->max, c->min, c->max, rate_mask); }
return snd_interval_list(r, ARRAY_SIZE(eld_rates), eld_rates,
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.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- .../devicetree/bindings/drm/i2c/tda998x.txt | 51 ++++++++++++ drivers/gpu/drm/i2c/tda998x_drv.c | 90 +++++++++++++++++++--- include/sound/tda998x.h | 8 ++ 3 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 include/sound/tda998x.h
diff --git a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt index e9e4bce..386b6c3 100644 --- a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt +++ b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt @@ -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 at 230145 { + port-type = "rgb"; + reg = <0x230145>; + hdmi_0: endpoint { + remote-endpoint = <&lcd0_0>; + }; + }; + port at 3 { /* AP1 = I2S */ + port-type = "i2s"; + reg = <0x03>; + tda998x_i2s: endpoint { + remote-endpoint = <&audio1_i2s>; + }; + }; + port at 4 { /* AP2 = S/PDIF */ + port-type = "spdif"; + reg = <0x04>; + tda998x_spdif: endpoint { + remote-endpoint = <&audio1_spdif1>; + }; + }; }; diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 5febffd..bcf96f7 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -27,6 +27,7 @@ #include <drm/drm_edid.h> #include <drm/drm_of.h> #include <drm/i2c/tda998x.h> +#include <sound/tda998x.h>
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
@@ -47,6 +48,8 @@ struct tda998x_priv { wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; + + struct tda998x_audio_s audio; };
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) @@ -771,6 +774,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) @@ -1227,9 +1232,57 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = {
/* I2C driver functions */
+static int tda998x_parse_ports(struct tda998x_priv *priv, + struct device_node *np) +{ + struct device_node *of_port; + const char *port_type; + int ret, audio_index, reg, afmt; + + audio_index = 0; + for_each_child_of_node(np, of_port) { + if (!of_port->name + || of_node_cmp(of_port->name, "port") != 0) + continue; + ret = of_property_read_string(of_port, "port-type", + &port_type); + if (ret < 0) + continue; + ret = of_property_read_u32(of_port, "reg", ®); + if (strcmp(port_type, "rgb") == 0) { + if (!ret) { /* video reg is optional */ + priv->vip_cntrl_0 = reg >> 16; + priv->vip_cntrl_1 = reg >> 8; + priv->vip_cntrl_2 = reg; + } + continue; + } + if (strcmp(port_type, "i2s") == 0) + afmt = AFMT_I2S; + else if (strcmp(port_type, "spdif") == 0) + afmt = AFMT_SPDIF; + else + continue; + if (ret < 0) { + dev_err(&priv->hdmi->dev, "missing reg for %s\n", + port_type); + return ret; + } + if (audio_index >= ARRAY_SIZE(priv->audio.ports)) { + dev_err(&priv->hdmi->dev, "too many audio ports\n"); + break; + } + priv->audio.ports[audio_index] = reg; + priv->audio.port_types[audio_index] = afmt; + audio_index++; + } + return 0; +} + static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; + struct device_node *of_port; u32 video; int rev_lo, rev_hi, ret; unsigned short cec_addr; @@ -1334,15 +1387,34 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
- if (!np) - return 0; /* non-DT */ - - /* get the optional video properties */ - ret = of_property_read_u32(np, "video-ports", &video); - if (ret == 0) { - priv->vip_cntrl_0 = video >> 16; - priv->vip_cntrl_1 = video >> 8; - priv->vip_cntrl_2 = video; + /* get the device tree parameters */ + if (np) { + of_port = of_get_child_by_name(np, "port"); + if (of_port) { /* graph of ports */ + of_node_put(of_port); + ret = tda998x_parse_ports(priv, np); + if (ret < 0) + goto fail; + + /* initialize the default audio configuration */ + if (priv->audio.ports[0]) { + priv->params.audio_cfg = priv->audio.ports[0]; + priv->params.audio_format = + priv->audio.port_types[0]; + priv->params.audio_clk_cfg = + priv->params.audio_format == + AFMT_SPDIF ? 0 : 1; + } + } else { + + /* optional video properties */ + ret = of_property_read_u32(np, "video-ports", &video); + if (ret == 0) { + priv->vip_cntrl_0 = video >> 16; + priv->vip_cntrl_1 = video >> 8; + priv->vip_cntrl_2 = video; + } + } }
return 0; diff --git a/include/sound/tda998x.h b/include/sound/tda998x.h new file mode 100644 index 0000000..487a809 --- /dev/null +++ b/include/sound/tda998x.h @@ -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
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.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- drivers/gpu/drm/i2c/tda998x_drv.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index bcf96f7..7a9d9a7 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -1450,6 +1450,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; }
@@ -1577,7 +1579,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); @@ -1636,7 +1638,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);
The tda998x CODEC maintains the audio constraints according to the HDMI device parameters (EDID) and sets dynamically the input ports in the TDA998x I2C driver on start/stop audio streaming.
Signed-off-by: Jean-Francois Moine moinejf@free.fr --- drivers/gpu/drm/i2c/tda998x_drv.c | 80 +++++++++++++++++++++++ include/sound/tda998x.h | 15 +++++ sound/soc/codecs/Kconfig | 6 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tda998x.c | 132 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+) create mode 100644 sound/soc/codecs/tda998x.c
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 7a9d9a7..64eddcf 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -755,6 +755,66 @@ 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 ELD to the CODEC */ +static u8 *tda998x_get_eld(struct device *dev) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + struct drm_encoder *encoder = priv->encoder; + struct drm_device *drm = encoder->dev; + struct drm_connector *connector; + + if (!priv->is_hdmi_sink + || !encoder->crtc) + return NULL; + + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (connector->encoder == encoder) + return connector->eld; + } + return NULL; +} + +/* 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; +} + +static struct tda998x_ops_s tda998x_codec_ops = { + .get_eld = tda998x_get_eld, + .set_audio_input = tda998x_set_audio_input, +}; +#endif /* SND_SOC */ + /* DRM encoder functions */
static void tda998x_encoder_set_config(struct tda998x_priv *priv, @@ -1120,6 +1180,12 @@ 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) + if (priv->is_hdmi_sink) + drm_edid_to_eld(connector, edid); +#endif + kfree(edid);
return n; @@ -1155,6 +1221,10 @@ 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); }
@@ -1291,6 +1361,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 */ @@ -1404,6 +1477,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 {
diff --git a/include/sound/tda998x.h b/include/sound/tda998x.h index 487a809..8e89db4 100644 --- a/include/sound/tda998x.h +++ b/include/sound/tda998x.h @@ -1,8 +1,23 @@ #ifndef SND_TDA998X_H #define SND_TDA998X_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 */ }; + +struct tda998x_ops_s { + u8 *(*get_eld)(struct device *dev); + 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 diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 061c465..e7a33cc 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -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,11 @@ config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C
+config SND_SOC_TDA998X + def_tristate y + select SND_PCM_ELD + depends on DRM_I2C_NXP_TDA998X + config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index abe2d7e..8183474 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -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 diff --git a/sound/soc/codecs/tda998x.c b/sound/soc/codecs/tda998x.c new file mode 100644 index 0000000..f588a75 --- /dev/null +++ b/sound/soc/codecs/tda998x.c @@ -0,0 +1,132 @@ +/* + * ALSA SoC TDA998x CODEC + * + * Copyright (C) 2015 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 <linux/of.h> +#include <linux/of_device.h> +#include <sound/pcm_drm_eld.h> +#include <drm/i2c/tda998x.h> +#include <sound/tda998x.h> + +/* functions in tda998x_drv */ +static struct tda998x_ops_s *tda998x_ops; + +static int tda998x_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u8 *eld; + + eld = tda998x_ops->get_eld(dai->dev); + if (!eld) + return -ENODEV; + return snd_pcm_hw_constraint_eld(runtime, eld); +} + +/* ask the HDMI transmitter to activate the audio input port */ +static int tda998x_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + return tda998x_ops->set_audio_input(dai->dev, dai->id, + params_rate(params)); +} + +static void tda998x_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + tda998x_ops->set_audio_input(dai->dev, PORT_NONE, 0); +} + +static const struct snd_soc_dai_ops tda998x_codec_ops = { + .startup = tda998x_codec_startup, + .hw_params = tda998x_codec_hw_params, + .shutdown = tda998x_codec_shutdown, +}; + +#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver tda998x_dai_i2s = { + .name = "i2s-hifi", + .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 = &tda998x_codec_ops, +}; + +static const struct snd_soc_dapm_widget tda998x_widgets[] = { + SND_SOC_DAPM_OUTPUT("hdmi-out"), +}; +static const struct snd_soc_dapm_route tda998x_routes[] = { + { "hdmi-out", NULL, "HDMI I2S Playback" }, + { "hdmi-out", NULL, "HDMI SPDIF Playback" }, +}; + +static struct snd_soc_codec_driver tda998x_codec_drv = { + .dapm_widgets = tda998x_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda998x_widgets), + .dapm_routes = tda998x_routes, + .num_dapm_routes = ARRAY_SIZE(tda998x_routes), + .ignore_pmdown_time = true, +}; + +int tda9998x_codec_register(struct device *dev, + struct tda998x_audio_s *tda998x_audio, + struct tda998x_ops_s *tda998x_ops_i) +{ + struct snd_soc_dai_driver *dais, *p_dai; + int i, ndais; + + tda998x_ops = tda998x_ops_i; + + /* build the DAIs */ + for (ndais = 0; ndais < ARRAY_SIZE(tda998x_audio->ports); ndais++) { + if (!tda998x_audio->ports[ndais]) + break; + } + dais = devm_kzalloc(dev, sizeof(*dais) * ndais, GFP_KERNEL); + if (!dais) + return -ENOMEM; + for (i = 0, p_dai = dais; i < ndais ; i++, p_dai++) { + memcpy(p_dai, &tda998x_dai_i2s, sizeof(*p_dai)); + p_dai->id = i; + if (tda998x_audio->port_types[i] == AFMT_SPDIF) { + p_dai->name = "spdif-hifi"; + p_dai->playback.stream_name = "HDMI SPDIF Playback"; + p_dai->playback.channels_max = 2; + p_dai->playback.rate_min = 22050; + } + } + + return snd_soc_register_codec(dev, + &tda998x_codec_drv, + dais, ndais); +} +EXPORT_SYMBOL(tda9998x_codec_register); + +void tda9998x_codec_unregister(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} +EXPORT_SYMBOL(tda9998x_codec_unregister); + +MODULE_AUTHOR("Jean-Francois Moine moinejf@free.fr"); +MODULE_DESCRIPTION("TDA998X CODEC"); +MODULE_LICENSE("GPL");
participants (2)
-
Jean-Francois Moine
-
Russell King - ARM Linux