]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
drm/hisilicon/hibmc: Add dp serdes cfg to adjust serdes rate, voltage and pre-emphasis
authorBaihan Li <libaihan@huawei.com>
Mon, 31 Mar 2025 07:42:05 +0000 (15:42 +0800)
committerDmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Fri, 11 Apr 2025 11:40:00 +0000 (14:40 +0300)
This dp controller need features of digital-to-analog conversion and
high-speed transmission in chip by its extern serdes controller. Our
serdes cfg is relatively simple, just need two register configurations.
Don't need too much functions, like: power on/off, initialize, and some
complex configurations, so I'm not going to use the phy framework.
This serdes is inited and configured in dp initialization, and also
integrating them into link training process.

For rate changing, we can change from 1.62-8.2Gpbs by cfg reg.
For voltage and pre-emphasis levels changing, we can cfg different
serdes ffe value.

Signed-off-by: Baihan Li <libaihan@huawei.com>
Signed-off-by: Yongbang Shi <shiyongbang@huawei.com>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/20250331074212.3370287-3-shiyongbang@huawei.com
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
drivers/gpu/drm/hisilicon/hibmc/Makefile
drivers/gpu/drm/hisilicon/hibmc/dp/dp_comm.h
drivers/gpu/drm/hisilicon/hibmc/dp/dp_hw.c
drivers/gpu/drm/hisilicon/hibmc/dp/dp_reg.h
drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c [new file with mode: 0644]

index 95a4ed599d98030605631c9a36bc521dab0c91cf..43de077d6769a52e1c1aff46a40265ca6f1651a1 100644 (file)
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \
-              dp/dp_aux.o dp/dp_link.o dp/dp_hw.o hibmc_drm_dp.o
+              dp/dp_aux.o dp/dp_link.o dp/dp_hw.o dp/dp_serdes.o hibmc_drm_dp.o
 
 obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o
index 2c52a4476c4dd30c6459830f6763b25f94d3d221..e0c6a3b7463b87cadc5c50181c33b354bc8ef77c 100644 (file)
@@ -38,6 +38,7 @@ struct hibmc_dp_dev {
        struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */
        struct hibmc_dp_link link;
        u8 dpcd[DP_RECEIVER_CAP_SIZE];
+       void __iomem *serdes_base;
 };
 
 #define dp_field_modify(reg_value, mask, val)                          \
@@ -59,5 +60,8 @@ struct hibmc_dp_dev {
 
 void hibmc_dp_aux_init(struct hibmc_dp_dev *dp);
 int hibmc_dp_link_training(struct hibmc_dp_dev *dp);
+int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp);
+int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp);
+int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX]);
 
 #endif
index a8d543881c0999c6413f8dfbe78053c1c626277b..3612f3c5ab231d4ada62fa2035a875885abdb2e0 100644 (file)
@@ -151,6 +151,7 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp)
 {
        struct drm_device *drm_dev = dp->drm_dev;
        struct hibmc_dp_dev *dp_dev;
+       int ret;
 
        dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL);
        if (!dp_dev)
@@ -165,6 +166,10 @@ int hibmc_dp_hw_init(struct hibmc_dp *dp)
 
        hibmc_dp_aux_init(dp_dev);
 
+       ret = hibmc_dp_serdes_init(dp_dev);
+       if (ret)
+               return ret;
+
        dp_dev->link.cap.lanes = 0x2;
        dp_dev->link.cap.link_rate = DP_LINK_BW_2_7;
 
index dc2bd3f80b70de52a50527cfdbb151e563ba555f..16ea589035980fa590e8e1fb1f2e48358b507c55 100644 (file)
 
 #define HIBMC_DP_TIMING_SYNC_CTRL              0xFF0
 
