blob: 73bf9351afb89ce0283b6705c9936fd4a6753e51 [file] [log] [blame]
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -07001/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/slab.h>
16#include <linux/bitops.h>
17#include <linux/delay.h>
18#include <linux/module.h>
19#include <linux/mutex.h>
20#include <linux/iopoll.h>
21#include <linux/types.h>
22#include <linux/of_platform.h>
23#include <linux/msm_ext_display.h>
24
25struct msm_ext_disp_list {
26 struct msm_ext_disp_init_data *data;
27 struct list_head list;
28};
29
30struct msm_ext_disp {
31 struct msm_ext_disp_data ext_disp_data;
32 struct platform_device *pdev;
33 enum msm_ext_disp_type current_disp;
34 struct msm_ext_disp_audio_codec_ops *ops;
35 struct extcon_dev audio_sdev;
36 bool audio_session_on;
37 struct list_head display_list;
38 struct mutex lock;
39};
40
41static const unsigned int msm_ext_disp_supported_cable[] = {
42 EXTCON_DISP_DP,
43 EXTCON_DISP_HDMI,
44 EXTCON_NONE,
45};
46
47static int msm_ext_disp_extcon_register(struct msm_ext_disp *ext_disp)
48{
49 int ret = 0;
50
51 if (!ext_disp) {
52 pr_err("invalid params\n");
53 return -EINVAL;
54 }
55
56 memset(&ext_disp->audio_sdev, 0x0, sizeof(ext_disp->audio_sdev));
57 ext_disp->audio_sdev.supported_cable = msm_ext_disp_supported_cable;
58 ext_disp->audio_sdev.dev.parent = &ext_disp->pdev->dev;
59 ret = extcon_dev_register(&ext_disp->audio_sdev);
60 if (ret) {
61 pr_err("audio registration failed");
62 return ret;
63 }
64
65 pr_debug("extcon registration done\n");
66
67 return ret;
68}
69
70static void msm_ext_disp_extcon_unregister(struct msm_ext_disp *ext_disp)
71{
72 if (!ext_disp) {
73 pr_err("Invalid params\n");
74 return;
75 }
76
77 extcon_dev_unregister(&ext_disp->audio_sdev);
78}
79
80static const char *msm_ext_disp_name(enum msm_ext_disp_type type)
81{
82 switch (type) {
83 case EXT_DISPLAY_TYPE_HDMI: return "EXT_DISPLAY_TYPE_HDMI";
84 case EXT_DISPLAY_TYPE_DP: return "EXT_DISPLAY_TYPE_DP";
85 default: return "???";
86 }
87}
88
89static int msm_ext_disp_add_intf_data(struct msm_ext_disp *ext_disp,
90 struct msm_ext_disp_init_data *data)
91{
92 struct msm_ext_disp_list *node;
93
94 if (!ext_disp && !data) {
95 pr_err("Invalid params\n");
96 return -EINVAL;
97 }
98
99 node = kzalloc(sizeof(*node), GFP_KERNEL);
100 if (!node)
101 return -ENOMEM;
102
103 node->data = data;
104 list_add(&node->list, &ext_disp->display_list);
105
106 pr_debug("Added new display (%s)\n", msm_ext_disp_name(data->type));
107
108 return 0;
109}
110
111static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp,
112 enum msm_ext_disp_type type,
113 struct msm_ext_disp_init_data **data)
114{
115 int ret = 0;
116 struct msm_ext_disp_list *node;
117 struct list_head *position = NULL;
118
119 if (!ext_disp || !data || type < EXT_DISPLAY_TYPE_HDMI ||
120 type >= EXT_DISPLAY_TYPE_MAX) {
121 pr_err("Invalid params\n");
122 ret = -EINVAL;
123 goto end;
124 }
125
126 *data = NULL;
127 list_for_each(position, &ext_disp->display_list) {
128 node = list_entry(position, struct msm_ext_disp_list, list);
129 if (node->data->type == type) {
130 *data = node->data;
131 break;
132 }
133 }
134
135 if (!*data) {
136 pr_err("Display not found (%s)\n", msm_ext_disp_name(type));
137 ret = -ENODEV;
138 }
139end:
140 return ret;
141}
142
143static int msm_ext_disp_process_audio(struct msm_ext_disp *ext_disp,
144 enum msm_ext_disp_type type,
145 enum msm_ext_disp_cable_state new_state)
146{
147 int ret = 0;
148 int state;
149
150 state = ext_disp->audio_sdev.state;
151 ret = extcon_set_state_sync(&ext_disp->audio_sdev,
152 ext_disp->current_disp, !!new_state);
153
154 pr_debug("Audio state %s %d\n",
155 ext_disp->audio_sdev.state == state ?
156 "is same" : "switched to",
157 ext_disp->audio_sdev.state);
158
159 return ret;
160}
161
162static struct msm_ext_disp *msm_ext_disp_validate_and_get(
163 struct platform_device *pdev,
164 enum msm_ext_disp_type type,
165 enum msm_ext_disp_cable_state state)
166{
167 struct msm_ext_disp_data *ext_disp_data;
168 struct msm_ext_disp *ext_disp;
169
170 if (!pdev) {
171 pr_err("invalid platform device\n");
172 goto err;
173 }
174
175 ext_disp_data = platform_get_drvdata(pdev);
176 if (!ext_disp_data) {
177 pr_err("invalid drvdata\n");
178 goto err;
179 }
180
181 ext_disp = container_of(ext_disp_data,
182 struct msm_ext_disp, ext_disp_data);
183
184 if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
185 state >= EXT_DISPLAY_CABLE_STATE_MAX) {
186 pr_err("invalid HPD state (%d)\n", state);
187 goto err;
188 }
189
190 if (state == EXT_DISPLAY_CABLE_CONNECT) {
191 if (ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX &&
192 ext_disp->current_disp != type) {
193 pr_err("invalid interface call\n");
194 goto err;
195 }
196 } else {
197 if (ext_disp->current_disp == EXT_DISPLAY_TYPE_MAX ||
198 ext_disp->current_disp != type) {
199 pr_err("invalid interface call\n");
200 goto err;
201 }
202 }
203 return ext_disp;
204err:
205 return ERR_PTR(-EINVAL);
206}
207
208static int msm_ext_disp_update_audio_ops(struct msm_ext_disp *ext_disp,
209 enum msm_ext_disp_type type,
210 enum msm_ext_disp_cable_state state)
211{
212 int ret = 0;
213 struct msm_ext_disp_init_data *data = NULL;
214
215 ret = msm_ext_disp_get_intf_data(ext_disp, type, &data);
216 if (ret || !data) {
217 pr_err("interface %s not found\n", msm_ext_disp_name(type));
218 goto end;
219 }
220
Ajay Singh Parmar255c4f62017-08-23 08:37:46 -0700221 if (!ext_disp->ops) {
222 pr_err("codec ops not registered\n");
223 ret = -EINVAL;
224 goto end;
225 }
226
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700227 if (state == EXT_DISPLAY_CABLE_CONNECT) {
228 /* connect codec with interface */
229 *ext_disp->ops = data->codec_ops;
230
231 /* update pdev for interface to use */
232 ext_disp->ext_disp_data.intf_pdev = data->pdev;
233 ext_disp->ext_disp_data.intf_data = data->intf_data;
234
235 ext_disp->current_disp = type;
236
237 pr_debug("codec ops set for %s\n", msm_ext_disp_name(type));
238 } else if (state == EXT_DISPLAY_CABLE_DISCONNECT) {
239 *ext_disp->ops = (struct msm_ext_disp_audio_codec_ops){NULL};
240 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
241
242 pr_debug("codec ops cleared for %s\n", msm_ext_disp_name(type));
243 }
244end:
245 return ret;
246}
247
248static int msm_ext_disp_audio_config(struct platform_device *pdev,
249 enum msm_ext_disp_type type,
250 enum msm_ext_disp_cable_state state)
251{
252 int ret = 0;
253 struct msm_ext_disp *ext_disp;
254
255 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
256 if (IS_ERR(ext_disp)) {
257 ret = PTR_ERR(ext_disp);
258 goto end;
259 }
260
261 mutex_lock(&ext_disp->lock);
262 ret = msm_ext_disp_update_audio_ops(ext_disp, type, state);
263 mutex_unlock(&ext_disp->lock);
264end:
265 return ret;
266}
267
268static int msm_ext_disp_audio_notify(struct platform_device *pdev,
269 enum msm_ext_disp_type type,
270 enum msm_ext_disp_cable_state state)
271{
272 int ret = 0;
273 struct msm_ext_disp *ext_disp;
274
275 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
276 if (IS_ERR(ext_disp)) {
277 ret = PTR_ERR(ext_disp);
278 goto end;
279 }
280
281 mutex_lock(&ext_disp->lock);
282 ret = msm_ext_disp_process_audio(ext_disp, type, state);
283 mutex_unlock(&ext_disp->lock);
284end:
285 return ret;
286}
287
288int msm_hdmi_register_audio_codec(struct platform_device *pdev,
289 struct msm_ext_disp_audio_codec_ops *ops)
290{
291 return msm_ext_disp_register_audio_codec(pdev, ops);
292}
293
Laxminath Kasamb04f0222017-09-28 15:50:21 +0530294/**
295 * Register audio codec ops to display driver
296 * for HDMI/Display Port usecase support.
297 *
298 * @return 0 on success, negative value on error
299 *
300 */
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700301int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
302 struct msm_ext_disp_audio_codec_ops *ops)
303{
304 int ret = 0;
305 struct msm_ext_disp *ext_disp = NULL;
306 struct msm_ext_disp_data *ext_disp_data = NULL;
307
308 if (!pdev || !ops) {
309 pr_err("Invalid params\n");
310 return -EINVAL;
311 }
312
313 ext_disp_data = platform_get_drvdata(pdev);
314 if (!ext_disp_data) {
315 pr_err("Invalid drvdata\n");
316 return -EINVAL;
317 }
318
319 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
320 ext_disp_data);
321
322 mutex_lock(&ext_disp->lock);
323
324 if ((ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
325 && ext_disp->ops) {
326 pr_err("Codec already registered\n");
327 ret = -EINVAL;
328 goto end;
329 }
330
331 ext_disp->ops = ops;
332
333 pr_debug("audio codec registered\n");
334
335end:
336 mutex_unlock(&ext_disp->lock);
337
338 return ret;
339}
Laxminath Kasamb04f0222017-09-28 15:50:21 +0530340EXPORT_SYMBOL(msm_ext_disp_register_audio_codec);
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700341
342static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
343{
344 if (!init_data) {
345 pr_err("Invalid init_data\n");
346 return -EINVAL;
347 }
348
349 if (!init_data->pdev) {
350 pr_err("Invalid display intf pdev\n");
351 return -EINVAL;
352 }
353
354 if (!init_data->codec_ops.get_audio_edid_blk ||
355 !init_data->codec_ops.cable_status ||
356 !init_data->codec_ops.audio_info_setup) {
357 pr_err("Invalid codec operation pointers\n");
358 return -EINVAL;
359 }
360
361 return 0;
362}
363
364int msm_ext_disp_register_intf(struct platform_device *pdev,
365 struct msm_ext_disp_init_data *init_data)
366{
367 int ret = 0;
368 struct msm_ext_disp_init_data *data = NULL;
369 struct msm_ext_disp *ext_disp = NULL;
370 struct msm_ext_disp_data *ext_disp_data = NULL;
371
372 if (!pdev || !init_data) {
373 pr_err("Invalid params\n");
374 return -EINVAL;
375 }
376
377 ext_disp_data = platform_get_drvdata(pdev);
378 if (!ext_disp_data) {
379 pr_err("Invalid drvdata\n");
380 return -EINVAL;
381 }
382
383 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
384 ext_disp_data);
385
386 mutex_lock(&ext_disp->lock);
387
388 ret = msm_ext_disp_validate_intf(init_data);
389 if (ret)
390 goto end;
391
392 ret = msm_ext_disp_get_intf_data(ext_disp, init_data->type, &data);
393 if (!ret) {
394 pr_err("%s already registered\n",
395 msm_ext_disp_name(init_data->type));
396 goto end;
397 }
398
399 ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
400 if (ret)
401 goto end;
402
403 init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
404 init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
405
406 pr_debug("%s registered\n", msm_ext_disp_name(init_data->type));
407
408 mutex_unlock(&ext_disp->lock);
409
410 return ret;
411
412end:
413 mutex_unlock(&ext_disp->lock);
414
415 return ret;
416}
417
418static int msm_ext_disp_probe(struct platform_device *pdev)
419{
420 int ret = 0;
421 struct device_node *of_node = NULL;
422 struct msm_ext_disp *ext_disp = NULL;
423
424 if (!pdev) {
425 pr_err("No platform device found\n");
426 ret = -ENODEV;
427 goto end;
428 }
429
430 of_node = pdev->dev.of_node;
431 if (!of_node) {
432 pr_err("No device node found\n");
433 ret = -ENODEV;
434 goto end;
435 }
436
437 ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
438 if (!ext_disp) {
439 ret = -ENOMEM;
440 goto end;
441 }
442
443 platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
444 ext_disp->pdev = pdev;
445
446 ret = msm_ext_disp_extcon_register(ext_disp);
447 if (ret)
448 goto extcon_dev_failure;
449
450 ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
451 if (ret) {
452 pr_err("Failed to add child devices. Error = %d\n", ret);
453 goto child_node_failure;
454 } else {
455 pr_debug("%s: Added child devices.\n", __func__);
456 }
457
458 mutex_init(&ext_disp->lock);
459
460 INIT_LIST_HEAD(&ext_disp->display_list);
461 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
462
463 return ret;
464
465child_node_failure:
466 msm_ext_disp_extcon_unregister(ext_disp);
467extcon_dev_failure:
468 devm_kfree(&ext_disp->pdev->dev, ext_disp);
469end:
470 return ret;
471}
472
473static int msm_ext_disp_remove(struct platform_device *pdev)
474{
475 int ret = 0;
476 struct msm_ext_disp *ext_disp = NULL;
477 struct msm_ext_disp_data *ext_disp_data = NULL;
478
479 if (!pdev) {
480 pr_err("No platform device\n");
481 ret = -ENODEV;
482 goto end;
483 }
484
485 ext_disp_data = platform_get_drvdata(pdev);
486 if (!ext_disp_data) {
487 pr_err("No drvdata found\n");
488 ret = -ENODEV;
489 goto end;
490 }
491
492 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
493 ext_disp_data);
494
495 msm_ext_disp_extcon_unregister(ext_disp);
496
497 mutex_destroy(&ext_disp->lock);
498 devm_kfree(&ext_disp->pdev->dev, ext_disp);
499
500end:
501 return ret;
502}
503
504static const struct of_device_id msm_ext_dt_match[] = {
505 {.compatible = "qcom,msm-ext-disp",},
506 { /* Sentinel */ },
507};
508MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
509
510static struct platform_driver this_driver = {
511 .probe = msm_ext_disp_probe,
512 .remove = msm_ext_disp_remove,
513 .driver = {
514 .name = "msm-ext-disp",
515 .of_match_table = msm_ext_dt_match,
516 },
517};
518
519static int __init msm_ext_disp_init(void)
520{
521 int ret = 0;
522
523 ret = platform_driver_register(&this_driver);
524 if (ret)
525 pr_err("failed, ret = %d\n", ret);
526
527 return ret;
528}
529
530static void __exit msm_ext_disp_exit(void)
531{
532 platform_driver_unregister(&this_driver);
533}
534
535subsys_initcall(msm_ext_disp_init);
536module_exit(msm_ext_disp_exit);
537
538MODULE_LICENSE("GPL v2");
539MODULE_DESCRIPTION("MSM External Display");