From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B2238C433ED for ; Wed, 5 May 2021 08:17:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 744E061154 for ; Wed, 5 May 2021 08:17:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232013AbhEEIS3 (ORCPT ); Wed, 5 May 2021 04:18:29 -0400 Received: from alexa-out.qualcomm.com ([129.46.98.28]:29574 "EHLO alexa-out.qualcomm.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229744AbhEEISS (ORCPT ); Wed, 5 May 2021 04:18:18 -0400 Received: from ironmsg09-lv.qualcomm.com ([10.47.202.153]) by alexa-out.qualcomm.com with ESMTP; 05 May 2021 01:17:22 -0700 X-QCInternal: smtphost Received: from ironmsg01-blr.qualcomm.com ([10.86.208.130]) by ironmsg09-lv.qualcomm.com with ESMTP/TLS/AES256-SHA; 05 May 2021 01:17:20 -0700 X-QCInternal: smtphost Received: from sbillaka-linux.qualcomm.com ([10.204.66.13]) by ironmsg01-blr.qualcomm.com with ESMTP; 05 May 2021 13:46:55 +0530 Received: by sbillaka-linux.qualcomm.com (Postfix, from userid 2305739) id 274A321D5F; Wed, 5 May 2021 13:46:54 +0530 (IST) From: Sankeerth Billakanti To: dri-devel@lists.freedesktop.org, linux-arm-msm@vger.kernel.org, freedreno@lists.freedesktop.org, linux-kernel@vger.kernel.org Cc: Sankeerth Billakanti , robdclark@gmail.com, seanpaul@chromium.org, swboyd@chromium.org, kalyan_t@codeaurora.org, abhinavk@codeaurora.org, dianders@chromium.org, khsieh@codeaurora.org, mkrishn@codeaurora.org Subject: [PATCH v1 2/3] drm/msm/edp: add support for next gen edp Date: Wed, 5 May 2021 13:46:18 +0530 Message-Id: <1620202579-19066-3-git-send-email-sbillaka@codeaurora.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1620202579-19066-1-git-send-email-sbillaka@codeaurora.org> References: <1620202579-19066-1-git-send-email-sbillaka@codeaurora.org> Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org The eDP controller and phy hardware for the next generation snapdragon platform has a different desgin compared to their earlier generations. So, there is a need to implement new sequences in driver software for the new hardware. This change will implement the required seuqences to enable eDP interface on the next generation snapdragon platforms. Signed-off-by: Sankeerth Billakanti --- drivers/gpu/drm/msm/Makefile | 6 + drivers/gpu/drm/msm/edp/edp_common.c | 4 + drivers/gpu/drm/msm/edp/edp_common.h | 6 + drivers/gpu/drm/msm/edp/v510/edp_v510.c | 220 +++ drivers/gpu/drm/msm/edp/v510/edp_v510.h | 151 ++ drivers/gpu/drm/msm/edp/v510/edp_v510_aux.c | 268 ++++ drivers/gpu/drm/msm/edp/v510/edp_v510_bridge.c | 111 ++ drivers/gpu/drm/msm/edp/v510/edp_v510_connector.c | 117 ++ drivers/gpu/drm/msm/edp/v510/edp_v510_ctrl.c | 1583 +++++++++++++++++++++ drivers/gpu/drm/msm/edp/v510/edp_v510_phy.c | 641 +++++++++ drivers/gpu/drm/msm/edp/v510/edp_v510_reg.h | 339 +++++ 11 files changed, 3446 insertions(+) create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510.h create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_aux.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_bridge.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_connector.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_ctrl.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_phy.c create mode 100644 drivers/gpu/drm/msm/edp/v510/edp_v510_reg.h diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 0a4ff4ea..f405fd6 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -32,6 +32,12 @@ msm-y := \ edp/v200/edp_v200_connector.o \ edp/v200/edp_v200_ctrl.o \ edp/v200/edp_v200_phy.o \ + edp/v510/edp_v510.o \ + edp/v510/edp_v510_aux.o \ + edp/v510/edp_v510_bridge.o \ + edp/v510/edp_v510_connector.o \ + edp/v510/edp_v510_ctrl.o \ + edp/v510/edp_v510_phy.o \ disp/mdp_format.o \ disp/mdp_kms.o \ disp/mdp4/mdp4_crtc.o \ diff --git a/drivers/gpu/drm/msm/edp/edp_common.c b/drivers/gpu/drm/msm/edp/edp_common.c index 8330050..32336a6 100644 --- a/drivers/gpu/drm/msm/edp/edp_common.c +++ b/drivers/gpu/drm/msm/edp/edp_common.c @@ -8,11 +8,13 @@ void __init msm_edp_register(void) { msm_edp_v200_register(); + msm_edp_v510_register(); } void __exit msm_edp_unregister(void) { msm_edp_v200_unregister(); + msm_edp_v510_unregister(); } /* Second part of initialization, the drm/kms level modeset_init */ @@ -29,6 +31,8 @@ int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev, if (edp->version == MSM_EDP_VERSION_200) ret = msm_edp_v200_modeset_init(edp, dev, encoder); + else if (edp->version == MSM_EDP_VERSION_510) + ret = msm_edp_v510_modeset_init(edp, dev, encoder); return ret; } diff --git a/drivers/gpu/drm/msm/edp/edp_common.h b/drivers/gpu/drm/msm/edp/edp_common.h index 30a74d9..768fce0 100644 --- a/drivers/gpu/drm/msm/edp/edp_common.h +++ b/drivers/gpu/drm/msm/edp/edp_common.h @@ -19,6 +19,7 @@ #include "dpu_io_util.h" #define MSM_EDP_VERSION_200 200 +#define MSM_EDP_VERSION_510 510 struct msm_edp { struct drm_device *dev; @@ -38,4 +39,9 @@ void __exit msm_edp_v200_unregister(void); int msm_edp_v200_modeset_init(struct msm_edp *edp, struct drm_device *dev, struct drm_encoder *encoder); +void __init msm_edp_v510_register(void); +void __exit msm_edp_v510_unregister(void); +int msm_edp_v510_modeset_init(struct msm_edp *edp, struct drm_device *dev, + struct drm_encoder *encoder); + #endif /* __EDP_COMMON_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510.c b/drivers/gpu/drm/msm/edp/v510/edp_v510.c new file mode 100644 index 0000000..8650411 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include "edp_v510.h" + +static irqreturn_t edp_irq(int irq, void *dev_id) +{ + struct msm_edp_v510 *edp = dev_id; + + /* Process eDP irq */ + return msm_edp_v510_ctrl_irq(edp->ctrl); +} + +static void edp_destroy(struct platform_device *pdev) +{ + struct msm_edp_v510 *edp = platform_get_drvdata(pdev); + + if (!edp) + return; + + if (edp->ctrl) { + msm_edp_v510_ctrl_destroy(edp->ctrl); + edp->ctrl = NULL; + } + + platform_set_drvdata(pdev, NULL); +} + +/* construct eDP at bind/probe time, grab all the resources. */ +static struct msm_edp *edp_init(struct platform_device *pdev) +{ + struct msm_edp_v510 *edp = NULL; + int ret; + + if (!pdev) { + DRM_ERROR("no eDP device\n"); + ret = -ENXIO; + goto fail; + } + + edp = devm_kzalloc(&pdev->dev, sizeof(*edp), GFP_KERNEL); + if (!edp) { + ret = -ENOMEM; + goto fail; + } + DRM_INFO("eDP v510 probed\n"); + edp->base.version = MSM_EDP_VERSION_510; + + edp->base.pdev = pdev; + platform_set_drvdata(pdev, edp); + + ret = msm_edp_v510_ctrl_init(edp); + if (ret) + goto fail; + + return &edp->base; + +fail: + if (edp) + edp_destroy(pdev); + + return ERR_PTR(ret); +} + +static int edp_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = dev_get_drvdata(master); + struct msm_drm_private *priv = drm->dev_private; + struct msm_edp *edp; + + edp = edp_init(to_platform_device(dev)); + if (IS_ERR(edp)) + return PTR_ERR(edp); + priv->edp = edp; + + return 0; +} + +static void edp_unbind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = dev_get_drvdata(master); + struct msm_drm_private *priv = drm->dev_private; + + if (priv->edp) { + edp_destroy(to_platform_device(dev)); + priv->edp = NULL; + } +} + +static const struct component_ops edp_ops = { + .bind = edp_bind, + .unbind = edp_unbind, +}; + +static int edp_dev_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &edp_ops); +} + +static int edp_dev_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &edp_ops); + return 0; +} + +static const struct of_device_id dt_match[] = { + { .compatible = "qcom,mdss-edp-v510" }, + {} +}; + +static struct platform_driver edp_driver = { + .probe = edp_dev_probe, + .remove = edp_dev_remove, + .driver = { + .name = "msm_edp_v510", + .of_match_table = dt_match, + }, +}; + +void __init msm_edp_v510_register(void) +{ + platform_driver_register(&edp_driver); +} + +void __exit msm_edp_v510_unregister(void) +{ + platform_driver_unregister(&edp_driver); +} + +static void edp_display_set_encoder_mode(struct msm_edp_v510 *edp) +{ + struct msm_drm_private *priv = edp->base.dev->dev_private; + struct msm_kms *kms = priv->kms; + + if (!edp->encoder_mode_set && edp->base.encoder && + kms->funcs->set_encoder_mode) { + kms->funcs->set_encoder_mode(kms, + edp->base.encoder, true); + + edp->encoder_mode_set = true; + } +} + +/* Second part of initialization, the drm/kms level modeset_init */ +int msm_edp_v510_modeset_init(struct msm_edp *edp, struct drm_device *dev, + struct drm_encoder *encoder) +{ + struct platform_device *pdev = edp->pdev; + struct msm_drm_private *priv = dev->dev_private; + struct msm_edp_v510 *edp_ptr = NULL; + int ret; + + if (WARN_ON(!encoder) || WARN_ON(!edp) || WARN_ON(!dev)) + return -EINVAL; + + edp->encoder = encoder; + edp->dev = dev; + + edp_ptr = container_of(edp, struct msm_edp_v510, base); + if (IS_ERR(edp_ptr)) { + ret = PTR_ERR(edp_ptr); + DRM_DEV_ERROR(dev->dev, "failed to retrieve edp_v510 ptr: %d\n", ret); + goto fail; + } + + edp_ptr->base.bridge = msm_edp_v510_bridge_init(edp_ptr); + if (IS_ERR(edp_ptr->base.bridge)) { + ret = PTR_ERR(edp_ptr->base.bridge); + DRM_DEV_ERROR(dev->dev, "failed to create eDP bridge: %d\n", ret); + edp_ptr->base.bridge = NULL; + goto fail; + } + + edp_ptr->base.connector = msm_edp_v510_connector_init(edp_ptr); + if (IS_ERR(edp_ptr->base.connector)) { + ret = PTR_ERR(edp_ptr->base.connector); + DRM_DEV_ERROR(dev->dev, "failed to create eDP connector: %d\n", ret); + edp_ptr->base.connector = NULL; + goto fail; + } + + edp_ptr->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (edp_ptr->irq < 0) { + ret = edp_ptr->irq; + DRM_DEV_ERROR(dev->dev, "failed to get IRQ: %d\n", ret); + goto fail; + } + + ret = devm_request_irq(&pdev->dev, edp_ptr->irq, + edp_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "edp_isr", edp_ptr); + if (ret < 0) { + DRM_DEV_ERROR(dev->dev, "failed to request IRQ%u: %d\n", + edp_ptr->irq, ret); + goto fail; + } + + edp_display_set_encoder_mode(edp_ptr); + + priv->bridges[priv->num_bridges++] = edp->bridge; + priv->connectors[priv->num_connectors++] = edp->connector; + + return 0; + +fail: + /* bridge/connector are normally destroyed by drm */ + if (edp->bridge) { + edp_v510_bridge_destroy(edp->bridge); + edp->bridge = NULL; + } + if (edp->connector) { + edp->connector->funcs->destroy(edp->connector); + edp->connector = NULL; + } + + return ret; +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510.h b/drivers/gpu/drm/msm/edp/v510/edp_v510.h new file mode 100644 index 0000000..9a01987 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __EDP_V510_CONNECTOR_H__ +#define __EDP_V510_CONNECTOR_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include "msm_drv.h" +#include "msm_kms.h" +#include "dpu_io_util.h" +#include "../edp_common.h" + +#define MSM_EDP_CONTROLLER_AHB_OFFSET 0x0000 +#define MSM_EDP_CONTROLLER_AHB_SIZE 0x0200 +#define MSM_EDP_CONTROLLER_AUX_OFFSET 0x0200 +#define MSM_EDP_CONTROLLER_AUX_SIZE 0x0200 +#define MSM_EDP_CONTROLLER_LINK_OFFSET 0x0400 +#define MSM_EDP_CONTROLLER_LINK_SIZE 0x0C00 +#define MSM_EDP_CONTROLLER_P0_OFFSET 0x1000 +#define MSM_EDP_CONTROLLER_P0_SIZE 0x0400 + +static inline u32 edp_read_aux(void *base, u32 offset) +{ + offset += MSM_EDP_CONTROLLER_AUX_OFFSET; + return readl_relaxed(base + offset); +} + +static inline void edp_write_aux(void *base, u32 offset, u32 data) +{ + offset += MSM_EDP_CONTROLLER_AUX_OFFSET; + /* + * To make sure aux reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + writel(data, base + offset); +} + +static inline u32 edp_read_ahb(void *base, u32 offset) +{ + offset += MSM_EDP_CONTROLLER_AHB_OFFSET; + return readl_relaxed(base + offset); +} + +static inline void edp_write_ahb(void *base, u32 offset, u32 data) +{ + offset += MSM_EDP_CONTROLLER_AHB_OFFSET; + /* + * To make sure ahb reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + writel(data, base + offset); +} + +static inline void edp_write_p0(void *base, u32 offset, u32 data) +{ + offset += MSM_EDP_CONTROLLER_P0_OFFSET; + /* + * To make sure pclk reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + writel(data, base + offset); +} + +static inline u32 edp_read_p0(void *base, u32 offset) +{ + offset += MSM_EDP_CONTROLLER_P0_OFFSET; + return readl_relaxed(base + offset); +} + +static inline u32 edp_read_link(void *base, u32 offset) +{ + offset += MSM_EDP_CONTROLLER_LINK_OFFSET; + return readl_relaxed(base + offset); +} + +static inline void edp_write_link(void *base, u32 offset, u32 data) +{ + offset += MSM_EDP_CONTROLLER_LINK_OFFSET; + /* + * To make sure link reg writes happens before any other operation, + * this function uses writel() instread of writel_relaxed() + */ + writel(data, base + offset); +} + +struct edp_ctrl; +struct edp_aux; +struct edp_phy; +struct edp_phy_opts { + unsigned long link_rate; + int lanes; + int voltage[4]; + int pre[4]; +}; + + +struct msm_edp_v510 { + struct msm_edp base; + struct edp_ctrl *ctrl; + + int irq; + bool encoder_mode_set; +}; + +/* eDP bridge */ +struct drm_bridge *msm_edp_v510_bridge_init(struct msm_edp_v510 *edp); +void edp_v510_bridge_destroy(struct drm_bridge *bridge); + +/* eDP connector */ +struct drm_connector *msm_edp_v510_connector_init(struct msm_edp_v510 *edp); + +/* AUX */ +void *msm_edp_v510_aux_init(struct device *dev, void __iomem *regbase, + struct drm_dp_aux **drm_aux); +void msm_edp_v510_aux_destroy(struct device *dev, struct edp_aux *aux); +irqreturn_t msm_edp_v510_aux_irq(struct edp_aux *aux, u32 isr); +void msm_edp_v510_aux_ctrl(struct edp_aux *aux, int enable); + +/* Phy */ +int msm_edp_v510_phy_enable(struct edp_phy *edp_phy); +void msm_edp_v510_phy_vm_pe_init(struct edp_phy *edp_phy, + struct edp_phy_opts *opts); +void *msm_edp_v510_phy_init(struct device *dev, void __iomem *regbase, + struct edp_phy_opts *opts); +int msm_edp_v510_phy_power_on(struct edp_phy *edp_phy); +void msm_edp_v510_phy_config(struct edp_phy *edp_phy, u8 v_level, u8 p_level); + +/* Ctrl */ +irqreturn_t msm_edp_v510_ctrl_irq(struct edp_ctrl *ctrl); +void msm_edp_v510_ctrl_power(struct edp_ctrl *ctrl, bool on); +int msm_edp_v510_ctrl_init(struct msm_edp_v510 *edp); +void msm_edp_v510_ctrl_destroy(struct edp_ctrl *ctrl); +bool msm_edp_v510_ctrl_panel_connected(struct edp_ctrl *ctrl); +int msm_edp_v510_ctrl_get_panel_info(struct edp_ctrl *ctrl, + struct drm_connector *connector, struct edid **edid); +int msm_edp_v510_ctrl_mode_set(struct edp_ctrl *ctrl, + const struct drm_display_mode *mode, + const struct drm_display_info *info); +/* @pixel_rate is in kHz */ +bool msm_edp_v510_ctrl_pixel_clock_valid(struct edp_ctrl *ctrl, u32 pixel_rate); + +#endif /* __EDP_V510_CONNECTOR_H__ */ diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_aux.c b/drivers/gpu/drm/msm/edp/v510/edp_v510_aux.c new file mode 100644 index 0000000..817e0e6 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_aux.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include "edp_v510.h" +#include "edp_v510_reg.h" + +#define AUX_CMD_FIFO_LEN 144 +#define AUX_CMD_NATIVE_MAX 16 +#define AUX_CMD_I2C_MAX 128 + +#define EDP_INTR_AUX_I2C_ERR \ + (EDP_INTR_WRONG_ADDR | EDP_INTR_TIMEOUT | \ + EDP_INTR_NACK_DEFER | EDP_INTR_WRONG_DATA_CNT | \ + EDP_INTR_I2C_NACK | EDP_INTR_I2C_DEFER) + +#define EDP_INTR_TRANS_STATUS \ + (EDP_INTR_AUX_I2C_DONE | EDP_INTR_AUX_I2C_ERR) + +struct edp_aux { + void __iomem *base; + bool msg_err; + + struct completion msg_comp; + + /* To prevent the message transaction routine from reentry. */ + struct mutex msg_mutex; + + struct drm_dp_aux drm_aux; +}; +#define to_edp_aux(x) container_of(x, struct edp_aux, drm_aux) + +static int edp_msg_fifo_tx(struct edp_aux *aux, struct drm_dp_aux_msg *msg) +{ + u32 data[4]; + u32 reg, len; + bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + u8 *msgdata = msg->buffer; + int i; + + if (read) + len = 4; + else + len = msg->size + 4; + + /* + * cmd fifo only has depth of 144 bytes + */ + if (len > AUX_CMD_FIFO_LEN) + return -EINVAL; + + /* Pack cmd and write to HW */ + data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */ + if (read) + data[0] |= BIT(4); /* R/W */ + + data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */ + data[2] = msg->address & 0xff; /* addr[7:0] */ + data[3] = (msg->size - 1) & 0xff; /* len[7:0] */ + + for (i = 0; i < len; i++) { + reg = (i < 4) ? data[i] : msgdata[i - 4]; + reg = EDP_AUX_DATA_DATA(reg); /* index = 0, write */ + if (i == 0) + reg |= EDP_AUX_DATA_INDEX_WRITE; + edp_write_aux(aux->base, REG_EDP_AUX_DATA, reg); + } + + reg = 0; /* Transaction number is always 1 */ + if (!native) /* i2c */ + reg |= EDP_AUX_TRANS_CTRL_I2C; + + reg |= EDP_AUX_TRANS_CTRL_GO; + edp_write_aux(aux->base, REG_EDP_AUX_TRANS_CTRL, reg); + + return 0; +} + +static int edp_msg_fifo_rx(struct edp_aux *aux, struct drm_dp_aux_msg *msg) +{ + u32 data; + u8 *dp; + int i; + u32 len = msg->size; + + edp_write_aux(aux->base, REG_EDP_AUX_DATA, + EDP_AUX_DATA_INDEX_WRITE | EDP_AUX_DATA_READ); /* index = 0 */ + + dp = msg->buffer; + + /* discard first byte */ + data = edp_read_aux(aux->base, REG_EDP_AUX_DATA); + for (i = 0; i < len; i++) { + data = edp_read_aux(aux->base, REG_EDP_AUX_DATA); + dp[i] = (u8)((data >> 8) & 0xff); + } + + return 0; +} + +/* + * This function does the real job to process an AUX transaction. + * It will call msm_edp_aux_ctrl() function to reset the AUX channel, + * if the waiting is timeout. + * The caller who triggers the transaction should avoid the + * msm_edp_aux_ctrl() running concurrently in other threads, i.e. + * start transaction only when AUX channel is fully enabled. + */ +static ssize_t edp_aux_transfer(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ + struct edp_aux *aux = to_edp_aux(drm_aux); + ssize_t ret; + unsigned long time_left; + bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + + /* Ignore address only message */ + if ((msg->size == 0) || (msg->buffer == NULL)) { + msg->reply = native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + return msg->size; + } + + /* msg sanity check */ + if ((native && (msg->size > AUX_CMD_NATIVE_MAX)) || + (msg->size > AUX_CMD_I2C_MAX)) { + pr_err("%s: invalid msg: size(%zu), request(%x)\n", + __func__, msg->size, msg->request); + return -EINVAL; + } + + mutex_lock(&aux->msg_mutex); + + aux->msg_err = false; + reinit_completion(&aux->msg_comp); + + ret = edp_msg_fifo_tx(aux, msg); + if (ret < 0) + goto unlock_exit; + + DRM_DEBUG_DP("wait_for_completion"); + time_left = wait_for_completion_timeout(&aux->msg_comp, + msecs_to_jiffies(300)); + if (!time_left) { + /* + * Clear GO and reset AUX channel + * to cancel the current transaction. + */ + edp_write_aux(aux->base, REG_EDP_AUX_TRANS_CTRL, 0); + msm_edp_v510_aux_ctrl(aux, 1); + pr_err("%s: aux timeout,\n", __func__); + ret = -ETIMEDOUT; + goto unlock_exit; + } + DRM_DEBUG_DP("completion"); + + if (!aux->msg_err) { + if (read) { + ret = edp_msg_fifo_rx(aux, msg); + if (ret < 0) + goto unlock_exit; + } + + msg->reply = native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + } else { + /* Reply defer to retry */ + msg->reply = native ? + DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER; + /* + * The sleep time in caller is not long enough to make sure + * our H/W completes transactions. Add more defer time here. + */ + msleep(100); + } + + /* Return requested size for success or retry */ + ret = msg->size; + +unlock_exit: + mutex_unlock(&aux->msg_mutex); + return ret; +} + +void *msm_edp_v510_aux_init(struct device *dev, void __iomem *regbase, + struct drm_dp_aux **drm_aux) +{ + struct edp_aux *aux = NULL; + int ret; + + aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL); + if (!aux) + return NULL; + + aux->base = regbase; + mutex_init(&aux->msg_mutex); + init_completion(&aux->msg_comp); + + aux->drm_aux.name = "msm_edp_aux"; + aux->drm_aux.dev = dev; + aux->drm_aux.transfer = edp_aux_transfer; + ret = drm_dp_aux_register(&aux->drm_aux); + if (ret) { + pr_err("%s: failed to register drm aux: %d\n", __func__, ret); + mutex_destroy(&aux->msg_mutex); + } + + if (drm_aux && aux) + *drm_aux = &aux->drm_aux; + + return aux; +} + +void msm_edp_v510_aux_destroy(struct device *dev, struct edp_aux *aux) +{ + if (aux) { + drm_dp_aux_unregister(&aux->drm_aux); + mutex_destroy(&aux->msg_mutex); + } +} + +irqreturn_t msm_edp_v510_aux_irq(struct edp_aux *aux, u32 isr) +{ + if (isr & EDP_INTR_TRANS_STATUS) { + DRM_DEBUG_DP("isr=%x", isr); + edp_write_aux(aux->base, REG_EDP_AUX_TRANS_CTRL, 0); + + if (isr & EDP_INTR_AUX_I2C_ERR) + aux->msg_err = true; + else + aux->msg_err = false; + + complete(&aux->msg_comp); + } + + return IRQ_HANDLED; +} + +void msm_edp_v510_aux_ctrl(struct edp_aux *aux, int enable) +{ + u32 data; + + DRM_INFO("enable=%d", enable); + data = edp_read_aux(aux->base, REG_EDP_AUX_CTRL); + + if (enable) { + data |= EDP_AUX_CTRL_RESET; + edp_write_aux(aux->base, REG_EDP_AUX_CTRL, data); + /* Make sure full reset */ + wmb(); + usleep_range(500, 1000); + + data &= ~EDP_AUX_CTRL_RESET; + edp_write_aux(aux->base, REG_EDP_AUX_CTRL, data); + + edp_write_aux(aux->base, REG_EDP_TIMEOUT_COUNT, 0xffff); + edp_write_aux(aux->base, REG_EDP_AUX_LIMITS, 0xffff); + + data |= EDP_AUX_CTRL_ENABLE; + edp_write_aux(aux->base, REG_EDP_AUX_CTRL, data); + } else { + data &= ~EDP_AUX_CTRL_ENABLE; + edp_write_aux(aux->base, REG_EDP_AUX_CTRL, data); + } +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_bridge.c b/drivers/gpu/drm/msm/edp/v510/edp_v510_bridge.c new file mode 100644 index 0000000..12a85dd --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_bridge.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include "edp_v510.h" + +struct edp_bridge { + struct drm_bridge base; + struct msm_edp_v510 *edp; +}; +#define to_edp_bridge(x) container_of(x, struct edp_bridge, base) + +void edp_v510_bridge_destroy(struct drm_bridge *bridge) +{ +} + +static void edp_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct edp_bridge *edp_bridge = to_edp_bridge(bridge); + struct msm_edp_v510 *edp = edp_bridge->edp; + + DBG(""); + msm_edp_v510_ctrl_power(edp->ctrl, true); +} + +static void edp_bridge_enable(struct drm_bridge *bridge) +{ + DBG(""); +} + +static void edp_bridge_disable(struct drm_bridge *bridge) +{ + DBG(""); +} + +static void edp_bridge_post_disable(struct drm_bridge *bridge) +{ + struct edp_bridge *edp_bridge = to_edp_bridge(bridge); + struct msm_edp_v510 *edp = edp_bridge->edp; + + DBG(""); + msm_edp_v510_ctrl_power(edp->ctrl, false); +} + +static void edp_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = bridge->dev; + struct drm_connector *connector; + struct edp_bridge *edp_bridge = to_edp_bridge(bridge); + struct msm_edp_v510 *edp = edp_bridge->edp; + + DRM_INFO("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + struct drm_encoder *encoder = connector->encoder; + struct drm_bridge *first_bridge; + + if (!connector->encoder) + continue; + + first_bridge = drm_bridge_chain_get_first_bridge(encoder); + if (bridge == first_bridge) { + msm_edp_v510_ctrl_mode_set(edp->ctrl, + adjusted_mode, &connector->display_info); + break; + } + } +} + +static const struct drm_bridge_funcs edp_bridge_funcs = { + .pre_enable = edp_bridge_pre_enable, + .enable = edp_bridge_enable, + .disable = edp_bridge_disable, + .post_disable = edp_bridge_post_disable, + .mode_set = edp_bridge_mode_set, +}; + +/* initialize bridge */ +struct drm_bridge *msm_edp_v510_bridge_init(struct msm_edp_v510 *edp) +{ + struct drm_bridge *bridge = NULL; + struct edp_bridge *edp_bridge; + int ret; + + edp_bridge = devm_kzalloc(edp->base.dev->dev, + sizeof(*edp_bridge), GFP_KERNEL); + if (!edp_bridge) { + ret = -ENOMEM; + goto fail; + } + + edp_bridge->edp = edp; + + bridge = &edp_bridge->base; + bridge->funcs = &edp_bridge_funcs; + + ret = drm_bridge_attach(edp->base.encoder, bridge, NULL, 0); + if (ret) + goto fail; + + return bridge; + +fail: + if (bridge) + edp_v510_bridge_destroy(bridge); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_connector.c b/drivers/gpu/drm/msm/edp/v510/edp_v510_connector.c new file mode 100644 index 0000000..6ba2071 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_connector.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include "drm/drm_edid.h" +#include "msm_kms.h" +#include "edp_v510.h" + +struct edp_connector { + struct drm_connector base; + struct msm_edp_v510 *edp; +}; +#define to_edp_connector(x) container_of(x, struct edp_connector, base) + +static enum drm_connector_status edp_connector_detect( + struct drm_connector *connector, bool force) +{ + struct edp_connector *edp_connector = to_edp_connector(connector); + struct msm_edp_v510 *edp = edp_connector->edp; + + DBG(""); + return msm_edp_v510_ctrl_panel_connected(edp->ctrl) ? + connector_status_connected : connector_status_disconnected; +} + +static void edp_connector_destroy(struct drm_connector *connector) +{ + struct edp_connector *edp_connector = to_edp_connector(connector); + + DBG(""); + + drm_connector_cleanup(connector); + + kfree(edp_connector); +} + +static int edp_connector_get_modes(struct drm_connector *connector) +{ + struct edp_connector *edp_connector = to_edp_connector(connector); + struct msm_edp_v510 *edp = edp_connector->edp; + + struct edid *drm_edid = NULL; + int ret = 0; + + DBG(""); + ret = msm_edp_v510_ctrl_get_panel_info(edp->ctrl, connector, &drm_edid); + if (ret) + return ret; + + drm_connector_update_edid_property(connector, drm_edid); + if (drm_edid) + ret = drm_add_edid_modes(connector, drm_edid); + + return ret; +} + +static int edp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct edp_connector *edp_connector = to_edp_connector(connector); + struct msm_edp_v510 *edp = edp_connector->edp; + + if (!msm_edp_v510_ctrl_pixel_clock_valid(edp->ctrl, mode->clock)) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static const struct drm_connector_funcs edp_connector_funcs = { + .detect = edp_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = edp_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs edp_connector_helper_funcs = { + .get_modes = edp_connector_get_modes, + .mode_valid = edp_connector_mode_valid, +}; + +/* initialize connector */ +struct drm_connector *msm_edp_v510_connector_init(struct msm_edp_v510 *edp) +{ + struct drm_connector *connector = NULL; + struct edp_connector *edp_connector; + int ret; + + edp_connector = devm_kzalloc(edp->base.dev->dev, sizeof(*edp_connector), + GFP_KERNEL); + if (!edp_connector) + return ERR_PTR(-ENOMEM); + + edp_connector->edp = edp; + + connector = &edp_connector->base; + + ret = drm_connector_init(edp->base.dev, connector, &edp_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) + return ERR_PTR(ret); + + drm_connector_helper_add(connector, &edp_connector_helper_funcs); + + /* We don't support HPD, so only poll status until connected. */ + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + /* Display driver doesn't support interlace now. */ + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + + drm_connector_attach_encoder(connector, edp->base.encoder); + + return connector; +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_ctrl.c b/drivers/gpu/drm/msm/edp/v510/edp_v510_ctrl.c new file mode 100644 index 0000000..8bc4609 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_ctrl.c @@ -0,0 +1,1583 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edp_v510.h" +#include "edp_v510_reg.h" + +#define VDDA_UA_ON_LOAD 21800 /* uA units */ +#define VDDA_UA_OFF_LOAD 4 /* uA units */ +#define LVL_UA_ON_LOAD 36000 /* uA units */ +#define LVL_UA_OFF_LOAD 32 /* uA units */ + +#define DPCD_LINK_VOLTAGE_MAX 4 +#define DPCD_LINK_PRE_EMPHASIS_MAX 4 + +#define EDP_LINK_BW_MAX DP_LINK_BW_5_4 + +/* Link training return value */ +#define EDP_TRAIN_FAIL -1 +#define EDP_TRAIN_SUCCESS 0 +#define EDP_TRAIN_RECONFIG 1 + +#define EDP_CLK_MASK_AHB BIT(0) +#define EDP_CLK_MASK_AUX BIT(1) +#define EDP_CLK_MASK_LINK BIT(2) +#define EDP_CLK_MASK_PIXEL BIT(3) +#define EDP_CLK_MASK_MDP_CORE BIT(4) +#define EDP_CLK_MASK_LINK_CHAN (EDP_CLK_MASK_LINK | EDP_CLK_MASK_PIXEL) +#define EDP_CLK_MASK_AUX_CHAN \ + (EDP_CLK_MASK_AHB | EDP_CLK_MASK_AUX | EDP_CLK_MASK_MDP_CORE) +#define EDP_CLK_MASK_ALL (EDP_CLK_MASK_AUX_CHAN | EDP_CLK_MASK_LINK_CHAN) + +#define EDP_BACKLIGHT_MAX 255 + +#define EDP_INTERRUPT_STATUS_ACK_SHIFT 1 +#define EDP_INTERRUPT_STATUS_MASK_SHIFT 2 + +#define EDP_INTERRUPT_STATUS1 \ + (EDP_INTR_AUX_I2C_DONE| \ + EDP_INTR_WRONG_ADDR | EDP_INTR_TIMEOUT | \ + EDP_INTR_NACK_DEFER | EDP_INTR_WRONG_DATA_CNT | \ + EDP_INTR_I2C_NACK | EDP_INTR_I2C_DEFER | \ + EDP_INTR_PLL_UNLOCKED | EDP_INTR_AUX_ERROR) + +#define EDP_INTERRUPT_STATUS1_ACK \ + (EDP_INTERRUPT_STATUS1 << EDP_INTERRUPT_STATUS_ACK_SHIFT) +#define EDP_INTERRUPT_STATUS1_MASK \ + (EDP_INTERRUPT_STATUS1 << EDP_INTERRUPT_STATUS_MASK_SHIFT) + +#define EDP_INTERRUPT_STATUS2 \ + (EDP_INTR_READY_FOR_VIDEO | EDP_INTR_IDLE_PATTERN_SENT | \ + EDP_INTR_FRAME_END | EDP_INTR_CRC_UPDATED | EDP_INTR_SST_FIFO_UNDERFLOW) + +#define EDP_INTERRUPT_STATUS2_ACK \ + (EDP_INTERRUPT_STATUS2 << EDP_INTERRUPT_STATUS_ACK_SHIFT) +#define EDP_INTERRUPT_STATUS2_MASK \ + (EDP_INTERRUPT_STATUS2 << EDP_INTERRUPT_STATUS_MASK_SHIFT) + +enum edp_pm_type { + EDP_CORE_PM, + EDP_CTRL_PM, + EDP_STREAM_PM, + EDP_PHY_PM, + EDP_MAX_PM +}; + +struct edp_ctrl { + struct platform_device *pdev; + + void __iomem *base; + void __iomem *phy_base; + + /* regulators */ + struct regulator *vdda_vreg; /* 1.8 V */ + struct regulator *lvl_vreg; + + /* clocks */ + struct dss_module_power mp[EDP_MAX_PM]; + bool core_clks_on; + bool link_clks_on; + bool stream_clks_on; + + /* gpios */ + struct gpio_desc *panel_en_gpio; + struct gpio_desc *panel_hpd_gpio; + struct gpio_desc *panel_bklt1_gpio; + struct gpio_desc *panel_bklt2_gpio; + struct gpio_desc *panel_pwm_gpio; + + /* completion and mutex */ + struct completion idle_comp; + struct mutex dev_mutex; /* To protect device power status */ + + /* work queue */ + struct work_struct on_work; + struct work_struct off_work; + struct workqueue_struct *workqueue; + + /* Interrupt register lock */ + spinlock_t irq_lock; + + bool edp_connected; + bool power_on; + bool core_initialized; + + /* edid raw data */ + struct edid *edid; + + struct drm_dp_aux *drm_aux; + + /* dpcd raw data */ + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + /* Link status */ + u8 link_rate; + u8 lane_cnt; + u8 v_level; + u8 p_level; + struct edp_phy_opts edp_opts; + + /* Timing status */ + u8 interlaced; + u32 pixel_rate; /* in kHz */ + u32 color_depth; + struct drm_display_mode drm_mode; + + struct edp_aux *aux; + struct edp_phy *phy; +}; + +struct edp_ctrl_tu { + u32 rate; + u32 edp_tu; + u32 valid_boundary; + u32 valid_boundary2; +}; + +#define MAX_TU_TABLE 1 +static const struct edp_ctrl_tu tu[MAX_TU_TABLE] = { + {285550, 0x20, 0x13001B, 0x920035}, /* 1920x1080@120Hz CVT RB1 */ +}; + +static inline bool edp_check_prefix(const char *clk_prefix, + const char *clk_name) +{ + return !strncmp(clk_prefix, clk_name, strlen(clk_prefix)); +} + +static int edp_init_clk_data(struct edp_ctrl *ctrl) +{ + int num_clk, i, rc; + int core_clk_count = 0, ctrl_clk_count = 0, stream_clk_count = 0; + const char *clk_name; + struct device *dev = &ctrl->pdev->dev; + struct dss_module_power *core_power = &ctrl->mp[EDP_CORE_PM]; + struct dss_module_power *ctrl_power = &ctrl->mp[EDP_CTRL_PM]; + struct dss_module_power *stream_power = &ctrl->mp[EDP_STREAM_PM]; + + num_clk = of_property_count_strings(dev->of_node, "clock-names"); + if (num_clk <= 0) { + DRM_ERROR("no clocks are defined\n"); + return -EINVAL; + } + + for (i = 0; i < num_clk; i++) { + rc = of_property_read_string_index(dev->of_node, + "clock-names", i, &clk_name); + if (rc < 0) + return rc; + + if (edp_check_prefix("core", clk_name)) + core_clk_count++; + + if (edp_check_prefix("ctrl", clk_name)) + ctrl_clk_count++; + + if (edp_check_prefix("stream", clk_name)) + stream_clk_count++; + } + + /* Initialize the CORE power module */ + if (core_clk_count == 0) { + DRM_ERROR("no core clocks are defined\n"); + return -EINVAL; + } + + core_power->num_clk = core_clk_count; + core_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * core_power->num_clk, + GFP_KERNEL); + if (!core_power->clk_config) + return -EINVAL; + + /* Initialize the CTRL power module */ + if (ctrl_clk_count == 0) { + DRM_ERROR("no ctrl clocks are defined\n"); + return -EINVAL; + } + + ctrl_power->num_clk = ctrl_clk_count; + ctrl_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * ctrl_power->num_clk, + GFP_KERNEL); + if (!ctrl_power->clk_config) { + ctrl_power->num_clk = 0; + return -EINVAL; + } + + /* Initialize the STREAM power module */ + if (stream_clk_count == 0) { + DRM_ERROR("no stream (pixel) clocks are defined\n"); + return -EINVAL; + } + + stream_power->num_clk = stream_clk_count; + stream_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * stream_power->num_clk, + GFP_KERNEL); + if (!stream_power->clk_config) { + stream_power->num_clk = 0; + return -EINVAL; + } + + return 0; +} + +static int edp_clk_init(struct edp_ctrl *ctrl) +{ + int rc = 0, i = 0; + int num_clk = 0; + int core_clk_index = 0, ctrl_clk_index = 0, stream_clk_index = 0; + int core_clk_count = 0, ctrl_clk_count = 0, stream_clk_count = 0; + const char *clk_name; + struct device *dev = &ctrl->pdev->dev; + struct dss_module_power *core_power = &ctrl->mp[EDP_CORE_PM]; + struct dss_module_power *ctrl_power = &ctrl->mp[EDP_CTRL_PM]; + struct dss_module_power *stream_power = &ctrl->mp[EDP_STREAM_PM]; + + rc = edp_init_clk_data(ctrl); + if (rc) { + DRM_ERROR("failed to initialize power data %d\n", rc); + return -EINVAL; + } + + core_clk_count = core_power->num_clk; + ctrl_clk_count = ctrl_power->num_clk; + stream_clk_count = stream_power->num_clk; + + num_clk = core_clk_count + ctrl_clk_count + stream_clk_count; + + for (i = 0; i < num_clk; i++) { + rc = of_property_read_string_index(dev->of_node, "clock-names", + i, &clk_name); + if (rc) { + DRM_ERROR("error reading clock-names %d\n", rc); + return rc; + } + if (edp_check_prefix("core", clk_name) && + core_clk_index < core_clk_count) { + struct dss_clk *clk = + &core_power->clk_config[core_clk_index]; + strscpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + clk->type = DSS_CLK_AHB; + core_clk_index++; + } else if (edp_check_prefix("stream", clk_name) && + stream_clk_index < stream_clk_count) { + struct dss_clk *clk = + &stream_power->clk_config[stream_clk_index]; + strscpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + clk->type = DSS_CLK_PCLK; + stream_clk_index++; + } else if (edp_check_prefix("ctrl", clk_name) && + ctrl_clk_index < ctrl_clk_count) { + struct dss_clk *clk = + &ctrl_power->clk_config[ctrl_clk_index]; + strscpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + ctrl_clk_index++; + if (edp_check_prefix("ctrl_link", clk_name) || + edp_check_prefix("stream_pixel", clk_name)) + clk->type = DSS_CLK_PCLK; + else + clk->type = DSS_CLK_AHB; + } + } + + DRM_DEBUG_DP("clock parsing successful\n"); + + rc = msm_dss_get_clk(dev, core_power->clk_config, core_power->num_clk); + if (rc) { + DRM_ERROR("failed to get core clk. err=%d\n", rc); + return rc; + } + + rc = msm_dss_get_clk(dev, ctrl_power->clk_config, ctrl_power->num_clk); + if (rc) { + DRM_ERROR("failed to get ctrl clk. err=%d\n", rc); + msm_dss_put_clk(core_power->clk_config, core_power->num_clk); + return -ENODEV; + } + + rc = msm_dss_get_clk(dev, stream_power->clk_config, stream_power->num_clk); + if (rc) { + DRM_ERROR("failed to get strem clk. err=%d\n", rc); + msm_dss_put_clk(core_power->clk_config, core_power->num_clk); + return -ENODEV; + } + + return 0; +} + +static void edp_clk_deinit(struct edp_ctrl *ctrl) +{ + struct dss_module_power *core_power, *ctrl_power, *stream_power; + + core_power = &ctrl->mp[EDP_CORE_PM]; + ctrl_power = &ctrl->mp[EDP_CTRL_PM]; + stream_power = &ctrl->mp[EDP_STREAM_PM]; + + if (!core_power || !ctrl_power || !stream_power) + DRM_ERROR("invalid power_data\n"); + + msm_dss_put_clk(ctrl_power->clk_config, ctrl_power->num_clk); + msm_dss_put_clk(core_power->clk_config, core_power->num_clk); + msm_dss_put_clk(stream_power->clk_config, stream_power->num_clk); +} + +static int edp_clk_set_rate(struct edp_ctrl *ctrl, + enum edp_pm_type module, bool enable) +{ + int rc = 0; + struct dss_module_power *mp = &ctrl->mp[module]; + + if (enable) { + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); + if (rc) { + DRM_ERROR("failed to set clks rate.\n"); + return rc; + } + } + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); + if (rc) { + DRM_ERROR("failed to %d clks, err: %d\n", enable, rc); + return rc; + } + + return 0; +} + +int edp_clk_enable(struct edp_ctrl *ctrl, + enum edp_pm_type pm_type, bool enable) +{ + int rc = 0; + + if (pm_type != EDP_CORE_PM && pm_type != EDP_CTRL_PM && + pm_type != EDP_STREAM_PM) { + DRM_ERROR("unsupported power module\n"); + return -EINVAL; + } + + if (enable) { + if (pm_type == EDP_CORE_PM && ctrl->core_clks_on) { + DRM_DEBUG_DP("core clks already enabled\n"); + return 0; + } + + if (pm_type == EDP_CTRL_PM && ctrl->link_clks_on) { + DRM_DEBUG_DP("links clks already enabled\n"); + return 0; + } + + if (pm_type == EDP_STREAM_PM && ctrl->stream_clks_on) { + DRM_DEBUG_DP("pixel clks already enabled\n"); + return 0; + } + + if ((pm_type == EDP_CTRL_PM) && (!ctrl->core_clks_on)) { + DRM_DEBUG_DP("Enable core clks before link clks\n"); + + rc = edp_clk_set_rate(ctrl, EDP_CORE_PM, enable); + if (rc) { + DRM_ERROR("fail to enable clks: core. err=%d\n", + rc); + return rc; + } + ctrl->core_clks_on = true; + } + } + + rc = edp_clk_set_rate(ctrl, pm_type, enable); + if (rc) { + DRM_ERROR("failed to '%s' clks. err=%d\n", + enable ? "enable" : "disable", rc); + return rc; + } + + if (pm_type == EDP_CORE_PM) + ctrl->core_clks_on = enable; + else if (pm_type == EDP_STREAM_PM) + ctrl->stream_clks_on = enable; + else + ctrl->link_clks_on = enable; + + DRM_DEBUG_DP("stream_clks:%s link_clks:%s core_clks:%s\n", + ctrl->stream_clks_on ? "on" : "off", + ctrl->link_clks_on ? "on" : "off", + ctrl->core_clks_on ? "on" : "off"); + + return 0; +} + +static void edp_ctrl_set_clock_rate(struct edp_ctrl *ctrl, + enum edp_pm_type module, char *name, unsigned long rate) +{ + u32 num = ctrl->mp[module].num_clk; + struct dss_clk *cfg = ctrl->mp[module].clk_config; + + while (num && strcmp(cfg->clk_name, name)) { + num--; + cfg++; + } + + DRM_DEBUG_DP("setting rate=%lu on clk=%s\n", rate, name); + + if (num) + cfg->rate = rate; + else + DRM_ERROR("%s clock doesn't exit to set rate %lu\n", + name, rate); +} + +static int edp_regulator_init(struct edp_ctrl *ctrl) +{ + struct device *dev = &ctrl->pdev->dev; + int ret; + + ctrl->vdda_vreg = devm_regulator_get(dev, "vdda"); + ret = PTR_ERR_OR_ZERO(ctrl->vdda_vreg); + if (ret) { + DRM_ERROR("%s: Could not get vdda reg, ret = %d\n", __func__, + ret); + ctrl->vdda_vreg = NULL; + return ret; + } + ctrl->lvl_vreg = devm_regulator_get(dev, "lvl-vdd"); + ret = PTR_ERR_OR_ZERO(ctrl->lvl_vreg); + if (ret) { + DRM_ERROR("%s: Could not get lvl-vdd reg, ret = %d\n", __func__, + ret); + ctrl->lvl_vreg = NULL; + return ret; + } + + return 0; +} + +static int edp_regulator_enable(struct edp_ctrl *ctrl) +{ + int ret; + + ret = regulator_set_load(ctrl->vdda_vreg, VDDA_UA_ON_LOAD); + if (ret < 0) { + DRM_ERROR("%s: vdda_vreg set regulator mode failed.\n", __func__); + goto vdda_set_fail; + } + + ret = regulator_enable(ctrl->vdda_vreg); + if (ret) { + DRM_ERROR("%s: Failed to enable vdda_vreg regulator.\n", __func__); + goto vdda_enable_fail; + } + + ret = regulator_set_load(ctrl->lvl_vreg, LVL_UA_ON_LOAD); + if (ret < 0) { + DRM_ERROR("%s: vdda_vreg set regulator mode failed.\n", __func__); + goto vdda_set_fail; + } + + ret = regulator_enable(ctrl->lvl_vreg); + if (ret) { + DRM_ERROR("Failed to enable lvl-vdd reg regulator, %d", ret); + goto lvl_enable_fail; + } + + return 0; + +lvl_enable_fail: + regulator_disable(ctrl->vdda_vreg); +vdda_enable_fail: + regulator_set_load(ctrl->vdda_vreg, VDDA_UA_OFF_LOAD); +vdda_set_fail: + return ret; +} + +static void edp_regulator_disable(struct edp_ctrl *ctrl) +{ + regulator_disable(ctrl->lvl_vreg); + regulator_set_load(ctrl->lvl_vreg, LVL_UA_OFF_LOAD); + regulator_disable(ctrl->vdda_vreg); + regulator_set_load(ctrl->vdda_vreg, VDDA_UA_OFF_LOAD); +} + +static int edp_gpio_config(struct edp_ctrl *ctrl) +{ + struct device *dev = &ctrl->pdev->dev; + int ret; + + ctrl->panel_hpd_gpio = devm_gpiod_get(dev, "panel-hpd", GPIOD_IN); + if (IS_ERR(ctrl->panel_hpd_gpio)) { + ret = PTR_ERR(ctrl->panel_hpd_gpio); + ctrl->panel_hpd_gpio = NULL; + DRM_ERROR("%s: cannot get panel-hpd-gpios, %d\n", __func__, ret); + return ret; + } + + ctrl->panel_en_gpio = devm_gpiod_get(dev, "panel-en", GPIOD_OUT_HIGH); + if (IS_ERR(ctrl->panel_en_gpio)) { + ret = PTR_ERR(ctrl->panel_en_gpio); + ctrl->panel_en_gpio = NULL; + DRM_ERROR("%s: cannot get panel-en-gpios, %d\n", __func__, ret); + return ret; + } + + ctrl->panel_bklt1_gpio = devm_gpiod_get(dev, "panel-bklt1", + GPIOD_OUT_HIGH); + if (IS_ERR(ctrl->panel_bklt1_gpio)) { + ret = PTR_ERR(ctrl->panel_bklt1_gpio); + ctrl->panel_bklt1_gpio = NULL; + DRM_ERROR("%s: cannot get panel-bklt1-gpios, %d\n", __func__, ret); + return ret; + } + + ctrl->panel_bklt2_gpio = devm_gpiod_get(dev, "panel-bklt2", + GPIOD_OUT_HIGH); + if (IS_ERR(ctrl->panel_bklt2_gpio)) { + ret = PTR_ERR(ctrl->panel_bklt2_gpio); + ctrl->panel_bklt2_gpio = NULL; + DRM_ERROR("%s: cannot get panel-bklt2-gpios, %d\n", __func__, ret); + return ret; + } + + ctrl->panel_pwm_gpio = devm_gpiod_get(dev, "panel-pwm", GPIOD_OUT_HIGH); + if (IS_ERR(ctrl->panel_pwm_gpio)) { + ret = PTR_ERR(ctrl->panel_pwm_gpio); + ctrl->panel_pwm_gpio = NULL; + DRM_ERROR("%s: cannot get panel-pwm-gpios, %d\n", __func__, ret); + return ret; + } + + DRM_INFO("gpio on"); + + return 0; +} + +static void edp_ctrl_irq_enable(struct edp_ctrl *ctrl, int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&ctrl->irq_lock, flags); + if (enable) { + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS, + EDP_INTERRUPT_STATUS1_MASK); + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS2, + EDP_INTERRUPT_STATUS2_MASK); + } else { + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS, + EDP_INTERRUPT_STATUS1_ACK); + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS2, + EDP_INTERRUPT_STATUS2_ACK); + } + spin_unlock_irqrestore(&ctrl->irq_lock, flags); +} + +static void edp_fill_link_cfg(struct edp_ctrl *ctrl) +{ + u32 prate; + u32 bpp; + u8 max_lane = drm_dp_max_lane_count(ctrl->dpcd); + + prate = ctrl->pixel_rate; + bpp = ctrl->color_depth * 3; + + /* + * By default, use the maximum link rate and minimum lane count, + * so that we can do rate down shift during link training. + */ + ctrl->link_rate = ctrl->dpcd[DP_MAX_LINK_RATE]; + ctrl->lane_cnt = max_lane; + DRM_INFO("rate=%d lane=%d", ctrl->link_rate, ctrl->lane_cnt); +} + +static void edp_config_ctrl(struct edp_ctrl *ctrl) +{ + u32 config = 0, depth = 0; + u8 *dpcd = ctrl->dpcd; + + /* Default-> LSCLK DIV: 1/4 LCLK */ + config |= (2 << EDP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); + + /* Scrambler reset enable */ + if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP) + config |= EDP_CONFIGURATION_CTRL_ASSR; + + if (ctrl->color_depth == 8) + depth = EDP_8BIT; + else if (ctrl->color_depth == 10) + depth = EDP_10BIT; + else if (ctrl->color_depth == 12) + depth = EDP_12BIT; + else if (ctrl->color_depth == 16) + depth = EDP_16BIT; + config |= depth << EDP_CONFIGURATION_CTRL_BPC_SHIFT; + + /* Num of Lanes */ + config |= ((ctrl->lane_cnt - 1) + << EDP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); + + if (drm_dp_enhanced_frame_cap(dpcd)) + config |= EDP_CONFIGURATION_CTRL_ENHANCED_FRAMING; + + config |= EDP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */ + + /* sync clock & static Mvid */ + config |= EDP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; + config |= EDP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; + + edp_write_link(ctrl->base, REG_EDP_CONFIGURATION_CTRL, config); +} + +static void edp_state_ctrl(struct edp_ctrl *ctrl, u32 state) +{ + edp_write_link(ctrl->base, REG_EDP_STATE_CTRL, state); + /* Make sure H/W status is set */ + wmb(); +} + +static int edp_lane_set_write(struct edp_ctrl *ctrl, + u8 voltage_level, u8 pre_emphasis_level) +{ + int i; + u8 buf[4]; + + if (voltage_level >= DPCD_LINK_VOLTAGE_MAX) + voltage_level |= 0x04; + + if (pre_emphasis_level >= DPCD_LINK_PRE_EMPHASIS_MAX) + pre_emphasis_level |= 0x04; + + pre_emphasis_level <<= 3; + + for (i = 0; i < 4; i++) + buf[i] = voltage_level | pre_emphasis_level; + + DRM_INFO("%s: p|v=0x%x", __func__, voltage_level | pre_emphasis_level); + if (drm_dp_dpcd_write(ctrl->drm_aux, 0x103, buf, 4) < 4) { + DRM_ERROR("%s: Set sw/pe to panel failed\n", __func__); + return -ENOLINK; + } + + return 0; +} + +static int edp_train_pattern_set_write(struct edp_ctrl *ctrl, u8 pattern) +{ + u8 p = pattern; + + DRM_DEBUG_DP("pattern=%x", p); + if (drm_dp_dpcd_write(ctrl->drm_aux, + DP_TRAINING_PATTERN_SET, &p, 1) < 1) { + DRM_ERROR("%s: Set training pattern to panel failed\n", __func__); + return -ENOLINK; + } + + return 0; +} + +static void edp_sink_train_set_adjust(struct edp_ctrl *ctrl, + const u8 *link_status) +{ + int i; + u8 max = 0; + u8 data; + + /* use the max level across lanes */ + for (i = 0; i < ctrl->lane_cnt; i++) { + data = drm_dp_get_adjust_request_voltage(link_status, i); + DRM_DEBUG_DP("lane=%d req_voltage_swing=0x%x", i, data); + if (max < data) + max = data; + } + + ctrl->v_level = max >> DP_TRAIN_VOLTAGE_SWING_SHIFT; + + /* use the max level across lanes */ + max = 0; + for (i = 0; i < ctrl->lane_cnt; i++) { + data = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + DRM_DEBUG_DP("lane=%d req_pre_emphasis=0x%x", i, data); + if (max < data) + max = data; + } + + ctrl->p_level = max >> DP_TRAIN_PRE_EMPHASIS_SHIFT; + DRM_DEBUG_DP("v_level=%d, p_level=%d", ctrl->v_level, ctrl->p_level); +} + +static void edp_host_train_set(struct edp_ctrl *ctrl, u32 train) +{ + int cnt = 10; + u32 data; + u32 shift = train - 1; + + DRM_DEBUG_DP("train=%d", train); + + edp_state_ctrl(ctrl, EDP_STATE_CTRL_LINK_TRAINING_PATTERN1 << shift); + while (--cnt) { + data = edp_read_link(ctrl->base, REG_EDP_MAINLINK_READY); + if (data & (EDP_MAINLINK_READY_TRAIN_PATTERN_1_READY << shift)) + break; + } + + if (cnt == 0) + DRM_DEBUG_DP("%s: set link_train=%d failed\n", __func__, train); +} + +static int edp_voltage_pre_emphasis_set(struct edp_ctrl *ctrl) +{ + DRM_DEBUG_DP("v=%d p=%d", ctrl->v_level, ctrl->p_level); + + msm_edp_v510_phy_config(ctrl->phy, ctrl->v_level, ctrl->p_level); + return edp_lane_set_write(ctrl, ctrl->v_level, ctrl->p_level); +} + +static int edp_start_link_train_1(struct edp_ctrl *ctrl) +{ + u8 link_status[DP_LINK_STATUS_SIZE]; + u8 old_v_level; + int tries; + int ret; + int rlen; + + edp_host_train_set(ctrl, DP_TRAINING_PATTERN_1); + ret = edp_voltage_pre_emphasis_set(ctrl); + if (ret) + return ret; + + ret = edp_train_pattern_set_write(ctrl, + DP_TRAINING_PATTERN_1 | DP_RECOVERED_CLOCK_OUT_EN); + if (ret) + return ret; + + tries = 0; + old_v_level = ctrl->v_level; + while (1) { + drm_dp_link_train_clock_recovery_delay(ctrl->dpcd); + + rlen = drm_dp_dpcd_read_link_status(ctrl->drm_aux, link_status); + if (rlen < DP_LINK_STATUS_SIZE) { + DRM_ERROR("%s: read link status failed\n", __func__); + return -ENOLINK; + } + if (drm_dp_clock_recovery_ok(link_status, ctrl->lane_cnt)) { + ret = 0; + break; + } + + if (ctrl->v_level == DPCD_LINK_VOLTAGE_MAX) { + ret = -1; + break; + } + + if (old_v_level == ctrl->v_level) { + tries++; + if (tries >= 5) { + ret = -1; + break; + } + } else { + tries = 0; + old_v_level = ctrl->v_level; + } + + edp_sink_train_set_adjust(ctrl, link_status); + ret = edp_voltage_pre_emphasis_set(ctrl); + if (ret) + return ret; + } + + return ret; +} + +static int edp_start_link_train_2(struct edp_ctrl *ctrl) +{ + u8 link_status[DP_LINK_STATUS_SIZE]; + int tries = 0; + int ret; + int rlen; + + edp_host_train_set(ctrl, DP_TRAINING_PATTERN_2); + ret = edp_voltage_pre_emphasis_set(ctrl); + if (ret) + return ret; + + ret = edp_train_pattern_set_write(ctrl, + DP_TRAINING_PATTERN_2 | DP_RECOVERED_CLOCK_OUT_EN); + if (ret) + return ret; + + while (1) { + drm_dp_link_train_channel_eq_delay(ctrl->dpcd); + + rlen = drm_dp_dpcd_read_link_status(ctrl->drm_aux, link_status); + if (rlen < DP_LINK_STATUS_SIZE) { + DRM_ERROR("%s: read link status failed\n", __func__); + return -ENOLINK; + } + if (drm_dp_channel_eq_ok(link_status, ctrl->lane_cnt)) { + ret = 0; + break; + } + + tries++; + if (tries > 10) { + ret = -1; + break; + } + + edp_sink_train_set_adjust(ctrl, link_status); + ret = edp_voltage_pre_emphasis_set(ctrl); + if (ret) + return ret; + } + + return ret; +} + +static int edp_link_rate_down_shift(struct edp_ctrl *ctrl) +{ + u32 prate, lrate, bpp; + u8 rate, lane, max_lane; + int changed = 0; + + rate = ctrl->link_rate; + lane = ctrl->lane_cnt; + max_lane = drm_dp_max_lane_count(ctrl->dpcd); + + bpp = ctrl->color_depth * 3; + prate = ctrl->pixel_rate; + prate *= bpp; + prate /= 8; /* in kByte */ + + if (rate > DP_LINK_BW_1_62 && rate <= EDP_LINK_BW_MAX) { + rate -= 4; /* reduce rate */ + changed++; + } + + if (changed) { + if (lane >= 1 && lane < max_lane) + lane <<= 1; /* increase lane */ + + lrate = 270000; /* in kHz */ + lrate *= rate; + lrate /= 10; /* kByte, 10 bits --> 8 bits */ + lrate *= lane; + + DRM_DEBUG_DP("new lrate=%u prate=%u(kHz) rate=%d lane=%d p=%u b=%d", + lrate, prate, rate, lane, + ctrl->pixel_rate, + bpp); + + if (lrate > prate) { + ctrl->link_rate = rate; + ctrl->lane_cnt = lane; + DRM_DEBUG_DP("new rate=%d %d", rate, lane); + return 0; + } + } + + return -EINVAL; +} + +static int edp_clear_training_pattern(struct edp_ctrl *ctrl) +{ + int ret; + + ret = edp_train_pattern_set_write(ctrl, 0); + + drm_dp_link_train_channel_eq_delay(ctrl->dpcd); + + return ret; +} + +static int edp_do_link_train(struct edp_ctrl *ctrl) +{ + u8 values[2], edp_config = 0; + int ret; + + /* + * Set the current link rate and lane cnt to panel. They may have been + * adjusted and the values are different from them in DPCD CAP + */ + values[0] = ctrl->lane_cnt; + values[1] = ctrl->link_rate; + + if (drm_dp_enhanced_frame_cap(ctrl->dpcd)) + values[0] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + if (drm_dp_dpcd_write(ctrl->drm_aux, DP_LINK_BW_SET, &values[1], 1) < 0) + return EDP_TRAIN_FAIL; + + drm_dp_dpcd_write(ctrl->drm_aux, DP_LANE_COUNT_SET, &values[0], 1); + ctrl->v_level = 0; /* start from default level */ + ctrl->p_level = 0; + + values[0] = DP_SPREAD_AMP_0_5; + values[1] = 1; + drm_dp_dpcd_write(ctrl->drm_aux, DP_DOWNSPREAD_CTRL, &values[0], 1); + drm_dp_dpcd_write(ctrl->drm_aux, DP_MAIN_LINK_CHANNEL_CODING_SET, &values[1], 1); + + edp_state_ctrl(ctrl, 0); + if (edp_clear_training_pattern(ctrl)) + return EDP_TRAIN_FAIL; + + ret = edp_start_link_train_1(ctrl); + if (ret < 0) { + if (edp_link_rate_down_shift(ctrl) == 0) { + DRM_ERROR("link reconfig"); + ret = EDP_TRAIN_RECONFIG; + goto clear; + } else { + DRM_ERROR("%s: Training 1 failed", __func__); + ret = EDP_TRAIN_FAIL; + goto clear; + } + } + DRM_INFO("Training 1 completed successfully"); + + edp_state_ctrl(ctrl, 0); + if (edp_clear_training_pattern(ctrl)) + return EDP_TRAIN_FAIL; + + ret = edp_start_link_train_2(ctrl); + if (ret < 0) { + if (edp_link_rate_down_shift(ctrl) == 0) { + DRM_ERROR("link reconfig"); + ret = EDP_TRAIN_RECONFIG; + goto clear; + } else { + DRM_ERROR("%s: Training 2 failed", __func__); + ret = EDP_TRAIN_FAIL; + goto clear; + } + } + DRM_INFO("Training 2 completed successfully"); + + edp_config = DP_ALTERNATE_SCRAMBLER_RESET_ENABLE; + drm_dp_dpcd_write(ctrl->drm_aux, DP_EDP_CONFIGURATION_SET, + &edp_config, 1); + + edp_state_ctrl(ctrl, EDP_STATE_CTRL_SEND_VIDEO); +clear: + edp_clear_training_pattern(ctrl); + + return ret; +} + +static void edp_ctrl_config_misc(struct edp_ctrl *ctrl) +{ + u32 misc_val; + enum edp_color_depth depth = EDP_8BIT; + + misc_val = edp_read_link(ctrl->base, REG_EDP_MISC1_MISC0); + + if (ctrl->color_depth == 8) + depth = EDP_8BIT; + else if (ctrl->color_depth == 10) + depth = EDP_10BIT; + else if (ctrl->color_depth == 12) + depth = EDP_12BIT; + else if (ctrl->color_depth == 16) + depth = EDP_16BIT; + + /* clear bpp bits */ + misc_val &= ~(0x07 << EDP_MISC0_TEST_BITS_DEPTH_SHIFT); + misc_val |= depth << EDP_MISC0_TEST_BITS_DEPTH_SHIFT; + + /* Configure clock to synchronous mode */ + misc_val |= EDP_MISC0_SYNCHRONOUS_CLK; + + DRM_DEBUG_DP("misc settings = 0x%x\n", misc_val); + edp_write_link(ctrl->base, REG_EDP_MISC1_MISC0, misc_val); +} + +static void edp_ctrl_config_msa(struct edp_ctrl *ctrl) +{ + u32 pixel_m, pixel_n; + u32 mvid, nvid, pixel_div = 0, dispcc_input_rate; + unsigned long den, num; + u8 rate = ctrl->link_rate; + u32 stream_rate_khz = ctrl->pixel_rate; + + if (rate == DP_LINK_BW_8_1) + pixel_div = 6; + else if (rate == DP_LINK_BW_1_62 || rate == DP_LINK_BW_2_7) + pixel_div = 2; + else if (rate == DP_LINK_BW_5_4) + pixel_div = 4; + else + DRM_ERROR("Invalid pixel mux divider\n"); + + dispcc_input_rate = (drm_dp_bw_code_to_link_rate(rate) * 10) / pixel_div; + + rational_best_approximation(dispcc_input_rate, stream_rate_khz, + (unsigned long)(1 << 16) - 1, + (unsigned long)(1 << 16) - 1, &den, &num); + + den = ~(den - num); + den = den & 0xFFFF; + pixel_m = num; + pixel_n = den; + + mvid = (pixel_m & 0xFFFF) * 5; + nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF); + + if (rate == DP_LINK_BW_5_4) + nvid *= 2; + + if (rate == DP_LINK_BW_8_1) + nvid *= 3; + + DRM_DEBUG_DP("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + edp_write_link(ctrl->base, REG_EDP_SOFTWARE_MVID, mvid); + edp_write_link(ctrl->base, REG_EDP_SOFTWARE_NVID, nvid); + edp_write_p0(ctrl->base, REG_EDP_DSC_DTO, 0x0); +} + +static void edp_ctrl_config_TU(struct edp_ctrl *ctrl) +{ + int i; + + for (i = 0; i < MAX_TU_TABLE; i++) { + if (tu[i].rate == ctrl->pixel_rate) + break; + } + + edp_write_link(ctrl->base, REG_EDP_VALID_BOUNDARY, + tu[i].valid_boundary); + + edp_write_link(ctrl->base, REG_EDP_TU, tu[i].edp_tu); + + edp_write_link(ctrl->base, REG_EDP_VALID_BOUNDARY_2, + tu[i].valid_boundary2); +} + +static void edp_ctrl_timing_cfg(struct edp_ctrl *ctrl) +{ + struct drm_display_mode *mode = &ctrl->drm_mode; + u32 hstart_from_sync, vstart_from_sync; + u32 data; + + /* Configure eDP timing to HW */ + edp_write_link(ctrl->base, REG_EDP_TOTAL_HOR_VER, + EDP_TOTAL_HOR_VER_HORIZ(mode->htotal) | + EDP_TOTAL_HOR_VER_VERT(mode->vtotal)); + + vstart_from_sync = mode->vtotal - mode->vsync_start; + hstart_from_sync = mode->htotal - mode->hsync_start; + edp_write_link(ctrl->base, REG_EDP_START_HOR_VER_FROM_SYNC, + EDP_START_HOR_VER_FROM_SYNC_HORIZ(hstart_from_sync) | + EDP_START_HOR_VER_FROM_SYNC_VERT(vstart_from_sync)); + + data = EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT( + mode->vsync_end - mode->vsync_start); + data |= EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ( + mode->hsync_end - mode->hsync_start); + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + data |= EDP_HSYNC_VSYNC_WIDTH_POLARITY_NVSYNC; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + data |= EDP_HSYNC_VSYNC_WIDTH_POLARITY_NHSYNC; + edp_write_link(ctrl->base, REG_EDP_HSYNC_VSYNC_WIDTH_POLARITY, data); + + edp_write_link(ctrl->base, REG_EDP_ACTIVE_HOR_VER, + EDP_ACTIVE_HOR_VER_HORIZ(mode->hdisplay) | + EDP_ACTIVE_HOR_VER_VERT(mode->vdisplay)); + +} + +static void edp_mainlink_ctrl(struct edp_ctrl *ctrl, int enable) +{ + u32 data = 0; + + edp_write_link(ctrl->base, REG_EDP_MAINLINK_CTRL, EDP_MAINLINK_CTRL_RESET); + /* Make sure fully reset */ + wmb(); + usleep_range(500, 1000); + + if (enable) { + data = (EDP_MAINLINK_CTRL_ENABLE | + EDP_MAINLINK_FB_BOUNDARY_SEL); + } + + edp_write_link(ctrl->base, REG_EDP_MAINLINK_CTRL, data); +} + +static void edp_ctrl_phy_enable(struct edp_ctrl *ctrl, int enable) +{ + if (enable) { + edp_write_ahb(ctrl->base, REG_EDP_PHY_CTRL, + EDP_PHY_CTRL_SW_RESET | EDP_PHY_CTRL_SW_RESET_PLL); + usleep_range(1000, 1100); + edp_write_ahb(ctrl->base, REG_EDP_PHY_CTRL, 0); + + msm_edp_v510_phy_enable(ctrl->phy); + } +} + +static void edp_ctrl_phy_aux_enable(struct edp_ctrl *ctrl, int enable) +{ + if (ctrl->core_initialized == enable) + return; + + if (enable) { + pm_runtime_get_sync(&ctrl->pdev->dev); + edp_regulator_enable(ctrl); + edp_clk_enable(ctrl, EDP_CORE_PM, 1); + edp_ctrl_phy_enable(ctrl, 1); + msm_edp_v510_aux_ctrl(ctrl->aux, 1); + ctrl->core_initialized = true; + } else { + msm_edp_v510_aux_ctrl(ctrl->aux, 0); + edp_clk_enable(ctrl, EDP_CORE_PM, 0); + edp_regulator_disable(ctrl); + pm_runtime_put_sync(&ctrl->pdev->dev); + ctrl->core_initialized = false; + } +} + +static void edp_ctrl_link_enable(struct edp_ctrl *ctrl, int enable) +{ + unsigned long link_rate; + + link_rate = drm_dp_max_link_rate(ctrl->dpcd); + ctrl->edp_opts.link_rate = link_rate; + ctrl->edp_opts.lanes = drm_dp_max_lane_count(ctrl->dpcd); + + if (enable) { + msm_edp_v510_phy_vm_pe_init(ctrl->phy, &ctrl->edp_opts); + msm_edp_v510_phy_power_on(ctrl->phy); + + /* Enable link channel clocks */ + edp_ctrl_set_clock_rate(ctrl, EDP_CTRL_PM, "ctrl_link", + link_rate * 1000); + edp_clk_enable(ctrl, EDP_CTRL_PM, 1); + + edp_ctrl_set_clock_rate(ctrl, EDP_STREAM_PM, "stream_pixel", + ctrl->pixel_rate * 1000); + edp_clk_enable(ctrl, EDP_STREAM_PM, 1); + + edp_mainlink_ctrl(ctrl, 1); + edp_config_ctrl(ctrl); + edp_ctrl_config_misc(ctrl); + edp_ctrl_timing_cfg(ctrl); + edp_ctrl_config_msa(ctrl); + edp_ctrl_config_TU(ctrl); + + } else { + edp_mainlink_ctrl(ctrl, 0); + edp_clk_enable(ctrl, EDP_STREAM_PM, 0); + edp_clk_enable(ctrl, EDP_CTRL_PM, 0); + } +} + +static int edp_ctrl_training(struct edp_ctrl *ctrl) +{ + int ret; + + /* Do link training only when power is on */ + if (!ctrl->power_on) + return -EINVAL; + +train_start: + ret = edp_do_link_train(ctrl); + if (ret == EDP_TRAIN_RECONFIG) { + /* Re-configure main link */ + edp_ctrl_irq_enable(ctrl, 0); + edp_ctrl_link_enable(ctrl, 0); + + /* Make sure link is fully disabled */ + wmb(); + usleep_range(500, 1000); + + edp_ctrl_phy_enable(ctrl, 1); + edp_ctrl_irq_enable(ctrl, 1); + edp_ctrl_link_enable(ctrl, 1); + goto train_start; + } + + return ret; +} + +static void edp_ctrl_on_worker(struct work_struct *work) +{ + struct edp_ctrl *ctrl = container_of( + work, struct edp_ctrl, on_work); + u8 value; + int ret; + + mutex_lock(&ctrl->dev_mutex); + + if (ctrl->power_on) { + DRM_INFO("already on"); + goto unlock_ret; + } + + edp_ctrl_phy_aux_enable(ctrl, 1); + edp_ctrl_irq_enable(ctrl, 1); + edp_ctrl_link_enable(ctrl, 1); + + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (ctrl->dpcd[DP_DPCD_REV] >= 0x11) { + ret = drm_dp_dpcd_readb(ctrl->drm_aux, DP_SET_POWER, &value); + if (ret < 0) + goto fail; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D0; + + ret = drm_dp_dpcd_writeb(ctrl->drm_aux, DP_SET_POWER, value); + if (ret < 0) + goto fail; + + /* + * According to the DP 1.1 specification, a "Sink Device must + * exit the power saving state within 1 ms" (Section 2.5.3.1, + * Table 5-52, "Sink Control Field" (register 0x600). + */ + usleep_range(1000, 2000); + } + + ctrl->power_on = true; + + /* Start link training */ + ret = edp_ctrl_training(ctrl); + if (ret != EDP_TRAIN_SUCCESS) + goto fail; + + DRM_INFO("DONE"); + goto unlock_ret; + +fail: + edp_ctrl_irq_enable(ctrl, 0); + edp_ctrl_link_enable(ctrl, 0); + edp_ctrl_phy_aux_enable(ctrl, 0); + ctrl->power_on = false; +unlock_ret: + mutex_unlock(&ctrl->dev_mutex); +} + +static void edp_ctrl_off_worker(struct work_struct *work) +{ + struct edp_ctrl *ctrl = container_of( + work, struct edp_ctrl, off_work); + unsigned long time_left; + + mutex_lock(&ctrl->dev_mutex); + + if (!ctrl->power_on) { + DRM_INFO("already off"); + goto unlock_ret; + } + + reinit_completion(&ctrl->idle_comp); + + edp_state_ctrl(ctrl, EDP_STATE_CTRL_PUSH_IDLE); + + time_left = wait_for_completion_timeout(&ctrl->idle_comp, + msecs_to_jiffies(500)); + if (!time_left) + DRM_ERROR("%s: idle pattern timedout\n", __func__); + + edp_state_ctrl(ctrl, 0); + + /* DP_SET_POWER register is only available on DPCD v1.1 and later */ + if (ctrl->dpcd[DP_DPCD_REV] >= 0x11) { + u8 value; + int ret; + + ret = drm_dp_dpcd_readb(ctrl->drm_aux, DP_SET_POWER, &value); + if (ret > 0) { + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D3; + + drm_dp_dpcd_writeb(ctrl->drm_aux, DP_SET_POWER, value); + } + } + + edp_ctrl_irq_enable(ctrl, 0); + + edp_ctrl_link_enable(ctrl, 0); + + edp_ctrl_phy_aux_enable(ctrl, 0); + + ctrl->power_on = false; + +unlock_ret: + mutex_unlock(&ctrl->dev_mutex); +} + +irqreturn_t msm_edp_v510_ctrl_irq(struct edp_ctrl *ctrl) +{ + u32 isr1, isr2, mask1, mask2; + u32 ack; + + spin_lock(&ctrl->irq_lock); + isr1 = edp_read_ahb(ctrl->base, REG_EDP_INTR_STATUS); + isr2 = edp_read_ahb(ctrl->base, REG_EDP_INTR_STATUS2); + + mask1 = isr1 & EDP_INTERRUPT_STATUS1_MASK; + mask2 = isr2 & EDP_INTERRUPT_STATUS2_MASK; + + isr1 &= ~mask1; /* remove masks bit */ + isr2 &= ~mask2; + + DRM_DEBUG_DP("isr=%x mask=%x isr2=%x mask2=%x", + isr1, mask1, isr2, mask2); + + ack = isr1 & EDP_INTERRUPT_STATUS1; + ack <<= 1; /* ack bits */ + ack |= mask1; + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS, ack); + + ack = isr2 & EDP_INTERRUPT_STATUS2; + ack <<= 1; /* ack bits */ + ack |= mask2; + edp_write_ahb(ctrl->base, REG_EDP_INTR_STATUS2, ack); + spin_unlock(&ctrl->irq_lock); + + if (isr2 & EDP_INTR_READY_FOR_VIDEO) + DRM_INFO("edp_video_ready"); + + if (isr2 & EDP_INTR_IDLE_PATTERN_SENT) { + DRM_INFO("idle_patterns_sent"); + complete(&ctrl->idle_comp); + } + + msm_edp_v510_aux_irq(ctrl->aux, isr1); + + return IRQ_HANDLED; +} + +void msm_edp_v510_ctrl_power(struct edp_ctrl *ctrl, bool on) +{ + if (on) + queue_work(ctrl->workqueue, &ctrl->on_work); + else + queue_work(ctrl->workqueue, &ctrl->off_work); +} + +int msm_edp_v510_ctrl_init(struct msm_edp_v510 *edp) +{ + struct edp_ctrl *ctrl = NULL; + struct device *dev = &edp->base.pdev->dev; + int ret; + + if (!edp) { + DRM_ERROR("%s: edp is NULL!\n", __func__); + return -EINVAL; + } + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + edp->ctrl = ctrl; + ctrl->pdev = edp->base.pdev; + + ctrl->base = msm_ioremap(ctrl->pdev, "edp_ctrl", "eDP_CTRL"); + if (IS_ERR(ctrl->base)) + return PTR_ERR(ctrl->base); + + ctrl->phy_base = msm_ioremap(ctrl->pdev, "edp_phy", "eDP_PHY"); + if (IS_ERR(ctrl->phy_base)) + return PTR_ERR(ctrl->phy_base); + + /* Get regulator, clock, gpio, pwm */ + ret = edp_regulator_init(ctrl); + if (ret) { + DRM_ERROR("%s:regulator init fail\n", __func__); + return ret; + } + ret = edp_clk_init(ctrl); + if (ret) { + DRM_ERROR("%s:clk init fail\n", __func__); + return ret; + } + ret = edp_gpio_config(ctrl); + if (ret) { + DRM_ERROR("%s:failed to configure GPIOs: %d", __func__, ret); + return ret; + } + + /* Init aux and phy */ + ctrl->aux = msm_edp_v510_aux_init(dev, ctrl->base, &ctrl->drm_aux); + if (!ctrl->aux || !ctrl->drm_aux) { + DRM_ERROR("%s:failed to init aux\n", __func__); + return -ENOMEM; + } + + ctrl->phy = msm_edp_v510_phy_init(dev, ctrl->phy_base, &ctrl->edp_opts); + if (!ctrl->phy) { + DRM_ERROR("%s:failed to init phy\n", __func__); + ret = -ENOMEM; + goto err_destory_aux; + } + + pm_runtime_enable(dev); + spin_lock_init(&ctrl->irq_lock); + mutex_init(&ctrl->dev_mutex); + init_completion(&ctrl->idle_comp); + + /* setup workqueue */ + ctrl->workqueue = alloc_ordered_workqueue("edp_drm_work", 0); + INIT_WORK(&ctrl->on_work, edp_ctrl_on_worker); + INIT_WORK(&ctrl->off_work, edp_ctrl_off_worker); + + return 0; + +err_destory_aux: + msm_edp_v510_aux_destroy(dev, ctrl->aux); + ctrl->aux = NULL; + return ret; +} + +void msm_edp_v510_ctrl_destroy(struct edp_ctrl *ctrl) +{ + if (!ctrl) + return; + + if (ctrl->workqueue) { + flush_workqueue(ctrl->workqueue); + destroy_workqueue(ctrl->workqueue); + ctrl->workqueue = NULL; + } + + if (ctrl->aux) { + msm_edp_v510_aux_destroy(&ctrl->pdev->dev, ctrl->aux); + ctrl->aux = NULL; + } + + edp_clk_deinit(ctrl); + + kfree(ctrl->edid); + ctrl->edid = NULL; + + mutex_destroy(&ctrl->dev_mutex); +} + +bool msm_edp_v510_ctrl_panel_connected(struct edp_ctrl *ctrl) +{ + mutex_lock(&ctrl->dev_mutex); + if (ctrl->edp_connected) { + mutex_unlock(&ctrl->dev_mutex); + return true; + } + + if (!ctrl->power_on) { + edp_ctrl_phy_aux_enable(ctrl, 1); + edp_ctrl_irq_enable(ctrl, 1); + } + + if (drm_dp_dpcd_read(ctrl->drm_aux, DP_DPCD_REV, ctrl->dpcd, + DP_RECEIVER_CAP_SIZE) < DP_RECEIVER_CAP_SIZE) { + DRM_ERROR("%s: AUX channel is NOT ready\n", __func__); + memset(ctrl->dpcd, 0, DP_RECEIVER_CAP_SIZE); + + if (!ctrl->power_on) { + edp_ctrl_irq_enable(ctrl, 0); + edp_ctrl_phy_aux_enable(ctrl, 0); + } + + } else { + ctrl->edp_connected = true; + } + + + DRM_INFO("connect status=%d", ctrl->edp_connected); + + mutex_unlock(&ctrl->dev_mutex); + + return ctrl->edp_connected; +} + +int msm_edp_v510_ctrl_get_panel_info(struct edp_ctrl *ctrl, + struct drm_connector *connector, struct edid **edid) +{ + int ret = 0; + + mutex_lock(&ctrl->dev_mutex); + + if (ctrl->edid) { + if (edid) { + DRM_DEBUG_DP("Just return edid buffer"); + *edid = ctrl->edid; + } + goto unlock_ret; + } + + if (!ctrl->power_on && !ctrl->edp_connected) { + edp_ctrl_phy_aux_enable(ctrl, 1); + edp_ctrl_irq_enable(ctrl, 1); + } + + /* Initialize link rate as panel max link rate */ + ctrl->link_rate = ctrl->dpcd[DP_MAX_LINK_RATE]; + + + ctrl->edid = drm_get_edid(connector, &ctrl->drm_aux->ddc); + if (!ctrl->edid) { + DRM_ERROR("%s: edid read fail\n", __func__); + if (!ctrl->power_on) { + edp_ctrl_irq_enable(ctrl, 0); + edp_ctrl_phy_aux_enable(ctrl, 0); + } + goto unlock_ret; + } + + if (edid) + *edid = ctrl->edid; + +unlock_ret: + mutex_unlock(&ctrl->dev_mutex); + return ret; +} + +int msm_edp_v510_ctrl_mode_set(struct edp_ctrl *ctrl, + const struct drm_display_mode *mode, + const struct drm_display_info *info) +{ + /* + * Need to keep color depth, pixel rate and + * interlaced information in ctrl context + */ + ctrl->color_depth = info->bpc; + ctrl->pixel_rate = mode->clock; + + memcpy(&ctrl->drm_mode, mode, sizeof(*mode)); + + ctrl->interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + + /* Fill initial link config based on passed in timing */ + edp_fill_link_cfg(ctrl); + + return 0; +} + + +bool msm_edp_v510_ctrl_pixel_clock_valid(struct edp_ctrl *ctrl, u32 pixel_rate) +{ + u32 link_clock = 0; + unsigned long link_bw = 0, stream_bw = 0; + + link_clock = drm_dp_bw_code_to_link_rate(ctrl->link_rate); + link_bw = link_clock * ctrl->lane_cnt; + stream_bw = pixel_rate * ctrl->color_depth * 3 / 8; + + if (stream_bw > link_bw) { + DRM_ERROR("pixel clock %d(kHz) not supported", pixel_rate); + return false; + } + + return true; +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_phy.c b/drivers/gpu/drm/msm/edp/v510/edp_v510_phy.c new file mode 100644 index 0000000..3c575f4 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_phy.c @@ -0,0 +1,641 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edp_v510.h" +#include "edp_v510_reg.h" + +#define MSM_EDP_PLL_OFFSET 0x0000 +#define MSM_EDP_TX0_OFFSET 0x0200 +#define MSM_EDP_TX1_OFFSET 0x0600 +#define MSM_EDP_PHY_OFFSET 0x0a00 + +struct edp_phy_clks { + struct edp_phy *edp_phy; + struct clk_hw edp_link_hw; + struct clk_hw edp_pixel_hw; +}; + +struct edp_phy { + void __iomem *base; + struct edp_phy_opts *edp_opts; + struct edp_phy_clks *edp_clks; +}; + +static inline u32 edp_pll_read(struct edp_phy *phy, u32 offset) +{ + offset += MSM_EDP_PLL_OFFSET; + return readl_relaxed(phy->base + offset); +} + +static inline u32 edp_tx0_read(struct edp_phy *phy, u32 offset) +{ + offset += MSM_EDP_TX0_OFFSET; + return readl_relaxed(phy->base + offset); +} + +static inline u32 edp_tx1_read(struct edp_phy *phy, u32 offset) +{ + offset += MSM_EDP_TX1_OFFSET; + return readl_relaxed(phy->base + offset); +} + +static inline u32 edp_phy_read(struct edp_phy *phy, u32 offset) +{ + offset += MSM_EDP_PHY_OFFSET; + return readl_relaxed(phy->base + offset); +} + +static inline void edp_pll_write(struct edp_phy *phy, u32 offset, u32 data) +{ + offset += MSM_EDP_PLL_OFFSET; + writel(data, phy->base + offset); +} + +static inline void edp_tx0_write(struct edp_phy *phy, u32 offset, u32 data) +{ + offset += MSM_EDP_TX0_OFFSET; + writel(data, phy->base + offset); +} + +static inline void edp_tx1_write(struct edp_phy *phy, u32 offset, u32 data) +{ + offset += MSM_EDP_TX1_OFFSET; + writel(data, phy->base + offset); +} + +static inline void edp_phy_write(struct edp_phy *phy, u32 offset, u32 data) +{ + offset += MSM_EDP_PHY_OFFSET; + writel(data, phy->base + offset); +} + +static int edp_pixel_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + switch (req->rate) { + case 1620000000UL / 2: + case 2160000000UL / 2: + case 2430000000UL / 2: + case 2700000000UL / 2: + case 5940000000UL / 6: + return 0; + default: + return -EINVAL; + } +} + +static unsigned long +edp_pixel_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct edp_phy_clks *edp_clks; + struct edp_phy *edp_phy; + struct edp_phy_opts *opts; + + edp_clks = container_of(hw, struct edp_phy_clks, edp_pixel_hw); + edp_phy = edp_clks->edp_phy; + opts = edp_phy->edp_opts; + + switch (opts->link_rate) { + case 162000: + return 1620000000UL / 2; + break; + case 216000: + return 2160000000UL / 2; + break; + case 243000: + return 2430000000UL / 2; + break; + case 270000: + return 2700000000UL / 2; + break; + case 324000: + return 3240000000UL / 4; + break; + case 432000: + return 4320000000UL / 4; + break; + case 540000: + return 5400000000UL / 4; + break; + case 594000: + return 5940000000UL / 6; + case 810000: + return 8100000000UL / 6; + default: + return 0; + } +} + +static const struct clk_ops edp_pixel_clk_ops = { + .determine_rate = edp_pixel_clk_determine_rate, + .recalc_rate = edp_pixel_clk_recalc_rate, +}; + +static int edp_link_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + switch (req->rate) { + case 162000000: + case 216000000: + case 243000000: + case 270000000: + case 324000000: + case 432000000: + case 540000000: + case 594000000: + case 810000000: + return 0; + default: + return -EINVAL; + } +} + +static unsigned long +edp_link_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct edp_phy_clks *edp_clks; + struct edp_phy *edp_phy; + struct edp_phy_opts *opts; + + edp_clks = container_of(hw, struct edp_phy_clks, edp_link_hw); + edp_phy = edp_clks->edp_phy; + opts = edp_phy->edp_opts; + + switch (opts->link_rate) { + case 162000: + case 216000: + case 243000: + case 270000: + case 324000: + case 432000: + case 540000: + case 594000: + case 810000: + return opts->link_rate * 1000; + default: + return 0; + } +} + +static const struct clk_ops edp_link_clk_ops = { + .determine_rate = edp_link_clk_determine_rate, + .recalc_rate = edp_link_clk_recalc_rate, +}; + +static struct clk_hw * +edp_clks_hw_get(struct of_phandle_args *clkspec, void *data) +{ + struct edp_phy_clks *edp_clks = data; + unsigned int idx = clkspec->args[0]; + + if (idx >= 2) { + pr_err("%s: invalid index %u\n", __func__, idx); + return ERR_PTR(-EINVAL); + } + + if (idx == 0) + return &edp_clks->edp_link_hw; + + return &edp_clks->edp_pixel_hw; +} + +static void edp_clk_release_provider(void *res) +{ + of_clk_del_provider(res); +} + +static int edp_phy_clks_register(struct device *dev, struct edp_phy *edp_phy) +{ + struct clk_init_data init = { }; + struct edp_phy_clks *edp_clks; + int ret; + + edp_clks = devm_kzalloc(dev, sizeof(*edp_clks), GFP_KERNEL); + if (!edp_clks) + return -ENOMEM; + + edp_clks->edp_phy = edp_phy; + edp_phy->edp_clks = edp_clks; + + init.ops = &edp_link_clk_ops; + init.name = "edp_phy_pll_link_clk"; + edp_clks->edp_link_hw.init = &init; + ret = devm_clk_hw_register(dev, &edp_clks->edp_link_hw); + if (ret) + return ret; + + init.ops = &edp_pixel_clk_ops; + init.name = "edp_phy_pll_vco_div_clk"; + edp_clks->edp_pixel_hw.init = &init; + ret = devm_clk_hw_register(dev, &edp_clks->edp_pixel_hw); + if (ret) + return ret; + + ret = of_clk_add_hw_provider(dev->of_node, edp_clks_hw_get, edp_clks); + if (ret) + return ret; + + /* + * Roll a devm action because the clock provider is the child node, but + * the child node is not actually a device. + */ + ret = devm_add_action(dev, edp_clk_release_provider, dev->of_node); + if (ret) + edp_clk_release_provider(dev->of_node); + + return ret; +} + +static void edp_phy_ssc_en(struct edp_phy *edp_phy, bool en) +{ + if (en) { + edp_pll_write(edp_phy, 0x10, 0x01); + edp_pll_write(edp_phy, 0x14, 0x00); + edp_pll_write(edp_phy, 0x1c, 0x36); + edp_pll_write(edp_phy, 0x20, 0x01); + edp_pll_write(edp_phy, 0x24, 0x5c); + edp_pll_write(edp_phy, 0x28, 0x08); + } else { + edp_pll_write(edp_phy, 0x10, 0x00); + } +} + +int msm_edp_v510_phy_enable(struct edp_phy *edp_phy) +{ + u32 status; + + edp_phy_write(edp_phy, EDP_PHY_PD_CTL, 0x7D); + edp_pll_write(edp_phy, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x17); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG1, 0x13); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG2, 0x24); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG3, 0x00); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG4, 0x0a); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG5, 0x26); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG6, 0x0a); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG7, 0x03); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG8, 0xB7); + edp_phy_write(edp_phy, EDP_PHY_AUX_CFG9, 0x03); + edp_phy_write(edp_phy, EDP_PHY_AUX_INTERRUPT_MASK, 0x1f); + + edp_phy_write(edp_phy, EDP_PHY_MODE, 0xFC); + + if (readl_poll_timeout_atomic((edp_phy->base + + MSM_EDP_PLL_OFFSET + QSERDES_COM_CMN_STATUS), + status, ((status & BIT(7)) > 0), 5, 100)) + DRM_ERROR("%s: refgen not ready. Status=0x%x\n", __func__, status); + + edp_tx0_write(edp_phy, TXn_LDO_CONFIG, 0x01); + edp_tx1_write(edp_phy, TXn_LDO_CONFIG, 0x01); + edp_tx0_write(edp_phy, TXn_LANE_MODE_1, 0x00); + edp_tx1_write(edp_phy, TXn_LANE_MODE_1, 0x00); + + return 0; +} + +static const u8 edp_hbr2_pre_emphasis[4][4] = { + {0x08, 0x11, 0x17, 0x1B}, /* pe0, 0 db */ + {0x00, 0x0C, 0x13, 0xFF}, /* pe1, 3.5 db */ + {0x05, 0x10, 0xFF, 0xFF}, /* pe2, 6.0 db */ + {0x00, 0xFF, 0xFF, 0xFF} /* pe3, 9.5 db */ +}; + +static const u8 edp_hbr2_voltage_swing[4][4] = { + {0x0A, 0x11, 0x17, 0x1F}, /* sw0, 0.4v */ + {0x0C, 0x14, 0x1D, 0xFF}, /* sw1, 0.6 v */ + {0x15, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */ + {0x17, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ +}; + +void msm_edp_v510_phy_vm_pe_init(struct edp_phy *edp_phy, struct edp_phy_opts *opts) +{ + + edp_phy->edp_opts = opts; + + edp_tx0_write(edp_phy, TXn_TX_DRV_LVL, edp_hbr2_voltage_swing[0][0]); + edp_tx0_write(edp_phy, TXn_TX_EMP_POST1_LVL, + edp_hbr2_pre_emphasis[0][0]); + edp_tx1_write(edp_phy, TXn_TX_DRV_LVL, edp_hbr2_voltage_swing[0][0]); + edp_tx1_write(edp_phy, TXn_TX_EMP_POST1_LVL, + edp_hbr2_pre_emphasis[0][0]); + + edp_tx0_write(edp_phy, TXn_HIGHZ_DRVR_EN, 4); + edp_tx0_write(edp_phy, TXn_TRANSCEIVER_BIAS_EN, 3); + edp_tx1_write(edp_phy, TXn_HIGHZ_DRVR_EN, 7); + edp_tx1_write(edp_phy, TXn_TRANSCEIVER_BIAS_EN, 0); + edp_phy_write(edp_phy, EDP_PHY_CFG_1, 3); + +} + +void msm_edp_v510_phy_config(struct edp_phy *edp_phy, u8 v_level, u8 p_level) +{ + edp_tx0_write(edp_phy, TXn_TX_DRV_LVL, + edp_hbr2_voltage_swing[v_level][p_level]); + edp_tx0_write(edp_phy, TXn_TX_EMP_POST1_LVL, + edp_hbr2_pre_emphasis[v_level][p_level]); + + edp_tx1_write(edp_phy, TXn_TX_DRV_LVL, + edp_hbr2_voltage_swing[v_level][p_level]); + edp_tx1_write(edp_phy, TXn_TX_EMP_POST1_LVL, + edp_hbr2_pre_emphasis[v_level][p_level]); +} + +static void edp_pll_vco_init(struct edp_phy *edp_phy) +{ + edp_phy_ssc_en(edp_phy, true); + edp_pll_write(edp_phy, QSERDES_COM_SVS_MODE_CLK_SEL, 0x01); + edp_pll_write(edp_phy, QSERDES_COM_SYSCLK_EN_SEL, 0x0b); + edp_pll_write(edp_phy, QSERDES_COM_SYS_CLK_CTRL, 0x02); + edp_pll_write(edp_phy, QSERDES_COM_CLK_ENABLE1, 0x0c); + edp_pll_write(edp_phy, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); + edp_pll_write(edp_phy, QSERDES_COM_CLK_SEL, 0x30); + edp_pll_write(edp_phy, QSERDES_COM_PLL_IVCO, 0x07); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP_EN, 0x04); + edp_pll_write(edp_phy, QSERDES_COM_PLL_CCTRL_MODE0, 0x36); + edp_pll_write(edp_phy, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + edp_pll_write(edp_phy, QSERDES_COM_CP_CTRL_MODE0, 0x06); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_CMN_CONFIG, 0x02); + edp_pll_write(edp_phy, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x3f); + edp_pll_write(edp_phy, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE_MAP, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_BG_TIMER, 0x0a); + edp_pll_write(edp_phy, QSERDES_COM_CORECLK_DIV_MODE0, 0x14); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE_CTRL, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x17); + edp_pll_write(edp_phy, QSERDES_COM_CORE_CLK_EN, 0x0f); + + switch (edp_phy->edp_opts->link_rate) { + case 162000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x05); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x69); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x80); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x07); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x6f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x08); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0xa0); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x03); + break; + case 216000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x04); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x70); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x08); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x3f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x0b); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0x34); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x03); + break; + case 243000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x04); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x7e); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x09); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0xa7); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x0c); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0x5c); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x02); + break; + case 270000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x03); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x69); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x80); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x07); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x0f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x0e); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0xa0); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x03); + break; + case 324000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x03); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x7e); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x09); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0xdf); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x10); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0x5c); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x02); + break; + case 432000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x01); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x70); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x08); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x7f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x16); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0x34); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x03); + break; + case 540000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x01); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x8c); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x0a); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x1f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x1c); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0x84); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x01); + break; + case 594000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x01); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x9a); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x0b); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0xef); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x1e); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0xac); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x00); + break; + case 810000: + edp_pll_write(edp_phy, QSERDES_COM_HSCLK_SEL, 0x00); + edp_pll_write(edp_phy, QSERDES_COM_DEC_START_MODE0, 0x69); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, 0x80); + edp_pll_write(edp_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, 0x07); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP1_MODE0, 0x2f); + edp_pll_write(edp_phy, QSERDES_COM_LOCK_CMP2_MODE0, 0x2a); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE1_MODE0, 0xa0); + edp_pll_write(edp_phy, QSERDES_COM_VCO_TUNE2_MODE0, 0x03); + break; + default: + DRM_ERROR("%s: Invalid link rate. rate=%lu\n", __func__, + edp_phy->edp_opts->link_rate); + break; + } +} + +static void edp_lanes_init(struct edp_phy *edp_phy) +{ + edp_tx0_write(edp_phy, TXn_TRANSCEIVER_BIAS_EN, 0x03); + edp_tx0_write(edp_phy, TXn_CLKBUF_ENABLE, 0x0f); + edp_tx0_write(edp_phy, TXn_RESET_TSYNC_EN, 0x03); + edp_tx0_write(edp_phy, TXn_TRAN_DRVR_EMP_EN, 0x01); + edp_tx0_write(edp_phy, TXn_TX_BAND, 0x4); + + edp_tx1_write(edp_phy, TXn_TRANSCEIVER_BIAS_EN, 0x03); + edp_tx1_write(edp_phy, TXn_CLKBUF_ENABLE, 0x0f); + edp_tx1_write(edp_phy, TXn_RESET_TSYNC_EN, 0x03); + edp_tx1_write(edp_phy, TXn_TRAN_DRVR_EMP_EN, 0x01); + edp_tx1_write(edp_phy, TXn_TX_BAND, 0x4); +} + +static void edp_lanes_configure(struct edp_phy *edp_phy) +{ + edp_tx0_write(edp_phy, TXn_HIGHZ_DRVR_EN, 0x1f); + edp_tx0_write(edp_phy, TXn_HIGHZ_DRVR_EN, 0x04); + edp_tx0_write(edp_phy, TXn_TX_POL_INV, 0x00); + + edp_tx1_write(edp_phy, TXn_HIGHZ_DRVR_EN, 0x1f); + edp_tx1_write(edp_phy, TXn_HIGHZ_DRVR_EN, 0x04); + edp_tx1_write(edp_phy, TXn_TX_POL_INV, 0x00); + + edp_tx1_write(edp_phy, TXn_HIGHZ_DRVR_EN, 0x04); + edp_tx1_write(edp_phy, TXn_TX_POL_INV, 0x00); + + edp_tx0_write(edp_phy, TXn_TX_DRV_LVL_OFFSET, 0x10); + edp_tx1_write(edp_phy, TXn_TX_DRV_LVL_OFFSET, 0x10); + + edp_tx0_write(edp_phy, TXn_RES_CODE_LANE_OFFSET_TX0, 0x11); + edp_tx0_write(edp_phy, TXn_RES_CODE_LANE_OFFSET_TX1, 0x11); + + edp_tx1_write(edp_phy, TXn_RES_CODE_LANE_OFFSET_TX0, 0x11); + edp_tx1_write(edp_phy, TXn_RES_CODE_LANE_OFFSET_TX1, 0x11); + + edp_tx0_write(edp_phy, TXn_TX_EMP_POST1_LVL, 0x00); + edp_tx0_write(edp_phy, TXn_TX_DRV_LVL, 0x18); + edp_tx1_write(edp_phy, TXn_TX_EMP_POST1_LVL, 0x00); + edp_tx1_write(edp_phy, TXn_TX_DRV_LVL, 0x18); +} + +static int edp_pll_vco_configure(struct edp_phy *edp_phy) +{ + struct edp_phy_clks *edp_clks = edp_phy->edp_clks; + u32 phy_vco_div = 0, status; + unsigned long pixel_freq = 0; + + switch (edp_phy->edp_opts->link_rate) { + case 162000: + phy_vco_div = 2; + pixel_freq = 1620000000UL / 2; + break; + case 216000: + phy_vco_div = 1; + pixel_freq = 2160000000UL / 2; + break; + case 243000: + phy_vco_div = 1; + pixel_freq = 2430000000UL / 2; + break; + case 270000: + phy_vco_div = 1; + pixel_freq = 2700000000UL / 2; + break; + case 324000: + phy_vco_div = 2; + pixel_freq = 3240000000UL / 4; + break; + case 432000: + phy_vco_div = 2; + pixel_freq = 4320000000UL / 4; + break; + case 540000: + phy_vco_div = 2; + pixel_freq = 5400000000UL / 4; + break; + case 594000: + phy_vco_div = 0; + pixel_freq = 5940000000UL / 6; + break; + case 810000: + phy_vco_div = 0; + pixel_freq = 8100000000UL / 6; + break; + default: + DRM_ERROR("%s: Invalid link rate. rate=%lu\n", __func__, + edp_phy->edp_opts->link_rate); + break; + } + + edp_phy_write(edp_phy, EDP_PHY_VCO_DIV, phy_vco_div); + + clk_set_rate(edp_clks->edp_link_hw.clk, + edp_phy->edp_opts->link_rate * 1000); + clk_set_rate(edp_clks->edp_pixel_hw.clk, pixel_freq); + + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x01); + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x05); + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x01); + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x09); + + edp_pll_write(edp_phy, QSERDES_COM_RESETSM_CNTRL, 0x20); + + if (readl_poll_timeout_atomic((edp_phy->base + + MSM_EDP_PLL_OFFSET + QSERDES_COM_C_READY_STATUS), + status, ((status & BIT(0)) > 0), 500, 10000)) { + DRM_ERROR("%s: PLL not locked. Status=0x%x\n", __func__, status); + return -ETIMEDOUT; + } + + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x19); + edp_lanes_configure(edp_phy); + edp_phy_write(edp_phy, EDP_PHY_CFG_1, 0x03); + + if (readl_poll_timeout_atomic((edp_phy->base + + MSM_EDP_PHY_OFFSET + EDP_PHY_STATUS), + status, ((status & BIT(1)) > 0), 500, 10000)) { + DRM_ERROR("%s: PHY not ready. Status=0x%x\n", __func__, status); + return -ETIMEDOUT; + } + + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x18); + udelay(2000); + edp_phy_write(edp_phy, EDP_PHY_CFG, 0x19); + + return readl_poll_timeout_atomic((edp_phy->base + + MSM_EDP_PLL_OFFSET + QSERDES_COM_C_READY_STATUS), + status, ((status & BIT(0)) > 0), 500, 10000); + +} + +int msm_edp_v510_phy_power_on(struct edp_phy *edp_phy) +{ + int ret = 0; + + edp_pll_vco_init(edp_phy); + + edp_phy_write(edp_phy, EDP_PHY_TX0_TX1_LANE_CTL, 0x05); + edp_phy_write(edp_phy, EDP_PHY_TX2_TX3_LANE_CTL, 0x05); + + edp_lanes_init(edp_phy); + + ret = edp_pll_vco_configure(edp_phy); + + return ret; +} + +void *msm_edp_v510_phy_init(struct device *dev, void __iomem *regbase, + struct edp_phy_opts *opts) +{ + struct edp_phy *phy = NULL; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return NULL; + + phy->base = regbase; + phy->edp_opts = opts; + edp_phy_clks_register(dev, phy); + + return phy; +} diff --git a/drivers/gpu/drm/msm/edp/v510/edp_v510_reg.h b/drivers/gpu/drm/msm/edp/v510/edp_v510_reg.h new file mode 100644 index 0000000..10b4428 --- /dev/null +++ b/drivers/gpu/drm/msm/edp/v510/edp_v510_reg.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014-2021, The Linux Foundation. All rights reserved. + */ + +#ifndef EDP_V510_REG +#define EDP_V510_REG + +enum edp_color_depth { + EDP_6BIT = 0, + EDP_8BIT = 1, + EDP_10BIT = 2, + EDP_12BIT = 3, + EDP_16BIT = 4, +}; + +enum edp_component_format { + EDP_RGB = 0, + EDP_YUV422 = 1, + EDP_YUV444 = 2, +}; + +/* DP_TX Registers */ +#define REG_EDP_HW_VERSION (0x00000000) + +#define REG_EDP_SW_RESET (0x00000010) +#define DP_SW_RESET (0x00000001) + +#define REG_EDP_PHY_CTRL (0x00000014) +#define EDP_PHY_CTRL_SW_RESET_PLL (0x00000001) +#define EDP_PHY_CTRL_SW_RESET (0x00000004) + +#define REG_EDP_CLK_CTRL (0x00000018) +#define REG_EDP_CLK_ACTIVE (0x0000001C) + +#define REG_EDP_INTR_STATUS (0x00000020) +#define EDP_INTR_AUX_I2C_DONE BIT(3) +#define EDP_INTR_WRONG_ADDR BIT(6) +#define EDP_INTR_TIMEOUT BIT(9) +#define EDP_INTR_NACK_DEFER BIT(12) +#define EDP_INTR_WRONG_DATA_CNT BIT(15) +#define EDP_INTR_I2C_NACK BIT(18) +#define EDP_INTR_I2C_DEFER BIT(21) +#define EDP_INTR_PLL_UNLOCKED BIT(24) +#define EDP_INTR_AUX_ERROR BIT(27) + +#define REG_EDP_INTR_STATUS2 (0x00000024) +#define EDP_INTR_READY_FOR_VIDEO BIT(0) +#define EDP_INTR_IDLE_PATTERN_SENT BIT(3) +#define EDP_INTR_FRAME_END BIT(6) +#define EDP_INTR_CRC_UPDATED BIT(9) +#define EDP_INTR_SST_FIFO_UNDERFLOW BIT(28) + + +#define REG_EDP_DP_HPD_CTRL (0x00000000) +#define EDP_DP_HPD_CTRL_HPD_EN (0x00000001) + +#define REG_EDP_DP_HPD_INT_STATUS (0x00000004) + +#define REG_EDP_DP_HPD_INT_ACK (0x00000008) +#define EDP_DP_HPD_PLUG_INT_ACK (0x00000001) +#define EDP_DP_IRQ_HPD_INT_ACK (0x00000002) +#define EDP_DP_HPD_REPLUG_INT_ACK (0x00000004) +#define EDP_DP_HPD_UNPLUG_INT_ACK (0x00000008) +#define EDP_DP_HPD_STATE_STATUS_BITS_MASK (0x0000000F) +#define EDP_DP_HPD_STATE_STATUS_BITS_SHIFT (0x1C) + +#define REG_EDP_DP_HPD_INT_MASK (0x0000000C) +#define EDP_DP_HPD_PLUG_INT_MASK (0x00000001) +#define EDP_DP_IRQ_HPD_INT_MASK (0x00000002) +#define EDP_DP_HPD_REPLUG_INT_MASK (0x00000004) +#define EDP_DP_HPD_UNPLUG_INT_MASK (0x00000008) +#define EDP_DP_HPD_INT_MASK (EDP_DP_HPD_PLUG_INT_MASK | \ + EDP_DP_IRQ_HPD_INT_MASK | \ + EDP_DP_HPD_REPLUG_INT_MASK | \ + EDP_DP_HPD_UNPLUG_INT_MASK) +#define EDP_DP_HPD_STATE_STATUS_CONNECTED (0x40000000) +#define EDP_DP_HPD_STATE_STATUS_PENDING (0x20000000) +#define EDP_DP_HPD_STATE_STATUS_DISCONNECTED (0x00000000) +#define EDP_DP_HPD_STATE_STATUS_MASK (0xE0000000) + +#define REG_EDP_DP_HPD_REFTIMER (0x00000018) +#define EDP_DP_HPD_REFTIMER_ENABLE (1 << 16) + +#define REG_EDP_DP_HPD_EVENT_TIME_0 (0x0000001C) +#define REG_EDP_DP_HPD_EVENT_TIME_1 (0x00000020) +#define EDP_DP_HPD_EVENT_TIME_0_VAL (0x3E800FA) +#define EDP_DP_HPD_EVENT_TIME_1_VAL (0x1F407D0) + +#define REG_EDP_AUX_CTRL (0x00000030) +#define EDP_AUX_CTRL_ENABLE (0x00000001) +#define EDP_AUX_CTRL_RESET (0x00000002) + +#define REG_EDP_AUX_DATA (0x00000034) +#define EDP_AUX_DATA_READ (0x00000001) +#define EDP_AUX_DATA_DATA__MASK (0x0000ff00) +#define EDP_AUX_DATA_DATA__SHIFT (8) +static inline uint32_t EDP_AUX_DATA_DATA(uint32_t val) +{ + return ((val) << EDP_AUX_DATA_DATA__SHIFT) & EDP_AUX_DATA_DATA__MASK; +} +#define EDP_AUX_DATA_INDEX__MASK (0x00ff0000) +#define EDP_AUX_DATA_INDEX__SHIFT (16) +static inline uint32_t EDP_AUX_DATA_INDEX(uint32_t val) +{ + return ((val) << EDP_AUX_DATA_INDEX__SHIFT) & EDP_AUX_DATA_INDEX__MASK; +} +#define EDP_AUX_DATA_INDEX_WRITE (0x80000000) + +#define REG_EDP_AUX_TRANS_CTRL (0x00000038) +#define EDP_AUX_TRANS_CTRL_I2C (0x00000100) +#define EDP_AUX_TRANS_CTRL_GO (0x00000200) +#define EDP_AUX_TRANS_CTRL_NO_SEND_ADDR (0x00000400) +#define EDP_AUX_TRANS_CTRL_NO_SEND_STOP (0x00000800) + +#define REG_EDP_TIMEOUT_COUNT (0x0000003C) +#define REG_EDP_AUX_LIMITS (0x00000040) +#define REG_EDP_AUX_STATUS (0x00000044) + +#define EDP_INTERRUPT_TRANS_NUM (0x000000A0) + +#define REG_EDP_MAINLINK_CTRL (0x00000000) +#define EDP_MAINLINK_CTRL_ENABLE (0x00000001) +#define EDP_MAINLINK_CTRL_RESET (0x00000002) +#define EDP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER (0x00000010) +#define EDP_MAINLINK_FB_BOUNDARY_SEL (0x02000000) + +#define REG_EDP_STATE_CTRL (0x00000004) +#define EDP_STATE_CTRL_LINK_TRAINING_PATTERN1 (0x00000001) +#define EDP_STATE_CTRL_LINK_TRAINING_PATTERN2 (0x00000002) +#define EDP_STATE_CTRL_LINK_TRAINING_PATTERN3 (0x00000004) +#define EDP_STATE_CTRL_LINK_TRAINING_PATTERN4 (0x00000008) +#define EDP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE (0x00000010) +#define EDP_STATE_CTRL_LINK_PRBS7 (0x00000020) +#define EDP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN (0x00000040) +#define EDP_STATE_CTRL_SEND_VIDEO (0x00000080) +#define EDP_STATE_CTRL_PUSH_IDLE (0x00000100) + +#define REG_EDP_CONFIGURATION_CTRL (0x00000008) +#define EDP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK (0x00000001) +#define EDP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN (0x00000002) +#define EDP_CONFIGURATION_CTRL_P_INTERLACED (0x00000004) +#define EDP_CONFIGURATION_CTRL_INTERLACED_BTF (0x00000008) +#define EDP_CONFIGURATION_CTRL_NUM_OF_LANES (0x00000010) +#define EDP_CONFIGURATION_CTRL_ENHANCED_FRAMING (0x00000040) +#define EDP_CONFIGURATION_CTRL_SEND_VSC (0x00000080) +#define EDP_CONFIGURATION_CTRL_BPC (0x00000100) +#define EDP_CONFIGURATION_CTRL_ASSR (0x00000400) +#define EDP_CONFIGURATION_CTRL_RGB_YUV (0x00000800) +#define EDP_CONFIGURATION_CTRL_LSCLK_DIV (0x00002000) +#define EDP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT (0x04) +#define EDP_CONFIGURATION_CTRL_BPC_SHIFT (0x08) +#define EDP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT (0x0D) + +#define REG_EDP_SOFTWARE_MVID (0x00000010) +#define REG_EDP_SOFTWARE_NVID (0x00000018) + +#define REG_EDP_TOTAL_HOR_VER (0x0000001C) +#define EDP_TOTAL_HOR_VER_HORIZ__MASK (0x0000FFFF) +#define EDP_TOTAL_HOR_VER_HORIZ__SHIFT (0) +static inline uint32_t EDP_TOTAL_HOR_VER_HORIZ(uint32_t val) +{ + return ((val) << EDP_TOTAL_HOR_VER_HORIZ__SHIFT) & EDP_TOTAL_HOR_VER_HORIZ__MASK; +} +#define EDP_TOTAL_HOR_VER_VERT__MASK (0xffff0000) +#define EDP_TOTAL_HOR_VER_VERT__SHIFT (16) +static inline uint32_t EDP_TOTAL_HOR_VER_VERT(uint32_t val) +{ + return ((val) << EDP_TOTAL_HOR_VER_VERT__SHIFT) & EDP_TOTAL_HOR_VER_VERT__MASK; +} + +#define REG_EDP_START_HOR_VER_FROM_SYNC (0x00000020) +#define EDP_START_HOR_VER_FROM_SYNC_HORIZ__MASK (0x0000ffff) +#define EDP_START_HOR_VER_FROM_SYNC_HORIZ__SHIFT (0) +static inline uint32_t EDP_START_HOR_VER_FROM_SYNC_HORIZ(uint32_t val) +{ + return ((val) << EDP_START_HOR_VER_FROM_SYNC_HORIZ__SHIFT) & + EDP_START_HOR_VER_FROM_SYNC_HORIZ__MASK; +} +#define EDP_START_HOR_VER_FROM_SYNC_VERT__MASK (0xffff0000) +#define EDP_START_HOR_VER_FROM_SYNC_VERT__SHIFT (16) +static inline uint32_t EDP_START_HOR_VER_FROM_SYNC_VERT(uint32_t val) +{ + return ((val) << EDP_START_HOR_VER_FROM_SYNC_VERT__SHIFT) & + EDP_START_HOR_VER_FROM_SYNC_VERT__MASK; +} + +#define REG_EDP_HSYNC_VSYNC_WIDTH_POLARITY (0x00000024) +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ__MASK (0x00007fff) +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ__SHIFT (0) +static inline uint32_t EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ(uint32_t val) +{ + return ((val) << EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ__SHIFT) & + EDP_HSYNC_VSYNC_WIDTH_POLARITY_HORIZ__MASK; +} +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_NHSYNC (0x00008000) +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT__MASK (0x7fff0000) +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT__SHIFT (16) +static inline uint32_t EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT(uint32_t val) +{ + return ((val) << EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT__SHIFT) & + EDP_HSYNC_VSYNC_WIDTH_POLARITY_VERT__MASK; +} +#define EDP_HSYNC_VSYNC_WIDTH_POLARITY_NVSYNC (0x80000000) + +#define REG_EDP_ACTIVE_HOR_VER (0x00000028) +#define EDP_ACTIVE_HOR_VER_HORIZ__MASK (0x0000ffff) +#define EDP_ACTIVE_HOR_VER_HORIZ__SHIFT (0) +static inline uint32_t EDP_ACTIVE_HOR_VER_HORIZ(uint32_t val) +{ + return ((val) << EDP_ACTIVE_HOR_VER_HORIZ__SHIFT) & EDP_ACTIVE_HOR_VER_HORIZ__MASK; +} +#define EDP_ACTIVE_HOR_VER_VERT__MASK (0xffff0000) +#define EDP_ACTIVE_HOR_VER_VERT__SHIFT (16) +static inline uint32_t EDP_ACTIVE_HOR_VER_VERT(uint32_t val) +{ + return ((val) << EDP_ACTIVE_HOR_VER_VERT__SHIFT) & EDP_ACTIVE_HOR_VER_VERT__MASK; +} + + +#define REG_EDP_MISC1_MISC0 (0x0000002C) +#define EDP_MISC0_SYNCHRONOUS_CLK (0x00000001) +#define EDP_MISC0_COLORIMETRY_CFG_SHIFT (0x00000001) +#define EDP_MISC0_TEST_BITS_DEPTH_SHIFT (0x00000005) + +#define REG_EDP_VALID_BOUNDARY (0x00000030) +#define REG_EDP_VALID_BOUNDARY_2 (0x00000034) + +#define REG_EDP_LOGICAL2PHYSICAL_LANE_MAPPING (0x00000038) +#define LANE0_MAPPING_SHIFT (0x00000000) +#define LANE1_MAPPING_SHIFT (0x00000002) +#define LANE2_MAPPING_SHIFT (0x00000004) +#define LANE3_MAPPING_SHIFT (0x00000006) + +#define REG_EDP_MAINLINK_READY (0x00000040) +#define EDP_MAINLINK_READY_FOR_VIDEO (0x00000001) +#define EDP_MAINLINK_READY_TRAIN_PATTERN_1_READY (0x00000008) + +#define REG_EDP_MAINLINK_LEVELS (0x00000044) +#define EDP_MAINLINK_SAFE_TO_EXIT_LEVEL_2 (0x00000002) + +#define REG_EDP_TU (0x0000004C) + +/* PCLK registers */ +#define REG_EDP_DSC_DTO (0x0000007C) + +/* PHY registers */ +#define EDP_PHY_CFG (0x00000010) +#define EDP_PHY_CFG_1 (0x00000014) +#define EDP_PHY_PD_CTL (0x0000001C) +#define EDP_PHY_MODE (0x00000020) +#define EDP_PHY_AUX_CFG0 (0x00000024) +#define EDP_PHY_AUX_CFG1 (0x00000028) +#define EDP_PHY_AUX_CFG2 (0x0000002C) +#define EDP_PHY_AUX_CFG3 (0x00000030) +#define EDP_PHY_AUX_CFG4 (0x00000034) +#define EDP_PHY_AUX_CFG5 (0x00000038) +#define EDP_PHY_AUX_CFG6 (0x0000003C) +#define EDP_PHY_AUX_CFG7 (0x00000040) +#define EDP_PHY_AUX_CFG8 (0x00000044) +#define EDP_PHY_AUX_CFG9 (0x00000048) +#define EDP_PHY_AUX_INTERRUPT_MASK (0x00000058) +#define EDP_PHY_VCO_DIV (0x00000074) +#define EDP_PHY_TX0_TX1_LANE_CTL (0x0000007C) +#define EDP_PHY_TX2_TX3_LANE_CTL (0x000000A0) +#define EDP_PHY_SPARE0 (0x000000CC) +#define EDP_PHY_STATUS (0x000000E0) + + +/* Tx registers */ +#define TXn_CLKBUF_ENABLE (0x00000000) +#define TXn_TX_EMP_POST1_LVL (0x00000004) +#define TXn_TX_DRV_LVL (0x00000014) +#define TXn_TX_DRV_LVL_OFFSET (0x00000018) +#define TXn_RESET_TSYNC_EN (0x0000001C) +#define TXn_TX_BAND (0x00000028) +#define TXn_RES_CODE_LANE_OFFSET_TX0 (0x00000044) +#define TXn_RES_CODE_LANE_OFFSET_TX1 (0x00000048) +#define TXn_TRANSCEIVER_BIAS_EN (0x00000054) +#define TXn_HIGHZ_DRVR_EN (0x00000058) +#define TXn_TX_POL_INV (0x0000005C) +#define TXn_LANE_MODE_1 (0x00000064) +#define TXn_TRAN_DRVR_EMP_EN (0x00000078) +#define TXn_LDO_CONFIG (0x00000084) + + +/* PLL registers */ +#define QSERDES_COM_BG_TIMER (0x0000000C) +#define QSERDES_COM_SSC_EN_CENTER (0x00000010) +#define QSERDES_COM_SSC_ADJ_PER1 (0x00000014) +#define QSERDES_COM_SSC_ADJ_PER2 (0x00000018) +#define QSERDES_COM_SSC_PER1 (0x0000001C) +#define QSERDES_COM_SSC_PER2 (0x00000020) +#define QSERDES_COM_SSC_STEP_SIZE1_MODE0 (0x00000024) +#define QSERDES_COM_SSC_STEP_SIZE2_MODE0 (0x00000028) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x00000044) +#define QSERDES_COM_CLK_ENABLE1 (0x00000048) +#define QSERDES_COM_SYS_CLK_CTRL (0x0000004C) +#define QSERDES_COM_SYSCLK_BUF_ENABLE (0x00000050) +#define QSERDES_COM_PLL_IVCO (0x00000058) +#define QSERDES_COM_CP_CTRL_MODE0 (0x00000074) +#define QSERDES_COM_PLL_RCTRL_MODE0 (0x0000007C) +#define QSERDES_COM_PLL_CCTRL_MODE0 (0x00000084) +#define QSERDES_COM_SYSCLK_EN_SEL (0x00000094) +#define QSERDES_COM_RESETSM_CNTRL (0x0000009C) +#define QSERDES_COM_LOCK_CMP_EN (0x000000A4) +#define QSERDES_COM_LOCK_CMP1_MODE0 (0x000000AC) +#define QSERDES_COM_LOCK_CMP2_MODE0 (0x000000B0) +#define QSERDES_COM_DEC_START_MODE0 (0x000000BC) +#define QSERDES_COM_DIV_FRAC_START1_MODE0 (0x000000CC) +#define QSERDES_COM_DIV_FRAC_START2_MODE0 (0x000000D0) +#define QSERDES_COM_DIV_FRAC_START3_MODE0 (0x000000D4) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 (0x000000EC) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 (0x000000F0) +#define QSERDES_COM_VCO_TUNE_CTRL (0x00000108) +#define QSERDES_COM_VCO_TUNE_MAP (0x0000010C) +#define QSERDES_COM_VCO_TUNE1_MODE0 (0x00000110) +#define QSERDES_COM_VCO_TUNE2_MODE0 (0x00000114) +#define QSERDES_COM_CMN_STATUS (0x00000140) +#define QSERDES_COM_CLK_SEL (0x00000154) +#define QSERDES_COM_HSCLK_SEL (0x00000158) +#define QSERDES_COM_CORECLK_DIV_MODE0 (0x00000168) +#define QSERDES_COM_CORE_CLK_EN (0x00000174) +#define QSERDES_COM_C_READY_STATUS (0x00000178) +#define QSERDES_COM_CMN_CONFIG (0x0000017C) +#define QSERDES_COM_SVS_MODE_CLK_SEL (0x00000184) + + +#define DP_PHY_PLL_POLL_SLEEP_US (500) +#define DP_PHY_PLL_POLL_TIMEOUT_US (10000) + + +#define EDP_VCO_RATE_8100MHZDIV1000 (8100000UL) +#define EDP_VCO_RATE_8640MHZDIV1000 (8640000UL) +#define EDP_VCO_RATE_9720MHZDIV1000 (9720000UL) +#define EDP_VCO_RATE_10800MHZDIV1000 (10800000UL) +#define EDP_VCO_RATE_11880MHZDIV1000 (11880000UL) + +#endif /* EDP_V510_REG */ -- The Qualcomm Innovatin Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project