+/* dp serdes reg */
+#define HIBMC_DP_HOST_OFFSET           0x10000
+#define HIBMC_DP_LANE0_RATE_OFFSET     0x4
+#define HIBMC_DP_LANE1_RATE_OFFSET     0xc
+#define HIBMC_DP_LANE_STATUS_OFFSET    0x10
+#define HIBMC_DP_PMA_LANE0_OFFSET      0x18
+#define HIBMC_DP_PMA_LANE1_OFFSET      0x1c
+#define HIBMC_DP_PMA_TXDEEMPH          GENMASK(18, 1)
+#define DP_SERDES_DONE                 0x3
+
+/* dp serdes TX-Deempth Configuration */
+#define DP_SERDES_VOL0_PRE0            0x280
+#define DP_SERDES_VOL0_PRE1            0x2300
+#define DP_SERDES_VOL0_PRE2            0x53c0
+#define DP_SERDES_VOL0_PRE3            0x8400
+#define DP_SERDES_VOL1_PRE0            0x380
+#define DP_SERDES_VOL1_PRE1            0x3440
+#define DP_SERDES_VOL1_PRE2            0x6480
+#define DP_SERDES_VOL2_PRE0            0x4c1
+#define DP_SERDES_VOL2_PRE1            0x4500
+#define DP_SERDES_VOL3_PRE0            0x600
+#define DP_SERDES_BW_8_1               0x3
+
 #endif
diff --git a/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c b/drivers/gpu/drm/hisilicon/hibmc/dp/dp_serdes.c
new file mode 100644 (file)
index 0000000..676059d
--- /dev/null
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright (c) 2025 Hisilicon Limited.
+
+#include <linux/delay.h>
+#include <drm/drm_device.h>
+#include <drm/drm_print.h>
+#include "dp_comm.h"
+#include "dp_config.h"
+#include "dp_reg.h"
+
+int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX])
+{
+       static const u32 serdes_tx_cfg[4][4] = { {DP_SERDES_VOL0_PRE0, DP_SERDES_VOL0_PRE1,
+                                                 DP_SERDES_VOL0_PRE2, DP_SERDES_VOL0_PRE3},
+                                                {DP_SERDES_VOL1_PRE0, DP_SERDES_VOL1_PRE1,
+                                                 DP_SERDES_VOL1_PRE2}, {DP_SERDES_VOL2_PRE0,
+                                                 DP_SERDES_VOL2_PRE1}, {DP_SERDES_VOL3_PRE0}};
+       int cfg[2];
+       int i;
+
+       for (i = 0; i < HIBMC_DP_LANE_NUM_MAX; i++) {
+               cfg[i] = serdes_tx_cfg[FIELD_GET(DP_TRAIN_VOLTAGE_SWING_MASK, train_set[i])]
+                                     [FIELD_GET(DP_TRAIN_PRE_EMPHASIS_MASK, train_set[i])];
+               if (!cfg[i])
+                       return -EINVAL;
+
+               /* lane1 offset is 4 */
+               writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, cfg[i]),
+                      dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET + i * 4);
+       }
+
+       usleep_range(300, 500);
+
+       if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) {
+               drm_dbg_dp(dp->dev, "dp serdes cfg failed\n");
+               return -EAGAIN;
+       }
+
+       return 0;
+}
+
+int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp)
+{
+       writel(rate, dp->serdes_base + HIBMC_DP_LANE0_RATE_OFFSET);
+       writel(rate, dp->serdes_base + HIBMC_DP_LANE1_RATE_OFFSET);
+
+       usleep_range(300, 500);
+
+       if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) {
+               drm_dbg_dp(dp->dev, "dp serdes rate switching failed\n");
+               return -EAGAIN;
+       }
+
+       if (rate < DP_SERDES_BW_8_1)
+               drm_dbg_dp(dp->dev, "reducing serdes rate to :%d\n",
+                          rate ? rate * HIBMC_DP_LINK_RATE_CAL * 10 : 162);
+
+       return 0;
+}
+
+int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp)
+{
+       dp->serdes_base = dp->base + HIBMC_DP_HOST_OFFSET;
+
+       writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0),
+              dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET);
+       writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0),
+              dp->serdes_base + HIBMC_DP_PMA_LANE1_OFFSET);
+
+       return hibmc_dp_serdes_rate_switch(DP_SERDES_BW_8_1, dp);
+}