[alsa-devel] [PATCH RFC v3 0/7] Implement generic ASoC HDMI codec and use it in tda998x
This is my third RFC patch series on the subject. This time also the tda998x driver patches are a serious attempt to come up with an initial implementation for HDMI audio ASoC support.
Russell's "drm/edid: add function to help find SADs" is here just to produce a working patch-set on top of mainline. It should be getting in trough drm-next at some point.
The binding for tda998x is taken from Jean Francois' patch series[1] on the same subject. The implementation of the of-node parsing has some minor changes from my self.
Here is what I think at least could or should still be done, but non of that stuff does not sounds critical right now.
Missing from tda998x driver side - hdmi_codec_ops audio_startup() implementation for audio abort support - multi channel audio support (I would need specs and preferably some HW to test for this).
Missing from ASoC side generic implementation: - channel_allocation handling is completely left for the video side driver, see if ASoC side could help in any way - snd_soc_jack functionality to handle hdmi cable plug/unplug events
[1] http://mailman.alsa-project.org/pipermail/alsa-devel/2015-July/095596.html
Best regards, Jyri
Jean-Francois Moine (1): drm/i2c: tda998x: Add support of a DT graph of ports
Jyri Sarha (5): ASoC: hdmi: Remove obsolete dummy HDMI codec ASoC: hdmi-codec: Add hdmi-codec for external HDMI-encoders drm/i2c: tda998x: Remove include/sound/tda998x.h and fix graph parsing drm/i2c: tda998x: Register ASoC HDMI codec for audio functionality ARM: dts: am335x-boneblack: Add HDMI audio support
Russell King - ARM Linux (1): drm/edid: add function to help find SADs, DO NOT MERGE
.../devicetree/bindings/drm/i2c/tda998x.txt | 51 +++ arch/arm/boot/dts/am335x-boneblack.dts | 90 +++- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 336 +++++++++++++-- include/drm/drm_edid.h | 19 + include/sound/hdmi-codec.h | 99 +++++ sound/soc/codecs/Kconfig | 3 +- sound/soc/codecs/Makefile | 4 +- sound/soc/codecs/hdmi-codec.c | 467 +++++++++++++++++++++ sound/soc/codecs/hdmi.c | 109 ----- 10 files changed, 1026 insertions(+), 153 deletions(-) create mode 100644 include/sound/hdmi-codec.h create mode 100644 sound/soc/codecs/hdmi-codec.c delete mode 100644 sound/soc/codecs/hdmi.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: Russell King rmk+kernel@arm.linux.org.uk
This should already be coming in from drm-next.
Signed-off-by: Jyri Sarha jsarha@ti.com --- 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 */
The hdmi stub codec has not been used since refactoring of OMAP HDMI audio support.
Signed-off-by: Jyri Sarha jsarha@ti.com --- sound/soc/codecs/Kconfig | 4 -- sound/soc/codecs/Makefile | 2 - sound/soc/codecs/hdmi.c | 109 ---------------------------------------------- 3 files changed, 115 deletions(-) delete mode 100644 sound/soc/codecs/hdmi.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index efaafce..fc8e729 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -76,7 +76,6 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C - select SND_SOC_HDMI_CODEC select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008 @@ -432,9 +431,6 @@ config SND_SOC_BT_SCO config SND_SOC_DMIC tristate
-config SND_SOC_HDMI_CODEC - tristate "HDMI stub CODEC" - config SND_SOC_ES8328 tristate "Everest Semi ES8328 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index cf160d9..01b4601 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -69,7 +69,6 @@ snd-soc-max98925-objs := max98925.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o -snd-soc-hdmi-codec-objs := hdmi.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o @@ -256,7 +255,6 @@ obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o -obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o diff --git a/sound/soc/codecs/hdmi.c b/sound/soc/codecs/hdmi.c deleted file mode 100644 index bd42ad3..0000000 --- a/sound/soc/codecs/hdmi.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * ALSA SoC codec driver for HDMI audio codecs. - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ - * Author: Ricardo Neri ricardo.neri@ti.com - * - * 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. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - */ -#include <linux/module.h> -#include <sound/soc.h> -#include <linux/of.h> -#include <linux/of_device.h> - -#define DRV_NAME "hdmi-audio-codec" - -static const struct snd_soc_dapm_widget hdmi_widgets[] = { - SND_SOC_DAPM_INPUT("RX"), - SND_SOC_DAPM_OUTPUT("TX"), -}; - -static const struct snd_soc_dapm_route hdmi_routes[] = { - { "Capture", NULL, "RX" }, - { "TX", NULL, "Playback" }, -}; - -static struct snd_soc_dai_driver hdmi_codec_dai = { - .name = "hdmi-hifi", - .playback = { - .stream_name = "Playback", - .channels_min = 2, - .channels_max = 8, - .rates = 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, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, - .sig_bits = 24, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 2, - .channels_max = 2, - .rates = 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, - .formats = SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S24_LE, - }, - -}; - -#ifdef CONFIG_OF -static const struct of_device_id hdmi_audio_codec_ids[] = { - { .compatible = "linux,hdmi-audio", }, - { } -}; -MODULE_DEVICE_TABLE(of, hdmi_audio_codec_ids); -#endif - -static struct snd_soc_codec_driver hdmi_codec = { - .dapm_widgets = hdmi_widgets, - .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), - .dapm_routes = hdmi_routes, - .num_dapm_routes = ARRAY_SIZE(hdmi_routes), - .ignore_pmdown_time = true, -}; - -static int hdmi_codec_probe(struct platform_device *pdev) -{ - return snd_soc_register_codec(&pdev->dev, &hdmi_codec, - &hdmi_codec_dai, 1); -} - -static int hdmi_codec_remove(struct platform_device *pdev) -{ - snd_soc_unregister_codec(&pdev->dev); - return 0; -} - -static struct platform_driver hdmi_codec_driver = { - .driver = { - .name = DRV_NAME, - .of_match_table = of_match_ptr(hdmi_audio_codec_ids), - }, - - .probe = hdmi_codec_probe, - .remove = hdmi_codec_remove, -}; - -module_platform_driver(hdmi_codec_driver); - -MODULE_AUTHOR("Ricardo Neri ricardo.neri@ti.com"); -MODULE_DESCRIPTION("ASoC generic HDMI codec driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRV_NAME);
On Fri, Aug 14, 2015 at 12:30:40PM +0300, Jyri Sarha wrote:
The hdmi stub codec has not been used since refactoring of OMAP HDMI audio support.
grep tells me that the OMAP HDMI4 and HDMI5 drivers are still registering this device in -next...
On 08/14/15 19:10, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:40PM +0300, Jyri Sarha wrote:
The hdmi stub codec has not been used since refactoring of OMAP HDMI audio support.
grep tells me that the OMAP HDMI4 and HDMI5 drivers are still registering this device in -next...
Really? My source trees for mainline and linux-next look like this:
$ git grep "hdmi-audio-codec" sound/soc/codecs/hdmi.c:#define DRV_NAME "hdmi-audio-codec"
Don't you mean "omap-hdmi-audio", that is implemented in sound/soc/omap/omap-hdmi-audio.c ?
That driver is bit different. It implements ASoC card and uses generic dummy codec. The "hdmi-audio-codec" has not been used for OMAP HDMI audio for several releases already.
Best regards, Jyri
On Mon, Aug 17, 2015 at 10:00:07AM +0300, Jyri Sarha wrote:
On 08/14/15 19:10, Mark Brown wrote:
Don't you mean "omap-hdmi-audio", that is implemented in sound/soc/omap/omap-hdmi-audio.c ?
That driver is bit different. It implements ASoC card and uses generic dummy codec. The "hdmi-audio-codec" has not been used for OMAP HDMI audio for several releases already.
Possibly, yes, or I was on an old tree by mistake. It's definitely not used now like you say.
The hdmi-codec is a platform device driver to be registered from drivers of external HDMI encoders with I2S and/or spdif interface. The driver in turn registers an ASoC codec for the HDMI encoder's audio functionality.
The structures and definitions in the API header are mostly redundant copies of similar structures in ASoC headers. This is on purpose to avoid direct dependencies to ASoC structures in video side driver.
Signed-off-by: Jyri Sarha jsarha@ti.com --- include/sound/hdmi-codec.h | 99 +++++++++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdmi-codec.c | 467 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 573 insertions(+) create mode 100644 include/sound/hdmi-codec.h create mode 100644 sound/soc/codecs/hdmi-codec.c
diff --git a/include/sound/hdmi-codec.h b/include/sound/hdmi-codec.h new file mode 100644 index 0000000..b8a90f7 --- /dev/null +++ b/include/sound/hdmi-codec.h @@ -0,0 +1,99 @@ +/* + * hdmi-codec.h - HDMI Codec driver API + * + * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Jyri Sarha jsarha@ti.com + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#ifndef __HDMI_CODEC_H__ +#define __HDMI_CODEC_H__ + +#include <linux/hdmi.h> +#include <drm/drm_edid.h> +#include <sound/asoundef.h> +#include <uapi/sound/asound.h> + +/* + * Protocol between ASoC cpu-dai and HDMI-encoder + */ +struct hdmi_codec_daifmt { + enum { + HDMI_I2S, + HDMI_RIGHT_J, + HDMI_LEFT_J, + HDMI_DSP_A, + HDMI_DSP_B, + HDMI_AC97, + HDMI_SPDIF, + } fmt; + int bit_clk_inv:1; + int frame_clk_inv:1; + int bit_clk_master:1; + int frame_clk_master:1; +}; + +/* + * HDMI audio parameters + */ +struct hdmi_codec_params { + struct hdmi_audio_infoframe cea; + struct snd_aes_iec958 iec; + int sample_rate; + int sample_width; + int channels; +}; + +struct hdmi_codec_ops { + /* For runtime clock configuration from ASoC machine driver. + * A direct forward from set_sysclk in struct snd_soc_dai_ops. + * Optional */ + int (*set_clk)(struct device *dev, int clk_id, int freq); + + /* Called when ASoC starts an audio stream setup. The call + * provides an audio abort callback for stoping an ongoing + * stream if the HDMI audio becomes unavailable. + * Optional */ + int (*audio_startup)(struct device *dev, + void (*abort_cb)(struct device *dev)); + + /* Configures HDMI-encoder for audio stream. + * Mandatory */ + int (*hw_params)(struct device *dev, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms); + + /* Shuts down the audio stream. + * Mandatory */ + void (*audio_shutdown)(struct device *dev); + + /* Mute/unmute HDMI audio stream. + * Optional */ + int (*digital_mute)(struct device *dev, bool enable); + + /* Provides EDID-Like-Data from connected HDMI device. + * Optional */ + int (*get_eld)(struct device *dev, uint8_t *buf, size_t len); +}; + +/* HDMI codec initalization data */ +struct hdmi_codec_pdata { + struct device *dev; /* The HDMI encoder registering the codec */ + const struct hdmi_codec_ops *ops; + uint i2s:1; + uint spdif:1; + int max_i2s_channels; +}; + +#define HDMI_CODEC_DRV_NAME "hdmi-audio-codec" + +#endif /* __HDMI_CODEC_H__ */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index fc8e729..a5cc574 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -76,6 +76,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX9877 if I2C select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C + select SND_SOC_HDMI_CODEC select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008 @@ -431,6 +432,10 @@ config SND_SOC_BT_SCO config SND_SOC_DMIC tristate
+config SND_SOC_HDMI_CODEC + tristate + select SND_PCM_ELD + config SND_SOC_ES8328 tristate "Everest Semi ES8328 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 01b4601..0245e67 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -69,6 +69,7 @@ snd-soc-max98925-objs := max98925.o snd-soc-max9850-objs := max9850.o snd-soc-mc13783-objs := mc13783.o snd-soc-ml26124-objs := ml26124.o +snd-soc-hdmi-codec-objs := hdmi-codec.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o @@ -255,6 +256,7 @@ obj-$(CONFIG_SND_SOC_MAX98925) += snd-soc-max98925.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o +obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c new file mode 100644 index 0000000..882dd8f --- /dev/null +++ b/sound/soc/codecs/hdmi-codec.c @@ -0,0 +1,467 @@ +/* + * ALSA SoC codec for HDMI encoder drivers + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Author: Jyri Sarha jsarha@ti.com + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include <linux/module.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/pcm_drm_eld.h> +#include <sound/hdmi-codec.h> + +#include <drm/drm_crtc.h> /* This is only to get MAX_ELD_BYTES */ + +struct hdmi_codec_priv { + struct hdmi_codec_pdata hcd; + struct snd_soc_dai_driver *daidrv; + struct hdmi_codec_daifmt daifmt[2]; + struct mutex current_stream_lock; + struct snd_pcm_substream *current_stream; + struct snd_pcm_hw_constraint_list ratec; + uint8_t eld[MAX_ELD_BYTES]; +}; + +static const struct snd_soc_dapm_widget hdmi_widgets[] = { + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route hdmi_routes[] = { + { "TX", NULL, "Playback" }, +}; + +enum { + DAI_ID_I2C = 0, + DAI_ID_SPDIF, +}; + +static void hdmi_codec_abort(struct device *dev) +{ + struct hdmi_codec_priv *hcp = dev_get_drvdata(dev); + + dev_dbg(dev, "%s()\n", __func__); + + mutex_lock(&hcp->current_stream_lock); + if (hcp->current_stream && hcp->current_stream->runtime && + snd_pcm_running(hcp->current_stream)) { + dev_info(dev, "HDMI audio playback aborted\n"); + snd_pcm_stream_lock_irq(hcp->current_stream); + snd_pcm_stop(hcp->current_stream, SNDRV_PCM_STATE_DISCONNECTED); + snd_pcm_stream_unlock_irq(hcp->current_stream); + } + mutex_unlock(&hcp->current_stream_lock); +} + +static int hdmi_codec_new_stream(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + mutex_lock(&hcp->current_stream_lock); + if (!hcp->current_stream) { + hcp->current_stream = substream; + } else if (hcp->current_stream != substream) { + dev_err(dai->dev, "Only one simultaneous stream supported!\n"); + ret = -EINVAL; + } + mutex_unlock(&hcp->current_stream_lock); + + return ret; +} + +static int hdmi_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + dev_dbg(dai->dev, "%s()\n", __func__); + + ret = hdmi_codec_new_stream(substream, dai); + if (ret) + return ret; + + if (hcp->hcd.ops->audio_startup) { + ret = hcp->hcd.ops->audio_startup(hcp->hcd.dev, + hdmi_codec_abort); + if (ret) { + mutex_lock(&hcp->current_stream_lock); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); + return ret; + } + } + + if (hcp->hcd.ops->get_eld) { + ret = hcp->hcd.ops->get_eld(hcp->hcd.dev, hcp->eld, + sizeof(hcp->eld)); + + if (!ret) { + ret = snd_pcm_hw_constraint_eld(substream->runtime, + hcp->eld); + if (ret) + return ret; + } + } + return 0; +} + +static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s()\n", __func__); + + mutex_lock(&hcp->current_stream_lock); + BUG_ON(hcp->current_stream != substream); + hcp->current_stream = NULL; + mutex_unlock(&hcp->current_stream_lock); + + hcp->hcd.ops->audio_shutdown(hcp->hcd.dev); +} + +static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + struct hdmi_codec_params hp = { + .cea = { 0 }, + .iec = { + .status = { + IEC958_AES0_CON_NOT_COPYRIGHT, + IEC958_AES1_CON_GENERAL, + IEC958_AES2_CON_SOURCE_UNSPEC, + IEC958_AES3_CON_CLOCK_VARIABLE, + }, + .subcode = { 0 }, + .pad = 0, + .dig_subframe = { 0 }, + } + }; + int ret; + + dev_dbg(dai->dev, "%s()\n", __func__); + + ret = hdmi_codec_new_stream(substream, dai); + if (ret) + return ret; + + hdmi_audio_infoframe_init(&hp.cea); + hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_PCM; + hp.cea.channels = params_channels(params); + + switch (params_width(params)) { + case 16: + hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_20_16; + hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_16; + break; + case 18: + hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_22_18; + hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20; + break; + case 20: + hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_24_20; + hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20; + break; + case 24: + case 32: + hp.iec.status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_24; + break; + default: + dev_err(dai->dev, "sample width not supported!\n"); + return -EINVAL; + } + + switch (params_rate(params)) { + case 32000: + hp.iec.status[3] |= IEC958_AES3_CON_FS_32000; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_32000; + break; + case 44100: + hp.iec.status[3] |= IEC958_AES3_CON_FS_44100; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_44100; + break; + case 48000: + hp.iec.status[3] |= IEC958_AES3_CON_FS_48000; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_48000; + break; + case 88200: + hp.iec.status[3] |= IEC958_AES3_CON_FS_88200; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_88200; + break; + case 96000: + hp.iec.status[3] |= IEC958_AES3_CON_FS_96000; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_96000; + break; + case 176400: + hp.iec.status[3] |= IEC958_AES3_CON_FS_176400; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_176400; + break; + case 192000: + hp.iec.status[3] |= IEC958_AES3_CON_FS_192000; + hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_192000; + break; + default: + dev_err(dai->dev, "rate not supported!\n"); + return -EINVAL; + } + hp.sample_width = params_width(params); + hp.sample_rate = params_rate(params); + hp.channels = params_channels(params); + + return hcp->hcd.ops->hw_params(hcp->hcd.dev, &hcp->daifmt[dai->id], + &hp); +} + +static int hdmi_codec_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s()\n", __func__); + + if (hcp->hcd.ops->set_clk) + return hcp->hcd.ops->set_clk(hcp->hcd.dev, clk_id, freq); + + return 0; +} + +static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + struct hdmi_codec_daifmt cf = { 0 }; + int ret = 0; + + dev_dbg(dai->dev, "%s()\n", __func__); + + if (dai->id == DAI_ID_SPDIF) { + cf.fmt = HDMI_SPDIF; + } else { + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + cf.bit_clk_master = 1; + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFM: + cf.frame_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + cf.bit_clk_master = 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + cf.frame_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_NF: + cf.bit_clk_inv = 1; + break; + case SND_SOC_DAIFMT_IB_IF: + cf.frame_clk_inv = 1; + cf.bit_clk_inv = 1; + break; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cf.fmt = HDMI_I2S; + break; + case SND_SOC_DAIFMT_DSP_A: + cf.fmt = HDMI_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + cf.fmt = HDMI_DSP_B; + break; + case SND_SOC_DAIFMT_RIGHT_J: + cf.fmt = HDMI_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + cf.fmt = HDMI_LEFT_J; + break; + case SND_SOC_DAIFMT_AC97: + cf.fmt = HDMI_AC97; + break; + default: + dev_err(dai->dev, "Invalid DAI interface format\n"); + return -EINVAL; + } + } + + hcp->daifmt[dai->id] = cf; + + return ret; +} + +static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + + dev_dbg(dai->dev, "%s()\n", __func__); + + if (hcp->hcd.ops->digital_mute) + return hcp->hcd.ops->digital_mute(hcp->hcd.dev, mute); + + return 0; +} + +static const struct snd_soc_dai_ops hdmi_dai_ops = { + .startup = hdmi_codec_startup, + .shutdown = hdmi_codec_shutdown, + .hw_params = hdmi_codec_hw_params, + .set_sysclk = hdmi_codec_set_sysclk, + .set_fmt = hdmi_codec_set_fmt, + .digital_mute = hdmi_codec_digital_mute, +}; + + +#define HDMI_RATES (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) + +#define SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) + +#define I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) + +static struct snd_soc_dai_driver hdmi_i2s_dai = { + .name = "i2s-hifi", + .id = DAI_ID_I2C, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = HDMI_RATES, + .formats = I2S_FORMATS, + .sig_bits = 24, + }, + .ops = &hdmi_dai_ops, +}; + +static const struct snd_soc_dai_driver hdmi_spdif_dai = { + .name = "spdif-hifi", + .id = DAI_ID_SPDIF, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = HDMI_RATES, + .formats = SPDIF_FORMATS, + }, + .ops = &hdmi_dai_ops, +}; + +static struct snd_soc_codec_driver hdmi_codec = { + .dapm_widgets = hdmi_widgets, + .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), + .dapm_routes = hdmi_routes, + .num_dapm_routes = ARRAY_SIZE(hdmi_routes), +}; + +static int hdmi_codec_probe(struct platform_device *pdev) +{ + struct hdmi_codec_pdata *hcd = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct hdmi_codec_priv *hcp; + int dai_count, i = 0; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + if (!hcd) { + dev_err(dev, "%s: No plalform data\n", __func__); + return -EINVAL; + } + + dai_count = hcd->i2s + hcd->spdif; + if (dai_count < 1 || !hcd->dev || !hcd->ops || + !hcd->ops->hw_params || !hcd->ops->audio_shutdown) { + dev_err(dev, "%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + hcp = devm_kzalloc(dev, sizeof(*hcp), GFP_KERNEL); + if (!hcp) + return -ENOMEM; + + hcp->hcd = *hcd; + mutex_init(&hcp->current_stream_lock); + + hcp->daidrv = devm_kzalloc(hcd->dev, dai_count * sizeof(*hcp->daidrv), + GFP_KERNEL); + if (!hcp->daidrv) + return -ENOMEM; + + if (hcd->i2s) { + hcp->daidrv[i] = hdmi_i2s_dai; + hcp->daidrv[i].playback.channels_max = + hcd->max_i2s_channels; + i++; + } + + if (hcd->spdif) + hcp->daidrv[i] = hdmi_spdif_dai; + + ret = snd_soc_register_codec(dev, &hdmi_codec, hcp->daidrv, + dai_count); + if (ret) { + dev_err(dev, "%s: snd_soc_register_codec() failed (%d)\n", + __func__, ret); + return ret; + } + + dev_set_drvdata(dev, hcp); + return 0; +} + +static int hdmi_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver hdmi_codec_driver = { + .driver = { + .name = HDMI_CODEC_DRV_NAME, + }, + .probe = hdmi_codec_probe, + .remove = hdmi_codec_remove, +}; + +module_platform_driver(hdmi_codec_driver); + +MODULE_AUTHOR("Jyri Sarha jsarha@ti.com"); +MODULE_DESCRIPTION("HDMI Audio Codec Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" HDMI_CODEC_DRV_NAME);
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
+static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
- struct hdmi_codec_params hp = {
.cea = { 0 },
Unnecessary initialisation - because you are initialising this structure, all unnamed fields will be zeroed.
.iec = {
.status = {
IEC958_AES0_CON_NOT_COPYRIGHT,
IEC958_AES1_CON_GENERAL,
IEC958_AES2_CON_SOURCE_UNSPEC,
IEC958_AES3_CON_CLOCK_VARIABLE,
},
...
- hdmi_audio_infoframe_init(&hp.cea);
- hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_PCM;
Something tells me here that you haven't read the HDMI specification. HDMI says that the coding type will be zero (refer to stream header). The same goes for much of the CEA audio infoframe. Please see the Audio InfoFrame details in the HDMI specification.
- hp.cea.channels = params_channels(params);
- switch (params_width(params)) {
- case 16:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_20_16;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_16;
break;
- case 18:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_22_18;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20;
break;
- case 20:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_24_20;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20;
break;
- case 24:
- case 32:
hp.iec.status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24 |
IEC958_AES4_CON_WORDLEN_24_20;
Why not use the generic code to generate the AES channel status bits? See sound/core/pcm_iec958.c.
On 08/14/15 12:57, Russell King - ARM Linux wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
+static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
- struct hdmi_codec_params hp = {
.cea = { 0 },
Unnecessary initialisation - because you are initialising this structure, all unnamed fields will be zeroed.
True, I just tried to be explicit.
.iec = {
.status = {
IEC958_AES0_CON_NOT_COPYRIGHT,
IEC958_AES1_CON_GENERAL,
IEC958_AES2_CON_SOURCE_UNSPEC,
IEC958_AES3_CON_CLOCK_VARIABLE,
},
...
- hdmi_audio_infoframe_init(&hp.cea);
- hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_PCM;
Something tells me here that you haven't read the HDMI specification. HDMI says that the coding type will be zero (refer to stream header). The same goes for much of the CEA audio infoframe. Please see the Audio InfoFrame details in the HDMI specification.
Must admit, that I have not read it end to end. Obviously I have missed a relevant piece of information here. I'll fix that and check the related items too.
- hp.cea.channels = params_channels(params);
- switch (params_width(params)) {
- case 16:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_20_16;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_16;
break;
- case 18:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_22_18;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20;
break;
- case 20:
hp.iec.status[4] |= IEC958_AES4_CON_WORDLEN_24_20;
hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_20;
break;
- case 24:
- case 32:
hp.iec.status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24 |
IEC958_AES4_CON_WORDLEN_24_20;
Why not use the generic code to generate the AES channel status bits? See sound/core/pcm_iec958.c.
Thanks, I did not know that exist. I'll make use of that.
Best regards, Jyri
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
+struct hdmi_codec_ops {
- /* For runtime clock configuration from ASoC machine driver.
* A direct forward from set_sysclk in struct snd_soc_dai_ops.
* Optional */
- int (*set_clk)(struct device *dev, int clk_id, int freq);
I'd be much happier if we were using the clock API as the external interface here, it's where we want to be internally too and it's going to be easier to not introduce any external dependencies on the ASoC internal stuff.
- /* Called when ASoC starts an audio stream setup. The call
* provides an audio abort callback for stoping an ongoing
* stream if the HDMI audio becomes unavailable.
* Optional */
- int (*audio_startup)(struct device *dev,
void (*abort_cb)(struct device *dev));
I'm a bit confused about what is going to use abort_cb() and why they wouldn't just call shutdown instead?
+/* HDMI codec initalization data */ +struct hdmi_codec_pdata {
- struct device *dev; /* The HDMI encoder registering the codec */
Shouldn't this just be dev->parent?
+enum {
- DAI_ID_I2C = 0,
- DAI_ID_SPDIF,
+};
I2C? :P
On 08/14/15 19:18, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
+struct hdmi_codec_ops {
- /* For runtime clock configuration from ASoC machine driver.
* A direct forward from set_sysclk in struct snd_soc_dai_ops.
* Optional */
- int (*set_clk)(struct device *dev, int clk_id, int freq);
I'd be much happier if we were using the clock API as the external interface here, it's where we want to be internally too and it's going to be easier to not introduce any external dependencies on the ASoC internal stuff.
Sounds better. I'll change that.
- /* Called when ASoC starts an audio stream setup. The call
* provides an audio abort callback for stoping an ongoing
* stream if the HDMI audio becomes unavailable.
* Optional */
- int (*audio_startup)(struct device *dev,
void (*abort_cb)(struct device *dev));
I'm a bit confused about what is going to use abort_cb() and why they wouldn't just call shutdown instead?
audio_shutdown() is for ASoC side to tell video side that audio playback has stopped.
The abort_cb() is for video side to inform ASoC that current audio stream can not continue anymore and it should be aborted. The similar mechanism is currently in use in sound/soc/omap/omap-hdmi-audio.c.
+/* HDMI codec initalization data */ +struct hdmi_codec_pdata {
- struct device *dev; /* The HDMI encoder registering the codec */
Shouldn't this just be dev->parent?
+enum {
- DAI_ID_I2C = 0,
- DAI_ID_SPDIF,
+};
I2C? :P
Right, should be I2S. Thanks!
Best regards, Jyri
On Mon, Aug 17, 2015 at 10:07:55AM +0300, Jyri Sarha wrote:
On 08/14/15 19:18, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
- /* Called when ASoC starts an audio stream setup. The call
* provides an audio abort callback for stoping an ongoing
* stream if the HDMI audio becomes unavailable.
* Optional */
- int (*audio_startup)(struct device *dev,
void (*abort_cb)(struct device *dev));
I'm a bit confused about what is going to use abort_cb() and why they wouldn't just call shutdown instead?
audio_shutdown() is for ASoC side to tell video side that audio playback has stopped.
The abort_cb() is for video side to inform ASoC that current audio stream can not continue anymore and it should be aborted. The similar mechanism is currently in use in sound/soc/omap/omap-hdmi-audio.c.
Someone reading the code needs to be able to understand this.
On 08/17/15 21:41, Mark Brown wrote:
On Mon, Aug 17, 2015 at 10:07:55AM +0300, Jyri Sarha wrote:
On 08/14/15 19:18, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
- /* Called when ASoC starts an audio stream setup. The call
* provides an audio abort callback for stoping an ongoing
* stream if the HDMI audio becomes unavailable.
* Optional */
- int (*audio_startup)(struct device *dev,
void (*abort_cb)(struct device *dev));
I'm a bit confused about what is going to use abort_cb() and why they wouldn't just call shutdown instead?
audio_shutdown() is for ASoC side to tell video side that audio playback has stopped.
The abort_cb() is for video side to inform ASoC that current audio stream can not continue anymore and it should be aborted. The similar mechanism is currently in use in sound/soc/omap/omap-hdmi-audio.c.
Someone reading the code needs to be able to understand this.
Ok, I'll improve the comment above.
Thanks, Jyri
Missed one commet first time around...
On 08/14/15 19:18, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
...
+/* HDMI codec initalization data */ +struct hdmi_codec_pdata {
- struct device *dev; /* The HDMI encoder registering the codec */
Shouldn't this just be dev->parent?
No. The HDMI encoder device is the parent for HDMI-codec.
The patch you took in in the last round uses the ASoC component drivers parent's of-node if the component driver does not have one itself. In this case the phandle in the binding points to the HDMI encoder's node, which is the parent of the HDMI codec.
Best regards, Jyri
On 08/17/15 10:57, Jyri Sarha wrote:
Missed one commet first time around...
On 08/14/15 19:18, Mark Brown wrote:
On Fri, Aug 14, 2015 at 12:30:41PM +0300, Jyri Sarha wrote:
...
+/* HDMI codec initalization data */ +struct hdmi_codec_pdata {
- struct device *dev; /* The HDMI encoder registering the codec */
Shouldn't this just be dev->parent?
No. The HDMI encoder device is the parent for HDMI-codec.
The patch you took in in the last round uses the ASoC component drivers parent's of-node if the component driver does not have one itself. In this case the phandle in the binding points to the HDMI encoder's node, which is the parent of the HDMI codec.
After reading the comment again I see now what you meant.
Yes, I can extract the encoder device from the dev->parent pointer. No need to have it in pdata.
Thanks, Jyri
From: Jean-Francois Moine moinejf@free.fr
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 Signed-off-by: Jyri Sarha jsarha@ti.com --- .../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..35f6a80 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@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>; + }; + }; }; diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 424228b..0952eac 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 audio; };
#define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) @@ -774,6 +777,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) @@ -1230,9 +1235,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; @@ -1337,15 +1390,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..bef1da7 --- /dev/null +++ b/include/sound/tda998x.h @@ -0,0 +1,8 @@ +#ifndef SND_TDA998X_H +#define SND_TDA998X_H + +struct tda998x_audio { + u8 ports[2]; /* AP value */ + u8 port_types[2]; /* AFMT_xxx */ +}; +#endif
Move struct tda998x_audio definition to tda998x_drv.c and remove include/sound/tda998x.h. There is no external use for struct tda998x_audio.
Fix graph parsing to allow ports to be inside a separate "ports"-node as specified in Documentation/devicetree/bindings/graph.txt.
Signed-off-by: Jyri Sarha jsarha@ti.com --- drivers/gpu/drm/i2c/tda998x_drv.c | 59 +++++++++++++++++++++------------------ include/sound/tda998x.h | 8 ------ 2 files changed, 32 insertions(+), 35 deletions(-) delete mode 100644 include/sound/tda998x.h
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 0952eac..4dc2dc0 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -27,10 +27,14 @@ #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__)
+struct tda998x_audio { + u8 ports[2]; /* AP value */ + u8 port_types[2]; /* AFMT_xxx */ +}; + struct tda998x_priv { struct i2c_client *cec; struct i2c_client *hdmi; @@ -1240,9 +1244,10 @@ static int tda998x_parse_ports(struct tda998x_priv *priv, { struct device_node *of_port; const char *port_type; - int ret, audio_index, reg, afmt; + int ret, audio_index, reg, afmt, rgb_initialized;
audio_index = 0; + rgb_initialized = 0; for_each_child_of_node(np, of_port) { if (!of_port->name || of_node_cmp(of_port->name, "port") != 0) @@ -1252,11 +1257,17 @@ static int tda998x_parse_ports(struct tda998x_priv *priv, if (ret < 0) continue; ret = of_property_read_u32(of_port, "reg", ®); + if (ret < 0) { + dev_err(&priv->hdmi->dev, "missing reg for %s\n", + port_type); + return ret; + } 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; + rgb_initialized = 1; } continue; } @@ -1266,11 +1277,6 @@ static int tda998x_parse_ports(struct tda998x_priv *priv, 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; @@ -1279,13 +1285,13 @@ static int tda998x_parse_ports(struct tda998x_priv *priv, priv->audio.port_types[audio_index] = afmt; audio_index++; } - return 0; + return rgb_initialized; }
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; + struct device_node *ports; u32 video; int rev_lo, rev_hi, ret; unsigned short cec_addr; @@ -1392,24 +1398,15 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv)
/* 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 { - + ports = of_get_child_by_name(np, "ports"); + if (!ports) + ports = of_node_get(np); + /* graph of ports */ + ret = tda998x_parse_ports(priv, ports); + of_node_put(ports); + if (ret < 0) + goto fail; + if (ret == 0) { /* optional video properties */ ret = of_property_read_u32(np, "video-ports", &video); if (ret == 0) { @@ -1418,6 +1415,14 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->vip_cntrl_2 = video; } } + 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; + } }
return 0; diff --git a/include/sound/tda998x.h b/include/sound/tda998x.h deleted file mode 100644 index bef1da7..0000000 --- a/include/sound/tda998x.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SND_TDA998X_H -#define SND_TDA998X_H - -struct tda998x_audio { - u8 ports[2]; /* AP value */ - u8 port_types[2]; /* AFMT_xxx */ -}; -#endif
Register ASoC HDMI codec for audio functionality. This is an initial ASoC audio implementation for tda998x driver and it does not use all the features provided by hdmi-codec.
HDMI audio info-frame and audio stream header is generated by the ASoC HDMI codec. The codec also applies constraints for available sample-rates.
Implementation of audio_startup for hdmi_codec_ops would enable tda998x driver to abort ongoing playback if the HDMI cable is unplugged or re-plugged to a device without audio capability.
Signed-off-by: Jyri Sarha jsarha@ti.com --- drivers/gpu/drm/i2c/Kconfig | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 241 +++++++++++++++++++++++++++++++++----- 2 files changed, 214 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..088f278 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -28,6 +28,7 @@ config DRM_I2C_SIL164 config DRM_I2C_NXP_TDA998X tristate "NXP Semiconductors TDA998X HDMI encoder" default m if DRM_TILCDC + select SND_SOC_HDMI_CODEC if SND_SOC help Support for NXP Semiconductors TDA998X HDMI encoders.
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 4dc2dc0..8444e18 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/hdmi-codec.h>
#include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -49,6 +50,8 @@ struct tda998x_priv { u8 vip_cntrl_2; struct tda998x_encoder_params params;
+ struct platform_device *audio_pdev; + wait_queue_head_t wq_edid; volatile int wq_edid_wait; struct drm_encoder *encoder; @@ -435,7 +438,7 @@ out: }
static void -reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt) +reg_write_range(struct tda998x_priv *priv, uint16_t reg, const u8 *p, int cnt) { struct i2c_client *client = priv->hdmi; uint8_t buf[cnt+1]; @@ -619,7 +622,7 @@ tda998x_write_if(struct tda998x_priv *priv, uint8_t bit, uint16_t addr, }
static void -tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) +tda998x_write_raw_aif(struct tda998x_priv *priv, u8 *audio_frame) { u8 buf[PB(HDMI_AUDIO_INFOFRAME_SIZE) + 1];
@@ -627,10 +630,10 @@ tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) buf[HB(0)] = HDMI_INFOFRAME_TYPE_AUDIO; buf[HB(1)] = 0x01; buf[HB(2)] = HDMI_AUDIO_INFOFRAME_SIZE; - buf[PB(1)] = p->audio_frame[1] & 0x07; /* CC */ - buf[PB(2)] = p->audio_frame[2] & 0x1c; /* SF */ - buf[PB(4)] = p->audio_frame[4]; - buf[PB(5)] = p->audio_frame[5] & 0xf8; /* DM_INH + LSV */ + buf[PB(1)] = audio_frame[1] & 0x07; /* CC */ + buf[PB(2)] = audio_frame[2] & 0x1c; /* SF */ + buf[PB(4)] = audio_frame[4]; + buf[PB(5)] = audio_frame[5] & 0xf8; /* DM_INH + LSV */
buf[PB(0)] = tda998x_cksum(buf, sizeof(buf));
@@ -638,6 +641,24 @@ tda998x_write_aif(struct tda998x_priv *priv, struct tda998x_encoder_params *p) sizeof(buf)); }
+static int tda998x_write_aif(struct tda998x_priv *priv, + struct hdmi_audio_infoframe *cea) +{ + uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)]; + int len; + + len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf)); + if (len < 0) { + dev_err(&priv->hdmi->dev, + "Failed to pack audio infoframe: %d\n", len); + return len; + } + + /* Write the audio information packet */ + tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len); + return 0; +} + static void tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode) { @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) } }
-static void +static int tda998x_configure_audio(struct tda998x_priv *priv, - struct drm_display_mode *mode, struct tda998x_encoder_params *p) + int mode_clock, + int ena_ap, + int dai_format, + int sample_width, + int sample_rate, + const u8 *status) { uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv; 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); + reg_write(priv, REG_ENA_AP, ena_ap); + reg_write(priv, REG_ENA_ACLK, dai_format == AFMT_SPDIF ? 0 : 1);
/* Set audio input source */ - switch (p->audio_format) { + switch (dai_format) { case AFMT_SPDIF: reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF); clksel_aip = AIP_CLKSEL_AIP_SPDIF; @@ -694,12 +720,25 @@ 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 (sample_width) { + case 16: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case 18: + case 20: + case 24: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case 32: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } break;
default: - BUG(); - return; + dev_err(&priv->hdmi->dev, "Unsupported I2S format\n"); + return -EINVAL; }
reg_write(priv, REG_AIP_CLKSEL, clksel_aip); @@ -715,11 +754,11 @@ tda998x_configure_audio(struct tda998x_priv *priv, * assume 100MHz requires larger divider. */ adiv = AUDIO_DIV_SERCLK_8; - if (mode->clock > 100000) + if (mode_clock > 100000) adiv++; /* AUDIO_DIV_SERCLK_16 */
/* S/PDIF asks for a larger divider */ - if (p->audio_format == AFMT_SPDIF) + if (dai_format == AFMT_SPDIF) adiv++; /* AUDIO_DIV_SERCLK_16 or _32 */
reg_write(priv, REG_AUDIO_DIV, adiv); @@ -728,7 +767,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, * This is the approximate value of N, which happens to be * the recommended values for non-coherent clocks. */ - n = 128 * p->audio_sample_rate / 1000; + n = 128 * sample_rate / 1000;
/* Write the CTS and N values */ buf[0] = 0x44; @@ -747,19 +786,13 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_CTS);
/* Write the channel status */ - buf[0] = IEC958_AES0_CON_NOT_COPYRIGHT; - buf[1] = 0x00; - buf[2] = IEC958_AES3_CON_FS_NOTID; - buf[3] = IEC958_AES4_CON_ORIGFS_NOTID | - IEC958_AES4_CON_MAX_WORDLEN_24; - reg_write_range(priv, REG_CH_STAT_B(0), buf, 4); + reg_write_range(priv, REG_CH_STAT_B(0), status, 4);
tda998x_audio_mute(priv, true); msleep(20); tda998x_audio_mute(priv, false);
- /* Write the audio information packet */ - tda998x_write_aif(priv, p); + return 0; }
/* DRM encoder functions */ @@ -1033,9 +1066,24 @@ tda998x_encoder_mode_set(struct tda998x_priv *priv,
tda998x_write_avi(priv, adjusted_mode);
- if (priv->params.audio_cfg) - tda998x_configure_audio(priv, adjusted_mode, - &priv->params); + if (priv->params.audio_cfg) { + static const u8 status[] = { + IEC958_AES0_CON_NOT_COPYRIGHT, + 0x00, + IEC958_AES3_CON_FS_NOTID, + IEC958_AES4_CON_ORIGFS_NOTID, + IEC958_AES4_CON_MAX_WORDLEN_24 + }; + + tda998x_configure_audio(priv, adjusted_mode->clock, + priv->params.audio_cfg, + priv->params.audio_clk_cfg, + 24, + priv->params.audio_sample_rate, + status); + + tda998x_write_raw_aif(priv, priv->params.audio_frame); + } } }
@@ -1127,6 +1175,8 @@ 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); + drm_edid_to_eld(connector, edid); + kfree(edid);
return n; @@ -1163,6 +1213,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) }
i2c_unregister_device(priv->cec); + + if (priv->audio_pdev) + platform_device_unregister(priv->audio_pdev); }
/* Slave encoder support */ @@ -1237,6 +1290,134 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = { .set_property = tda998x_encoder_set_property, };
+static int tda998x_audio_hw_params(struct device *dev, + struct hdmi_codec_daifmt *daifmt, + struct hdmi_codec_params *params) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + int ena_ap = -1; + int dai_format, i, ret; + + if (!priv->encoder->crtc) + return -ENODEV; + + switch (daifmt->fmt) { + case HDMI_I2S: + if (daifmt->bit_clk_inv || daifmt->frame_clk_inv || + daifmt->bit_clk_master || daifmt->frame_clk_master) { + dev_err(dev, "%s: Bad flags %d %d %d %d\n", __func__, + daifmt->bit_clk_inv, daifmt->frame_clk_inv, + daifmt->bit_clk_master, + daifmt->frame_clk_master); + return -EINVAL; + } + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types[i] == AFMT_I2S) + ena_ap = priv->audio.ports[i]; + dai_format = AFMT_I2S; + break; + case HDMI_SPDIF: + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) + if (priv->audio.port_types == AFMT_SPDIF) + ena_ap = priv->audio.ports[i]; + dai_format = AFMT_SPDIF; + break; + default: + dev_err(dev, "%s: Invalid format %d\n", __func__, daifmt->fmt); + return -EINVAL; + } + + if (ena_ap < 0) { + dev_err(dev, "%s: No audio configutation found\n", __func__); + return -EINVAL; + } + + ret = tda998x_configure_audio(priv, + priv->encoder->crtc->hwmode.clock, + ena_ap, + dai_format, + params->sample_width, + params->sample_rate, + params->iec.status); + + if (!ret) + ret = tda998x_write_aif(priv, ¶ms->cea); + + return ret; +} + +static void tda998x_audio_shutdown(struct device *dev) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + reg_write(priv, REG_ENA_AP, 0); +} + +int tda998x_audio_digital_mute(struct device *dev, bool enable) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + + tda998x_audio_mute(priv, enable); + + return 0; +} + +static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + struct drm_mode_config *config = &priv->encoder->dev->mode_config; + struct drm_connector *connector; + int ret = -ENODEV; + + mutex_lock(&config.mutex); + list_for_each_entry(connector, &config->connector_list, head) { + if (priv->encoder == connector->encoder) { + memcpy(buf, connector->eld, + min(sizeof(connector->eld), len)); + ret = 0; + } + } + mutex_unlock(&config.mutex); + + return ret; +} + +static const struct hdmi_codec_ops audio_codec_ops = { + .hw_params = tda998x_audio_hw_params, + .audio_shutdown = tda998x_audio_shutdown, + .digital_mute = tda998x_audio_digital_mute, + .get_eld = tda998x_audio_get_eld, +}; + +static int tda998x_audio_codec_init(struct tda998x_priv *priv, + struct device *dev) +{ + struct hdmi_codec_pdata codec_data = { + .dev = dev, + .ops = &audio_codec_ops, + .max_i2s_channels = 2, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(priv->audio.ports); i++) { + if (priv->audio.port_types[i] == AFMT_I2S && + priv->audio.ports[i] != 0) + codec_data.i2s = 1; + if (priv->audio.port_types[i] == AFMT_SPDIF && + priv->audio.ports[i] != 0) + codec_data.spdif = 1; + } + + priv->audio_pdev = platform_device_register_data( + dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + if (IS_ERR(priv->audio_pdev)) + return PTR_ERR(priv->audio_pdev); + + return 0; +} + /* I2C driver functions */
static int tda998x_parse_ports(struct tda998x_priv *priv, @@ -1425,6 +1606,8 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) } }
+ tda998x_audio_codec_init(priv, &client->dev); + return 0;
fail: @@ -1455,6 +1638,8 @@ static int tda998x_encoder_init(struct i2c_client *client, return ret; }
+ dev_set_drvdata(&client->dev, priv); + encoder_slave->slave_priv = priv; encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs;
On Fri, Aug 14, 2015 at 12:30:44PM +0300, Jyri Sarha wrote:
+static int tda998x_write_aif(struct tda998x_priv *priv,
struct hdmi_audio_infoframe *cea)
+{
- uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)];
- int len;
- len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf));
- if (len < 0) {
dev_err(&priv->hdmi->dev,
"Failed to pack audio infoframe: %d\n", len);
return len;
- }
- /* Write the audio information packet */
- tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len);
- return 0;
+}
I have such a function already queued up, but I can't push it out at the moment because of too many conflicts across all my DRM work. I'm waiting for after 4.3-rc1 before publishing anything from or accepting anything else into DRM branches.
static void tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode) { @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) } }
-static void +static int tda998x_configure_audio(struct tda998x_priv *priv,
struct drm_display_mode *mode, struct tda998x_encoder_params *p)
int mode_clock,
int ena_ap,
int dai_format,
int sample_width,
int sample_rate,
const u8 *status)
I don't think this is an improvement.
+static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len) +{
- struct tda998x_priv *priv = dev_get_drvdata(dev);
- struct drm_mode_config *config = &priv->encoder->dev->mode_config;
- struct drm_connector *connector;
- int ret = -ENODEV;
- mutex_lock(&config.mutex);
- list_for_each_entry(connector, &config->connector_list, head) {
if (priv->encoder == connector->encoder) {
memcpy(buf, connector->eld,
min(sizeof(connector->eld), len));
ret = 0;
}
- }
- mutex_unlock(&config.mutex);
Obviously untested. Should be config->mutex.
But in any case, when I kill the DRM slave encoder code in here, this becomes unnecessary.
On 08/14/15 13:06, Russell King - ARM Linux wrote:
On Fri, Aug 14, 2015 at 12:30:44PM +0300, Jyri Sarha wrote:
+static int tda998x_write_aif(struct tda998x_priv *priv,
struct hdmi_audio_infoframe *cea)
+{
- uint8_t buf[HDMI_INFOFRAME_SIZE(AUDIO)];
- int len;
- len = hdmi_audio_infoframe_pack(cea, buf, sizeof(buf));
- if (len < 0) {
dev_err(&priv->hdmi->dev,
"Failed to pack audio infoframe: %d\n", len);
return len;
- }
- /* Write the audio information packet */
- tda998x_write_if(priv, DIP_IF_FLAGS_IF4, REG_IF4_HB0, buf, len);
- return 0;
+}
I have such a function already queued up, but I can't push it out at the moment because of too many conflicts across all my DRM work. I'm waiting for after 4.3-rc1 before publishing anything from or accepting anything else into DRM branches.
Ok, is the code available some where? Could take a look so I can align my code to that.
static void tda998x_write_avi(struct tda998x_priv *priv, struct drm_display_mode *mode) { @@ -670,19 +691,24 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on) } }
-static void +static int tda998x_configure_audio(struct tda998x_priv *priv,
struct drm_display_mode *mode, struct tda998x_encoder_params *p)
int mode_clock,
int ena_ap,
int dai_format,
int sample_width,
int sample_rate,
const u8 *status)
I don't think this is an improvement.
Still it makes the function more generic and enables its usage in HDMI-codec API implementation. I'll try to make it look tidier.
+static int tda998x_audio_get_eld(struct device *dev, uint8_t *buf, size_t len) +{
- struct tda998x_priv *priv = dev_get_drvdata(dev);
- struct drm_mode_config *config = &priv->encoder->dev->mode_config;
- struct drm_connector *connector;
- int ret = -ENODEV;
- mutex_lock(&config.mutex);
- list_for_each_entry(connector, &config->connector_list, head) {
if (priv->encoder == connector->encoder) {
memcpy(buf, connector->eld,
min(sizeof(connector->eld), len));
ret = 0;
}
- }
- mutex_unlock(&config.mutex);
Obviously untested. Should be config->mutex.
Sorry. Should never do these last minute changes. I must have been compiling and testing different code version. I first had this function using config->connection_mutex, but then - after reading the eld related code - found that mode_config mutex should be used instead.
But in any case, when I kill the DRM slave encoder code in here, this becomes unnecessary.
Ok, The information from ELD needs be passed to audio side constraints somehow. I'd love to see the code you have in queue.
Best regards, Jyri
Add HDMI audio support. Adds mcasp0_pins, clk_mcasp0_fixed, clk_mcasp0, mcasp0, sound node, and updates the tda19988 node to follow the new binding.
Signed-off-by: Jyri Sarha jsarha@ti.com --- arch/arm/boot/dts/am335x-boneblack.dts | 90 ++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/am335x-boneblack.dts b/arch/arm/boot/dts/am335x-boneblack.dts index eadbba3..7d3e899 100644 --- a/arch/arm/boot/dts/am335x-boneblack.dts +++ b/arch/arm/boot/dts/am335x-boneblack.dts @@ -64,6 +64,16 @@ 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ >; }; + + mcasp0_pins: mcasp0_pins { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLUP | MUX_MODE0) /* mcasp0_ahclkx.mcasp0_ahclkx */ + 0x19c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkr.mcasp0_axr2 */ + 0x194 (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mcasp0_fsx.mcasp0_fsx */ + 0x190 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx.mcasp0_aclkx */ + 0x06c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a11.GPIO1_27 */ + >; + }; };
&lcdc { @@ -76,17 +86,89 @@ };
&i2c0 { - tda19988 { + tda19988: tda19988 { compatible = "nxp,tda998x"; reg = <0x70>; + + #sound-dai-cells = <0>; + pinctrl-names = "default", "off"; pinctrl-0 = <&nxp_hdmi_bonelt_pins>; pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>;
- port { - hdmi_0: endpoint@0 { - remote-endpoint = <&lcdc_0>; + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + port-type = "rgb"; + reg = <0x230145>; + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; }; + port@1 { + port-type = "i2s"; + reg = <0x03>; + tda19988_i2s: endpoint { + remote-endpoint = <&mcasp0_i2s>; + }; + }; + }; + }; +}; + +&rtc { + system-power-controller; +}; + +&mcasp0 { + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcasp0_pins>; + status = "okay"; + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ + 0 0 1 0 + >; + tx-num-evt = <1>; + rx-num-evt = <1>; + + port { + mcasp0_i2s: endpoint { + remote-endpoint = <&tda19988_i2s>; + }; + }; +}; + +/ { + clk_mcasp0_fixed: clk_mcasp0_fixed { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24576000>; + }; + + clk_mcasp0: clk_mcasp0 { + #clock-cells = <0>; + compatible = "gpio-gate-clock"; + clocks = <&clk_mcasp0_fixed>; + enable-gpios = <&gpio1 27 0>; /* BeagleBone Black Clk enable on GPIO1_27 */ + }; + + sound { + compatible = "simple-audio-card"; + simple-audio-card,name = "TI BeagleBone Black"; + simple-audio-card,format = "i2s"; + simple-audio-card,bitclock-master = <&dailink0_master>; + simple-audio-card,frame-master = <&dailink0_master>; + + dailink0_master: simple-audio-card,cpu { + sound-dai = <&mcasp0>; + clocks = <&clk_mcasp0>; + }; + + simple-audio-card,codec { + sound-dai = <&tda19988>; }; }; };
participants (3)
-
Jyri Sarha
-
Mark Brown
-
Russell King - ARM Linux