diff --git a/drivers/video/rockchip/dp/cdn-dp-reg.c b/drivers/video/rockchip/dp/cdn-dp-reg.c index a2aa87350d1b..00d7942067d4 100644 --- a/drivers/video/rockchip/dp/cdn-dp-reg.c +++ b/drivers/video/rockchip/dp/cdn-dp-reg.c @@ -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; } diff --git a/drivers/video/rockchip/dp/cdn-dp-reg.h b/drivers/video/rockchip/dp/cdn-dp-reg.h index 61937b1eb814..29a45714e50b 100644 --- a/drivers/video/rockchip/dp/cdn-dp-reg.h +++ b/drivers/video/rockchip/dp/cdn-dp-reg.h @@ -15,6 +15,8 @@ #ifndef _CDN_DP_REG_H #define _CDN_DP_REG_H +#include +#include #include #include #include @@ -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); diff --git a/drivers/video/rockchip/dp/rockchip_dp.c b/drivers/video/rockchip/dp/rockchip_dp.c index 18979b4481ff..552785dbea7b 100644 --- a/drivers/video/rockchip/dp/rockchip_dp.c +++ b/drivers/video/rockchip/dp/rockchip_dp.c @@ -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; diff --git a/drivers/video/rockchip/dp/rockchip_dp.h b/drivers/video/rockchip/dp/rockchip_dp.h index 7714ff06a79d..175f03aeb421 100644 --- a/drivers/video/rockchip/dp/rockchip_dp.h +++ b/drivers/video/rockchip/dp/rockchip_dp.h @@ -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 diff --git a/drivers/video/rockchip/dp/rockchip_dp_core.c b/drivers/video/rockchip/dp/rockchip_dp_core.c index 7da46859fcc3..8c6ad8945f13 100644 --- a/drivers/video/rockchip/dp/rockchip_dp_core.c +++ b/drivers/video/rockchip/dp/rockchip_dp_core.c @@ -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_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; - u32 rate; 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; - if (dp_dev->dpms_mode != DRM_MODE_DPMS_ON) + mutex_lock(&dp_dev->lock); + + 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,22 +494,208 @@ 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; + + if (dp->suspend) + return 0; - dfp = extcon_get_state(edev, EXTCON_USB_HOST); dptx = extcon_get_state(edev, EXTCON_DISP_DP); - - if (dfp && dptx) - lanes = 2; - else if (dptx) - lanes = 4; - else - lanes = 0; + 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_get_dpcd(struct cdn_dp_device *dp, struct cdn_dp_port *port) +{ + u8 sink_count; + int i, ret; + int retry = 5; + + /* + * 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_enter_standy(struct cdn_dp_device *dp, + struct cdn_dp_port *port) +{ + int i, ret; + + ret = phy_power_off(port->phy); + if (ret) { + dev_err(dp->dev, "phy power off failed: %d", ret); + return; + } + + port->phy_status = false; + port->cap_lanes = 0; + for (i = 0; i < dp->ports; i++) + if (dp->port[i]->phy_status) + return; + + memset(dp->dpcd, 0, DP_RECEIVER_CAP_SIZE); + if (dp->fw_loaded) + cdn_dp_set_firmware_active(dp, false); + cdn_dp_clk_disable(dp); + dp->hpd_status = connector_status_disconnected; + + 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) { + 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) { + dev_err(dp->dev, "phy power on failed: %d\n", ret); + goto err_phy; + } + + port->phy_status = true; + + if (!dp->fw_loaded) { + ret = cdn_dp_firmware_init(dp); + if (ret) { + dev_err(dp->dev, "firmware init failed: %d", ret); + goto err_firmware; + } + } + + 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) { + 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_hpd; + } + + 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_hpd; + } + + ret = cdn_dp_get_dpcd(dp, port); + if (ret) + goto err_hpd; + + return 0; + +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: + 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) { @@ -410,146 +710,78 @@ 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); - union extcon_property_value property; struct cdn_dp_device *dp = port->dp; - u8 new_cap_lanes, i; + 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) { - dev_warn(dp->dev, "lanes count does not change: %d\n", - new_cap_lanes); - return; + 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) { - ret = phy_power_off(port->phy); - if (ret) { - port->cap_lanes = 0; - dev_err(dp->dev, "phy power off failed: %d", ret); - 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; - cdn_dp_set_firmware_active(dp, false); - hpd_change(dp->dev, new_cap_lanes); - return; + 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 touch the hpd_status, and return */ + /* 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); - port->cap_lanes = 0; - return; + goto out; } } - 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 (dp->fw_loaded) - cdn_dp_set_firmware_active(dp, true); - - ret = phy_power_on(port->phy); + ret = cdn_dp_start_work(dp, port, new_cap_lanes); 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; + dev_err(dp->dev, "dp failed to connect ,error = %d\n", ret); + goto out; } - port->phy_status = true; + 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); - 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_get_hpd_status(dp); - if (ret <= 0) { - dev_err(dp->dev, "get hpd failed: %d", ret); - goto err_firmware; - } - - 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; - } - - ret = cdn_dp_set_host_cap(dp, new_cap_lanes, property.intval); - if (ret) { - dev_err(dp->dev, "set host capabilities failed: %d\n", ret); - goto err_firmware; - } - - /* - * 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); - } - - dev_err(dp->dev, "get dpcd failed: %d", ret); - -err_firmware: - port->cap_lanes = 0; - ret = phy_power_off(port->phy); - if (ret) - dev_err(dp->dev, "phy power off failed: %d", ret); - else - port->phy_status = false; - if (dp->fw_loaded) - cdn_dp_set_firmware_active(dp, false); + 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, diff --git a/drivers/video/rockchip/dp/rockchip_dp_core.h b/drivers/video/rockchip/dp/rockchip_dp_core.h index e0885b31b566..025e18532146 100644 --- a/drivers/video/rockchip/dp/rockchip_dp_core.h +++ b/drivers/video/rockchip/dp/rockchip_dp_core.h @@ -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);