123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
- * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
- */
- #define pr_fmt(fmt) "%s: " fmt, __func__
- #include <linux/slab.h>
- #include <linux/bitops.h>
- #include <linux/delay.h>
- #include <linux/module.h>
- #include <linux/mutex.h>
- #include <linux/iopoll.h>
- #include <linux/types.h>
- #include <linux/of_platform.h>
- #include <linux/extcon-provider.h>
- #include <linux/soc/qcom/msm_ext_display.h>
- #include <linux/extcon-provider.h>
- struct msm_ext_disp_list {
- struct msm_ext_disp_init_data *data;
- struct list_head list;
- };
- struct msm_ext_disp {
- struct msm_ext_disp_data ext_disp_data;
- struct platform_device *pdev;
- struct msm_ext_disp_codec_id current_codec;
- struct msm_ext_disp_audio_codec_ops *ops;
- struct extcon_dev *audio_sdev[MSM_EXT_DISP_MAX_CODECS];
- bool audio_session_on;
- struct list_head display_list;
- struct mutex lock;
- bool update_audio;
- };
- static const unsigned int msm_ext_disp_supported_cable[] = {
- EXTCON_DISP_DP,
- EXTCON_DISP_HDMI,
- EXTCON_NONE,
- };
- static int msm_ext_disp_extcon_register(struct msm_ext_disp *ext_disp, int id)
- {
- int ret = 0;
- if (!ext_disp || !ext_disp->pdev || id >= MSM_EXT_DISP_MAX_CODECS) {
- pr_err("invalid params\n");
- return -EINVAL;
- }
- ext_disp->audio_sdev[id] = devm_extcon_dev_allocate(
- &ext_disp->pdev->dev,
- msm_ext_disp_supported_cable);
- if (IS_ERR(ext_disp->audio_sdev[id]))
- return PTR_ERR(ext_disp->audio_sdev[id]);
- ret = devm_extcon_dev_register(&ext_disp->pdev->dev,
- ext_disp->audio_sdev[id]);
- if (ret) {
- pr_err("audio registration failed\n");
- return ret;
- }
- pr_debug("extcon registration done\n");
- return ret;
- }
- static void msm_ext_disp_extcon_unregister(struct msm_ext_disp *ext_disp,
- int id)
- {
- if (!ext_disp || !ext_disp->pdev || id >= MSM_EXT_DISP_MAX_CODECS) {
- pr_err("Invalid params\n");
- return;
- }
- devm_extcon_dev_unregister(&ext_disp->pdev->dev,
- ext_disp->audio_sdev[id]);
- }
- static const char *msm_ext_disp_name(enum msm_ext_disp_type type)
- {
- switch (type) {
- case EXT_DISPLAY_TYPE_HDMI:
- return "EXT_DISPLAY_TYPE_HDMI";
- case EXT_DISPLAY_TYPE_DP:
- return "EXT_DISPLAY_TYPE_DP";
- default: return "???";
- }
- }
- static int msm_ext_disp_add_intf_data(struct msm_ext_disp *ext_disp,
- struct msm_ext_disp_init_data *data)
- {
- struct msm_ext_disp_list *node;
- if (!ext_disp || !data) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- node = kzalloc(sizeof(*node), GFP_KERNEL);
- if (!node)
- return -ENOMEM;
- node->data = data;
- list_add(&node->list, &ext_disp->display_list);
- pr_debug("Added new display (%s) ctld (%d) stream (%d)\n",
- msm_ext_disp_name(data->codec.type),
- data->codec.ctrl_id, data->codec.stream_id);
- return 0;
- }
- static int msm_ext_disp_remove_intf_data(struct msm_ext_disp *ext_disp,
- struct msm_ext_disp_init_data *data)
- {
- struct msm_ext_disp_list *node;
- struct list_head *pos = NULL;
- if (!ext_disp || !data) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- list_for_each(pos, &ext_disp->display_list) {
- node = list_entry(pos, struct msm_ext_disp_list, list);
- if (node->data == data) {
- list_del(pos);
- pr_debug("Deleted the intf data\n");
- kfree(node);
- return 0;
- }
- }
- pr_debug("Intf data not present for delete op\n");
- return 0;
- }
- static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp,
- struct msm_ext_disp_codec_id *codec,
- struct msm_ext_disp_init_data **data)
- {
- int ret = 0;
- struct msm_ext_disp_list *node;
- struct list_head *position = NULL;
- if (!ext_disp || !data || !codec) {
- pr_err("Invalid params\n");
- ret = -EINVAL;
- goto end;
- }
- *data = NULL;
- list_for_each(position, &ext_disp->display_list) {
- node = list_entry(position, struct msm_ext_disp_list, list);
- if (node->data->codec.type == codec->type &&
- node->data->codec.stream_id == codec->stream_id &&
- node->data->codec.ctrl_id == codec->ctrl_id) {
- *data = node->data;
- break;
- }
- }
- if (!*data)
- ret = -ENODEV;
- end:
- return ret;
- }
- static int msm_ext_disp_process_audio(struct msm_ext_disp *ext_disp,
- struct msm_ext_disp_codec_id *codec,
- enum msm_ext_disp_cable_state new_state)
- {
- int ret = 0;
- int state;
- struct extcon_dev *audio_sdev;
- if (!ext_disp->ops) {
- pr_err("codec not registered, skip notification\n");
- ret = -EPERM;
- goto end;
- }
- audio_sdev = ext_disp->audio_sdev[codec->stream_id];
- state = extcon_get_state(audio_sdev, codec->type);
- if (state == !!new_state) {
- ret = -EEXIST;
- pr_debug("same state\n");
- goto end;
- }
- ret = extcon_set_state_sync(audio_sdev,
- codec->type, !!new_state);
- if (ret)
- pr_err("Failed to set state. Error = %d\n", ret);
- else
- pr_debug("state changed to %d\n", new_state);
- end:
- return ret;
- }
- static struct msm_ext_disp *msm_ext_disp_validate_and_get(
- struct platform_device *pdev,
- struct msm_ext_disp_codec_id *codec,
- enum msm_ext_disp_cable_state state)
- {
- struct msm_ext_disp_data *ext_disp_data;
- struct msm_ext_disp *ext_disp;
- if (!pdev) {
- pr_err("invalid platform device\n");
- goto err;
- }
- if (!codec ||
- codec->type >= EXT_DISPLAY_TYPE_MAX ||
- codec->ctrl_id != 0 ||
- codec->stream_id >= MSM_EXT_DISP_MAX_CODECS) {
- pr_err("invalid display codec id\n");
- goto err;
- }
- if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
- state >= EXT_DISPLAY_CABLE_STATE_MAX) {
- pr_err("invalid HPD state (%d)\n", state);
- goto err;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("invalid drvdata\n");
- goto err;
- }
- ext_disp = container_of(ext_disp_data,
- struct msm_ext_disp, ext_disp_data);
- return ext_disp;
- err:
- return ERR_PTR(-EINVAL);
- }
- static int msm_ext_disp_update_audio_ops(struct msm_ext_disp *ext_disp,
- struct msm_ext_disp_codec_id *codec)
- {
- int ret = 0;
- struct msm_ext_disp_init_data *data = NULL;
- ret = msm_ext_disp_get_intf_data(ext_disp, codec, &data);
- if (ret || !data) {
- pr_err("Display not found (%s) ctld (%d) stream (%d)\n",
- msm_ext_disp_name(codec->type),
- codec->ctrl_id, codec->stream_id);
- goto end;
- }
- if (ext_disp->ops) {
- *ext_disp->ops = data->codec_ops;
- ext_disp->current_codec = *codec;
- /* update pdev for interface to use */
- ext_disp->ext_disp_data.intf_pdev = data->pdev;
- ext_disp->ext_disp_data.intf_data = data->intf_data;
- }
- end:
- return ret;
- }
- static int msm_ext_disp_audio_config(struct platform_device *pdev,
- struct msm_ext_disp_codec_id *codec,
- enum msm_ext_disp_cable_state state)
- {
- int ret = 0;
- struct msm_ext_disp *ext_disp;
- ext_disp = msm_ext_disp_validate_and_get(pdev, codec, state);
- if (IS_ERR(ext_disp)) {
- ret = PTR_ERR(ext_disp);
- goto end;
- }
- if (state == EXT_DISPLAY_CABLE_CONNECT) {
- ret = msm_ext_disp_select_audio_codec(pdev, codec);
- } else {
- mutex_lock(&ext_disp->lock);
- if (ext_disp->ops)
- memset(ext_disp->ops, 0, sizeof(*ext_disp->ops));
- pr_debug("codec ops cleared for %s\n",
- msm_ext_disp_name(ext_disp->current_codec.type));
- ext_disp->current_codec.type = EXT_DISPLAY_TYPE_MAX;
- mutex_unlock(&ext_disp->lock);
- }
- end:
- return ret;
- }
- static int msm_ext_disp_audio_notify(struct platform_device *pdev,
- struct msm_ext_disp_codec_id *codec,
- enum msm_ext_disp_cable_state state)
- {
- int ret = 0;
- struct msm_ext_disp *ext_disp;
- ext_disp = msm_ext_disp_validate_and_get(pdev, codec, state);
- if (IS_ERR(ext_disp)) {
- ret = PTR_ERR(ext_disp);
- goto end;
- }
- mutex_lock(&ext_disp->lock);
- ret = msm_ext_disp_process_audio(ext_disp, codec, state);
- mutex_unlock(&ext_disp->lock);
- end:
- return ret;
- }
- static void msm_ext_disp_ready_for_display(struct msm_ext_disp *ext_disp)
- {
- int ret;
- struct msm_ext_disp_init_data *data = NULL;
- if (!ext_disp) {
- pr_err("invalid input\n");
- return;
- }
- ret = msm_ext_disp_get_intf_data(ext_disp,
- &ext_disp->current_codec, &data);
- if (ret) {
- pr_err("%s not found\n",
- msm_ext_disp_name(ext_disp->current_codec.type));
- return;
- }
- *ext_disp->ops = data->codec_ops;
- data->codec_ops.ready(ext_disp->pdev);
- }
- int msm_hdmi_register_audio_codec(struct platform_device *pdev,
- struct msm_ext_disp_audio_codec_ops *ops)
- {
- return msm_ext_disp_register_audio_codec(pdev, ops);
- }
- /**
- * Register audio codec ops to display driver
- * for HDMI/Display Port usecase support.
- *
- * @return 0 on success, negative value on error
- *
- */
- int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
- struct msm_ext_disp_audio_codec_ops *ops)
- {
- int ret = 0;
- struct msm_ext_disp *ext_disp = NULL;
- struct msm_ext_disp_data *ext_disp_data = NULL;
- if (!pdev || !ops) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("Invalid drvdata\n");
- return -EINVAL;
- }
- ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
- ext_disp_data);
- mutex_lock(&ext_disp->lock);
- if (ext_disp->ops) {
- pr_err("Codec already registered\n");
- ret = -EINVAL;
- goto end;
- }
- ext_disp->ops = ops;
- pr_debug("audio codec registered\n");
- if (ext_disp->update_audio) {
- ext_disp->update_audio = false;
- msm_ext_disp_update_audio_ops(ext_disp, &ext_disp->current_codec);
- msm_ext_disp_process_audio(ext_disp, &ext_disp->current_codec,
- EXT_DISPLAY_CABLE_CONNECT);
- }
- end:
- mutex_unlock(&ext_disp->lock);
- if (ext_disp->current_codec.type != EXT_DISPLAY_TYPE_MAX)
- msm_ext_disp_ready_for_display(ext_disp);
- return ret;
- }
- EXPORT_SYMBOL(msm_ext_disp_register_audio_codec);
- int msm_ext_disp_select_audio_codec(struct platform_device *pdev,
- struct msm_ext_disp_codec_id *codec)
- {
- int ret = 0;
- struct msm_ext_disp *ext_disp = NULL;
- struct msm_ext_disp_data *ext_disp_data = NULL;
- if (!pdev || !codec) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("Invalid drvdata\n");
- return -EINVAL;
- }
- ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
- ext_disp_data);
- mutex_lock(&ext_disp->lock);
- if (!ext_disp->ops) {
- pr_warn("Codec is not registered\n");
- ext_disp->update_audio = true;
- ext_disp->current_codec = *codec;
- ret = -EINVAL;
- goto end;
- }
- ret = msm_ext_disp_update_audio_ops(ext_disp, codec);
- end:
- mutex_unlock(&ext_disp->lock);
- return ret;
- }
- EXPORT_SYMBOL(msm_ext_disp_select_audio_codec);
- static int msm_ext_disp_validate_intf(struct msm_ext_disp_init_data *init_data)
- {
- struct msm_ext_disp_audio_codec_ops *ops;
- if (!init_data) {
- pr_err("Invalid init_data\n");
- return -EINVAL;
- }
- if (!init_data->pdev) {
- pr_err("Invalid display intf pdev\n");
- return -EINVAL;
- }
- if (init_data->codec.type >= EXT_DISPLAY_TYPE_MAX ||
- init_data->codec.ctrl_id != 0 ||
- init_data->codec.stream_id >= MSM_EXT_DISP_MAX_CODECS) {
- pr_err("Invalid codec info type(%d), ctrl(%d) stream(%d)\n",
- init_data->codec.type,
- init_data->codec.ctrl_id,
- init_data->codec.stream_id);
- return -EINVAL;
- }
- ops = &init_data->codec_ops;
- if (!ops->audio_info_setup || !ops->get_audio_edid_blk ||
- !ops->cable_status || !ops->get_intf_id ||
- !ops->teardown_done || !ops->acknowledge ||
- !ops->ready) {
- pr_err("Invalid codec operation pointers\n");
- return -EINVAL;
- }
- return 0;
- }
- int msm_ext_disp_register_intf(struct platform_device *pdev,
- struct msm_ext_disp_init_data *init_data)
- {
- int ret = 0;
- struct msm_ext_disp_init_data *data = NULL;
- struct msm_ext_disp *ext_disp = NULL;
- struct msm_ext_disp_data *ext_disp_data = NULL;
- if (!pdev || !init_data) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("Invalid drvdata\n");
- return -EINVAL;
- }
- ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
- ext_disp_data);
- mutex_lock(&ext_disp->lock);
- ret = msm_ext_disp_validate_intf(init_data);
- if (ret)
- goto end;
- ret = msm_ext_disp_get_intf_data(ext_disp, &init_data->codec, &data);
- if (!ret) {
- pr_err("%s already registered. ctrl(%d) stream(%d)\n",
- msm_ext_disp_name(init_data->codec.type),
- init_data->codec.ctrl_id,
- init_data->codec.stream_id);
- goto end;
- }
- ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
- if (ret)
- goto end;
- init_data->intf_ops.audio_config = msm_ext_disp_audio_config;
- init_data->intf_ops.audio_notify = msm_ext_disp_audio_notify;
- pr_debug("%s registered. ctrl(%d) stream(%d)\n",
- msm_ext_disp_name(init_data->codec.type),
- init_data->codec.ctrl_id,
- init_data->codec.stream_id);
- end:
- mutex_unlock(&ext_disp->lock);
- return ret;
- }
- EXPORT_SYMBOL(msm_ext_disp_register_intf);
- int msm_ext_disp_deregister_intf(struct platform_device *pdev,
- struct msm_ext_disp_init_data *init_data)
- {
- int ret = 0;
- struct msm_ext_disp *ext_disp = NULL;
- struct msm_ext_disp_data *ext_disp_data = NULL;
- if (!pdev || !init_data) {
- pr_err("Invalid params\n");
- return -EINVAL;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("Invalid drvdata\n");
- return -EINVAL;
- }
- ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
- ext_disp_data);
- mutex_lock(&ext_disp->lock);
- ret = msm_ext_disp_remove_intf_data(ext_disp, init_data);
- if (ret)
- goto end;
- init_data->intf_ops.audio_config = NULL;
- init_data->intf_ops.audio_notify = NULL;
- pr_debug("%s deregistered\n",
- msm_ext_disp_name(init_data->codec.type));
- end:
- mutex_unlock(&ext_disp->lock);
- return ret;
- }
- EXPORT_SYMBOL(msm_ext_disp_deregister_intf);
- static int msm_ext_disp_probe(struct platform_device *pdev)
- {
- int ret = 0, id;
- struct device_node *of_node = NULL;
- struct msm_ext_disp *ext_disp = NULL;
- if (!pdev) {
- pr_err("No platform device found\n");
- ret = -ENODEV;
- goto end;
- }
- of_node = pdev->dev.of_node;
- if (!of_node) {
- pr_err("No device node found\n");
- ret = -ENODEV;
- goto end;
- }
- ext_disp = devm_kzalloc(&pdev->dev, sizeof(*ext_disp), GFP_KERNEL);
- if (!ext_disp) {
- ret = -ENOMEM;
- goto end;
- }
- platform_set_drvdata(pdev, &ext_disp->ext_disp_data);
- ext_disp->pdev = pdev;
- for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++) {
- ret = msm_ext_disp_extcon_register(ext_disp, id);
- if (ret)
- goto child_node_failure;
- }
- ret = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
- if (ret) {
- pr_err("Failed to add child devices. Error = %d\n", ret);
- goto child_node_failure;
- } else {
- pr_debug("%s: Added child devices.\n", __func__);
- }
- mutex_init(&ext_disp->lock);
- INIT_LIST_HEAD(&ext_disp->display_list);
- ext_disp->current_codec.type = EXT_DISPLAY_TYPE_MAX;
- ext_disp->update_audio = false;
- return ret;
- child_node_failure:
- for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++)
- msm_ext_disp_extcon_unregister(ext_disp, id);
- devm_kfree(&ext_disp->pdev->dev, ext_disp);
- end:
- return ret;
- }
- static int msm_ext_disp_remove(struct platform_device *pdev)
- {
- int ret = 0, id;
- struct msm_ext_disp *ext_disp = NULL;
- struct msm_ext_disp_data *ext_disp_data = NULL;
- if (!pdev) {
- pr_err("No platform device\n");
- ret = -ENODEV;
- goto end;
- }
- ext_disp_data = platform_get_drvdata(pdev);
- if (!ext_disp_data) {
- pr_err("No drvdata found\n");
- ret = -ENODEV;
- goto end;
- }
- ext_disp = container_of(ext_disp_data, struct msm_ext_disp,
- ext_disp_data);
- for (id = 0; id < MSM_EXT_DISP_MAX_CODECS; id++)
- msm_ext_disp_extcon_unregister(ext_disp, id);
- mutex_destroy(&ext_disp->lock);
- devm_kfree(&ext_disp->pdev->dev, ext_disp);
- end:
- return ret;
- }
- static const struct of_device_id msm_ext_dt_match[] = {
- {.compatible = "qcom,msm-ext-disp",},
- { /* Sentinel */ },
- };
- MODULE_DEVICE_TABLE(of, msm_ext_dt_match);
- static struct platform_driver this_driver = {
- .probe = msm_ext_disp_probe,
- .remove = msm_ext_disp_remove,
- .driver = {
- .name = "msm-ext-disp",
- .of_match_table = msm_ext_dt_match,
- },
- };
- static int __init msm_ext_disp_init(void)
- {
- int ret = 0;
- ret = platform_driver_register(&this_driver);
- if (ret)
- pr_err("failed, ret = %d\n", ret);
- return ret;
- }
- subsys_initcall(msm_ext_disp_init);
- MODULE_LICENSE("GPL v2");
- MODULE_DESCRIPTION("MSM External Display");
|