1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. |
4 | */ |
5 | |
6 | |
7 | #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ |
8 | |
9 | #include <linux/platform_device.h> |
10 | |
11 | #include <drm/display/drm_dp_helper.h> |
12 | #include <drm/drm_edid.h> |
13 | |
14 | #include "dp_catalog.h" |
15 | #include "dp_audio.h" |
16 | #include "dp_panel.h" |
17 | #include "dp_display.h" |
18 | #include "dp_utils.h" |
19 | |
20 | struct dp_audio_private { |
21 | struct platform_device *audio_pdev; |
22 | struct platform_device *pdev; |
23 | struct drm_device *drm_dev; |
24 | struct dp_catalog *catalog; |
25 | struct dp_panel *panel; |
26 | |
27 | bool engine_on; |
28 | u32 channels; |
29 | |
30 | struct dp_audio dp_audio; |
31 | }; |
32 | |
33 | static u32 (struct dp_catalog *catalog, |
34 | enum dp_catalog_audio_sdp_type sdp, |
35 | enum dp_catalog_audio_header_type ) |
36 | { |
37 | catalog->sdp_type = sdp; |
38 | catalog->sdp_header = header; |
39 | dp_catalog_audio_get_header(catalog); |
40 | |
41 | return catalog->audio_data; |
42 | } |
43 | |
44 | static void (struct dp_catalog *catalog, |
45 | u32 data, |
46 | enum dp_catalog_audio_sdp_type sdp, |
47 | enum dp_catalog_audio_header_type ) |
48 | { |
49 | catalog->sdp_type = sdp; |
50 | catalog->sdp_header = header; |
51 | catalog->audio_data = data; |
52 | dp_catalog_audio_set_header(catalog); |
53 | } |
54 | |
55 | static void dp_audio_stream_sdp(struct dp_audio_private *audio) |
56 | { |
57 | struct dp_catalog *catalog = audio->catalog; |
58 | u32 value, new_value; |
59 | u8 parity_byte; |
60 | |
61 | /* Config header and parity byte 1 */ |
62 | value = dp_audio_get_header(catalog, |
63 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_1); |
64 | |
65 | new_value = 0x02; |
66 | parity_byte = dp_utils_calculate_parity(data: new_value); |
67 | value |= ((new_value << HEADER_BYTE_1_BIT) |
68 | | (parity_byte << PARITY_BYTE_1_BIT)); |
69 | drm_dbg_dp(audio->drm_dev, |
70 | "Header Byte 1: value = 0x%x, parity_byte = 0x%x\n" , |
71 | value, parity_byte); |
72 | dp_audio_set_header(catalog, data: value, |
73 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_1); |
74 | |
75 | /* Config header and parity byte 2 */ |
76 | value = dp_audio_get_header(catalog, |
77 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_2); |
78 | new_value = value; |
79 | parity_byte = dp_utils_calculate_parity(data: new_value); |
80 | value |= ((new_value << HEADER_BYTE_2_BIT) |
81 | | (parity_byte << PARITY_BYTE_2_BIT)); |
82 | drm_dbg_dp(audio->drm_dev, |
83 | "Header Byte 2: value = 0x%x, parity_byte = 0x%x\n" , |
84 | value, parity_byte); |
85 | |
86 | dp_audio_set_header(catalog, data: value, |
87 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_2); |
88 | |
89 | /* Config header and parity byte 3 */ |
90 | value = dp_audio_get_header(catalog, |
91 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_3); |
92 | |
93 | new_value = audio->channels - 1; |
94 | parity_byte = dp_utils_calculate_parity(data: new_value); |
95 | value |= ((new_value << HEADER_BYTE_3_BIT) |
96 | | (parity_byte << PARITY_BYTE_3_BIT)); |
97 | drm_dbg_dp(audio->drm_dev, |
98 | "Header Byte 3: value = 0x%x, parity_byte = 0x%x\n" , |
99 | value, parity_byte); |
100 | |
101 | dp_audio_set_header(catalog, data: value, |
102 | sdp: DP_AUDIO_SDP_STREAM, header: DP_AUDIO_SDP_HEADER_3); |
103 | } |
104 | |
105 | static void dp_audio_timestamp_sdp(struct dp_audio_private *audio) |
106 | { |
107 | struct dp_catalog *catalog = audio->catalog; |
108 | u32 value, new_value; |
109 | u8 parity_byte; |
110 | |
111 | /* Config header and parity byte 1 */ |
112 | value = dp_audio_get_header(catalog, |
113 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_1); |
114 | |
115 | new_value = 0x1; |
116 | parity_byte = dp_utils_calculate_parity(data: new_value); |
117 | value |= ((new_value << HEADER_BYTE_1_BIT) |
118 | | (parity_byte << PARITY_BYTE_1_BIT)); |
119 | drm_dbg_dp(audio->drm_dev, |
120 | "Header Byte 1: value = 0x%x, parity_byte = 0x%x\n" , |
121 | value, parity_byte); |
122 | dp_audio_set_header(catalog, data: value, |
123 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_1); |
124 | |
125 | /* Config header and parity byte 2 */ |
126 | value = dp_audio_get_header(catalog, |
127 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_2); |
128 | |
129 | new_value = 0x17; |
130 | parity_byte = dp_utils_calculate_parity(data: new_value); |
131 | value |= ((new_value << HEADER_BYTE_2_BIT) |
132 | | (parity_byte << PARITY_BYTE_2_BIT)); |
133 | drm_dbg_dp(audio->drm_dev, |
134 | "Header Byte 2: value = 0x%x, parity_byte = 0x%x\n" , |
135 | value, parity_byte); |
136 | dp_audio_set_header(catalog, data: value, |
137 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_2); |
138 | |
139 | /* Config header and parity byte 3 */ |
140 | value = dp_audio_get_header(catalog, |
141 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_3); |
142 | |
143 | new_value = (0x0 | (0x11 << 2)); |
144 | parity_byte = dp_utils_calculate_parity(data: new_value); |
145 | value |= ((new_value << HEADER_BYTE_3_BIT) |
146 | | (parity_byte << PARITY_BYTE_3_BIT)); |
147 | drm_dbg_dp(audio->drm_dev, |
148 | "Header Byte 3: value = 0x%x, parity_byte = 0x%x\n" , |
149 | value, parity_byte); |
150 | dp_audio_set_header(catalog, data: value, |
151 | sdp: DP_AUDIO_SDP_TIMESTAMP, header: DP_AUDIO_SDP_HEADER_3); |
152 | } |
153 | |
154 | static void dp_audio_infoframe_sdp(struct dp_audio_private *audio) |
155 | { |
156 | struct dp_catalog *catalog = audio->catalog; |
157 | u32 value, new_value; |
158 | u8 parity_byte; |
159 | |
160 | /* Config header and parity byte 1 */ |
161 | value = dp_audio_get_header(catalog, |
162 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_1); |
163 | |
164 | new_value = 0x84; |
165 | parity_byte = dp_utils_calculate_parity(data: new_value); |
166 | value |= ((new_value << HEADER_BYTE_1_BIT) |
167 | | (parity_byte << PARITY_BYTE_1_BIT)); |
168 | drm_dbg_dp(audio->drm_dev, |
169 | "Header Byte 1: value = 0x%x, parity_byte = 0x%x\n" , |
170 | value, parity_byte); |
171 | dp_audio_set_header(catalog, data: value, |
172 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_1); |
173 | |
174 | /* Config header and parity byte 2 */ |
175 | value = dp_audio_get_header(catalog, |
176 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_2); |
177 | |
178 | new_value = 0x1b; |
179 | parity_byte = dp_utils_calculate_parity(data: new_value); |
180 | value |= ((new_value << HEADER_BYTE_2_BIT) |
181 | | (parity_byte << PARITY_BYTE_2_BIT)); |
182 | drm_dbg_dp(audio->drm_dev, |
183 | "Header Byte 2: value = 0x%x, parity_byte = 0x%x\n" , |
184 | value, parity_byte); |
185 | dp_audio_set_header(catalog, data: value, |
186 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_2); |
187 | |
188 | /* Config header and parity byte 3 */ |
189 | value = dp_audio_get_header(catalog, |
190 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_3); |
191 | |
192 | new_value = (0x0 | (0x11 << 2)); |
193 | parity_byte = dp_utils_calculate_parity(data: new_value); |
194 | value |= ((new_value << HEADER_BYTE_3_BIT) |
195 | | (parity_byte << PARITY_BYTE_3_BIT)); |
196 | drm_dbg_dp(audio->drm_dev, |
197 | "Header Byte 3: value = 0x%x, parity_byte = 0x%x\n" , |
198 | new_value, parity_byte); |
199 | dp_audio_set_header(catalog, data: value, |
200 | sdp: DP_AUDIO_SDP_INFOFRAME, header: DP_AUDIO_SDP_HEADER_3); |
201 | } |
202 | |
203 | static void dp_audio_copy_management_sdp(struct dp_audio_private *audio) |
204 | { |
205 | struct dp_catalog *catalog = audio->catalog; |
206 | u32 value, new_value; |
207 | u8 parity_byte; |
208 | |
209 | /* Config header and parity byte 1 */ |
210 | value = dp_audio_get_header(catalog, |
211 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_1); |
212 | |
213 | new_value = 0x05; |
214 | parity_byte = dp_utils_calculate_parity(data: new_value); |
215 | value |= ((new_value << HEADER_BYTE_1_BIT) |
216 | | (parity_byte << PARITY_BYTE_1_BIT)); |
217 | drm_dbg_dp(audio->drm_dev, |
218 | "Header Byte 1: value = 0x%x, parity_byte = 0x%x\n" , |
219 | value, parity_byte); |
220 | dp_audio_set_header(catalog, data: value, |
221 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_1); |
222 | |
223 | /* Config header and parity byte 2 */ |
224 | value = dp_audio_get_header(catalog, |
225 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_2); |
226 | |
227 | new_value = 0x0F; |
228 | parity_byte = dp_utils_calculate_parity(data: new_value); |
229 | value |= ((new_value << HEADER_BYTE_2_BIT) |
230 | | (parity_byte << PARITY_BYTE_2_BIT)); |
231 | drm_dbg_dp(audio->drm_dev, |
232 | "Header Byte 2: value = 0x%x, parity_byte = 0x%x\n" , |
233 | value, parity_byte); |
234 | dp_audio_set_header(catalog, data: value, |
235 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_2); |
236 | |
237 | /* Config header and parity byte 3 */ |
238 | value = dp_audio_get_header(catalog, |
239 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_3); |
240 | |
241 | new_value = 0x0; |
242 | parity_byte = dp_utils_calculate_parity(data: new_value); |
243 | value |= ((new_value << HEADER_BYTE_3_BIT) |
244 | | (parity_byte << PARITY_BYTE_3_BIT)); |
245 | drm_dbg_dp(audio->drm_dev, |
246 | "Header Byte 3: value = 0x%x, parity_byte = 0x%x\n" , |
247 | value, parity_byte); |
248 | dp_audio_set_header(catalog, data: value, |
249 | sdp: DP_AUDIO_SDP_COPYMANAGEMENT, header: DP_AUDIO_SDP_HEADER_3); |
250 | } |
251 | |
252 | static void dp_audio_isrc_sdp(struct dp_audio_private *audio) |
253 | { |
254 | struct dp_catalog *catalog = audio->catalog; |
255 | u32 value, new_value; |
256 | u8 parity_byte; |
257 | |
258 | /* Config header and parity byte 1 */ |
259 | value = dp_audio_get_header(catalog, |
260 | sdp: DP_AUDIO_SDP_ISRC, header: DP_AUDIO_SDP_HEADER_1); |
261 | |
262 | new_value = 0x06; |
263 | parity_byte = dp_utils_calculate_parity(data: new_value); |
264 | value |= ((new_value << HEADER_BYTE_1_BIT) |
265 | | (parity_byte << PARITY_BYTE_1_BIT)); |
266 | drm_dbg_dp(audio->drm_dev, |
267 | "Header Byte 1: value = 0x%x, parity_byte = 0x%x\n" , |
268 | value, parity_byte); |
269 | dp_audio_set_header(catalog, data: value, |
270 | sdp: DP_AUDIO_SDP_ISRC, header: DP_AUDIO_SDP_HEADER_1); |
271 | |
272 | /* Config header and parity byte 2 */ |
273 | value = dp_audio_get_header(catalog, |
274 | sdp: DP_AUDIO_SDP_ISRC, header: DP_AUDIO_SDP_HEADER_2); |
275 | |
276 | new_value = 0x0F; |
277 | parity_byte = dp_utils_calculate_parity(data: new_value); |
278 | value |= ((new_value << HEADER_BYTE_2_BIT) |
279 | | (parity_byte << PARITY_BYTE_2_BIT)); |
280 | drm_dbg_dp(audio->drm_dev, |
281 | "Header Byte 2: value = 0x%x, parity_byte = 0x%x\n" , |
282 | value, parity_byte); |
283 | dp_audio_set_header(catalog, data: value, |
284 | sdp: DP_AUDIO_SDP_ISRC, header: DP_AUDIO_SDP_HEADER_2); |
285 | } |
286 | |
287 | static void dp_audio_setup_sdp(struct dp_audio_private *audio) |
288 | { |
289 | dp_catalog_audio_config_sdp(catalog: audio->catalog); |
290 | |
291 | dp_audio_stream_sdp(audio); |
292 | dp_audio_timestamp_sdp(audio); |
293 | dp_audio_infoframe_sdp(audio); |
294 | dp_audio_copy_management_sdp(audio); |
295 | dp_audio_isrc_sdp(audio); |
296 | } |
297 | |
298 | static void dp_audio_setup_acr(struct dp_audio_private *audio) |
299 | { |
300 | u32 select = 0; |
301 | struct dp_catalog *catalog = audio->catalog; |
302 | |
303 | switch (audio->dp_audio.bw_code) { |
304 | case DP_LINK_BW_1_62: |
305 | select = 0; |
306 | break; |
307 | case DP_LINK_BW_2_7: |
308 | select = 1; |
309 | break; |
310 | case DP_LINK_BW_5_4: |
311 | select = 2; |
312 | break; |
313 | case DP_LINK_BW_8_1: |
314 | select = 3; |
315 | break; |
316 | default: |
317 | drm_dbg_dp(audio->drm_dev, "Unknown link rate\n" ); |
318 | select = 0; |
319 | break; |
320 | } |
321 | |
322 | catalog->audio_data = select; |
323 | dp_catalog_audio_config_acr(catalog); |
324 | } |
325 | |
326 | static void dp_audio_safe_to_exit_level(struct dp_audio_private *audio) |
327 | { |
328 | struct dp_catalog *catalog = audio->catalog; |
329 | u32 safe_to_exit_level = 0; |
330 | |
331 | switch (audio->dp_audio.lane_count) { |
332 | case 1: |
333 | safe_to_exit_level = 14; |
334 | break; |
335 | case 2: |
336 | safe_to_exit_level = 8; |
337 | break; |
338 | case 4: |
339 | safe_to_exit_level = 5; |
340 | break; |
341 | default: |
342 | drm_dbg_dp(audio->drm_dev, |
343 | "setting the default safe_to_exit_level = %u\n" , |
344 | safe_to_exit_level); |
345 | safe_to_exit_level = 14; |
346 | break; |
347 | } |
348 | |
349 | catalog->audio_data = safe_to_exit_level; |
350 | dp_catalog_audio_sfe_level(catalog); |
351 | } |
352 | |
353 | static void dp_audio_enable(struct dp_audio_private *audio, bool enable) |
354 | { |
355 | struct dp_catalog *catalog = audio->catalog; |
356 | |
357 | catalog->audio_data = enable; |
358 | dp_catalog_audio_enable(catalog); |
359 | |
360 | audio->engine_on = enable; |
361 | } |
362 | |
363 | static struct dp_audio_private *dp_audio_get_data(struct platform_device *pdev) |
364 | { |
365 | struct dp_audio *dp_audio; |
366 | struct msm_dp *dp_display; |
367 | |
368 | if (!pdev) { |
369 | DRM_ERROR("invalid input\n" ); |
370 | return ERR_PTR(error: -ENODEV); |
371 | } |
372 | |
373 | dp_display = platform_get_drvdata(pdev); |
374 | if (!dp_display) { |
375 | DRM_ERROR("invalid input\n" ); |
376 | return ERR_PTR(error: -ENODEV); |
377 | } |
378 | |
379 | dp_audio = dp_display->dp_audio; |
380 | |
381 | if (!dp_audio) { |
382 | DRM_ERROR("invalid dp_audio data\n" ); |
383 | return ERR_PTR(error: -EINVAL); |
384 | } |
385 | |
386 | return container_of(dp_audio, struct dp_audio_private, dp_audio); |
387 | } |
388 | |
389 | static int dp_audio_hook_plugged_cb(struct device *dev, void *data, |
390 | hdmi_codec_plugged_cb fn, |
391 | struct device *codec_dev) |
392 | { |
393 | |
394 | struct platform_device *pdev; |
395 | struct msm_dp *dp_display; |
396 | |
397 | pdev = to_platform_device(dev); |
398 | if (!pdev) { |
399 | pr_err("invalid input\n" ); |
400 | return -ENODEV; |
401 | } |
402 | |
403 | dp_display = platform_get_drvdata(pdev); |
404 | if (!dp_display) { |
405 | pr_err("invalid input\n" ); |
406 | return -ENODEV; |
407 | } |
408 | |
409 | return dp_display_set_plugged_cb(dp_display, fn, codec_dev); |
410 | } |
411 | |
412 | static int dp_audio_get_eld(struct device *dev, |
413 | void *data, uint8_t *buf, size_t len) |
414 | { |
415 | struct platform_device *pdev; |
416 | struct msm_dp *dp_display; |
417 | |
418 | pdev = to_platform_device(dev); |
419 | |
420 | if (!pdev) { |
421 | DRM_ERROR("invalid input\n" ); |
422 | return -ENODEV; |
423 | } |
424 | |
425 | dp_display = platform_get_drvdata(pdev); |
426 | if (!dp_display) { |
427 | DRM_ERROR("invalid input\n" ); |
428 | return -ENODEV; |
429 | } |
430 | |
431 | memcpy(buf, dp_display->connector->eld, |
432 | min(sizeof(dp_display->connector->eld), len)); |
433 | |
434 | return 0; |
435 | } |
436 | |
437 | int dp_audio_hw_params(struct device *dev, |
438 | void *data, |
439 | struct hdmi_codec_daifmt *daifmt, |
440 | struct hdmi_codec_params *params) |
441 | { |
442 | int rc = 0; |
443 | struct dp_audio_private *audio; |
444 | struct platform_device *pdev; |
445 | struct msm_dp *dp_display; |
446 | |
447 | pdev = to_platform_device(dev); |
448 | dp_display = platform_get_drvdata(pdev); |
449 | |
450 | /* |
451 | * there could be cases where sound card can be opened even |
452 | * before OR even when DP is not connected . This can cause |
453 | * unclocked access as the audio subsystem relies on the DP |
454 | * driver to maintain the correct state of clocks. To protect |
455 | * such cases check for connection status and bail out if not |
456 | * connected. |
457 | */ |
458 | if (!dp_display->power_on) { |
459 | rc = -EINVAL; |
460 | goto end; |
461 | } |
462 | |
463 | audio = dp_audio_get_data(pdev); |
464 | if (IS_ERR(ptr: audio)) { |
465 | rc = PTR_ERR(ptr: audio); |
466 | goto end; |
467 | } |
468 | |
469 | audio->channels = params->channels; |
470 | |
471 | dp_audio_setup_sdp(audio); |
472 | dp_audio_setup_acr(audio); |
473 | dp_audio_safe_to_exit_level(audio); |
474 | dp_audio_enable(audio, enable: true); |
475 | dp_display_signal_audio_start(dp_display); |
476 | dp_display->audio_enabled = true; |
477 | |
478 | end: |
479 | return rc; |
480 | } |
481 | |
482 | static void dp_audio_shutdown(struct device *dev, void *data) |
483 | { |
484 | struct dp_audio_private *audio; |
485 | struct platform_device *pdev; |
486 | struct msm_dp *dp_display; |
487 | |
488 | pdev = to_platform_device(dev); |
489 | dp_display = platform_get_drvdata(pdev); |
490 | audio = dp_audio_get_data(pdev); |
491 | if (IS_ERR(ptr: audio)) { |
492 | DRM_ERROR("failed to get audio data\n" ); |
493 | return; |
494 | } |
495 | |
496 | /* |
497 | * if audio was not enabled there is no need |
498 | * to execute the shutdown and we can bail out early. |
499 | * This also makes sure that we dont cause an unclocked |
500 | * access when audio subsystem calls this without DP being |
501 | * connected. is_connected cannot be used here as its set |
502 | * to false earlier than this call |
503 | */ |
504 | if (!dp_display->audio_enabled) |
505 | return; |
506 | |
507 | dp_audio_enable(audio, enable: false); |
508 | /* signal the dp display to safely shutdown clocks */ |
509 | dp_display_signal_audio_complete(dp_display); |
510 | } |
511 | |
512 | static const struct hdmi_codec_ops dp_audio_codec_ops = { |
513 | .hw_params = dp_audio_hw_params, |
514 | .audio_shutdown = dp_audio_shutdown, |
515 | .get_eld = dp_audio_get_eld, |
516 | .hook_plugged_cb = dp_audio_hook_plugged_cb, |
517 | }; |
518 | |
519 | static struct hdmi_codec_pdata codec_data = { |
520 | .ops = &dp_audio_codec_ops, |
521 | .max_i2s_channels = 8, |
522 | .i2s = 1, |
523 | }; |
524 | |
525 | void dp_unregister_audio_driver(struct device *dev, struct dp_audio *dp_audio) |
526 | { |
527 | struct dp_audio_private *audio_priv; |
528 | |
529 | audio_priv = container_of(dp_audio, struct dp_audio_private, dp_audio); |
530 | |
531 | if (audio_priv->audio_pdev) { |
532 | platform_device_unregister(audio_priv->audio_pdev); |
533 | audio_priv->audio_pdev = NULL; |
534 | } |
535 | } |
536 | |
537 | int dp_register_audio_driver(struct device *dev, |
538 | struct dp_audio *dp_audio) |
539 | { |
540 | struct dp_audio_private *audio_priv; |
541 | |
542 | audio_priv = container_of(dp_audio, |
543 | struct dp_audio_private, dp_audio); |
544 | |
545 | audio_priv->audio_pdev = platform_device_register_data(parent: dev, |
546 | HDMI_CODEC_DRV_NAME, |
547 | PLATFORM_DEVID_AUTO, |
548 | data: &codec_data, |
549 | size: sizeof(codec_data)); |
550 | return PTR_ERR_OR_ZERO(ptr: audio_priv->audio_pdev); |
551 | } |
552 | |
553 | struct dp_audio *dp_audio_get(struct platform_device *pdev, |
554 | struct dp_panel *panel, |
555 | struct dp_catalog *catalog) |
556 | { |
557 | int rc = 0; |
558 | struct dp_audio_private *audio; |
559 | struct dp_audio *dp_audio; |
560 | |
561 | if (!pdev || !panel || !catalog) { |
562 | DRM_ERROR("invalid input\n" ); |
563 | rc = -EINVAL; |
564 | goto error; |
565 | } |
566 | |
567 | audio = devm_kzalloc(dev: &pdev->dev, size: sizeof(*audio), GFP_KERNEL); |
568 | if (!audio) { |
569 | rc = -ENOMEM; |
570 | goto error; |
571 | } |
572 | |
573 | audio->pdev = pdev; |
574 | audio->panel = panel; |
575 | audio->catalog = catalog; |
576 | |
577 | dp_audio = &audio->dp_audio; |
578 | |
579 | dp_catalog_audio_init(catalog); |
580 | |
581 | return dp_audio; |
582 | error: |
583 | return ERR_PTR(error: rc); |
584 | } |
585 | |
586 | void dp_audio_put(struct dp_audio *dp_audio) |
587 | { |
588 | struct dp_audio_private *audio; |
589 | |
590 | if (!dp_audio) |
591 | return; |
592 | |
593 | audio = container_of(dp_audio, struct dp_audio_private, dp_audio); |
594 | |
595 | devm_kfree(dev: &audio->pdev->dev, p: audio); |
596 | } |
597 | |