msm_ext_display.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
  4. * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
  5. */
  6. #define pr_fmt(fmt) "%s: " fmt, __func__
  7. #include <linux/slab.h>
  8. #include <linux/bitops.h>
  9. #include <linux/delay.h>
  10. #include <linux/module.h>
  11. #include <linux/mutex.h>
  12. #include <linux/iopoll.h>
  13. #include <linux/types.h>
  14. #include <linux/of_platform.h>
  15. #include <linux/extcon-provider.h>
  16. #include <linux/soc/qcom/msm_ext_display.h>
  17. #include <linux/extcon-provider.h>
  18. struct msm_ext_disp_list {
  19. struct msm_ext_disp_init_data *data;
  20. struct list_head list;
  21. };
  22. struct msm_ext_disp {
  23. struct msm_ext_disp_data ext_disp_data;
  24. struct platform_device *pdev;
  25. struct msm_ext_disp_codec_id current_codec;
  26. struct msm_ext_disp_audio_codec_ops *ops;
  27. struct extcon_dev *audio_sdev[MSM_EXT_DISP_MAX_CODECS];
  28. bool audio_session_on;
  29. struct list_head display_list;
  30. struct mutex lock;
  31. bool update_audio;
  32. };
  33. static const unsigned int msm_ext_disp_supported_cable[] = {
  34. EXTCON_DISP_DP,
  35. EXTCON_DISP_HDMI,
  36. EXTCON_NONE,
  37. };
  38. static int msm_ext_disp_extcon_register(struct msm_ext_disp *ext_disp, int id)
  39. {
  40. int ret = 0;
  41. if (!ext_disp || !ext_disp->pdev || id >= MSM_EXT_DISP_MAX_CODECS) {
  42. pr_err("invalid params\n");
  43. return -EINVAL;
  44. }
  45. ext_disp->audio_sdev[id] = devm_extcon_dev_allocate(
  46. &ext_disp->pdev->dev,
  47. msm_ext_disp_supported_cable);
  48. if (IS_ERR(ext_disp->audio_sdev[id]))
  49. return PTR_ERR(ext_disp->audio_sdev[id]);
  50. ret = devm_extcon_dev_register(&ext_disp->pdev->dev,
  51. ext_disp->audio_sdev[id]);
  52. if (ret) {
  53. pr_err("audio registration failed\n");
  54. return ret;
  55. }
  56. pr_debug("extcon registration done\n");
  57. return ret;
  58. }
  59. static void msm_ext_disp_extcon_unregister(struct msm_ext_disp *ext_disp,
  60. int id)
  61. {
  62. if (!ext_disp || !ext_disp->pdev || id >= MSM_EXT_DISP_MAX_CODECS) {
  63. pr_err("Invalid params\n");
  64. return;
  65. }
  66. devm_extcon_dev_unregister(&ext_disp->pdev->dev,
  67. ext_disp->audio_sdev[id]);
  68. }
  69. static const char *msm_ext_disp_name(enum msm_ext_disp_type type)
  70. {
  71. switch (type) {
  72. case EXT_DISPLAY_TYPE_HDMI:
  73. return "EXT_DISPLAY_TYPE_HDMI";
  74. case EXT_DISPLAY_TYPE_DP:
  75. return "EXT_DISPLAY_TYPE_DP";
  76. default: return "???";
  77. }
  78. }
  79. static int msm_ext_disp_add_intf_data(struct msm_ext_disp *ext_disp,
  80. struct msm_ext_disp_init_data *data)
  81. {
  82. struct msm_ext_disp_list *node;
  83. if (!ext_disp || !data) {
  84. pr_err("Invalid params\n");
  85. return -EINVAL;
  86. }
  87. node = kzalloc(sizeof(*node), GFP_KERNEL);
  88. if (!node)
  89. return -ENOMEM;
  90. node->data = data;
  91. list_add(&node->list, &ext_disp->display_list);
  92. pr_debug("Added new display (%s) ctld (%d) stream (%d)\n",
  93. msm_ext_disp_name(data->codec.type),
  94. data->codec.ctrl_id, data->codec.stream_id);
  95. return 0;
  96. }
  97. static int msm_ext_disp_remove_intf_data(struct msm_ext_disp *ext_disp,
  98. struct msm_ext_disp_init_data *data)
  99. {
  100. struct msm_ext_disp_list *node;
  101. struct list_head *pos = NULL;
  102. if (!ext_disp || !data) {
  103. pr_err("Invalid params\n");
  104. return -EINVAL;
  105. }
  106. list_for_each(pos, &ext_disp->display_list) {
  107. node = list_entry(pos, struct msm_ext_disp_list, list);
  108. if (node->data == data) {
  109. list_del(pos);
  110. pr_debug("Deleted the intf data\n");
  111. kfree(node);
  112. return 0;
  113. }
  114. }
  115. pr_debug("Intf data not present for delete op\n");
  116. return 0;
  117. }
  118. static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp,
  119. struct msm_ext_disp_codec_id *codec,
  120. struct msm_ext_disp_init_data **data)
  121. {
  122. int ret = 0;
  123. struct msm_ext_disp_list *node;
  124. struct list_head *position = NULL;
  125. if (!ext_disp || !data || !codec) {
  126. pr_err("Invalid params\n");
  127. ret = -EINVAL;
  128. goto end;
  129. }
  130. *data = NULL;
  131. list_for_each(position, &ext_disp->display_list) {
  132. node = list_entry(position, struct msm_ext_disp_list, list);
  133. if (node->data->codec.type == codec->type &&
  134. node->data->codec.stream_id == codec->stream_id &&
  135. node->data->codec.ctrl_id == codec->ctrl_id) {
  136. *data = node->data;
  137. break;
  138. }
  139. }
  140. if (!*data)
  141. ret = -ENODEV;
  142. end:
  143. return ret;
  144. }
  145. static int msm_ext_disp_process_audio(struct msm_ext_disp *ext_disp,
  146. struct msm_ext_disp_codec_id *codec,
  147. enum msm_ext_disp_cable_state new_state)
  148. {
  149. int ret = 0;
  150. int state;
  151. struct extcon_dev *audio_sdev;
  152. if (!ext_disp->ops) {
  153. pr_err("codec not registered, skip notification\n");
  154. ret = -EPERM;
  155. goto end;
  156. }
  157. audio_sdev = ext_disp->audio_sdev[codec->stream_id];
  158. state = extcon_get_state(audio_sdev, codec->type);
  159. if (state == !!new_state) {
  160. ret = -EEXIST;
  161. pr_debug("same state\n");
  162. goto end;
  163. }
  164. ret = extcon_set_state_sync(audio_sdev,
  165. codec->type, !!new_state);
  166. if (ret)
  167. pr_err("Failed to set state. Error = %d\n", ret);
  168. else
  169. pr_debug("state changed to %d\n", new_state);
  170. end:
  171. return ret;
  172. }
  173. static struct msm_ext_disp *msm_ext_disp_validate_and_get(
  174. struct platform_device *pdev,
  175. struct msm_ext_disp_codec_id *codec,
  176. enum msm_ext_disp_cable_state state)
  177. {
  178. struct msm_ext_disp_data *ext_disp_data;
  179. struct msm_ext_disp *ext_disp;
  180. if (!pdev) {
  181. pr_err("invalid platform device\n");
  182. goto err;
  183. }
  184. if (!codec ||
  185. codec->type >= EXT_DISPLAY_TYPE_MAX ||
  186. codec->ctrl_id != 0 ||
  187. codec->stream_id >= MSM_EXT_DISP_MAX_CODECS) {
  188. pr_err("invalid display codec id\n");
  189. goto err;
  190. }
  191. if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
  192. state >= EXT_DISPLAY_CABLE_STATE_MAX) {
  193. pr_err("invalid HPD state (%d)\n", state);
  194. goto err;
  195. }
  196. ext_disp_data = platform_get_drvdata(pdev);
  197. if (!ext_disp_data) {
  198. pr_err("invalid drvdata\n");
  199. goto err;
  200. }
  201. ext_disp = container_of(ext_disp_data,
  202. struct msm_ext_disp, ext_disp_data);
  203. return ext_disp;
  204. err:
  205. return ERR_PTR(-EINVAL);
  206. }
  207. static int msm_ext_disp_update_audio_ops(struct msm_ext_disp *ext_disp,
  208. struct msm_ext_disp_codec_id *codec)
  209. {
  210. int ret = 0;
  211. struct msm_ext_disp_init_data *data = NULL;
  212. ret = msm_ext_disp_get_intf_data(ext_disp, codec, &data);
  213. if (ret || !data) {
  214. pr_err("Display not found (%s) ctld (%d) stream (%d)\n",
  215. msm_ext_disp_name(codec->type),
  216. codec->ctrl_id, codec->stream_id);
  217. goto end;
  218. }
  219. if (ext_disp->ops) {
  220. *ext_disp->ops = data->codec_ops;
  221. ext_disp->current_codec = *codec;
  222. /* update pdev for interface to use */
  223. ext_disp->ext_disp_data.intf_pdev = data->pdev;
  224. ext_disp->ext_disp_data.intf_data = data->intf_data;
  225. }
  226. end:
  227. return ret;
  228. }
  229. static int msm_ext_disp_audio_config(struct platform_device *pdev,
  230. struct msm_ext_disp_codec_id *codec,
  231. enum msm_ext_disp_cable_state state)
  232. {
  233. int ret = 0;
  234. struct msm_ext_disp *ext_disp;
  235. ext_disp = msm_ext_disp_validate_and_get(pdev, codec, state);
  236. if (IS_ERR(ext_disp)) {
  237. ret = PTR_ERR(ext_disp);
  238. goto end;
  239. }
  240. if (state == EXT_DISPLAY_CABLE_CONNECT) {
  241. ret = msm_ext_disp_select_audio_codec(pdev, codec);
  242. } else {
  243. mutex_lock(&ext_disp->lock);
  244. if (ext_disp->ops)
  245. memset(ext_disp->ops, 0, sizeof(*ext_disp->ops));
  246. pr_debug("codec ops cleared for %s\n",
  247. msm_ext_disp_name(ext_disp->current_codec.type));
  248. ext_disp->current_codec.type = EXT_DISPLAY_TYPE_MAX;
  249. mutex_unlock(&ext_disp->lock);
  250. }
  251. end:
  252. return ret;
  253. }
  254. static int msm_ext_disp_audio_notify(struct platform_device *pdev,
  255. struct msm_ext_disp_codec_id *codec,
  256. enum msm_ext_disp_cable_state state)
  257. {
  258. int ret = 0;
  259. struct msm_ext_disp *ext_disp;
  260. ext_disp = msm_ext_disp_validate_and_get(pdev, codec, state);
  261. if (IS_ERR(ext_disp)) {
  262. ret = PTR_ERR(ext_disp);
  263. goto end;
  264. }
  265. mutex_lock(&ext_disp->lock);
  266. ret = msm_ext_disp_process_audio(ext_disp, codec, state);
  267. mutex_unlock(&ext_disp->lock);
  268. end:
  269. return ret;
  270. }
  271. static void msm_ext_disp_ready_for_display(struct msm_ext_disp *ext_disp)
  272. {
  273. int ret;
  274. struct msm_ext_disp_init_data *data = NULL;
  275. if (!ext_disp) {
  276. pr_err("invalid input\n");
  277. return;
  278. }
  279. ret = msm_ext_disp_get_intf_data(ext_disp,
  280. &ext_disp->current_codec, &data);
  281. if (ret) {
  282. pr_err("%s not found\n",
  283. msm_ext_disp_name(ext_disp->current_codec.type));
  284. return;
  285. }
  286. *ext_disp->ops = data->codec_ops;
  287. data->codec_ops.ready(ext_disp->pdev);
  288. }
  289. int msm_hdmi_register_audio_codec(struct platform_device *pdev,
  290. struct msm_ext_disp_audio_codec_ops *ops)
  291. {
  292. return msm_ext_disp_register_audio_codec(pdev, ops);
  293. }
  294. /**
  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. */
  301. int 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. if (!pdev || !ops) {
  308. pr_err("Invalid params\n");
  309. return -EINVAL;
  310. }
  311. ext_disp_data = platform_get_drvdata(pdev);
  312. if (!ext_disp_data) {
  313. pr_err("Invalid drvdata\n");
  314. return -EINVAL;
  315. }
  316. ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
  317. ext_disp_data);
  318. mutex_lock(&ext_disp->lock);
  319. if (ext_disp->ops) {
  320. pr_err("Codec already registered\n");
  321. ret = -EINVAL;
  322. goto end;
  323. }
  324. ext_disp->ops = ops;
  325. pr_debug("audio codec registered\n");
  326. if (ext_disp->update_audio) {
  327. ext_disp->update_audio = false;
  328. msm_ext_disp_update_audio_ops(ext_disp, &ext_disp->current_codec);
  329. msm_ext_disp_process_audio(ext_disp, &ext_disp->current_codec,
  330. EXT_DISPLAY_CABLE_CONNECT);
  331. }
  332. end:
  333. mutex_unlock(&ext_disp->lock);
  334. if (ext_disp->current_codec.type != EXT_DISPLAY_TYPE_MAX)
  335. msm_ext_disp_ready_for_display(ext_disp);
  336. return ret;
  337. }
  338. EXPORT_SYMBOL(msm_ext_disp_register_audio_codec);
  339. int msm_ext_disp_select_audio_codec(struct platform_device *pdev,
  340. struct msm_ext_disp_codec_id *codec)
  341. {
  342. int ret = 0;
  343. struct msm_ext_disp *ext_disp = NULL;
  344. struct msm_ext_disp_data *ext_disp_data = NULL;
  345. if (!pdev || !codec) {
  346. pr_err("Invalid params\n");
  347. return -EINVAL;
  348. }
  349. ext_disp_data = platform_get_drvdata(pdev);
  350. if (!ext_disp_data) {
  351. pr_err("Invalid drvdata\n");
  352. return -EINVAL;
  353. }
  354. ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
  355. ext_disp_data);
  356. mutex_lock(&ext_disp->lock);
  357. if (!ext_disp->ops) {
  358. pr_warn("Codec is not registered\n");
  359. ext_disp->update_audio = true;
  360. ext_disp->current_codec = *codec;
  361. ret = -EINVAL;
  362. goto end;
  363. }
  364. ret = msm_ext_disp_update_audio_ops(ext_disp, codec);
  365. end:
  366. mutex_unlock(&ext_disp->lock);
  367. return ret;
  368. }
  369. EXPORT_SYMBOL(msm_ext_disp_select_audio_codec);
  370. static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
  371. {
  372. struct msm_ext_disp_audio_codec_ops *ops;
  373. if (!init_data) {
  374. pr_err("Invalid init_data\n");
  375. return -EINVAL;
  376. }
  377. if (!init_data->pdev) {
  378. pr_err("Invalid display intf pdev\n");
  379. return -EINVAL;
  380. }
  381. if (init_data->codec.type >= EXT_DISPLAY_TYPE_MAX ||
  382. init_data->codec.ctrl_id != 0 ||
  383. init_data->codec.stream_id >= MSM_EXT_DISP_MAX_CODECS) {
  384. pr_err("Invalid codec info type(%d), ctrl(%d) stream(%d)\n",
  385. init_data->codec.type,
  386. init_data->codec.ctrl_id,
  387. init_data->codec.stream_id);
  388. return -EINVAL;
  389. }
  390. ops = &init_data->codec_ops;
  391. if (!ops->audio_info_setup || !ops->get_audio_edid_blk ||
  392. !ops->cable_status || !ops->get_intf_id ||
  393. !ops->teardown_done || !ops->acknowledge ||
  394. !ops->ready) {
  395. pr_err("Invalid codec operation pointers\n");
  396. return -EINVAL;
  397. }
  398. return 0;
  399. }
  400. int 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. if (!pdev || !init_data) {
  408. pr_err("Invalid params\n");
  409. return -EINVAL;
  410. }
  411. ext_disp_data = platform_get_drvdata(pdev);
  412. if (!ext_disp_data) {
  413. pr_err("Invalid drvdata\n");
  414. return -EINVAL;
  415. }
  416. ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
  417. ext_disp_data);
  418. mutex_lock(&ext_disp->lock);
  419. ret = msm_ext_disp_validate_intf(init_data);
  420. if (ret)
  421. goto end;
  422. ret = msm_ext_disp_get_intf_data(ext_disp, &init_data->codec, &data);
  423. if (!ret) {
  424. pr_err("%s already registered. ctrl(%d) stream(%d)\n",
  425. msm_ext_disp_name(init_data->codec.type),
  426. init_data->codec.ctrl_id,
  427. init_data->codec.stream_id);
  428. goto end;
  429. }
  430. ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
  431. if (ret)
  432. goto end;
  433. init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
  434. init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
  435. pr_debug("%s registered. ctrl(%d) stream(%d)\n",
  436. msm_ext_disp_name(init_data->codec.type),
  437. init_data->codec.ctrl_id,
  438. init_data->codec.stream_id);
  439. end:
  440. mutex_unlock(&ext_disp->lock);
  441. return ret;
  442. }
  443. EXPORT_SYMBOL(msm_ext_disp_register_intf);
  444. int msm_ext_disp_deregister_intf(struct platform_device *pdev,
  445. struct msm_ext_disp_init_data *init_data)
  446. {
  447. int ret = 0;
  448. struct msm_ext_disp *ext_disp = NULL;
  449. struct msm_ext_disp_data *ext_disp_data = NULL;
  450. if (!pdev || !init_data) {
  451. pr_err("Invalid params\n");
  452. return -EINVAL;
  453. }
  454. ext_disp_data = platform_get_drvdata(pdev);
  455. if (!ext_disp_data) {
  456. pr_err("Invalid drvdata\n");
  457. return -EINVAL;
  458. }
  459. ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
  460. ext_disp_data);
  461. mutex_lock(&ext_disp->lock);
  462. ret = msm_ext_disp_remove_intf_data(ext_disp, init_data);
  463. if (ret)
  464. goto end;
  465. init_data->intf_ops.audio_config = NULL;
  466. init_data->intf_ops.audio_notify = NULL;
  467. pr_debug("%s deregistered\n",
  468. msm_ext_disp_name(init_data->codec.type));
  469. end:
  470. mutex_unlock(&ext_disp->lock);
  471. return ret;
  472. }
  473. EXPORT_SYMBOL(msm_ext_disp_deregister_intf);
  474. static int msm_ext_disp_probe(struct platform_device *pdev)
  475. {
  476. int ret = 0, id;
  477. struct device_node *of_node = NULL;
  478. struct msm_ext_disp *ext_disp = NULL;
  479. if (!pdev) {
  480. pr_err("No platform device found\n");
  481. ret = -ENODEV;
  482. goto end;
  483. }
  484. of_node = pdev->dev.of_node;
  485. if (!of_node) {
  486. pr_err("No device node found\n");
  487. ret = -ENODEV;
  488. goto end;
  489. }
  490. ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
  491. if (!ext_disp) {
  492. ret = -ENOMEM;
  493. goto end;
  494. }
  495. platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
  496. ext_disp->pdev = pdev;
  497. for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++) {
  498. ret = msm_ext_disp_extcon_register(ext_disp, id);
  499. if (ret)
  500. goto child_node_failure;
  501. }
  502. ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
  503. if (ret) {
  504. pr_err("Failed to add child devices. Error = %d\n", ret);
  505. goto child_node_failure;
  506. } else {
  507. pr_debug("%s: Added child devices.\n", __func__);
  508. }
  509. mutex_init(&ext_disp->lock);
  510. INIT_LIST_HEAD(&ext_disp->display_list);
  511. ext_disp->current_codec.type = EXT_DISPLAY_TYPE_MAX;
  512. ext_disp->update_audio = false;
  513. return ret;
  514. child_node_failure:
  515. for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++)
  516. msm_ext_disp_extcon_unregister(ext_disp, id);
  517. devm_kfree(&ext_disp->pdev->dev, ext_disp);
  518. end:
  519. return ret;
  520. }
  521. static int msm_ext_disp_remove(struct platform_device *pdev)
  522. {
  523. int ret = 0, id;
  524. struct msm_ext_disp *ext_disp = NULL;
  525. struct msm_ext_disp_data *ext_disp_data = NULL;
  526. if (!pdev) {
  527. pr_err("No platform device\n");
  528. ret = -ENODEV;
  529. goto end;
  530. }
  531. ext_disp_data = platform_get_drvdata(pdev);
  532. if (!ext_disp_data) {
  533. pr_err("No drvdata found\n");
  534. ret = -ENODEV;
  535. goto end;
  536. }
  537. ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
  538. ext_disp_data);
  539. for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++)
  540. msm_ext_disp_extcon_unregister(ext_disp, id);
  541. mutex_destroy(&ext_disp->lock);
  542. devm_kfree(&ext_disp->pdev->dev, ext_disp);
  543. end:
  544. return ret;
  545. }
  546. static const struct of_device_id msm_ext_dt_match[] = {
  547. {.compatible = "qcom,msm-ext-disp",},
  548. { /* Sentinel */ },
  549. };
  550. MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
  551. static struct platform_driver this_driver = {
  552. .probe = msm_ext_disp_probe,
  553. .remove = msm_ext_disp_remove,
  554. .driver = {
  555. .name = "msm-ext-disp",
  556. .of_match_table = msm_ext_dt_match,
  557. },
  558. };
  559. static int __init msm_ext_disp_init(void)
  560. {
  561. int ret = 0;
  562. ret = platform_driver_register(&this_driver);
  563. if (ret)
  564. pr_err("failed, ret = %d\n", ret);
  565. return ret;
  566. }
  567. subsys_initcall(msm_ext_disp_init);
  568. MODULE_LICENSE("GPL v2");
  569. MODULE_DESCRIPTION("MSM External Display");