blob: bc4df044741cca28f53d5ad50eb1d73ce470da78 [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
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700150 if (!ext_disp->ops) {
151 pr_err("codec not registered, skip notification\n");
152 ret = -EPERM;
153 goto end;
154 }
155
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700156 state = ext_disp->audio_sdev.state;
157 ret = extcon_set_state_sync(&ext_disp->audio_sdev,
158 ext_disp->current_disp, !!new_state);
159
160 pr_debug("Audio state %s %d\n",
161 ext_disp->audio_sdev.state == state ?
162 "is same" : "switched to",
163 ext_disp->audio_sdev.state);
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700164end:
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700165 return ret;
166}
167
168static struct msm_ext_disp *msm_ext_disp_validate_and_get(
169 struct platform_device *pdev,
170 enum msm_ext_disp_type type,
171 enum msm_ext_disp_cable_state state)
172{
173 struct msm_ext_disp_data *ext_disp_data;
174 struct msm_ext_disp *ext_disp;
175
176 if (!pdev) {
177 pr_err("invalid platform device\n");
178 goto err;
179 }
180
181 ext_disp_data = platform_get_drvdata(pdev);
182 if (!ext_disp_data) {
183 pr_err("invalid drvdata\n");
184 goto err;
185 }
186
187 ext_disp = container_of(ext_disp_data,
188 struct msm_ext_disp, ext_disp_data);
189
190 if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
191 state >= EXT_DISPLAY_CABLE_STATE_MAX) {
192 pr_err("invalid HPD state (%d)\n", state);
193 goto err;
194 }
195
196 if (state == EXT_DISPLAY_CABLE_CONNECT) {
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 } else {
203 if (ext_disp->current_disp == EXT_DISPLAY_TYPE_MAX ||
204 ext_disp->current_disp != type) {
205 pr_err("invalid interface call\n");
206 goto err;
207 }
208 }
209 return ext_disp;
210err:
211 return ERR_PTR(-EINVAL);
212}
213
214static int msm_ext_disp_update_audio_ops(struct msm_ext_disp *ext_disp,
215 enum msm_ext_disp_type type,
216 enum msm_ext_disp_cable_state state)
217{
218 int ret = 0;
219 struct msm_ext_disp_init_data *data = NULL;
220
221 ret = msm_ext_disp_get_intf_data(ext_disp, type, &data);
222 if (ret || !data) {
223 pr_err("interface %s not found\n", msm_ext_disp_name(type));
224 goto end;
225 }
226
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700227 if (state == EXT_DISPLAY_CABLE_CONNECT) {
228 /* connect codec with interface */
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700229 if (ext_disp->ops)
230 *ext_disp->ops = data->codec_ops;
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700231
232 /* update pdev for interface to use */
233 ext_disp->ext_disp_data.intf_pdev = data->pdev;
234 ext_disp->ext_disp_data.intf_data = data->intf_data;
235
236 ext_disp->current_disp = type;
237
238 pr_debug("codec ops set for %s\n", msm_ext_disp_name(type));
239 } else if (state == EXT_DISPLAY_CABLE_DISCONNECT) {
Ajay Singh Parmarb1d5bcd2017-11-23 00:09:02 -0800240 if (ext_disp->ops)
241 *ext_disp->ops =
242 (struct msm_ext_disp_audio_codec_ops){NULL};
243
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700244 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
245
246 pr_debug("codec ops cleared for %s\n", msm_ext_disp_name(type));
247 }
248end:
249 return ret;
250}
251
252static int msm_ext_disp_audio_config(struct platform_device *pdev,
253 enum msm_ext_disp_type type,
254 enum msm_ext_disp_cable_state state)
255{
256 int ret = 0;
257 struct msm_ext_disp *ext_disp;
258
259 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
260 if (IS_ERR(ext_disp)) {
261 ret = PTR_ERR(ext_disp);
262 goto end;
263 }
264
265 mutex_lock(&ext_disp->lock);
266 ret = msm_ext_disp_update_audio_ops(ext_disp, type, state);
267 mutex_unlock(&ext_disp->lock);
268end:
269 return ret;
270}
271
272static int msm_ext_disp_audio_notify(struct platform_device *pdev,
273 enum msm_ext_disp_type type,
274 enum msm_ext_disp_cable_state state)
275{
276 int ret = 0;
277 struct msm_ext_disp *ext_disp;
278
279 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
280 if (IS_ERR(ext_disp)) {
281 ret = PTR_ERR(ext_disp);
282 goto end;
283 }
284
285 mutex_lock(&ext_disp->lock);
286 ret = msm_ext_disp_process_audio(ext_disp, type, state);
287 mutex_unlock(&ext_disp->lock);
288end:
289 return ret;
290}
291
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700292static void msm_ext_disp_ready_for_display(struct msm_ext_disp *ext_disp)
293{
294 int ret;
295 struct msm_ext_disp_init_data *data = NULL;
296
297 if (!ext_disp) {
298 pr_err("invalid input\n");
299 return;
300 }
301
302 ret = msm_ext_disp_get_intf_data(ext_disp,
303 ext_disp->current_disp, &data);
304 if (ret) {
305 pr_err("%s not found\n",
306 msm_ext_disp_name(ext_disp->current_disp));
307 return;
308 }
309
310 *ext_disp->ops = data->codec_ops;
311 data->codec_ops.ready(ext_disp->pdev);
312}
313
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700314int msm_hdmi_register_audio_codec(struct platform_device *pdev,
315 struct msm_ext_disp_audio_codec_ops *ops)
316{
317 return msm_ext_disp_register_audio_codec(pdev, ops);
318}
319
Laxminath Kasamb04f0222017-09-28 15:50:21 +0530320/**
321 * Register audio codec ops to display driver
322 * for HDMI/Display Port usecase support.
323 *
324 * @return 0 on success, negative value on error
325 *
326 */
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700327int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
328 struct msm_ext_disp_audio_codec_ops *ops)
329{
330 int ret = 0;
331 struct msm_ext_disp *ext_disp = NULL;
332 struct msm_ext_disp_data *ext_disp_data = NULL;
333
334 if (!pdev || !ops) {
335 pr_err("Invalid params\n");
336 return -EINVAL;
337 }
338
339 ext_disp_data = platform_get_drvdata(pdev);
340 if (!ext_disp_data) {
341 pr_err("Invalid drvdata\n");
342 return -EINVAL;
343 }
344
345 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
346 ext_disp_data);
347
348 mutex_lock(&ext_disp->lock);
349
350 if ((ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
351 && ext_disp->ops) {
352 pr_err("Codec already registered\n");
353 ret = -EINVAL;
354 goto end;
355 }
356
357 ext_disp->ops = ops;
358
359 pr_debug("audio codec registered\n");
360
361end:
362 mutex_unlock(&ext_disp->lock);
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700363 if (ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
364 msm_ext_disp_ready_for_display(ext_disp);
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700365
366 return ret;
367}
Laxminath Kasamb04f0222017-09-28 15:50:21 +0530368EXPORT_SYMBOL(msm_ext_disp_register_audio_codec);
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700369
370static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
371{
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700372 struct msm_ext_disp_audio_codec_ops *ops;
373
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700374 if (!init_data) {
375 pr_err("Invalid init_data\n");
376 return -EINVAL;
377 }
378
379 if (!init_data->pdev) {
380 pr_err("Invalid display intf pdev\n");
381 return -EINVAL;
382 }
383
Tatenda Chipeperekwadee24ac2017-10-26 18:04:33 -0700384 ops = &init_data->codec_ops;
385
386 if (!ops->audio_info_setup ||
387 !ops->get_audio_edid_blk ||
388 !ops->cable_status ||
389 !ops->get_intf_id ||
390 !ops->teardown_done ||
391 !ops->acknowledge ||
392 !ops->ready) {
Tatenda Chipeperekwa326526e2017-04-24 16:51:50 -0700393 pr_err("Invalid codec operation pointers\n");
394 return -EINVAL;
395 }
396
397 return 0;
398}
399
400int msm_ext_disp_register_intf(struct platform_device *pdev,
401 struct msm_ext_disp_init_data *init_data)
402{
403 int ret = 0;
404 struct msm_ext_disp_init_data *data = NULL;
405 struct msm_ext_disp *ext_disp = NULL;
406 struct msm_ext_disp_data *ext_disp_data = NULL;
407
408 if (!pdev || !init_data) {
409 pr_err("Invalid params\n");
410 return -EINVAL;
411 }
412
413 ext_disp_data = platform_get_drvdata(pdev);
414 if (!ext_disp_data) {
415 pr_err("Invalid drvdata\n");
416 return -EINVAL;
417 }
418
419 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
420 ext_disp_data);
421
422 mutex_lock(&ext_disp->lock);
423
424 ret = msm_ext_disp_validate_intf(init_data);
425 if (ret)
426 goto end;
427
428 ret = msm_ext_disp_get_intf_data(ext_disp, init_data->type, &data);
429 if (!ret) {
430 pr_err("%s already registered\n",
431 msm_ext_disp_name(init_data->type));
432 goto end;
433 }
434
435 ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
436 if (ret)
437 goto end;
438
439 init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
440 init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
441
442 pr_debug("%s registered\n", msm_ext_disp_name(init_data->type));
443
444 mutex_unlock(&ext_disp->lock);
445
446 return ret;
447
448end:
449 mutex_unlock(&ext_disp->lock);
450
451 return ret;
452}
453
454static int msm_ext_disp_probe(struct platform_device *pdev)
455{
456 int ret = 0;
457 struct device_node *of_node = NULL;
458 struct msm_ext_disp *ext_disp = NULL;
459
460 if (!pdev) {
461 pr_err("No platform device found\n");
462 ret = -ENODEV;
463 goto end;
464 }
465
466 of_node = pdev->dev.of_node;
467 if (!of_node) {
468 pr_err("No device node found\n");
469 ret = -ENODEV;
470 goto end;
471 }
472
473 ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
474 if (!ext_disp) {
475 ret = -ENOMEM;
476 goto end;
477 }
478
479 platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
480 ext_disp->pdev = pdev;
481
482 ret = msm_ext_disp_extcon_register(ext_disp);
483 if (ret)
484 goto extcon_dev_failure;
485
486 ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
487 if (ret) {
488 pr_err("Failed to add child devices. Error = %d\n", ret);
489 goto child_node_failure;
490 } else {
491 pr_debug("%s: Added child devices.\n", __func__);
492 }
493
494 mutex_init(&ext_disp->lock);
495
496 INIT_LIST_HEAD(&ext_disp->display_list);
497 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
498
499 return ret;
500
501child_node_failure:
502 msm_ext_disp_extcon_unregister(ext_disp);
503extcon_dev_failure:
504 devm_kfree(&ext_disp->pdev->dev, ext_disp);
505end:
506 return ret;
507}
508
509static int msm_ext_disp_remove(struct platform_device *pdev)
510{
511 int ret = 0;
512 struct msm_ext_disp *ext_disp = NULL;
513 struct msm_ext_disp_data *ext_disp_data = NULL;
514
515 if (!pdev) {
516 pr_err("No platform device\n");
517 ret = -ENODEV;
518 goto end;
519 }
520
521 ext_disp_data = platform_get_drvdata(pdev);
522 if (!ext_disp_data) {
523 pr_err("No drvdata found\n");
524 ret = -ENODEV;
525 goto end;
526 }
527
528 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
529 ext_disp_data);
530
531 msm_ext_disp_extcon_unregister(ext_disp);
532
533 mutex_destroy(&ext_disp->lock);
534 devm_kfree(&ext_disp->pdev->dev, ext_disp);
535
536end:
537 return ret;
538}
539
540static const struct of_device_id msm_ext_dt_match[] = {
541 {.compatible = "qcom,msm-ext-disp",},
542 { /* Sentinel */ },
543};
544MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
545
546static struct platform_driver this_driver = {
547 .probe = msm_ext_disp_probe,
548 .remove = msm_ext_disp_remove,
549 .driver = {
550 .name = "msm-ext-disp",
551 .of_match_table = msm_ext_dt_match,
552 },
553};
554
555static int __init msm_ext_disp_init(void)
556{
557 int ret = 0;
558
559 ret = platform_driver_register(&this_driver);
560 if (ret)
561 pr_err("failed, ret = %d\n", ret);
562
563 return ret;
564}
565
566static void __exit msm_ext_disp_exit(void)
567{
568 platform_driver_unregister(&this_driver);
569}
570
571subsys_initcall(msm_ext_disp_init);
572module_exit(msm_ext_disp_exit);
573
574MODULE_LICENSE("GPL v2");
575MODULE_DESCRIPTION("MSM External Display");