aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJyri Sarha <jsarha@ti.com>2016-03-31 09:36:00 -0400
committerMark Brown <broonie@kernel.org>2016-04-18 13:09:18 -0400
commit09184118a8abae030539469848d475adcc0e5839 (patch)
treea7f94de2481b06fdc80f678c2a04899eab7d77ee
parent4a462ce084d5beb92cfc68f53f88c035c82e6b59 (diff)
ASoC: hdmi-codec: Add hdmi-codec for external HDMI-encoders
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> Acked-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> Acked-by: PC Liao <pc.liao@mediatek.com> Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--include/sound/hdmi-codec.h100
-rw-r--r--sound/soc/codecs/Kconfig6
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/hdmi-codec.c396
4 files changed, 504 insertions, 0 deletions
diff --git a/include/sound/hdmi-codec.h b/include/sound/hdmi-codec.h
new file mode 100644
index 000000000000..fc3a481ad91e
--- /dev/null
+++ b/include/sound/hdmi-codec.h
@@ -0,0 +1,100 @@
1/*
2 * hdmi-codec.h - HDMI Codec driver API
3 *
4 * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
5 *
6 * Author: Jyri Sarha <jsarha@ti.com>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * version 2 as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 */
17
18#ifndef __HDMI_CODEC_H__
19#define __HDMI_CODEC_H__
20
21#include <linux/hdmi.h>
22#include <drm/drm_edid.h>
23#include <sound/asoundef.h>
24#include <uapi/sound/asound.h>
25
26/*
27 * Protocol between ASoC cpu-dai and HDMI-encoder
28 */
29struct hdmi_codec_daifmt {
30 enum {
31 HDMI_I2S,
32 HDMI_RIGHT_J,
33 HDMI_LEFT_J,
34 HDMI_DSP_A,
35 HDMI_DSP_B,
36 HDMI_AC97,
37 HDMI_SPDIF,
38 } fmt;
39 int bit_clk_inv:1;
40 int frame_clk_inv:1;
41 int bit_clk_master:1;
42 int frame_clk_master:1;
43};
44
45/*
46 * HDMI audio parameters
47 */
48struct hdmi_codec_params {
49 struct hdmi_audio_infoframe cea;
50 struct snd_aes_iec958 iec;
51 int sample_rate;
52 int sample_width;
53 int channels;
54};
55
56struct hdmi_codec_ops {
57 /*
58 * Called when ASoC starts an audio stream setup.
59 * Optional
60 */
61 int (*audio_startup)(struct device *dev);
62
63 /*
64 * Configures HDMI-encoder for audio stream.
65 * Mandatory
66 */
67 int (*hw_params)(struct device *dev,
68 struct hdmi_codec_daifmt *fmt,
69 struct hdmi_codec_params *hparms);
70
71 /*
72 * Shuts down the audio stream.
73 * Mandatory
74 */
75 void (*audio_shutdown)(struct device *dev);
76
77 /*
78 * Mute/unmute HDMI audio stream.
79 * Optional
80 */
81 int (*digital_mute)(struct device *dev, bool enable);
82
83 /*
84 * Provides EDID-Like-Data from connected HDMI device.
85 * Optional
86 */
87 int (*get_eld)(struct device *dev, uint8_t *buf, size_t len);
88};
89
90/* HDMI codec initalization data */
91struct hdmi_codec_pdata {
92 const struct hdmi_codec_ops *ops;
93 uint i2s:1;
94 uint spdif:1;
95 int max_i2s_channels;
96};
97
98#define HDMI_CODEC_DRV_NAME "hdmi-audio-codec"
99
100#endif /* __HDMI_CODEC_H__ */
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 649e92a252ae..06d0e0593ec3 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -88,6 +88,7 @@ config SND_SOC_ALL_CODECS
88 select SND_SOC_MC13783 if MFD_MC13XXX 88 select SND_SOC_MC13783 if MFD_MC13XXX
89 select SND_SOC_ML26124 if I2C 89 select SND_SOC_ML26124 if I2C
90 select SND_SOC_NAU8825 if I2C 90 select SND_SOC_NAU8825 if I2C
91 select SND_SOC_HDMI_CODEC
91 select SND_SOC_PCM1681 if I2C 92 select SND_SOC_PCM1681 if I2C
92 select SND_SOC_PCM179X_I2C if I2C 93 select SND_SOC_PCM179X_I2C if I2C
93 select SND_SOC_PCM179X_SPI if SPI_MASTER 94 select SND_SOC_PCM179X_SPI if SPI_MASTER
@@ -477,6 +478,11 @@ config SND_SOC_BT_SCO
477config SND_SOC_DMIC 478config SND_SOC_DMIC
478 tristate 479 tristate
479 480
481config SND_SOC_HDMI_CODEC
482 tristate
483 select SND_PCM_ELD
484 select SND_PCM_IEC958
485
480config SND_SOC_ES8328 486config SND_SOC_ES8328
481 tristate "Everest Semi ES8328 CODEC" 487 tristate "Everest Semi ES8328 CODEC"
482 488
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 185a712a7fe7..d7185dda58b8 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -81,6 +81,7 @@ snd-soc-max9850-objs := max9850.o
81snd-soc-mc13783-objs := mc13783.o 81snd-soc-mc13783-objs := mc13783.o
82snd-soc-ml26124-objs := ml26124.o 82snd-soc-ml26124-objs := ml26124.o
83snd-soc-nau8825-objs := nau8825.o 83snd-soc-nau8825-objs := nau8825.o
84snd-soc-hdmi-codec-objs := hdmi-codec.o
84snd-soc-pcm1681-objs := pcm1681.o 85snd-soc-pcm1681-objs := pcm1681.o
85snd-soc-pcm179x-codec-objs := pcm179x.o 86snd-soc-pcm179x-codec-objs := pcm179x.o
86snd-soc-pcm179x-i2c-objs := pcm179x-i2c.o 87snd-soc-pcm179x-i2c-objs := pcm179x-i2c.o
@@ -290,6 +291,7 @@ obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
290obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o 291obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o
291obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o 292obj-$(CONFIG_SND_SOC_ML26124) += snd-soc-ml26124.o
292obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o 293obj-$(CONFIG_SND_SOC_NAU8825) += snd-soc-nau8825.o
294obj-$(CONFIG_SND_SOC_HDMI_CODEC) += snd-soc-hdmi-codec.o
293obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o 295obj-$(CONFIG_SND_SOC_PCM1681) += snd-soc-pcm1681.o
294obj-$(CONFIG_SND_SOC_PCM179X) += snd-soc-pcm179x-codec.o 296obj-$(CONFIG_SND_SOC_PCM179X) += snd-soc-pcm179x-codec.o
295obj-$(CONFIG_SND_SOC_PCM179X_I2C) += snd-soc-pcm179x-i2c.o 297obj-$(CONFIG_SND_SOC_PCM179X_I2C) += snd-soc-pcm179x-i2c.o
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
new file mode 100644
index 000000000000..b46b8edb9319
--- /dev/null
+++ b/sound/soc/codecs/hdmi-codec.c
@@ -0,0 +1,396 @@
1/*
2 * ALSA SoC codec for HDMI encoder drivers
3 * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
4 * Author: Jyri Sarha <jsarha@ti.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * version 2 as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 */
15#include <linux/module.h>
16#include <linux/string.h>
17#include <sound/core.h>
18#include <sound/pcm.h>
19#include <sound/pcm_params.h>
20#include <sound/soc.h>
21#include <sound/pcm_drm_eld.h>
22#include <sound/hdmi-codec.h>
23#include <sound/pcm_iec958.h>
24
25#include <drm/drm_crtc.h> /* This is only to get MAX_ELD_BYTES */
26
27struct hdmi_codec_priv {
28 struct hdmi_codec_pdata hcd;
29 struct snd_soc_dai_driver *daidrv;
30 struct hdmi_codec_daifmt daifmt[2];
31 struct mutex current_stream_lock;
32 struct snd_pcm_substream *current_stream;
33 struct snd_pcm_hw_constraint_list ratec;
34 uint8_t eld[MAX_ELD_BYTES];
35};
36
37static const struct snd_soc_dapm_widget hdmi_widgets[] = {
38 SND_SOC_DAPM_OUTPUT("TX"),
39};
40
41static const struct snd_soc_dapm_route hdmi_routes[] = {
42 { "TX", NULL, "Playback" },
43};
44
45enum {
46 DAI_ID_I2S = 0,
47 DAI_ID_SPDIF,
48};
49
50static int hdmi_codec_new_stream(struct snd_pcm_substream *substream,
51 struct snd_soc_dai *dai)
52{
53 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
54 int ret = 0;
55
56 mutex_lock(&hcp->current_stream_lock);
57 if (!hcp->current_stream) {
58 hcp->current_stream = substream;
59 } else if (hcp->current_stream != substream) {
60 dev_err(dai->dev, "Only one simultaneous stream supported!\n");
61 ret = -EINVAL;
62 }
63 mutex_unlock(&hcp->current_stream_lock);
64
65 return ret;
66}
67
68static int hdmi_codec_startup(struct snd_pcm_substream *substream,
69 struct snd_soc_dai *dai)
70{
71 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
72 int ret = 0;
73
74 dev_dbg(dai->dev, "%s()\n", __func__);
75
76 ret = hdmi_codec_new_stream(substream, dai);
77 if (ret)
78 return ret;
79
80 if (hcp->hcd.ops->audio_startup) {
81 ret = hcp->hcd.ops->audio_startup(dai->dev->parent);
82 if (ret) {
83 mutex_lock(&hcp->current_stream_lock);
84 hcp->current_stream = NULL;
85 mutex_unlock(&hcp->current_stream_lock);
86 return ret;
87 }
88 }
89
90 if (hcp->hcd.ops->get_eld) {
91 ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->eld,
92 sizeof(hcp->eld));
93
94 if (!ret) {
95 ret = snd_pcm_hw_constraint_eld(substream->runtime,
96 hcp->eld);
97 if (ret)
98 return ret;
99 }
100 }
101 return 0;
102}
103
104static void hdmi_codec_shutdown(struct snd_pcm_substream *substream,
105 struct snd_soc_dai *dai)
106{
107 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
108
109 dev_dbg(dai->dev, "%s()\n", __func__);
110
111 WARN_ON(hcp->current_stream != substream);
112
113 hcp->hcd.ops->audio_shutdown(dai->dev->parent);
114
115 mutex_lock(&hcp->current_stream_lock);
116 hcp->current_stream = NULL;
117 mutex_unlock(&hcp->current_stream_lock);
118}
119
120static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
121 struct snd_pcm_hw_params *params,
122 struct snd_soc_dai *dai)
123{
124 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
125 struct hdmi_codec_params hp = {
126 .iec = {
127 .status = { 0 },
128 .subcode = { 0 },
129 .pad = 0,
130 .dig_subframe = { 0 },
131 }
132 };
133 int ret;
134
135 dev_dbg(dai->dev, "%s() width %d rate %d channels %d\n", __func__,
136 params_width(params), params_rate(params),
137 params_channels(params));
138
139 if (params_width(params) > 24)
140 params->msbits = 24;
141
142 ret = snd_pcm_create_iec958_consumer_hw_params(params, hp.iec.status,
143 sizeof(hp.iec.status));
144 if (ret < 0) {
145 dev_err(dai->dev, "Creating IEC958 channel status failed %d\n",
146 ret);
147 return ret;
148 }
149
150 ret = hdmi_codec_new_stream(substream, dai);
151 if (ret)
152 return ret;
153
154 hdmi_audio_infoframe_init(&hp.cea);
155 hp.cea.channels = params_channels(params);
156 hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
157 hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM;
158 hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM;
159
160 hp.sample_width = params_width(params);
161 hp.sample_rate = params_rate(params);
162 hp.channels = params_channels(params);
163
164 return hcp->hcd.ops->hw_params(dai->dev->parent, &hcp->daifmt[dai->id],
165 &hp);
166}
167
168static int hdmi_codec_set_fmt(struct snd_soc_dai *dai,
169 unsigned int fmt)
170{
171 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
172 struct hdmi_codec_daifmt cf = { 0 };
173 int ret = 0;
174
175 dev_dbg(dai->dev, "%s()\n", __func__);
176
177 if (dai->id == DAI_ID_SPDIF) {
178 cf.fmt = HDMI_SPDIF;
179 } else {
180 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
181 case SND_SOC_DAIFMT_CBM_CFM:
182 cf.bit_clk_master = 1;
183 cf.frame_clk_master = 1;
184 break;
185 case SND_SOC_DAIFMT_CBS_CFM:
186 cf.frame_clk_master = 1;
187 break;
188 case SND_SOC_DAIFMT_CBM_CFS:
189 cf.bit_clk_master = 1;
190 break;
191 case SND_SOC_DAIFMT_CBS_CFS:
192 break;
193 default:
194 return -EINVAL;
195 }
196
197 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
198 case SND_SOC_DAIFMT_NB_NF:
199 break;
200 case SND_SOC_DAIFMT_NB_IF:
201 cf.frame_clk_inv = 1;
202 break;
203 case SND_SOC_DAIFMT_IB_NF:
204 cf.bit_clk_inv = 1;
205 break;
206 case SND_SOC_DAIFMT_IB_IF:
207 cf.frame_clk_inv = 1;
208 cf.bit_clk_inv = 1;
209 break;
210 }
211
212 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
213 case SND_SOC_DAIFMT_I2S:
214 cf.fmt = HDMI_I2S;
215 break;
216 case SND_SOC_DAIFMT_DSP_A:
217 cf.fmt = HDMI_DSP_A;
218 break;
219 case SND_SOC_DAIFMT_DSP_B:
220 cf.fmt = HDMI_DSP_B;
221 break;
222 case SND_SOC_DAIFMT_RIGHT_J:
223 cf.fmt = HDMI_RIGHT_J;
224 break;
225 case SND_SOC_DAIFMT_LEFT_J:
226 cf.fmt = HDMI_LEFT_J;
227 break;
228 case SND_SOC_DAIFMT_AC97:
229 cf.fmt = HDMI_AC97;
230 break;
231 default:
232 dev_err(dai->dev, "Invalid DAI interface format\n");
233 return -EINVAL;
234 }
235 }
236
237 hcp->daifmt[dai->id] = cf;
238
239 return ret;
240}
241
242static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute)
243{
244 struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
245
246 dev_dbg(dai->dev, "%s()\n", __func__);
247
248 if (hcp->hcd.ops->digital_mute)
249 return hcp->hcd.ops->digital_mute(dai->dev->parent, mute);
250
251 return 0;
252}
253
254static const struct snd_soc_dai_ops hdmi_dai_ops = {
255 .startup = hdmi_codec_startup,
256 .shutdown = hdmi_codec_shutdown,
257 .hw_params = hdmi_codec_hw_params,
258 .set_fmt = hdmi_codec_set_fmt,
259 .digital_mute = hdmi_codec_digital_mute,
260};
261
262
263#define HDMI_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
264 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\
265 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\
266 SNDRV_PCM_RATE_192000)
267
268#define SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\
269 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\
270 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\
271 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
272
273/*
274 * This list is only for formats allowed on the I2S bus. So there is
275 * some formats listed that are not supported by HDMI interface. For
276 * instance allowing the 32-bit formats enables 24-precision with CPU
277 * DAIs that do not support 24-bit formats. If the extra formats cause
278 * problems, we should add the video side driver an option to disable
279 * them.
280 */
281#define I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\
282 SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\
283 SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\
284 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\
285 SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE)
286
287static struct snd_soc_dai_driver hdmi_i2s_dai = {
288 .name = "i2s-hifi",
289 .id = DAI_ID_I2S,
290 .playback = {
291 .stream_name = "Playback",
292 .channels_min = 2,
293 .channels_max = 8,
294 .rates = HDMI_RATES,
295 .formats = I2S_FORMATS,
296 .sig_bits = 24,
297 },
298 .ops = &hdmi_dai_ops,
299};
300
301static const struct snd_soc_dai_driver hdmi_spdif_dai = {
302 .name = "spdif-hifi",
303 .id = DAI_ID_SPDIF,
304 .playback = {
305 .stream_name = "Playback",
306 .channels_min = 2,
307 .channels_max = 2,
308 .rates = HDMI_RATES,
309 .formats = SPDIF_FORMATS,
310 },
311 .ops = &hdmi_dai_ops,
312};
313
314static struct snd_soc_codec_driver hdmi_codec = {
315 .dapm_widgets = hdmi_widgets,
316 .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets),
317 .dapm_routes = hdmi_routes,
318 .num_dapm_routes = ARRAY_SIZE(hdmi_routes),
319};
320
321static int hdmi_codec_probe(struct platform_device *pdev)
322{
323 struct hdmi_codec_pdata *hcd = pdev->dev.platform_data;
324 struct device *dev = &pdev->dev;
325 struct hdmi_codec_priv *hcp;
326 int dai_count, i = 0;
327 int ret;
328
329 dev_dbg(dev, "%s()\n", __func__);
330
331 if (!hcd) {
332 dev_err(dev, "%s: No plalform data\n", __func__);
333 return -EINVAL;
334 }
335
336 dai_count = hcd->i2s + hcd->spdif;
337 if (dai_count < 1 || !hcd->ops || !hcd->ops->hw_params ||
338 !hcd->ops->audio_shutdown) {
339 dev_err(dev, "%s: Invalid parameters\n", __func__);
340 return -EINVAL;
341 }
342
343 hcp = devm_kzalloc(dev, sizeof(*hcp), GFP_KERNEL);
344 if (!hcp)
345 return -ENOMEM;
346
347 hcp->hcd = *hcd;
348 mutex_init(&hcp->current_stream_lock);
349
350 hcp->daidrv = devm_kzalloc(dev, dai_count * sizeof(*hcp->daidrv),
351 GFP_KERNEL);
352 if (!hcp->daidrv)
353 return -ENOMEM;
354
355 if (hcd->i2s) {
356 hcp->daidrv[i] = hdmi_i2s_dai;
357 hcp->daidrv[i].playback.channels_max =
358 hcd->max_i2s_channels;
359 i++;
360 }
361
362 if (hcd->spdif)
363 hcp->daidrv[i] = hdmi_spdif_dai;
364
365 ret = snd_soc_register_codec(dev, &hdmi_codec, hcp->daidrv,
366 dai_count);
367 if (ret) {
368 dev_err(dev, "%s: snd_soc_register_codec() failed (%d)\n",
369 __func__, ret);
370 return ret;
371 }
372
373 dev_set_drvdata(dev, hcp);
374 return 0;
375}
376
377static int hdmi_codec_remove(struct platform_device *pdev)
378{
379 snd_soc_unregister_codec(&pdev->dev);
380 return 0;
381}
382
383static struct platform_driver hdmi_codec_driver = {
384 .driver = {
385 .name = HDMI_CODEC_DRV_NAME,
386 },
387 .probe = hdmi_codec_probe,
388 .remove = hdmi_codec_remove,
389};
390
391module_platform_driver(hdmi_codec_driver);
392
393MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>");
394MODULE_DESCRIPTION("HDMI Audio Codec Driver");
395MODULE_LICENSE("GPL");
396MODULE_ALIAS("platform:" HDMI_CODEC_DRV_NAME);