mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-07 12:25:00 -04:00
platform: msm: add support for external display module
Add support for the external display module, which is a utility
to manage display interface and audio codec interactions
associated with the cable connect/disconnect events of the
display interfaces. The helper class is responsible for
routing operations called by the audio codec to a particular
display, and for creating and updating the audio switch nodes.
This implementation is based on a snapshot of the external
display module as of this commit 104eef6e1fe8d4d2 ("platform:
msm: Remove unused local count variable") on kernel-4.19.
Change-Id: I919adf3fda110ecba8d7054a1385e2cbdaca2467
Signed-off-by: Tatenda Chipeperekwa <tatendac@codeaurora.org>
This commit is contained in:
parent
113f80cdcf
commit
bed7a261e1
4 changed files with 906 additions and 0 deletions
|
|
@ -3,6 +3,16 @@
|
|||
menu "Qualcomm technologies inc. MSM specific device drivers"
|
||||
depends on ARCH_QCOM
|
||||
|
||||
config MSM_EXT_DISPLAY
|
||||
bool "MSM External Display Driver"
|
||||
depends on DRM && QGKI
|
||||
help
|
||||
Enabling this option adds MSM External Display Driver.
|
||||
External Display driver was added to support the communication
|
||||
between external display driver and its counterparts.
|
||||
This should be enabled to support audio & video over HDMI or
|
||||
DP for hot pluggable sink devices.
|
||||
|
||||
config SPS
|
||||
tristate "SPS support"
|
||||
select GENERIC_ALLOCATOR
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
#
|
||||
# Makefile for the MSM specific device drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_MSM_EXT_DISPLAY) += msm_ext_display.o
|
||||
obj-$(CONFIG_GSI) += gsi/
|
||||
obj-$(CONFIG_IPA3) += ipa/
|
||||
obj-$(CONFIG_MSM_GENI_SE) += msm-geni-se.o
|
||||
|
|
|
|||
689
drivers/platform/msm/msm_ext_display.c
Normal file
689
drivers/platform/msm/msm_ext_display.c
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2016-2020, 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/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;
|
||||
};
|
||||
|
||||
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");
|
||||
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);
|
||||
if (ret)
|
||||
pr_err("error setting audio codec\n");
|
||||
} 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");
|
||||
|
||||
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_err("Codec is not registered\n");
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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");
|
||||
205
include/linux/msm_ext_display.h
Normal file
205
include/linux/msm_ext_display.h
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MSM_EXT_DISPLAY_H_
|
||||
#define _MSM_EXT_DISPLAY_H_
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/extcon.h>
|
||||
|
||||
#define AUDIO_ACK_SET_ENABLE BIT(5)
|
||||
#define AUDIO_ACK_ENABLE BIT(4)
|
||||
#define AUDIO_ACK_CONNECT BIT(0)
|
||||
|
||||
#define MSM_EXT_DISP_MAX_CODECS 2
|
||||
|
||||
/*
|
||||
* Flags to be used with the HPD operation of the external display
|
||||
* interface:
|
||||
* MSM_EXT_DISP_HPD_AUDIO: audio will be routed to external display
|
||||
* MSM_EXT_DISP_HPD_VIDEO: video will be routed to external display
|
||||
*/
|
||||
#define MSM_EXT_DISP_HPD_AUDIO BIT(0)
|
||||
#define MSM_EXT_DISP_HPD_VIDEO BIT(1)
|
||||
|
||||
/**
|
||||
* struct ext_disp_cable_notify - cable notify handler structure
|
||||
* @link: a link for the linked list
|
||||
* @status: current status of HDMI/DP cable connection
|
||||
* @hpd_notify: callback function to provide cable status
|
||||
*/
|
||||
struct ext_disp_cable_notify {
|
||||
struct list_head link;
|
||||
int status;
|
||||
void (*hpd_notify)(struct ext_disp_cable_notify *h);
|
||||
};
|
||||
|
||||
struct msm_ext_disp_audio_edid_blk {
|
||||
u8 *audio_data_blk;
|
||||
unsigned int audio_data_blk_size; /* in bytes */
|
||||
u8 *spk_alloc_data_blk;
|
||||
unsigned int spk_alloc_data_blk_size; /* in bytes */
|
||||
};
|
||||
|
||||
struct msm_ext_disp_audio_setup_params {
|
||||
u32 sample_rate_hz;
|
||||
u32 num_of_channels;
|
||||
u32 channel_allocation;
|
||||
u32 level_shift;
|
||||
bool down_mix;
|
||||
u32 sample_present;
|
||||
};
|
||||
|
||||
/*
|
||||
* External Display identifier for use to determine which interface
|
||||
* the audio driver is interacting with.
|
||||
*/
|
||||
enum msm_ext_disp_type {
|
||||
EXT_DISPLAY_TYPE_HDMI = EXTCON_DISP_HDMI,
|
||||
EXT_DISPLAY_TYPE_DP = EXTCON_DISP_DP,
|
||||
EXT_DISPLAY_TYPE_MAX = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
/*
|
||||
* External Display cable state used by display interface to indicate
|
||||
* connect/disconnect of interface.
|
||||
*/
|
||||
enum msm_ext_disp_cable_state {
|
||||
EXT_DISPLAY_CABLE_DISCONNECT,
|
||||
EXT_DISPLAY_CABLE_CONNECT,
|
||||
EXT_DISPLAY_CABLE_STATE_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* External Display power state used by display interface to indicate
|
||||
* power on/off of the interface.
|
||||
*/
|
||||
enum msm_ext_disp_power_state {
|
||||
EXT_DISPLAY_POWER_OFF,
|
||||
EXT_DISPLAY_POWER_ON,
|
||||
EXT_DISPLAY_POWER_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_ext_disp_codec_id - codec information
|
||||
* @type: external display type
|
||||
* @ctrl_id: controller id
|
||||
* @stream_id: stream_id
|
||||
*/
|
||||
struct msm_ext_disp_codec_id {
|
||||
enum msm_ext_disp_type type;
|
||||
int ctrl_id;
|
||||
int stream_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_ext_disp_intf_ops - operations exposed to display interface
|
||||
* @audio_config: configures the audio operations exposed to codec driver
|
||||
* @audio_notify: notifies the audio connection state to user modules.
|
||||
* @video_notify: notifies the video connection state to user modules.
|
||||
*/
|
||||
struct msm_ext_disp_intf_ops {
|
||||
int (*audio_config)(struct platform_device *pdev,
|
||||
struct msm_ext_disp_codec_id *codec,
|
||||
enum msm_ext_disp_cable_state state);
|
||||
|
||||
int (*audio_notify)(struct platform_device *pdev,
|
||||
struct msm_ext_disp_codec_id *codec,
|
||||
enum msm_ext_disp_cable_state state);
|
||||
|
||||
int (*video_notify)(struct platform_device *pdev,
|
||||
struct msm_ext_disp_codec_id *codec,
|
||||
enum msm_ext_disp_cable_state state);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_ext_disp_audio_codec_ops - operations exposed to audio codec
|
||||
* @audio_info_setup: configure audio on interface
|
||||
* @get_audio_edid_blk: retrieve audio edid block
|
||||
* @cable_status: cable connected/disconnected
|
||||
* @get_intf_id: id of connected interface
|
||||
* @teardown_done: audio session teardown done by qdsp
|
||||
* @acknowledge: acknowledge audio status received by user modules
|
||||
* @ready: notify audio when codec driver is ready.
|
||||
*/
|
||||
struct msm_ext_disp_audio_codec_ops {
|
||||
int (*audio_info_setup)(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_setup_params *params);
|
||||
int (*get_audio_edid_blk)(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_edid_blk *blk);
|
||||
int (*cable_status)(struct platform_device *pdev, u32 vote);
|
||||
int (*get_intf_id)(struct platform_device *pdev);
|
||||
void (*teardown_done)(struct platform_device *pdev);
|
||||
int (*acknowledge)(struct platform_device *pdev, u32 ack);
|
||||
int (*ready)(struct platform_device *pdev);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_ext_disp_init_data - data needed to register a display interface
|
||||
* @type: external display type
|
||||
* @intf_ops: external display interface operations
|
||||
* @codec_ops: audio codec operations
|
||||
* @pdev: platform device instance of the interface driver
|
||||
* @intf_data: interface specific data
|
||||
*/
|
||||
struct msm_ext_disp_init_data {
|
||||
struct msm_ext_disp_codec_id codec;
|
||||
struct msm_ext_disp_intf_ops intf_ops;
|
||||
struct msm_ext_disp_audio_codec_ops codec_ops;
|
||||
struct platform_device *pdev;
|
||||
void *intf_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msm_ext_disp_data - data needed by interface modules
|
||||
* @intf_pdev: platform device instance of the interface
|
||||
* @intf_data: data related to interface module
|
||||
*/
|
||||
struct msm_ext_disp_data {
|
||||
struct platform_device *intf_pdev;
|
||||
void *intf_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* msm_ext_disp_register_audio_codec() - audio codec registration
|
||||
* @pdev: platform device pointer
|
||||
* @codec_ops: audio codec operations
|
||||
*/
|
||||
int msm_ext_disp_register_audio_codec(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_codec_ops *ops);
|
||||
|
||||
/**
|
||||
* msm_ext_disp_select_audio_codec() - select audio codec
|
||||
* @pdev: platform device pointer
|
||||
* @codec: codec id information
|
||||
*/
|
||||
int msm_ext_disp_select_audio_codec(struct platform_device *pdev,
|
||||
struct msm_ext_disp_codec_id *codec);
|
||||
|
||||
/**
|
||||
* msm_hdmi_register_audio_codec() - wrapper for hdmi audio codec
|
||||
* registration
|
||||
* @pdev: platform device pointer
|
||||
* @codec_ops: audio codec operations
|
||||
*/
|
||||
int msm_hdmi_register_audio_codec(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_codec_ops *ops);
|
||||
|
||||
/**
|
||||
* msm_ext_disp_register_intf() - display interface registration
|
||||
* @init_data: data needed to register the display interface
|
||||
*/
|
||||
int msm_ext_disp_register_intf(struct platform_device *pdev,
|
||||
struct msm_ext_disp_init_data *init_data);
|
||||
|
||||
/**
|
||||
* msm_ext_disp_deregister_intf() - display interface deregistration
|
||||
* @init_data: data needed to deregister the display interface
|
||||
*/
|
||||
int msm_ext_disp_deregister_intf(struct platform_device *pdev,
|
||||
struct msm_ext_disp_init_data *init_data);
|
||||
|
||||
#endif /*_MSM_EXT_DISPLAY_H_*/
|
||||
Loading…
Reference in a new issue