video: rockchip: dp: merge the dp driver from chrome and fix error of suspend and...
authorwenping.zhang <wenping.zhang@rock-chips.com>
Thu, 29 Sep 2016 10:59:07 +0000 (18:59 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Thu, 20 Oct 2016 02:48:30 +0000 (10:48 +0800)
Add usb super speed,support dp 4 lanes and usb2.0 function.
Also add power domain control function and optimize the logic
of dp recognizing flow.And also change the logic of dp suspend,
the clock of dp will be disabled after early suspend, and enabled
after early resume by trigger a hotplug event.

Change-Id: I917d0d34b0909373ba037c62b3582893d7dce00b
Signed-off-by: wenping.zhang <wenping.zhang@rock-chips.com>
drivers/video/rockchip/dp/cdn-dp-reg.c
drivers/video/rockchip/dp/cdn-dp-reg.h
drivers/video/rockchip/dp/rockchip_dp.c
drivers/video/rockchip/dp/rockchip_dp.h
drivers/video/rockchip/dp/rockchip_dp_core.c
drivers/video/rockchip/dp/rockchip_dp_core.h

index a2aa87350d1b1a0bb7f4262be54f95ab577a2c1a..00d7942067d4d38cda5b7b0dac8cd188010ee5a8 100644 (file)
@@ -139,7 +139,7 @@ static int cdn_dp_mailbox_validate_receive(struct cdn_dp_device *dp,
            req_size != mbox_size) {
                /*
                 * If the message in mailbox is not what we want, we need to
-                * clear the mailbox by read.
+                * clear the mailbox by reading its contents.
                 */
                for (i = 0; i < mbox_size; i++)
                        if (cdn_dp_mailbox_read(dp, 0) < 0)
@@ -448,37 +448,39 @@ int cdn_dp_get_edid_block(void *data, u8 *edid,
                          unsigned int block, size_t length)
 {
        struct cdn_dp_device *dp = data;
-       u8 msg[2], reg[2];
+       u8 msg[2], reg[2], i;
        int ret;
 
-       msg[0] = block / 2;
-       msg[1] = block % 2;
+       for (i = 0; i < 4; i++) {
+               msg[0] = block / 2;
+               msg[1] = block % 2;
 
-       ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
-                                 sizeof(msg), msg);
-       if (ret)
-               goto err_get_edid;
+               ret = cdn_dp_mailbox_send(dp, MB_MODULE_ID_DP_TX, DPTX_GET_EDID,
+                                         sizeof(msg), msg);
+               if (ret)
+                       continue;
 
-       ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
-                                             DPTX_GET_EDID,
-                                             sizeof(reg) + length);
-       if (ret)
-               goto err_get_edid;
+               ret = cdn_dp_mailbox_validate_receive(dp, MB_MODULE_ID_DP_TX,
+                                                     DPTX_GET_EDID,
+                                                     sizeof(reg) + length);
+               if (ret)
+                       continue;
 
-       ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
-       if (ret)
-               goto err_get_edid;
+               ret = cdn_dp_mailbox_read_receive(dp, reg, sizeof(reg));
+               if (ret)
+                       continue;
 
-       ret = cdn_dp_mailbox_read_receive(dp, edid, length);
-       if (ret)
-               goto err_get_edid;
+               ret = cdn_dp_mailbox_read_receive(dp, edid, length);
+               if (ret)
+                       continue;
 
-       if (reg[0] != length || reg[1] != block / 2)
-               ret = -EINVAL;
+               if (reg[0] == length && reg[1] == block / 2)
+                       break;
+       }
 
-err_get_edid:
        if (ret)
                dev_err(dp->dev, "get block[%d] edid failed: %d\n", block, ret);
+
        return ret;
 }
 
index 61937b1eb814faa7cd32ec882052e9789a96c061..29a45714e50bf524b3056c0c361707982e1d4a63 100644 (file)
@@ -15,6 +15,8 @@
 #ifndef _CDN_DP_REG_H
 #define _CDN_DP_REG_H
 
+#include <linux/wakelock.h>
+#include <linux/mutex.h>
 #include <linux/bitops.h>
 #include <drm/drmP.h>
 #include <drm/drm_crtc_helper.h>
