From: Markus Schneider-Pargmann <msp@xxxxxxxxxxxx>
This patch adds a embedded displayport driver for the MediaTek mt8195 SoC.
It supports the MT8195, the embedded DisplayPort units. It offers
DisplayPort 1.4 with up to 4 lanes.
The driver creates a child device for the phy. The child device will
never exist without the parent being active. As they are sharing a
register range, the parent passes a regmap pointer to the child so that
both can work with the same register range. The phy driver sets device
data that is read by the parent to get the phy device that can be used
to control the phy properties.
This driver is based on an initial version by
Jitao shi <jitao.shi@xxxxxxxxxxxx>
Signed-off-by: Markus Schneider-Pargmann <msp@xxxxxxxxxxxx>
Signed-off-by: Guillaume Ranquet <granquet@xxxxxxxxxxxx>
[Bo-Chen: Cleanup the drivers and modify comments from reviewers]
Signed-off-by: Bo-Chen Chen <rex-bc.chen@xxxxxxxxxxxx>
---
drivers/gpu/drm/mediatek/Kconfig | 10 +
drivers/gpu/drm/mediatek/Makefile | 1 +
drivers/gpu/drm/mediatek/mtk_dp.c | 2198 ++++++++++++++++++++++++
drivers/gpu/drm/mediatek/mtk_dp_reg.h | 543 ++++++
drivers/gpu/drm/mediatek/mtk_drm_drv.c | 3 +
drivers/gpu/drm/mediatek/mtk_drm_drv.h | 3 +
6 files changed, 2758 insertions(+)
create mode 100644 drivers/gpu/drm/mediatek/mtk_dp.c
create mode 100644 drivers/gpu/drm/mediatek/mtk_dp_reg.h
diff --git a/drivers/gpu/drm/mediatek/mtk_dp.c b/drivers/gpu/drm/mediatek/mtk_dp.c
new file mode 100644
index 000000000000..9e9b516409e2
--- /dev/null
+++ b/drivers/gpu/drm/mediatek/mtk_dp.c
@@ -0,0 +1,2198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019-2022 MediaTek Inc.
+ * Copyright (c) 2022 BayLibre
+ */
+
+#include <drm/display/drm_dp.h>
+#include <drm/display/drm_dp_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <sound/hdmi-codec.h>
+#include <video/videomode.h>
+
+#include "mtk_dp_reg.h"
+
+#define MTK_DP_SIP_CONTROL_AARCH32 0x82000523
+
+#define MTK_VDOSYS1_MAX_FRAMERATE 60
+#define MTK_DP_4P1T 4
+#define MTK_DP_HDE 2
+#define MTK_DP_PIX_PER_ADDR 2
+#define MTK_DP_AUX_WAIT_REPLY_COUNT 20
+#define MTK_DP_CHECK_SINK_CAP_TIMEOUT_COUNT 3
+#define MTK_DP_TBC_BUF_READ_START_ADDR 0x08
+#define MTK_DP_TRAIN_RETRY_LIMIT 8
+#define MTK_DP_TRAIN_MAX_ITERATIONS 5
+#define MTK_DP_DP_VERSION_11 0x11
+
+enum mtk_dp_train_state {
+ MTK_DP_TRAIN_STATE_TRAINING,
+ MTK_DP_TRAIN_STATE_NORMAL,
+};
+
+struct mtk_dp_timings {
+ struct videomode vm;
+ u8 frame_rate;
+};
+
+struct mtk_dp_irq_sta {
+ bool hpd_disconnect;
+ bool hpd_inerrupt;
+};
+
+struct mtk_dp_train_info {
+ bool tps3;
+ bool tps4;
+ bool sink_ssc;
+ bool cable_plugged_in;
+ bool cable_state_change;
+ bool cr_done;
+ bool eq_done;
+ /* link_rate is in multiple of 0.27Gbps */
+ int link_rate;
+ int lane_count;
+ struct mtk_dp_irq_sta irq_sta;
+};
+
+struct mtk_dp_info {
+ u32 depth;
+ enum dp_pixelformat format;
+ struct mtk_dp_timings timings;
+};
+
+struct dp_cal_data {
+ unsigned int glb_bias_trim;
+ unsigned int clktx_impse;
+
+ unsigned int ln_tx_impsel_pmos[4];
+ unsigned int ln_tx_impsel_nmos[4];
+};
+
+struct mtk_dp {
+ struct device *dev;
+ struct platform_device *phy_dev;
+ struct phy *phy;
+ struct dp_cal_data cal_data;
+ u8 max_lanes;
+ u8 max_linkrate;
+
+ struct drm_device *drm_dev;
+ struct drm_bridge bridge;
+ struct drm_bridge *next_bridge;
+ struct drm_dp_aux aux;
+
+ u8 rx_cap[DP_RECEIVER_CAP_SIZE];
+
+ struct mtk_dp_info info;
+
+ struct mtk_dp_train_info train_info;
+ enum mtk_dp_train_state train_state;
+
+ struct regmap *regs;
+
+ bool enabled;
+
+ struct drm_connector *conn;
+};
+
+static struct regmap_config mtk_dp_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = SEC_OFFSET + 0x90,
+ .name = "mtk-dp-registers",
+};
+
+static struct mtk_dp *mtk_dp_from_bridge(struct drm_bridge *b)
+{
+ return container_of(b, struct mtk_dp, bridge);
+}
+
+static u32 mtk_dp_read(struct mtk_dp *mtk_dp, u32 offset)
+{
+ u32 read_val;
+ int ret;
+
+ ret = regmap_read(mtk_dp->regs, offset, &read_val);
+ if (ret) {
+ dev_err(mtk_dp->dev, "Failed to read register 0x%x: %d\n",
+ offset, ret);
+ return 0;
+ }
+
+ return read_val;
+}
+
+static void mtk_dp_write(struct mtk_dp *mtk_dp, u32 offset, u32 val)
+{
+ if (regmap_write(mtk_dp->regs, offset, val))
+ dev_err(mtk_dp->dev,
+ "Failed to write register 0x%x with value 0x%x\n",
+ offset, val);
+}
+
+static void mtk_dp_update_bits(struct mtk_dp *mtk_dp, u32 offset,
+ u32 val, u32 mask)
+{
+ if (regmap_update_bits(mtk_dp->regs, offset, mask, val))
+ dev_err(mtk_dp->dev,
+ "Failed to update register 0x%x with value 0x%x, mask 0x%x\n",
+ offset, val, mask);
+}
+
+static void mtk_dp_bulk_16bit_write(struct mtk_dp *mtk_dp, u32 offset, u8 *buf,
+ size_t length)
+{
+ int i;
+ int num_regs = (length + 1) / 2;
+
+ /* 2 bytes per register */
+ for (i = 0; i < num_regs; i++) {
+ u32 val = buf[i * 2] |
+ (i * 2 + 1 < length ? buf[i * 2 + 1] << 8 : 0);
+
+ mtk_dp_write(mtk_dp, offset + i * 4, val);
+ }
+}
+
+static unsigned long mtk_dp_sip_atf_call(struct mtk_dp *mtk_dp,
+ unsigned int cmd, unsigned int para)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(MTK_DP_SIP_CONTROL_AARCH32, cmd, para, 0, 0, 0, 0, 0,
+ &res);
+
+ dev_dbg(mtk_dp->dev, "sip cmd 0x%x, p1 0x%x, ret 0x%lx-0x%lx",
+ cmd, para, res.a0, res.a1);
+
+ return res.a1;
+}
+
+
+static void mtk_dp_set_color_format(struct mtk_dp *mtk_dp,
+ enum dp_pixelformat color_format)
+{
+ u32 val;
+
+ mtk_dp->info.format = color_format;
+
+ /* update MISC0 */
+ mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3034,
+ color_format << DP_TEST_COLOR_FORMAT_SHIFT,
+ DP_TEST_COLOR_FORMAT_MASK);
+
+ switch (color_format) {
+ case DP_PIXELFORMAT_YUV422:
+ val = PIXEL_ENCODE_FORMAT_DP_ENC0_P0_YCBCR422;
+ break;
+ case DP_PIXELFORMAT_YUV420:
+ val = PIXEL_ENCODE_FORMAT_DP_ENC0_P0_YCBCR420;
+ break;
+ case DP_PIXELFORMAT_RGB:
+ case DP_PIXELFORMAT_YUV444:
+ val = PIXEL_ENCODE_FORMAT_DP_ENC0_P0_RGB;
+ break;
+ case DP_PIXELFORMAT_Y_ONLY:
+ case DP_PIXELFORMAT_RAW:
+ case DP_PIXELFORMAT_RESERVED:
+ default:
+ drm_warn(mtk_dp->drm_dev, "Unsupported color format: %d\n",
+ color_format);
+ return;
+ }
+
+ mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C,
+ val, PIXEL_ENCODE_FORMAT_DP_ENC0_P0_MASK);
+}
+
+static void mtk_dp_set_color_depth(struct mtk_dp *mtk_dp)
+{
+ u32 val;
+ /* Only support 8 bits currently */
+ u32 color_depth = DP_MSA_MISC_8_BPC;
+
+ mtk_dp->info.depth = color_depth;
+
+ /* Update MISC0 */
+ mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_3034,
+ color_depth, DP_TEST_BIT_DEPTH_MASK);
+
+ switch (color_depth) {
+ case DP_MSA_MISC_6_BPC:
+ val = VIDEO_COLOR_DEPTH_DP_ENC0_P0_6BIT;
+ break;
+ case DP_MSA_MISC_8_BPC:
+ val = VIDEO_COLOR_DEPTH_DP_ENC0_P0_8BIT;
+ break;
+ case DP_MSA_MISC_10_BPC:
+ val = VIDEO_COLOR_DEPTH_DP_ENC0_P0_10BIT;
+ break;
+ case DP_MSA_MISC_12_BPC:
+ val = VIDEO_COLOR_DEPTH_DP_ENC0_P0_12BIT;
+ break;
+ case DP_MSA_MISC_16_BPC:
+ val = VIDEO_COLOR_DEPTH_DP_ENC0_P0_16BIT;
+ break;
+ default:
+ drm_warn(mtk_dp->drm_dev, "Unsupported color depth %d\n",
+ color_depth);
+ return;
+ }
+
+ mtk_dp_update_bits(mtk_dp, MTK_DP_ENC0_P0_303C, val,
+ VIDEO_COLOR_DEPTH_DP_ENC0_P0_MASK);
+}
+
+
+static int mtk_dp_phy_configure(struct mtk_dp *mtk_dp,
+ u32 link_rate, int lane_count)
+{
+ int ret;
+ union phy_configure_opts phy_opts = {
+ .dp = {
+ .link_rate = link_rate_to_mb_per_s(mtk_dp, link_rate),
+ .set_rate = 1,
+ .lanes = lane_count,
+ .set_lanes = 1,
+ .ssc = mtk_dp->train_info.sink_ssc,
+ }
+ };
+
+ mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE, DP_PWR_STATE_BANDGAP,
+ DP_PWR_STATE_MASK);
+
+ ret = phy_configure(mtk_dp->phy, &phy_opts);
+
+ if (ret)
+ return ret;
+
+ mtk_dp_set_cal_data(mtk_dp);
+ mtk_dp_update_bits(mtk_dp, MTK_DP_TOP_PWR_STATE,
+ DP_PWR_STATE_BANDGAP_TPLL_LANE, DP_PWR_STATE_MASK);
+
+ return 0;
+}
+
+
+static void mtk_dp_calculate_pixrate(struct mtk_dp *mtk_dp)
+{
+ u8 target_frame_rate = 60;
+ u32 target_pixel_clk;
+ struct drm_display_mode mode;
+ struct mtk_dp_timings *timings = &mtk_dp->info.timings;
+
+ drm_display_mode_from_videomode(&timings->vm, &mode);
+
+ if (mtk_dp->info.timings.frame_rate > 0) {
+ target_frame_rate = mtk_dp->info.timings.frame_rate;
+ target_pixel_clk = mode.htotal * mode.vtotal *
+ target_frame_rate;
+ } else {
+ target_pixel_clk = mode.htotal * mode.vtotal *
+ target_frame_rate;
+ }
+}
+
+static void mtk_dp_set_tx_out(struct mtk_dp *mtk_dp)
+{
+ mtk_dp_msa_bypass_disable(mtk_dp);
+ mtk_dp_calculate_pixrate(mtk_dp);
+ mtk_dp_pg_disable(mtk_dp);
+ mtk_dp_setup_tu(mtk_dp);
+}
+
+static ssize_t mtk_dp_hpd_sink_event(struct mtk_dp *mtk_dp)
+{
+ ssize_t ret;
+ u8 sink_count;
+ bool locked;
+ u8 link_status[DP_LINK_STATUS_SIZE] = {};
+ u32 sink_count_reg = DP_SINK_COUNT_ESI;
+ u32 link_status_reg = DP_LANE0_1_STATUS;
+
+ ret = drm_dp_dpcd_readb(&mtk_dp->aux, sink_count_reg, &sink_count);
+ if (ret < 0) {
+ drm_err(mtk_dp->drm_dev, "Read sink count failed\n");
+ return ret;
+ }
+
+ ret = drm_dp_dpcd_read(&mtk_dp->aux, link_status_reg, link_status,
+ sizeof(link_status));
+ if (!ret) {
+ drm_err(mtk_dp->drm_dev, "Read link status failed\n");
+ return ret;
+ }
+
+ locked = drm_dp_channel_eq_ok(link_status,
+ mtk_dp->train_info.lane_count);
+ if (!locked && mtk_dp->train_state > MTK_DP_TRAIN_STATE_TRAINING)
+ mtk_dp->train_state = MTK_DP_TRAIN_STATE_TRAINING;
+
+ if (link_status[1] & DP_REMOTE_CONTROL_COMMAND_PENDING)
+ drm_dp_dpcd_writeb(&mtk_dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR,
+ DP_REMOTE_CONTROL_COMMAND_PENDING);
+
+ if (DP_GET_SINK_COUNT(sink_count) &&
+ (link_status[2] & DP_DOWNSTREAM_PORT_STATUS_CHANGED)) {
+ mtk_dp->train_state = MTK_DP_TRAIN_STATE_TRAINING;
+ msleep(20);
+ }
+
+ return 0;
+}
+
+
+static int mtk_dp_train_flow(struct mtk_dp *mtk_dp, u8 target_link_rate,
+ u8 target_lane_count)
+{
+ u8 lane_adjust[2] = {};
+ bool pass_tps1 = false;
+ bool pass_tps2_3 = false;
+ int train_retries;
+ int status_control;
+ int iteration_count;
+ int ret;
+ u8 prev_lane_adjust;
+
+ drm_dp_dpcd_writeb(&mtk_dp->aux, DP_LINK_BW_SET, target_link_rate);
+ drm_dp_dpcd_writeb(&mtk_dp->aux, DP_LANE_COUNT_SET,
+ target_lane_count | DP_LANE_COUNT_ENHANCED_FRAME_EN);
+
+ if (mtk_dp->train_info.sink_ssc)
+ drm_dp_dpcd_writeb(&mtk_dp->aux, DP_DOWNSPREAD_CTRL,
+ DP_SPREAD_AMP_0_5);
+
+ train_retries = 0;
+ status_control = 0;
+ iteration_count = 1;
+ prev_lane_adjust = 0xFF;
+
+ mtk_dp_set_lanes(mtk_dp, target_lane_count / 2);
+ ret = mtk_dp_phy_configure(mtk_dp, target_link_rate, target_lane_count);
+ if (ret)
+ return -EINVAL;
+
+ dev_dbg(mtk_dp->dev,
+ "Link train target_link_rate = 0x%x, target_lane_count = 0x%x\n",
+ target_link_rate, target_lane_count);
+