blob: 3be414bc9f84af365dab78063b5327cce4346781 [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
221 if (state == EXT_DISPLAY_CABLE_CONNECT) {
222 /* connect codec with interface */
223 *ext_disp->ops = data->codec_ops;
224
225 /* update pdev for interface to use */
226 ext_disp->ext_disp_data.intf_pdev = data->pdev;
227 ext_disp->ext_disp_data.intf_data = data->intf_data;
228
229 ext_disp->current_disp = type;
230
231 pr_debug("codec ops set for %s\n", msm_ext_disp_name(type));
232 } else if (state == EXT_DISPLAY_CABLE_DISCONNECT) {
233 *ext_disp->ops = (struct msm_ext_disp_audio_codec_ops){NULL};
234 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
235
236 pr_debug("codec ops cleared for %s\n", msm_ext_disp_name(type));
237 }
238end:
239 return ret;
240}
241
242static int msm_ext_disp_audio_config(struct platform_device *pdev,
243 enum msm_ext_disp_type type,
244 enum msm_ext_disp_cable_state state)
245{
246 int ret = 0;
247 struct msm_ext_disp *ext_disp;
248
249 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
250 if (IS_ERR(ext_disp)) {
251 ret = PTR_ERR(ext_disp);
252 goto end;
253 }
254
255 mutex_lock(&ext_disp->lock);
256 ret = msm_ext_disp_update_audio_ops(ext_disp, type, state);
257 mutex_unlock(&ext_disp->lock);
258end:
259 return ret;
260}
261
262static int msm_ext_disp_audio_notify(struct platform_device *pdev,
263 enum msm_ext_disp_type type,
264 enum msm_ext_disp_cable_state state)
265{
266 int ret = 0;
267 struct msm_ext_disp *ext_disp;
268
269 ext_disp = msm_ext_disp_validate_and_get(pdev, type, state);
270 if (IS_ERR(ext_disp)) {
271 ret = PTR_ERR(ext_disp);
272 goto end;
273 }
274
275 mutex_lock(&ext_disp->lock);
276 ret = msm_ext_disp_process_audio(ext_disp, type, state);
277 mutex_unlock(&ext_disp->lock);
278end:
279 return ret;
280}
281
282int msm_hdmi_register_audio_codec(struct platform_device *pdev,
283 struct msm_ext_disp_audio_codec_ops *ops)
284{
285 return msm_ext_disp_register_audio_codec(pdev, ops);
286}
287
288int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
289 struct msm_ext_disp_audio_codec_ops *ops)
290{
291 int ret = 0;
292 struct msm_ext_disp *ext_disp = NULL;
293 struct msm_ext_disp_data *ext_disp_data = NULL;
294
295 if (!pdev || !ops) {
296 pr_err("Invalid params\n");
297 return -EINVAL;
298 }
299
300 ext_disp_data = platform_get_drvdata(pdev);
301 if (!ext_disp_data) {
302 pr_err("Invalid drvdata\n");
303 return -EINVAL;
304 }
305
306 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
307 ext_disp_data);
308
309 mutex_lock(&ext_disp->lock);
310
311 if ((ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
312 && ext_disp->ops) {
313 pr_err("Codec already registered\n");
314 ret = -EINVAL;
315 goto end;
316 }
317
318 ext_disp->ops = ops;
319
320 pr_debug("audio codec registered\n");
321
322end:
323 mutex_unlock(&ext_disp->lock);
324
325 return ret;
326}
327
328static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
329{
330 if (!init_data) {
331 pr_err("Invalid init_data\n");
332 return -EINVAL;
333 }
334
335 if (!init_data->pdev) {
336 pr_err("Invalid display intf pdev\n");
337 return -EINVAL;
338 }
339
340 if (!init_data->codec_ops.get_audio_edid_blk ||
341 !init_data->codec_ops.cable_status ||
342 !init_data->codec_ops.audio_info_setup) {
343 pr_err("Invalid codec operation pointers\n");
344 return -EINVAL;
345 }
346
347 return 0;
348}
349
350int msm_ext_disp_register_intf(struct platform_device *pdev,
351 struct msm_ext_disp_init_data *init_data)
352{
353 int ret = 0;
354 struct msm_ext_disp_init_data *data = NULL;
355 struct msm_ext_disp *ext_disp = NULL;
356 struct msm_ext_disp_data *ext_disp_data = NULL;
357
358 if (!pdev || !init_data) {
359 pr_err("Invalid params\n");
360 return -EINVAL;
361 }
362
363 ext_disp_data = platform_get_drvdata(pdev);
364 if (!ext_disp_data) {
365 pr_err("Invalid drvdata\n");
366 return -EINVAL;
367 }
368
369 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
370 ext_disp_data);
371
372 mutex_lock(&ext_disp->lock);
373
374 ret = msm_ext_disp_validate_intf(init_data);
375 if (ret)
376 goto end;
377
378 ret = msm_ext_disp_get_intf_data(ext_disp, init_data->type, &data);
379 if (!ret) {
380 pr_err("%s already registered\n",
381 msm_ext_disp_name(init_data->type));
382 goto end;
383 }
384
385 ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
386 if (ret)
387 goto end;
388
389 init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
390 init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
391
392 pr_debug("%s registered\n", msm_ext_disp_name(init_data->type));
393
394 mutex_unlock(&ext_disp->lock);
395
396 return ret;
397
398end:
399 mutex_unlock(&ext_disp->lock);
400
401 return ret;
402}
403
404static int msm_ext_disp_probe(struct platform_device *pdev)
405{
406 int ret = 0;
407 struct device_node *of_node = NULL;
408 struct msm_ext_disp *ext_disp = NULL;
409
410 if (!pdev) {
411 pr_err("No platform device found\n");
412 ret = -ENODEV;
413 goto end;
414 }
415
416 of_node = pdev->dev.of_node;
417 if (!of_node) {
418 pr_err("No device node found\n");
419 ret = -ENODEV;
420 goto end;
421 }
422
423 ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
424 if (!ext_disp) {
425 ret = -ENOMEM;
426 goto end;
427 }
428
429 platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
430 ext_disp->pdev = pdev;
431
432 ret = msm_ext_disp_extcon_register(ext_disp);
433 if (ret)
434 goto extcon_dev_failure;
435
436 ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
437 if (ret) {
438 pr_err("Failed to add child devices. Error = %d\n", ret);
439 goto child_node_failure;
440 } else {
441 pr_debug("%s: Added child devices.\n", __func__);
442 }
443
444 mutex_init(&ext_disp->lock);
445
446 INIT_LIST_HEAD(&ext_disp->display_list);
447 ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
448
449 return ret;
450
451child_node_failure:
452 msm_ext_disp_extcon_unregister(ext_disp);
453extcon_dev_failure:
454 devm_kfree(&ext_disp->pdev->dev, ext_disp);
455end:
456 return ret;
457}
458
459static int msm_ext_disp_remove(struct platform_device *pdev)
460{
461 int ret = 0;
462 struct msm_ext_disp *ext_disp = NULL;
463 struct msm_ext_disp_data *ext_disp_data = NULL;
464
465 if (!pdev) {
466 pr_err("No platform device\n");
467 ret = -ENODEV;
468 goto end;
469 }
470
471 ext_disp_data = platform_get_drvdata(pdev);
472 if (!ext_disp_data) {
473 pr_err("No drvdata found\n");
474 ret = -ENODEV;
475 goto end;
476 }
477
478 ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
479 ext_disp_data);
480
481 msm_ext_disp_extcon_unregister(ext_disp);
482
483 mutex_destroy(&ext_disp->lock);
484 devm_kfree(&ext_disp->pdev->dev, ext_disp);
485
486end:
487 return ret;
488}
489
490static const struct of_device_id msm_ext_dt_match[] = {
491 {.compatible = "qcom,msm-ext-disp",},
492 { /* Sentinel */ },
493};
494MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
495
496static struct platform_driver this_driver = {
497 .probe = msm_ext_disp_probe,
498 .remove = msm_ext_disp_remove,
499 .driver = {
500 .name = "msm-ext-disp",
501 .of_match_table = msm_ext_dt_match,
502 },
503};
504
505static int __init msm_ext_disp_init(void)
506{
507 int ret = 0;
508
509 ret = platform_driver_register(&this_driver);
510 if (ret)
511 pr_err("failed, ret = %d\n", ret);
512
513 return ret;
514}
515
516static void __exit msm_ext_disp_exit(void)
517{
518 platform_driver_unregister(&this_driver);
519}
520
521subsys_initcall(msm_ext_disp_init);
522module_exit(msm_ext_disp_exit);
523
524MODULE_LICENSE("GPL v2");
525MODULE_DESCRIPTION("MSM External Display");