@@ -524,12 +526,16 @@ struct cdn_dp_device {
        u32 fw_wait;
        bool fw_loaded;
        bool fw_actived;
+       bool fw_clk_enabled;
        void __iomem *regs;
        struct regmap *grf;
+       struct clk *grf_clk;
        struct clk *core_clk;
        struct clk *pclk;
        struct clk *spdif_clk;
        struct reset_control *spdif_rst;
+       struct reset_control *dptx_rst;
+       struct reset_control *apb_rst;
        struct audio_info audio_info;
        struct video_info video_info;
        struct drm_dp_link link;
@@ -539,7 +545,11 @@ struct cdn_dp_device {
        u8 dpcd[DP_RECEIVER_CAP_SIZE];
        enum drm_connector_status hpd_status;
        int dpms_mode;
+       bool suspend;
        bool sink_has_audio;
+
+       struct mutex lock;
+       struct wake_lock        wake_lock;
 };
 
 void cdn_dp_clock_reset(struct cdn_dp_device *dp);
index 18979b4481ff5d0011c9462ecbf2ff1fa87d6d17..552785dbea7b88ac737399002efc23093fb980c0 100644 (file)
@@ -4,14 +4,16 @@
 static int rockchip_dp_removed(struct hdmi *hdmi_drv)
 {
        struct dp_dev *dp_dev = hdmi_drv->property->priv;
+       int ret;
 
-       cdn_dp_encoder_disable(dp_dev->dp);
+       ret = cdn_dp_encoder_disable(dp_dev->dp);
+       if (ret)
+               dev_warn(hdmi_drv->dev, "dp has been removed twice:%d\n", ret);
        return HDMI_ERROR_SUCCESS;
 }
 
 static int rockchip_dp_enable(struct hdmi *hdmi_drv)
 {
-       hdmi_submit_work(hdmi_drv, HDMI_HPD_CHANGE, 10, 0);
        return 0;
 }
 
@@ -23,11 +25,21 @@ static int rockchip_dp_disable(struct hdmi *hdmi_drv)
 static int rockchip_dp_control_output(struct hdmi *hdmi_drv, int enable)
 {
        struct dp_dev *dp_dev = hdmi_drv->property->priv;
+       int ret;
 
-       if (enable == HDMI_AV_UNMUTE)
-               cdn_dp_encoder_enable(dp_dev->dp);
-       else if (enable & HDMI_VIDEO_MUTE)
-               cdn_dp_encoder_disable(dp_dev->dp);
+       if (enable == HDMI_AV_UNMUTE) {
+               if (!dp_dev->early_suspended) {
+                       ret = cdn_dp_encoder_enable(dp_dev->dp);
+                       if (ret) {
+                               dev_err(hdmi_drv->dev,
+                                       "dp enable video and audio output error:%d\n", ret);
+                               return HDMI_ERROR_FALSE;
+                       }
+               } else
+                       dev_warn(hdmi_drv->dev,
+                               "don't output video and audio after dp has been suspended!\n");
+       } else if (enable & HDMI_VIDEO_MUTE)
+               dev_dbg(hdmi_drv->dev, "dp disable video and audio output !\n");
 
        return 0;
 }
@@ -44,6 +56,13 @@ static int rockchip_dp_config_video(struct hdmi *hdmi_drv,
        struct hdmi_video_timing *timing = NULL;
        struct dp_dev *dp_dev = hdmi_drv->property->priv;
        struct dp_disp_info *disp_info = &dp_dev->disp_info;
+       int ret;
+
+       if (dp_dev->early_suspended) {
+               dev_warn(hdmi_drv->dev,
+                       "don't config video after dp has been suspended!\n");
+               return 0;
+       }
 
        timing = (struct hdmi_video_timing *)hdmi_vic2timing(vpara->vic);
        if (!timing) {
@@ -57,7 +76,12 @@ static int rockchip_dp_config_video(struct hdmi *hdmi_drv,
        disp_info->vsync_polarity = 1;
        disp_info->hsync_polarity = 1;
 
-       cdn_dp_encoder_mode_set(dp_dev->dp, disp_info);
+       ret = cdn_dp_encoder_mode_set(dp_dev->dp, disp_info);
+       if (ret) {
+               dev_err(hdmi_drv->dev, "dp config video mode error:%d\n", ret);
+               return HDMI_ERROR_FALSE;
+       }
+
        return 0;
 }
 
@@ -72,7 +96,7 @@ static int rockchip_dp_detect_hotplug(struct hdmi *hdmi_drv)
 
 static int rockchip_dp_read_edid(struct hdmi *hdmi_drv, int block, u8 *buf)
 {
-       int ret;
+       int ret = 0;
        struct dp_dev *dp_dev = hdmi_drv->property->priv;
 
        if (dp_dev->lanes == 4)
@@ -82,8 +106,9 @@ static int rockchip_dp_read_edid(struct hdmi *hdmi_drv, int block, u8 *buf)
 
        ret = cdn_dp_get_edid(dp_dev->dp, buf, block);
        if (ret)
-               return ret;
-       return 0;
+               dev_err(hdmi_drv->dev, "dp config video mode error:%d\n", ret);
+
+       return ret;
 }
 
 static int rockchip_dp_insert(struct hdmi *hdmi_drv)
@@ -120,17 +145,25 @@ void hpd_change(struct device *dev, int lanes)
 
        if (lanes)
                dp_dev->lanes = lanes;
-       if (dp_dev->hdmi->enable)
-               hdmi_submit_work(dp_dev->hdmi, HDMI_HPD_CHANGE, 20, 0);
+
+       if (dp_dev->hdmi->enable) {
+               if (dp_dev->early_suspended)
+                       dev_warn(dp_dev->hdmi->dev,
+                               "hpd triggered after early suspend, so don't send hpd change event !\n");
+               else
+                       hdmi_submit_work(dp_dev->hdmi, HDMI_HPD_CHANGE, 10, 0);
+       }
 }
 
 static void rockchip_dp_early_suspend(struct dp_dev *dp_dev)
 {
        hdmi_submit_work(dp_dev->hdmi, HDMI_SUSPEND_CTL, 0, 1);
+       cdn_dp_suspend(dp_dev->dp);
 }
 
 static void rockchip_dp_early_resume(struct dp_dev *dp_dev)
 {
+       cdn_dp_resume(dp_dev->dp);
        hdmi_submit_work(dp_dev->hdmi, HDMI_RESUME_CTL, 0, 0);
 }
 
@@ -147,15 +180,19 @@ static int rockchip_dp_fb_event_notify(struct notifier_block *self,
                case FB_BLANK_UNBLANK:
                        break;
                default:
-                       if (!dp_dev->hdmi->sleep)
+                       if (!dp_dev->hdmi->sleep) {
                                rockchip_dp_early_suspend(dp_dev);
+                               dp_dev->early_suspended = true;
+                       }
                        break;
                }
        } else if (action == FB_EVENT_BLANK) {
                switch (blank_mode) {
                case FB_BLANK_UNBLANK:
-                       if (dp_dev->hdmi->sleep)
+                       if (dp_dev->hdmi->sleep) {
+                               dp_dev->early_suspended = false;
                                rockchip_dp_early_resume(dp_dev);
+                       }
                        break;
                default:
                        break;
@@ -212,6 +249,7 @@ int cdn_dp_fb_register(struct platform_device *pdev, void *dp)
                                                rk_dp_ops);
        dp_dev->hdmi->dev = dev;
        dp_dev->hdmi->enable = 1;
+       dp_dev->early_suspended = 0;
        dp_dev->hdmi->sleep = 0;
        dp_dev->hdmi->colormode = HDMI_COLOR_RGB_0_255;
        dp_dev->dp = dp;
index 7714ff06a79d16c5df7154d4b27a2a00d6bbbb63..175f03aeb421a430028150c8a08c84970df6efd5 100644 (file)
@@ -5,11 +5,13 @@
 #include "rockchip_dp_core.h"
 
 int cdn_dp_get_edid(void *dp, u8 *edid, unsigned int block);
-void cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info);
-void cdn_dp_encoder_enable(void *dp);
+int cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info);
+int cdn_dp_encoder_enable(void *dp);
 int cdn_dp_connector_detect(void *dp);
