summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/msm/dp/dp_display.c
diff options
context:
space:
mode:
authorKuogee Hsieh <khsieh@codeaurora.org>2020-09-11 23:36:42 +0300
committerRob Clark <robdclark@chromium.org>2020-09-15 20:54:34 +0300
commit8ede2ecc3e5ee327923f6e3cfe52761ce73607d1 (patch)
tree2c36060e4f032d16e90e1480f790c359b06f5350 /drivers/gpu/drm/msm/dp/dp_display.c
parent220b856a3d3742a22831cb6cd94e48133a58d30e (diff)
downloadlinux-8ede2ecc3e5ee327923f6e3cfe52761ce73607d1.tar.xz
drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets
add event thread to execute events serially from event queue. Also timeout mode is supported which allow an event be deferred to be executed at later time. Both link and phy compliant tests had been done successfully. Changes in v2: -- Fix potential deadlock by removing redundant connect_mutex -- Check and enable link clock during modeset -- Drop unused code and fix function prototypes. -- set sink power to normal operation state (D0) before DPCD read Changes in v3: -- push idle pattern at main link before timing generator off -- add timeout handles for both connect and disconnect Changes in v4: -- add ST_SUSPEND_PENDING to handles suspend/modeset test operations -- clear dp phy aux interrupt status when ERR_DPPHY_AUX error -- send segment addr during edid read -- clear bpp depth before MISC register write Changes in v5: -- add ST_SUSPENDED to fix crash at resume Changes in v6: -- at msm_dp_display_enable() do not return until resume_done to avoid kms commit timeout Signed-off-by: Kuogee Hsieh <khsieh@codeaurora.org> Signed-off-by: Rob Clark <robdclark@chromium.org>
Diffstat (limited to 'drivers/gpu/drm/msm/dp/dp_display.c')
-rw-r--r--drivers/gpu/drm/msm/dp/dp_display.c828
1 files changed, 569 insertions, 259 deletions
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index 925c89720a16..18ca5eac8e20 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -9,6 +9,7 @@
#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"
@@ -28,6 +29,52 @@
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;
@@ -37,11 +84,9 @@ struct dp_display_private {
bool power_on;
bool hpd_irq_on;
bool audio_supported;
- atomic_t hpd_isr_status;
struct platform_device *pdev;
struct dentry *root;
- struct completion notification_comp;
struct dp_usbpd *usbpd;
struct dp_parser *parser;
@@ -52,12 +97,22 @@ struct dp_display_private {
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;
- struct delayed_work config_hpd_work;
+ /* 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;
};
static const struct of_device_id dp_dt_match[] = {
@@ -65,79 +120,58 @@ static const struct of_device_id dp_dt_match[] = {
{}
};
-static irqreturn_t dp_display_irq(int irq, void *dev_id)
+static int dp_add_event(struct dp_display_private *dp_priv, u32 event,
+ u32 data, u32 delay)
{
- 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 & DP_DP_HPD_INT_MASK) {
- atomic_set(&dp->hpd_isr_status, hpd_isr_status);
- ret = IRQ_WAKE_THREAD;
+ 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);
- /* DP controller isr */
- dp_ctrl_isr(dp->ctrl);
-
- /* DP aux isr */
- dp_aux_isr(dp->aux);
-
- return ret;
+ return 0;
}
-static irqreturn_t dp_display_hpd_isr_work(int irq, void *data)
+static int dp_del_event(struct dp_display_private *dp_priv, u32 event)
{
- struct dp_display_private *dp;
- struct dp_usbpd *hpd;
- u32 isr = 0;
-
- dp = (struct dp_display_private *)data;
- if (!dp)
- return IRQ_NONE;
-
- isr = atomic_read(&dp->hpd_isr_status);
-
- /* reset to default */
- atomic_set(&dp->hpd_isr_status, 0);
-
- hpd = dp->usbpd;
- if (!hpd)
- return IRQ_NONE;
-
- if (isr & DP_DP_HPD_PLUG_INT_MASK &&
- isr & DP_DP_HPD_STATE_STATUS_CONNECTED) {
- hpd->hpd_high = 1;
- dp->usbpd_cb.configure(&dp->pdev->dev);
- } else if (isr & DP_DP_HPD_UNPLUG_INT_MASK &&
- (isr & DP_DP_HPD_STATE_STATUS_MASK) ==
- DP_DP_HPD_STATE_STATUS_DISCONNECTED) {
-
- /* 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->usbpd_cb.disconnect(&dp->pdev->dev);
+ 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;
+ }
- dp_catalog_hpd_config_intr(dp->catalog,
- DP_DP_HPD_PLUG_INT_MASK | DP_DP_IRQ_HPD_INT_MASK,
- true);
+ 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 IRQ_HANDLED;
+ return 0;
}
static int dp_display_bind(struct device *dev, struct device *master,
@@ -178,7 +212,6 @@ static int dp_display_bind(struct device *dev, struct device *master,
DRM_ERROR("Power client create failed\n");
goto end;
}
-
end:
return rc;
}
@@ -237,11 +270,9 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp,
struct msm_drm_private *priv = dp->dp_display.drm_dev->dev_private;
struct msm_kms *kms = priv->kms;
- mutex_lock(&dp->dp_display.connect_mutex);
if ((hpd && dp->dp_display.is_connected) ||
(!hpd && !dp->dp_display.is_connected)) {
DRM_DEBUG_DP("HPD already %s\n", (hpd ? "on" : "off"));
- mutex_unlock(&dp->dp_display.connect_mutex);
return 0;
}
@@ -250,7 +281,6 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp,
dp->panel->video_test = false;
dp->dp_display.is_connected = hpd;
- reinit_completion(&dp->notification_comp);
if (dp->dp_display.is_connected && dp->dp_display.encoder
&& !encoder_mode_set
@@ -263,13 +293,6 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp,
dp_display_send_hpd_event(&dp->dp_display);
- if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2)) {
- pr_warn("%s timeout\n", hpd ? "connect" : "disconnect");
- mutex_unlock(&dp->dp_display.connect_mutex);
- return -EINVAL;
- }
-
- mutex_unlock(&dp->dp_display.connect_mutex);
return 0;
}
@@ -278,23 +301,14 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp)
int rc = 0;
struct edid *edid;
- if (dp->link->psm_enabled)
- goto notify;
-
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 notify;
+ goto end;
dp_link_process_request(dp->link);
- if (dp_display_is_sink_count_zero(dp)) {
- DRM_DEBUG_DP("no downstream devices connected\n");
- rc = -EINVAL;
- goto end;
- }
-
edid = dp->panel->edid;
dp->audio_supported = drm_detect_monitor_audio(edid);
@@ -302,8 +316,15 @@ static int dp_display_process_hpd_high(struct dp_display_private *dp)
dp->dp_display.max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ;
dp->dp_display.max_dp_lanes = dp->parser->max_dp_lanes;
-notify:
- dp_display_send_hpd_notification(dp, true);
+
+ 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;
@@ -327,23 +348,6 @@ static void dp_display_host_init(struct dp_display_private *dp)
dp->core_initialized = true;
}
-static void dp_display_host_deinit(struct dp_display_private *dp)
-{
- if (!dp->core_initialized) {
- DRM_DEBUG_DP("DP core already off\n");
- return;
- }
-
- dp->core_initialized = false;
-}
-
-static void dp_display_process_hpd_low(struct dp_display_private *dp)
-{
- dp_display_send_hpd_notification(dp, false);
-
- dp_aux_deinit(dp->aux);
-}
-
static int dp_display_usbpd_configure_cb(struct device *dev)
{
int rc = 0;
@@ -364,18 +368,16 @@ static int dp_display_usbpd_configure_cb(struct device *dev)
dp_display_host_init(dp);
- if (dp->usbpd->hpd_high)
- dp_display_process_hpd_high(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 void dp_display_clean(struct dp_display_private *dp)
-{
- dp_ctrl_push_idle(dp->ctrl);
- dp_ctrl_off(dp->ctrl);
-}
-
static int dp_display_usbpd_disconnect_cb(struct device *dev)
{
int rc = 0;
@@ -383,16 +385,8 @@ static int dp_display_usbpd_disconnect_cb(struct device *dev)
dp = dev_get_drvdata(dev);
- rc = dp_display_send_hpd_notification(dp, false);
+ dp_add_event(dp, EV_USER_NOTIFICATION, false, 0);
- /* if cable is disconnected, reset psm_enabled flag */
- if (!dp->usbpd->alt_mode_cfg_done)
- dp->link->psm_enabled = false;
-
- if ((rc < 0) && dp->power_on)
- dp_display_clean(dp);
-
- dp_display_host_deinit(dp);
return rc;
}
@@ -407,11 +401,14 @@ static void dp_display_handle_video_request(struct dp_display_private *dp)
}
}
-static int dp_display_handle_hpd_irq(struct dp_display_private *dp)
+static int dp_display_handle_irq_hpd(struct dp_display_private *dp)
{
- if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) {
- dp_display_send_hpd_notification(dp, false);
+ 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;
@@ -422,7 +419,8 @@ static int dp_display_handle_hpd_irq(struct dp_display_private *dp)
dp_ctrl_handle_sink_request(dp->ctrl);
- dp_display_handle_video_request(dp);
+ if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN)
+ dp_display_handle_video_request(dp);
return 0;
}
@@ -443,27 +441,174 @@ static int dp_display_usbpd_attention_cb(struct device *dev)
return -ENODEV;
}
- if (dp->usbpd->hpd_irq) {
- dp->hpd_irq_on = true;
+ /* check for any test request issued by sink */
+ rc = dp_link_process_request(dp->link);
+ if (!rc)
+ dp_display_handle_irq_hpd(dp);
- rc = dp_link_process_request(dp->link);
- /* check for any test request issued by sink */
- if (!rc)
- dp_display_handle_hpd_irq(dp);
+ return rc;
+}
- dp->hpd_irq_on = false;
- goto end;
+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 (!dp->usbpd->hpd_high) {
- dp_display_process_hpd_low(dp);
- goto end;
+ if (state == ST_CONNECT_PENDING || state == ST_CONNECTED) {
+ mutex_unlock(&dp->event_mutex);
+ return 0;
}
- if (dp->usbpd->alt_mode_cfg_done)
- dp_display_process_hpd_high(dp);
-end:
- return rc;
+ 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 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);
+
+ 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)
@@ -599,106 +744,39 @@ static int dp_display_prepare(struct msm_dp *dp)
return 0;
}
-static void dp_display_dump(struct msm_dp *dp_display)
-{
- struct dp_display_private *dp;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
-
- dp_panel_dump_regs(dp->panel);
-}
-
-static int dp_display_enable(struct msm_dp *dp_display)
+static int dp_display_enable(struct dp_display_private *dp, u32 data)
{
int rc = 0;
- struct dp_display_private *dp;
- bool dump_dp = false;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
if (dp->power_on) {
DRM_DEBUG_DP("Link already setup, return\n");
return 0;
}
- rc = dp_ctrl_on(dp->ctrl);
+ rc = dp_ctrl_on_stream(dp->ctrl);
if (!rc)
dp->power_on = true;
- if (dump_dp != false)
- dp_display_dump(dp_display);
-
+ /* 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;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
-
- complete_all(&dp->notification_comp);
- return 0;
-}
-
-static int dp_display_pre_disable(struct msm_dp *dp_display)
-{
- struct dp_display_private *dp;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
-
- if (dp->usbpd->alt_mode_cfg_done)
- dp_link_psm_config(dp->link, &dp->panel->link_info, true);
-
- dp_ctrl_push_idle(dp->ctrl);
return 0;
}
-static int dp_display_disable(struct msm_dp *dp_display)
+static int dp_display_disable(struct dp_display_private *dp, u32 data)
{
- struct dp_display_private *dp;
-
- dp = container_of(dp_display, struct dp_display_private, dp_display);
-
- if (!dp->power_on || !dp->core_initialized)
+ if (!dp->power_on)
return -EINVAL;
dp_ctrl_off(dp->ctrl);
- dp->power_on = false;
-
- complete_all(&dp->notification_comp);
- return 0;
-}
-
-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;
- }
+ dp->core_initialized = false;
- rc = devm_request_threaded_irq(&dp->pdev->dev, dp->irq,
- dp_display_irq, dp_display_hpd_isr_work,
- 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);
+ dp->power_on = false;
return 0;
}
@@ -783,6 +861,192 @@ int dp_display_get_test_bpp(struct msm_dp *dp)
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_timeout(dp_priv->event_q,
+ (dp_priv->event_pndx != dp_priv->event_gndx),
+ EVENT_TIMEOUT);
+ }
+ 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;
@@ -797,8 +1061,6 @@ static int dp_display_probe(struct platform_device *pdev)
if (!dp)
return -ENOMEM;
- init_completion(&dp->notification_comp);
-
dp->pdev = pdev;
dp->name = "drm_dp";
@@ -810,7 +1072,8 @@ static int dp_display_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, dp);
- mutex_init(&dp->dp_display.connect_mutex);
+ mutex_init(&dp->event_mutex);
+ init_completion(&dp->resume_comp);
g_dp_display = &dp->dp_display;
rc = component_add(&pdev->dev, &dp_display_comp_ops);
@@ -843,6 +1106,16 @@ static int dp_pm_resume(struct device *dev)
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;
}
@@ -890,26 +1163,6 @@ void __exit msm_dp_unregister(void)
platform_driver_unregister(&dp_display_driver);
}
-static void dp_display_config_hpd_work(struct work_struct *work)
-{
- struct dp_display_private *dp;
- struct delayed_work *dw = to_delayed_work(work);
-
- dp = container_of(dw, struct dp_display_private, config_hpd_work);
-
- dp_display_host_init(dp);
- dp_catalog_ctrl_hpd_config(dp->catalog);
-
- /* set default to 0 */
- atomic_set(&dp->hpd_isr_status, 0);
-
- /* Enable interrupt first time
- * we are leaving dp clocks on during disconnect
- * and never disable interrupt
- */
- enable_irq(dp->irq);
-}
-
void msm_dp_irq_postinstall(struct msm_dp *dp_display)
{
struct dp_display_private *dp;
@@ -919,8 +1172,9 @@ void msm_dp_irq_postinstall(struct msm_dp *dp_display)
dp = container_of(dp_display, struct dp_display_private, dp_display);
- INIT_DELAYED_WORK(&dp->config_hpd_work, dp_display_config_hpd_work);
- queue_delayed_work(system_wq, &dp->config_hpd_work, HZ * 10);
+ dp_hpd_event_setup(dp);
+
+ dp_add_event(dp, EV_HPD_INIT_SETUP, 0, 100);
}
int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev,
@@ -956,10 +1210,24 @@ int msm_dp_modeset_init(struct msm_dp *dp_display, struct drm_device *dev,
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) {
@@ -967,54 +1235,96 @@ int msm_dp_display_enable(struct msm_dp *dp, struct drm_encoder *encoder)
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;
}
- rc = dp_display_enable(dp);
- if (rc) {
- DRM_ERROR("DP display enable failed, rc=%d\n", rc);
- dp_display_unprepare(dp);
- 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);
+ 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;
- rc = dp_display_pre_disable(dp);
- if (rc) {
- DRM_ERROR("DP display pre disable failed, rc=%d\n", rc);
- return rc;
- }
+ dp_display = container_of(dp, struct dp_display_private, dp_display);
- rc = dp_display_disable(dp);
- if (rc) {
- DRM_ERROR("DP display disable failed, rc=%d\n", rc);
- return rc;
- }
+ 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;
}