blob: d5fc8670bf92ad97cac1d443dced405ed94af8a8 [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
294int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
295 struct msm_ext_disp_audio_codec_ops *ops)
296{
297 int ret = 0;
298 struct msm_ext_disp *ext_disp = NULL;
299 struct msm_ext_disp_data *ext_disp_data = NULL;
300
301 if (!pdev || !ops) {
302 pr_err("Invalid params\n");
303 return -EINVAL;
304 }
305
306 ext_disp_data = platform_get_drvdata(pdev);
307 if (!ext_disp_data) {
308 pr_err("Invalid drvdata\n");
309 return -EINVAL;
310 }
311
312 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
313 ext_disp_data);
314
315 mutex_lock(&ext_disp->lock);
316
317 if ((ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
318 && ext_disp->ops) {
319 pr_err("Codec already registered\n");
320 ret = -EINVAL;
321 goto end;
322 }
323
324 ext_disp->ops = ops;
325
326 pr_debug("audio codec registered\n");
327
328end:
329 mutex_unlock(&ext_disp->lock);
330
331 return ret;
332}
333
334static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
335{
336 if (!init_data) {
337 pr_err("Invalid init_data\n");
338 return -EINVAL;
339 }
340
341 if (!init_data->pdev) {
342 pr_err("Invalid display intf pdev\n");
343 return -EINVAL;
344 }
345
346 if (!init_data->codec_ops.get_audio_edid_blk ||
347 !init_data->codec_ops.cable_status ||
348 !init_data->codec_ops.audio_info_setup) {
349 pr_err("Invalid codec operation pointers\n");
350 return -EINVAL;
351 }
352
353 return 0;
354}
355
356int msm_ext_disp_register_intf(struct platform_device *pdev,
357 struct msm_ext_disp_init_data *init_data)
358{
359 int ret = 0;
360 struct msm_ext_disp_init_data *data = NULL;
361 struct msm_ext_disp *ext_disp = NULL;
362 struct msm_ext_disp_data *ext_disp_data = NULL;
363
364 if (!pdev || !init_data) {
365 pr_err("Invalid params\n");
366 return -EINVAL;
367 }
368
369 ext_disp_data = platform_get_drvdata(pdev);
370 if (!ext_disp_data) {
371 pr_err("Invalid drvdata\n");
372 return -EINVAL;
373 }
374
375 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
376 ext_disp_data);
377
378 mutex_lock(&ext_disp->lock);
379
380 ret = msm_ext_disp_validate_intf(init_data);
381 if (ret)
382 goto end;
383
384 ret = msm_ext_disp_get_intf_data(ext_disp, init_data->type, &data);
385 if (!ret) {
386 pr_err("%s already registered\n",
387 msm_ext_disp_name(init_data->type));
388 goto end;
389 }
390
391 ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
392 if (ret)
393 goto end;
394
395 init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
396 init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
397
398 pr_debug("%s registered\n", msm_ext_disp_name(init_data->type));
399
400 mutex_unlock(&ext_disp->lock);
401
402 return ret;
403
404end:
405 mutex_unlock(&ext_disp->lock);
406
407 return ret;
408}
409
410static int msm_ext_disp_probe(struct platform_device *pdev)
411{
412 int ret = 0;
413 struct device_node *of_node = NULL;
414 struct msm_ext_disp *ext_disp = NULL;
415
416 if (!pdev) {
417 pr_err("No platform device found\n");
418 ret = -ENODEV;
419 goto end;
420 }
421
422 of_node = pdev->dev.of_node;
423 if (!of_node) {
424 pr_err("No device node found\n");
425 ret = -ENODEV;
426 goto end;
427 }
428
429 ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
430 if (!ext_disp) {
431 ret = -ENOMEM;
432 goto end;
433 }
434
435 platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
436 ext_disp->pdev = pdev;
437
438 ret = msm_ext_disp_extcon_register(ext_disp);
439 if (ret)
440 goto extcon_dev_failure;
441
442 ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
443 if (ret) {
444 pr_err("Failed to add child devices. Error = %d\n", ret);
445 goto child_node_failure;
446 } else {
447 pr_debug("%s: Added child devices.\n", __func__);
448 }
449
450 mutex_init(&ext_disp->lock);
451
452 INIT_LIST_HEAD(&ext_disp->display_list);
453 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
454
455 return ret;
456
457child_node_failure:
458 msm_ext_disp_extcon_unregister(ext_disp);
459extcon_dev_failure:
460 devm_kfree(&ext_disp->pdev->dev, ext_disp);
461end:
462 return ret;
463}
464
465static int msm_ext_disp_remove(struct platform_device *pdev)
466{
467 int ret = 0;
468 struct msm_ext_disp *ext_disp = NULL;
469 struct msm_ext_disp_data *ext_disp_data = NULL;
470
471 if (!pdev) {
472 pr_err("No platform device\n");
473 ret = -ENODEV;
474 goto end;
475 }
476
477 ext_disp_data = platform_get_drvdata(pdev);
478 if (!ext_disp_data) {
479 pr_err("No drvdata found\n");
480 ret = -ENODEV;
481 goto end;
482 }
483
484 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
485 ext_disp_data);
486
487 msm_ext_disp_extcon_unregister(ext_disp);
488
489 mutex_destroy(&ext_disp->lock);
490 devm_kfree(&ext_disp->pdev->dev, ext_disp);
491
492end:
493 return ret;
494}
495
496static const struct of_device_id msm_ext_dt_match[] = {
497 {.compatible = "qcom,msm-ext-disp",},
498 { /* Sentinel */ },
499};
500MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
501
502static struct platform_driver this_driver = {
503 .probe = msm_ext_disp_probe,
504 .remove = msm_ext_disp_remove,
505 .driver = {
506 .name = "msm-ext-disp",
507 .of_match_table = msm_ext_dt_match,
508 },
509};
510
511static int __init msm_ext_disp_init(void)
512{
513 int ret = 0;
514
515 ret = platform_driver_register(&this_driver);
516 if (ret)
517 pr_err("failed, ret = %d\n", ret);
518
519 return ret;
520}
521
522static void __exit msm_ext_disp_exit(void)
523{
524 platform_driver_unregister(&this_driver);
525}
526
527subsys_initcall(msm_ext_disp_init);
528module_exit(msm_ext_disp_exit);
529
530MODULE_LICENSE("GPL v2");
531MODULE_DESCRIPTION("MSM External Display");