-void cdn_dp_encoder_disable(void *dp);
+int cdn_dp_encoder_disable(void *dp);
 int cdn_dp_audio_hw_params(void *dp);
 int cdn_dp_audio_digital_mute(void *dp, bool enable);
+int cdn_dp_resume(void *dp_dev);
+int cdn_dp_suspend(void *dp_dev);
 
 #endif
index 7da46859fcc3282e466d2be28742dd8a7a8c1729..8c6ad8945f130d4e821b296962fe162cf83d8d79 100644 (file)
@@ -41,15 +41,54 @@ static const struct of_device_id cdn_dp_dt_ids[] = {
 
 MODULE_DEVICE_TABLE(of, cdn_dp_dt_ids);
 
-static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+static int cdn_dp_grf_write(struct cdn_dp_device *dp,
+                           unsigned int reg, unsigned int val)
 {
        int ret;
+
+       ret = clk_prepare_enable(dp->grf_clk);
+       if (ret) {
+               dev_err(dp->dev, "Failed to prepare_enable grf clock\n");
+               return ret;
+       }
+
+       ret = regmap_write(dp->grf, reg, val);
+       if (ret) {
+               dev_err(dp->dev, "Could not write to GRF: %d\n", ret);
+               return ret;
+       }
+
+       clk_disable_unprepare(dp->grf_clk);
+
+       return 0;
+}
+
+static int cdn_dp_set_fw_rate(struct cdn_dp_device *dp)
+{
        u32 rate;
 
+       if (!dp->fw_clk_enabled) {
+               rate = clk_get_rate(dp->core_clk);
+               if (rate < 0) {
+                       dev_err(dp->dev, "get clk rate failed: %d\n", rate);
+                       return rate;
+               }
+               cdn_dp_set_fw_clk(dp, rate);
+               cdn_dp_clock_reset(dp);
+               dp->fw_clk_enabled = true;
+       }
+
+       return 0;
+}
+
+static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
+{
+       int ret;
+
        ret = clk_prepare_enable(dp->pclk);
        if (ret < 0) {
                dev_err(dp->dev, "cannot enable dp pclk %d\n", ret);
-               goto err_pclk;
+               goto runtime_get_pm;
        }
 
        ret = clk_prepare_enable(dp->core_clk);
@@ -58,13 +97,17 @@ static int cdn_dp_clk_enable(struct cdn_dp_device *dp)
                goto err_core_clk;
        }
 
-       rate = clk_get_rate(dp->core_clk);
-       if (rate < 0) {
-               dev_err(dp->dev, "get clk rate failed: %d\n", rate);
-               goto err_set_rate;
+       ret = pm_runtime_get_sync(dp->dev);
+       if (ret < 0) {
+               dev_err(dp->dev, "cannot get pm runtime %d\n", ret);
+               return ret;
        }
 
-       cdn_dp_set_fw_clk(dp, rate);
+       ret = cdn_dp_set_fw_rate(dp);
+       if (ret < 0) {
+               dev_err(dp->dev, "cannot get pm runtime %d\n", ret);
+               goto err_set_rate;
+       }
 
        return 0;
 
@@ -72,33 +115,64 @@ err_set_rate:
        clk_disable_unprepare(dp->core_clk);
 err_core_clk:
        clk_disable_unprepare(dp->pclk);
-err_pclk:
+runtime_get_pm:
+       pm_runtime_put_sync(dp->dev);
        return ret;
 }
 
+static void cdn_dp_clk_disable(struct cdn_dp_device *dp)
+{
+       pm_runtime_put_sync(dp->dev);
+       clk_disable_unprepare(dp->pclk);
+       clk_disable_unprepare(dp->core_clk);
+}
+
 int cdn_dp_get_edid(void *dp, u8 *buf, int block)
 {
        int ret;
        struct cdn_dp_device *dp_dev = dp;
 
+       mutex_lock(&dp_dev->lock);
        ret = cdn_dp_get_edid_block(dp_dev, buf, block, EDID_BLOCK_SIZE);
+       mutex_unlock(&dp_dev->lock);
+
        return ret;
 }
 
 int cdn_dp_connector_detect(void *dp)
 {
        struct cdn_dp_device *dp_dev = dp;
+       bool ret = false;
 
+       mutex_lock(&dp_dev->lock);
        if (dp_dev->hpd_status == connector_status_connected)
-               return true;
-       return false;
+               ret = true;
+       mutex_unlock(&dp_dev->lock);
+
+       return ret;
 }
 
-void cdn_dp_encoder_disable(void *dp)
+int cdn_dp_encoder_disable(void *dp)
 {
        struct cdn_dp_device *dp_dev = dp;
+       int ret = 0;
 
-       dp_dev->dpms_mode = DRM_MODE_DPMS_OFF;
+       mutex_lock(&dp_dev->lock);
+       if (dp_dev->hpd_status == connector_status_disconnected) {
+               dp_dev->dpms_mode = DRM_MODE_DPMS_OFF;
+               mutex_unlock(&dp_dev->lock);
+               return ret;
+       }
+
+       if (dp_dev->dpms_mode == DRM_MODE_DPMS_ON) {
+               dp_dev->dpms_mode = DRM_MODE_DPMS_OFF;
+       } else{
+               dev_warn(dp_dev->dev, "wrong dpms status,dp encoder has already been disabled\n");
+               ret = -1;
+       }
+       mutex_unlock(&dp_dev->lock);
+
+       return ret;
 }
 
 static void cdn_dp_commit(struct cdn_dp_device *dp)
@@ -150,7 +224,7 @@ static void cdn_dp_commit(struct cdn_dp_device *dp)
        dp->dpms_mode = DRM_MODE_DPMS_ON;
 }
 
