[alsa-devel] [RFC 0/5] another generic audio hdmi codec proposal
I 'm working on HDMI implementation for sti platform. As some discussions are on going for audio on HDMI. Here is the begin of my work...
This patch set is a tentative to implement a generic code for the HDMI audio. Main concept are aligned with solution proposeded for TI platform. - ASoC codec driver registered by DRM driver - ASOC driver is a generic driver. - compatible with simple card
Difference is that i propose a DRM generic interface based on bridge structure. Advantage is that all data exchanges are done through the DRM API.
I think also that some helper functions could been used for N and CTS parameters calculation, as suggested by Russell King in a previous mail.
I full aware that some features (like ELD and info frame) are partially or not implemented in my patches. This patch set is more a skeleton than a full implementation... I just post it to suggest a possible DRM API.
Arnaud Pouliquen (5): video: hdmi: add help function for N and cts drm: add helper functionto add audio capabilities for bridge ASoC: codec: hdmi drm codec driver drm: sti: connect audio driver DT: sti: add audio HDMI dai link in audio card
arch/arm/boot/dts/stih410.dtsi | 4 +- arch/arm/boot/dts/stihxxx-b2120.dtsi | 21 +++++ arch/arm/configs/multi_v7_defconfig | 1 + drivers/gpu/drm/drm_bridge.c | 114 +++++++++++++++++++++++++++ drivers/gpu/drm/sti/sti_hdmi.c | 146 +++++++++++++++++++++++++++++++--- drivers/gpu/drm/sti/sti_hdmi.h | 3 + drivers/video/hdmi.c | 148 +++++++++++++++++++++++++++++++++++ include/drm/drm_crtc.h | 31 ++++++++ include/linux/hdmi.h | 24 ++++++ include/sound/hdmi_drm.h | 16 ++++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdmi_drm.c | 125 +++++++++++++++++++++++++++++ 13 files changed, 625 insertions(+), 14 deletions(-) create mode 100644 include/sound/hdmi_drm.h create mode 100644 sound/soc/codecs/hdmi_drm.c
Add helper function to compute CTS and N parameters base on table described in HDMI specification 1.4b
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/video/hdmi.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/hdmi.h | 8 +++ 2 files changed, 156 insertions(+)
diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c index 1626892..9c487fb 100644 --- a/drivers/video/hdmi.c +++ b/drivers/video/hdmi.c @@ -1242,3 +1242,151 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer) return ret; } EXPORT_SYMBOL(hdmi_infoframe_unpack); + +/* + * audio clock regeneration (acr) parameters + * N and CTS computation are based on HDMI specification 1.4b + */ + +enum audio_rate { + HDMI_AUDIO_N_CTS_32KHZ, + HDMI_AUDIO_N_CTS_44_1KHZ, + HDMI_AUDIO_N_CTS_48KHZ, +}; + +struct hdmi_audio_acr { + unsigned long pixel_clk; + struct hdmi_audio_n_cts n_cts; +}; + +static const struct hdmi_audio_acr hdmi_audio_standard_acr[3][12] = { + { /*32 kHz*/ + { 25175, { 4576, 28125 } }, /* 25,20/1.001 MHz */ + { 25200, { 4096, 25200 } }, /* 25.20 MHz */ + { 27000, { 4096, 27000 } }, /* 27.00 MHz */ + { 27027, { 4096, 27027 } }, /* 27.00*1.001 MHz */ + { 54000, { 4096, 54000 } }, /* 54.00 MHz */ + { 54054, { 4096, 54054 } }, /* 54.00*1.001 MHz */ + { 74176, { 11648, 310938 } }, /* 74.25/1.001 MHz */ + { 74250, { 4096, 74250 } }, /* 74.25 MHz */ + { 148352, { 11648, 421875 } }, /* 148.50/1.001 MHz */ + { 148500, { 4096, 148500 } }, /* 148.50 MHz */ + { 296703, { 5824, 421875 } }, /* 297/1.001 MHz */ + { 297000, { 3072, 222750 } }, /* 297 MHz */ + }, + { /*44.1 kHz, 88.2 kHz 176.4 kHz*/ + { 25175, { 7007, 31250 } }, /* 25,20/1.001 MHz */ + { 25200, { 6272, 28000 } }, /* 25.20 MHz */ + { 27000, { 6272, 30000 } }, /* 27.00 MHz */ + { 27027, { 6272, 30030 } }, /* 27.00*1.001 MHz */ + { 54000, { 6272, 60000 } }, /* 54.00 MHz */ + { 54054, { 6272, 60060 } }, /* 54.00*1.001 MHz */ + { 74176, { 17836, 234375 } }, /* 74.25/1.001 MHz */ + { 74250, { 6272, 82500 } }, /* 74.25 MHz */ + { 148352, { 8918, 234375 } }, /* 148.50/1.001 MHz */ + { 148500, { 6272, 165000 } }, /* 148.50 MHz */ + { 296703, { 4459, 234375 } }, /* 297/1.001 MHz */ + { 297000, { 4704, 247500 } }, /* 297 MHz */ + }, + { /*48 kHz, 96 kHz 192 kHz*/ + { 25175, { 6864, 28125 } }, /* 25,20/1.001 MHz */ + { 25200, { 6144, 25200 } }, /* 25.20 MHz */ + { 27000, { 6144, 27000 } }, /* 27.00 MHz */ + { 27027, { 6144, 27027 } }, /* 27.00*1.001 MHz */ + { 54000, { 6144, 54000 } }, /* 54.00 MHz */ + { 54054, { 6144, 54054 } }, /* 54.00*1.001 MHz */ + { 74176, { 11648, 140625 } }, /* 74.25/1.001 MHz */ + { 74250, { 6144, 74250 } }, /* 74.25 MHz */ + { 148352, { 5824, 140625 } }, /* 148.50/1.001 MHz */ + { 148500, { 6144, 148500 } }, /* 148.50 MHz */ + { 296703, { 5824, 281250 } }, /* 297/1.001 MHz */ + { 297000, { 5120, 247500 } }, /* 297 MHz */ + } +}; + +/** + * hdmi_compute_n_cts() - compute N and CTS parameters + * @audio_clk: audio frame clock frequency in KHz + * @pixel_clk: pixel cloack frequency in kHz + * @n_cts: N and CTS parameter returned to user + * + * Values computed are based on table described in HDMI specification 1.4b + * + * Returns 0 on success or a negative error code on failure. + */ + +int hdmi_audio_compute_n_cts(unsigned int audio_fs, unsigned long pixel_clk, + struct hdmi_audio_n_cts *n_cts) +{ + int audio_freq_id, i; + int ratio = 1; + const struct hdmi_audio_acr *acr_table; + const struct hdmi_audio_n_cts *predef_n_cts = NULL; + + switch (audio_fs) { + case 32000: + audio_freq_id = HDMI_AUDIO_N_CTS_32KHZ; + n_cts->n = 4096; + break; + + case 44100: + audio_freq_id = HDMI_AUDIO_N_CTS_44_1KHZ; + n_cts->n = 6272; + break; + + case 48000: + audio_freq_id = HDMI_AUDIO_N_CTS_48KHZ; + n_cts->n = 6144; + break; + + case 88200: + audio_freq_id = HDMI_AUDIO_N_CTS_44_1KHZ; + ratio = 2; + n_cts->n = 6272 * 2; + break; + + case 96000: + audio_freq_id = HDMI_AUDIO_N_CTS_48KHZ; + ratio = 2; + n_cts->n = 6144 * 2; + break; + + case 176400: + audio_freq_id = HDMI_AUDIO_N_CTS_44_1KHZ; + ratio = 2; + n_cts->n = 6272 * 4; + break; + + case 192000: + audio_freq_id = HDMI_AUDIO_N_CTS_48KHZ; + ratio = 4; + n_cts->n = 6144 * 4; + break; + + default: + return -EINVAL; + } + + acr_table = hdmi_audio_standard_acr[audio_freq_id]; + for (i = 0; i < ARRAY_SIZE(hdmi_audio_standard_acr[0]); i++) { + if (pixel_clk == acr_table[i].pixel_clk) { + predef_n_cts = &acr_table[i].n_cts; + break; + } + } + + if (!predef_n_cts) { + /* + * predifined frequency not found compute CTS using formula: + * CTS = (Ftdms_clk * N) / (128* audio_fs) + */ + n_cts->cts = pixel_clk * n_cts->n / (128 * audio_fs); + } else { + n_cts->n = predef_n_cts->n * ratio; + n_cts->cts = predef_n_cts->cts; + } + + return 0; +} +EXPORT_SYMBOL(hdmi_audio_compute_n_cts); + diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h index e974420..32107a0 100644 --- a/include/linux/hdmi.h +++ b/include/linux/hdmi.h @@ -333,4 +333,12 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer); void hdmi_infoframe_log(const char *level, struct device *dev, union hdmi_infoframe *frame);
+struct hdmi_audio_n_cts { + unsigned int n; + unsigned int cts; +}; + +int hdmi_audio_compute_n_cts(unsigned int audio_fs, unsigned long pixel_clk, + struct hdmi_audio_n_cts *n_cts); + #endif /* _DRM_HDMI_H */
Extend bridge capabilities for audio to enable to connect an audio driver to a DRM driver with audio capabilities
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/gpu/drm/drm_bridge.c | 114 +++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_crtc.h | 31 ++++++++++++ include/linux/hdmi.h | 16 ++++++ 3 files changed, 161 insertions(+)
diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 6b8f721..2284ac9 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -328,6 +328,120 @@ struct drm_bridge *of_drm_find_bridge(struct device_node *np) EXPORT_SYMBOL(of_drm_find_bridge); #endif
+/** + * DOC: audio bridge callbacks + * + * The drm_audio_bridge_funcs ops are populated by the bridge driver that have + * audio capabilities (a.e HDMI) + * The drm internals(atomic and crtc helpers) use the helpers defined in + * drm_bridge.c + * These helpers call a specific drm_audio_bridge_funcs ops for all the bridges + * during encoder configuration. + * + * When creating a bridge driver, one can implement drm_audio_bridge_funcs op + * with the help of these rough rules: + * + * pre_enable: this contains things needed to be done for the bridge before + * audio is enabled by its source. + * + * enable: this contains things needed to be done for the audio bridge once its + * source is enabled. In other words, enable is called once the source is + * ready to start stream rendering. + * + * disable: this contains things needed to be done for audio bridge when to + * disable the audio part for the bridge, assuming that its source is still + * enabled. + * + * post_disable: this contains things needed to be done for the bridge once + * its source is disabled. + * + * mode_set: this sets up the mode for the audio bridge. It assumes that its + * source (an encoder or a bridge) has set the mode too. + */ + +/** + * drm_audio_bridge_pre_enable - calls 'pre_enable' drm_audio_bridge_funcs ops + * for audio bridges in the encoder chain. + * @bridge: bridge control structure + * + * Calls 'pre_enable' drm_audio_bridge_funcs op for audio bridge in the + * encoder chain. + * + */ + +void drm_audio_bridge_pre_enable(struct drm_bridge *bridge) +{ + if (!bridge) + return; + + if (bridge->audio_funcs->pre_enable) + bridge->audio_funcs->pre_enable(bridge); +} +EXPORT_SYMBOL(drm_audio_bridge_pre_enable); + +/** + * drm_audio_bridge_disable - calls 'disable' drm_audio_bridge_funcs op for + * audio bridge in the encoder chain. + * @bridge: bridge control structure + * + * Calls 'disable' drm_audio_bridge_funcs op for bridges with audio in the + * encoder chain. + * + */ +void drm_audio_bridge_disable(struct drm_bridge *bridge) +{ + if (!bridge) + return; + + if (bridge->audio_funcs->disable) + bridge->audio_funcs->disable(bridge); +} +EXPORT_SYMBOL(drm_audio_bridge_disable); + +/** + * drm_audio_bridge_mode_set - set audio mode for audio bridge in the + * encoder chain + * @bridge: bridge control structure + * @mode: desired audio mode to be set for the audio bridge + * + * Calls 'mode_set' drm_audio_bridge_funcs op for audio bridge in the + * encoder chain. + * + */ +void drm_audio_bridge_mode_set(struct drm_bridge *bridge, + struct hdmi_audio_mode *mode) +{ + if (!bridge) + return; + + if (bridge->audio_funcs->mode_set) + bridge->audio_funcs->mode_set(bridge, mode); +} +EXPORT_SYMBOL(drm_audio_bridge_mode_set); + +/** + * drm_audio_bridge_enable - calls 'enable' drm_audio_bridge_funcs op for all bridges + * in the encoder chain. + * @bridge: bridge control structure + * + * Calls 'enable' drm_audio_bridge_funcs op for all the bridges in the encoder + * chain, starting from the first bridge to the last. These are called + * after completing the encoder's commit op. + * + * Note that the bridge passed should be the one closest to the encoder + */ +void drm_audio_bridge_enable(struct drm_bridge *bridge) +{ + if (!bridge) + return; + + if (bridge->audio_funcs->mode_set) + bridge->audio_funcs->enable(bridge); + +} +EXPORT_SYMBOL(drm_audio_bridge_enable); + + MODULE_AUTHOR("Ajay Kumar ajaykumar.rs@samsung.com"); MODULE_DESCRIPTION("DRM bridge infrastructure"); MODULE_LICENSE("GPL and additional rights"); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index 3b4d8a4..5074019 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -583,6 +583,7 @@ struct drm_encoder_funcs { * @possible_clones: bitmask of potential sibling encoders for cloning * @crtc: currently bound CRTC * @bridge: bridge associated to the encoder + * @abridge: optional audio bridge associated to the encoder (HDMI) * @funcs: control functions * @helper_private: mid-layer private data * @@ -601,6 +602,7 @@ struct drm_encoder {
struct drm_crtc *crtc; struct drm_bridge *bridge; + struct drm_bridge *abridge; const struct drm_encoder_funcs *funcs; const void *helper_private; }; @@ -905,6 +907,24 @@ struct drm_bridge_funcs { };
/** + * struct drm_audio_bridge_funcs - audio drm_bridge control functions + * @attach: Called during drm_audio_bridge_attach + * @mode_fixup: Try to fixup (or reject entirely) proposed mode for this bridge + * @disable: Called right before encoder prepare, disables the bridge + * @post_disable: Called right after encoder prepare, for lockstepped disable + * @mode_set: Set this mode to the bridge + * @pre_enable: Called right before encoder commit, for lockstepped commit + * @enable: Called right after encoder commit, enables the bridge + */ +struct drm_audio_bridge_funcs { + void (*disable)(struct drm_bridge *bridge); + void (*pre_enable)(struct drm_bridge *bridge); + void (*enable)(struct drm_bridge *bridge); + int (*mode_set)(struct drm_bridge *bridge, + struct hdmi_audio_mode *mode); +}; + +/** * struct drm_bridge - central DRM bridge control structure * @dev: DRM device this bridge belongs to * @encoder: encoder to which this bridge is connected @@ -925,7 +945,9 @@ struct drm_bridge { struct list_head list;
const struct drm_bridge_funcs *funcs; + const struct drm_audio_bridge_funcs *audio_funcs; void *driver_private; + void *snd_private; };
/** @@ -1271,6 +1293,15 @@ extern int drm_encoder_init(struct drm_device *dev, const struct drm_encoder_funcs *funcs, int encoder_type);
+int drm_audio_bridge_attach(struct drm_device *dev, + struct drm_bridge *bridge); +void drm_audio_bridge_disable(struct drm_bridge *bridge); +void drm_audio_bridge_mode_set(struct drm_bridge *bridge, + struct hdmi_audio_mode *mode); +void drm_audio_bridge_pre_enable(struct drm_bridge *bridge); +void drm_audio_bridge_enable(struct drm_bridge *bridge); + + /** * drm_encoder_crtc_ok - can a given crtc drive a given encoder? * @encoder: encoder to test diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h index 32107a0..d3d1a80 100644 --- a/include/linux/hdmi.h +++ b/include/linux/hdmi.h @@ -333,11 +333,27 @@ int hdmi_infoframe_unpack(union hdmi_infoframe *frame, void *buffer); void hdmi_infoframe_log(const char *level, struct device *dev, union hdmi_infoframe *frame);
+/** + * struct hdmi_audio_n_cts - n and cts parameter for ACR packets + */ struct hdmi_audio_n_cts { unsigned int n; unsigned int cts; };
+/** + * struct hdmi_audio_mode - hdmi audio structure for audio configuration + * @enabled audio state + * @infoframe: audio infoframe info frame + * @data: private data use by alsa driver to retrieve context + * + * This is used by audio driver to configure the HDMI audio part + */ +struct hdmi_audio_mode { + bool enabled; + struct hdmi_audio_infoframe infoframe; +}; + int hdmi_audio_compute_n_cts(unsigned int audio_fs, unsigned long pixel_clk, struct hdmi_audio_n_cts *n_cts);
Add a generic codec to interface audio with DRM drivers
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- include/sound/hdmi_drm.h | 16 ++++++ sound/soc/codecs/Kconfig | 4 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdmi_drm.c | 125 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 include/sound/hdmi_drm.h create mode 100644 sound/soc/codecs/hdmi_drm.c
diff --git a/include/sound/hdmi_drm.h b/include/sound/hdmi_drm.h new file mode 100644 index 0000000..0146b88 --- /dev/null +++ b/include/sound/hdmi_drm.h @@ -0,0 +1,16 @@ +/* + * Interface for HDMI DRM codec + * + * Author: Arnaud Pouliquen arnaud.pouliquen@st.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. + */ +#ifndef __HDMI_DRM__H__ +#define __HDMI_DRM__H__ + +int hdmi_drm_codec_register(struct device *dev); +void hdmi_drm_codec_unregister(struct device *dev); + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0c9733e..922af30 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -80,6 +80,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C select SND_SOC_HDMI_CODEC + select SND_SOC_HDMI_DRM_CODEC select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008 @@ -445,6 +446,9 @@ config SND_SOC_DMIC config SND_SOC_HDMI_CODEC tristate "HDMI stub CODEC"
+config SND_SOC_HDMI_DRM_CODEC + tristate "HDMI DRM CODEC" + config SND_SOC_ES8328 tristate "Everest Semi ES8328 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a32077..c92aaf7 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -73,6 +73,7 @@ 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-hdmi-drm-codec-objs := hdmi_drm.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o @@ -265,6 +266,7 @@ 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_HDMI_DRM_CODEC) += snd-soc-hdmi-drm-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_drm.c b/sound/soc/codecs/hdmi_drm.c new file mode 100644 index 0000000..2df9a8f --- /dev/null +++ b/sound/soc/codecs/hdmi_drm.c @@ -0,0 +1,125 @@ +/* + * ALSA SoC codec driver for DRM HDMI device. + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Arnaud Pouliquen arnaud.pouliquen@st.com + * for STMicroelectronics. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <drm/drm_crtc_helper.h> + +struct hdmi_drm_dai_data { + struct drm_bridge *bridge; +}; + +static const struct snd_soc_dapm_widget hdmi_drm_widgets[] = { + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route hdmi_drm_routes[] = { + { "TX", NULL, "Playback" }, +}; + +int hdmi_drm_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct hdmi_drm_dai_data *priv = snd_soc_dai_get_drvdata(dai); + + dev_err(dai->dev, "%s: enter for bridge %p\n", __func__, priv->bridge); + drm_audio_bridge_pre_enable(priv->bridge); + return 0; +} + +int hdmi_drm_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct hdmi_drm_dai_data *priv = snd_soc_dai_get_drvdata(dai); + + dev_err(dai->dev, "%s: enter\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + drm_audio_bridge_enable(priv->bridge); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + drm_audio_bridge_disable(priv->bridge); + break; + } + + return 0; +} + +static int st_hdmi_dai_probe(struct snd_soc_dai *dai) +{ + struct hdmi_drm_dai_data *priv; + + dev_err(dai->dev, "%s: enter\n", __func__); + priv = devm_kzalloc(dai->dev, sizeof(*priv), GFP_KERNEL); + + priv->bridge = of_drm_find_bridge(dai->dev->of_node); + + dev_err(dai->dev, "%s: bridge %p\n", __func__, priv->bridge); + + snd_soc_dai_set_drvdata(dai, priv); + + return 0; +} + +static const struct snd_soc_dai_ops hdmi_drm_codec_ops = { + .prepare = hdmi_drm_dai_prepare, + .trigger = hdmi_drm_dai_trigger, +}; + +static struct snd_soc_dai_driver hdmi_drm_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, + }, + .probe = st_hdmi_dai_probe, + .ops = &hdmi_drm_codec_ops, +}; + +static struct snd_soc_codec_driver hdmi_drm_codec = { + .dapm_widgets = hdmi_drm_widgets, + .num_dapm_widgets = ARRAY_SIZE(hdmi_drm_widgets), + .dapm_routes = hdmi_drm_routes, + .num_dapm_routes = ARRAY_SIZE(hdmi_drm_routes), + .ignore_pmdown_time = true, +}; + +int hdmi_drm_codec_register(struct device *dev) +{ + dev_err(dev, "%s: enter", __func__); + return snd_soc_register_codec(dev, &hdmi_drm_codec, + &hdmi_drm_codec_dai, 1); +} +EXPORT_SYMBOL_GPL(hdmi_drm_codec_register); + +void hdmi_drm_codec_unregister(struct device *dev) +{ + dev_err(dev, "%s: enter", __func__); + snd_soc_unregister_codec(dev); +} +EXPORT_SYMBOL_GPL(hdmi_drm_codec_unregister); + +MODULE_AUTHOR("Arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("ASoC HDMI codec driver"); +MODULE_LICENSE("GPL");
Despite my earlier comment this implementation and the related HW is quite similar in all significant aspects to the patch set posted couple of days ago [1] for Beaglebone-Black HDMI audio.
[1] http://permalink.gmane.org/gmane.linux.alsa.devel/144144
I have not yet gotten to bottom of drm-side audio bride part, but I am working on it. Bellow is couple of early comments to the ASoC part.
Best regards, Jyri
On 09/21/15 16:19, Arnaud Pouliquen wrote:
Add a generic codec to interface audio with DRM drivers
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com
include/sound/hdmi_drm.h | 16 ++++++ sound/soc/codecs/Kconfig | 4 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hdmi_drm.c | 125 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 include/sound/hdmi_drm.h create mode 100644 sound/soc/codecs/hdmi_drm.c
diff --git a/include/sound/hdmi_drm.h b/include/sound/hdmi_drm.hhere are several important callbacks missing here new file mode 100644 index 0000000..0146b88 --- /dev/null +++ b/include/sound/hdmi_drm.h @@ -0,0 +1,16 @@ +/*
- Interface for HDMI DRM codec
- Author: Arnaud Pouliquen arnaud.pouliquen@st.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.
- */
+#ifndef __HDMI_DRM__H__ +#define __HDMI_DRM__H__
+int hdmi_drm_codec_register(struct device *dev); +void hdmi_drm_codec_unregister(struct device *dev);
+#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0c9733e..922af30 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -80,6 +80,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MC13783 if MFD_MC13XXX select SND_SOC_ML26124 if I2C select SND_SOC_HDMI_CODEC
- select SND_SOC_HDMI_DRM_CODEC select SND_SOC_PCM1681 if I2C select SND_SOC_PCM1792A if SPI_MASTER select SND_SOC_PCM3008
@@ -445,6 +446,9 @@ config SND_SOC_DMIC config SND_SOC_HDMI_CODEC tristate "HDMI stub CODEC"
+config SND_SOC_HDMI_DRM_CODEC
tristate "HDMI DRM CODEC"
- config SND_SOC_ES8328 tristate "Everest Semi ES8328 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a32077..c92aaf7 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -73,6 +73,7 @@ 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-hdmi-drm-codec-objs := hdmi_drm.o snd-soc-pcm1681-objs := pcm1681.o snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o @@ -265,6 +266,7 @@ 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_HDMI_DRM_CODEC) += snd-soc-hdmi-drm-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_drm.c b/sound/soc/codecs/hdmi_drm.c new file mode 100644 index 0000000..2df9a8f --- /dev/null +++ b/sound/soc/codecs/hdmi_drm.c @@ -0,0 +1,125 @@ +/*
- ALSA SoC codec driver for DRM HDMI device.
- Copyright (C) STMicroelectronics SA 2015
- Authors: Arnaud Pouliquen arnaud.pouliquen@st.com
for STMicroelectronics.
- License terms: GNU General Public License (GPL), version 2
- */
+#include <linux/module.h> +#include <sound/soc.h> +#include <linux/of.h> +#include <linux/of_device.h>
+#include <drm/drm_crtc_helper.h>
+struct hdmi_drm_dai_data {
- struct drm_bridge *bridge;
+};here are several important callbacks missing here
+static const struct snd_soc_dapm_widget hdmi_drm_widgets[] = {
- SND_SOC_DAPM_OUTPUT("TX"),
+};
+static const struct snd_soc_dapm_route hdmi_drm_routes[] = {
- { "TX", NULL, "Playback" },
+};
+int hdmi_drm_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct hdmi_drm_dai_data *priv = snd_soc_dai_get_drvdata(dai);
- dev_err(dai->dev, "%s: enter for bridge %p\n", __func__, priv->bridge);
- drm_audio_bridge_pre_enable(priv->bridge);
- return 0;
+}
+int hdmi_drm_dai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
+{
- struct hdmi_drm_dai_data *priv = snd_soc_dai_get_drvdata(dai);
- dev_err(dai->dev, "%s: enter\n", __func__);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- case SNDRV_PCM_TRIGGER_RESUME:
drm_audio_bridge_enable(priv->bridge);
break;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
drm_audio_bridge_disable(priv->bridge);
break;
- }
- return 0;here are several important callbacks missing here
+}
+static int st_hdmi_dai_probe(struct snd_soc_dai *dai) +{
- struct hdmi_drm_dai_data *priv;
- dev_err(dai->dev, "%s: enter\n", __func__);
- priv = devm_kzalloc(dai->dev, sizeof(*priv), GFP_KERNEL);
- priv->bridge = of_drm_find_bridge(dai->dev->of_node);
- dev_err(dai->dev, "%s: bridge %p\n", __func__, priv->bridge);
- snd_soc_dai_set_drvdata(dai, priv);
The call above overwrites the private data pointer of the drivers that registering the codec. This hardly works in general.
A separate platform driver - with this already merged patch [2] - that I use with my patch-set solves this issue quite nicely.
[2] http://lists.freedesktop.org/archives/dri-devel/2015-May/083517.html
- return 0;
+}here are several important callbacks missing here
+static const struct snd_soc_dai_ops hdmi_drm_codec_ops = {
.prepare = hdmi_drm_dai_prepare,
.trigger = hdmi_drm_dai_trigger,
+};
At least set_daifmt() and hw_params() callbacks should be defined before this could be generally usable. HDMI encoders do not usually support too many daifmts, but the driver should be able the check that the selected format is supported. But as you said this not complete code yet.
+static struct snd_soc_dai_driver hdmi_drm_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,
- },11.3
- .probe = st_hdmi_dai_probe,
- .ops = &hdmi_drm_codec_ops,
+};
+static struct snd_soc_codec_driver hdmi_drm_codec = {
- .dapm_widgets = hdmi_drm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(hdmi_drm_widgets),
- .dapm_routes = hdmi_drm_routes,
- .num_dapm_routes = ARRAY_SIZE(hdmi_drm_routes),
- .ignore_pmdown_time = true,
+};
+int hdmi_drm_codec_register(struct device *dev) +{
- dev_err(dev, "%s: enter", __func__);
- return snd_soc_register_codec(dev, &hdmi_drm_codec,
&hdmi_drm_codec_dai, 1);
+} +EXPORT_SYMBOL_GPL(hdmi_drm_codec_register);
+void hdmi_drm_codec_unregister(struct device *dev) +{
- dev_err(dev, "%s: enter", __func__);
- snd_soc_unregister_codec(dev);
+} +EXPORT_SYMBOL_GPL(hdmi_drm_codec_unregister);
+MODULE_AUTHOR("Arnaud.pouliquen@st.com"); +MODULE_DESCRIPTION("ASoC HDMI codec driver"); +MODULE_LICENSE("GPL");
Hello Jyri,
Yes using or not DRM bridge we should be able to have a common implementation
Please find,my answer belows
BR, Arnaud
On 09/25/2015 04:11 PM, Jyri Sarha wrote:
Despite my earlier comment this implementation and the related HW is quite similar in all significant aspects to the patch set posted couple of days ago [1] for Beaglebone-Black HDMI audio.
[1] http://permalink.gmane.org/gmane.linux.alsa.devel/144144
yes i trying to align my dev on it. to match with your development. Aim for me is to reuse it and adapt it using a DRM bridge interface. i hope to provide a V2 next week.
I have not yet gotten to bottom of drm-side audio bride part, but I am working on it. Bellow is couple of early comments to the ASoC part.
Best regards, Jyri
On 09/21/15 16:19, Arnaud Pouliquen wrote:
+static int st_hdmi_dai_probe(struct snd_soc_dai *dai) +{
- struct hdmi_drm_dai_data *priv;
- dev_err(dai->dev, "%s: enter\n", __func__);
- priv = devm_kzalloc(dai->dev, sizeof(*priv), GFP_KERNEL);
- priv->bridge = of_drm_find_bridge(dai->dev->of_node);
- dev_err(dai->dev, "%s: bridge %p\n", __func__, priv->bridge);
- snd_soc_dai_set_drvdata(dai, priv);
The call above overwrites the private data pointer of the drivers that registering the codec. This hardly works in general.
A separate platform driver - with this already merged patch [2] - that I use with my patch-set solves this issue quite nicely.
[2] http://lists.freedesktop.org/archives/dri-devel/2015-May/083517.html
Yes same dev,(but no crash...?).i need to define sub node.
- return 0;
+}here are several important callbacks missing here
+static const struct snd_soc_dai_ops hdmi_drm_codec_ops = {
.prepare = hdmi_drm_dai_prepare,
.trigger = hdmi_drm_dai_trigger,
+};
At least set_daifmt() and hw_params() callbacks should be defined before this could be generally usable. HDMI encoders do not usually support too many daifmts, but the driver should be able the check that the selected format is supported. But as you said this not complete code yet.
I'm trying to match codec ops with following DRM audio bridge ops, that is similar to the existing drm_bridge_funcs structure. struct drm_audio_bridge_funcs { void (*disable)(struct drm_bridge *bridge); void (*post_disable)(struct drm_bridge *bridge); void (*pre_enable)(struct drm_bridge *bridge); void (*enable)(struct drm_bridge *bridge); int (*mode_set)(struct drm_bridge *bridge, struct hdmi_audio_mode *mode); uint8_t *(*mode_get)(struct drm_bridge *bridge); /*return eld*/ }; audio parameters should be part of struct hdmi_audio_mode that contains audio configurations ( info frame,iec, format, clk...)
On 09/25/15 18:50, Arnaud Pouliquen wrote:
Hello Jyri,
Yes using or not DRM bridge we should be able to have a common implementation
Please find,my answer belows
BR, Arnaud
On 09/25/2015 04:11 PM, Jyri Sarha wrote:
Despite my earlier comment this implementation and the related HW is quite similar in all significant aspects to the patch set posted couple of days ago [1] for Beaglebone-Black HDMI audio.
[1] http://permalink.gmane.org/gmane.linux.alsa.devel/144144
yes i trying to align my dev on it. to match with your development. Aim for me is to reuse it and adapt it using a DRM bridge interface. i hope to provide a V2 next week.
I have not yet gotten to bottom of drm-side audio bride part, but I am working on it. Bellow is couple of early comments to the ASoC part.
Best regards, Jyri
On 09/21/15 16:19, Arnaud Pouliquen wrote:
+static int st_hdmi_dai_probe(struct snd_soc_dai *dai) +{
- struct hdmi_drm_dai_data *priv;
- dev_err(dai->dev, "%s: enter\n", __func__);
- priv = devm_kzalloc(dai->dev, sizeof(*priv), GFP_KERNEL);
- priv->bridge = of_drm_find_bridge(dai->dev->of_node);
- dev_err(dai->dev, "%s: bridge %p\n", __func__, priv->bridge);
- snd_soc_dai_set_drvdata(dai, priv);
The call above overwrites the private data pointer of the drivers that registering the codec. This hardly works in general.
A separate platform driver - with this already merged patch [2] - that I use with my patch-set solves this issue quite nicely.
[2] http://lists.freedesktop.org/archives/dri-devel/2015-May/083517.html
Yes same dev,(but no crash...?).i need to define sub node.
- return 0;
+}here are several important callbacks missing here
+static const struct snd_soc_dai_ops hdmi_drm_codec_ops = {
.prepare = hdmi_drm_dai_prepare,
.trigger = hdmi_drm_dai_trigger,
+};
At least set_daifmt() and hw_params() callbacks should be defined before this could be generally usable. HDMI encoders do not usually support too many daifmts, but the driver should be able the check that the selected format is supported. But as you said this not complete code yet.
I'm trying to match codec ops with following DRM audio bridge ops, that is similar to the existing drm_bridge_funcs structure.
I am not yet too familiar with drm way of doing things. My code is trying to follow the way how ALSA does things. I tried to survive with as few callback as possible, but if you think more is needed I can add those if there is a corresponding callback in ALSA.
struct drm_audio_bridge_funcs { void (*disable)(struct drm_bridge *bridge);
There is no such thing in my HDMI codec. However, there is digital_mute callback that is used by alsa before the streams are shut down to avoid undesired pops and clicks.
void (*post_disable)(struct drm_bridge *bridge);
Post_disable should map more or less directly to audio_shutdown() in my code.
void (*pre_enable)(struct drm_bridge *bridge);
audio_startup() and hw_params() should both be called at pre_enable() phase.
void (*enable)(struct drm_bridge *bridge);
... or one could see hw_params() to map to enable. And there is digital_mute which is toggled by ALSA at this phase.
int (*mode_set)(struct drm_bridge *bridge, struct hdmi_audio_mode *mode);
Actually hw_params() does pretty much the same thing as set_mode(), but it should be called after audio_startup() has been called.
uint8_t *(*mode_get)(struct drm_bridge *bridge); /*return eld*/
};
For this there is get_eld() in my HDMI codec code.
audio parameters should be part of struct hdmi_audio_mode that contains audio configurations ( info frame,iec, format, clk...)
BTW, the HDMI codec is made in such a way that one can get by with only hw_params() and audio_shutdown(). In such an implementation hw_params() sets the HDMI encoder ready for receiving i2s or spdif from CPU DAI and audio_shutdown() disables the audio stream.
Best regards, Jyri
Add management of the audio bridge
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- drivers/gpu/drm/sti/sti_hdmi.c | 146 +++++++++++++++++++++++++++++++++++++---- drivers/gpu/drm/sti/sti_hdmi.h | 3 + 2 files changed, 137 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c index 06595e9..d324e0a 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.c +++ b/drivers/gpu/drm/sti/sti_hdmi.c @@ -17,6 +17,8 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h>
+#include <sound/hdmi_drm.h> + #include "sti_hdmi.h" #include "sti_hdmi_tx3g4c28phy.h" #include "sti_hdmi_tx3g0c55phy.h" @@ -34,6 +36,8 @@ #define HDMI_DFLT_CHL0_DAT 0x0110 #define HDMI_DFLT_CHL1_DAT 0x0114 #define HDMI_DFLT_CHL2_DAT 0x0118 +#define HDMI_AUDIO_CFG 0x0200 +#define HDMI_SPDIF_FIFO_STATUS 0x0204 #define HDMI_SW_DI_1_HEAD_WORD 0x0210 #define HDMI_SW_DI_1_PKT_WORD0 0x0214 #define HDMI_SW_DI_1_PKT_WORD1 0x0218 @@ -43,6 +47,9 @@ #define HDMI_SW_DI_1_PKT_WORD5 0x0228 #define HDMI_SW_DI_1_PKT_WORD6 0x022C #define HDMI_SW_DI_CFG 0x0230 +#define HDMI_SAMPLE_FLAT_MASK 0x0244 +#define HDMI_AUDN 0x0400 +#define HDMI_AUD_CTS 0x0404 #define HDMI_SW_DI_2_HEAD_WORD 0x0600 #define HDMI_SW_DI_2_PKT_WORD0 0x0604 #define HDMI_SW_DI_2_PKT_WORD1 0x0608 @@ -52,6 +59,7 @@ #define HDMI_SW_DI_2_PKT_WORD5 0x0618 #define HDMI_SW_DI_2_PKT_WORD6 0x061C
+ #define HDMI_IFRAME_SLOT_AVI 1 #define HDMI_IFRAME_SLOT_AUDIO 2
@@ -109,6 +117,27 @@
#define HDMI_STA_SW_RST BIT(1)
+#define HDMI_AUD_CFG_8CH BIT(0) +#define HDMI_AUD_CFG_SPDIF_DIV_2 BIT(1) +#define HDMI_AUD_CFG_SPDIF_DIV_3 BIT(2) +#define HDMI_AUD_CFG_SPDIF_CLK_DIV_4 (BIT(1) | BIT(1)) +#define HDMI_AUD_CFG_CTS_CLK_128FS BIT(17) +#define HDMI_AUD_CFG_CH12_VALID BIT(28) +#define HDMI_AUD_CFG_CH34_VALID BIT(29) +#define HDMI_AUD_CFG_CH56_VALID BIT(30) +#define HDMI_AUD_CFG_CH78_VALID BIT(31) + +/* sample flat mask */ +#define HDMI_SAMPLE_FLAT_NO 0 +#define HDMI_SAMPLE_FLAT_SP0 BIT(0) +#define HDMI_SAMPLE_FLAT_SP1 BIT(1) +#define HDMI_SAMPLE_FLAT_SP2 BIT(2) +#define HDMI_SAMPLE_FLAT_SP3 BIT(3) +#define HDMI_SAMPLE_FLAT_ALL (HDMI_SAMPLE_FLAT_SP0 \ + | HDMI_SAMPLE_FLAT_SP1 \ + | HDMI_SAMPLE_FLAT_SP2 \ + | HDMI_SAMPLE_FLAT_SP3) + #define HDMI_INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) #define HDMI_INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) #define HDMI_INFOFRAME_HEADER_LEN(x) (((x) & 0x0f) << 16) @@ -380,19 +409,13 @@ static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi) */ static int hdmi_audio_infoframe_config(struct sti_hdmi *hdmi) { - struct hdmi_audio_infoframe infofame; + struct hdmi_audio_infoframe *infoframe; u8 buffer[HDMI_INFOFRAME_SIZE(AUDIO)]; int ret;
- ret = hdmi_audio_infoframe_init(&infofame); - if (ret < 0) { - DRM_ERROR("failed to setup audio infoframe: %d\n", ret); - return ret; - } - - infofame.channels = 2; + infoframe = &hdmi->audio.infoframe;
- ret = hdmi_audio_infoframe_pack(&infofame, buffer, sizeof(buffer)); + ret = hdmi_audio_infoframe_pack(infoframe, buffer, sizeof(buffer)); if (ret < 0) { DRM_ERROR("failed to pack audio infoframe: %d\n", ret); return ret; @@ -404,6 +427,56 @@ static int hdmi_audio_infoframe_config(struct sti_hdmi *hdmi) }
/** + * set audio frame rate + * + * @hdmi: pointer on the hdmi internal structure + * + */ +static int hdmi_audio_set_infoframe(struct sti_hdmi *hdmi, + struct hdmi_audio_infoframe *info) +{ + struct hdmi_audio_n_cts n_cts; + int ret, audio_cfg; + + hdmi->audio.infoframe = *info; + + if (!hdmi->enabled) + return 0; + + /* update HDMI registers according to configuration */ + audio_cfg = HDMI_AUD_CFG_SPDIF_DIV_3 | HDMI_AUD_CFG_CTS_CLK_128FS; + + switch (info->channels) { + case 8: + audio_cfg |= HDMI_AUD_CFG_CH78_VALID; + case 6: + audio_cfg |= HDMI_AUD_CFG_CH56_VALID; + case 4: + audio_cfg |= HDMI_AUD_CFG_CH34_VALID | HDMI_AUD_CFG_8CH; + case 2: + audio_cfg = HDMI_AUD_CFG_CH12_VALID; + break; + default: + DRM_ERROR("ERROR: Unsupported number of channels (%d)!\n", + info->channels); + return -EINVAL; + } + + hdmi_write(hdmi, audio_cfg, HDMI_AUDIO_CFG); + + /* update N parameter */ + ret = hdmi_audio_compute_n_cts(info->sample_frequency, + hdmi->mode.clock * 1000, &n_cts); + + DRM_DEBUG_DRIVER("n= %d, cts = %d\n", n_cts.n, n_cts.cts); + + /* clear interrupt status */ + hdmi_write(hdmi, n_cts.n, HDMI_AUDN); + + return 0; +} + +/** * Software reset of the hdmi subsystem * * @hdmi: pointer on the hdmi internal structure @@ -462,7 +535,6 @@ static void sti_hdmi_disable(struct drm_bridge *bridge) /* Disable HDMI */ val &= ~HDMI_CFG_DEVICE_EN; hdmi_write(hdmi, val, HDMI_CFG); - hdmi_write(hdmi, 0xffffffff, HDMI_INT_CLR);
/* Stop the phy */ @@ -520,8 +592,9 @@ static void sti_hdmi_pre_enable(struct drm_bridge *bridge) DRM_ERROR("Unable to configure AVI infoframe\n");
/* Program AUDIO infoframe */ - if (hdmi_audio_infoframe_config(hdmi)) - DRM_ERROR("Unable to configure AUDIO infoframe\n"); + if (hdmi->audio.enabled) + if (hdmi_audio_infoframe_config(hdmi)) + DRM_ERROR("Unable to configure AUDIO infoframe\n");
/* Sw reset */ hdmi_swreset(hdmi); @@ -567,6 +640,43 @@ static const struct drm_bridge_funcs sti_hdmi_bridge_funcs = { .mode_set = sti_hdmi_set_mode, };
+void sti_hdmi_audio_disable(struct drm_bridge *bridge) +{ + struct sti_hdmi *hdmi = bridge->driver_private; + + DRM_ERROR("enter %s\n", __func__); + /* mute */ + hdmi_write(hdmi, HDMI_SAMPLE_FLAT_ALL, HDMI_SAMPLE_FLAT_MASK); +} + +int sti_hdmi_audio_set_mode(struct drm_bridge *bridge, + struct hdmi_audio_mode *mode) +{ + DRM_ERROR("enter %s\n", __func__); + return 0; +} + +void sti_hdmi_audio_pre_enable(struct drm_bridge *bridge) +{ + DRM_ERROR("enter %s\n", __func__); +} + +void sti_hdmi_audio_bridge_enable(struct drm_bridge *bridge) +{ + struct sti_hdmi *hdmi = bridge->driver_private; + + DRM_ERROR("enter %s\n", __func__); + /* unmute */ + hdmi_write(hdmi, HDMI_SAMPLE_FLAT_NO, HDMI_SAMPLE_FLAT_MASK); +} + +static const struct drm_audio_bridge_funcs sti_hdmi_audio_bridge_funcs = { + .pre_enable = sti_hdmi_audio_pre_enable, + .enable = sti_hdmi_audio_bridge_enable, + .disable = sti_hdmi_audio_disable, + .mode_set = sti_hdmi_audio_set_mode, +}; + static int sti_hdmi_connector_get_modes(struct drm_connector *connector) { struct sti_hdmi_connector *hdmi_connector @@ -658,6 +768,7 @@ static void sti_hdmi_connector_destroy(struct drm_connector *connector) struct sti_hdmi_connector *hdmi_connector = to_sti_hdmi_connector(connector);
+ drm_bridge_remove(connector->encoder->bridge); drm_connector_unregister(connector); drm_connector_cleanup(connector); kfree(hdmi_connector); @@ -715,8 +826,14 @@ static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
bridge->driver_private = hdmi; bridge->funcs = &sti_hdmi_bridge_funcs; + bridge->audio_funcs = &sti_hdmi_audio_bridge_funcs; drm_bridge_attach(drm_dev, bridge);
+ bridge->of_node = dev->of_node; + err = drm_bridge_add(bridge); + if (err) + goto err_adapt; + encoder->bridge = bridge; connector->encoder = encoder;
@@ -748,6 +865,7 @@ err_sysfs: drm_connector_unregister(drm_connector); err_connector: drm_connector_cleanup(drm_connector); + drm_bridge_remove(bridge); err_adapt: put_device(&hdmi->ddc_adapt->dev); return -EINVAL; @@ -877,6 +995,10 @@ static int sti_hdmi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, hdmi);
+ /* Register audio driver */ + if (hdmi_drm_codec_register(dev)) + DRM_INFO("Failed to register HDMI audio driver\n"); + return component_add(&pdev->dev, &sti_hdmi_ops); }
diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h index 3d22390..3b93a63 100644 --- a/drivers/gpu/drm/sti/sti_hdmi.h +++ b/drivers/gpu/drm/sti/sti_hdmi.h @@ -24,6 +24,7 @@ struct hdmi_phy_ops { void (*stop)(struct sti_hdmi *hdmi); };
+ /** * STI hdmi structure * @@ -36,6 +37,7 @@ struct hdmi_phy_ops { * @clk_tmds: hdmi tmds clock * @clk_phy: hdmi phy clock * @clk_audio: hdmi audio clock + * @audio: hdmi audio state * @irq: hdmi interrupt number * @irq_status: interrupt status register * @phy_ops: phy start/stop operations @@ -55,6 +57,7 @@ struct sti_hdmi { struct clk *clk_tmds; struct clk *clk_phy; struct clk *clk_audio; + struct hdmi_audio_mode audio; int irq; u32 irq_status; struct hdmi_phy_ops *phy_ops;
Add the HDMI dai link to support audio for HDMi output
Signed-off-by: Arnaud Pouliquen arnaud.pouliquen@st.com --- arch/arm/boot/dts/stih410.dtsi | 4 ++-- arch/arm/boot/dts/stihxxx-b2120.dtsi | 21 +++++++++++++++++++++ arch/arm/configs/multi_v7_defconfig | 1 + 3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/arch/arm/boot/dts/stih410.dtsi b/arch/arm/boot/dts/stih410.dtsi index 7ee7dc0..3881016 100644 --- a/arch/arm/boot/dts/stih410.dtsi +++ b/arch/arm/boot/dts/stih410.dtsi @@ -186,8 +186,9 @@ <&clk_s_d2_quadfs 0>; };
- sti-hdmi@8d04000 { + sti_hdmi: sti-hdmi@8d04000 { compatible = "st,stih407-hdmi"; + #sound-dai-cells = <0>; reg = <0x8d04000 0x1000>; reg-names = "hdmi-reg"; interrupts = <GIC_SPI 106 IRQ_TYPE_NONE>; @@ -210,7 +211,6 @@ reset-names = "hdmi"; resets = <&softreset STIH407_HDMI_TX_PHY_SOFTRESET>; ddc = <&hdmiddc>; - };
sti-hda@8d02000 { diff --git a/arch/arm/boot/dts/stihxxx-b2120.dtsi b/arch/arm/boot/dts/stihxxx-b2120.dtsi index 3ad9c82..80fed19 100644 --- a/arch/arm/boot/dts/stihxxx-b2120.dtsi +++ b/arch/arm/boot/dts/stihxxx-b2120.dtsi @@ -81,6 +81,9 @@ audio_controller: sti-asoc-platform { status = "okay"; }; + sti_uni_player0: sti-uni-player@0 { + status = "okay"; + }; sti_uni_player2: sti-uni-player@2 { status = "okay"; }; @@ -93,6 +96,12 @@ sti_uni_reader1: sti-uni-reader@1 { status = "okay"; }; + + sti-display-subsystem { + sti_hdmi: sti-hdmi@8d04000 { + status = "okay"; + }; + }; };
sound { @@ -125,6 +134,18 @@ sound-dai = <&sti_sasg_codec 0>; }; }; + simple-audio-card,dai-link@2 { + /* HDMI */ + format = "i2s"; + mclk-fs = <128>; + cpu { + sound-dai = <&sti_uni_player0>; + }; + + codec { + sound-dai = <&sti_hdmi>; + }; + }; };
}; diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index 2a44694..6f43666 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -461,6 +461,7 @@ CONFIG_SND_ATMEL_SOC=m CONFIG_SND_ATMEL_SOC_WM8904=m CONFIG_SND_SOC_SH4_FSI=m CONFIG_SND_SOC_RCAR=m +CONFIG_SND_SOC_HDMI_DRM_CODEC=y CONFIG_SND_SOC_STI=y CONFIG_SND_SOC_STI_SAS=y CONFIG_SND_SOC_TEGRA=m
participants (2)
-
Arnaud Pouliquen
-
Jyri Sarha