]>
Commit | Line | Data |
---|---|---|
09184118 JS |
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 | ||
9731f82d KM |
27 | struct hdmi_device { |
28 | struct device *dev; | |
29 | struct list_head list; | |
30 | int cnt; | |
31 | }; | |
32 | #define pos_to_hdmi_device(pos) container_of((pos), struct hdmi_device, list) | |
33 | LIST_HEAD(hdmi_device_list); | |
34 | ||
35 | #define DAI_NAME_SIZE 16 | |
09184118 JS |
36 | struct hdmi_codec_priv { |
37 | struct hdmi_codec_pdata hcd; | |
38 | struct snd_soc_dai_driver *daidrv; | |
39 | struct hdmi_codec_daifmt daifmt[2]; | |
40 | struct mutex current_stream_lock; | |
41 | struct snd_pcm_substream *current_stream; | |
42 | struct snd_pcm_hw_constraint_list ratec; | |
43 | uint8_t eld[MAX_ELD_BYTES]; | |
44 | }; | |
45 | ||
46 | static const struct snd_soc_dapm_widget hdmi_widgets[] = { | |
47 | SND_SOC_DAPM_OUTPUT("TX"), | |
48 | }; | |
49 | ||
50 | static const struct snd_soc_dapm_route hdmi_routes[] = { | |
51 | { "TX", NULL, "Playback" }, | |
52 | }; | |
53 | ||
54 | enum { | |
55 | DAI_ID_I2S = 0, | |
56 | DAI_ID_SPDIF, | |
57 | }; | |
58 | ||
81151cfb PZ |
59 | static int hdmi_eld_ctl_info(struct snd_kcontrol *kcontrol, |
60 | struct snd_ctl_elem_info *uinfo) | |
61 | { | |
62 | struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | |
63 | struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); | |
64 | ||
65 | uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; | |
66 | uinfo->count = sizeof(hcp->eld); | |
67 | ||
68 | return 0; | |
69 | } | |
70 | ||
71 | static int hdmi_eld_ctl_get(struct snd_kcontrol *kcontrol, | |
72 | struct snd_ctl_elem_value *ucontrol) | |
73 | { | |
74 | struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | |
75 | struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(component); | |
76 | ||
81151cfb | 77 | memcpy(ucontrol->value.bytes.data, hcp->eld, sizeof(hcp->eld)); |
81151cfb PZ |
78 | |
79 | return 0; | |
80 | } | |
81 | ||
82 | static const struct snd_kcontrol_new hdmi_controls[] = { | |
83 | { | |
84 | .access = SNDRV_CTL_ELEM_ACCESS_READ | | |
85 | SNDRV_CTL_ELEM_ACCESS_VOLATILE, | |
86 | .iface = SNDRV_CTL_ELEM_IFACE_PCM, | |
87 | .name = "ELD", | |
88 | .info = hdmi_eld_ctl_info, | |
89 | .get = hdmi_eld_ctl_get, | |
90 | }, | |
91 | }; | |
92 | ||
09184118 JS |
93 | static int hdmi_codec_new_stream(struct snd_pcm_substream *substream, |
94 | struct snd_soc_dai *dai) | |
95 | { | |
96 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
97 | int ret = 0; | |
98 | ||
99 | mutex_lock(&hcp->current_stream_lock); | |
100 | if (!hcp->current_stream) { | |
101 | hcp->current_stream = substream; | |
102 | } else if (hcp->current_stream != substream) { | |
103 | dev_err(dai->dev, "Only one simultaneous stream supported!\n"); | |
104 | ret = -EINVAL; | |
105 | } | |
106 | mutex_unlock(&hcp->current_stream_lock); | |
107 | ||
108 | return ret; | |
109 | } | |
110 | ||
111 | static int hdmi_codec_startup(struct snd_pcm_substream *substream, | |
112 | struct snd_soc_dai *dai) | |
113 | { | |
114 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
115 | int ret = 0; | |
116 | ||
117 | dev_dbg(dai->dev, "%s()\n", __func__); | |
118 | ||
119 | ret = hdmi_codec_new_stream(substream, dai); | |
120 | if (ret) | |
121 | return ret; | |
122 | ||
123 | if (hcp->hcd.ops->audio_startup) { | |
efc9194b | 124 | ret = hcp->hcd.ops->audio_startup(dai->dev->parent, hcp->hcd.data); |
09184118 JS |
125 | if (ret) { |
126 | mutex_lock(&hcp->current_stream_lock); | |
127 | hcp->current_stream = NULL; | |
128 | mutex_unlock(&hcp->current_stream_lock); | |
129 | return ret; | |
130 | } | |
131 | } | |
132 | ||
133 | if (hcp->hcd.ops->get_eld) { | |
efc9194b KM |
134 | ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, |
135 | hcp->eld, sizeof(hcp->eld)); | |
09184118 JS |
136 | |
137 | if (!ret) { | |
138 | ret = snd_pcm_hw_constraint_eld(substream->runtime, | |
139 | hcp->eld); | |
140 | if (ret) | |
141 | return ret; | |
142 | } | |
143 | } | |
144 | return 0; | |
145 | } | |
146 | ||
147 | static void hdmi_codec_shutdown(struct snd_pcm_substream *substream, | |
148 | struct snd_soc_dai *dai) | |
149 | { | |
150 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
151 | ||
152 | dev_dbg(dai->dev, "%s()\n", __func__); | |
153 | ||
154 | WARN_ON(hcp->current_stream != substream); | |
155 | ||
efc9194b | 156 | hcp->hcd.ops->audio_shutdown(dai->dev->parent, hcp->hcd.data); |
09184118 JS |
157 | |
158 | mutex_lock(&hcp->current_stream_lock); | |
159 | hcp->current_stream = NULL; | |
160 | mutex_unlock(&hcp->current_stream_lock); | |
161 | } | |
162 | ||
163 | static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, | |
164 | struct snd_pcm_hw_params *params, | |
165 | struct snd_soc_dai *dai) | |
166 | { | |
167 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
168 | struct hdmi_codec_params hp = { | |
169 | .iec = { | |
170 | .status = { 0 }, | |
171 | .subcode = { 0 }, | |
172 | .pad = 0, | |
173 | .dig_subframe = { 0 }, | |
174 | } | |
175 | }; | |
176 | int ret; | |
177 | ||
178 | dev_dbg(dai->dev, "%s() width %d rate %d channels %d\n", __func__, | |
179 | params_width(params), params_rate(params), | |
180 | params_channels(params)); | |
181 | ||
182 | if (params_width(params) > 24) | |
183 | params->msbits = 24; | |
184 | ||
185 | ret = snd_pcm_create_iec958_consumer_hw_params(params, hp.iec.status, | |
186 | sizeof(hp.iec.status)); | |
187 | if (ret < 0) { | |
188 | dev_err(dai->dev, "Creating IEC958 channel status failed %d\n", | |
189 | ret); | |
190 | return ret; | |
191 | } | |
192 | ||
193 | ret = hdmi_codec_new_stream(substream, dai); | |
194 | if (ret) | |
195 | return ret; | |
196 | ||
197 | hdmi_audio_infoframe_init(&hp.cea); | |
198 | hp.cea.channels = params_channels(params); | |
199 | hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; | |
200 | hp.cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; | |
201 | hp.cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; | |
202 | ||
203 | hp.sample_width = params_width(params); | |
204 | hp.sample_rate = params_rate(params); | |
205 | hp.channels = params_channels(params); | |
206 | ||
efc9194b KM |
207 | return hcp->hcd.ops->hw_params(dai->dev->parent, hcp->hcd.data, |
208 | &hcp->daifmt[dai->id], &hp); | |
09184118 JS |
209 | } |
210 | ||
211 | static int hdmi_codec_set_fmt(struct snd_soc_dai *dai, | |
212 | unsigned int fmt) | |
213 | { | |
214 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
215 | struct hdmi_codec_daifmt cf = { 0 }; | |
216 | int ret = 0; | |
217 | ||
218 | dev_dbg(dai->dev, "%s()\n", __func__); | |
219 | ||
220 | if (dai->id == DAI_ID_SPDIF) { | |
221 | cf.fmt = HDMI_SPDIF; | |
222 | } else { | |
223 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | |
224 | case SND_SOC_DAIFMT_CBM_CFM: | |
225 | cf.bit_clk_master = 1; | |
226 | cf.frame_clk_master = 1; | |
227 | break; | |
228 | case SND_SOC_DAIFMT_CBS_CFM: | |
229 | cf.frame_clk_master = 1; | |
230 | break; | |
231 | case SND_SOC_DAIFMT_CBM_CFS: | |
232 | cf.bit_clk_master = 1; | |
233 | break; | |
234 | case SND_SOC_DAIFMT_CBS_CFS: | |
235 | break; | |
236 | default: | |
237 | return -EINVAL; | |
238 | } | |
239 | ||
240 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
241 | case SND_SOC_DAIFMT_NB_NF: | |
242 | break; | |
243 | case SND_SOC_DAIFMT_NB_IF: | |
244 | cf.frame_clk_inv = 1; | |
245 | break; | |
246 | case SND_SOC_DAIFMT_IB_NF: | |
247 | cf.bit_clk_inv = 1; | |
248 | break; | |
249 | case SND_SOC_DAIFMT_IB_IF: | |
250 | cf.frame_clk_inv = 1; | |
251 | cf.bit_clk_inv = 1; | |
252 | break; | |
253 | } | |
254 | ||
255 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
256 | case SND_SOC_DAIFMT_I2S: | |
257 | cf.fmt = HDMI_I2S; | |
258 | break; | |
259 | case SND_SOC_DAIFMT_DSP_A: | |
260 | cf.fmt = HDMI_DSP_A; | |
261 | break; | |
262 | case SND_SOC_DAIFMT_DSP_B: | |
263 | cf.fmt = HDMI_DSP_B; | |
264 | break; | |
265 | case SND_SOC_DAIFMT_RIGHT_J: | |
266 | cf.fmt = HDMI_RIGHT_J; | |
267 | break; | |
268 | case SND_SOC_DAIFMT_LEFT_J: | |
269 | cf.fmt = HDMI_LEFT_J; | |
270 | break; | |
271 | case SND_SOC_DAIFMT_AC97: | |
272 | cf.fmt = HDMI_AC97; | |
273 | break; | |
274 | default: | |
275 | dev_err(dai->dev, "Invalid DAI interface format\n"); | |
276 | return -EINVAL; | |
277 | } | |
278 | } | |
279 | ||
280 | hcp->daifmt[dai->id] = cf; | |
281 | ||
282 | return ret; | |
283 | } | |
284 | ||
285 | static int hdmi_codec_digital_mute(struct snd_soc_dai *dai, int mute) | |
286 | { | |
287 | struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); | |
288 | ||
289 | dev_dbg(dai->dev, "%s()\n", __func__); | |
290 | ||
291 | if (hcp->hcd.ops->digital_mute) | |
efc9194b KM |
292 | return hcp->hcd.ops->digital_mute(dai->dev->parent, |
293 | hcp->hcd.data, mute); | |
09184118 JS |
294 | |
295 | return 0; | |
296 | } | |
297 | ||
298 | static const struct snd_soc_dai_ops hdmi_dai_ops = { | |
299 | .startup = hdmi_codec_startup, | |
300 | .shutdown = hdmi_codec_shutdown, | |
301 | .hw_params = hdmi_codec_hw_params, | |
302 | .set_fmt = hdmi_codec_set_fmt, | |
303 | .digital_mute = hdmi_codec_digital_mute, | |
304 | }; | |
305 | ||
306 | ||
307 | #define HDMI_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ | |
308 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ | |
309 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 |\ | |
310 | SNDRV_PCM_RATE_192000) | |
311 | ||
312 | #define SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ | |
313 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ | |
314 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ | |
315 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) | |
316 | ||
317 | /* | |
318 | * This list is only for formats allowed on the I2S bus. So there is | |
319 | * some formats listed that are not supported by HDMI interface. For | |
320 | * instance allowing the 32-bit formats enables 24-precision with CPU | |
321 | * DAIs that do not support 24-bit formats. If the extra formats cause | |
322 | * problems, we should add the video side driver an option to disable | |
323 | * them. | |
324 | */ | |
325 | #define I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ | |
326 | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE |\ | |
327 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |\ | |
328 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |\ | |
329 | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) | |
330 | ||
331 | static struct snd_soc_dai_driver hdmi_i2s_dai = { | |
09184118 JS |
332 | .id = DAI_ID_I2S, |
333 | .playback = { | |
334 | .stream_name = "Playback", | |
335 | .channels_min = 2, | |
336 | .channels_max = 8, | |
337 | .rates = HDMI_RATES, | |
338 | .formats = I2S_FORMATS, | |
339 | .sig_bits = 24, | |
340 | }, | |
341 | .ops = &hdmi_dai_ops, | |
342 | }; | |
343 | ||
344 | static const struct snd_soc_dai_driver hdmi_spdif_dai = { | |
09184118 JS |
345 | .id = DAI_ID_SPDIF, |
346 | .playback = { | |
347 | .stream_name = "Playback", | |
348 | .channels_min = 2, | |
349 | .channels_max = 2, | |
350 | .rates = HDMI_RATES, | |
351 | .formats = SPDIF_FORMATS, | |
352 | }, | |
353 | .ops = &hdmi_dai_ops, | |
354 | }; | |
355 | ||
9731f82d KM |
356 | static char hdmi_dai_name[][DAI_NAME_SIZE] = { |
357 | "hdmi-hifi.0", | |
358 | "hdmi-hifi.1", | |
359 | "hdmi-hifi.2", | |
360 | "hdmi-hifi.3", | |
361 | }; | |
362 | ||
363 | static int hdmi_of_xlate_dai_name(struct snd_soc_component *component, | |
364 | struct of_phandle_args *args, | |
365 | const char **dai_name) | |
366 | { | |
367 | int id = args->args[0]; | |
368 | ||
369 | if (id < ARRAY_SIZE(hdmi_dai_name)) { | |
370 | *dai_name = hdmi_dai_name[id]; | |
371 | return 0; | |
372 | } | |
373 | ||
374 | return -EAGAIN; | |
375 | } | |
376 | ||
09184118 | 377 | static struct snd_soc_codec_driver hdmi_codec = { |
9731f82d | 378 | .component_driver = { |
88b0f761 KM |
379 | .controls = hdmi_controls, |
380 | .num_controls = ARRAY_SIZE(hdmi_controls), | |
381 | .dapm_widgets = hdmi_widgets, | |
382 | .num_dapm_widgets = ARRAY_SIZE(hdmi_widgets), | |
383 | .dapm_routes = hdmi_routes, | |
384 | .num_dapm_routes = ARRAY_SIZE(hdmi_routes), | |
9731f82d KM |
385 | .of_xlate_dai_name = hdmi_of_xlate_dai_name, |
386 | }, | |
09184118 JS |
387 | }; |
388 | ||
389 | static int hdmi_codec_probe(struct platform_device *pdev) | |
390 | { | |
391 | struct hdmi_codec_pdata *hcd = pdev->dev.platform_data; | |
392 | struct device *dev = &pdev->dev; | |
393 | struct hdmi_codec_priv *hcp; | |
9731f82d KM |
394 | struct hdmi_device *hd; |
395 | struct list_head *pos; | |
09184118 JS |
396 | int dai_count, i = 0; |
397 | int ret; | |
398 | ||
399 | dev_dbg(dev, "%s()\n", __func__); | |
400 | ||
401 | if (!hcd) { | |
402 | dev_err(dev, "%s: No plalform data\n", __func__); | |
403 | return -EINVAL; | |
404 | } | |
405 | ||
406 | dai_count = hcd->i2s + hcd->spdif; | |
407 | if (dai_count < 1 || !hcd->ops || !hcd->ops->hw_params || | |
408 | !hcd->ops->audio_shutdown) { | |
409 | dev_err(dev, "%s: Invalid parameters\n", __func__); | |
410 | return -EINVAL; | |
411 | } | |
412 | ||
413 | hcp = devm_kzalloc(dev, sizeof(*hcp), GFP_KERNEL); | |
414 | if (!hcp) | |
415 | return -ENOMEM; | |
416 | ||
9731f82d KM |
417 | hd = NULL; |
418 | list_for_each(pos, &hdmi_device_list) { | |
419 | struct hdmi_device *tmp = pos_to_hdmi_device(pos); | |
420 | ||
421 | if (tmp->dev == dev->parent) { | |
422 | hd = tmp; | |
423 | break; | |
424 | } | |
425 | } | |
426 | ||
427 | if (!hd) { | |
428 | hd = devm_kzalloc(dev, sizeof(*hd), GFP_KERNEL); | |
429 | if (!hd) | |
430 | return -ENOMEM; | |
431 | ||
432 | hd->dev = dev->parent; | |
433 | ||
434 | list_add_tail(&hd->list, &hdmi_device_list); | |
435 | } | |
436 | ||
437 | if (hd->cnt >= ARRAY_SIZE(hdmi_dai_name)) { | |
438 | dev_err(dev, "too many hdmi codec are deteced\n"); | |
439 | return -EINVAL; | |
440 | } | |
441 | ||
09184118 JS |
442 | hcp->hcd = *hcd; |
443 | mutex_init(&hcp->current_stream_lock); | |
444 | ||
445 | hcp->daidrv = devm_kzalloc(dev, dai_count * sizeof(*hcp->daidrv), | |
446 | GFP_KERNEL); | |
447 | if (!hcp->daidrv) | |
448 | return -ENOMEM; | |
449 | ||
450 | if (hcd->i2s) { | |
451 | hcp->daidrv[i] = hdmi_i2s_dai; | |
452 | hcp->daidrv[i].playback.channels_max = | |
453 | hcd->max_i2s_channels; | |
9731f82d | 454 | hcp->daidrv[i].name = hdmi_dai_name[hd->cnt++]; |
09184118 JS |
455 | i++; |
456 | } | |
457 | ||
9731f82d | 458 | if (hcd->spdif) { |
09184118 | 459 | hcp->daidrv[i] = hdmi_spdif_dai; |
9731f82d KM |
460 | hcp->daidrv[i].name = hdmi_dai_name[hd->cnt++]; |
461 | } | |
09184118 JS |
462 | |
463 | ret = snd_soc_register_codec(dev, &hdmi_codec, hcp->daidrv, | |
464 | dai_count); | |
465 | if (ret) { | |
466 | dev_err(dev, "%s: snd_soc_register_codec() failed (%d)\n", | |
467 | __func__, ret); | |
468 | return ret; | |
469 | } | |
470 | ||
471 | dev_set_drvdata(dev, hcp); | |
472 | return 0; | |
473 | } | |
474 | ||
475 | static int hdmi_codec_remove(struct platform_device *pdev) | |
476 | { | |
477 | snd_soc_unregister_codec(&pdev->dev); | |
478 | return 0; | |
479 | } | |
480 | ||
481 | static struct platform_driver hdmi_codec_driver = { | |
482 | .driver = { | |
483 | .name = HDMI_CODEC_DRV_NAME, | |
484 | }, | |
485 | .probe = hdmi_codec_probe, | |
486 | .remove = hdmi_codec_remove, | |
487 | }; | |
488 | ||
489 | module_platform_driver(hdmi_codec_driver); | |
490 | ||
491 | MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>"); | |
492 | MODULE_DESCRIPTION("HDMI Audio Codec Driver"); | |
493 | MODULE_LICENSE("GPL"); | |
494 | MODULE_ALIAS("platform:" HDMI_CODEC_DRV_NAME); |