-void cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info)
+int cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info)
 {
        int ret, val;
        struct cdn_dp_device *dp_dev = dp;
@@ -158,6 +232,7 @@ void cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info)
        struct drm_display_mode disp_mode;
        struct fb_videomode *mode = disp_info->mode;
 
+       mutex_lock(&dp_dev->lock);
        disp_mode.clock = mode->pixclock / 1000;
        disp_mode.hdisplay = mode->xres;
        disp_mode.hsync_start = disp_mode.hdisplay + mode->right_margin;
@@ -192,19 +267,35 @@ void cdn_dp_encoder_mode_set(void *dp, struct dp_disp_info *disp_info)
        else
                val = DP_SEL_VOP_LIT << 16;
 
-       ret = regmap_write(dp_dev->grf, DP_VOP_SEL, val);
-       if (ret != 0)
+       ret = cdn_dp_grf_write(dp, GRF_SOC_CON9, val);
+       if (ret != 0) {
                dev_err(dp_dev->dev, "Could not write to GRF: %d\n", ret);
-
+               mutex_unlock(&dp_dev->lock);
+               return ret;
+       }
        memcpy(&dp_dev->mode, &disp_mode, sizeof(disp_mode));
+
+       mutex_unlock(&dp_dev->lock);
+
+       return 0;
 }
 
-void cdn_dp_encoder_enable(void *dp)
+int cdn_dp_encoder_enable(void *dp)
 {
        struct cdn_dp_device *dp_dev = dp;
+       int ret = 0;
+
+       mutex_lock(&dp_dev->lock);
 
-       if (dp_dev->dpms_mode != DRM_MODE_DPMS_ON)
+       if (dp_dev->dpms_mode == DRM_MODE_DPMS_OFF) {
                cdn_dp_commit(dp_dev);
+       } else {
+               dev_warn(dp_dev->dev, "wrong dpms status,dp encoder has already been enabled\n");
+               ret = -1;
+       }
+       mutex_unlock(&dp_dev->lock);
+
+       return ret;
 }
 
 static int cdn_dp_firmware_init(struct cdn_dp_device *dp)
@@ -244,7 +335,6 @@ static int cdn_dp_init(struct cdn_dp_device *dp)
        struct device_node *np = dev->of_node;
        struct platform_device *pdev = to_platform_device(dev);
        struct resource *res;
