msm: mdss: add external display class helper
Implement the external display class helper as 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, updating the hdmi switch node, and updating the audio switch node. CRs-Fixed: 1009284 Change-Id: Ie8d1006d3f11091a861733485cb67939ad47fdfe Signed-off-by: Tatenda Chipeperekwa <tatendac@codeaurora.org>
This commit is contained in:
parent
1bdf3dcbf1
commit
a3ce403844
8 changed files with 935 additions and 245 deletions
|
@ -61,7 +61,17 @@ these devices will be disabled as well. Ex. HDMI Audio Codec device.
|
|||
Required properties:
|
||||
- compatible : "msm-hdmi-audio-codec-rx";
|
||||
|
||||
msm_ext_disp is a device which manages the interaction between external
|
||||
displays (HDMI and Display Port) and the audio and display frameworks.
|
||||
|
||||
Required properties:
|
||||
- compatible: Must be "qcom,msm-ext-disp"
|
||||
|
||||
Example:
|
||||
msm_ext_disp: qcom,msm_ext_disp {
|
||||
compatible = "qcom,msm-ext-disp";
|
||||
};
|
||||
|
||||
mdss_hdmi_tx: qcom,hdmi_tx@fd922100 {
|
||||
cell-index = <0>;
|
||||
compatible = "qcom,hdmi-tx";
|
||||
|
@ -83,6 +93,8 @@ Example:
|
|||
qcom,enable-load = <0 0 0 1800000 0>;
|
||||
qcom,disable-load = <0 0 0 0 0>;
|
||||
|
||||
qcom,msm_ext_disp = <&msm_ext_disp>;
|
||||
|
||||
qcom,hdmi-tx-ddc-mux-sel = <&pma8084_gpios 6 0>;
|
||||
qcom,hdmi-tx-cec = <&msmgpio 31 0>;
|
||||
qcom,hdmi-tx-ddc-clk = <&msmgpio 32 0>;
|
||||
|
|
|
@ -47,6 +47,7 @@ obj-$(CONFIG_FB_MSM_MDSS_DP_PANEL) += mdss_dp.o mdss_dp_util.o
|
|||
obj-$(CONFIG_FB_MSM_MDSS_DP_PANEL) += mdss_dp_aux.o
|
||||
|
||||
obj-$(CONFIG_FB_MSM_MDSS) += mdss_io_util.o
|
||||
obj-$(CONFIG_FB_MSM_MDSS) += msm_ext_display.o
|
||||
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_tx.o
|
||||
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_panel.o
|
||||
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_hdcp.o
|
||||
|
|
|
@ -65,7 +65,6 @@ enum hdmi_audio_sample_rates {
|
|||
struct hdmi_audio {
|
||||
struct dss_io_data *io;
|
||||
struct msm_ext_disp_audio_setup_params params;
|
||||
struct switch_dev sdev;
|
||||
u32 pclk;
|
||||
bool ack_enabled;
|
||||
bool audio_ack_enabled;
|
||||
|
@ -370,76 +369,6 @@ static void hdmi_audio_off(void *ctx)
|
|||
pr_debug("HDMI Audio: Disabled\n");
|
||||
}
|
||||
|
||||
static void hdmi_audio_notify(void *ctx, int val)
|
||||
{
|
||||
struct hdmi_audio *audio = ctx;
|
||||
int state = 0;
|
||||
bool switched;
|
||||
|
||||
if (!audio) {
|
||||
pr_err("invalid input\n");
|
||||
return;
|
||||
}
|
||||
|
||||
state = audio->sdev.state;
|
||||
if (state == val)
|
||||
return;
|
||||
|
||||
if (audio->ack_enabled &&
|
||||
atomic_read(&audio->ack_pending)) {
|
||||
pr_err("%s ack pending, not notifying %s\n",
|
||||
state ? "connect" : "disconnect",
|
||||
val ? "connect" : "disconnect");
|
||||
return;
|
||||
}
|
||||
|
||||
switch_set_state(&audio->sdev, val);
|
||||
switched = audio->sdev.state != state;
|
||||
|
||||
if (audio->ack_enabled && switched)
|
||||
atomic_set(&audio->ack_pending, 1);
|
||||
|
||||
pr_debug("audio %s %s\n", switched ? "switched to" : "same as",
|
||||
audio->sdev.state ? "HDMI" : "SPKR");
|
||||
}
|
||||
|
||||
static void hdmi_audio_ack(void *ctx, u32 ack, u32 hpd)
|
||||
{
|
||||
struct hdmi_audio *audio = ctx;
|
||||
u32 ack_hpd;
|
||||
|
||||
if (!audio) {
|
||||
pr_err("invalid input\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ack & AUDIO_ACK_SET_ENABLE) {
|
||||
audio->ack_enabled = ack & AUDIO_ACK_ENABLE ?
|
||||
true : false;
|
||||
|
||||
pr_debug("audio ack feature %s\n",
|
||||
audio->ack_enabled ? "enabled" : "disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audio->ack_enabled)
|
||||
return;
|
||||
|
||||
atomic_set(&audio->ack_pending, 0);
|
||||
|
||||
ack_hpd = ack & AUDIO_ACK_CONNECT;
|
||||
|
||||
pr_debug("acknowledging %s\n",
|
||||
ack_hpd ? "connect" : "disconnect");
|
||||
|
||||
if (ack_hpd != hpd) {
|
||||
pr_debug("unbalanced audio state, ack %d, hpd %d\n",
|
||||
ack_hpd, hpd);
|
||||
|
||||
hdmi_audio_notify(ctx, hpd);
|
||||
}
|
||||
}
|
||||
|
||||
static void hdmi_audio_reset(void *ctx)
|
||||
{
|
||||
struct hdmi_audio *audio = ctx;
|
||||
|
@ -452,20 +381,6 @@ static void hdmi_audio_reset(void *ctx)
|
|||
atomic_set(&audio->ack_pending, 0);
|
||||
}
|
||||
|
||||
static void hdmi_audio_status(void *ctx, struct hdmi_audio_status *status)
|
||||
{
|
||||
struct hdmi_audio *audio = ctx;
|
||||
|
||||
if (!audio || !status) {
|
||||
pr_err("invalid input\n");
|
||||
return;
|
||||
}
|
||||
|
||||
status->ack_enabled = audio->ack_enabled;
|
||||
status->ack_pending = atomic_read(&audio->ack_pending);
|
||||
status->switched = audio->sdev.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* hdmi_audio_register() - audio registeration function
|
||||
* @data: registeration initialization data
|
||||
|
@ -480,7 +395,6 @@ static void hdmi_audio_status(void *ctx, struct hdmi_audio_status *status)
|
|||
void *hdmi_audio_register(struct hdmi_audio_init_data *data)
|
||||
{
|
||||
struct hdmi_audio *audio = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if (!data)
|
||||
goto end;
|
||||
|
@ -489,22 +403,11 @@ void *hdmi_audio_register(struct hdmi_audio_init_data *data)
|
|||
if (!audio)
|
||||
goto end;
|
||||
|
||||
audio->sdev.name = "hdmi_audio";
|
||||
rc = switch_dev_register(&audio->sdev);
|
||||
if (rc) {
|
||||
pr_err("audio switch registration failed\n");
|
||||
kzfree(audio);
|
||||
goto end;
|
||||
}
|
||||
|
||||
audio->io = data->io;
|
||||
|
||||
data->ops->on = hdmi_audio_on;
|
||||
data->ops->off = hdmi_audio_off;
|
||||
data->ops->notify = hdmi_audio_notify;
|
||||
data->ops->ack = hdmi_audio_ack;
|
||||
data->ops->reset = hdmi_audio_reset;
|
||||
data->ops->status = hdmi_audio_status;
|
||||
end:
|
||||
return audio;
|
||||
}
|
||||
|
@ -519,8 +422,6 @@ void hdmi_audio_unregister(void *ctx)
|
|||
{
|
||||
struct hdmi_audio *audio = ctx;
|
||||
|
||||
if (audio) {
|
||||
switch_dev_unregister(&audio->sdev);
|
||||
if (audio)
|
||||
kfree(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,24 +16,6 @@
|
|||
#include <linux/mdss_io_util.h>
|
||||
#include <linux/msm_ext_display.h>
|
||||
|
||||
#define AUDIO_ACK_SET_ENABLE BIT(5)
|
||||
#define AUDIO_ACK_ENABLE BIT(4)
|
||||
#define AUDIO_ACK_CONNECT BIT(0)
|
||||
|
||||
/**
|
||||
* struct hdmi_audio_status - hdmi audio current status info
|
||||
* @ack_pending: notification acknowledgment status
|
||||
* @ack_enabled: acknowledgment feature is enabled or disabled
|
||||
* @switched: audio notification status for routing
|
||||
*
|
||||
* Data for client to query about the current status of audio
|
||||
*/
|
||||
struct hdmi_audio_status {
|
||||
bool ack_pending;
|
||||
bool ack_enabled;
|
||||
bool switched;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hdmi_audio_ops - audio operations for clients to call
|
||||
* @on: function pointer to enable audio
|
||||
|
@ -49,9 +31,6 @@ struct hdmi_audio_ops {
|
|||
struct msm_ext_disp_audio_setup_params *params);
|
||||
void (*off)(void *ctx);
|
||||
void (*reset)(void *ctx);
|
||||
void (*status)(void *ctx, struct hdmi_audio_status *status);
|
||||
void (*notify)(void *ctx, int val);
|
||||
void (*ack)(void *ctx, u32 ack, u32 hpd);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -116,6 +116,11 @@ static void hdmi_tx_fps_work(struct work_struct *work);
|
|||
static int hdmi_tx_pinctrl_set_state(struct hdmi_tx_ctrl *hdmi_ctrl,
|
||||
enum hdmi_tx_power_module_type module, bool active);
|
||||
static void hdmi_panel_set_hdr_infoframe(struct hdmi_tx_ctrl *hdmi_ctrl);
|
||||
static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_setup_params *params);
|
||||
static int hdmi_tx_get_audio_edid_blk(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_edid_blk *blk);
|
||||
static int hdmi_tx_get_cable_status(struct platform_device *pdev, u32 vote);
|
||||
|
||||
static struct mdss_hw hdmi_tx_hw = {
|
||||
.hw_ndx = MDSS_HW_HDMI,
|
||||
|
@ -386,30 +391,17 @@ static inline void hdmi_tx_cec_device_suspend(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
static inline void hdmi_tx_send_cable_notification(
|
||||
struct hdmi_tx_ctrl *hdmi_ctrl, int val)
|
||||
{
|
||||
int state = 0;
|
||||
|
||||
if (!hdmi_ctrl) {
|
||||
DEV_ERR("%s: invalid input\n", __func__);
|
||||
return;
|
||||
}
|
||||
state = hdmi_ctrl->sdev.state;
|
||||
|
||||
switch_set_state(&hdmi_ctrl->sdev, val);
|
||||
|
||||
DEV_INFO("%s: cable state %s %d\n", __func__,
|
||||
hdmi_ctrl->sdev.state == state ?
|
||||
"is same" : "switched to",
|
||||
hdmi_ctrl->sdev.state);
|
||||
|
||||
/* Notify all registered modules of cable connection status */
|
||||
schedule_work(&hdmi_ctrl->cable_notify_work);
|
||||
} /* hdmi_tx_send_cable_notification */
|
||||
if (hdmi_ctrl && hdmi_ctrl->ext_audio_data.intf_ops.hpd)
|
||||
hdmi_ctrl->ext_audio_data.intf_ops.hpd(hdmi_ctrl->ext_pdev,
|
||||
hdmi_ctrl->ext_audio_data.type, val);
|
||||
}
|
||||
|
||||
static inline void hdmi_tx_set_audio_switch_node(
|
||||
struct hdmi_tx_ctrl *hdmi_ctrl, int val)
|
||||
{
|
||||
if (hdmi_ctrl && hdmi_ctrl->audio_ops.notify)
|
||||
hdmi_ctrl->audio_ops.notify(hdmi_ctrl->audio_data, val);
|
||||
if (hdmi_ctrl && hdmi_ctrl->ext_audio_data.intf_ops.notify)
|
||||
hdmi_ctrl->ext_audio_data.intf_ops.notify(hdmi_ctrl->ext_pdev,
|
||||
val);
|
||||
}
|
||||
|
||||
static void hdmi_tx_wait_for_audio_engine(struct hdmi_tx_ctrl *hdmi_ctrl)
|
||||
|
@ -638,34 +630,6 @@ static ssize_t hdmi_tx_sysfs_rda_edid(struct device *dev,
|
|||
return size;
|
||||
}
|
||||
|
||||
static ssize_t hdmi_tx_sysfs_wta_audio_cb(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int ack, rc = 0;
|
||||
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
||||
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
||||
|
||||
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
||||
|
||||
if (!hdmi_ctrl) {
|
||||
DEV_ERR("%s: invalid input\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
rc = kstrtoint(buf, 10, &ack);
|
||||
if (rc) {
|
||||
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (hdmi_ctrl->audio_ops.ack)
|
||||
hdmi_ctrl->audio_ops.ack(hdmi_ctrl->audio_data,
|
||||
ack, hdmi_ctrl->hpd_state);
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hdmi_tx_update_pixel_clk(struct hdmi_tx_ctrl *hdmi_ctrl)
|
||||
{
|
||||
struct dss_module_power *power_data = NULL;
|
||||
|
@ -885,9 +849,10 @@ static ssize_t hdmi_tx_sysfs_wta_hpd(struct device *dev,
|
|||
}
|
||||
|
||||
/* disable audio ack feature */
|
||||
if (hdmi_ctrl->audio_ops.ack)
|
||||
hdmi_ctrl->audio_ops.ack(hdmi_ctrl->audio_data,
|
||||
AUDIO_ACK_SET_ENABLE, hdmi_ctrl->hpd_state);
|
||||
if (hdmi_ctrl->ext_audio_data.intf_ops.ack)
|
||||
hdmi_ctrl->ext_audio_data.intf_ops.ack(
|
||||
hdmi_ctrl->ext_pdev,
|
||||
AUDIO_ACK_SET_ENABLE);
|
||||
|
||||
if (hdmi_ctrl->panel_power_on) {
|
||||
hdmi_ctrl->hpd_off_pending = true;
|
||||
|
@ -895,7 +860,6 @@ static ssize_t hdmi_tx_sysfs_wta_hpd(struct device *dev,
|
|||
} else {
|
||||
hdmi_tx_hpd_off(hdmi_ctrl);
|
||||
|
||||
hdmi_ctrl->sdev.state = 0;
|
||||
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
|
||||
}
|
||||
|
||||
|
@ -1339,7 +1303,6 @@ end:
|
|||
}
|
||||
|
||||
static DEVICE_ATTR(connected, S_IRUGO, hdmi_tx_sysfs_rda_connected, NULL);
|
||||
static DEVICE_ATTR(hdmi_audio_cb, S_IWUSR, NULL, hdmi_tx_sysfs_wta_audio_cb);
|
||||
static DEVICE_ATTR(hot_plug, S_IWUSR, NULL, hdmi_tx_sysfs_wta_hot_plug);
|
||||
static DEVICE_ATTR(sim_mode, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_sim_mode,
|
||||
hdmi_tx_sysfs_wta_sim_mode);
|
||||
|
@ -1362,7 +1325,6 @@ static DEVICE_ATTR(hdr_stream, S_IWUSR, NULL, hdmi_tx_sysfs_wta_hdr_stream);
|
|||
|
||||
static struct attribute *hdmi_tx_fs_attrs[] = {
|
||||
&dev_attr_connected.attr,
|
||||
&dev_attr_hdmi_audio_cb.attr,
|
||||
&dev_attr_hot_plug.attr,
|
||||
&dev_attr_sim_mode.attr,
|
||||
&dev_attr_edid.attr,
|
||||
|
@ -1937,6 +1899,56 @@ static int hdmi_tx_init_audio(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int hdmi_tx_init_ext_disp(struct hdmi_tx_ctrl *hdmi_ctrl)
|
||||
{
|
||||
int ret = 0;
|
||||
struct device_node *pd_np;
|
||||
const char *phandle = "qcom,msm_ext_disp";
|
||||
|
||||
if (!hdmi_ctrl) {
|
||||
pr_err("%s: invalid input\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
hdmi_ctrl->ext_audio_data.type = EXT_DISPLAY_TYPE_HDMI;
|
||||
hdmi_ctrl->ext_audio_data.kobj = hdmi_ctrl->kobj;
|
||||
hdmi_ctrl->ext_audio_data.codec_ops.audio_info_setup =
|
||||
hdmi_tx_audio_info_setup;
|
||||
hdmi_ctrl->ext_audio_data.codec_ops.get_audio_edid_blk =
|
||||
hdmi_tx_get_audio_edid_blk;
|
||||
hdmi_ctrl->ext_audio_data.codec_ops.cable_status =
|
||||
hdmi_tx_get_cable_status;
|
||||
|
||||
if (!hdmi_ctrl->pdev->dev.of_node) {
|
||||
pr_err("%s cannot find hdmi_ctrl dev.of_node\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
pd_np = of_parse_phandle(hdmi_ctrl->pdev->dev.of_node, phandle, 0);
|
||||
if (!pd_np) {
|
||||
pr_err("%s cannot find %s dev\n", __func__, phandle);
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
hdmi_ctrl->ext_pdev = of_find_device_by_node(pd_np);
|
||||
if (!hdmi_ctrl->ext_pdev) {
|
||||
pr_err("%s cannot find %s pdev\n", __func__, phandle);
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_register_intf(hdmi_ctrl->ext_pdev,
|
||||
&hdmi_ctrl->ext_audio_data);
|
||||
if (ret)
|
||||
pr_err("%s: failed to register disp\n", __func__);
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hdmi_tx_deinit_features(struct hdmi_tx_ctrl *hdmi_ctrl,
|
||||
u32 features)
|
||||
{
|
||||
|
@ -2029,6 +2041,12 @@ static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|||
goto err;
|
||||
}
|
||||
|
||||
ret = hdmi_tx_init_ext_disp(hdmi_ctrl);
|
||||
if (ret) {
|
||||
hdmi_audio_unregister(hdmi_ctrl->audio_data);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
hdmi_tx_deinit_features(hdmi_ctrl, deinit_features);
|
||||
|
@ -2851,20 +2869,6 @@ static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
|
|||
rc = -EPERM;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
struct hdmi_audio_status status = {0};
|
||||
|
||||
if (hdmi_ctrl->audio_ops.status)
|
||||
hdmi_ctrl->audio_ops.status(hdmi_ctrl->audio_data,
|
||||
&status);
|
||||
|
||||
dev_err_ratelimited(&hdmi_ctrl->pdev->dev,
|
||||
"%s: hpd %d, ack %d, switch %d, mode %s, power %d\n",
|
||||
__func__, hdmi_ctrl->hpd_state,
|
||||
status.ack_pending, status.switched,
|
||||
is_mode_dvi ? "dvi" : "hdmi",
|
||||
hdmi_ctrl->panel_power_on);
|
||||
}
|
||||
mutex_unlock(&hdmi_ctrl->tx_lock);
|
||||
return rc;
|
||||
}
|
||||
|
@ -2961,25 +2965,6 @@ static int hdmi_tx_get_cable_status(struct platform_device *pdev, u32 vote)
|
|||
if (vote && hpd)
|
||||
hdmi_ctrl->vote_hdmi_core_on = true;
|
||||
|
||||
/*
|
||||
* if cable is not connected and audio calls this function,
|
||||
* consider this as an error as it will result in whole
|
||||
* audio path to fail.
|
||||
*/
|
||||
if (!hpd) {
|
||||
struct hdmi_audio_status status = {0};
|
||||
|
||||
if (hdmi_ctrl->audio_ops.status)
|
||||
hdmi_ctrl->audio_ops.status(hdmi_ctrl->audio_data,
|
||||
&status);
|
||||
|
||||
dev_err_ratelimited(&hdmi_ctrl->pdev->dev,
|
||||
"%s: hpd %d, ack %d, switch %d, power %d\n",
|
||||
__func__, hdmi_ctrl->hpd_state,
|
||||
status.ack_pending, status.switched,
|
||||
hdmi_ctrl->panel_power_on);
|
||||
}
|
||||
|
||||
return hpd;
|
||||
}
|
||||
|
||||
|
@ -2987,17 +2972,21 @@ int msm_hdmi_register_audio_codec(struct platform_device *pdev,
|
|||
struct msm_ext_disp_audio_codec_ops *ops)
|
||||
{
|
||||
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
||||
int ret = 0;
|
||||
|
||||
if (!hdmi_ctrl || !ops) {
|
||||
DEV_ERR("%s: invalid input\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ops->audio_info_setup = hdmi_tx_audio_info_setup;
|
||||
ops->get_audio_edid_blk = hdmi_tx_get_audio_edid_blk;
|
||||
ops->cable_status = hdmi_tx_get_cable_status;
|
||||
ret = msm_ext_disp_register_audio_codec(hdmi_ctrl->ext_pdev, ops);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to register codec\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
return 0;
|
||||
end:
|
||||
return ret;
|
||||
} /* hdmi_tx_audio_register */
|
||||
EXPORT_SYMBOL(msm_hdmi_register_audio_codec);
|
||||
|
||||
|
@ -3478,7 +3467,6 @@ static void hdmi_tx_dev_deinit(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
hdmi_ctrl->hdcp_ops = NULL;
|
||||
hdmi_ctrl->hdcp_data = NULL;
|
||||
|
||||
switch_dev_unregister(&hdmi_ctrl->sdev);
|
||||
if (hdmi_ctrl->workq)
|
||||
destroy_workqueue(hdmi_ctrl->workq);
|
||||
mutex_destroy(&hdmi_ctrl->tx_lock);
|
||||
|
@ -3571,25 +3559,6 @@ static int hdmi_tx_start_hdcp(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int hdmi_tx_init_switch_dev(struct hdmi_tx_ctrl *hdmi_ctrl)
|
||||
{
|
||||
int rc = -EINVAL;
|
||||
|
||||
if (!hdmi_ctrl) {
|
||||
DEV_ERR("%s: invalid input\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
hdmi_ctrl->sdev.name = "hdmi";
|
||||
rc = switch_dev_register(&hdmi_ctrl->sdev);
|
||||
if (rc) {
|
||||
DEV_ERR("%s: display switch registration failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
end:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int hdmi_tx_hdcp_off(struct hdmi_tx_ctrl *hdmi_ctrl)
|
||||
{
|
||||
int rc = 0;
|
||||
|
@ -3728,12 +3697,6 @@ static int hdmi_tx_evt_handle_register(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
goto init_err;
|
||||
}
|
||||
|
||||
rc = hdmi_tx_init_switch_dev(hdmi_ctrl);
|
||||
if (rc) {
|
||||
DEV_ERR("%s: init switch dev failed.rc=%d\n", __func__, rc);
|
||||
goto switch_err;
|
||||
}
|
||||
|
||||
if (hdmi_ctrl->pdata.primary || !hdmi_ctrl->pdata.pluggable) {
|
||||
reinit_completion(&hdmi_ctrl->hpd_int_done);
|
||||
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
||||
|
@ -3748,8 +3711,6 @@ static int hdmi_tx_evt_handle_register(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|||
return 0;
|
||||
|
||||
primary_err:
|
||||
switch_dev_unregister(&hdmi_ctrl->sdev);
|
||||
switch_err:
|
||||
hdmi_tx_deinit_features(hdmi_ctrl, HDMI_TX_FEAT_MAX);
|
||||
init_err:
|
||||
hdmi_tx_sysfs_remove(hdmi_ctrl);
|
||||
|
|
|
@ -88,6 +88,7 @@ struct hdmi_tx_hdr_stream_data {
|
|||
|
||||
struct hdmi_tx_ctrl {
|
||||
struct platform_device *pdev;
|
||||
struct platform_device *ext_pdev;
|
||||
struct hdmi_tx_platform_data pdata;
|
||||
struct mdss_panel_data panel_data;
|
||||
struct mdss_util_intf *mdss_util;
|
||||
|
@ -112,6 +113,7 @@ struct hdmi_tx_ctrl {
|
|||
struct hdmi_panel_data panel;
|
||||
struct hdmi_panel_ops panel_ops;
|
||||
struct msm_ext_disp_audio_setup_params audio_params;
|
||||
struct msm_ext_disp_init_data ext_audio_data;
|
||||
struct work_struct fps_work;
|
||||
struct hdmi_tx_hdr_stream_data hdr_data;
|
||||
|
||||
|
|
748
drivers/video/fbdev/msm/msm_ext_display.c
Normal file
748
drivers/video/fbdev/msm/msm_ext_display.c
Normal file
|
@ -0,0 +1,748 @@
|
|||
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#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/switch.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/msm_ext_display.h>
|
||||
|
||||
#include "mdss_hdmi_util.h"
|
||||
|
||||
struct msm_ext_disp_list {
|
||||
struct msm_ext_disp_init_data *data;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct msm_ext_disp {
|
||||
struct platform_device *pdev;
|
||||
enum msm_ext_disp_type current_disp;
|
||||
struct msm_ext_disp_audio_codec_ops *ops;
|
||||
struct switch_dev hdmi_sdev;
|
||||
struct switch_dev audio_sdev;
|
||||
bool ack_enabled;
|
||||
atomic_t ack_pending;
|
||||
struct list_head display_list;
|
||||
struct mutex lock;
|
||||
};
|
||||
|
||||
static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp,
|
||||
enum msm_ext_disp_type type,
|
||||
struct msm_ext_disp_init_data **data);
|
||||
static int msm_ext_disp_audio_ack(struct platform_device *pdev, u32 ack);
|
||||
|
||||
static int msm_ext_disp_switch_dev_register(struct msm_ext_disp *ext_disp)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid params\n");
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
memset(&ext_disp->hdmi_sdev, 0x0, sizeof(ext_disp->hdmi_sdev));
|
||||
ext_disp->hdmi_sdev.name = "hdmi";
|
||||
ret = switch_dev_register(&ext_disp->hdmi_sdev);
|
||||
if (ret) {
|
||||
pr_err("hdmi switch registration failed\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
memset(&ext_disp->audio_sdev, 0x0, sizeof(ext_disp->audio_sdev));
|
||||
ext_disp->audio_sdev.name = "hdmi_audio";
|
||||
ret = switch_dev_register(&ext_disp->audio_sdev);
|
||||
if (ret) {
|
||||
pr_err("hdmi_audio switch registration failed");
|
||||
goto hdmi_audio_failure;
|
||||
}
|
||||
|
||||
pr_debug("Display switch registration pass\n");
|
||||
|
||||
return ret;
|
||||
|
||||
hdmi_audio_failure:
|
||||
switch_dev_unregister(&ext_disp->hdmi_sdev);
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void msm_ext_disp_switch_dev_unregister(struct msm_ext_disp *ext_disp)
|
||||
{
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid params\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
switch_dev_unregister(&ext_disp->hdmi_sdev);
|
||||
switch_dev_unregister(&ext_disp->audio_sdev);
|
||||
|
||||
end:
|
||||
return;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_get_pdev(struct device *dev,
|
||||
struct platform_device *pdev) {
|
||||
int ret = 0;
|
||||
struct device_node *pd_np;
|
||||
const char *phandle = "qcom,msm_ext_disp";
|
||||
|
||||
if (!dev || !dev->of_node) {
|
||||
ret = -ENODEV;
|
||||
pr_err("Invalid params\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
pd_np = of_parse_phandle(dev->of_node, phandle, 0);
|
||||
if (!pd_np) {
|
||||
pr_err("Cannot find %s dev\n", phandle);
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
pdev = of_find_device_by_node(pd_np);
|
||||
if (!pdev) {
|
||||
pr_err("Cannot find %s pdev\n", phandle);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t msm_ext_disp_sysfs_wta_audio_cb(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int ack, ret = 0;
|
||||
ssize_t size = strnlen(buf, PAGE_SIZE);
|
||||
struct platform_device *pdev = NULL;
|
||||
|
||||
ret = kstrtoint(buf, 10, &ack);
|
||||
if (ret) {
|
||||
pr_err("kstrtoint failed. ret=%d\n", ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_get_pdev(dev, pdev);
|
||||
if (ret) {
|
||||
pr_err("Failed to get pdev. ret=%d\n", ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_audio_ack(pdev, ack);
|
||||
if (ret)
|
||||
pr_err("Failed to process ack. ret=%d\n", ret);
|
||||
|
||||
end:
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(hdmi_audio_cb, S_IWUSR, NULL,
|
||||
msm_ext_disp_sysfs_wta_audio_cb);
|
||||
|
||||
static struct attribute *msm_ext_disp_fs_attrs[] = {
|
||||
&dev_attr_hdmi_audio_cb.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group msm_ext_disp_fs_attrs_group = {
|
||||
.attrs = msm_ext_disp_fs_attrs,
|
||||
};
|
||||
|
||||
static int msm_ext_disp_sysfs_create(struct msm_ext_disp_init_data *data)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (!data || !data->kobj) {
|
||||
pr_err("Invalid params\n");
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(data->kobj, &msm_ext_disp_fs_attrs_group);
|
||||
if (ret)
|
||||
pr_err("Failed, ret=%d\n", ret);
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void msm_ext_disp_sysfs_remove(struct msm_ext_disp_init_data *data)
|
||||
{
|
||||
if (!data || !data->kobj) {
|
||||
pr_err("Invalid params\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sysfs_remove_group(data->kobj, &msm_ext_disp_fs_attrs_group);
|
||||
}
|
||||
|
||||
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)\n",
|
||||
msm_ext_disp_name(data->type));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_get_intf_data(struct msm_ext_disp *ext_disp,
|
||||
enum msm_ext_disp_type type,
|
||||
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 || type < EXT_DISPLAY_TYPE_HDMI ||
|
||||
type >= EXT_DISPLAY_TYPE_MAX) {
|
||||
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->type == type) {
|
||||
pr_debug("Found display (%s)\n",
|
||||
msm_ext_disp_name(type));
|
||||
*data = node->data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!*data) {
|
||||
pr_debug("Display not found (%s)\n",
|
||||
msm_ext_disp_name(type));
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void msm_ext_disp_remove_intf_data(struct msm_ext_disp *ext_disp,
|
||||
enum msm_ext_disp_type type)
|
||||
{
|
||||
struct msm_ext_disp_list *node;
|
||||
struct list_head *position = NULL;
|
||||
struct list_head *temp = NULL;
|
||||
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid params\n");
|
||||
return;
|
||||
}
|
||||
|
||||
list_for_each_safe(position, temp, &ext_disp->display_list) {
|
||||
node = list_entry(position, struct msm_ext_disp_list, list);
|
||||
if (node->data->type == type) {
|
||||
msm_ext_disp_sysfs_remove(node->data);
|
||||
list_del(&node->list);
|
||||
pr_debug("Removed display (%s)\n",
|
||||
msm_ext_disp_name(type));
|
||||
kfree(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void msm_ext_disp_send_cable_notification(struct msm_ext_disp *ext_disp,
|
||||
enum msm_ext_disp_cable_state new_state)
|
||||
{
|
||||
int state = EXT_DISPLAY_CABLE_STATE_MAX;
|
||||
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid params\n");
|
||||
return;
|
||||
}
|
||||
|
||||
state = ext_disp->hdmi_sdev.state;
|
||||
switch_set_state(&ext_disp->hdmi_sdev, !!new_state);
|
||||
|
||||
pr_debug("Cable state %s %d\n",
|
||||
ext_disp->hdmi_sdev.state == state ?
|
||||
"is same" : "switched to",
|
||||
ext_disp->hdmi_sdev.state);
|
||||
}
|
||||
|
||||
static int msm_ext_disp_hpd(struct platform_device *pdev,
|
||||
enum msm_ext_disp_type type,
|
||||
enum msm_ext_disp_cable_state state)
|
||||
{
|
||||
int ret = 0;
|
||||
struct msm_ext_disp_init_data *data = NULL;
|
||||
struct msm_ext_disp *ext_disp = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("Invalid platform device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid drvdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
|
||||
pr_debug("HPD for display (%s), NEW STATE = %d\n",
|
||||
msm_ext_disp_name(type), state);
|
||||
|
||||
if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
|
||||
state >= EXT_DISPLAY_CABLE_STATE_MAX) {
|
||||
pr_err("Invalid HPD state (%d)\n", state);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((state == EXT_DISPLAY_CABLE_CONNECT) &&
|
||||
(ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)) {
|
||||
pr_err("Display interface (%s) already connected\n",
|
||||
msm_ext_disp_name(ext_disp->current_disp));
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((state == EXT_DISPLAY_CABLE_DISCONNECT) &&
|
||||
(ext_disp->current_disp != type)) {
|
||||
pr_err("Display interface (%s) is not connected\n",
|
||||
msm_ext_disp_name(type));
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_get_intf_data(ext_disp, type, &data);
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
if (state == EXT_DISPLAY_CABLE_CONNECT) {
|
||||
ext_disp->current_disp = data->type;
|
||||
} else if ((state == EXT_DISPLAY_CABLE_DISCONNECT) &&
|
||||
!ext_disp->ack_enabled) {
|
||||
ext_disp->ops->audio_info_setup = NULL;
|
||||
ext_disp->ops->get_audio_edid_blk = NULL;
|
||||
ext_disp->ops->cable_status = NULL;
|
||||
ext_disp->ops->get_intf_id = NULL;
|
||||
ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
|
||||
}
|
||||
|
||||
msm_ext_disp_send_cable_notification(ext_disp, state);
|
||||
|
||||
pr_debug("Hpd (%d) for display (%s)\n", state,
|
||||
msm_ext_disp_name(type));
|
||||
|
||||
end:
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_get_intf_id(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct msm_ext_disp *ext_disp = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("No platform device\n");
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("No drvdata found\n");
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
ret = ext_disp->current_disp;
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_notify(struct platform_device *pdev,
|
||||
enum msm_ext_disp_cable_state new_state)
|
||||
{
|
||||
int ret = 0;
|
||||
int state = 0;
|
||||
bool switched;
|
||||
struct msm_ext_disp_init_data *data = NULL;
|
||||
struct msm_ext_disp *ext_disp = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("Invalid platform device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid drvdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
|
||||
if (state < EXT_DISPLAY_CABLE_DISCONNECT ||
|
||||
state >= EXT_DISPLAY_CABLE_STATE_MAX) {
|
||||
pr_err("Invalid state (%d)\n", state);
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
state = ext_disp->audio_sdev.state;
|
||||
if (state == new_state)
|
||||
goto end;
|
||||
|
||||
if (ext_disp->ack_enabled &&
|
||||
atomic_read(&ext_disp->ack_pending)) {
|
||||
ret = -EINVAL;
|
||||
pr_err("%s ack pending, not notifying %s\n",
|
||||
state ? "connect" : "disconnect",
|
||||
new_state ? "connect" : "disconnect");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_get_intf_data(ext_disp, ext_disp->current_disp,
|
||||
&data);
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
if (new_state == EXT_DISPLAY_CABLE_CONNECT) {
|
||||
ext_disp->ops->audio_info_setup =
|
||||
data->codec_ops.audio_info_setup;
|
||||
ext_disp->ops->get_audio_edid_blk =
|
||||
data->codec_ops.get_audio_edid_blk;
|
||||
ext_disp->ops->cable_status =
|
||||
data->codec_ops.cable_status;
|
||||
ext_disp->ops->get_intf_id =
|
||||
msm_ext_disp_get_intf_id;
|
||||
}
|
||||
|
||||
switch_set_state(&ext_disp->audio_sdev, (int)new_state);
|
||||
switched = ext_disp->audio_sdev.state != state;
|
||||
|
||||
if (ext_disp->ack_enabled && switched)
|
||||
atomic_set(&ext_disp->ack_pending, 1);
|
||||
|
||||
pr_debug("audio %s %s\n", switched ? "switched to" : "same as",
|
||||
ext_disp->audio_sdev.state ? "HDMI" : "SPKR");
|
||||
|
||||
end:
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_audio_ack(struct platform_device *pdev, u32 ack)
|
||||
{
|
||||
u32 ack_hpd;
|
||||
u32 hpd;
|
||||
int ret = 0;
|
||||
struct msm_ext_disp *ext_disp = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("Invalid platform device\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid drvdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
|
||||
hpd = ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX;
|
||||
|
||||
if (ack & AUDIO_ACK_SET_ENABLE) {
|
||||
ext_disp->ack_enabled = ack & AUDIO_ACK_ENABLE ?
|
||||
true : false;
|
||||
|
||||
pr_debug("audio ack feature %s\n",
|
||||
ext_disp->ack_enabled ? "enabled" : "disabled");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!ext_disp->ack_enabled)
|
||||
goto end;
|
||||
|
||||
atomic_set(&ext_disp->ack_pending, 0);
|
||||
|
||||
ack_hpd = ack & AUDIO_ACK_CONNECT;
|
||||
|
||||
pr_debug("acknowledging %s\n",
|
||||
ack_hpd ? "connect" : "disconnect");
|
||||
|
||||
/**
|
||||
* If the ack feature is enabled and we receive an ack for
|
||||
* disconnect then we reset the current display state to
|
||||
* empty.
|
||||
*/
|
||||
if (!ack_hpd) {
|
||||
ext_disp->ops->audio_info_setup = NULL;
|
||||
ext_disp->ops->get_audio_edid_blk = NULL;
|
||||
ext_disp->ops->cable_status = NULL;
|
||||
ext_disp->ops->get_intf_id = NULL;
|
||||
ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
|
||||
}
|
||||
|
||||
if (ack_hpd != hpd) {
|
||||
pr_err("unbalanced audio state, ack %d, hpd %d\n",
|
||||
ack_hpd, hpd);
|
||||
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
ret = msm_ext_disp_notify(pdev, hpd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
end:
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!pdev || !ops) {
|
||||
pr_err("Invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid drvdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
|
||||
if ((ext_disp->current_disp != EXT_DISPLAY_TYPE_MAX)
|
||||
&& 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);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!pdev || !init_data) {
|
||||
pr_err("Invalid params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("Invalid drvdata\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&ext_disp->lock);
|
||||
|
||||
ret = msm_ext_disp_get_intf_data(ext_disp, init_data->type, &data);
|
||||
if (!ret) {
|
||||
pr_debug("Display (%s) already registered\n",
|
||||
msm_ext_disp_name(init_data->type));
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = msm_ext_disp_add_intf_data(ext_disp, init_data);
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
ret = msm_ext_disp_sysfs_create(init_data);
|
||||
if (ret)
|
||||
goto sysfs_failure;
|
||||
|
||||
init_data->intf_ops.hpd = msm_ext_disp_hpd;
|
||||
init_data->intf_ops.notify = msm_ext_disp_notify;
|
||||
|
||||
pr_debug("Display (%s) registered\n",
|
||||
msm_ext_disp_name(init_data->type));
|
||||
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
return ret;
|
||||
|
||||
sysfs_failure:
|
||||
msm_ext_disp_remove_intf_data(ext_disp, init_data->type);
|
||||
end:
|
||||
mutex_unlock(&ext_disp->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
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->pdev = pdev;
|
||||
|
||||
ret = msm_ext_disp_switch_dev_register(ext_disp);
|
||||
if (ret)
|
||||
goto switch_dev_failure;
|
||||
|
||||
mutex_init(&ext_disp->lock);
|
||||
|
||||
INIT_LIST_HEAD(&ext_disp->display_list);
|
||||
ext_disp->current_disp = EXT_DISPLAY_TYPE_MAX;
|
||||
|
||||
return ret;
|
||||
|
||||
switch_dev_failure:
|
||||
devm_kfree(&ext_disp->pdev->dev, ext_disp);
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int msm_ext_disp_remove(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct msm_ext_disp *ext_disp = NULL;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("No platform device\n");
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
ext_disp = platform_get_drvdata(pdev);
|
||||
if (!ext_disp) {
|
||||
pr_err("No drvdata found\n");
|
||||
ret = -ENODEV;
|
||||
goto end;
|
||||
}
|
||||
|
||||
msm_ext_disp_switch_dev_unregister(ext_disp);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void __exit msm_ext_disp_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&this_driver);
|
||||
}
|
||||
|
||||
module_init(msm_ext_disp_init);
|
||||
module_exit(msm_ext_disp_exit);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("MSM External Display");
|
||||
|
|
@ -17,11 +17,15 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
/*
|
||||
* External display 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
|
||||
#define AUDIO_ACK_SET_ENABLE BIT(5)
|
||||
#define AUDIO_ACK_ENABLE BIT(4)
|
||||
#define AUDIO_ACK_CONNECT BIT(0)
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
@ -45,14 +49,96 @@ struct msm_ext_disp_audio_setup_params {
|
|||
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,
|
||||
EXT_DISPLAY_TYPE_DP,
|
||||
EXT_DISPLAY_TYPE_MAX
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_intf_ops - operations exposed to display interface
|
||||
* @hpd: updates external display interface state
|
||||
* @notify: updates audio framework with interface state
|
||||
*/
|
||||
struct msm_ext_disp_intf_ops {
|
||||
int (*hpd)(struct platform_device *pdev,
|
||||
enum msm_ext_disp_type type,
|
||||
enum msm_ext_disp_cable_state state);
|
||||
int (*notify)(struct platform_device *pdev,
|
||||
enum msm_ext_disp_cable_state state);
|
||||
int (*ack)(struct platform_device *pdev,
|
||||
u32 ack);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
/*
|
||||
* struct msm_ext_disp_init_data - data needed to register the display interface
|
||||
* @disp: external display type
|
||||
* @intf_ops: external display interface operations
|
||||
* @codec_ops: audio codec operations
|
||||
*/
|
||||
struct msm_ext_disp_init_data {
|
||||
enum msm_ext_disp_type type;
|
||||
struct kobject *kobj;
|
||||
struct msm_ext_disp_intf_ops intf_ops;
|
||||
struct msm_ext_disp_audio_codec_ops codec_ops;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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_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);
|
||||
|
||||
/* TODO: remove all the display specific functions below */
|
||||
#ifdef CONFIG_FB_MSM_MDSS_DP_PANEL
|
||||
int msm_dp_register_audio_codec(struct platform_device *pdev,
|
||||
struct msm_ext_disp_audio_codec_ops *ops);
|
||||
|
|
Loading…
Add table
Reference in a new issue