1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * omap-hdmi-audio.c -- OMAP4+ DSS HDMI audio support library |
4 | * |
5 | * Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com |
6 | * |
7 | * Author: Jyri Sarha <jsarha@ti.com> |
8 | */ |
9 | |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/err.h> |
13 | #include <linux/string.h> |
14 | #include <linux/platform_device.h> |
15 | #include <sound/soc.h> |
16 | #include <sound/pcm_params.h> |
17 | #include <sound/dmaengine_pcm.h> |
18 | #include <uapi/sound/asound.h> |
19 | #include <sound/asoundef.h> |
20 | #include <sound/omap-hdmi-audio.h> |
21 | |
22 | #include "sdma-pcm.h" |
23 | |
24 | #define DRV_NAME "omap-hdmi-audio" |
25 | |
26 | struct hdmi_audio_data { |
27 | struct snd_soc_card *card; |
28 | |
29 | const struct omap_hdmi_audio_ops *ops; |
30 | struct device *dssdev; |
31 | struct snd_dmaengine_dai_dma_data dma_data; |
32 | struct omap_dss_audio dss_audio; |
33 | struct snd_aes_iec958 iec; |
34 | struct snd_cea_861_aud_if cea; |
35 | |
36 | struct mutex current_stream_lock; |
37 | struct snd_pcm_substream *current_stream; |
38 | }; |
39 | |
40 | static |
41 | struct hdmi_audio_data *card_drvdata_substream(struct snd_pcm_substream *ss) |
42 | { |
43 | struct snd_soc_pcm_runtime *rtd = ss->private_data; |
44 | |
45 | return snd_soc_card_get_drvdata(card: rtd->card); |
46 | } |
47 | |
48 | static void hdmi_dai_abort(struct device *dev) |
49 | { |
50 | struct hdmi_audio_data *ad = dev_get_drvdata(dev); |
51 | |
52 | mutex_lock(&ad->current_stream_lock); |
53 | if (ad->current_stream && ad->current_stream->runtime && |
54 | snd_pcm_running(substream: ad->current_stream)) { |
55 | dev_err(dev, "HDMI display disabled, aborting playback\n" ); |
56 | snd_pcm_stream_lock_irq(substream: ad->current_stream); |
57 | snd_pcm_stop(substream: ad->current_stream, SNDRV_PCM_STATE_DISCONNECTED); |
58 | snd_pcm_stream_unlock_irq(substream: ad->current_stream); |
59 | } |
60 | mutex_unlock(lock: &ad->current_stream_lock); |
61 | } |
62 | |
63 | static int hdmi_dai_startup(struct snd_pcm_substream *substream, |
64 | struct snd_soc_dai *dai) |
65 | { |
66 | struct hdmi_audio_data *ad = card_drvdata_substream(ss: substream); |
67 | int ret; |
68 | /* |
69 | * Make sure that the period bytes are multiple of the DMA packet size. |
70 | * Largest packet size we use is 32 32-bit words = 128 bytes |
71 | */ |
72 | ret = snd_pcm_hw_constraint_step(runtime: substream->runtime, cond: 0, |
73 | SNDRV_PCM_HW_PARAM_PERIOD_BYTES, step: 128); |
74 | if (ret < 0) { |
75 | dev_err(dai->dev, "Could not apply period constraint: %d\n" , |
76 | ret); |
77 | return ret; |
78 | } |
79 | ret = snd_pcm_hw_constraint_step(runtime: substream->runtime, cond: 0, |
80 | SNDRV_PCM_HW_PARAM_BUFFER_BYTES, step: 128); |
81 | if (ret < 0) { |
82 | dev_err(dai->dev, "Could not apply buffer constraint: %d\n" , |
83 | ret); |
84 | return ret; |
85 | } |
86 | |
87 | snd_soc_dai_set_dma_data(dai, substream, &ad->dma_data); |
88 | |
89 | mutex_lock(&ad->current_stream_lock); |
90 | ad->current_stream = substream; |
91 | mutex_unlock(lock: &ad->current_stream_lock); |
92 | |
93 | ret = ad->ops->audio_startup(ad->dssdev, hdmi_dai_abort); |
94 | |
95 | if (ret) { |
96 | mutex_lock(&ad->current_stream_lock); |
97 | ad->current_stream = NULL; |
98 | mutex_unlock(lock: &ad->current_stream_lock); |
99 | } |
100 | |
101 | return ret; |
102 | } |
103 | |
104 | static int hdmi_dai_hw_params(struct snd_pcm_substream *substream, |
105 | struct snd_pcm_hw_params *params, |
106 | struct snd_soc_dai *dai) |
107 | { |
108 | struct hdmi_audio_data *ad = card_drvdata_substream(ss: substream); |
109 | struct snd_aes_iec958 *iec = &ad->iec; |
110 | struct snd_cea_861_aud_if *cea = &ad->cea; |
111 | |
112 | WARN_ON(ad->current_stream != substream); |
113 | |
114 | switch (params_format(p: params)) { |
115 | case SNDRV_PCM_FORMAT_S16_LE: |
116 | ad->dma_data.maxburst = 16; |
117 | break; |
118 | case SNDRV_PCM_FORMAT_S24_LE: |
119 | ad->dma_data.maxburst = 32; |
120 | break; |
121 | default: |
122 | dev_err(dai->dev, "format not supported!\n" ); |
123 | return -EINVAL; |
124 | } |
125 | |
126 | ad->dss_audio.iec = iec; |
127 | ad->dss_audio.cea = cea; |
128 | /* |
129 | * fill the IEC-60958 channel status word |
130 | */ |
131 | /* initialize the word bytes */ |
132 | memset(iec->status, 0, sizeof(iec->status)); |
133 | |
134 | /* specify IEC-60958-3 (commercial use) */ |
135 | iec->status[0] &= ~IEC958_AES0_PROFESSIONAL; |
136 | |
137 | /* specify that the audio is LPCM*/ |
138 | iec->status[0] &= ~IEC958_AES0_NONAUDIO; |
139 | |
140 | iec->status[0] |= IEC958_AES0_CON_NOT_COPYRIGHT; |
141 | |
142 | iec->status[0] |= IEC958_AES0_CON_EMPHASIS_NONE; |
143 | |
144 | iec->status[1] = IEC958_AES1_CON_GENERAL; |
145 | |
146 | iec->status[2] |= IEC958_AES2_CON_SOURCE_UNSPEC; |
147 | |
148 | iec->status[2] |= IEC958_AES2_CON_CHANNEL_UNSPEC; |
149 | |
150 | switch (params_rate(p: params)) { |
151 | case 32000: |
152 | iec->status[3] |= IEC958_AES3_CON_FS_32000; |
153 | break; |
154 | case 44100: |
155 | iec->status[3] |= IEC958_AES3_CON_FS_44100; |
156 | break; |
157 | case 48000: |
158 | iec->status[3] |= IEC958_AES3_CON_FS_48000; |
159 | break; |
160 | case 88200: |
161 | iec->status[3] |= IEC958_AES3_CON_FS_88200; |
162 | break; |
163 | case 96000: |
164 | iec->status[3] |= IEC958_AES3_CON_FS_96000; |
165 | break; |
166 | case 176400: |
167 | iec->status[3] |= IEC958_AES3_CON_FS_176400; |
168 | break; |
169 | case 192000: |
170 | iec->status[3] |= IEC958_AES3_CON_FS_192000; |
171 | break; |
172 | default: |
173 | dev_err(dai->dev, "rate not supported!\n" ); |
174 | return -EINVAL; |
175 | } |
176 | |
177 | /* specify the clock accuracy */ |
178 | iec->status[3] |= IEC958_AES3_CON_CLOCK_1000PPM; |
179 | |
180 | /* |
181 | * specify the word length. The same word length value can mean |
182 | * two different lengths. Hence, we need to specify the maximum |
183 | * word length as well. |
184 | */ |
185 | switch (params_format(p: params)) { |
186 | case SNDRV_PCM_FORMAT_S16_LE: |
187 | iec->status[4] |= IEC958_AES4_CON_WORDLEN_20_16; |
188 | iec->status[4] &= ~IEC958_AES4_CON_MAX_WORDLEN_24; |
189 | break; |
190 | case SNDRV_PCM_FORMAT_S24_LE: |
191 | iec->status[4] |= IEC958_AES4_CON_WORDLEN_24_20; |
192 | iec->status[4] |= IEC958_AES4_CON_MAX_WORDLEN_24; |
193 | break; |
194 | default: |
195 | dev_err(dai->dev, "format not supported!\n" ); |
196 | return -EINVAL; |
197 | } |
198 | |
199 | /* |
200 | * Fill the CEA-861 audio infoframe (see spec for details) |
201 | */ |
202 | |
203 | cea->db1_ct_cc = (params_channels(p: params) - 1) |
204 | & CEA861_AUDIO_INFOFRAME_DB1CC; |
205 | cea->db1_ct_cc |= CEA861_AUDIO_INFOFRAME_DB1CT_FROM_STREAM; |
206 | |
207 | cea->db2_sf_ss = CEA861_AUDIO_INFOFRAME_DB2SF_FROM_STREAM; |
208 | cea->db2_sf_ss |= CEA861_AUDIO_INFOFRAME_DB2SS_FROM_STREAM; |
209 | |
210 | cea->db3 = 0; /* not used, all zeros */ |
211 | |
212 | if (params_channels(p: params) == 2) |
213 | cea->db4_ca = 0x0; |
214 | else if (params_channels(p: params) == 6) |
215 | cea->db4_ca = 0xb; |
216 | else |
217 | cea->db4_ca = 0x13; |
218 | |
219 | if (cea->db4_ca == 0x00) |
220 | cea->db5_dminh_lsv = CEA861_AUDIO_INFOFRAME_DB5_DM_INH_PERMITTED; |
221 | else |
222 | cea->db5_dminh_lsv = CEA861_AUDIO_INFOFRAME_DB5_DM_INH_PROHIBITED; |
223 | |
224 | /* the expression is trivial but makes clear what we are doing */ |
225 | cea->db5_dminh_lsv |= (0 & CEA861_AUDIO_INFOFRAME_DB5_LSV); |
226 | |
227 | return ad->ops->audio_config(ad->dssdev, &ad->dss_audio); |
228 | } |
229 | |
230 | static int hdmi_dai_trigger(struct snd_pcm_substream *substream, int cmd, |
231 | struct snd_soc_dai *dai) |
232 | { |
233 | struct hdmi_audio_data *ad = card_drvdata_substream(ss: substream); |
234 | int err = 0; |
235 | |
236 | WARN_ON(ad->current_stream != substream); |
237 | |
238 | switch (cmd) { |
239 | case SNDRV_PCM_TRIGGER_START: |
240 | case SNDRV_PCM_TRIGGER_RESUME: |
241 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
242 | err = ad->ops->audio_start(ad->dssdev); |
243 | break; |
244 | case SNDRV_PCM_TRIGGER_STOP: |
245 | case SNDRV_PCM_TRIGGER_SUSPEND: |
246 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
247 | ad->ops->audio_stop(ad->dssdev); |
248 | break; |
249 | default: |
250 | err = -EINVAL; |
251 | } |
252 | return err; |
253 | } |
254 | |
255 | static void hdmi_dai_shutdown(struct snd_pcm_substream *substream, |
256 | struct snd_soc_dai *dai) |
257 | { |
258 | struct hdmi_audio_data *ad = card_drvdata_substream(ss: substream); |
259 | |
260 | WARN_ON(ad->current_stream != substream); |
261 | |
262 | ad->ops->audio_shutdown(ad->dssdev); |
263 | |
264 | mutex_lock(&ad->current_stream_lock); |
265 | ad->current_stream = NULL; |
266 | mutex_unlock(lock: &ad->current_stream_lock); |
267 | } |
268 | |
269 | static const struct snd_soc_dai_ops hdmi_dai_ops = { |
270 | .startup = hdmi_dai_startup, |
271 | .hw_params = hdmi_dai_hw_params, |
272 | .trigger = hdmi_dai_trigger, |
273 | .shutdown = hdmi_dai_shutdown, |
274 | }; |
275 | |
276 | static const struct snd_soc_component_driver omap_hdmi_component = { |
277 | .name = "omapdss_hdmi" , |
278 | .legacy_dai_naming = 1, |
279 | }; |
280 | |
281 | static struct snd_soc_dai_driver omap5_hdmi_dai = { |
282 | .name = "omap5-hdmi-dai" , |
283 | .playback = { |
284 | .channels_min = 2, |
285 | .channels_max = 8, |
286 | .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | |
287 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | |
288 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | |
289 | SNDRV_PCM_RATE_192000), |
290 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
291 | }, |
292 | .ops = &hdmi_dai_ops, |
293 | }; |
294 | |
295 | static struct snd_soc_dai_driver omap4_hdmi_dai = { |
296 | .name = "omap4-hdmi-dai" , |
297 | .playback = { |
298 | .channels_min = 2, |
299 | .channels_max = 8, |
300 | .rates = (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | |
301 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | |
302 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | |
303 | SNDRV_PCM_RATE_192000), |
304 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, |
305 | }, |
306 | .ops = &hdmi_dai_ops, |
307 | }; |
308 | |
309 | static int omap_hdmi_audio_probe(struct platform_device *pdev) |
310 | { |
311 | struct omap_hdmi_audio_pdata *ha = pdev->dev.platform_data; |
312 | struct device *dev = &pdev->dev; |
313 | struct hdmi_audio_data *ad; |
314 | struct snd_soc_dai_driver *dai_drv; |
315 | struct snd_soc_card *card; |
316 | struct snd_soc_dai_link_component *compnent; |
317 | int ret; |
318 | |
319 | if (!ha) { |
320 | dev_err(dev, "No platform data\n" ); |
321 | return -EINVAL; |
322 | } |
323 | |
324 | ad = devm_kzalloc(dev, size: sizeof(*ad), GFP_KERNEL); |
325 | if (!ad) |
326 | return -ENOMEM; |
327 | ad->dssdev = ha->dev; |
328 | ad->ops = ha->ops; |
329 | ad->dma_data.addr = ha->audio_dma_addr; |
330 | ad->dma_data.filter_data = "audio_tx" ; |
331 | ad->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
332 | mutex_init(&ad->current_stream_lock); |
333 | |
334 | switch (ha->version) { |
335 | case 4: |
336 | dai_drv = &omap4_hdmi_dai; |
337 | break; |
338 | case 5: |
339 | dai_drv = &omap5_hdmi_dai; |
340 | break; |
341 | default: |
342 | return -EINVAL; |
343 | } |
344 | ret = devm_snd_soc_register_component(dev: ad->dssdev, component_driver: &omap_hdmi_component, |
345 | dai_drv, num_dai: 1); |
346 | if (ret) |
347 | return ret; |
348 | |
349 | ret = sdma_pcm_platform_register(dev: ad->dssdev, txdmachan: "audio_tx" , NULL); |
350 | if (ret) |
351 | return ret; |
352 | |
353 | card = devm_kzalloc(dev, size: sizeof(*card), GFP_KERNEL); |
354 | if (!card) |
355 | return -ENOMEM; |
356 | |
357 | card->name = devm_kasprintf(dev, GFP_KERNEL, |
358 | fmt: "HDMI %s" , dev_name(dev: ad->dssdev)); |
359 | if (!card->name) |
360 | return -ENOMEM; |
361 | |
362 | card->owner = THIS_MODULE; |
363 | card->dai_link = |
364 | devm_kzalloc(dev, size: sizeof(*(card->dai_link)), GFP_KERNEL); |
365 | if (!card->dai_link) |
366 | return -ENOMEM; |
367 | |
368 | compnent = devm_kzalloc(dev, size: sizeof(*compnent), GFP_KERNEL); |
369 | if (!compnent) |
370 | return -ENOMEM; |
371 | card->dai_link->cpus = compnent; |
372 | card->dai_link->num_cpus = 1; |
373 | card->dai_link->codecs = &snd_soc_dummy_dlc; |
374 | card->dai_link->num_codecs = 1; |
375 | |
376 | card->dai_link->name = card->name; |
377 | card->dai_link->stream_name = card->name; |
378 | card->dai_link->cpus->dai_name = dev_name(dev: ad->dssdev); |
379 | card->num_links = 1; |
380 | card->dev = dev; |
381 | |
382 | ret = devm_snd_soc_register_card(dev, card); |
383 | if (ret) { |
384 | dev_err(dev, "snd_soc_register_card failed (%d)\n" , ret); |
385 | return ret; |
386 | } |
387 | |
388 | ad->card = card; |
389 | snd_soc_card_set_drvdata(card, data: ad); |
390 | |
391 | dev_set_drvdata(dev, data: ad); |
392 | |
393 | return 0; |
394 | } |
395 | |
396 | static struct platform_driver hdmi_audio_driver = { |
397 | .driver = { |
398 | .name = DRV_NAME, |
399 | }, |
400 | .probe = omap_hdmi_audio_probe, |
401 | }; |
402 | |
403 | module_platform_driver(hdmi_audio_driver); |
404 | |
405 | MODULE_AUTHOR("Jyri Sarha <jsarha@ti.com>" ); |
406 | MODULE_DESCRIPTION("OMAP HDMI Audio Driver" ); |
407 | MODULE_LICENSE("GPL" ); |
408 | MODULE_ALIAS("platform:" DRV_NAME); |
409 | |