Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bed7a261 authored by Tatenda Chipeperekwa's avatar Tatenda Chipeperekwa
Browse files

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: default avatarTatenda Chipeperekwa <tatendac@codeaurora.org>
parent 113f80cd
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
// 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 −0
Original line number Diff line number Diff line
/* 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_*/