-       int ret;
 
        dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
        if (IS_ERR(dp->grf)) {
@@ -277,20 +367,44 @@ static int cdn_dp_init(struct cdn_dp_device *dp)
                return PTR_ERR(dp->spdif_clk);
        }
 
+       dp->grf_clk = devm_clk_get(dev, "grf");
+       if (IS_ERR(dp->grf_clk)) {
+               dev_err(dev, "cannot get grf clk\n");
+               return PTR_ERR(dp->grf_clk);
+       }
+
        dp->spdif_rst = devm_reset_control_get(dev, "spdif");
        if (IS_ERR(dp->spdif_rst)) {
                dev_err(dev, "no spdif reset control found\n");
                return PTR_ERR(dp->spdif_rst);
        }
 
+       dp->dptx_rst = devm_reset_control_get(dev, "dptx");
+       if (IS_ERR(dp->dptx_rst)) {
+               dev_err(dev, "no uphy reset control found\n");
+               return PTR_ERR(dp->dptx_rst);
+       }
+
+       dp->apb_rst = devm_reset_control_get(dev, "apb");
+       if (IS_ERR(dp->apb_rst)) {
+               dev_err(dev, "no apb reset control found\n");
+               return PTR_ERR(dp->apb_rst);
+       }
+
        dp->dpms_mode = DRM_MODE_DPMS_OFF;
+       dp->fw_clk_enabled = false;
 
-       ret = cdn_dp_clk_enable(dp);
-       if (ret < 0)
-               return ret;
+       pm_runtime_enable(dev);
 
-       cdn_dp_clock_reset(dp);
+       reset_control_assert(dp->dptx_rst);
+       udelay(15);
+       reset_control_deassert(dp->dptx_rst);
+       reset_control_assert(dp->apb_rst);
+       udelay(15);
+       reset_control_deassert(dp->apb_rst);
 
+       mutex_init(&dp->lock);
+       wake_lock_init(&dp->wake_lock, WAKE_LOCK_SUSPEND, "cdn_dp_fb");
        return 0;
 }
 
