We shouldn't be waiting for an event here with a timeout of 100ms when
we're not in the 'timeout' arm of the if condition. Instead we should be
sleeping in the interruptible state (S) until something happens and we
need to wakeup. Right now this kthread is running almost all the time
because it sleeps for 100ms, wakes up, sees there's nothing to do, and
then starts the process all over again. Looking at top it shows up in
the D state (uninterruptible) because it uses wait_event_timeout(). FIx
this up.
Cc: Tanmay Shah <tanmay@codeaurora.org>
Cc: Kuogee Hsieh <khsieh@codeaurora.org>
Reported-by: Douglas Anderson <dianders@chromium.org>
Fixes: 8ede2ecc3e
("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets")
Signed-off-by: Stephen Boyd <swboyd@chromium.org>
Reviewed-by: Kuogee Hsieh <khsieh@codeaurora.org>
Signed-off-by: Rob Clark <robdclark@chromium.org>
1463 lines
32 KiB
C
1463 lines
32 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/component.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "msm_drv.h"
|
|
#include "msm_kms.h"
|
|
#include "dp_hpd.h"
|
|
#include "dp_parser.h"
|
|
#include "dp_power.h"
|
|
#include "dp_catalog.h"
|
|
#include "dp_aux.h"
|
|
#include "dp_reg.h"
|
|
#include "dp_link.h"
|
|
#include "dp_panel.h"
|
|
#include "dp_ctrl.h"
|
|
#include "dp_display.h"
|
|
#include "dp_drm.h"
|
|
#include "dp_audio.h"
|
|
#include "dp_debug.h"
|
|
|
|
static struct msm_dp *g_dp_display;
|
|
#define HPD_STRING_SIZE 30
|
|
|
|
enum {
|
|
ISR_DISCONNECTED,
|
|
ISR_CONNECT_PENDING,
|
|
ISR_CONNECTED,
|
|
ISR_HPD_REPLUG_COUNT,
|
|
ISR_IRQ_HPD_PULSE_COUNT,
|
|
ISR_HPD_LO_GLITH_COUNT,
|
|
};
|
|
|
|
/* event thread connection state */
|
|
enum {
|
|
ST_DISCONNECTED,
|
|
ST_CONNECT_PENDING,
|
|
ST_CONNECTED,
|
|
ST_DISCONNECT_PENDING,
|
|
ST_SUSPEND_PENDING,
|
|
ST_SUSPENDED,
|
|
};
|
|
|
|
enum {
|
|
EV_NO_EVENT,
|
|
/* hpd events */
|
|
EV_HPD_INIT_SETUP,
|
|
EV_HPD_PLUG_INT,
|
|
EV_IRQ_HPD_INT,
|
|
EV_HPD_REPLUG_INT,
|
|
EV_HPD_UNPLUG_INT,
|
|
EV_USER_NOTIFICATION,
|
|
EV_CONNECT_PENDING_TIMEOUT,
|
|
EV_DISCONNECT_PENDING_TIMEOUT,
|
|
};
|
|
|
|
#define EVENT_TIMEOUT (HZ/10) /* 100ms */
|
|
#define DP_EVENT_Q_MAX 8
|
|
|
|
#define DP_TIMEOUT_5_SECOND (5000/EVENT_TIMEOUT)
|
|
#define DP_TIMEOUT_NONE 0
|
|
|
|
#define WAIT_FOR_RESUME_TIMEOUT_JIFFIES (HZ / 2)
|
|
|
|
struct dp_event {
|
|
u32 event_id;
|
|
u32 data;
|
|
u32 delay;
|
|
};
|
|
|
|
struct dp_display_private {
|
|
char *name;
|
|
int irq;
|
|
|
|
/* state variables */
|
|
bool core_initialized;
|
|
bool hpd_irq_on;
|
|
bool audio_supported;
|
|
|
|
struct platform_device *pdev;
|
|
struct dentry *root;
|
|
|
|
struct dp_usbpd *usbpd;
|
|
struct dp_parser *parser;
|
|
struct dp_power *power;
|
|
struct dp_catalog *catalog;
|
|
struct drm_dp_aux *aux;
|
|
struct dp_link *link;
|
|
struct dp_panel *panel;
|
|
struct dp_ctrl *ctrl;
|
|
struct dp_debug *debug;
|
|
|
|
struct dp_usbpd_cb usbpd_cb;
|
|
struct dp_display_mode dp_mode;
|
|
struct msm_dp dp_display;
|
|
|
|
/* wait for audio signaling */
|
|
struct completion audio_comp;
|
|
|
|
/* event related only access by event thread */
|
|
struct mutex event_mutex;
|
|
wait_queue_head_t event_q;
|
|
atomic_t hpd_state;
|
|
u32 event_pndx;
|
|
u32 event_gndx;
|
|
struct dp_event event_list[DP_EVENT_Q_MAX];
|
|
spinlock_t event_lock;
|
|
|
|
struct completion resume_comp;
|
|
|
|
struct dp_audio *audio;
|
|
};
|
|
|
|
static const struct of_device_id dp_dt_match[] = {
|
|
{.compatible = "qcom,sc7180-dp"},
|
|
{}
|
|
};
|
|
|
|
static int dp_add_event(struct dp_display_private *dp_priv, u32 event,
|
|
u32 data, u32 delay)
|
|
{
|
|
unsigned long flag;
|
|
struct dp_event *todo;
|
|
int pndx;
|
|
|
|
spin_lock_irqsave(&dp_priv->event_lock, flag);
|
|
pndx = dp_priv->event_pndx + 1;
|
|
pndx %= DP_EVENT_Q_MAX;
|
|
if (pndx == dp_priv->event_gndx) {
|
|
pr_err("event_q is full: pndx=%d gndx=%d\n",
|
|
dp_priv->event_pndx, dp_priv->event_gndx);
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
return -EPERM;
|
|
}
|
|
todo = &dp_priv->event_list[dp_priv->event_pndx++];
|
|
dp_priv->event_pndx %= DP_EVENT_Q_MAX;
|
|
todo->event_id = event;
|
|
todo->data = data;
|
|
todo->delay = delay;
|
|
wake_up(&dp_priv->event_q);
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_del_event(struct dp_display_private *dp_priv, u32 event)
|
|
{
|
|
unsigned long flag;
|
|
struct dp_event *todo;
|
|
u32 gndx;
|
|
|
|
spin_lock_irqsave(&dp_priv->event_lock, flag);
|
|
if (dp_priv->event_pndx == dp_priv->event_gndx) {
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
return -ENOENT;
|
|
}
|
|
|
|
gndx = dp_priv->event_gndx;
|
|
while (dp_priv->event_pndx != gndx) {
|
|
todo = &dp_priv->event_list[gndx];
|
|
if (todo->event_id == event) {
|
|
todo->event_id = EV_NO_EVENT; /* deleted */
|
|
todo->delay = 0;
|
|
}
|
|
gndx++;
|
|
gndx %= DP_EVENT_Q_MAX;
|
|
}
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dp_display_signal_audio_complete(struct msm_dp *dp_display)
|
|
{
|
|
struct dp_display_private *dp;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
complete_all(&dp->audio_comp);
|
|
}
|
|
|
|
static int dp_display_bind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
struct drm_device *drm;
|
|
struct msm_drm_private *priv;
|
|
|
|
drm = dev_get_drvdata(master);
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
if (!dp) {
|
|
DRM_ERROR("DP driver bind failed. Invalid driver data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dp->dp_display.drm_dev = drm;
|
|
priv = drm->dev_private;
|
|
priv->dp = &(dp->dp_display);
|
|
|
|
rc = dp->parser->parse(dp->parser);
|
|
if (rc) {
|
|
DRM_ERROR("device tree parsing failed\n");
|
|
goto end;
|
|
}
|
|
|
|
rc = dp_aux_register(dp->aux);
|
|
if (rc) {
|
|
DRM_ERROR("DRM DP AUX register failed\n");
|
|
goto end;
|
|
}
|
|
|
|
rc = dp_power_client_init(dp->power);
|
|
if (rc) {
|
|
DRM_ERROR("Power client create failed\n");
|
|
goto end;
|
|
}
|
|
|
|
rc = dp_register_audio_driver(dev, dp->audio);
|
|
if (rc)
|
|
DRM_ERROR("Audio registration Dp failed\n");
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static void dp_display_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
struct dp_display_private *dp;
|
|
struct drm_device *drm = dev_get_drvdata(master);
|
|
struct msm_drm_private *priv = drm->dev_private;
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
if (!dp) {
|
|
DRM_ERROR("Invalid DP driver data\n");
|
|
return;
|
|
}
|
|
|
|
dp_power_client_deinit(dp->power);
|
|
dp_aux_unregister(dp->aux);
|
|
priv->dp = NULL;
|
|
}
|
|
|
|
static const struct component_ops dp_display_comp_ops = {
|
|
.bind = dp_display_bind,
|
|
.unbind = dp_display_unbind,
|
|
};
|
|
|
|
static bool dp_display_is_ds_bridge(struct dp_panel *panel)
|
|
{
|
|
return (panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] &
|
|
DP_DWN_STRM_PORT_PRESENT);
|
|
}
|
|
|
|
static bool dp_display_is_sink_count_zero(struct dp_display_private *dp)
|
|
{
|
|
return dp_display_is_ds_bridge(dp->panel) &&
|
|
(dp->link->sink_count == 0);
|
|
}
|
|
|
|
static void dp_display_send_hpd_event(struct msm_dp *dp_display)
|
|
{
|
|
struct dp_display_private *dp;
|
|
struct drm_connector *connector;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
connector = dp->dp_display.connector;
|
|
drm_helper_hpd_irq_event(connector->dev);
|
|
}
|
|
|
|
static int dp_display_send_hpd_notification(struct dp_display_private *dp,
|
|
bool hpd)
|
|
{
|
|
static bool encoder_mode_set;
|
|
struct msm_drm_private *priv = dp->dp_display.drm_dev->dev_private;
|
|
struct msm_kms *kms = priv->kms;
|
|
|
|
if ((hpd && dp->dp_display.is_connected) ||
|
|
(!hpd && !dp->dp_display.is_connected)) {
|
|
DRM_DEBUG_DP("HPD already %s\n", (hpd ? "on" : "off"));
|
|
return 0;
|
|
}
|
|
|
|
/* reset video pattern flag on disconnect */
|
|
if (!hpd)
|
|
dp->panel->video_test = false;
|
|
|
|
dp->dp_display.is_connected = hpd;
|
|
|
|
if (dp->dp_display.is_connected && dp->dp_display.encoder
|
|
&& !encoder_mode_set
|
|
&& kms->funcs->set_encoder_mode) {
|
|
kms->funcs->set_encoder_mode(kms,
|
|
dp->dp_display.encoder, false);
|
|
DRM_DEBUG_DP("set_encoder_mode() Completed\n");
|
|
encoder_mode_set = true;
|
|
}
|
|
|
|
dp_display_send_hpd_event(&dp->dp_display);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_process_hpd_high(struct dp_display_private *dp)
|
|
{
|
|
int rc = 0;
|
|
struct edid *edid;
|
|
|
|
dp->panel->max_dp_lanes = dp->parser->max_dp_lanes;
|
|
|
|
rc = dp_panel_read_sink_caps(dp->panel, dp->dp_display.connector);
|
|
if (rc)
|
|
goto end;
|
|
|
|
dp_link_process_request(dp->link);
|
|
|
|
edid = dp->panel->edid;
|
|
|
|
dp->audio_supported = drm_detect_monitor_audio(edid);
|
|
dp_panel_handle_sink_request(dp->panel);
|
|
|
|
dp->dp_display.max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ;
|
|
dp->dp_display.max_dp_lanes = dp->parser->max_dp_lanes;
|
|
|
|
rc = dp_ctrl_on_link(dp->ctrl);
|
|
if (rc) {
|
|
DRM_ERROR("failed to complete DP link training\n");
|
|
goto end;
|
|
}
|
|
|
|
dp_add_event(dp, EV_USER_NOTIFICATION, true, 0);
|
|
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static void dp_display_host_init(struct dp_display_private *dp)
|
|
{
|
|
bool flip = false;
|
|
|
|
if (dp->core_initialized) {
|
|
DRM_DEBUG_DP("DP core already initialized\n");
|
|
return;
|
|
}
|
|
|
|
if (dp->usbpd->orientation == ORIENTATION_CC2)
|
|
flip = true;
|
|
|
|
dp_power_init(dp->power, flip);
|
|
dp_ctrl_host_init(dp->ctrl, flip);
|
|
dp_aux_init(dp->aux);
|
|
dp->core_initialized = true;
|
|
}
|
|
|
|
static int dp_display_usbpd_configure_cb(struct device *dev)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
|
|
if (!dev) {
|
|
DRM_ERROR("invalid dev\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
if (!dp) {
|
|
DRM_ERROR("no driver data found\n");
|
|
rc = -ENODEV;
|
|
goto end;
|
|
}
|
|
|
|
dp_display_host_init(dp);
|
|
|
|
/*
|
|
* set sink to normal operation mode -- D0
|
|
* before dpcd read
|
|
*/
|
|
dp_link_psm_config(dp->link, &dp->panel->link_info, false);
|
|
rc = dp_display_process_hpd_high(dp);
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
static int dp_display_usbpd_disconnect_cb(struct device *dev)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
|
|
if (!dev) {
|
|
DRM_ERROR("invalid dev\n");
|
|
rc = -EINVAL;
|
|
return rc;
|
|
}
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
if (!dp) {
|
|
DRM_ERROR("no driver data found\n");
|
|
rc = -ENODEV;
|
|
return rc;
|
|
}
|
|
|
|
dp_add_event(dp, EV_USER_NOTIFICATION, false, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void dp_display_handle_video_request(struct dp_display_private *dp)
|
|
{
|
|
if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) {
|
|
dp->panel->video_test = true;
|
|
dp_link_send_test_response(dp->link);
|
|
}
|
|
}
|
|
|
|
static int dp_display_handle_irq_hpd(struct dp_display_private *dp)
|
|
{
|
|
u32 sink_request;
|
|
|
|
sink_request = dp->link->sink_request;
|
|
|
|
if (sink_request & DS_PORT_STATUS_CHANGED) {
|
|
dp_add_event(dp, EV_USER_NOTIFICATION, false, 0);
|
|
if (dp_display_is_sink_count_zero(dp)) {
|
|
DRM_DEBUG_DP("sink count is zero, nothing to do\n");
|
|
return 0;
|
|
}
|
|
|
|
return dp_display_process_hpd_high(dp);
|
|
}
|
|
|
|
dp_ctrl_handle_sink_request(dp->ctrl);
|
|
|
|
if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN)
|
|
dp_display_handle_video_request(dp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_usbpd_attention_cb(struct device *dev)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
|
|
if (!dev) {
|
|
DRM_ERROR("invalid dev\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
if (!dp) {
|
|
DRM_ERROR("no driver data found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* check for any test request issued by sink */
|
|
rc = dp_link_process_request(dp->link);
|
|
if (!rc)
|
|
dp_display_handle_irq_hpd(dp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int dp_hpd_plug_handle(struct dp_display_private *dp, u32 data)
|
|
{
|
|
struct dp_usbpd *hpd = dp->usbpd;
|
|
u32 state;
|
|
u32 tout = DP_TIMEOUT_5_SECOND;
|
|
int ret;
|
|
|
|
if (!hpd)
|
|
return 0;
|
|
|
|
mutex_lock(&dp->event_mutex);
|
|
|
|
state = atomic_read(&dp->hpd_state);
|
|
if (state == ST_SUSPEND_PENDING) {
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (state == ST_CONNECT_PENDING || state == ST_CONNECTED) {
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (state == ST_DISCONNECT_PENDING) {
|
|
/* wait until ST_DISCONNECTED */
|
|
dp_add_event(dp, EV_HPD_PLUG_INT, 0, 1); /* delay = 1 */
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (state == ST_SUSPENDED)
|
|
tout = DP_TIMEOUT_NONE;
|
|
|
|
atomic_set(&dp->hpd_state, ST_CONNECT_PENDING);
|
|
|
|
hpd->hpd_high = 1;
|
|
|
|
ret = dp_display_usbpd_configure_cb(&dp->pdev->dev);
|
|
if (ret) { /* failed */
|
|
hpd->hpd_high = 0;
|
|
atomic_set(&dp->hpd_state, ST_DISCONNECTED);
|
|
}
|
|
|
|
/* start sanity checking */
|
|
dp_add_event(dp, EV_CONNECT_PENDING_TIMEOUT, 0, tout);
|
|
|
|
mutex_unlock(&dp->event_mutex);
|
|
|
|
/* uevent will complete connection part */
|
|
return 0;
|
|
};
|
|
|
|
static int dp_display_enable(struct dp_display_private *dp, u32 data);
|
|
static int dp_display_disable(struct dp_display_private *dp, u32 data);
|
|
|
|
static int dp_connect_pending_timeout(struct dp_display_private *dp, u32 data)
|
|
{
|
|
u32 state;
|
|
|
|
mutex_lock(&dp->event_mutex);
|
|
|
|
state = atomic_read(&dp->hpd_state);
|
|
if (state == ST_CONNECT_PENDING) {
|
|
dp_display_enable(dp, 0);
|
|
atomic_set(&dp->hpd_state, ST_CONNECTED);
|
|
}
|
|
|
|
mutex_unlock(&dp->event_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dp_display_handle_plugged_change(struct msm_dp *dp_display,
|
|
bool plugged)
|
|
{
|
|
if (dp_display->plugged_cb && dp_display->codec_dev)
|
|
dp_display->plugged_cb(dp_display->codec_dev, plugged);
|
|
}
|
|
|
|
static int dp_hpd_unplug_handle(struct dp_display_private *dp, u32 data)
|
|
{
|
|
struct dp_usbpd *hpd = dp->usbpd;
|
|
u32 state;
|
|
|
|
if (!hpd)
|
|
return 0;
|
|
|
|
mutex_lock(&dp->event_mutex);
|
|
|
|
state = atomic_read(&dp->hpd_state);
|
|
if (state == ST_SUSPEND_PENDING) {
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (state == ST_DISCONNECT_PENDING || state == ST_DISCONNECTED) {
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (state == ST_CONNECT_PENDING) {
|
|
/* wait until CONNECTED */
|
|
dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 1); /* delay = 1 */
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
atomic_set(&dp->hpd_state, ST_DISCONNECT_PENDING);
|
|
|
|
/* disable HPD plug interrupt until disconnect is done */
|
|
dp_catalog_hpd_config_intr(dp->catalog, DP_DP_HPD_PLUG_INT_MASK
|
|
| DP_DP_IRQ_HPD_INT_MASK, false);
|
|
|
|
hpd->hpd_high = 0;
|
|
|
|
/*
|
|
* We don't need separate work for disconnect as
|
|
* connect/attention interrupts are disabled
|
|
*/
|
|
dp_display_usbpd_disconnect_cb(&dp->pdev->dev);
|
|
|
|
/* start sanity checking */
|
|
dp_add_event(dp, EV_DISCONNECT_PENDING_TIMEOUT, 0, DP_TIMEOUT_5_SECOND);
|
|
|
|
/* signal the disconnect event early to ensure proper teardown */
|
|
dp_display_handle_plugged_change(g_dp_display, false);
|
|
reinit_completion(&dp->audio_comp);
|
|
|
|
dp_catalog_hpd_config_intr(dp->catalog, DP_DP_HPD_PLUG_INT_MASK |
|
|
DP_DP_IRQ_HPD_INT_MASK, true);
|
|
|
|
/* uevent will complete disconnection part */
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static int dp_disconnect_pending_timeout(struct dp_display_private *dp, u32 data)
|
|
{
|
|
u32 state;
|
|
|
|
mutex_lock(&dp->event_mutex);
|
|
|
|
state = atomic_read(&dp->hpd_state);
|
|
if (state == ST_DISCONNECT_PENDING) {
|
|
dp_display_disable(dp, 0);
|
|
atomic_set(&dp->hpd_state, ST_DISCONNECTED);
|
|
}
|
|
|
|
mutex_unlock(&dp->event_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_irq_hpd_handle(struct dp_display_private *dp, u32 data)
|
|
{
|
|
u32 state;
|
|
|
|
mutex_lock(&dp->event_mutex);
|
|
|
|
/* irq_hpd can happen at either connected or disconnected state */
|
|
state = atomic_read(&dp->hpd_state);
|
|
if (state == ST_SUSPEND_PENDING) {
|
|
mutex_unlock(&dp->event_mutex);
|
|
return 0;
|
|
}
|
|
|
|
dp_display_usbpd_attention_cb(&dp->pdev->dev);
|
|
|
|
mutex_unlock(&dp->event_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dp_display_deinit_sub_modules(struct dp_display_private *dp)
|
|
{
|
|
dp_debug_put(dp->debug);
|
|
dp_ctrl_put(dp->ctrl);
|
|
dp_panel_put(dp->panel);
|
|
dp_aux_put(dp->aux);
|
|
dp_audio_put(dp->audio);
|
|
}
|
|
|
|
static int dp_init_sub_modules(struct dp_display_private *dp)
|
|
{
|
|
int rc = 0;
|
|
struct device *dev = &dp->pdev->dev;
|
|
struct dp_usbpd_cb *cb = &dp->usbpd_cb;
|
|
struct dp_panel_in panel_in = {
|
|
.dev = dev,
|
|
};
|
|
|
|
/* Callback APIs used for cable status change event */
|
|
cb->configure = dp_display_usbpd_configure_cb;
|
|
cb->disconnect = dp_display_usbpd_disconnect_cb;
|
|
cb->attention = dp_display_usbpd_attention_cb;
|
|
|
|
dp->usbpd = dp_hpd_get(dev, cb);
|
|
if (IS_ERR(dp->usbpd)) {
|
|
rc = PTR_ERR(dp->usbpd);
|
|
DRM_ERROR("failed to initialize hpd, rc = %d\n", rc);
|
|
dp->usbpd = NULL;
|
|
goto error;
|
|
}
|
|
|
|
dp->parser = dp_parser_get(dp->pdev);
|
|
if (IS_ERR(dp->parser)) {
|
|
rc = PTR_ERR(dp->parser);
|
|
DRM_ERROR("failed to initialize parser, rc = %d\n", rc);
|
|
dp->parser = NULL;
|
|
goto error;
|
|
}
|
|
|
|
dp->catalog = dp_catalog_get(dev, &dp->parser->io);
|
|
if (IS_ERR(dp->catalog)) {
|
|
rc = PTR_ERR(dp->catalog);
|
|
DRM_ERROR("failed to initialize catalog, rc = %d\n", rc);
|
|
dp->catalog = NULL;
|
|
goto error;
|
|
}
|
|
|
|
dp->power = dp_power_get(dp->parser);
|
|
if (IS_ERR(dp->power)) {
|
|
rc = PTR_ERR(dp->power);
|
|
DRM_ERROR("failed to initialize power, rc = %d\n", rc);
|
|
dp->power = NULL;
|
|
goto error;
|
|
}
|
|
|
|
dp->aux = dp_aux_get(dev, dp->catalog);
|
|
if (IS_ERR(dp->aux)) {
|
|
rc = PTR_ERR(dp->aux);
|
|
DRM_ERROR("failed to initialize aux, rc = %d\n", rc);
|
|
dp->aux = NULL;
|
|
goto error;
|
|
}
|
|
|
|
dp->link = dp_link_get(dev, dp->aux);
|
|
if (IS_ERR(dp->link)) {
|
|
rc = PTR_ERR(dp->link);
|
|
DRM_ERROR("failed to initialize link, rc = %d\n", rc);
|
|
dp->link = NULL;
|
|
goto error_link;
|
|
}
|
|
|
|
panel_in.aux = dp->aux;
|
|
panel_in.catalog = dp->catalog;
|
|
panel_in.link = dp->link;
|
|
|
|
dp->panel = dp_panel_get(&panel_in);
|
|
if (IS_ERR(dp->panel)) {
|
|
rc = PTR_ERR(dp->panel);
|
|
DRM_ERROR("failed to initialize panel, rc = %d\n", rc);
|
|
dp->panel = NULL;
|
|
goto error_link;
|
|
}
|
|
|
|
dp->ctrl = dp_ctrl_get(dev, dp->link, dp->panel, dp->aux,
|
|
dp->power, dp->catalog, dp->parser);
|
|
if (IS_ERR(dp->ctrl)) {
|
|
rc = PTR_ERR(dp->ctrl);
|
|
DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc);
|
|
dp->ctrl = NULL;
|
|
goto error_ctrl;
|
|
}
|
|
|
|
dp->audio = dp_audio_get(dp->pdev, dp->panel, dp->catalog);
|
|
if (IS_ERR(dp->audio)) {
|
|
rc = PTR_ERR(dp->audio);
|
|
pr_err("failed to initialize audio, rc = %d\n", rc);
|
|
dp->audio = NULL;
|
|
goto error_audio;
|
|
}
|
|
|
|
return rc;
|
|
|
|
error_audio:
|
|
dp_ctrl_put(dp->ctrl);
|
|
error_ctrl:
|
|
dp_panel_put(dp->panel);
|
|
error_link:
|
|
dp_aux_put(dp->aux);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static int dp_display_set_mode(struct msm_dp *dp_display,
|
|
struct dp_display_mode *mode)
|
|
{
|
|
struct dp_display_private *dp;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
dp->panel->dp_mode.drm_mode = mode->drm_mode;
|
|
dp->panel->dp_mode.bpp = mode->bpp;
|
|
dp->panel->dp_mode.capabilities = mode->capabilities;
|
|
dp_panel_init_panel_info(dp->panel);
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_prepare(struct msm_dp *dp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_enable(struct dp_display_private *dp, u32 data)
|
|
{
|
|
int rc = 0;
|
|
struct msm_dp *dp_display;
|
|
|
|
dp_display = g_dp_display;
|
|
|
|
if (dp_display->power_on) {
|
|
DRM_DEBUG_DP("Link already setup, return\n");
|
|
return 0;
|
|
}
|
|
|
|
rc = dp_ctrl_on_stream(dp->ctrl);
|
|
if (!rc)
|
|
dp_display->power_on = true;
|
|
|
|
/* complete resume_comp regardless it is armed or not */
|
|
complete(&dp->resume_comp);
|
|
return rc;
|
|
}
|
|
|
|
static int dp_display_post_enable(struct msm_dp *dp_display)
|
|
{
|
|
struct dp_display_private *dp;
|
|
u32 rate;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
rate = dp->link->link_params.rate;
|
|
|
|
if (dp->audio_supported) {
|
|
dp->audio->bw_code = drm_dp_link_rate_to_bw_code(rate);
|
|
dp->audio->lane_count = dp->link->link_params.num_lanes;
|
|
}
|
|
|
|
/* signal the connect event late to synchronize video and display */
|
|
dp_display_handle_plugged_change(dp_display, true);
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_disable(struct dp_display_private *dp, u32 data)
|
|
{
|
|
struct msm_dp *dp_display;
|
|
|
|
dp_display = g_dp_display;
|
|
|
|
if (!dp_display->power_on)
|
|
return -EINVAL;
|
|
|
|
/* wait only if audio was enabled */
|
|
if (dp_display->audio_enabled) {
|
|
if (!wait_for_completion_timeout(&dp->audio_comp,
|
|
HZ * 5))
|
|
DRM_ERROR("audio comp timeout\n");
|
|
}
|
|
|
|
dp_display->audio_enabled = false;
|
|
|
|
dp_ctrl_off(dp->ctrl);
|
|
|
|
dp->core_initialized = false;
|
|
|
|
dp_display->power_on = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_unprepare(struct msm_dp *dp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int dp_display_set_plugged_cb(struct msm_dp *dp_display,
|
|
hdmi_codec_plugged_cb fn, struct device *codec_dev)
|
|
{
|
|
bool plugged;
|
|
|
|
dp_display->plugged_cb = fn;
|
|
dp_display->codec_dev = codec_dev;
|
|
plugged = dp_display->is_connected;
|
|
dp_display_handle_plugged_change(dp_display, plugged);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dp_display_validate_mode(struct msm_dp *dp, u32 mode_pclk_khz)
|
|
{
|
|
const u32 num_components = 3, default_bpp = 24;
|
|
struct dp_display_private *dp_display;
|
|
struct dp_link_info *link_info;
|
|
u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0;
|
|
|
|
if (!dp || !mode_pclk_khz || !dp->connector) {
|
|
DRM_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
link_info = &dp_display->panel->link_info;
|
|
|
|
mode_bpp = dp->connector->display_info.bpc * num_components;
|
|
if (!mode_bpp)
|
|
mode_bpp = default_bpp;
|
|
|
|
mode_bpp = dp_panel_get_mode_bpp(dp_display->panel,
|
|
mode_bpp, mode_pclk_khz);
|
|
|
|
mode_rate_khz = mode_pclk_khz * mode_bpp;
|
|
supported_rate_khz = link_info->num_lanes * link_info->rate * 8;
|
|
|
|
if (mode_rate_khz > supported_rate_khz)
|
|
return MODE_BAD;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
int dp_display_get_modes(struct msm_dp *dp,
|
|
struct dp_display_mode *dp_mode)
|
|
{
|
|
struct dp_display_private *dp_display;
|
|
int ret = 0;
|
|
|
|
if (!dp) {
|
|
DRM_ERROR("invalid params\n");
|
|
return 0;
|
|
}
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
ret = dp_panel_get_modes(dp_display->panel,
|
|
dp->connector, dp_mode);
|
|
if (dp_mode->drm_mode.clock)
|
|
dp->max_pclk_khz = dp_mode->drm_mode.clock;
|
|
return ret;
|
|
}
|
|
|
|
bool dp_display_check_video_test(struct msm_dp *dp)
|
|
{
|
|
struct dp_display_private *dp_display;
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
return dp_display->panel->video_test;
|
|
}
|
|
|
|
int dp_display_get_test_bpp(struct msm_dp *dp)
|
|
{
|
|
struct dp_display_private *dp_display;
|
|
|
|
if (!dp) {
|
|
DRM_ERROR("invalid params\n");
|
|
return 0;
|
|
}
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
return dp_link_bit_depth_to_bpp(
|
|
dp_display->link->test_video.test_bit_depth);
|
|
}
|
|
|
|
static void dp_display_config_hpd(struct dp_display_private *dp)
|
|
{
|
|
|
|
dp_display_host_init(dp);
|
|
dp_catalog_ctrl_hpd_config(dp->catalog);
|
|
|
|
/* Enable interrupt first time
|
|
* we are leaving dp clocks on during disconnect
|
|
* and never disable interrupt
|
|
*/
|
|
enable_irq(dp->irq);
|
|
}
|
|
|
|
static int hpd_event_thread(void *data)
|
|
{
|
|
struct dp_display_private *dp_priv;
|
|
unsigned long flag;
|
|
struct dp_event *todo;
|
|
int timeout_mode = 0;
|
|
|
|
dp_priv = (struct dp_display_private *)data;
|
|
|
|
while (1) {
|
|
if (timeout_mode) {
|
|
wait_event_timeout(dp_priv->event_q,
|
|
(dp_priv->event_pndx == dp_priv->event_gndx),
|
|
EVENT_TIMEOUT);
|
|
} else {
|
|
wait_event_interruptible(dp_priv->event_q,
|
|
(dp_priv->event_pndx != dp_priv->event_gndx));
|
|
}
|
|
spin_lock_irqsave(&dp_priv->event_lock, flag);
|
|
todo = &dp_priv->event_list[dp_priv->event_gndx];
|
|
if (todo->delay) {
|
|
struct dp_event *todo_next;
|
|
|
|
dp_priv->event_gndx++;
|
|
dp_priv->event_gndx %= DP_EVENT_Q_MAX;
|
|
|
|
/* re enter delay event into q */
|
|
todo_next = &dp_priv->event_list[dp_priv->event_pndx++];
|
|
dp_priv->event_pndx %= DP_EVENT_Q_MAX;
|
|
todo_next->event_id = todo->event_id;
|
|
todo_next->data = todo->data;
|
|
todo_next->delay = todo->delay - 1;
|
|
|
|
/* clean up older event */
|
|
todo->event_id = EV_NO_EVENT;
|
|
todo->delay = 0;
|
|
|
|
/* switch to timeout mode */
|
|
timeout_mode = 1;
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
continue;
|
|
}
|
|
|
|
/* timeout with no events in q */
|
|
if (dp_priv->event_pndx == dp_priv->event_gndx) {
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
continue;
|
|
}
|
|
|
|
dp_priv->event_gndx++;
|
|
dp_priv->event_gndx %= DP_EVENT_Q_MAX;
|
|
timeout_mode = 0;
|
|
spin_unlock_irqrestore(&dp_priv->event_lock, flag);
|
|
|
|
switch (todo->event_id) {
|
|
case EV_HPD_INIT_SETUP:
|
|
dp_display_config_hpd(dp_priv);
|
|
break;
|
|
case EV_HPD_PLUG_INT:
|
|
dp_hpd_plug_handle(dp_priv, todo->data);
|
|
break;
|
|
case EV_HPD_UNPLUG_INT:
|
|
dp_hpd_unplug_handle(dp_priv, todo->data);
|
|
break;
|
|
case EV_IRQ_HPD_INT:
|
|
dp_irq_hpd_handle(dp_priv, todo->data);
|
|
break;
|
|
case EV_HPD_REPLUG_INT:
|
|
/* do nothing */
|
|
break;
|
|
case EV_USER_NOTIFICATION:
|
|
dp_display_send_hpd_notification(dp_priv,
|
|
todo->data);
|
|
break;
|
|
case EV_CONNECT_PENDING_TIMEOUT:
|
|
dp_connect_pending_timeout(dp_priv,
|
|
todo->data);
|
|
break;
|
|
case EV_DISCONNECT_PENDING_TIMEOUT:
|
|
dp_disconnect_pending_timeout(dp_priv,
|
|
todo->data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dp_hpd_event_setup(struct dp_display_private *dp_priv)
|
|
{
|
|
init_waitqueue_head(&dp_priv->event_q);
|
|
spin_lock_init(&dp_priv->event_lock);
|
|
|
|
kthread_run(hpd_event_thread, dp_priv, "dp_hpd_handler");
|
|
}
|
|
|
|
static irqreturn_t dp_display_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct dp_display_private *dp = dev_id;
|
|
irqreturn_t ret = IRQ_HANDLED;
|
|
u32 hpd_isr_status;
|
|
|
|
if (!dp) {
|
|
DRM_ERROR("invalid data\n");
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
hpd_isr_status = dp_catalog_hpd_get_intr_status(dp->catalog);
|
|
|
|
if (hpd_isr_status & 0x0F) {
|
|
/* hpd related interrupts */
|
|
if (hpd_isr_status & DP_DP_HPD_PLUG_INT_MASK ||
|
|
hpd_isr_status & DP_DP_HPD_REPLUG_INT_MASK) {
|
|
dp_add_event(dp, EV_HPD_PLUG_INT, 0, 0);
|
|
}
|
|
|
|
if (hpd_isr_status & DP_DP_IRQ_HPD_INT_MASK) {
|
|
/* delete connect pending event first */
|
|
dp_del_event(dp, EV_CONNECT_PENDING_TIMEOUT);
|
|
dp_add_event(dp, EV_IRQ_HPD_INT, 0, 0);
|
|
}
|
|
|
|
if (hpd_isr_status & DP_DP_HPD_REPLUG_INT_MASK)
|
|
dp_add_event(dp, EV_HPD_REPLUG_INT, 0, 0);
|
|
|
|
if (hpd_isr_status & DP_DP_HPD_UNPLUG_INT_MASK)
|
|
dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0);
|
|
}
|
|
|
|
/* DP controller isr */
|
|
dp_ctrl_isr(dp->ctrl);
|
|
|
|
/* DP aux isr */
|
|
dp_aux_isr(dp->aux);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int dp_display_request_irq(struct msm_dp *dp_display)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
|
|
if (!dp_display) {
|
|
DRM_ERROR("invalid input\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0);
|
|
if (dp->irq < 0) {
|
|
rc = dp->irq;
|
|
DRM_ERROR("failed to get irq: %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = devm_request_irq(&dp->pdev->dev, dp->irq,
|
|
dp_display_irq_handler,
|
|
IRQF_TRIGGER_HIGH, "dp_display_isr", dp);
|
|
if (rc < 0) {
|
|
DRM_ERROR("failed to request IRQ%u: %d\n",
|
|
dp->irq, rc);
|
|
return rc;
|
|
}
|
|
disable_irq(dp->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_probe(struct platform_device *pdev)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp;
|
|
|
|
if (!pdev || !pdev->dev.of_node) {
|
|
DRM_ERROR("pdev not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);
|
|
if (!dp)
|
|
return -ENOMEM;
|
|
|
|
dp->pdev = pdev;
|
|
dp->name = "drm_dp";
|
|
|
|
rc = dp_init_sub_modules(dp);
|
|
if (rc) {
|
|
DRM_ERROR("init sub module failed\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
mutex_init(&dp->event_mutex);
|
|
|
|
init_completion(&dp->resume_comp);
|
|
|
|
g_dp_display = &dp->dp_display;
|
|
|
|
/* Store DP audio handle inside DP display */
|
|
g_dp_display->dp_audio = dp->audio;
|
|
|
|
init_completion(&dp->audio_comp);
|
|
|
|
platform_set_drvdata(pdev, g_dp_display);
|
|
|
|
rc = component_add(&pdev->dev, &dp_display_comp_ops);
|
|
if (rc) {
|
|
DRM_ERROR("component add failed, rc=%d\n", rc);
|
|
dp_display_deinit_sub_modules(dp);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int dp_display_remove(struct platform_device *pdev)
|
|
{
|
|
struct dp_display_private *dp;
|
|
|
|
dp = container_of(g_dp_display,
|
|
struct dp_display_private, dp_display);
|
|
|
|
dp_display_deinit_sub_modules(dp);
|
|
|
|
component_del(&pdev->dev, &dp_display_comp_ops);
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_pm_resume(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int dp_pm_suspend(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct dp_display_private *dp = platform_get_drvdata(pdev);
|
|
|
|
if (!dp) {
|
|
DRM_ERROR("DP driver bind failed. Invalid driver data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
atomic_set(&dp->hpd_state, ST_SUSPENDED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_pm_prepare(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void dp_pm_complete(struct device *dev)
|
|
{
|
|
|
|
}
|
|
|
|
static const struct dev_pm_ops dp_pm_ops = {
|
|
.suspend = dp_pm_suspend,
|
|
.resume = dp_pm_resume,
|
|
.prepare = dp_pm_prepare,
|
|
.complete = dp_pm_complete,
|
|
};
|
|
|
|
static struct platform_driver dp_display_driver = {
|
|
.probe = dp_display_probe,
|
|
.remove = dp_display_remove,
|
|
.driver = {
|
|
.name = "msm-dp-display",
|
|
.of_match_table = dp_dt_match,
|
|
.suppress_bind_attrs = true,
|
|
.pm = &dp_pm_ops,
|
|
},
|
|
};
|
|
|
|
int __init msm_dp_register(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&dp_display_driver);
|
|
if (ret)
|
|
DRM_ERROR("Dp display driver register failed");
|
|
|
|
return ret;
|
|
}
|
|
|
|
void __exit msm_dp_unregister(void)
|
|
{
|
|
platform_driver_unregister(&dp_display_driver);
|
|
}
|
|
|
|
void msm_dp_irq_postinstall(struct msm_dp *dp_display)
|
|
{
|
|
struct dp_display_private *dp;
|
|
|
|
if (!dp_display)
|
|
return;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
|
|
dp_hpd_event_setup(dp);
|
|
|
|
dp_add_event(dp, EV_HPD_INIT_SETUP, 0, 100);
|
|
}
|
|
|
|
void msm_dp_debugfs_init(struct msm_dp *dp_display, struct drm_minor *minor)
|
|
{
|
|
struct dp_display_private *dp;
|
|
struct device *dev;
|
|
int rc;
|
|
|
|
dp = container_of(dp_display, struct dp_display_private, dp_display);
|
|
dev = &dp->pdev->dev;
|
|
|
|
dp->debug = dp_debug_get(dev, dp->panel, dp->usbpd,
|
|
dp->link, &dp->dp_display.connector,
|
|
minor);
|
|
if (IS_ERR(dp->debug)) {
|
|
rc = PTR_ERR(dp->debug);
|
|
DRM_ERROR("failed to initialize debug, rc = %d\n", rc);
|
|
dp->debug = NULL;
|
|
}
|
|
}
|
|
|
|
int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev,
|
|
struct drm_encoder *encoder)
|
|
{
|
|
struct msm_drm_private *priv;
|
|
int ret;
|
|
|
|
if (WARN_ON(!encoder) || WARN_ON(!dp_display) || WARN_ON(!dev))
|
|
return -EINVAL;
|
|
|
|
priv = dev->dev_private;
|
|
dp_display->drm_dev = dev;
|
|
|
|
ret = dp_display_request_irq(dp_display);
|
|
if (ret) {
|
|
DRM_ERROR("request_irq failed, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dp_display->encoder = encoder;
|
|
|
|
dp_display->connector = dp_drm_connector_init(dp_display);
|
|
if (IS_ERR(dp_display->connector)) {
|
|
ret = PTR_ERR(dp_display->connector);
|
|
DRM_DEV_ERROR(dev->dev,
|
|
"failed to create dp connector: %d\n", ret);
|
|
dp_display->connector = NULL;
|
|
return ret;
|
|
}
|
|
|
|
priv->connectors[priv->num_connectors++] = dp_display->connector;
|
|
return 0;
|
|
}
|
|
|
|
static int dp_display_wait4resume_done(struct dp_display_private *dp)
|
|
{
|
|
int ret = 0;
|
|
|
|
reinit_completion(&dp->resume_comp);
|
|
if (!wait_for_completion_timeout(&dp->resume_comp,
|
|
WAIT_FOR_RESUME_TIMEOUT_JIFFIES)) {
|
|
DRM_ERROR("wait4resume_done timedout\n");
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int msm_dp_display_enable(struct msm_dp *dp, struct drm_encoder *encoder)
|
|
{
|
|
int rc = 0;
|
|
struct dp_display_private *dp_display;
|
|
u32 state;
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
if (!dp_display->dp_mode.drm_mode.clock) {
|
|
DRM_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&dp_display->event_mutex);
|
|
|
|
rc = dp_display_set_mode(dp, &dp_display->dp_mode);
|
|
if (rc) {
|
|
DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc);
|
|
mutex_unlock(&dp_display->event_mutex);
|
|
return rc;
|
|
}
|
|
|
|
rc = dp_display_prepare(dp);
|
|
if (rc) {
|
|
DRM_ERROR("DP display prepare failed, rc=%d\n", rc);
|
|
mutex_unlock(&dp_display->event_mutex);
|
|
return rc;
|
|
}
|
|
|
|
state = atomic_read(&dp_display->hpd_state);
|
|
if (state == ST_SUSPENDED) {
|
|
/* start link training */
|
|
dp_add_event(dp_display, EV_HPD_PLUG_INT, 0, 0);
|
|
mutex_unlock(&dp_display->event_mutex);
|
|
|
|
/* wait until dp interface is up */
|
|
goto resume_done;
|
|
}
|
|
|
|
dp_display_enable(dp_display, 0);
|
|
|
|
rc = dp_display_post_enable(dp);
|
|
if (rc) {
|
|
DRM_ERROR("DP display post enable failed, rc=%d\n", rc);
|
|
dp_display_disable(dp_display, 0);
|
|
dp_display_unprepare(dp);
|
|
}
|
|
|
|
dp_del_event(dp_display, EV_CONNECT_PENDING_TIMEOUT);
|
|
|
|
if (state == ST_SUSPEND_PENDING)
|
|
dp_add_event(dp_display, EV_IRQ_HPD_INT, 0, 0);
|
|
|
|
/* completed connection */
|
|
atomic_set(&dp_display->hpd_state, ST_CONNECTED);
|
|
|
|
mutex_unlock(&dp_display->event_mutex);
|
|
|
|
return rc;
|
|
|
|
resume_done:
|
|
dp_display_wait4resume_done(dp_display);
|
|
return rc;
|
|
}
|
|
|
|
int msm_dp_display_pre_disable(struct msm_dp *dp, struct drm_encoder *encoder)
|
|
{
|
|
struct dp_display_private *dp_display;
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
dp_ctrl_push_idle(dp_display->ctrl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int msm_dp_display_disable(struct msm_dp *dp, struct drm_encoder *encoder)
|
|
{
|
|
int rc = 0;
|
|
u32 state;
|
|
struct dp_display_private *dp_display;
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
mutex_lock(&dp_display->event_mutex);
|
|
|
|
dp_display_disable(dp_display, 0);
|
|
|
|
rc = dp_display_unprepare(dp);
|
|
if (rc)
|
|
DRM_ERROR("DP display unprepare failed, rc=%d\n", rc);
|
|
|
|
dp_del_event(dp_display, EV_DISCONNECT_PENDING_TIMEOUT);
|
|
|
|
state = atomic_read(&dp_display->hpd_state);
|
|
if (state == ST_DISCONNECT_PENDING) {
|
|
/* completed disconnection */
|
|
atomic_set(&dp_display->hpd_state, ST_DISCONNECTED);
|
|
} else {
|
|
atomic_set(&dp_display->hpd_state, ST_SUSPEND_PENDING);
|
|
}
|
|
|
|
mutex_unlock(&dp_display->event_mutex);
|
|
return rc;
|
|
}
|
|
|
|
void msm_dp_display_mode_set(struct msm_dp *dp, struct drm_encoder *encoder,
|
|
struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct dp_display_private *dp_display;
|
|
|
|
dp_display = container_of(dp, struct dp_display_private, dp_display);
|
|
|
|
memset(&dp_display->dp_mode, 0x0, sizeof(struct dp_display_mode));
|
|
|
|
if (dp_display_check_video_test(dp))
|
|
dp_display->dp_mode.bpp = dp_display_get_test_bpp(dp);
|
|
else /* Default num_components per pixel = 3 */
|
|
dp_display->dp_mode.bpp = dp->connector->display_info.bpc * 3;
|
|
|
|
if (!dp_display->dp_mode.bpp)
|
|
dp_display->dp_mode.bpp = 24; /* Default bpp */
|
|
|
|
drm_mode_copy(&dp_display->dp_mode.drm_mode, adjusted_mode);
|
|
|
|
dp_display->dp_mode.v_active_low =
|
|
!!(dp_display->dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC);
|
|
|
|
dp_display->dp_mode.h_active_low =
|
|
!!(dp_display->dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC);
|
|
}
|