@@ -380,176 +494,294 @@ static int cdn_dp_audio_codec_init(struct cdn_dp_device *dp,
 static int cdn_dp_get_cap_lanes(struct cdn_dp_device *dp,
                                struct extcon_dev *edev)
 {
-       bool dfp, dptx;
-       u8 lanes;
+       union extcon_property_value property;
+       u8 lanes = 0;
+       int dptx;
 
-       dfp = extcon_get_state(edev, EXTCON_USB_HOST);
-       dptx = extcon_get_state(edev, EXTCON_DISP_DP);
+       if (dp->suspend)
+               return 0;
 
-       if (dfp && dptx)
-               lanes = 2;
-       else if (dptx)
-               lanes = 4;
-       else
-               lanes = 0;
+       dptx = extcon_get_state(edev, EXTCON_DISP_DP);
+       if (dptx > 0) {
+               extcon_get_property(edev, EXTCON_DISP_DP,
+                                   EXTCON_PROP_USB_SS, &property);
+               if (property.intval)
+                       lanes = 2;
+               else
+                       lanes = 4;
+       }
 
        return lanes;
 }
 
-static int cdn_dp_pd_event(struct notifier_block *nb,
-                          unsigned long event, void *priv)
+static int cdn_dp_get_dpcd(struct cdn_dp_device *dp, struct cdn_dp_port *port)
 {
-       struct cdn_dp_port *port;
+       u8 sink_count;
+       int i, ret;
+       int retry = 5;
 
-       port = container_of(nb, struct cdn_dp_port, event_nb);
-       schedule_delayed_work(&port->event_wq, 0);
-       return 0;
+       /*
+        * Native read with retry for link status and receiver capability reads
+        * for cases where the sink may still not be ready.
+        *
+        * Sinks are *supposed* to come up within 1ms from an off state, but
+        * some DOCKs need about 5 seconds to power up, so read the dpcd every
+        * 100ms, if can not get a good dpcd in 10 seconds, give up.
+        */
+       for (i = 0; i < 100; i++) {
+               ret = cdn_dp_dpcd_read(dp, DP_SINK_COUNT,
+                                      &sink_count, 1);
+               if (!ret) {
+                       dev_dbg(dp->dev, "get dpcd success!\n");
+
+                       sink_count = DP_GET_SINK_COUNT(sink_count);
+                       if (!sink_count) {
+                               if (retry-- <= 0) {
+                                       dev_err(dp->dev, "sink cout is 0, no sink device!\n");
+                                       return -ENODEV;
+                               }
+                               mdelay(50);
+                               continue;
+                       }
+
+                       ret = cdn_dp_dpcd_read(dp, 0x000, dp->dpcd,
+                                              DP_RECEIVER_CAP_SIZE);
+                       if (ret)
+                               continue;
+
+                       return ret;
+               } else if (!extcon_get_state(port->extcon, EXTCON_DISP_DP)) {
+                       break;
+               }
+
+               msleep(100);
+       }
+
+       dev_err(dp->dev, "get dpcd failed!\n");
+
+       return -ETIMEDOUT;
 }
 
-static void cdn_dp_pd_event_wq(struct work_struct *work)
+static void cdn_dp_enter_standy(struct cdn_dp_device *dp,
+                               struct cdn_dp_port *port)
 {
-       struct cdn_dp_port *port = container_of(work, struct cdn_dp_port,
-                                               event_wq.work);
-       union extcon_property_value property;
-       struct cdn_dp_device *dp = port->dp;
-       u8 new_cap_lanes, i;
-       int ret;
+       int i, ret;
 
-       new_cap_lanes = cdn_dp_get_cap_lanes(dp, port->extcon);
-       if (new_cap_lanes == port->cap_lanes) {
-               dev_warn(dp->dev, "lanes count does not change: %d\n",
-                        new_cap_lanes);
+       ret = phy_power_off(port->phy);
+       if (ret) {
+               dev_err(dp->dev, "phy power off failed: %d", ret);
                return;
        }
 
-       if (!new_cap_lanes) {
-               ret = phy_power_off(port->phy);
-               if (ret) {
-                       port->cap_lanes = 0;
-                       dev_err(dp->dev, "phy power off failed: %d", ret);
+       port->phy_status = false;
+       port->cap_lanes = 0;
+       for (i = 0; i < dp->ports; i++)
+               if (dp->port[i]->phy_status)
                        return;
-               }
-               port->phy_status = false;
-               port->cap_lanes = new_cap_lanes;
-               for (i = 0; i < dp->ports; i++) {
-                       if (dp->port[i]->phy_status)
-                               return;
-               }
 
-               dp->hpd_status = connector_status_disconnected;
+       memset(dp->dpcd, 0, DP_RECEIVER_CAP_SIZE);
+       if (dp->fw_loaded)
                cdn_dp_set_firmware_active(dp, false);
-               hpd_change(dp->dev, new_cap_lanes);
-               return;
-       }
+       cdn_dp_clk_disable(dp);
+       dp->hpd_status = connector_status_disconnected;
 
-       /* if other phy is running, do not touch the hpd_status, and return */
-       for (i = 0; i < dp->ports; i++) {
-               if (dp->port[i]->phy_status) {
-                       dev_warn(dp->dev, "busy, phy[%d] is running",
-                                dp->port[i]->id);
-                       port->cap_lanes = 0;
-                       return;
-               }
-       }
+       hpd_change(dp->dev, 0);
+}
+
+static int cdn_dp_start_work(struct cdn_dp_device *dp,
+                            struct cdn_dp_port *port,
+                            u8 cap_lanes)
+{
+       union extcon_property_value property;
+       int ret;
 
        if (!dp->fw_loaded) {
                ret = request_firmware(&dp->fw, CDN_DP_FIRMWARE, dp->dev);
-               if (ret == -ENOENT && dp->fw_wait <= MAX_FW_WAIT_SECS) {
-                       unsigned long time = msecs_to_jiffies(dp->fw_wait * HZ);
-
-                       /*
-                        * If can not find the file, retry to load the firmware
-                        * in 1 second, if still failed after 1 minute, give up.
-                        */
-                       schedule_delayed_work(&port->event_wq, time);
-                       dev_warn(dp->dev, "retry to request firmware in %dS\n",
-                                dp->fw_wait);
-                       dp->fw_wait *= 2;
-                       port->cap_lanes = 0;
-                       return;
-               } else if (ret) {
-                       port->cap_lanes = 0;
-                       dev_err(dp->dev, "failed to request firmware: %d\n",
-                               ret);
-                       return;
+               if (ret) {
+                       if (ret == -ENOENT && dp->fw_wait <= MAX_FW_WAIT_SECS) {
+                               unsigned long time = msecs_to_jiffies(dp->fw_wait * HZ);
+
+                               /*
+                                * Keep trying to load the firmware for up to 1 minute,
+                                * if can not find the file.
+                                */
+                               schedule_delayed_work(&port->event_wq, time);
+                               dp->fw_wait *= 2;
+                       } else {
+                               dev_err(dp->dev, "failed to request firmware: %d\n",
+                                       ret);
+                       }
+
+                       return ret;
                }
        }
 
+       ret = cdn_dp_clk_enable(dp);
+       if (ret < 0) {
+               dev_err(dp->dev, "failed to enable clock for dp: %d\n", ret);
+               return ret;
+       }
        if (dp->fw_loaded)
                cdn_dp_set_firmware_active(dp, true);
 
+       ret = cdn_dp_grf_write(dp, GRF_SOC_CON26,
+                              (port->id << UPHY_SEL_BIT) | UPHY_SEL_MASK);
+       if (ret)
+               goto err_phy;
+
        ret = phy_power_on(port->phy);
        if (ret) {
-               if (!dp->fw_loaded)
-                       release_firmware(dp->fw);
                dev_err(dp->dev, "phy power on failed: %d\n", ret);
-               port->cap_lanes = 0;
-               return;
+               goto err_phy;
        }
+
        port->phy_status = true;
 
        if (!dp->fw_loaded) {
                ret = cdn_dp_firmware_init(dp);
-               release_firmware(dp->fw);
                if (ret) {
                        dev_err(dp->dev, "firmware init failed: %d", ret);
                        goto err_firmware;
                }
        }
 
-       /* read hpd status failed, or the hpd does not exist. */
+       ret = cdn_dp_grf_write(dp, GRF_SOC_CON26,
+                              DPTX_HPD_SEL_MASK | DPTX_HPD_SEL);
+       if (ret)
+               goto err_grf;
+
        ret = cdn_dp_get_hpd_status(dp);
        if (ret <= 0) {
-               dev_err(dp->dev, "get hpd failed: %d", ret);
-               goto err_firmware;
+               if (!ret)
+                       dev_err(dp->dev, "hpd does not exist\n");
+               goto err_hpd;
        }
 
        ret = extcon_get_property(port->extcon, EXTCON_DISP_DP,
                                  EXTCON_PROP_USB_TYPEC_POLARITY, &property);
        if (ret) {
                dev_err(dp->dev, "get property failed\n");
-               goto err_firmware;
+               goto err_hpd;
        }
 
-       ret = cdn_dp_set_host_cap(dp, new_cap_lanes, property.intval);
+       ret = cdn_dp_set_host_cap(dp, cap_lanes, property.intval);
        if (ret) {
                dev_err(dp->dev, "set host capabilities failed: %d\n", ret);
-               goto err_firmware;
+               goto err_hpd;
        }
 
-       /*
-        * Native read with retry for link status and receiver capability reads
-        * for cases where the sink may still be asleep.
-        *
-        * Sinks are *supposed* to come up within 1ms from an off state, but
-        * we're also supposed to retry 3 times per the spec.
-        */
-       for (i = 1; i < 100; i++) {
-               ret = cdn_dp_dpcd_read(dp, 0x000, dp->dpcd,
-                                      DP_RECEIVER_CAP_SIZE);
-               if (!ret) {
-                       dev_dbg(dp->dev, "get dpcd success!\n");
-                       port->cap_lanes = new_cap_lanes;
-                       dp->hpd_status = connector_status_connected;
-                       hpd_change(dp->dev, new_cap_lanes);
-                       return;
-               } else if (!extcon_get_state(port->extcon, EXTCON_DISP_DP)) {
-                       break;
-               }
-               msleep(100);
-       }
+       ret = cdn_dp_get_dpcd(dp, port);
+       if (ret)
+               goto err_hpd;
+
+       return 0;
 
-       dev_err(dp->dev, "get dpcd failed: %d", ret);
+err_hpd:
+       cdn_dp_grf_write(dp, GRF_SOC_CON26,
+                        DPTX_HPD_SEL_MASK | DPTX_HPD_DEL);
+
+err_grf:
+       cdn_dp_set_firmware_active(dp, false);
 
 err_firmware:
-       port->cap_lanes = 0;
-       ret = phy_power_off(port->phy);
-       if (ret)
+       if (phy_power_off(port->phy))
                dev_err(dp->dev, "phy power off failed: %d", ret);
        else
                port->phy_status = false;
+
+err_phy:
        if (dp->fw_loaded)
                cdn_dp_set_firmware_active(dp, false);
+       cdn_dp_clk_disable(dp);
+       return ret;
+}
+
+static int cdn_dp_pd_event(struct notifier_block *nb,
+                          unsigned long event, void *priv)
+{
+       struct cdn_dp_port *port;
+
+       port = container_of(nb, struct cdn_dp_port, event_nb);
+       schedule_delayed_work(&port->event_wq, 0);
+       return 0;
+}
+
+static void cdn_dp_pd_event_wq(struct work_struct *work)
+{
+       struct cdn_dp_port *port = container_of(work, struct cdn_dp_port,
+                                               event_wq.work);
+       struct cdn_dp_device *dp = port->dp;
+       u8 new_cap_lanes, sink_count, i;
+       int ret;
+
+       mutex_lock(&dp->lock);
+       wake_lock_timeout(&dp->wake_lock, msecs_to_jiffies(1000));
+
+       new_cap_lanes = cdn_dp_get_cap_lanes(dp, port->extcon);
+
+       if (new_cap_lanes == port->cap_lanes) {
+               if (!new_cap_lanes) {
+                       dev_err(dp->dev, "dp lanes is 0, and same with last time\n");
+                       goto out;
+               }
+
+               /*
+                * If HPD interrupt is triggered, and cable states is still
+                * attached, that means something on the Type-C Dock/Dongle
+                * changed, check the sink count by DPCD. If sink count became
+                * 0, this port phy can be powered off; if the sink count does
+                * not change, it means the sink device status has update,
+                * re-training to make it work again.
+                */
+               ret = cdn_dp_dpcd_read(dp, DP_SINK_COUNT, &sink_count, 1);
+               if (ret || sink_count) {
+                       if (dp->dpms_mode == DRM_MODE_DPMS_ON) {
+                               dev_warn(dp->dev,
+                                       "hpd interrupt is triggered when dp is already connected successfully\n");
+                               ret = cdn_dp_training_start(dp);
+                               if (!ret)
+                                       cdn_dp_get_training_status(dp);
+                       }
+                       goto out;
+               }
+               new_cap_lanes = 0;
+       }
+
+       if (dp->hpd_status == connector_status_connected && new_cap_lanes) {
+               dev_err(dp->dev, "error, dp connector has already been connected\n");
+               goto out;
+       }
+
+       if (!new_cap_lanes) {
+               dev_info(dp->dev, "dp lanes is 0, enter standby\n");
+               cdn_dp_enter_standy(dp, port);
+               goto out;
+       }
+
+       /* if other phy is running, do not do anything, just return */
+       for (i = 0; i < dp->ports; i++) {
+               if (dp->port[i]->phy_status) {
+                       dev_warn(dp->dev, "busy, phy[%d] is running",
+                                dp->port[i]->id);
+                       goto out;
+               }
+       }
+
+       ret = cdn_dp_start_work(dp, port, new_cap_lanes);
+       if (ret) {
+               dev_err(dp->dev, "dp failed to connect ,error = %d\n", ret);
+               goto out;
+       }
+       port->cap_lanes = new_cap_lanes;
+       dp->hpd_status = connector_status_connected;
+       wake_unlock(&dp->wake_lock);
+       mutex_unlock(&dp->lock);
+       hpd_change(dp->dev, new_cap_lanes);
+
+       return;
+out:
+       wake_unlock(&dp->wake_lock);
+       mutex_unlock(&dp->lock);
 }
 
 static int cdn_dp_bind(struct cdn_dp_device *dp)
@@ -578,7 +810,56 @@ static int cdn_dp_bind(struct cdn_dp_device *dp)
                }
 
                if (extcon_get_state(port->extcon, EXTCON_DISP_DP))
+                       schedule_delayed_work(&port->event_wq,
+                                                       msecs_to_jiffies(2000));
+       }
+
+       return 0;
+}
+
+int cdn_dp_suspend(void *dp_dev)
+{
+       struct cdn_dp_device *dp = dp_dev;
+       struct cdn_dp_port *port;
+       int i;
+
+       for (i = 0; i < dp->ports; i++) {
+               port = dp->port[i];
+               if (port->phy_status) {
+                       cdn_dp_dpcd_write(dp, DP_SET_POWER, DP_SET_POWER_D3);
+                       cdn_dp_enter_standy(dp, port);
+               }
+       }
+
+       /*
+        * if dp has been suspended, need to download firmware
+        * and set fw clk again.
+        */
+       dp->fw_clk_enabled = false;
+       dp->fw_loaded = false;
+       dp->suspend = true;
+       return 0;
+}
+
+int cdn_dp_resume(void *dp_dev)
+{
+       struct cdn_dp_device *dp = dp_dev;
+       struct cdn_dp_port *port;
+       int i;
+       if (dp->suspend) {
+               dp->suspend = false;
+               reset_control_assert(dp->dptx_rst);
+               udelay(15);
+               reset_control_deassert(dp->dptx_rst);
+               reset_control_assert(dp->apb_rst);
+               udelay(15);
+               reset_control_deassert(dp->apb_rst);
+
+               for (i = 0; i < dp->ports; i++) {
+                       port = dp->port[i];
                        schedule_delayed_work(&port->event_wq, 0);
+                       flush_delayed_work(&port->event_wq);
+               }
        }
 
        return 0;
@@ -642,14 +923,8 @@ static int cdn_dp_probe(struct platform_device *pdev)
        return ret;
 }
 
-static int cdn_dp_remove(struct platform_device *pdev)
-{
-       return 0;
-}
-
 static struct platform_driver cdn_dp_driver = {
        .probe = cdn_dp_probe,
-       .remove = cdn_dp_remove,
        .driver = {
                   .name = "cdn-dp-fb",
                   .owner = THIS_MODULE,
index e0885b31b566d9beb9c8024b4acf64b1cbb29576..025e18532146d86d957579329f42daac38cd9ba9 100644 (file)
@@ -2,12 +2,20 @@
 #define __ROCKCHIP_DP_CORE_H__
 
 /* dp grf register offset */
-#define DP_VOP_SEL             0x6224
-#define DP_SEL_VOP_LIT         BIT(12)
-#define MAX_FW_WAIT_SECS        64
-#define CDN_DP_FIRMWARE         "cdn/dptx.bin"
+#define GRF_SOC_CON9           0x6224
+#define GRF_SOC_CON26          0x6268
+
+#define UPHY_SEL_BIT           3
+#define UPHY_SEL_MASK          BIT(19)
 
+#define DPTX_HPD_SEL           (3 << 12)
+#define DPTX_HPD_DEL           (2 << 12)
+#define DPTX_HPD_SEL_MASK      (3 << 28)
+
+#define DP_SEL_VOP_LIT         BIT(12)
+#define MAX_FW_WAIT_SECS       64
 #define EDID_BLOCK_SIZE                128
+#define CDN_DP_FIRMWARE        "cdn/dptx.bin"
 
 struct dp_disp_info {
        struct fb_videomode *mode;
@@ -27,6 +35,7 @@ struct dp_dev {
        void *dp;
        struct notifier_block fb_notif;
        int lanes;
+       bool early_suspended;
 };
 
 int cdn_dp_fb_register(struct platform_device *pdev, void *dp);