Pārlūkot izejas kodu

Merge "disp: msm: dp: optimize sim function handling in dp_debug"

qctecmdr 4 gadi atpakaļ
vecāks
revīzija
052949d046

+ 58 - 0
Documentation/devicetree/bindings/drm/msm/sde-dp-sim.txt

@@ -0,0 +1,58 @@
+QTI Snapdragon Display Engine (SDE) DP-MST sideband message emulation driver
+
+Required properties:
+- compatible:		"qcom,dp-mst-sim"
+
+Each child node represents a port at root branch, with properties:
+- qcom,mode-h-active:      A u32 property defines the horizontal active size.
+- qcom,mode-h-front-porch: A u32 property defines the horizontal front porch.
+- qcom,mode-h-pulse-width: A u32 property defines the horizontal pulse.
+- qcom,mode-h-back-porch:  A u32 property defines the horizontal back porch.
+- qcom,mode-h-active-high: A boolean property if horizontal polarity is high.
+- qcom,mode-v-active:      A u32 property defines the vertical active size.
+- qcom,mode-v-front-porch: A u32 property defines the vertical front portch.
+- qcom,mode-v-pulse-width: A u32 property defines the vertical pulse width.
+- qcom,mode-v-back-porch:  A u32 property defines the vertical back porch.
+- qcom,mode-v-active-high: A boolean property if vertical polarity is high.
+- qcom,mode-refresh-rate:  A u32 property defines vertial refresh rate.
+- qcom,mode-clock-in-khz:  A u32 property defines clock in kHz.
+
+Example:
+
+/ {
+	...
+
+	sde_dp_mst_sim: qcom,dp-mst-sim {
+		compatible = "qcom,dp-mst-sim";
+
+		port@0 {
+			qcom,mode-h-active = <1920>;
+			qcom,mode-h-front-porch = <88>;
+			qcom,mode-h-pulse-width = <44>;
+			qcom,mode-h-back-porch = <148>;
+			qcom,mode-h-active-high;
+			qcom,mode-v-active = <1080>;
+			qcom,mode-v-front-porch = <4>;
+			qcom,mode-v-pulse-width = <5>;
+			qcom,mode-v-back-porch = <36>;
+			qcom,mode-v-active-high;
+			qcom,mode-refresh-rate = <60>;
+			qcom,mode-clock-in-khz = <148500>;
+		};
+
+		port@1 {
+			qcom,mode-h-active = <1920>;
+			qcom,mode-h-front-porch = <88>;
+			qcom,mode-h-pulse-width = <44>;
+			qcom,mode-h-back-porch = <148>;
+			qcom,mode-h-active-high;
+			qcom,mode-v-active = <1080>;
+			qcom,mode-v-front-porch = <4>;
+			qcom,mode-v-pulse-width = <5>;
+			qcom,mode-v-back-porch = <36>;
+			qcom,mode-v-active-high;
+			qcom,mode-refresh-rate = <60>;
+			qcom,mode-clock-in-khz = <148500>;
+		};
+	};
+};

+ 2 - 0
config/gki_waipiodisp.conf

@@ -2,6 +2,8 @@ export CONFIG_DRM_MSM=y
 export CONFIG_DRM_MSM_SDE=y
 export CONFIG_SYNC_FILE=y
 export CONFIG_DRM_MSM_DSI=y
+export CONFIG_DRM_MSM_DP=y
+export CONFIG_DRM_MSM_DP_MST=y
 export CONFIG_DSI_PARSER=y
 export CONFIG_QCOM_MDSS_PLL=y
 export CONFIG_DRM_SDE_RSC=y

+ 2 - 0
config/gki_waipiodispconf.h

@@ -7,6 +7,8 @@
 #define CONFIG_DRM_MSM_SDE 1
 #define CONFIG_SYNC_FILE 1
 #define CONFIG_DRM_MSM_DSI 1
+#define CONFIG_DRM_MSM_DP 1
+#define CONFIG_DRM_MSM_DP_MST 1
 #define CONFIG_DSI_PARSER 1
 #define CONFIG_DRM_SDE_WB 1
 #define CONFIG_DRM_SDE_RSC 1

+ 4 - 0
msm/Kbuild

@@ -95,6 +95,10 @@ msm_drm-$(CONFIG_DRM_MSM_DP) += dp/dp_altmode.o \
 				dp/dp_audio.o \
 				dp/dp_debug.o \
 				dp/dp_hpd.o \
+				dp/dp_aux_bridge.o \
+				dp/dp_bridge_hpd.o \
+				dp/dp_mst_sim.o \
+				dp/dp_mst_sim_helper.o \
 				dp/dp_gpio_hpd.o \
 				dp/dp_lphw_hpd.o \
 				dp/dp_display.o \

+ 63 - 121
msm/dp/dp_aux.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/soc/qcom/fsa4480-i2c.h>
@@ -26,6 +26,11 @@ struct dp_aux_private {
 	struct completion comp;
 	struct drm_dp_aux drm_aux;
 
+	struct dp_aux_bridge *aux_bridge;
+	struct dp_aux_bridge *sim_bridge;
+	bool bridge_in_transfer;
+	bool sim_in_transfer;
+
 	bool cmd_busy;
 	bool native;
 	bool read;
@@ -39,9 +44,6 @@ struct dp_aux_private {
 	u32 retry_cnt;
 
 	atomic_t aborted;
-
-	u8 *dpcd;
-	u8 *edid;
 };
 
 #ifdef CONFIG_DYNAMIC_DEBUG
@@ -467,100 +469,10 @@ error:
 	return ret;
 }
 
-static ssize_t dp_aux_transfer_debug(struct drm_dp_aux *drm_aux,
-		struct drm_dp_aux_msg *msg)
+static inline bool dp_aux_is_sideband_msg(u32 address, size_t size)
 {
-	u32 timeout;
-	ssize_t ret;
-	struct dp_aux_private *aux = container_of(drm_aux,
-		struct dp_aux_private, drm_aux);
-
-	mutex_lock(&aux->mutex);
-
-	ret = dp_aux_transfer_ready(aux, msg, false);
-	if (ret)
-		goto end;
-
-	aux->aux_error_num = DP_AUX_ERR_NONE;
-
-	if (!aux->dpcd || !aux->edid) {
-		DP_ERR("invalid aux/dpcd structure\n");
-		goto end;
-	}
-
-	if ((msg->address + msg->size) > SZ_4K) {
-		DP_DEBUG("invalid dpcd access: addr=0x%x, size=0x%lx\n",
-				msg->address, msg->size);
-		goto address_error;
-	}
-
-	if (aux->native) {
-		mutex_lock(aux->dp_aux.access_lock);
-		aux->dp_aux.reg = msg->address;
-		aux->dp_aux.read = aux->read;
-		aux->dp_aux.size = msg->size;
-
-		if (!aux->read)
-			memcpy(aux->dpcd + msg->address,
-				msg->buffer, msg->size);
-
-		reinit_completion(&aux->comp);
-		mutex_unlock(aux->dp_aux.access_lock);
-
-		timeout = wait_for_completion_timeout(&aux->comp, HZ * 2);
-		if (!timeout) {
-			DP_ERR("%s timeout: 0x%x\n",
-				aux->read ? "read" : "write",
-				msg->address);
-			atomic_set(&aux->aborted, 1);
-			ret = -ETIMEDOUT;
-			goto end;
-		}
-
-		mutex_lock(aux->dp_aux.access_lock);
-		if (aux->read)
-			memcpy(msg->buffer, aux->dpcd + msg->address,
-				msg->size);
-		mutex_unlock(aux->dp_aux.access_lock);
-
-		aux->aux_error_num = DP_AUX_ERR_NONE;
-	} else {
-		if (aux->read && msg->address == 0x50) {
-			memcpy(msg->buffer,
-				aux->edid + aux->offset - 16,
-				msg->size);
-		}
-	}
-
-	if (aux->aux_error_num == DP_AUX_ERR_NONE) {
-		dp_aux_hex_dump(drm_aux, msg);
-
-		if (!aux->read)
-			memset(msg->buffer, 0, msg->size);
-
-		msg->reply = aux->native ?
-			DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
-	} else {
-		/* Reply defer to retry */
-		msg->reply = aux->native ?
-			DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER;
-	}
-
-	ret = msg->size;
-	goto end;
-
-address_error:
-	memset(msg->buffer, 0, msg->size);
-	ret = msg->size;
-end:
-	if (ret == -ETIMEDOUT)
-		aux->dp_aux.state |= DP_STATE_AUX_TIMEOUT;
-	aux->dp_aux.reg = 0xFFFF;
-	aux->dp_aux.read = true;
-	aux->dp_aux.size = 0;
-
-	mutex_unlock(&aux->mutex);
-	return ret;
+	return (address >= 0x1000 && address + size < 0x1800) ||
+			(address >= 0x2000 && address + size < 0x2200);
 }
 
 /*
@@ -623,6 +535,47 @@ unlock_exit:
 	return ret;
 }
 
+static ssize_t dp_aux_bridge_transfer(struct drm_dp_aux *drm_aux,
+		struct drm_dp_aux_msg *msg)
+{
+	struct dp_aux_private *aux = container_of(drm_aux,
+			struct dp_aux_private, drm_aux);
+	ssize_t size;
+
+	if (aux->bridge_in_transfer) {
+		size = dp_aux_transfer(drm_aux, msg);
+	} else {
+		aux->bridge_in_transfer = true;
+		size = aux->aux_bridge->transfer(aux->aux_bridge,
+				drm_aux, msg);
+		aux->bridge_in_transfer = false;
+	}
+
+	return size;
+}
+
+static ssize_t dp_aux_transfer_debug(struct drm_dp_aux *drm_aux,
+		struct drm_dp_aux_msg *msg)
+{
+	struct dp_aux_private *aux = container_of(drm_aux,
+			struct dp_aux_private, drm_aux);
+	ssize_t size;
+
+	if (aux->sim_in_transfer) {
+		if (aux->aux_bridge && aux->aux_bridge->transfer)
+			size = dp_aux_bridge_transfer(drm_aux, msg);
+		else
+			size = dp_aux_transfer(drm_aux, msg);
+	} else {
+		aux->sim_in_transfer = true;
+		size = aux->sim_bridge->transfer(aux->sim_bridge,
+				drm_aux, msg);
+		aux->sim_in_transfer = false;
+	}
+
+	return size;
+}
+
 static void dp_aux_reset_phy_config_indices(struct dp_aux_cfg *aux_cfg)
 {
 	int i = 0;
@@ -696,6 +649,10 @@ static int dp_aux_register(struct dp_aux *dp_aux)
 		goto exit;
 	}
 	dp_aux->drm_aux = &aux->drm_aux;
+
+	/* if bridge is defined, override transfer function */
+	if (aux->aux_bridge && aux->aux_bridge->transfer)
+		aux->drm_aux.transfer = dp_aux_bridge_transfer;
 exit:
 	return ret;
 }
@@ -713,24 +670,8 @@ static void dp_aux_deregister(struct dp_aux *dp_aux)
 	drm_dp_aux_unregister(&aux->drm_aux);
 }
 
-static void dp_aux_dpcd_updated(struct dp_aux *dp_aux)
-{
-	struct dp_aux_private *aux;
-
-	if (!dp_aux) {
-		DP_ERR("invalid input\n");
-		return;
-	}
-
-	aux = container_of(dp_aux, struct dp_aux_private, dp_aux);
-
-	/* make sure wait has started */
-	usleep_range(20, 30);
-	complete(&aux->comp);
-}
-
-static void dp_aux_set_sim_mode(struct dp_aux *dp_aux, bool en,
-		u8 *edid, u8 *dpcd)
+static void dp_aux_set_sim_mode(struct dp_aux *dp_aux,
+		struct dp_aux_bridge *sim_bridge)
 {
 	struct dp_aux_private *aux;
 
@@ -743,12 +684,13 @@ static void dp_aux_set_sim_mode(struct dp_aux *dp_aux, bool en,
 
 	mutex_lock(&aux->mutex);
 
-	aux->edid = edid;
-	aux->dpcd = dpcd;
+	aux->sim_bridge = sim_bridge;
 
-	if (en) {
+	if (sim_bridge) {
 		atomic_set(&aux->aborted, 0);
 		aux->drm_aux.transfer = dp_aux_transfer_debug;
+	} else if (aux->aux_bridge && aux->aux_bridge->transfer) {
+		aux->drm_aux.transfer = dp_aux_bridge_transfer;
 	} else {
 		aux->drm_aux.transfer = dp_aux_transfer;
 	}
@@ -803,7 +745,8 @@ end:
 }
 
 struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
-		struct dp_parser *parser, struct device_node *aux_switch)
+		struct dp_parser *parser, struct device_node *aux_switch,
+		struct dp_aux_bridge *aux_bridge)
 {
 	int rc = 0;
 	struct dp_aux_private *aux;
@@ -832,9 +775,9 @@ struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
 	aux->catalog = catalog;
 	aux->cfg = parser->aux_cfg;
 	aux->aux_switch_node = aux_switch;
+	aux->aux_bridge = aux_bridge;
 	dp_aux = &aux->dp_aux;
 	aux->retry_cnt = 0;
-	aux->dp_aux.reg = 0xFFFF;
 
 	dp_aux->isr     = dp_aux_isr;
 	dp_aux->init    = dp_aux_init;
@@ -843,7 +786,6 @@ struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
 	dp_aux->drm_aux_deregister = dp_aux_deregister;
 	dp_aux->reconfig = dp_aux_reconfig;
 	dp_aux->abort = dp_aux_abort_transaction;
-	dp_aux->dpcd_updated = dp_aux_dpcd_updated;
 	dp_aux->set_sim_mode = dp_aux_set_sim_mode;
 	dp_aux->aux_switch = dp_aux_configure_aux_switch;
 

+ 5 - 5
msm/dp/dp_aux.h

@@ -8,6 +8,7 @@
 
 #include "dp_catalog.h"
 #include "drm/drm_dp_helper.h"
+#include "dp_aux_bridge.h"
 
 #define DP_STATE_NOTIFICATION_SENT          BIT(0)
 #define DP_STATE_TRAIN_1_STARTED            BIT(1)
@@ -35,8 +36,6 @@ enum dp_aux_error {
 };
 
 struct dp_aux {
-	u32 reg;
-	u32 size;
 	u32 state;
 
 	bool read;
@@ -51,13 +50,14 @@ struct dp_aux {
 	void (*deinit)(struct dp_aux *aux);
 	void (*reconfig)(struct dp_aux *aux);
 	void (*abort)(struct dp_aux *aux, bool abort);
-	void (*dpcd_updated)(struct dp_aux *aux);
-	void (*set_sim_mode)(struct dp_aux *aux, bool en, u8 *edid, u8 *dpcd);
+	void (*set_sim_mode)(struct dp_aux *aux,
+		struct dp_aux_bridge *sim_bridge);
 	int (*aux_switch)(struct dp_aux *aux, bool enable, int orientation);
 };
 
 struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
-		struct dp_parser *parser, struct device_node *aux_switch);
+		struct dp_parser *parser, struct device_node *aux_switch,
+		struct dp_aux_bridge *aux_bridge);
 void dp_aux_put(struct dp_aux *aux);
 
 #endif /*__DP_AUX_H_*/

+ 70 - 0
msm/dp/dp_aux_bridge.c

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dp_aux_bridge.h"
+
+static DEFINE_MUTEX(dp_aux_bridge_lock);
+static LIST_HEAD(du_aux_bridge_list);
+
+int dp_aux_add_bridge(struct dp_aux_bridge *bridge)
+{
+	mutex_lock(&dp_aux_bridge_lock);
+	list_add_tail(&bridge->head, &du_aux_bridge_list);
+	mutex_unlock(&dp_aux_bridge_lock);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+struct dp_aux_bridge *of_dp_aux_find_bridge(struct device_node *np)
+{
+	struct dp_aux_bridge *bridge;
+
+	mutex_lock(&dp_aux_bridge_lock);
+
+	list_for_each_entry(bridge, &du_aux_bridge_list, head) {
+		if (bridge->of_node == np) {
+			mutex_unlock(&dp_aux_bridge_lock);
+			return bridge;
+		}
+	}
+
+	mutex_unlock(&dp_aux_bridge_lock);
+	return NULL;
+}
+#endif
+

+ 129 - 0
msm/dp/dp_aux_bridge.h

@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Copyright (c) 2016 Intel Corporation
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _DP_AUX_BRIDGE_H_
+#define _DP_AUX_BRIDGE_H_
+
+#include <linux/types.h>
+#include <drm/drm_dp_helper.h>
+
+/**
+ * enum dp_aux_bridge_flag - DP aux bridge capability flag
+ * DP_AUX_BRIDGE_HPD: HPD will be generated by DP aux bridge
+ * DP_AUX_BRIDGE_MST: MST simulator is used by DP aux bridge
+ */
+enum dp_aux_bridge_flag {
+	DP_AUX_BRIDGE_HPD = (1 << 0),
+	DP_AUX_BRIDGE_MST = (1 << 1),
+};
+
+/**
+ * struct dp_aux_bridge - DP aux bridge control structure
+ * @of_node: device node pointer to the bridge
+ * @dev_priv: pointer to the bridge driver's internal context
+ * @flag: flag for capability
+ * @mst_ctx: pointer to mst context when DP_AUX_BRIDGE_MST is set
+ * @head: to keep track of all added bridges
+ */
+struct dp_aux_bridge {
+#ifdef CONFIG_OF
+	struct device_node *of_node;
+#endif
+	void *dev_priv;
+	u32 flag;
+	void *mst_ctx;
+	struct list_head head;
+
+	/**
+	 * @register_hpd:
+	 *
+	 * This callback is invoked whenever bridge is registered
+	 * for HPD handling
+	 *
+	 * The attach callback is optional.
+	 *
+	 * Host will pass HPD callback handle to bridge, with
+	 * arguments @hpd_cb(void* dev, bool hpd, bool hpd_irq):
+	 *
+	 *     @dev: private handle passed in register_hpd
+	 *     @hpd: true if HPD is high, false if HPD is low
+	 *     @hpd_irq: true if this is a HPD irq. @hpd will be
+	 *     ignored when hpd_irq is true.
+	 *
+	 * RETURNS:
+	 *
+	 * Zero on success, error code on failure.
+	 */
+	int (*register_hpd)(struct dp_aux_bridge *bridge,
+			int (*hpd_cb)(void *, bool, bool), void *dev);
+
+	/**
+	 * @transfer:
+	 *
+	 * This callback is invoked whenever dp_aux transfer
+	 * is called from host. Inside @transfer bridge can still
+	 * call @drm_aux->transfer to trigger the actual
+	 * DPCD/I2C transfer at host side.
+	 *
+	 * The attach callback is optional.
+	 *
+	 * RETURNS:
+	 *
+	 * Size of the bytes transferred, error code on failure.
+	 */
+	ssize_t (*transfer)(struct dp_aux_bridge *bridge,
+			struct drm_dp_aux *drm_aux,
+			struct drm_dp_aux_msg *msg);
+};
+
+/**
+ * dp_aux_add_bridge - Register DP aux bridge
+ * @bridge: bridge pointer
+ * return: 0 if successful
+ */
+int dp_aux_add_bridge(struct dp_aux_bridge *bridge);
+
+#ifdef CONFIG_OF
+/**
+ * of_dp_aux_find_bridge - Find registered DP aux bridge
+ * @np: device node pointer to the bridge
+ * return: DP aux bridge pointer, NULL if not found
+ */
+struct dp_aux_bridge *of_dp_aux_find_bridge(struct device_node *np);
+#endif
+
+#endif /* _DP_AUX_BRIDGE_H_ */
+

+ 208 - 0
msm/dp/dp_bridge_hpd.c

@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#define pr_fmt(fmt)	"[drm-dp] %s: " fmt, __func__
+
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include "dp_bridge_hpd.h"
+
+struct dp_bridge_hpd_private {
+	struct device *dev;
+	struct dp_hpd base;
+	struct dp_aux_bridge *bridge;
+	struct delayed_work work;
+	struct dp_hpd_cb *cb;
+	bool hpd;
+	bool hpd_irq;
+	struct mutex hpd_lock;
+};
+
+static int dp_bridge_hpd_connect(struct dp_bridge_hpd_private *bridge_hpd,
+		bool hpd)
+{
+	int rc = 0;
+
+	if (!bridge_hpd) {
+		pr_err("invalid input\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	bridge_hpd->base.hpd_high = hpd;
+	bridge_hpd->base.alt_mode_cfg_done = hpd;
+	bridge_hpd->base.hpd_irq = false;
+
+	if (!bridge_hpd->cb ||
+		!bridge_hpd->cb->configure ||
+		!bridge_hpd->cb->disconnect) {
+		pr_err("invalid cb\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	if (hpd)
+		rc = bridge_hpd->cb->configure(bridge_hpd->dev);
+	else
+		rc = bridge_hpd->cb->disconnect(bridge_hpd->dev);
+
+error:
+	return rc;
+}
+
+static int dp_bridge_hpd_attention(struct dp_bridge_hpd_private *bridge_hpd)
+{
+	int rc = 0;
+
+	if (!bridge_hpd) {
+		pr_err("invalid input\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	bridge_hpd->base.hpd_irq = true;
+
+	if (bridge_hpd->cb && bridge_hpd->cb->attention)
+		rc = bridge_hpd->cb->attention(bridge_hpd->dev);
+
+error:
+	return rc;
+}
+
+static void dp_bridge_hpd_work(struct work_struct *work)
+{
+	struct delayed_work *dw = to_delayed_work(work);
+	struct dp_bridge_hpd_private *bridge_hpd = container_of(dw,
+		struct dp_bridge_hpd_private, work);
+
+	mutex_lock(&bridge_hpd->hpd_lock);
+
+	if (bridge_hpd->hpd_irq)
+		dp_bridge_hpd_attention(bridge_hpd);
+	else
+		dp_bridge_hpd_connect(bridge_hpd, bridge_hpd->hpd);
+
+	mutex_unlock(&bridge_hpd->hpd_lock);
+}
+
+static int dp_bridge_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
+{
+	int rc = 0;
+	struct dp_bridge_hpd_private *bridge_hpd;
+
+	if (!dp_hpd) {
+		pr_err("invalid input\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
+
+	dp_bridge_hpd_connect(bridge_hpd, hpd);
+error:
+	return rc;
+}
+
+static int dp_bridge_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
+{
+	int rc = 0;
+	struct dp_bridge_hpd_private *bridge_hpd;
+
+	if (!dp_hpd) {
+		pr_err("invalid input\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
+
+	dp_bridge_hpd_attention(bridge_hpd);
+error:
+	return rc;
+}
+
+static int dp_bridge_hpd_cb(void *dp_hpd, bool hpd, bool hpd_irq)
+{
+	struct dp_bridge_hpd_private *bridge_hpd = dp_hpd;
+
+	mutex_lock(&bridge_hpd->hpd_lock);
+
+	bridge_hpd->hpd = hpd;
+	bridge_hpd->hpd_irq = hpd_irq;
+	queue_delayed_work(system_wq, &bridge_hpd->work, 0);
+
+	mutex_unlock(&bridge_hpd->hpd_lock);
+
+	return 0;
+}
+
+static int dp_bridge_hpd_register(struct dp_hpd *dp_hpd)
+{
+	struct dp_bridge_hpd_private *bridge_hpd;
+
+	if (!dp_hpd)
+		return -EINVAL;
+
+	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
+
+	return bridge_hpd->bridge->register_hpd(bridge_hpd->bridge,
+			dp_bridge_hpd_cb, bridge_hpd);
+}
+
+struct dp_hpd *dp_bridge_hpd_get(struct device *dev,
+	struct dp_hpd_cb *cb, struct dp_aux_bridge *aux_bridge)
+{
+	int rc = 0;
+	struct dp_bridge_hpd_private *bridge_hpd;
+
+	if (!dev || !cb) {
+		pr_err("invalid device\n");
+		rc = -EINVAL;
+		goto error;
+	}
+
+	bridge_hpd = devm_kzalloc(dev, sizeof(*bridge_hpd), GFP_KERNEL);
+	if (!bridge_hpd) {
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	bridge_hpd->dev = dev;
+	bridge_hpd->cb = cb;
+	bridge_hpd->bridge = aux_bridge;
+	mutex_init(&bridge_hpd->hpd_lock);
+	INIT_DELAYED_WORK(&bridge_hpd->work, dp_bridge_hpd_work);
+	bridge_hpd->base.simulate_connect = dp_bridge_hpd_simulate_connect;
+	bridge_hpd->base.simulate_attention = dp_bridge_hpd_simulate_attention;
+	bridge_hpd->base.register_hpd = dp_bridge_hpd_register;
+
+	return &bridge_hpd->base;
+error:
+	return ERR_PTR(rc);
+}
+
+void dp_bridge_hpd_put(struct dp_hpd *dp_hpd)
+{
+	struct dp_bridge_hpd_private *bridge_hpd;
+
+	if (!dp_hpd)
+		return;
+
+	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);
+
+	devm_kfree(bridge_hpd->dev, bridge_hpd);
+}

+ 42 - 0
msm/dp/dp_bridge_hpd.h

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DP_BRIDGE_HPD_H_
+#define _DP_BRIDGE_HPD_H_
+
+#include "dp_hpd.h"
+
+/**
+ * dp_bridge_hpd_get() - configure and get the DisplayPlot HPD module data
+ *
+ * @dev: device instance of the caller
+ * @cb: callback function for HPD response
+ * @aux_bridge: handle for aux_bridge driver data
+ * return: pointer to allocated gpio hpd module data
+ *
+ * This function sets up the gpio hpd module
+ */
+struct dp_hpd *dp_bridge_hpd_get(struct device *dev,
+	struct dp_hpd_cb *cb, struct dp_aux_bridge *aux_bridge);
+
+/**
+ * dp_bridge_hpd_put()
+ *
+ * Cleans up dp_hpd instance
+ *
+ * @hpd: instance of gpio_hpd
+ */
+void dp_bridge_hpd_put(struct dp_hpd *hpd);
+
+#endif /* _DP_BRIDGE_HPD_H_ */

+ 256 - 302
msm/dp/dp_debug.c

@@ -5,6 +5,8 @@
 
 #include <linux/debugfs.h>
 #include <linux/slab.h>
+#include <drm/drm_dp_mst_helper.h>
+#include <drm/drm_probe_helper.h>
 
 #include "dp_power.h"
 #include "dp_catalog.h"
@@ -15,19 +17,20 @@
 #include "dp_display.h"
 #include "dp_pll.h"
 #include "dp_hpd.h"
+#include "dp_mst_sim.h"
 
 #define DEBUG_NAME "drm_dp"
 
 struct dp_debug_private {
 	struct dentry *root;
-	u8 *edid;
-	u32 edid_size;
 
-	u8 *dpcd;
+	u32 dpcd_offset;
 	u32 dpcd_size;
 
 	u32 mst_con_id;
+	u32 mst_edid_idx;
 	bool hotplug;
+	u32 sim_mode;
 
 	char exe_mode[SZ_32];
 	char reg_dump[SZ_32];
@@ -45,40 +48,73 @@ struct dp_debug_private {
 	struct dp_pll *pll;
 	struct dp_display *display;
 	struct mutex lock;
+	struct dp_aux_bridge *sim_bridge;
 };
 
-static int dp_debug_get_edid_buf(struct dp_debug_private *debug)
+static int dp_debug_sim_hpd_cb(void *arg, bool hpd, bool hpd_irq)
 {
-	int rc = 0;
+	struct dp_debug_private *debug = arg;
 
-	if (!debug->edid) {
-		debug->edid = devm_kzalloc(debug->dev, SZ_256, GFP_KERNEL);
-		if (!debug->edid) {
-			rc = -ENOMEM;
-			goto end;
-		}
+	if (hpd_irq)
+		return debug->hpd->simulate_attention(debug->hpd, 0);
+	else
+		return debug->hpd->simulate_connect(debug->hpd, hpd);
+}
 
-		debug->edid_size = SZ_256;
-	}
-end:
-	return rc;
+static int dp_debug_attach_sim_bridge(struct dp_debug_private *debug)
+{
+	int ret;
+
+	if (debug->sim_bridge)
+		return 0;
+
+	ret = dp_sim_create_bridge(debug->dev, &debug->sim_bridge);
+	if (ret)
+		return ret;
+
+	dp_sim_update_port_num(debug->sim_bridge, 1);
+
+	if (debug->sim_bridge->register_hpd)
+		debug->sim_bridge->register_hpd(debug->sim_bridge,
+				dp_debug_sim_hpd_cb, debug);
+
+	return 0;
 }
 
-static int dp_debug_get_dpcd_buf(struct dp_debug_private *debug)
+static void dp_debug_enable_sim_mode(struct dp_debug_private *debug,
+		u32 mode_mask)
 {
-	int rc = 0;
+	/* return if mode is already enabled */
+	if ((debug->sim_mode & mode_mask) == mode_mask)
+		return;
 
-	if (!debug->dpcd) {
-		debug->dpcd = devm_kzalloc(debug->dev, SZ_4K, GFP_KERNEL);
-		if (!debug->dpcd) {
-			rc = -ENOMEM;
-			goto end;
-		}
+	/* create bridge if not yet */
+	if (dp_debug_attach_sim_bridge(debug))
+		return;
 
-		debug->dpcd_size = SZ_4K;
-	}
-end:
-	return rc;
+	/* switch to bridge mode */
+	if (!debug->sim_mode)
+		debug->aux->set_sim_mode(debug->aux, debug->sim_bridge);
+
+	/* update sim mode */
+	debug->sim_mode |= mode_mask;
+	dp_sim_set_sim_mode(debug->sim_bridge, debug->sim_mode);
+}
+
+static void dp_debug_disable_sim_mode(struct dp_debug_private *debug,
+		u32 mode_mask)
+{
+	/* return if mode is already disabled */
+	if (!(debug->sim_mode & mode_mask))
+		return;
+
+	/* update sim mode */
+	debug->sim_mode &= ~mode_mask;
+	dp_sim_set_sim_mode(debug->sim_bridge, debug->sim_mode);
+
+	/* switch to normal mode */
+	if (!debug->sim_mode)
+		debug->aux->set_sim_mode(debug->aux, NULL);
 }
 
 static ssize_t dp_debug_write_edid(struct file *file,
@@ -112,32 +148,13 @@ static ssize_t dp_debug_write_edid(struct file *file,
 
 	edid_size = size / char_to_nib;
 	buf_t = buf;
+	size = edid_size;
 
-	if (dp_debug_get_edid_buf(debug))
+	edid = kzalloc(size, GFP_KERNEL);
+	if (!edid)
 		goto bail;
 
-	if (edid_size != debug->edid_size) {
-		DP_DEBUG("realloc debug edid\n");
-
-		if (debug->edid) {
-			devm_kfree(debug->dev, debug->edid);
-
-			debug->edid = devm_kzalloc(debug->dev,
-						edid_size, GFP_KERNEL);
-			if (!debug->edid) {
-				rc = -ENOMEM;
-				goto bail;
-			}
-
-			debug->edid_size = edid_size;
-
-			debug->aux->set_sim_mode(debug->aux,
-					debug->dp_debug.sim_mode,
-					debug->edid, debug->dpcd);
-		}
-	}
-
-	while (edid_size--) {
+	while (size--) {
 		char t[3];
 		int d;
 
@@ -149,23 +166,16 @@ static ssize_t dp_debug_write_edid(struct file *file,
 			goto bail;
 		}
 
-		if (debug->edid && (edid_buf_index < debug->edid_size))
-			debug->edid[edid_buf_index++] = d;
-
+		edid[edid_buf_index++] = d;
 		buf_t += char_to_nib;
 	}
 
-	edid = debug->edid;
+	dp_debug_enable_sim_mode(debug, DP_SIM_MODE_EDID);
+	dp_sim_update_port_edid(debug->sim_bridge, debug->mst_edid_idx,
+			edid, edid_size);
 bail:
 	kfree(buf);
-	debug->panel->set_edid(debug->panel, edid, debug->edid_size);
-
-	/*
-	 * print edid status as this code is executed
-	 * only while running in debug mode which is manually
-	 * triggered by a tester or a script.
-	 */
-	DP_INFO("[%s]\n", edid ? "SET" : "CLEAR");
+	kfree(edid);
 
 	mutex_unlock(&debug->lock);
 	return rc;
@@ -182,7 +192,6 @@ static ssize_t dp_debug_write_dpcd(struct file *file,
 	ssize_t rc = count;
 	char offset_ch[5];
 	u32 offset, data_len;
-	const u32 dp_receiver_cap_size = 16;
 
 	if (!debug)
 		return -ENODEV;
@@ -214,15 +223,6 @@ static ssize_t dp_debug_write_dpcd(struct file *file,
 		goto bail;
 	}
 
-	if (dp_debug_get_dpcd_buf(debug))
-		goto bail;
-
-	if (offset == 0xFFFF) {
-		DP_ERR("clearing dpcd\n");
-		memset(debug->dpcd, 0, debug->dpcd_size);
-		goto bail;
-	}
-
 	size -= 4;
 	if (size == 0)
 		goto bail;
@@ -231,7 +231,11 @@ static ssize_t dp_debug_write_dpcd(struct file *file,
 	data_len = dpcd_size;
 	buf_t = buf + 4;
 
-	dpcd_buf_index = offset;
+	dpcd = kzalloc(dpcd_size, GFP_KERNEL);
+	if (ZERO_OR_NULL_PTR(dpcd)) {
+		rc = -ENOMEM;
+		goto bail;
+	}
 
 	while (dpcd_size--) {
 		char t[3];
@@ -245,33 +249,33 @@ static ssize_t dp_debug_write_dpcd(struct file *file,
 			goto bail;
 		}
 
-		if (dpcd_buf_index < debug->dpcd_size)
-			debug->dpcd[dpcd_buf_index++] = d;
+		dpcd[dpcd_buf_index++] = d;
 
 		buf_t += char_to_nib;
 	}
 
-	dpcd = debug->dpcd;
+	/*
+	 * if link training status registers are reprogramed,
+	 * read link training status from simulator, otherwise
+	 * read link training status from real aux channel.
+	 */
+	if (offset <= DP_LANE0_1_STATUS &&
+			offset + dpcd_buf_index > DP_LANE0_1_STATUS)
+		dp_debug_enable_sim_mode(debug,
+			DP_SIM_MODE_DPCD_READ | DP_SIM_MODE_LINK_TRAIN);
+	else
+		dp_debug_enable_sim_mode(debug, DP_SIM_MODE_DPCD_READ);
+
+	dp_sim_write_dpcd_reg(debug->sim_bridge,
+			dpcd, dpcd_buf_index, offset);
+	debug->dpcd_offset = offset;
+	debug->dpcd_size = dpcd_buf_index;
+
 bail:
 	kfree(buf);
-
-	if (!dpcd || (size / char_to_nib) >= dp_receiver_cap_size ||
-	    offset == 0xffff) {
-		debug->panel->set_dpcd(debug->panel, dpcd);
-		/*
-		 * print dpcd status as this code is executed
-		 * only while running in debug mode which is manually
-		 * triggered by a tester or a script.
-		 */
-		if (!dpcd || (offset == 0xffff))
-			DP_INFO("[%s]\n", "CLEAR");
-		else
-			DP_INFO("[%s]\n", "SET");
-	}
+	kfree(dpcd);
 
 	mutex_unlock(&debug->lock);
-
-	debug->aux->dpcd_updated(debug->aux);
 	return rc;
 }
 
@@ -283,44 +287,42 @@ static ssize_t dp_debug_read_dpcd(struct file *file,
 	int const buf_size = SZ_4K;
 	u32 offset = 0;
 	u32 len = 0;
-	bool notify = false;
+	u8 *dpcd;
 
-	if (!debug || !debug->aux || !debug->dpcd)
+	if (!debug || !debug->aux)
 		return -ENODEV;
 
-	mutex_lock(&debug->lock);
 	if (*ppos)
-		goto end;
+		return 0;
 
 	buf = kzalloc(buf_size, GFP_KERNEL);
 	if (!buf)
-		goto end;
+		return -ENOMEM;
 
-	len += snprintf(buf, buf_size, "0x%x", debug->aux->reg);
+	mutex_lock(&debug->lock);
+	dpcd = kzalloc(buf_size, GFP_KERNEL);
+	if (!dpcd)
+		goto bail;
 
-	if (!debug->aux->read) {
-		while (1) {
-			if (debug->aux->reg + offset >= buf_size ||
-			    offset >= debug->aux->size)
-				break;
+	dp_sim_read_dpcd_reg(debug->sim_bridge, dpcd,
+			debug->dpcd_size, debug->dpcd_offset);
 
-			len += snprintf(buf + len, buf_size - len, "0x%x",
-				debug->dpcd[debug->aux->reg + offset++]);
-		}
+	len += snprintf(buf, buf_size, "0x%x", debug->dpcd_offset);
 
-		notify = true;
+	while (offset < debug->dpcd_size) {
+		len += snprintf(buf + len, buf_size - len, "0x%x",
+			dpcd[debug->dpcd_offset + offset++]);
 	}
 
+	kfree(dpcd);
+
 	len = min_t(size_t, count, len);
 	if (!copy_to_user(user_buff, buf, len))
 		*ppos += len;
 
-	kfree(buf);
-end:
+bail:
 	mutex_unlock(&debug->lock);
-
-	if (notify)
-		debug->aux->dpcd_updated(debug->aux);
+	kfree(buf);
 
 	return len;
 }
@@ -371,6 +373,7 @@ static ssize_t dp_debug_write_edid_modes(struct file *file,
 		const char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
+	struct dp_panel *panel;
 	char buf[SZ_32];
 	size_t len = 0;
 	int hdisplay = 0, vdisplay = 0, vrefresh = 0, aspect_ratio;
@@ -381,6 +384,8 @@ static ssize_t dp_debug_write_edid_modes(struct file *file,
 	if (*ppos)
 		goto end;
 
+	panel = debug->panel;
+
 	/* Leave room for termination char */
 	len = min_t(size_t, count, SZ_32 - 1);
 	if (copy_from_user(buf, user_buff, len))
@@ -395,15 +400,15 @@ static ssize_t dp_debug_write_edid_modes(struct file *file,
 	if (!hdisplay || !vdisplay || !vrefresh)
 		goto clear;
 
-	debug->dp_debug.debug_en = true;
-	debug->dp_debug.hdisplay = hdisplay;
-	debug->dp_debug.vdisplay = vdisplay;
-	debug->dp_debug.vrefresh = vrefresh;
-	debug->dp_debug.aspect_ratio = aspect_ratio;
+	panel->mode_override = true;
+	panel->hdisplay = hdisplay;
+	panel->vdisplay = vdisplay;
+	panel->vrefresh = vrefresh;
+	panel->aspect_ratio = aspect_ratio;
 	goto end;
 clear:
 	DP_DEBUG("clearing debug modes\n");
-	debug->dp_debug.debug_en = false;
+	panel->mode_override = false;
 end:
 	return len;
 }
@@ -412,18 +417,21 @@ static ssize_t dp_debug_write_edid_modes_mst(struct file *file,
 		const char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
-	struct dp_mst_connector *mst_connector;
+	struct drm_connector *connector;
+	struct sde_connector *sde_conn;
+	struct dp_panel *panel = NULL;
 	char buf[SZ_512];
 	char *read_buf;
 	size_t len = 0;
 
 	int hdisplay = 0, vdisplay = 0, vrefresh = 0, aspect_ratio = 0;
 	int con_id = 0, offset = 0, debug_en = 0;
-	bool in_list = false;
 
 	if (!debug)
 		return -ENODEV;
 
+	mutex_lock(&debug->lock);
+
 	if (*ppos)
 		goto end;
 
@@ -434,33 +442,32 @@ static ssize_t dp_debug_write_edid_modes_mst(struct file *file,
 	buf[len] = '\0';
 	read_buf = buf;
 
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
 	while (sscanf(read_buf, "%d %d %d %d %d %d%n", &debug_en, &con_id,
 			&hdisplay, &vdisplay, &vrefresh, &aspect_ratio,
 			&offset) == 6) {
-		list_for_each_entry(mst_connector,
-				&debug->dp_debug.dp_mst_connector_list.list,
-				list) {
-			if (mst_connector->con_id == con_id) {
-				in_list = true;
-				mst_connector->debug_en = (bool) debug_en;
-				mst_connector->hdisplay = hdisplay;
-				mst_connector->vdisplay = vdisplay;
-				mst_connector->vrefresh = vrefresh;
-				mst_connector->aspect_ratio = aspect_ratio;
-				DP_INFO("Setting %dx%dp%d on conn %d\n",
-					hdisplay, vdisplay, vrefresh, con_id);
+		connector = drm_connector_lookup((*debug->connector)->dev,
+				NULL, con_id);
+		if (connector) {
+			sde_conn = to_sde_connector(connector);
+			panel = sde_conn->drv_panel;
+			if (panel && sde_conn->mst_port) {
+				panel->mode_override = debug_en;
+				panel->hdisplay = hdisplay;
+				panel->vdisplay = vdisplay;
+				panel->vrefresh = vrefresh;
+				panel->aspect_ratio = aspect_ratio;
+			} else {
+				DP_ERR("connector id %d is not mst\n", con_id);
 			}
+			drm_connector_put(connector);
+		} else {
+			DP_ERR("invalid connector id %d\n", con_id);
 		}
 
-		if (!in_list)
-			DP_DEBUG("dp connector id %d is invalid\n", con_id);
-
-		in_list = false;
 		read_buf += offset;
 	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
 end:
+	mutex_unlock(&debug->lock);
 	return len;
 }
 
@@ -468,17 +475,19 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,
 		const char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
-	struct dp_mst_connector *mst_connector;
+	struct drm_connector *connector;
+	struct sde_connector *sde_conn;
+	struct drm_dp_mst_port *mst_port;
+	struct dp_panel *dp_panel;
 	char buf[SZ_32];
 	size_t len = 0;
 	int con_id = 0, status;
-	bool in_list = false;
-	const int dp_en = BIT(3), hpd_high = BIT(7), hpd_irq = BIT(8);
-	int vdo = dp_en | hpd_high | hpd_irq;
 
 	if (!debug)
 		return -ENODEV;
 
+	mutex_lock(&debug->lock);
+
 	if (*ppos)
 		goto end;
 
@@ -489,50 +498,49 @@ static ssize_t dp_debug_write_mst_con_id(struct file *file,
 
 	buf[len] = '\0';
 
-	if (sscanf(buf, "%d %d", &con_id, &status) != 2) {
-		len = 0;
+	if (sscanf(buf, "%d %d", &con_id, &status) != 2)
 		goto end;
-	}
 
 	if (!con_id)
 		goto clear;
 
-	/* Verify that the connector id is for a valid mst connector. */
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
-	list_for_each_entry(mst_connector,
-			&debug->dp_debug.dp_mst_connector_list.list, list) {
-		if (mst_connector->con_id == con_id) {
-			in_list = true;
-			debug->mst_con_id = con_id;
-			mst_connector->state = status;
-			break;
-		}
-	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
-
-	if (!in_list && status != connector_status_connected) {
+	connector = drm_connector_lookup((*debug->connector)->dev,
+			NULL, con_id);
+	if (!connector) {
 		DP_ERR("invalid connector id %u\n", con_id);
 		goto end;
 	}
 
+	sde_conn = to_sde_connector(connector);
+
+	if (!sde_conn->drv_panel || !sde_conn->mst_port) {
+		DP_ERR("invalid connector state %d\n", con_id);
+		goto out;
+	}
+
+	debug->mst_con_id = con_id;
+
 	if (status == connector_status_unknown)
-		goto end;
+		goto out;
 
-	debug->dp_debug.mst_hpd_sim = true;
+	mst_port = sde_conn->mst_port;
+	dp_panel = sde_conn->drv_panel;
 
-	if (status == connector_status_connected) {
-		DP_INFO("plug mst connector\n", con_id, status);
-		debug->dp_debug.mst_sim_add_con = true;
+	if (debug->dp_debug.sim_mode) {
+		dp_sim_update_port_status(debug->sim_bridge,
+				mst_port->port_num, status);
 	} else {
-		DP_INFO("unplug mst connector %d\n", con_id, status);
+		dp_panel->mst_hide = (status == connector_status_disconnected);
+		drm_kms_helper_hotplug_event(connector->dev);
 	}
-
-	debug->hpd->simulate_attention(debug->hpd, vdo);
+out:
+	drm_connector_put(connector);
 	goto end;
 clear:
 	DP_DEBUG("clearing mst_con_id\n");
 	debug->mst_con_id = 0;
 end:
+	mutex_unlock(&debug->lock);
 	return len;
 }
 
@@ -556,7 +564,6 @@ static ssize_t dp_debug_write_mst_con_add(struct file *file,
 	if (copy_from_user(buf, user_buff, len))
 		goto end;
 
-	debug->dp_debug.mst_hpd_sim = true;
 	debug->dp_debug.mst_sim_add_con = true;
 	debug->hpd->simulate_attention(debug->hpd, vdo);
 end:
@@ -567,7 +574,8 @@ static ssize_t dp_debug_write_mst_con_remove(struct file *file,
 		const char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
-	struct dp_mst_connector *mst_connector;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
 	char buf[SZ_32];
 	size_t len = 0;
 	int con_id = 0;
@@ -596,23 +604,20 @@ static ssize_t dp_debug_write_mst_con_remove(struct file *file,
 	if (!con_id)
 		goto end;
 
-	/* Verify that the connector id is for a valid mst connector. */
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
-	list_for_each_entry(mst_connector,
-			&debug->dp_debug.dp_mst_connector_list.list, list) {
-		if (mst_connector->con_id == con_id) {
+	drm_connector_list_iter_begin((*debug->connector)->dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		if (connector->base.id == con_id) {
 			in_list = true;
 			break;
 		}
 	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
+	drm_connector_list_iter_end(&conn_iter);
 
 	if (!in_list) {
 		DRM_ERROR("invalid connector id %u\n", con_id);
 		goto end;
 	}
 
-	debug->dp_debug.mst_hpd_sim = true;
 	debug->dp_debug.mst_sim_remove_con = true;
 	debug->dp_debug.mst_sim_remove_con_id = con_id;
 	debug->hpd->simulate_attention(debug->hpd, vdo);
@@ -812,6 +817,8 @@ static ssize_t dp_debug_mst_sideband_mode_write(struct file *file,
 	if (!debug)
 		return -ENODEV;
 
+	mutex_lock(&debug->lock);
+
 	/* Leave room for termination char */
 	len = min_t(size_t, count, SZ_8 - 1);
 	if (copy_from_user(buf, user_buff, len))
@@ -821,19 +828,29 @@ static ssize_t dp_debug_mst_sideband_mode_write(struct file *file,
 
 	if (sscanf(buf, "%d %u", &mst_sideband_mode, &mst_port_cnt) != 2) {
 		DP_ERR("invalid input\n");
-		return -EINVAL;
+		goto bail;
 	}
 
-	if (mst_port_cnt > DP_MST_SIM_MAX_PORTS) {
-		DP_ERR("port cnt:%d exceeding max:%d\n", mst_port_cnt,
-				DP_MST_SIM_MAX_PORTS);
-		return -EINVAL;
-	}
+	if (!mst_port_cnt)
+		mst_port_cnt = 1;
+
+	debug->mst_edid_idx = 0;
+
+	if (mst_sideband_mode)
+		dp_debug_disable_sim_mode(debug, DP_SIM_MODE_MST);
+	else
+		dp_debug_enable_sim_mode(debug, DP_SIM_MODE_MST);
+
+	dp_sim_update_port_num(debug->sim_bridge, mst_port_cnt);
+
+	buf[0] = !mst_sideband_mode;
+	dp_sim_write_dpcd_reg(debug->sim_bridge, buf, 1, DP_MSTM_CAP);
 
-	debug->parser->has_mst_sideband = mst_sideband_mode ? true : false;
-	debug->dp_debug.mst_port_cnt = mst_port_cnt;
 	DP_DEBUG("mst_sideband_mode: %d port_cnt:%d\n",
 			mst_sideband_mode, mst_port_cnt);
+
+bail:
+	mutex_unlock(&debug->lock);
 	return count;
 }
 
@@ -1056,50 +1073,29 @@ static ssize_t dp_debug_read_edid_modes_mst(struct file *file,
 		char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
-	struct dp_mst_connector *mst_connector;
 	char *buf;
 	u32 len = 0, ret = 0, max_size = SZ_4K;
-	int rc = 0;
 	struct drm_connector *connector;
 	struct drm_display_mode *mode;
-	bool in_list = false;
 
 	if (!debug) {
 		DP_ERR("invalid data\n");
-		rc = -ENODEV;
-		goto error;
-	}
-
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
-	list_for_each_entry(mst_connector,
-			&debug->dp_debug.dp_mst_connector_list.list, list) {
-		if (mst_connector->con_id == debug->mst_con_id) {
-			connector = mst_connector->conn;
-			in_list = true;
-		}
+		return -ENODEV;
 	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
 
-	if (!in_list) {
-		DP_ERR("connector %u not in mst list\n", debug->mst_con_id);
-		rc = -EINVAL;
-		goto error;
-	}
+	if (*ppos)
+		return 0;
 
+	connector = drm_connector_lookup((*debug->connector)->dev,
+			NULL, debug->mst_con_id);
 	if (!connector) {
-		DP_ERR("connector is NULL\n");
-		rc = -EINVAL;
-		goto error;
+		DP_ERR("connector %u not in mst list\n", debug->mst_con_id);
+		return 0;
 	}
 
-	if (*ppos)
-		goto error;
-
 	buf = kzalloc(SZ_4K, GFP_KERNEL);
-	if (!buf) {
-		rc = -ENOMEM;
-		goto error;
-	}
+	if (!buf)
+		goto clean;
 
 	mutex_lock(&connector->dev->mode_config.mutex);
 	list_for_each_entry(mode, &connector->modes, head) {
@@ -1115,17 +1111,15 @@ static ssize_t dp_debug_read_edid_modes_mst(struct file *file,
 
 	len = min_t(size_t, count, len);
 	if (copy_to_user(user_buff, buf, len)) {
-		kfree(buf);
-		rc = -EFAULT;
-		goto error;
+		len = -EFAULT;
+		goto clean;
 	}
 
 	*ppos += len;
+clean:
 	kfree(buf);
-
+	drm_connector_put(connector);
 	return len;
-error:
-	return rc;
 }
 
 static ssize_t dp_debug_read_mst_con_id(struct file *file,
@@ -1173,11 +1167,13 @@ static ssize_t dp_debug_read_mst_conn_info(struct file *file,
 		char __user *user_buff, size_t count, loff_t *ppos)
 {
 	struct dp_debug_private *debug = file->private_data;
-	struct dp_mst_connector *mst_connector;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+	struct sde_connector *sde_conn;
+	struct dp_display *display;
 	char *buf;
 	u32 len = 0, ret = 0, max_size = SZ_4K;
 	int rc = 0;
-	struct drm_connector *connector;
 
 	if (!debug) {
 		DP_ERR("invalid data\n");
@@ -1194,21 +1190,13 @@ static ssize_t dp_debug_read_mst_conn_info(struct file *file,
 		goto error;
 	}
 
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
-	list_for_each_entry(mst_connector,
-			&debug->dp_debug.dp_mst_connector_list.list, list) {
-		/* Do not print info for head node */
-		if (mst_connector->con_id == -1)
-			continue;
-
-		connector = mst_connector->conn;
-
-		if (!connector) {
-			DP_ERR("connector for id %d is NULL\n",
-					mst_connector->con_id);
+	drm_connector_list_iter_begin((*debug->connector)->dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		sde_conn = to_sde_connector(connector);
+		display = sde_conn->display;
+		if (!sde_conn->mst_port ||
+				display->base_connector != (*debug->connector))
 			continue;
-		}
-
 		ret = scnprintf(buf + len, max_size,
 				"conn name:%s, conn id:%d state:%d\n",
 				connector->name, connector->base.id,
@@ -1216,7 +1204,7 @@ static ssize_t dp_debug_read_mst_conn_info(struct file *file,
 		if (dp_debug_check_buffer_overflow(ret, &max_size, &len))
 			break;
 	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
+	drm_connector_list_iter_end(&conn_iter);
 
 	len = min_t(size_t, count, len);
 	if (copy_to_user(user_buff, buf, len)) {
@@ -1570,7 +1558,7 @@ static ssize_t dp_debug_read_hdr_mst(struct file *file,
 	struct dp_debug_private *debug = file->private_data;
 	char *buf = NULL;
 	u32 len = 0, max_size = SZ_4K;
-	struct dp_mst_connector *mst_connector;
+	struct drm_connector_list_iter conn_iter;
 	struct drm_connector *connector;
 	bool in_list = false;
 
@@ -1579,15 +1567,14 @@ static ssize_t dp_debug_read_hdr_mst(struct file *file,
 		return -ENODEV;
 	}
 
-	mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock);
-	list_for_each_entry(mst_connector,
-			&debug->dp_debug.dp_mst_connector_list.list, list) {
-		if (mst_connector->con_id == debug->mst_con_id) {
-			connector = mst_connector->conn;
+	drm_connector_list_iter_begin((*debug->connector)->dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		if (connector->base.id == debug->mst_con_id) {
 			in_list = true;
+			break;
 		}
 	}
-	mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock);
+	drm_connector_list_iter_end(&conn_iter);
 
 	if (!in_list) {
 		DP_ERR("connector %u not in mst list\n", debug->mst_con_id);
@@ -1626,20 +1613,15 @@ static ssize_t dp_debug_read_hdr_mst(struct file *file,
 
 static void dp_debug_set_sim_mode(struct dp_debug_private *debug, bool sim)
 {
-	if (sim) {
-		if (dp_debug_get_edid_buf(debug))
-			return;
-
-		if (dp_debug_get_dpcd_buf(debug)) {
-			devm_kfree(debug->dev, debug->edid);
-			debug->edid = NULL;
-			return;
-		}
+	struct drm_connector_list_iter conn_iter;
+	struct drm_connector *connector;
+	struct sde_connector *sde_conn;
+	struct dp_display *display;
+	struct dp_panel *panel;
 
+	if (sim) {
 		debug->dp_debug.sim_mode = true;
-		debug->aux->set_sim_mode(debug->aux, true,
-			debug->edid, debug->dpcd);
-		debug->ctrl->set_sim_mode(debug->ctrl, true);
+		dp_debug_enable_sim_mode(debug, DP_SIM_MODE_ALL);
 	} else {
 		if (debug->hotplug) {
 			DP_WARN("sim mode off before hotplug disconnect\n");
@@ -1649,22 +1631,24 @@ static void dp_debug_set_sim_mode(struct dp_debug_private *debug, bool sim)
 		debug->aux->abort(debug->aux, true);
 		debug->ctrl->abort(debug->ctrl, true);
 
-		debug->aux->set_sim_mode(debug->aux, false, NULL, NULL);
-		debug->ctrl->set_sim_mode(debug->ctrl, false);
 		debug->dp_debug.sim_mode = false;
 
-		debug->panel->set_edid(debug->panel, 0, 0);
-		if (debug->edid) {
-			devm_kfree(debug->dev, debug->edid);
-			debug->edid = NULL;
-		}
+		debug->mst_edid_idx = 0;
+		dp_debug_disable_sim_mode(debug, DP_SIM_MODE_ALL);
+	}
 
-		debug->panel->set_dpcd(debug->panel, 0);
-		if (debug->dpcd) {
-			devm_kfree(debug->dev, debug->dpcd);
-			debug->dpcd = NULL;
+	/* clear override settings in panel */
+	drm_connector_list_iter_begin((*debug->connector)->dev, &conn_iter);
+	drm_for_each_connector_iter(connector, &conn_iter) {
+		sde_conn = to_sde_connector(connector);
+		display = sde_conn->display;
+		if (display->base_connector == (*debug->connector)) {
+			panel = sde_conn->drv_panel;
+			panel->mode_override = false;
+			panel->mst_hide = false;
 		}
 	}
+	drm_connector_list_iter_end(&conn_iter);
 
 	/*
 	 * print simulation status as this code is executed
@@ -1989,6 +1973,8 @@ static int dp_debug_init_mst(struct dp_debug_private *debug, struct dentry *dir)
 		return rc;
 	}
 
+	debugfs_create_u32("mst_edid_idx", 0644, dir, &debug->mst_edid_idx);
+
 	return rc;
 }
 
@@ -2407,18 +2393,6 @@ error:
 	return rc;
 }
 
-u8 *dp_debug_get_edid(struct dp_debug *dp_debug)
-{
-	struct dp_debug_private *debug;
-
-	if (!dp_debug)
-		return NULL;
-
-	debug = container_of(dp_debug, struct dp_debug_private, dp_debug);
-
-	return debug->edid;
-}
-
 static void dp_debug_abort(struct dp_debug *dp_debug)
 {
 	struct dp_debug_private *debug;
@@ -2466,7 +2440,6 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in)
 		goto error;
 	}
 
-	debug->dp_debug.debug_en = false;
 	debug->hpd = in->hpd;
 	debug->link = in->link;
 	debug->panel = in->panel;
@@ -2480,9 +2453,6 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in)
 	debug->display = in->display;
 
 	dp_debug = &debug->dp_debug;
-	dp_debug->vdisplay = 0;
-	dp_debug->hdisplay = 0;
-	dp_debug->vrefresh = 0;
 
 	mutex_init(&debug->lock);
 
@@ -2493,21 +2463,9 @@ struct dp_debug *dp_debug_get(struct dp_debug_in *in)
 	}
 
 	debug->aux->access_lock = &debug->lock;
-	dp_debug->get_edid = dp_debug_get_edid;
 	dp_debug->abort = dp_debug_abort;
 	dp_debug->set_mst_con = dp_debug_set_mst_con;
 
-	INIT_LIST_HEAD(&dp_debug->dp_mst_connector_list.list);
-
-	/*
-	 * Do not associate the head of the list with any connector in order to
-	 * maintain backwards compatibility with the SST use case.
-	 */
-	dp_debug->dp_mst_connector_list.con_id = -1;
-	dp_debug->dp_mst_connector_list.conn = NULL;
-	dp_debug->dp_mst_connector_list.debug_en = false;
-	mutex_init(&dp_debug->dp_mst_connector_list.lock);
-
 	dp_debug->max_pclk_khz = debug->parser->max_pclk_khz;
 
 	return dp_debug;
@@ -2526,6 +2484,9 @@ static int dp_debug_deinit(struct dp_debug *dp_debug)
 
 	debugfs_remove_recursive(debug->root);
 
+	if (debug->sim_bridge)
+		dp_sim_destroy_bridge(debug->sim_bridge);
+
 	return 0;
 }
 
@@ -2540,14 +2501,7 @@ void dp_debug_put(struct dp_debug *dp_debug)
 
 	dp_debug_deinit(dp_debug);
 
-	mutex_destroy(&dp_debug->dp_mst_connector_list.lock);
 	mutex_destroy(&debug->lock);
 
-	if (debug->edid)
-		devm_kfree(debug->dev, debug->edid);
-
-	if (debug->dpcd)
-		devm_kfree(debug->dev, debug->dpcd);
-
 	devm_kfree(debug->dev, debug);
 }

+ 0 - 18
msm/dp/dp_debug.h

@@ -48,57 +48,39 @@
 
 /**
  * struct dp_debug
- * @debug_en: specifies whether debug mode enabled
  * @sim_mode: specifies whether sim mode enabled
  * @psm_enabled: specifies whether psm enabled
  * @hdcp_disabled: specifies if hdcp is disabled
  * @hdcp_wait_sink_sync: used to wait for sink synchronization before HDCP auth
- * @aspect_ratio: used to filter out aspect_ratio value
- * @vdisplay: used to filter out vdisplay value
- * @hdisplay: used to filter out hdisplay value
- * @vrefresh: used to filter out vrefresh value
  * @tpg_state: specifies whether tpg feature is enabled
  * @max_pclk_khz: max pclk supported
  * @force_encryption: enable/disable forced encryption for HDCP 2.2
  * @skip_uevent: skip hotplug uevent to the user space
  * @hdcp_status: string holding hdcp status information
- * @dp_mst_connector_list: list containing all dp mst connectors
- * @mst_hpd_sim: specifies whether simulated hpd enabled
  * @mst_sim_add_con: specifies whether new sim connector is to be added
  * @mst_sim_remove_con: specifies whether sim connector is to be removed
  * @mst_sim_remove_con_id: specifies id of sim connector to be removed
- * @mst_port_cnt: number of mst ports to be added during hpd
  * @connect_notification_delay_ms: time (in ms) to wait for any attention
  *              messages before sending the connect notification uevent
  * @disconnect_delay_ms: time (in ms) to wait before turning off the mainlink
  *              in response to HPD low of cable disconnect event
  */
 struct dp_debug {
-	bool debug_en;
 	bool sim_mode;
 	bool psm_enabled;
 	bool hdcp_disabled;
 	bool hdcp_wait_sink_sync;
-	int aspect_ratio;
-	int vdisplay;
-	int hdisplay;
-	int vrefresh;
 	bool tpg_state;
 	u32 max_pclk_khz;
 	bool force_encryption;
 	bool skip_uevent;
 	char hdcp_status[SZ_128];
-	struct dp_mst_connector dp_mst_connector_list;
-	bool mst_hpd_sim;
 	bool mst_sim_add_con;
 	bool mst_sim_remove_con;
 	int mst_sim_remove_con_id;
-	u32 mst_port_cnt;
 	unsigned long connect_notification_delay_ms;
 	u32 disconnect_delay_ms;
 
-	struct dp_mst_connector mst_connector_cache;
-	u8 *(*get_edid)(struct dp_debug *dp_debug);
 	void (*abort)(struct dp_debug *dp_debug);
 	void (*set_mst_con)(struct dp_debug *dp_debug, int con_id);
 };

+ 62 - 194
msm/dp/dp_display.c

@@ -163,6 +163,7 @@ struct dp_display_private {
 
 	struct platform_device *pdev;
 	struct device_node *aux_switch_node;
+	struct dp_aux_bridge *aux_bridge;
 	struct dentry *root;
 	struct completion notification_comp;
 	struct completion attention_comp;
@@ -991,19 +992,11 @@ static void dp_display_mst_init(struct dp_display_private *dp)
 static void dp_display_set_mst_mgr_state(struct dp_display_private *dp,
 					bool state)
 {
-	struct dp_mst_hpd_info info = {0};
-
 	if (!dp->mst.mst_active)
 		return;
 
-	info.mst_protocol = dp->parser->has_mst_sideband;
-	if (state) {
-		info.mst_port_cnt = dp->debug->mst_port_cnt;
-		info.edid = dp->debug->get_edid(dp->debug);
-	}
-
 	if (dp->mst.cbs.set_mgr_state)
-		dp->mst.cbs.set_mgr_state(&dp->dp_display, state, &info);
+		dp->mst.cbs.set_mgr_state(&dp->dp_display, state);
 
 	DP_MST_DEBUG("mst_mgr_state: %d\n", state);
 }
@@ -1584,19 +1577,8 @@ static int dp_display_stream_enable(struct dp_display_private *dp,
 
 static void dp_display_mst_attention(struct dp_display_private *dp)
 {
-	struct dp_mst_hpd_info hpd_irq = {0};
-
-	if (dp->mst.mst_active && dp->mst.cbs.hpd_irq) {
-		hpd_irq.mst_hpd_sim = dp->debug->mst_hpd_sim;
-		hpd_irq.mst_sim_add_con = dp->debug->mst_sim_add_con;
-		hpd_irq.mst_sim_remove_con = dp->debug->mst_sim_remove_con;
-		hpd_irq.mst_sim_remove_con_id = dp->debug->mst_sim_remove_con_id;
-		hpd_irq.edid = dp->debug->get_edid(dp->debug);
-		dp->mst.cbs.hpd_irq(&dp->dp_display, &hpd_irq);
-		dp->debug->mst_hpd_sim = false;
-		dp->debug->mst_sim_add_con = false;
-		dp->debug->mst_sim_remove_con = false;
-	}
+	if (dp->mst.mst_active && dp->mst.cbs.hpd_irq)
+		dp->mst.cbs.hpd_irq(&dp->dp_display);
 
 	DP_MST_DEBUG("mst_attention_work. mst_active:%d\n", dp->mst.mst_active);
 }
@@ -1617,7 +1599,7 @@ static void dp_display_attention_work(struct work_struct *work)
 		goto exit;
 	}
 
-	if (dp->debug->mst_hpd_sim || !dp_display_state_is(DP_STATE_READY)) {
+	if (!dp_display_state_is(DP_STATE_READY)) {
 		mutex_unlock(&dp->session_lock);
 		goto mst_attention;
 	}
@@ -1758,8 +1740,7 @@ static int dp_display_usbpd_attention_cb(struct device *dev)
 		return 0;
 	}
 
-	if ((dp->hpd->hpd_irq && dp_display_state_is(DP_STATE_READY)) ||
-			dp->debug->mst_hpd_sim) {
+	if (dp->hpd->hpd_irq && dp_display_state_is(DP_STATE_READY)) {
 		queue_work(dp->wq, &dp->attention_work);
 		complete_all(&dp->attention_comp);
 	} else if (dp->process_hpd_connect ||
@@ -1922,7 +1903,7 @@ static int dp_init_sub_modules(struct dp_display_private *dp)
 	}
 
 	dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser,
-			dp->aux_switch_node);
+			dp->aux_switch_node, dp->aux_bridge);
 	if (IS_ERR(dp->aux)) {
 		rc = PTR_ERR(dp->aux);
 		DP_ERR("failed to initialize aux, rc = %d\n", rc);
@@ -2024,7 +2005,8 @@ static int dp_init_sub_modules(struct dp_display_private *dp)
 	cb->disconnect = dp_display_usbpd_disconnect_cb;
 	cb->attention  = dp_display_usbpd_attention_cb;
 
-	dp->hpd = dp_hpd_get(dev, dp->parser, &dp->catalog->hpd, cb);
+	dp->hpd = dp_hpd_get(dev, dp->parser, &dp->catalog->hpd,
+			dp->aux_bridge, cb);
 	if (IS_ERR(dp->hpd)) {
 		rc = PTR_ERR(dp->hpd);
 		DP_ERR("failed to initialize hpd, rc = %d\n", rc);
@@ -2813,72 +2795,6 @@ static int dp_display_validate_topology(struct dp_display_private *dp,
 	return 0;
 }
 
-static void dp_display_validate_mst_connectors(struct dp_debug *debug,
-		struct dp_panel *dp_panel, struct drm_display_mode *mode,
-		enum drm_mode_status *mode_status, bool *use_default)
-{
-	struct dp_mst_connector *mst_connector;
-	int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar;
-	bool in_list = false;
-
-	/*
-	 * If the connector exists in the mst connector list and if debug is
-	 * enabled for that connector, use the mst connector settings from the
-	 * list for validation. Otherwise, use non-mst default settings.
-	 */
-	mutex_lock(&debug->dp_mst_connector_list.lock);
-
-	if (list_empty(&debug->dp_mst_connector_list.list)) {
-		mutex_unlock(&debug->dp_mst_connector_list.lock);
-		*use_default = true;
-		return;
-	}
-
-	list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list,
-			list) {
-		if (mst_connector->con_id != dp_panel->connector->base.id)
-			continue;
-
-		in_list = true;
-
-		if (!mst_connector->debug_en) {
-			mutex_unlock(&debug->dp_mst_connector_list.lock);
-			*use_default = false;
-			*mode_status = MODE_OK;
-			return;
-		}
-
-		hdis = mst_connector->hdisplay;
-		vdis = mst_connector->vdisplay;
-		vref = mst_connector->vrefresh;
-		ar = mst_connector->aspect_ratio;
-
-		_hdis = mode->hdisplay;
-		_vdis = mode->vdisplay;
-		_vref = drm_mode_vrefresh(mode);
-		_ar = mode->picture_aspect_ratio;
-
-		if (hdis == _hdis && vdis == _vdis && vref == _vref &&
-				ar == _ar) {
-			mutex_unlock(&debug->dp_mst_connector_list.lock);
-			*use_default = false;
-			*mode_status = MODE_OK;
-			return;
-		}
-
-		break;
-	}
-
-	mutex_unlock(&debug->dp_mst_connector_list.lock);
-
-	if (in_list) {
-		*use_default = false;
-		return;
-	}
-
-	*use_default = true;
-}
-
 static enum drm_mode_status dp_display_validate_mode(
 		struct dp_display *dp_display,
 		void *panel, struct drm_display_mode *mode,
@@ -2890,7 +2806,6 @@ static enum drm_mode_status dp_display_validate_mode(
 	enum drm_mode_status mode_status = MODE_BAD;
 	struct dp_display_mode dp_mode;
 	int rc = 0;
-	bool use_default = true;
 
 	if (!dp_display || !mode || !panel ||
 			!avail_res || !avail_res->max_mixer_width) {
@@ -2936,17 +2851,6 @@ static enum drm_mode_status dp_display_validate_mode(
 	if (rc)
 		goto end;
 
-	dp_display_validate_mst_connectors(debug, dp_panel, mode, &mode_status,
-			&use_default);
-	if (!use_default)
-		goto end;
-
-	if (debug->debug_en && (mode->hdisplay != debug->hdisplay ||
-			mode->vdisplay != debug->vdisplay ||
-			drm_mode_vrefresh(mode) != debug->vrefresh ||
-			mode->picture_aspect_ratio != debug->aspect_ratio))
-		goto end;
-
 	mode_status = MODE_OK;
 end:
 	mutex_unlock(&dp->session_lock);
@@ -3180,6 +3084,55 @@ end:
 	return rc;
 }
 
+static int dp_display_bridge_internal_hpd(void *dev, bool hpd, bool hpd_irq)
+{
+	struct dp_display_private *dp = dev;
+	struct drm_device *drm_dev = dp->dp_display.drm_dev;
+
+	if (!drm_dev || !drm_dev->mode_config.poll_enabled)
+		return -EBUSY;
+
+	if (hpd_irq)
+		dp_display_mst_attention(dp);
+	else
+		dp->hpd->simulate_connect(dp->hpd, hpd);
+
+	return 0;
+}
+
+static int dp_display_init_aux_bridge(struct dp_display_private *dp)
+{
+	int rc = 0;
+	const char *phandle = "qcom,dp-aux-bridge";
+	struct device_node *bridge_node;
+
+	if (!dp->pdev->dev.of_node) {
+		pr_err("cannot find dev.of_node\n");
+		rc = -ENODEV;
+		goto end;
+	}
+
+	bridge_node = of_parse_phandle(dp->pdev->dev.of_node,
+			phandle, 0);
+	if (!bridge_node)
+		goto end;
+
+	dp->aux_bridge = of_dp_aux_find_bridge(bridge_node);
+	if (!dp->aux_bridge) {
+		pr_err("failed to find dp aux bridge\n");
+		rc = -EPROBE_DEFER;
+		goto end;
+	}
+
+	if (dp->aux_bridge->register_hpd &&
+			!(dp->aux_bridge->flag & DP_AUX_BRIDGE_HPD))
+		dp->aux_bridge->register_hpd(dp->aux_bridge,
+				dp_display_bridge_internal_hpd, dp);
+
+end:
+	return rc;
+}
+
 static int dp_display_mst_install(struct dp_display *dp_display,
 			struct dp_mst_drm_install_info *mst_install_info)
 {
@@ -3249,8 +3202,6 @@ static int dp_display_mst_connector_install(struct dp_display *dp_display,
 	struct dp_panel_in panel_in;
 	struct dp_panel *dp_panel;
 	struct dp_display_private *dp;
-	struct dp_mst_connector *mst_connector;
-	struct dp_mst_connector *cached_connector;
 
 	if (!dp_display || !connector) {
 		DP_ERR("invalid input\n");
@@ -3294,38 +3245,6 @@ static int dp_display_mst_connector_install(struct dp_display *dp_display,
 	DP_MST_DEBUG("dp mst connector installed. conn:%d\n",
 			connector->base.id);
 
-	mutex_lock(&dp->debug->dp_mst_connector_list.lock);
-
-	mst_connector = kmalloc(sizeof(struct dp_mst_connector),
-			GFP_KERNEL);
-	if (!mst_connector) {
-		mutex_unlock(&dp->debug->dp_mst_connector_list.lock);
-		rc = -ENOMEM;
-		goto end;
-	}
-
-	cached_connector = &dp->debug->mst_connector_cache;
-	if (cached_connector->debug_en) {
-		mst_connector->debug_en = true;
-		mst_connector->hdisplay = cached_connector->hdisplay;
-		mst_connector->vdisplay = cached_connector->vdisplay;
-		mst_connector->vrefresh = cached_connector->vrefresh;
-		mst_connector->aspect_ratio = cached_connector->aspect_ratio;
-		memset(cached_connector, 0, sizeof(*cached_connector));
-		dp->debug->set_mst_con(dp->debug, connector->base.id);
-	} else {
-		mst_connector->debug_en = false;
-	}
-
-	mst_connector->conn = connector;
-	mst_connector->con_id = connector->base.id;
-	mst_connector->state = connector_status_unknown;
-	INIT_LIST_HEAD(&mst_connector->list);
-
-	list_add(&mst_connector->list,
-			&dp->debug->dp_mst_connector_list.list);
-
-	mutex_unlock(&dp->debug->dp_mst_connector_list.lock);
 end:
 	mutex_unlock(&dp->session_lock);
 	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state, rc);
@@ -3340,7 +3259,6 @@ static int dp_display_mst_connector_uninstall(struct dp_display *dp_display,
 	struct sde_connector *sde_conn;
 	struct dp_panel *dp_panel;
 	struct dp_display_private *dp;
-	struct dp_mst_connector *con_to_remove, *temp_con;
 
 	if (!dp_display || !connector) {
 		DP_ERR("invalid input\n");
@@ -3372,64 +3290,12 @@ static int dp_display_mst_connector_uninstall(struct dp_display *dp_display,
 	DP_MST_DEBUG("dp mst connector uninstalled. conn:%d\n",
 			connector->base.id);
 
-	mutex_lock(&dp->debug->dp_mst_connector_list.lock);
-
-	list_for_each_entry_safe(con_to_remove, temp_con,
-			&dp->debug->dp_mst_connector_list.list, list) {
-		if (con_to_remove->conn == connector) {
-			/*
-			 * cache any debug info if enabled that can be applied
-			 * on new connectors.
-			 */
-			if (con_to_remove->debug_en)
-				memcpy(&dp->debug->mst_connector_cache,
-						con_to_remove,
-						sizeof(*con_to_remove));
-
-			list_del(&con_to_remove->list);
-			kfree(con_to_remove);
-		}
-	}
-
-	mutex_unlock(&dp->debug->dp_mst_connector_list.lock);
 	mutex_unlock(&dp->session_lock);
 	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, dp->state);
 
 	return rc;
 }
 
-static int dp_display_mst_get_connector_info(struct dp_display *dp_display,
-			struct drm_connector *connector,
-			struct dp_mst_connector *mst_conn)
-{
-	struct dp_display_private *dp;
-	struct dp_mst_connector *conn, *temp_conn;
-
-	if (!connector || !mst_conn) {
-		DP_ERR("invalid input\n");
-		return -EINVAL;
-	}
-
-	dp = container_of(dp_display, struct dp_display_private, dp_display);
-
-	mutex_lock(&dp->session_lock);
-	if (!dp->mst.drm_registered) {
-		DP_DEBUG("drm mst not registered\n");
-		mutex_unlock(&dp->session_lock);
-		return -EPERM;
-	}
-
-	mutex_lock(&dp->debug->dp_mst_connector_list.lock);
-	list_for_each_entry_safe(conn, temp_conn,
-			&dp->debug->dp_mst_connector_list.list, list) {
-		if (conn->con_id == connector->base.id)
-			memcpy(mst_conn, conn, sizeof(*mst_conn));
-	}
-	mutex_unlock(&dp->debug->dp_mst_connector_list.lock);
-	mutex_unlock(&dp->session_lock);
-	return 0;
-}
-
 static int dp_display_mst_connector_update_edid(struct dp_display *dp_display,
 			struct drm_connector *connector,
 			struct edid *edid)
@@ -3637,6 +3503,10 @@ static int dp_display_probe(struct platform_device *pdev)
 		goto error;
 	}
 
+	rc = dp_display_init_aux_bridge(dp);
+	if (rc)
+		goto error;
+
 	rc = dp_display_create_workqueue(dp);
 	if (rc) {
 		DP_ERR("Failed to create workqueue\n");
@@ -3674,8 +3544,6 @@ static int dp_display_probe(struct platform_device *pdev)
 	g_dp_display->set_stream_info = dp_display_set_stream_info;
 	g_dp_display->update_pps = dp_display_update_pps;
 	g_dp_display->convert_to_dp_mode = dp_display_convert_to_dp_mode;
-	g_dp_display->mst_get_connector_info =
-					dp_display_mst_get_connector_info;
 	g_dp_display->mst_get_fixed_topology_port =
 					dp_display_mst_get_fixed_topology_port;
 	g_dp_display->wakeup_phy_layer =

+ 3 - 31
msm/dp/dp_display.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _DP_DISPLAY_H_
@@ -11,30 +11,18 @@
 
 #include "dp_panel.h"
 
-#define DP_MST_SIM_MAX_PORTS	8
 
 enum dp_drv_state {
 	PM_DEFAULT,
 	PM_SUSPEND,
 };
 
-struct dp_mst_hpd_info {
-	bool mst_protocol;
-	bool mst_hpd_sim;
-	u32 mst_port_cnt;
-	u8 *edid;
-	bool mst_sim_add_con;
-	bool mst_sim_remove_con;
-	int mst_sim_remove_con_id;
-};
-
 struct dp_mst_drm_cbs {
 	void (*hpd)(void *display, bool hpd_status);
-	void (*hpd_irq)(void *display, struct dp_mst_hpd_info *info);
+	void (*hpd_irq)(void *display);
 	void (*set_drv_state)(void *dp_display,
 			enum dp_drv_state mst_state);
-	int (*set_mgr_state)(void *dp_display, bool state,
-			struct dp_mst_hpd_info *info);
+	int (*set_mgr_state)(void *dp_display, bool state);
 };
 
 struct dp_mst_drm_install_info {
@@ -49,19 +37,6 @@ struct dp_mst_caps {
 	struct drm_dp_aux *drm_aux;
 };
 
-struct dp_mst_connector {
-	bool debug_en;
-	int con_id;
-	int hdisplay;
-	int vdisplay;
-	int vrefresh;
-	int aspect_ratio;
-	struct drm_connector *conn;
-	struct mutex lock;
-	struct list_head list;
-	enum drm_connector_status state;
-};
-
 struct dp_display {
 	struct drm_device *drm_dev;
 	struct dp_bridge *bridge;
@@ -111,9 +86,6 @@ struct dp_display {
 			struct edid *edid);
 	int (*mst_connector_update_link_info)(struct dp_display *dp_display,
 			struct drm_connector *connector);
-	int (*mst_get_connector_info)(struct dp_display *dp_display,
-			struct drm_connector *connector,
-			struct dp_mst_connector *mst_conn);
 	int (*mst_get_fixed_topology_port)(struct dp_display *dp_display,
 			u32 strm_id, u32 *port_num);
 	int (*get_mst_caps)(struct dp_display *dp_display,

+ 11 - 1
msm/dp/dp_drm.c

@@ -654,10 +654,11 @@ enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector,
 		struct drm_display_mode *mode, void *display,
 		const struct msm_resource_caps_info *avail_res)
 {
-	int rc = 0;
+	int rc = 0, vrefresh;
 	struct dp_display *dp_disp;
 	struct sde_connector *sde_conn;
 	struct msm_resource_caps_info avail_dp_res;
+	struct dp_panel *dp_panel;
 
 	if (!mode || !display || !connector) {
 		DP_ERR("invalid params\n");
@@ -671,6 +672,9 @@ enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector,
 	}
 
 	dp_disp = display;
+	dp_panel = sde_conn->drv_panel;
+
+	vrefresh = drm_mode_vrefresh(mode);
 
 	rc = dp_disp->get_available_dp_resources(dp_disp, avail_res,
 			&avail_dp_res);
@@ -679,6 +683,12 @@ enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector,
 		return MODE_ERROR;
 	}
 
+	if (dp_panel->mode_override && (mode->hdisplay != dp_panel->hdisplay ||
+			mode->vdisplay != dp_panel->vdisplay ||
+			vrefresh != dp_panel->vrefresh ||
+			mode->picture_aspect_ratio != dp_panel->aspect_ratio))
+		return MODE_BAD;
+
 	return dp_disp->validate_mode(dp_disp, sde_conn->drv_panel,
 			mode, &avail_dp_res);
 }

+ 16 - 3
msm/dp/dp_hpd.c

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
 #include <linux/slab.h>
@@ -14,6 +14,7 @@
 #include "dp_gpio_hpd.h"
 #include "dp_lphw_hpd.h"
 #include "dp_debug.h"
+#include "dp_bridge_hpd.h"
 
 static void dp_hpd_host_init(struct dp_hpd *dp_hpd,
 		struct dp_catalog_hpd *catalog)
@@ -40,11 +41,20 @@ static void dp_hpd_isr(struct dp_hpd *dp_hpd)
 }
 
 struct dp_hpd *dp_hpd_get(struct device *dev, struct dp_parser *parser,
-		struct dp_catalog_hpd *catalog, struct dp_hpd_cb *cb)
+		struct dp_catalog_hpd *catalog,
+		struct dp_aux_bridge *aux_bridge,
+		struct dp_hpd_cb *cb)
 {
 	struct dp_hpd *dp_hpd;
 
-	if (parser->no_aux_switch && parser->lphw_hpd) {
+	if (aux_bridge && (aux_bridge->flag & DP_AUX_BRIDGE_HPD)) {
+		dp_hpd = dp_bridge_hpd_get(dev, cb, aux_bridge);
+		if (IS_ERR(dp_hpd)) {
+			pr_err("failed to get bridge hpd\n");
+			return dp_hpd;
+		}
+		dp_hpd->type = DP_HPD_BRIDGE;
+	} else if (parser->no_aux_switch && parser->lphw_hpd) {
 		dp_hpd = dp_lphw_hpd_get(dev, parser, catalog, cb);
 		if (IS_ERR_OR_NULL(dp_hpd)) {
 			DP_ERR("failed to get lphw hpd\n");
@@ -104,6 +114,9 @@ void dp_hpd_put(struct dp_hpd *dp_hpd)
 	case DP_HPD_LPHW:
 		dp_lphw_hpd_put(dp_hpd);
 		break;
+	case DP_HPD_BRIDGE:
+		dp_bridge_hpd_put(dp_hpd);
+		break;
 	default:
 		DP_ERR("unknown hpd type %d\n", dp_hpd->type);
 		break;

+ 11 - 5
msm/dp/dp_hpd.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _DP_HPD_H_
@@ -9,6 +9,7 @@
 #include <linux/types.h>
 #include "dp_parser.h"
 #include "dp_catalog.h"
+#include "dp_aux_bridge.h"
 
 struct device;
 
@@ -29,7 +30,8 @@ enum dp_hpd_plug_orientation {
  * @DP_HPD_ALTMODE: AltMode over G-Link based HPD
  * @DP_HPD_USBPD:   USB type-c based HPD
  * @DP_HPD_GPIO:    GPIO based HPD
- * @DP_HPD_BUILTIN: Controller built-in HPD
+ * @DP_HPD_LPHW:    LPHW based HPD
+ * @DP_HPD_BRIDGE:  External bridge HPD
  */
 
 enum dp_hpd_type {
@@ -37,7 +39,7 @@ enum dp_hpd_type {
 	DP_HPD_USBPD,
 	DP_HPD_GPIO,
 	DP_HPD_LPHW,
-	DP_HPD_BUILTIN,
+	DP_HPD_BRIDGE,
 };
 
 /**
@@ -95,14 +97,18 @@ struct dp_hpd {
  * dp_hpd_get() - configure and get the DisplayPlot HPD module data
  *
  * @dev: device instance of the caller
- * @parser: DP parser
+ * @parser: pointer to DP parser module
+ * @catalog: pointer to DP catalog module
+ * @aux_bridge: handle for aux_bridge driver data
  * @cb: callback function for HPD response
  * return: pointer to allocated hpd module data
  *
  * This function sets up the hpd module
  */
 struct dp_hpd *dp_hpd_get(struct device *dev, struct dp_parser *parser,
-		struct dp_catalog_hpd *catalog, struct dp_hpd_cb *cb);
+		struct dp_catalog_hpd *catalog,
+		struct dp_aux_bridge *aux_bridge,
+		struct dp_hpd_cb *cb);
 
 /**
  * dp_hpd_put()

+ 31 - 515
msm/dp/dp_mst_drm.c

@@ -86,34 +86,12 @@ struct dp_drm_mst_fw_helper_ops {
 			struct drm_dp_mst_port *port);
 };
 
-struct dp_mst_sim_port_data {
-	bool input_port;
-	u8 peer_device_type;
-	u8 port_number;
-	bool mcs;
-	bool ddps;
-	bool legacy_device_plug_status;
-	u8 dpcd_revision;
-	u8 peer_guid[16];
-	u8 num_sdp_streams;
-	u8 num_sdp_stream_sinks;
-};
-
 struct dp_mst_sim_port_edid {
 	u8 port_number;
 	u8 edid[SZ_256];
 	bool valid;
 };
 
-struct dp_mst_sim_mode {
-	bool mst_state;
-	struct edid *edid;
-	struct dp_mst_sim_port_edid port_edids[DP_MST_SIM_MAX_PORTS];
-	struct work_struct probe_work;
-	const struct drm_dp_mst_topology_cbs *cbs;
-	u32 port_cnt;
-};
-
 struct dp_mst_bridge {
 	struct drm_bridge base;
 	struct drm_private_obj obj;
@@ -153,7 +131,6 @@ struct dp_mst_private {
 	struct dp_mst_bridge mst_bridge[MAX_DP_MST_DRM_BRIDGES];
 	struct dp_display *dp_display;
 	const struct dp_drm_mst_fw_helper_ops *mst_fw_cbs;
-	struct dp_mst_sim_mode simulator;
 	struct mutex mst_lock;
 	struct mutex edid_lock;
 	enum dp_drv_state state;
@@ -176,22 +153,6 @@ struct dp_mst_encoder_info_cache {
 struct dp_mst_private dp_mst;
 struct dp_mst_encoder_info_cache dp_mst_enc_cache;
 
-static void dp_mst_hotplug(struct drm_dp_mst_topology_mgr *mgr)
-{
-	struct dp_mst_private *mst = container_of(mgr, struct dp_mst_private,
-							mst_mgr);
-	struct drm_device *dev = mst->dp_display->drm_dev;
-	char event_string[] = "HOTPLUG=1";
-	char *envp[2];
-
-	envp[0] = event_string;
-	envp[1] = NULL;
-
-	kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
-
-	DP_MST_INFO("mst hot plug event\n");
-}
-
 static struct drm_private_state *dp_mst_duplicate_bridge_state(
 		struct drm_private_obj *obj)
 {
@@ -231,408 +192,6 @@ static struct dp_mst_bridge_state *dp_mst_get_bridge_atomic_state(
 			drm_atomic_get_private_obj_state(state, &bridge->obj));
 }
 
-static void dp_mst_sim_destroy_port(struct kref *ref)
-{
-	struct drm_dp_mst_port *port = container_of(ref,
-			struct drm_dp_mst_port, topology_kref);
-	struct drm_dp_mst_topology_mgr *mgr = port->mgr;
-
-	if (port->cached_edid)
-		kfree(port->cached_edid);
-
-	if (port->connector) {
-		mutex_lock(&mgr->delayed_destroy_lock);
-		list_add(&port->next, &mgr->destroy_port_list);
-		mutex_unlock(&mgr->delayed_destroy_lock);
-		schedule_work(&mgr->delayed_destroy_work);
-		return;
-	}
-
-	drm_dp_mst_put_port_malloc(port);
-}
-
-static void dp_mst_sim_topology_get_port(struct drm_dp_mst_port *port)
-{
-	WARN_ON(kref_read(&port->topology_kref) == 0);
-	kref_get(&port->topology_kref);
-	DP_MST_DEBUG("get port %p topology(%d)\n", port,
-			kref_read(&port->topology_kref));
-}
-
-static void dp_mst_sim_topology_put_port(struct drm_dp_mst_port *port)
-{
-	DP_MST_DEBUG("put port %p topology(%d)\n", port,
-			kref_read(&port->topology_kref) - 1);
-	kref_put(&port->topology_kref, dp_mst_sim_destroy_port);
-}
-
-/*
- * DRM DP MST Framework simulator OPs
- *
- * If simulation mode is enabled, on HPD connect, mst mode is enabled on the
- * topology manager and 2 simulated ports are created using
- * dp_mst_sim_add_port. These 2 ports are added to the MST branch device.
- * Each node in the DP MST topology has 2 reference objects one for memory
- * allocation and the other for its use on the topology tree. When the last
- * topology reference is released on a port, the port gets destroyed and when
- * the last malloc reference is released, then the port memory is freed.
- * Each port also gets a reference on its parent branch object to make sure
- * that the branch is not destroyed prematurely.
- */
-
-static void dp_mst_sim_add_port(struct dp_mst_private *mst,
-			struct dp_mst_sim_port_data *port_msg)
-{
-	struct drm_dp_mst_branch *mstb;
-	struct drm_dp_mst_port *port;
-
-	mstb = mst->mst_mgr.mst_primary;
-	if (!mstb) {
-		DP_ERR("Unable to add port. mst branch device was destroyed\n");
-		return;
-	}
-
-	port = kzalloc(sizeof(*port), GFP_KERNEL);
-	if (!port)
-		return;
-	kref_init(&port->topology_kref);
-	kref_init(&port->malloc_kref);
-	port->parent = mstb;
-	port->port_num = port_msg->port_number;
-	port->mgr = mstb->mgr;
-	port->aux.name = dp_mst.caps.drm_aux->name;
-	port->aux.dev = mst->dp_display->drm_dev->dev;
-
-	/*
-	 * Make sure the memory allocation for our parent branch stays
-	 * around until our own memory allocation is released
-	 */
-	kref_get(&mstb->malloc_kref);
-
-	port->pdt = port_msg->peer_device_type;
-	port->input = port_msg->input_port;
-	port->mcs = port_msg->mcs;
-	port->ddps = port_msg->ddps;
-	port->ldps = port_msg->legacy_device_plug_status;
-	port->dpcd_rev = port_msg->dpcd_revision;
-	port->num_sdp_streams = port_msg->num_sdp_streams;
-	port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks;
-
-	mutex_lock(&mstb->mgr->lock);
-	dp_mst_sim_topology_get_port(port);
-	list_add(&port->next, &mstb->ports);
-	mutex_unlock(&mstb->mgr->lock);
-
-	/* use fixed pbn for simulator ports */
-	port->full_pbn = 2520;
-
-	if (!port->input) {
-		port->connector = (*mstb->mgr->cbs->add_connector)
-				(mstb->mgr, port, NULL);
-		if (!port->connector) {
-			/* remove it from the port list */
-			mutex_lock(&mstb->mgr->lock);
-			list_del(&port->next);
-			mutex_unlock(&mstb->mgr->lock);
-			dp_mst_sim_topology_put_port(port);
-			goto put_port;
-		}
-
-		drm_connector_register(port->connector);
-	}
-
-put_port:
-	dp_mst_sim_topology_put_port(port);
-}
-
-static void dp_mst_sim_remove_port(struct dp_mst_private *mst,
-			struct drm_dp_mst_port *port)
-{
-	struct drm_dp_mst_branch *mstb;
-	int i;
-
-	mstb = mst->mst_mgr.mst_primary;
-	if (!mstb) {
-		DP_ERR("Unable to remove port. mst branch device is null\n");
-		return;
-	}
-
-	mutex_lock(&mstb->mgr->lock);
-	list_del(&port->next);
-	mutex_unlock(&mstb->mgr->lock);
-	dp_mst_sim_topology_put_port(port);
-
-	for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) {
-		if (mst->simulator.port_edids[i].port_number ==
-				port->port_num) {
-			mst->simulator.port_edids[i].valid = false;
-		}
-	}
-}
-
-static void dp_mst_sim_link_probe_work(struct work_struct *work)
-{
-	struct dp_mst_sim_mode *sim;
-	struct dp_mst_private *mst;
-	struct dp_mst_sim_port_data port_data;
-	u8 cnt, i;
-
-	DP_MST_DEBUG("enter\n");
-	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY);
-
-	sim = container_of(work, struct dp_mst_sim_mode, probe_work);
-	mst = container_of(sim, struct dp_mst_private, simulator);
-
-	port_data.input_port = false;
-	port_data.peer_device_type = DP_PEER_DEVICE_SST_SINK;
-	port_data.mcs = false;
-	port_data.ddps = true;
-	port_data.legacy_device_plug_status = false;
-	port_data.dpcd_revision = 0;
-	port_data.num_sdp_streams = 0;
-	port_data.num_sdp_stream_sinks = 0;
-
-	for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++)
-		sim->port_edids[i].valid = false;
-
-	for (cnt = 0; cnt < sim->port_cnt; cnt++) {
-		port_data.port_number = cnt;
-
-		for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) {
-			if (sim->port_edids[i].valid) continue;
-
-			sim->port_edids[i].port_number = port_data.port_number;
-			memcpy(sim->port_edids[i].edid, sim->edid, SZ_256);
-			sim->port_edids[i].valid = true;
-			break;
-		}
-
-		dp_mst_sim_add_port(mst, &port_data);
-	}
-
-	dp_mst_hotplug(&mst->mst_mgr);
-	DP_MST_DEBUG("completed\n");
-}
-
-static int dp_mst_sim_no_action(struct drm_dp_mst_topology_mgr *mgr)
-{
-	return 0;
-}
-
-static int dp_mst_sim_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr)
-{
-	int i, j;
-	int cur_slots = 1;
-	struct drm_dp_payload req_payload;
-	struct drm_dp_mst_port *port;
-
-	mutex_lock(&mgr->payload_lock);
-	for (i = 0; i < mgr->max_payloads; i++) {
-		req_payload.start_slot = cur_slots;
-		if (mgr->proposed_vcpis[i]) {
-			port = container_of(mgr->proposed_vcpis[i],
-					struct drm_dp_mst_port, vcpi);
-			req_payload.num_slots =
-					mgr->proposed_vcpis[i]->num_slots;
-			req_payload.vcpi = mgr->proposed_vcpis[i]->vcpi;
-		} else {
-			port = NULL;
-			req_payload.num_slots = 0;
-		}
-
-		if (mgr->payloads[i].start_slot != req_payload.start_slot)
-			mgr->payloads[i].start_slot = req_payload.start_slot;
-
-		if (mgr->payloads[i].num_slots != req_payload.num_slots) {
-			if (req_payload.num_slots) {
-				req_payload.payload_state = DP_PAYLOAD_LOCAL;
-				mgr->payloads[i].num_slots =
-						req_payload.num_slots;
-				mgr->payloads[i].vcpi = req_payload.vcpi;
-			} else if (mgr->payloads[i].num_slots) {
-				mgr->payloads[i].num_slots = 0;
-				mgr->payloads[i].payload_state =
-						DP_PAYLOAD_DELETE_LOCAL;
-				req_payload.payload_state =
-						mgr->payloads[i].payload_state;
-				mgr->payloads[i].start_slot = 0;
-			} else
-				req_payload.payload_state =
-					mgr->payloads[i].payload_state;
-
-			mgr->payloads[i].payload_state =
-				req_payload.payload_state;
-		}
-		cur_slots += req_payload.num_slots;
-	}
-
-	for (i = 0; i < mgr->max_payloads; i++) {
-		if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) {
-			DP_DEBUG("removing payload %d\n", i);
-			for (j = i; j < mgr->max_payloads - 1; j++) {
-				memcpy(&mgr->payloads[j],
-					&mgr->payloads[j + 1],
-					sizeof(struct drm_dp_payload));
-				mgr->proposed_vcpis[j] =
-					mgr->proposed_vcpis[j + 1];
-				if (mgr->proposed_vcpis[j] &&
-					mgr->proposed_vcpis[j]->num_slots) {
-					set_bit(j + 1, &mgr->payload_mask);
-				} else {
-					clear_bit(j + 1, &mgr->payload_mask);
-				}
-			}
-			memset(&mgr->payloads[mgr->max_payloads - 1], 0,
-					sizeof(struct drm_dp_payload));
-			mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL;
-			clear_bit(mgr->max_payloads, &mgr->payload_mask);
-		}
-	}
-	mutex_unlock(&mgr->payload_lock);
-	return 0;
-}
-
-static int dp_mst_sim_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr)
-{
-	struct drm_dp_mst_port *port;
-	int i;
-
-	mutex_lock(&mgr->payload_lock);
-	for (i = 0; i < mgr->max_payloads; i++) {
-
-		if (!mgr->proposed_vcpis[i])
-			continue;
-
-		port = container_of(mgr->proposed_vcpis[i],
-				struct drm_dp_mst_port, vcpi);
-
-		DP_DEBUG("payload %d %d\n", i, mgr->payloads[i].payload_state);
-		if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL)
-			mgr->payloads[i].payload_state = DP_PAYLOAD_REMOTE;
-		else if (mgr->payloads[i].payload_state ==
-				DP_PAYLOAD_DELETE_LOCAL)
-			mgr->payloads[i].payload_state = 0;
-	}
-	mutex_unlock(&mgr->payload_lock);
-	return 0;
-}
-
-static struct edid *dp_mst_sim_get_edid(struct drm_connector *connector,
-		struct drm_dp_mst_topology_mgr *mgr,
-		struct drm_dp_mst_port *port)
-{
-	struct dp_mst_private *mst = container_of(mgr,
-			struct dp_mst_private, mst_mgr);
-	int i;
-
-	for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) {
-		if (mst->simulator.port_edids[i].valid &&
-				mst->simulator.port_edids[i].port_number ==
-				port->port_num) {
-			return drm_edid_duplicate((struct edid *)
-					(mst->simulator.port_edids[i].edid));
-		}
-	}
-
-	DRM_ERROR("edid not found for connector %d\n", connector->base.id);
-	return NULL;
-}
-
-static int dp_mst_sim_topology_mgr_set_mst(
-		struct drm_dp_mst_topology_mgr *mgr,
-		bool mst_state)
-{
-	int rc;
-	struct dp_mst_private *mst = container_of(mgr,
-			struct dp_mst_private, mst_mgr);
-
-	DP_MST_DEBUG("enter: mst_state %d\n", mst_state);
-	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, mst_state);
-
-	rc = drm_dp_mst_topology_mgr_set_mst(mgr, mst_state);
-	if (rc < 0) {
-		DRM_ERROR("unable to set mst topology mgr, rc: %d\n", rc);
-		return rc;
-	}
-
-	if (mst_state)
-		queue_work(system_long_wq, &mst->simulator.probe_work);
-
-	mst->simulator.mst_state = mst_state;
-	return 0;
-}
-
-static void dp_mst_sim_handle_hpd_irq(void *dp_display,
-		struct dp_mst_hpd_info *info)
-{
-	struct dp_display *dp;
-	struct dp_mst_private *mst;
-	struct drm_dp_mst_port *port;
-	struct dp_mst_sim_port_data port_data;
-	struct drm_dp_mst_branch *mstb;
-	int i;
-	bool in_list, port_available;
-
-	dp = dp_display;
-	mst = dp->dp_mst_prv_info;
-
-	if (info->mst_sim_add_con) {
-		port_available = false;
-		for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) {
-			if (mst->simulator.port_edids[i].valid) continue;
-
-			port_data.port_number = i;
-			mst->simulator.port_edids[i].port_number = i;
-			memcpy(mst->simulator.port_edids[i].edid, info->edid,
-					SZ_256);
-			mst->simulator.port_edids[i].valid = true;
-			port_available = true;
-			break;
-		}
-
-		if (!port_available) {
-			DRM_ERROR("add port failed, limit (%d) reached\n",
-					DP_MST_SIM_MAX_PORTS);
-			return;
-		}
-
-		port_data.input_port = false;
-		port_data.peer_device_type = DP_PEER_DEVICE_SST_SINK;
-		port_data.mcs = false;
-		port_data.ddps = true;
-		port_data.legacy_device_plug_status = false;
-		port_data.dpcd_revision = 0;
-		port_data.num_sdp_streams = 0;
-		port_data.num_sdp_stream_sinks = 0;
-
-		dp_mst_sim_add_port(mst, &port_data);
-	} else if (info->mst_sim_remove_con) {
-		mstb = mst->mst_mgr.mst_primary;
-		in_list = false;
-
-		mutex_lock(&mst->mst_mgr.lock);
-		list_for_each_entry(port,
-				&mstb->ports, next) {
-			if (port->connector && port->connector->base.id ==
-					info->mst_sim_remove_con_id) {
-				in_list = true;
-				break;
-			}
-		}
-		mutex_unlock(&mst->mst_mgr.lock);
-
-		if (!in_list) {
-			DRM_ERROR("invalid connector id %d\n",
-					info->mst_sim_remove_con_id);
-			return;
-		} else {
-			dp_mst_sim_remove_port(mst, port);
-		}
-
-		dp_mst_sim_topology_put_port(port);
-	}
-}
-
 static int dp_mst_detect_port(
 			struct drm_connector *connector,
 			struct drm_modeset_acquire_ctx *ctx,
@@ -719,23 +278,6 @@ static const struct dp_drm_mst_fw_helper_ops drm_dp_mst_fw_helper_ops = {
 	.deallocate_vcpi           = drm_dp_mst_deallocate_vcpi,
 };
 
-static const struct dp_drm_mst_fw_helper_ops drm_dp_sim_mst_fw_helper_ops = {
-	.calc_pbn_mode             = dp_mst_calc_pbn_mode,
-	.find_vcpi_slots           = drm_dp_find_vcpi_slots,
-	.atomic_find_vcpi_slots    = drm_dp_atomic_find_vcpi_slots,
-	.allocate_vcpi             = drm_dp_mst_allocate_vcpi,
-	.update_payload_part1      = dp_mst_sim_update_payload_part1,
-	.check_act_status          = dp_mst_sim_no_action,
-	.update_payload_part2      = dp_mst_sim_update_payload_part2,
-	.detect_port_ctx           = dp_mst_detect_port,
-	.get_edid                  = dp_mst_sim_get_edid,
-	.topology_mgr_set_mst      = dp_mst_sim_topology_mgr_set_mst,
-	.get_vcpi_info             = _dp_mst_get_vcpi_info,
-	.atomic_release_vcpi_slots = drm_dp_atomic_release_vcpi_slots,
-	.reset_vcpi_slots          = drm_dp_mst_reset_vcpi_slots,
-	.deallocate_vcpi           = drm_dp_mst_deallocate_vcpi,
-};
-
 /* DP MST Bridge OPs */
 
 static int dp_mst_bridge_attach(struct drm_bridge *dp_bridge,
@@ -1008,9 +550,6 @@ static void _dp_mst_bridge_pre_disable_part2(struct dp_mst_bridge *dp_bridge)
 	port->vcpi.vcpi = dp_bridge->vcpi;
 	mst->mst_fw_cbs->deallocate_vcpi(&mst->mst_mgr, port);
 
-	if (mst->simulator.mst_state)
-		dp_mst_sim_remove_port(mst, port);
-
 	dp_bridge->vcpi = 0;
 	dp_bridge->pbn = 0;
 
@@ -1357,25 +896,27 @@ dp_mst_connector_detect(struct drm_connector *connector, bool force,
 	struct sde_connector *c_conn = to_sde_connector(connector);
 	struct dp_display *dp_display = c_conn->display;
 	struct dp_mst_private *mst = dp_display->dp_mst_prv_info;
-	enum drm_connector_status status;
-	struct dp_mst_connector mst_conn;
+	struct dp_panel *dp_panel;
 	struct drm_modeset_acquire_ctx ctx;
+	enum drm_connector_status status;
 
 	DP_MST_DEBUG("enter:\n");
 	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY);
 
+	if (!c_conn->drv_panel || !c_conn->mst_port) {
+		DP_DEBUG("conn %d is invalid\n");
+		return connector_status_disconnected;
+	}
+
+	dp_panel = c_conn->drv_panel;
+
+	if (dp_panel->mst_hide)
+		return connector_status_disconnected;
+
 	drm_modeset_acquire_init(&ctx, 0);
 
 	status = mst->mst_fw_cbs->detect_port_ctx(connector,
-			&ctx, &mst->mst_mgr,
-			c_conn->mst_port);
-
-	memset(&mst_conn, 0, sizeof(mst_conn));
-	dp_display->mst_get_connector_info(dp_display, connector, &mst_conn);
-	if (mst_conn.conn == connector &&
-			mst_conn.state != connector_status_unknown) {
-		status = mst_conn.state;
-	}
+			&ctx, &mst->mst_mgr, c_conn->mst_port);
 
 	DP_MST_INFO("conn:%d status:%d\n", connector->base.id, status);
 	SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT, connector->base.id, status);
@@ -1450,10 +991,11 @@ enum drm_mode_status dp_mst_connector_mode_valid(
 	struct sde_connector *c_conn;
 	struct drm_dp_mst_port *mst_port;
 	struct dp_display_mode dp_mode;
+	struct dp_panel *dp_panel;
 	uint16_t full_pbn, required_pbn;
 	int available_slots, required_slots;
 	struct dp_mst_bridge_state *dp_bridge_state;
-	int i, slots_in_use = 0, active_enc_cnt = 0;
+	int i, vrefresh, slots_in_use = 0, active_enc_cnt = 0;
 	const u32 tot_slots = 63;
 
 	if (!connector || !mode || !display) {
@@ -1464,6 +1006,18 @@ enum drm_mode_status dp_mst_connector_mode_valid(
 	mst = dp_display->dp_mst_prv_info;
 	c_conn = to_sde_connector(connector);
 	mst_port = c_conn->mst_port;
+	dp_panel = c_conn->drv_panel;
+
+	if (!dp_panel || !mst_port)
+		return MODE_ERROR;
+
+	vrefresh = drm_mode_vrefresh(mode);
+
+	if (dp_panel->mode_override && (mode->hdisplay != dp_panel->hdisplay ||
+			mode->vdisplay != dp_panel->vdisplay ||
+			vrefresh != dp_panel->vrefresh ||
+			mode->picture_aspect_ratio != dp_panel->aspect_ratio))
+		return MODE_BAD;
 
 	/* dp bridge state is protected by drm_mode_config.connection_mutex */
 	for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) {
@@ -1497,7 +1051,7 @@ enum drm_mode_status dp_mst_connector_mode_valid(
 		return MODE_BAD;
 	}
 
-	return dp_connector_mode_valid(connector, mode, display, avail_res);
+	return dp_display->validate_mode(dp_display, dp_panel, mode, avail_res);
 }
 
 int dp_mst_connector_get_info(struct drm_connector *connector,
@@ -2159,8 +1713,7 @@ static void dp_mst_hpd_event_notify(struct dp_mst_private *mst, bool hpd_status)
 
 /* DP Driver Callback OPs */
 
-static int dp_mst_display_set_mgr_state(void *dp_display, bool state,
-		struct dp_mst_hpd_info *info)
+static int dp_mst_display_set_mgr_state(void *dp_display, bool state)
 {
 	int rc;
 	struct dp_display *dp = dp_display;
@@ -2176,15 +1729,7 @@ static int dp_mst_display_set_mgr_state(void *dp_display, bool state,
 	if (state)
 		mst->mst_session_state = state;
 
-	if (info && !info->mst_protocol) {
-		if (state) {
-			mst->simulator.edid = (struct edid *)info->edid;
-			mst->simulator.port_cnt = info->mst_port_cnt;
-		}
-		mst->mst_fw_cbs = &drm_dp_sim_mst_fw_helper_ops;
-	} else {
-		mst->mst_fw_cbs = &drm_dp_mst_fw_helper_ops;
-	}
+	mst->mst_fw_cbs = &drm_dp_mst_fw_helper_ops;
 
 	rc = mst->mst_fw_cbs->topology_mgr_set_mst(&mst->mst_mgr, state);
 	if (rc < 0) {
@@ -2215,8 +1760,7 @@ static void dp_mst_display_hpd(void *dp_display, bool hpd_status)
 	dp_mst_hpd_event_notify(mst, hpd_status);
 }
 
-static void dp_mst_display_hpd_irq(void *dp_display,
-			struct dp_mst_hpd_info *info)
+static void dp_mst_display_hpd_irq(void *dp_display)
 {
 	int rc;
 	struct dp_display *dp = dp_display;
@@ -2228,26 +1772,6 @@ static void dp_mst_display_hpd_irq(void *dp_display,
 	struct drm_connector *conn;
 	struct sde_connector *c_conn;
 
-	if (info->mst_hpd_sim) {
-		if (mst->simulator.mst_state && (info->mst_sim_add_con ||
-				info->mst_sim_remove_con)) {
-			dp_mst_sim_handle_hpd_irq(dp_display, info);
-
-			/*
-			 * When removing a connector, hpd_irq -> sim_destroy ->
-			 * destroy_connector_work will be executed in a thread.
-			 * This thread will perform the dp_mst_hotplug at the
-			 * appropriate time. Do not perform hotplug here
-			 * because it may be too early.
-			 */
-			if (info->mst_sim_remove_con)
-				return;
-		}
-
-		dp_mst_hotplug(&mst->mst_mgr);
-		return;
-	}
-
 	if (!mst->mst_session_state) {
 		DP_ERR("mst_hpd_irq received before mst session start\n");
 		return;
@@ -2324,12 +1848,6 @@ static const struct drm_dp_mst_topology_cbs dp_mst_fixed_drm_cbs = {
 	.add_connector = dp_mst_add_fixed_connector,
 };
 
-static void dp_mst_sim_init(struct dp_mst_private *mst)
-{
-	INIT_WORK(&mst->simulator.probe_work, dp_mst_sim_link_probe_work);
-	mst->simulator.cbs = &dp_mst_drm_cbs;
-}
-
 int dp_mst_init(struct dp_display *dp_display)
 {
 	struct drm_device *dev;
@@ -2378,8 +1896,6 @@ int dp_mst_init(struct dp_display *dp_display)
 		goto error;
 	}
 
-	dp_mst_sim_init(&dp_mst);
-
 	dp_mst.mst_initialized = true;
 
 	/* create drm_bridges for cached mst encoders and clear cache */

+ 1712 - 0
msm/dp/dp_mst_sim.c

@@ -0,0 +1,1712 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_dp_helper.h>
+#include "dp_debug.h"
+#include "dp_mst_sim.h"
+
+struct dp_sim_dpcd_reg {
+	struct list_head head;
+	u32 addr;
+	u8 val;
+};
+
+#define DP_SIM_BRIDGE_PRIV_FLAG (1 << 31)
+
+#define MAX_BUILTIN_DPCD_ADDR SZ_2K
+#define MAX_MST_PORT 8
+
+struct dp_sim_device {
+	struct device *dev;
+	struct dp_aux_bridge bridge;
+	void *host_dev;
+	int (*hpd_cb)(void *, bool, bool);
+
+	struct mutex lock;
+	const char *label;
+
+	struct dentry *debugfs_dir;
+	struct dentry *debugfs_edid_dir;
+
+	u8 dpcd_reg[MAX_BUILTIN_DPCD_ADDR];
+	struct list_head dpcd_reg_list;
+	u32 dpcd_write_addr;
+	u32 dpcd_write_size;
+
+	u32 link_training_cnt;
+	u32 link_training_remain;
+	u32 link_training_lane_cnt;
+	bool link_training_mismatch;
+
+	struct dp_mst_sim_port *ports;
+	u32 port_num;
+	u32 current_port_num;
+	u32 sim_mode;
+
+	u32 edid_seg;
+	u32 edid_addr;
+
+	bool skip_edid;
+	bool skip_dpcd;
+	bool skip_link_training;
+	bool skip_config;
+	bool skip_hpd;
+	bool skip_mst;
+};
+
+struct dp_sim_debug_edid_entry {
+	struct dp_sim_device *sim_dev;
+	u32 index;
+};
+
+#define to_dp_sim_dev(x) container_of((x), struct dp_sim_device, bridge)
+
+static const struct dp_mst_sim_port output_port = {
+	false, false, true, 3, false, 0x12,
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+	0, 0, 2520, 2520, NULL, 0
+};
+
+#ifdef CONFIG_DYNAMIC_DEBUG
+static void dp_sim_aux_hex_dump(struct drm_dp_aux_msg *msg)
+{
+	char prefix[64];
+	int i, linelen, remaining = msg->size;
+	const int rowsize = 16;
+	u8 linebuf[64];
+
+	snprintf(prefix, sizeof(prefix), "%s %s %4xh(%2zu): ",
+		(msg->request & DP_AUX_I2C_MOT) ? "I2C" : "NAT",
+		(msg->request & DP_AUX_I2C_READ) ? "RD" : "WR",
+		msg->address, msg->size);
+
+	for (i = 0; i < msg->size; i += rowsize) {
+		linelen = min(remaining, rowsize);
+		remaining -= rowsize;
+
+		hex_dump_to_buffer(msg->buffer + i, linelen, rowsize, 1,
+			linebuf, sizeof(linebuf), false);
+
+		DP_DEBUG("%s%s\n", prefix, linebuf);
+	}
+}
+#else
+static void dp_sim_aux_hex_dump(struct drm_dp_aux_msg *msg)
+{
+}
+#endif
+
+static int dp_sim_register_hpd(struct dp_aux_bridge *bridge,
+	int (*hpd_cb)(void *, bool, bool), void *dev)
+{
+	struct dp_sim_device *sim_dev = to_dp_sim_dev(bridge);
+
+	sim_dev->host_dev = dev;
+	sim_dev->hpd_cb = hpd_cb;
+
+	if (sim_dev->skip_hpd)
+		hpd_cb(dev, true, false);
+
+	return 0;
+}
+
+static u8 dp_sim_read_dpcd(struct dp_sim_device *sim_dev,
+		u32 addr)
+{
+	struct dp_sim_dpcd_reg *reg;
+
+	if (addr < MAX_BUILTIN_DPCD_ADDR) {
+		return sim_dev->dpcd_reg[addr];
+	} else {
+		list_for_each_entry(reg, &sim_dev->dpcd_reg_list, head) {
+			if (reg->addr == addr)
+				return reg->val;
+		}
+	}
+
+	return 0;
+}
+
+static void dp_sim_write_dpcd(struct dp_sim_device *sim_dev,
+		u32 addr, u8 val)
+{
+	struct dp_sim_dpcd_reg *dpcd_reg;
+
+	if (addr < MAX_BUILTIN_DPCD_ADDR) {
+		sim_dev->dpcd_reg[addr] = val;
+	} else {
+		list_for_each_entry(dpcd_reg, &sim_dev->dpcd_reg_list, head) {
+			if (dpcd_reg->addr == addr) {
+				dpcd_reg->val = val;
+				return;
+			}
+		}
+
+		dpcd_reg = devm_kzalloc(sim_dev->dev,
+				sizeof(*dpcd_reg), GFP_KERNEL);
+		if (!dpcd_reg)
+			return;
+
+		dpcd_reg->addr = addr;
+		dpcd_reg->val = val;
+		list_add_tail(&dpcd_reg->head, &sim_dev->dpcd_reg_list);
+	}
+}
+
+static int dp_sim_read_dpcd_regs(struct dp_sim_device *sim_dev,
+		u8 *buf, u32 size, u32 offset)
+{
+	u32 i;
+
+	if (offset + size <= MAX_BUILTIN_DPCD_ADDR) {
+		memcpy(buf, &sim_dev->dpcd_reg[offset], size);
+	} else {
+		for (i = 0; i < size; i++)
+			buf[i] = dp_sim_read_dpcd(sim_dev, offset + i);
+	}
+
+	return size;
+}
+
+static int dp_sim_read_edid(struct dp_sim_device *sim_dev,
+		struct drm_dp_aux_msg *msg)
+{
+	u8 *buf = (u8 *)msg->buffer;
+	u32 addr;
+
+	if (!sim_dev->port_num || !msg->size)
+		return 0;
+
+	if (msg->request & DP_AUX_I2C_READ) {
+		addr = (sim_dev->edid_seg << 8) + sim_dev->edid_addr;
+		if (addr + msg->size <= sim_dev->ports[0].edid_size) {
+			memcpy(msg->buffer, &sim_dev->ports[0].edid[addr],
+					msg->size);
+		} else if (addr < sim_dev->ports[0].edid_size) {
+			memcpy(msg->buffer, &sim_dev->ports[0].edid[addr],
+					sim_dev->ports[0].edid_size - addr);
+		}
+		sim_dev->edid_addr += msg->size;
+		sim_dev->edid_addr &= 0xFF;
+	} else {
+		if (msg->address == 0x30)
+			sim_dev->edid_seg = buf[0];
+		else if (msg->address == 0x50)
+			sim_dev->edid_addr = buf[0];
+	}
+
+	return msg->size;
+}
+
+static int dp_sim_link_training(struct dp_sim_device *sim_dev,
+		struct drm_dp_aux *drm_aux,
+		struct drm_dp_aux_msg *msg)
+{
+	u8 *link_status = msg->buffer;
+	int ret, i;
+
+	if (msg->request == DP_AUX_NATIVE_READ &&
+			msg->address == DP_LANE0_1_STATUS) {
+		/*
+		 * remain is an option to allow limited actual
+		 * link training. this is needed for some device
+		 * when actual read is needed.
+		 */
+		if (sim_dev->link_training_remain) {
+			sim_dev->link_training_remain--;
+			ret = drm_aux->transfer(drm_aux, msg);
+			if (ret >= 0)
+				link_status[2] &= ~DP_LINK_STATUS_UPDATED;
+			return ret;
+		}
+
+		memcpy(msg->buffer, &sim_dev->dpcd_reg[msg->address],
+				msg->size);
+
+		/*
+		 * when mismatch happens, clear status and fail the link
+		 * training.
+		 */
+		if (sim_dev->link_training_mismatch) {
+			link_status[0] = 0;
+			link_status[1] = 0;
+		}
+
+		return msg->size;
+	}
+
+	if (msg->request == DP_AUX_NATIVE_WRITE) {
+		if (msg->address == DP_TRAINING_LANE0_SET) {
+			const u8 mask = DP_TRAIN_VOLTAGE_SWING_MASK |
+					DP_TRAIN_PRE_EMPHASIS_MASK;
+			/*
+			 * when link training is set, only pre-set vx/px is
+			 * going through. here we will fail the initial
+			 * vx/px and correct them automatically.
+			 */
+			sim_dev->link_training_mismatch = false;
+			for (i = 0; i < sim_dev->link_training_lane_cnt; i++) {
+				if ((link_status[i] & mask) !=
+					(sim_dev->dpcd_reg[
+					DP_TRAINING_LANE0_SET + i] & mask)) {
+					sim_dev->link_training_mismatch = true;
+					break;
+				}
+			}
+		} else if (msg->address == DP_TRAINING_PATTERN_SET) {
+			sim_dev->link_training_remain =
+				sim_dev->link_training_cnt;
+		} else if (msg->address == DP_LINK_BW_SET) {
+			sim_dev->link_training_lane_cnt =
+				link_status[1] & 0x1F;
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t dp_sim_transfer(struct dp_aux_bridge *bridge,
+	struct drm_dp_aux *drm_aux,
+	struct drm_dp_aux_msg *msg)
+{
+	struct dp_sim_device *sim_dev = to_dp_sim_dev(bridge);
+	int ret;
+
+	mutex_lock(&sim_dev->lock);
+
+	if (sim_dev->skip_link_training &&
+			!(sim_dev->sim_mode & DP_SIM_MODE_LINK_TRAIN)) {
+		ret = dp_sim_link_training(sim_dev, drm_aux, msg);
+		if (ret)
+			goto end;
+	}
+
+	if ((sim_dev->sim_mode & DP_SIM_MODE_MST) || sim_dev->skip_mst) {
+		ret = dp_mst_sim_transfer(sim_dev->bridge.mst_ctx, msg);
+		if (ret >= 0) {
+			ret = msg->size;
+			goto end;
+		}
+	}
+
+	if (msg->request == DP_AUX_NATIVE_WRITE) {
+		sim_dev->dpcd_write_addr = msg->address;
+		sim_dev->dpcd_write_size = msg->size;
+	}
+
+	if (((sim_dev->sim_mode & DP_SIM_MODE_EDID) ||
+			sim_dev->skip_edid) &&
+			(msg->request & DP_AUX_I2C_MOT))
+		ret = dp_sim_read_edid(sim_dev, msg);
+	else if (((sim_dev->sim_mode & DP_SIM_MODE_DPCD_READ) ||
+			sim_dev->skip_dpcd) &&
+			msg->request == DP_AUX_NATIVE_READ)
+		ret = dp_sim_read_dpcd_regs(sim_dev, msg->buffer,
+				msg->size, msg->address);
+	else if (((sim_dev->sim_mode & DP_SIM_MODE_DPCD_WRITE) ||
+			sim_dev->skip_config) &&
+			msg->request == DP_AUX_NATIVE_WRITE)
+		ret = msg->size;
+	else
+		ret = drm_aux->transfer(drm_aux, msg);
+
+end:
+	dp_sim_aux_hex_dump(msg);
+
+	mutex_unlock(&sim_dev->lock);
+
+	return ret;
+}
+
+static void dp_sim_host_hpd_irq(void *host_dev)
+{
+	struct dp_sim_device *sim_dev = host_dev;
+
+	if (sim_dev->hpd_cb)
+		sim_dev->hpd_cb(sim_dev->host_dev, false, true);
+}
+
+int dp_sim_set_sim_mode(struct dp_aux_bridge *bridge, u32 sim_mode)
+{
+	struct dp_sim_device *sim_dev;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	sim_dev->sim_mode = sim_mode;
+
+	return 0;
+}
+
+int dp_sim_update_port_num(struct dp_aux_bridge *bridge, u32 port_num)
+{
+	struct dp_sim_device *sim_dev;
+	struct dp_mst_sim_port *ports;
+	u32 i, rc;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	if (port_num > sim_dev->port_num) {
+		ports = devm_kzalloc(sim_dev->dev,
+				port_num * sizeof(*ports), GFP_KERNEL);
+		if (!ports)
+			return -ENOMEM;
+
+		memcpy(ports, sim_dev->ports,
+				sim_dev->port_num * sizeof(*ports));
+
+		if (sim_dev->ports)
+			devm_kfree(sim_dev->dev, sim_dev->ports);
+
+		sim_dev->ports = ports;
+
+		for (i = sim_dev->port_num; i < port_num; i++) {
+			memcpy(&ports[i], &output_port, sizeof(*ports));
+			ports[i].peer_guid[0] = i;
+		}
+
+		sim_dev->port_num = port_num;
+	}
+
+	rc = dp_mst_sim_update(sim_dev->bridge.mst_ctx,
+			port_num, sim_dev->ports);
+	if (rc)
+		return rc;
+
+	sim_dev->current_port_num = port_num;
+
+	return rc;
+}
+
+int dp_sim_update_port_status(struct dp_aux_bridge *bridge,
+		int port, enum drm_connector_status status)
+{
+	struct dp_sim_device *sim_dev;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	if (port < 0 || port >= sim_dev->current_port_num)
+		return -EINVAL;
+
+	sim_dev->ports[port].pdt = (status == connector_status_connected) ?
+			DP_PEER_DEVICE_SST_SINK : DP_PEER_DEVICE_NONE;
+
+	return dp_mst_sim_update(sim_dev->bridge.mst_ctx,
+			sim_dev->current_port_num, sim_dev->ports);
+}
+
+int dp_sim_update_port_edid(struct dp_aux_bridge *bridge,
+		int port, const u8 *edid, u32 size)
+{
+	struct dp_sim_device *sim_dev;
+	struct dp_mst_sim_port *sim_port;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	if (port < 0 || port >= sim_dev->current_port_num)
+		return -EINVAL;
+
+	sim_port = &sim_dev->ports[port];
+
+	if (size != sim_port->edid_size) {
+		if (sim_port->edid)
+			devm_kfree(sim_dev->dev, (u8 *)sim_port->edid);
+
+		sim_port->edid = devm_kzalloc(sim_dev->dev,
+				size, GFP_KERNEL);
+		if (!sim_port->edid)
+			return -ENOMEM;
+
+		sim_port->edid_size = size;
+	}
+
+	memcpy((u8 *)sim_port->edid, edid, size);
+
+	return dp_mst_sim_update(sim_dev->bridge.mst_ctx,
+			sim_dev->current_port_num, sim_dev->ports);
+}
+
+int dp_sim_write_dpcd_reg(struct dp_aux_bridge *bridge,
+		const u8 *dpcd, u32 size, u32 offset)
+{
+	struct dp_sim_device *sim_dev;
+	int i;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	for (i = 0; i < size; i++)
+		dp_sim_write_dpcd(sim_dev, offset + i, dpcd[i]);
+
+	return 0;
+}
+
+int dp_sim_read_dpcd_reg(struct dp_aux_bridge *bridge,
+		u8 *dpcd, u32 size, u32 offset)
+{
+	struct dp_sim_device *sim_dev;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	sim_dev = to_dp_sim_dev(bridge);
+
+	return dp_sim_read_dpcd_regs(sim_dev, dpcd, size, offset);
+}
+
+static void dp_sim_update_dtd(struct edid *edid,
+		struct drm_display_mode *mode)
+{
+	struct detailed_timing *dtd = &edid->detailed_timings[0];
+	struct detailed_pixel_timing *pd = &dtd->data.pixel_data;
+	u32 h_blank = mode->htotal - mode->hdisplay;
+	u32 v_blank = mode->vtotal - mode->vdisplay;
+	u32 h_img = 0, v_img = 0;
+
+	dtd->pixel_clock = cpu_to_le16(mode->clock / 10);
+
+	pd->hactive_lo = mode->hdisplay & 0xFF;
+	pd->hblank_lo = h_blank & 0xFF;
+	pd->hactive_hblank_hi = ((h_blank >> 8) & 0xF) |
+			((mode->hdisplay >> 8) & 0xF) << 4;
+
+	pd->vactive_lo = mode->vdisplay & 0xFF;
+	pd->vblank_lo = v_blank & 0xFF;
+	pd->vactive_vblank_hi = ((v_blank >> 8) & 0xF) |
+			((mode->vdisplay >> 8) & 0xF) << 4;
+
+	pd->hsync_offset_lo =
+		(mode->hsync_start - mode->hdisplay) & 0xFF;
+	pd->hsync_pulse_width_lo =
+		(mode->hsync_end - mode->hsync_start) & 0xFF;
+	pd->vsync_offset_pulse_width_lo =
+		(((mode->vsync_start - mode->vdisplay) & 0xF) << 4) |
+		((mode->vsync_end - mode->vsync_start) & 0xF);
+
+	pd->hsync_vsync_offset_pulse_width_hi =
+		((((mode->hsync_start - mode->hdisplay) >> 8) & 0x3) << 6) |
+		((((mode->hsync_end - mode->hsync_start) >> 8) & 0x3) << 4) |
+		((((mode->vsync_start - mode->vdisplay) >> 4) & 0x3) << 2) |
+		((((mode->vsync_end - mode->vsync_start) >> 4) & 0x3) << 0);
+
+	pd->width_mm_lo = h_img & 0xFF;
+	pd->height_mm_lo = v_img & 0xFF;
+	pd->width_height_mm_hi = (((h_img >> 8) & 0xF) << 4) |
+		((v_img >> 8) & 0xF);
+
+	pd->hborder = 0;
+	pd->vborder = 0;
+	pd->misc = 0;
+}
+
+static void dp_sim_update_checksum(struct edid *edid)
+{
+	u8 *data = (u8 *)edid;
+	u32 i, sum = 0;
+
+	for (i = 0; i < EDID_LENGTH - 1; i++)
+		sum += data[i];
+
+	edid->checksum = 0x100 - (sum & 0xFF);
+}
+
+static int dp_sim_parse_edid_from_node(struct dp_sim_device *sim_dev,
+		int index, struct device_node *node)
+{
+	struct dp_mst_sim_port *port;
+	struct drm_display_mode mode_buf, *mode = &mode_buf;
+	u16 h_front_porch, h_pulse_width, h_back_porch;
+	u16 v_front_porch, v_pulse_width, v_back_porch;
+	bool h_active_high, v_active_high;
+	u32 flags = 0;
+	int rc;
+	struct edid *edid;
+
+	const u8 edid_buf[EDID_LENGTH] = {
+		0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x44, 0x6D,
+		0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x10, 0x01, 0x03,
+		0x80, 0x50, 0x2D, 0x78, 0x0A, 0x0D, 0xC9, 0xA0, 0x57, 0x47,
+		0x98, 0x27, 0x12, 0x48, 0x4C, 0x00, 0x00, 0x00, 0x01, 0x01,
+		0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+		0x01, 0x01, 0x01, 0x01,
+	};
+
+	rc = of_property_read_u16(node, "qcom,mode-h-active",
+					&mode->hdisplay);
+	if (rc) {
+		DP_ERR("failed to read h-active, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-h-front-porch",
+					&h_front_porch);
+	if (rc) {
+		DP_ERR("failed to read h-front-porch, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-h-pulse-width",
+					&h_pulse_width);
+	if (rc) {
+		DP_ERR("failed to read h-pulse-width, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-h-back-porch",
+					&h_back_porch);
+	if (rc) {
+		DP_ERR("failed to read h-back-porch, rc=%d\n", rc);
+		goto fail;
+	}
+
+	h_active_high = of_property_read_bool(node,
+					"qcom,mode-h-active-high");
+
+	rc = of_property_read_u16(node, "qcom,mode-v-active",
+					&mode->vdisplay);
+	if (rc) {
+		DP_ERR("failed to read v-active, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-v-front-porch",
+					&v_front_porch);
+	if (rc) {
+		DP_ERR("failed to read v-front-porch, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-v-pulse-width",
+					&v_pulse_width);
+	if (rc) {
+		DP_ERR("failed to read v-pulse-width, rc=%d\n", rc);
+		goto fail;
+	}
+
+	rc = of_property_read_u16(node, "qcom,mode-v-back-porch",
+					&v_back_porch);
+	if (rc) {
+		DP_ERR("failed to read v-back-porch, rc=%d\n", rc);
+		goto fail;
+	}
+
+	v_active_high = of_property_read_bool(node,
+					"qcom,mode-v-active-high");
+
+	rc = of_property_read_u32(node, "qcom,mode-clock-in-khz",
+					&mode->clock);
+	if (rc) {
+		DP_ERR("failed to read clock, rc=%d\n", rc);
+		goto fail;
+	}
+
+	mode->hsync_start = mode->hdisplay + h_front_porch;
+	mode->hsync_end = mode->hsync_start + h_pulse_width;
+	mode->htotal = mode->hsync_end + h_back_porch;
+	mode->vsync_start = mode->vdisplay + v_front_porch;
+	mode->vsync_end = mode->vsync_start + v_pulse_width;
+	mode->vtotal = mode->vsync_end + v_back_porch;
+	if (h_active_high)
+		flags |= DRM_MODE_FLAG_PHSYNC;
+	else
+		flags |= DRM_MODE_FLAG_NHSYNC;
+	if (v_active_high)
+		flags |= DRM_MODE_FLAG_PVSYNC;
+	else
+		flags |= DRM_MODE_FLAG_NVSYNC;
+	mode->flags = flags;
+
+	edid = devm_kzalloc(sim_dev->dev, sizeof(*edid), GFP_KERNEL);
+	if (!edid) {
+		rc = -ENOMEM;
+		goto fail;
+	}
+
+	memcpy(edid, edid_buf, sizeof(edid_buf));
+	dp_sim_update_dtd(edid, mode);
+	dp_sim_update_checksum(edid);
+
+	port = &sim_dev->ports[index];
+	memcpy(port, &output_port, sizeof(*port));
+	port->peer_guid[0] = index;
+
+	if (port->edid)
+		devm_kfree(sim_dev->dev, (u8 *)port->edid);
+
+	port->edid = (u8 *)edid;
+	port->edid_size = sizeof(*edid);
+
+fail:
+	return rc;
+}
+
+static int dp_sim_parse_edid_from_data(struct dp_sim_device *sim_dev,
+		int index, const char *data, int len)
+{
+	struct dp_mst_sim_port *port;
+	u8 *edid_data;
+
+	edid_data = devm_kzalloc(sim_dev->dev, len, GFP_KERNEL);
+	if (!edid_data)
+		return -ENOMEM;
+
+	memcpy(edid_data, data, len);
+
+	port = &sim_dev->ports[index];
+	memcpy(port, &output_port, sizeof(*port));
+	port->peer_guid[0] = index;
+
+	if (port->edid)
+		devm_kfree(sim_dev->dev, (u8 *)port->edid);
+
+	port->edid = edid_data;
+	port->edid_size = len;
+
+	return 0;
+}
+
+static int dp_sim_parse_edid(struct dp_sim_device *sim_dev)
+{
+	struct dp_mst_sim_port *ports;
+	struct device_node *of_node = sim_dev->bridge.of_node;
+	struct device_node *node;
+	const char *data;
+	int rc, port_num, i, len;
+
+	port_num = of_get_child_count(of_node);
+
+	if (!port_num)
+		port_num = 1;
+
+	if (port_num >= 15)
+		return -EINVAL;
+
+	ports = devm_kzalloc(sim_dev->dev,
+			port_num * sizeof(*ports), GFP_KERNEL);
+	if (!ports)
+		return -ENOMEM;
+
+	sim_dev->ports = ports;
+	sim_dev->port_num = port_num;
+	sim_dev->current_port_num = port_num;
+
+	i = 0;
+	for_each_child_of_node(of_node, node) {
+		data = of_get_property(node, "qcom,edid", &len);
+
+		if (data)
+			rc = dp_sim_parse_edid_from_data(sim_dev, i,
+					data, len);
+		else
+			rc = dp_sim_parse_edid_from_node(sim_dev, i,
+					node);
+
+		if (rc)
+			return rc;
+
+		i++;
+	}
+
+	if (i == 0)
+		memcpy(ports, &output_port, sizeof(*ports));
+
+	return 0;
+}
+
+static int dp_sim_parse_dpcd(struct dp_sim_device *sim_dev)
+{
+	struct device_node *node = sim_dev->bridge.of_node;
+	u32 val, i;
+	const __be32 *arr;
+	int rc;
+
+	rc = of_property_read_u32(node, "qcom,dpcd-max-rate", &val);
+	if (!rc)
+		sim_dev->dpcd_reg[DP_MAX_LINK_RATE] = val;
+
+	rc = of_property_read_u32(node, "qcom,dpcd-max-lane", &val);
+	if (!rc)
+		sim_dev->dpcd_reg[DP_MAX_LANE_COUNT] = val;
+
+	rc = of_property_read_u32(node, "qcom,dpcd-mst", &val);
+	if (!rc)
+		sim_dev->dpcd_reg[DP_MSTM_CAP] = val;
+
+	arr = of_get_property(node, "qcom,dpcd-regs", &val);
+	if (arr) {
+		val /= sizeof(u32);
+		val &= ~0x1;
+		for (i = 0; i < val; i += 2)
+			dp_sim_write_dpcd(sim_dev,
+					be32_to_cpu(arr[i]),
+					be32_to_cpu(arr[i+1]));
+	}
+
+	rc = of_property_read_u32(node, "qcom,voltage-swing", &val);
+	if (!rc)
+		for (i = 0; i < 4; i++) {
+			sim_dev->dpcd_reg[DP_TRAINING_LANE0_SET + i] |=
+					val;
+			sim_dev->dpcd_reg[DP_ADJUST_REQUEST_LANE0_1 + (i/2)] |=
+					(val & 0x3) << ((i & 0x1) << 2);
+		}
+
+	rc = of_property_read_u32(node, "qcom,pre-emphasis", &val);
+	if (!rc)
+		for (i = 0; i < 4; i++) {
+			sim_dev->dpcd_reg[DP_TRAINING_LANE0_SET + i] |=
+					val << 3;
+			sim_dev->dpcd_reg[DP_ADJUST_REQUEST_LANE0_1 + (i/2)] |=
+					(val & 0x3) << (((i & 0x1) << 2) + 2);
+		}
+
+	rc = of_property_read_u32(node, "qcom,link-training-cnt", &val);
+	if (!rc)
+		sim_dev->link_training_cnt = val;
+	else
+		sim_dev->link_training_cnt = 0;
+
+	return 0;
+}
+
+static int dp_sim_parse_misc(struct dp_sim_device *sim_dev)
+{
+	struct device_node *node = sim_dev->bridge.of_node;
+
+	sim_dev->skip_edid = of_property_read_bool(node,
+			"qcom,skip-edid");
+
+	sim_dev->skip_dpcd = of_property_read_bool(node,
+			"qcom,skip-dpcd-read");
+
+	sim_dev->skip_link_training = of_property_read_bool(node,
+			"qcom,skip-link-training");
+
+	sim_dev->skip_config = of_property_read_bool(node,
+			"qcom,skip-dpcd-write");
+
+	sim_dev->skip_hpd = of_property_read_bool(node,
+			"qcom,skip-hpd");
+
+	sim_dev->skip_mst = of_property_read_bool(node,
+			"qcom,skip-mst");
+
+	DP_DEBUG("skip: edid=%d dpcd=%d LT=%d config=%d hpd=%d mst=%d\n",
+			sim_dev->skip_edid,
+			sim_dev->skip_dpcd,
+			sim_dev->skip_link_training,
+			sim_dev->skip_config,
+			sim_dev->skip_hpd,
+			sim_dev->skip_mst);
+
+	return 0;
+}
+
+static ssize_t dp_sim_debug_write_edid(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_debug_edid_entry *entry = file->private_data;
+	struct dp_sim_device *debug;
+	struct dp_mst_sim_port *port;
+	u8 *buf = NULL, *buf_t = NULL;
+	const int char_to_nib = 2;
+	size_t edid_size = 0;
+	size_t size = 0, edid_buf_index = 0;
+	ssize_t rc = count;
+
+	if (!entry)
+		return -ENODEV;
+
+	debug = entry->sim_dev;
+	if (!debug || entry->index >= debug->port_num)
+		return -EINVAL;
+
+	port = &debug->ports[entry->index];
+
+	mutex_lock(&debug->lock);
+
+	if (*ppos)
+		goto bail;
+
+	size = min_t(size_t, count, SZ_1K);
+
+	buf = kzalloc(size, GFP_KERNEL);
+	if (ZERO_OR_NULL_PTR(buf)) {
+		rc = -ENOMEM;
+		goto bail;
+	}
+
+	if (copy_from_user(buf, user_buff, size))
+		goto bail;
+
+	edid_size = size / char_to_nib;
+	buf_t = buf;
+
+	if (edid_size != port->edid_size) {
+		if (port->edid)
+			devm_kfree(debug->dev, (u8 *)port->edid);
+
+		port->edid = devm_kzalloc(debug->dev,
+					edid_size, GFP_KERNEL);
+		if (!port->edid) {
+			rc = -ENOMEM;
+			goto bail;
+		}
+		port->edid_size = edid_size;
+	}
+
+	while (edid_size--) {
+		char t[3];
+		int d;
+
+		memcpy(t, buf_t, sizeof(char) * char_to_nib);
+		t[char_to_nib] = '\0';
+
+		if (kstrtoint(t, 16, &d)) {
+			DP_ERR("kstrtoint error\n");
+			goto bail;
+		}
+
+		if (port->edid && (edid_buf_index < port->edid_size))
+			((u8 *)port->edid)[edid_buf_index++] = d;
+
+		buf_t += char_to_nib;
+	}
+
+	if (debug->skip_mst)
+		dp_mst_sim_update(debug->bridge.mst_ctx,
+				debug->port_num, debug->ports);
+
+	debug->skip_edid = true;
+
+bail:
+	kfree(buf);
+
+	mutex_unlock(&debug->lock);
+	return rc;
+}
+
+static ssize_t dp_sim_debug_write_dpcd(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	u8 *buf = NULL, *buf_t = NULL;
+	const int char_to_nib = 2;
+	size_t dpcd_size = 0;
+	size_t size = 0, dpcd_buf_index = 0;
+	ssize_t rc = count;
+	char offset_ch[5];
+	u32 offset, data_len;
+
+	if (!debug)
+		return -ENODEV;
+
+	mutex_lock(&debug->lock);
+
+	if (*ppos)
+		goto bail;
+
+	size = min_t(size_t, count, SZ_2K);
+	if (size < 4)
+		goto bail;
+
+	buf = kzalloc(size, GFP_KERNEL);
+	if (ZERO_OR_NULL_PTR(buf)) {
+		rc = -ENOMEM;
+		goto bail;
+	}
+
+	if (copy_from_user(buf, user_buff, size))
+		goto bail;
+
+	memcpy(offset_ch, buf, 4);
+	offset_ch[4] = '\0';
+
+	if (kstrtoint(offset_ch, 16, &offset)) {
+		DP_ERR("offset kstrtoint error\n");
+		goto bail;
+	}
+
+	if (offset == 0xFFFF) {
+		DP_ERR("clearing dpcd\n");
+		memset(debug->dpcd_reg, 0, sizeof(debug->dpcd_reg));
+		goto bail;
+	}
+
+	size -= 4;
+	if (size == 0)
+		goto bail;
+
+	dpcd_size = size / char_to_nib;
+	data_len = dpcd_size;
+	buf_t = buf + 4;
+
+	dpcd_buf_index = offset;
+
+	while (dpcd_size--) {
+		char t[3];
+		int d;
+
+		memcpy(t, buf_t, sizeof(char) * char_to_nib);
+		t[char_to_nib] = '\0';
+
+		if (kstrtoint(t, 16, &d)) {
+			DP_ERR("kstrtoint error\n");
+			goto bail;
+		}
+
+		dp_sim_write_dpcd(debug, dpcd_buf_index, d);
+		dpcd_buf_index++;
+
+		buf_t += char_to_nib;
+	}
+
+	debug->skip_dpcd = true;
+	debug->skip_config = true;
+
+bail:
+	kfree(buf);
+
+	mutex_unlock(&debug->lock);
+	return rc;
+}
+
+static ssize_t dp_sim_debug_read_dpcd(struct file *file,
+		char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char *buf;
+	int const buf_size = SZ_4K;
+	u32 offset = 0;
+	u32 len = 0;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	buf = kzalloc(buf_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	len += snprintf(buf, buf_size, "0x%x", debug->dpcd_write_addr);
+
+	while (1) {
+		if (debug->dpcd_write_addr + offset >= buf_size ||
+		    offset >= debug->dpcd_write_size)
+			break;
+
+		len += snprintf(buf + len, buf_size - len, "0x%x",
+			debug->dpcd_reg[debug->dpcd_write_addr + offset++]);
+	}
+
+	len = min_t(size_t, count, len);
+	if (!copy_to_user(user_buff, buf, len))
+		*ppos += len;
+
+	kfree(buf);
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_hpd(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_8];
+	size_t len = 0;
+	int hpd = 0;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &hpd) != 0)
+		goto end;
+
+	if (debug->hpd_cb)
+		debug->hpd_cb(debug->host_dev, !!hpd, false);
+
+end:
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_skip_link_training(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_8];
+	size_t len = 0;
+	int skip_lk, lk_cnt;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (sscanf(buf, "%d %u", &skip_lk, &lk_cnt) != 2) {
+		DP_ERR("invalid input\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&debug->lock);
+	debug->skip_link_training = !!skip_lk;
+	debug->link_training_cnt = lk_cnt;
+	mutex_unlock(&debug->lock);
+end:
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_skip_edid(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_8];
+	size_t len = 0;
+	int val = 0;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) != 0)
+		goto end;
+
+	mutex_lock(&debug->lock);
+	debug->skip_edid = !!val;
+	mutex_unlock(&debug->lock);
+end:
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_skip_dpcd(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_8];
+	size_t len = 0;
+	int val = 0;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) != 0)
+		goto end;
+
+	mutex_lock(&debug->lock);
+	debug->skip_dpcd = !!val;
+	mutex_unlock(&debug->lock);
+end:
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_skip_config(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_8];
+	size_t len = 0;
+	int val = 0;
+
+	if (!debug)
+		return -ENODEV;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &val) != 0)
+		goto end;
+
+	mutex_lock(&debug->lock);
+	debug->skip_config = !!val;
+	mutex_unlock(&debug->lock);
+end:
+	return len;
+}
+
+static ssize_t dp_sim_debug_write_mst_hpd(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_debug_edid_entry *entry = file->private_data;
+	struct dp_sim_device *debug;
+	char buf[SZ_8];
+	size_t len = 0;
+	int hpd = 0;
+
+	if (!entry)
+		return -ENODEV;
+
+	debug = entry->sim_dev;
+	if (!debug || entry->index >= debug->port_num)
+		return -EINVAL;
+
+	if (*ppos)
+		return 0;
+
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		goto end;
+
+	buf[len] = '\0';
+
+	if (kstrtoint(buf, 10, &hpd) != 0)
+		goto end;
+
+	dp_sim_update_port_status(&debug->bridge,
+				entry->index, hpd ?
+				connector_status_connected :
+				connector_status_disconnected);
+
+end:
+	return len;
+}
+
+static const struct file_operations sim_edid_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_edid,
+};
+
+static const struct file_operations sim_mst_hpd_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_mst_hpd,
+};
+
+static ssize_t dp_sim_debug_write_mst_mode(struct file *file,
+		const char __user *user_buff, size_t count, loff_t *ppos)
+{
+	struct dp_sim_device *debug = file->private_data;
+	char buf[SZ_16];
+	size_t len = 0;
+	int mst_sideband_mode = 0;
+	u32 mst_port_cnt = 0;
+	u32 mst_old_port_cnt;
+	struct dp_sim_debug_edid_entry *edid_entry;
+	u8 *edid;
+	u32 i, rc;
+
+	if (!debug)
+		return -ENODEV;
+
+	/* Leave room for termination char */
+	len = min_t(size_t, count, SZ_8 - 1);
+	if (copy_from_user(buf, user_buff, len))
+		return -EFAULT;
+
+	buf[len] = '\0';
+
+	if (sscanf(buf, "%d %u", &mst_sideband_mode, &mst_port_cnt) != 2) {
+		DP_ERR("invalid input\n");
+		return -EINVAL;
+	}
+
+	if (mst_port_cnt >= MAX_MST_PORT) {
+		DP_ERR("port cnt:%d exceeding max:%d\n", mst_port_cnt,
+				MAX_MST_PORT);
+		return -EINVAL;
+	}
+
+	if (!mst_port_cnt)
+		mst_port_cnt = 1;
+
+	debug->skip_mst = !mst_sideband_mode;
+	DP_DEBUG("mst_sideband_mode: %d port_cnt:%d\n",
+			mst_sideband_mode, mst_port_cnt);
+
+	mst_old_port_cnt = debug->port_num;
+	rc = dp_sim_update_port_num(&debug->bridge, mst_port_cnt);
+	if (rc)
+		return rc;
+
+	/* write mst */
+	dp_sim_write_dpcd(debug, DP_MSTM_CAP, debug->skip_mst);
+
+	/* create default edid nodes */
+	for (i = mst_old_port_cnt; i < mst_port_cnt; i++) {
+		edid_entry = devm_kzalloc(debug->dev,
+				sizeof(*edid_entry), GFP_KERNEL);
+		if (!edid_entry)
+			continue;
+
+		edid_entry->index = i;
+		edid_entry->sim_dev = debug;
+		scnprintf(buf, sizeof(buf), "edid-%d", i);
+		debugfs_create_file(buf,
+				0444,
+				debug->debugfs_edid_dir,
+				edid_entry,
+				&sim_edid_fops);
+		scnprintf(buf, sizeof(buf), "hpd-%d", i);
+		debugfs_create_file(buf,
+				0444,
+				debug->debugfs_edid_dir,
+				edid_entry,
+				&sim_mst_hpd_fops);
+
+		if (!debug->ports[0].edid_size)
+			continue;
+
+		edid = devm_kzalloc(debug->dev,
+				debug->ports[0].edid_size, GFP_KERNEL);
+		if (!edid)
+			return -ENOMEM;
+
+		memcpy(edid, debug->ports[0].edid, debug->ports[0].edid_size);
+		debug->ports[i].edid = edid;
+		debug->ports[i].edid_size = debug->ports[0].edid_size;
+	}
+
+	return count;
+}
+
+static const struct file_operations sim_dpcd_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_dpcd,
+	.read = dp_sim_debug_read_dpcd,
+};
+
+static const struct file_operations sim_hpd_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_hpd,
+};
+
+static const struct file_operations sim_skip_link_training_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_skip_link_training,
+};
+
+static const struct file_operations sim_skip_edid_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_skip_edid,
+};
+
+static const struct file_operations sim_skip_dpcd_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_skip_dpcd,
+};
+
+static const struct file_operations sim_skip_config_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_skip_config,
+};
+
+static const struct file_operations sim_mst_mode_fops = {
+	.open = simple_open,
+	.write = dp_sim_debug_write_mst_mode,
+};
+
+static int dp_sim_debug_init(struct dp_sim_device *sim_dev)
+{
+	struct dp_sim_debug_edid_entry *edid_entry;
+	struct dentry *dir, *file, *edid_dir;
+	char name[SZ_16];
+	int rc = 0, i;
+
+	if (!sim_dev->label)
+		return 0;
+
+	dir = debugfs_create_dir(sim_dev->label, NULL);
+	if (IS_ERR_OR_NULL(dir)) {
+		rc = PTR_ERR(dir);
+		DP_ERR("[%s] debugfs create dir failed, rc = %d\n",
+				sim_dev->label, rc);
+		goto error;
+	}
+
+	edid_dir = debugfs_create_dir("mst_edid", dir);
+	if (IS_ERR_OR_NULL(edid_dir)) {
+		rc = PTR_ERR(edid_dir);
+		DP_ERR("[%s] debugfs create dir failed, rc = %d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	for (i = 0; i < sim_dev->port_num; i++) {
+		edid_entry = devm_kzalloc(sim_dev->dev,
+				sizeof(*edid_entry), GFP_KERNEL);
+		edid_entry->index = i;
+		edid_entry->sim_dev = sim_dev;
+		scnprintf(name, sizeof(name), "edid-%d", i);
+		file = debugfs_create_file(name,
+				0444,
+				edid_dir,
+				edid_entry,
+				&sim_edid_fops);
+		if (IS_ERR_OR_NULL(file)) {
+			rc = PTR_ERR(file);
+			DP_ERR("[%s] debugfs create edid failed, rc=%d\n",
+					sim_dev->label, rc);
+			goto error_remove_dir;
+		}
+		scnprintf(name, sizeof(name), "hpd-%d", i);
+		file = debugfs_create_file(name,
+				0444,
+				edid_dir,
+				edid_entry,
+				&sim_mst_hpd_fops);
+		if (IS_ERR_OR_NULL(file)) {
+			rc = PTR_ERR(file);
+			DP_ERR("[%s] debugfs create hpd failed, rc=%d\n",
+					sim_dev->label, rc);
+			goto error_remove_dir;
+		}
+	}
+
+	file = debugfs_create_symlink("edid", dir, "./mst_edid/edid-0");
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create edid link failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("dpcd",
+			0444,
+			dir,
+			sim_dev,
+			&sim_dpcd_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("hpd",
+			0444,
+			dir,
+			sim_dev,
+			&sim_hpd_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("skip_link_training",
+			0444,
+			dir,
+			sim_dev,
+			&sim_skip_link_training_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("skip_edid",
+			0444,
+			dir,
+			sim_dev,
+			&sim_skip_edid_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("skip_dpcd_read",
+			0444,
+			dir,
+			sim_dev,
+			&sim_skip_dpcd_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("skip_dpcd_write",
+			0444,
+			dir,
+			sim_dev,
+			&sim_skip_config_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	file = debugfs_create_file("mst_sideband_mode",
+			0444,
+			dir,
+			sim_dev,
+			&sim_mst_mode_fops);
+	if (IS_ERR_OR_NULL(file)) {
+		rc = PTR_ERR(file);
+		DP_ERR("[%s] debugfs create failed, rc=%d\n",
+				sim_dev->label, rc);
+		goto error_remove_dir;
+	}
+
+	sim_dev->debugfs_dir = dir;
+	sim_dev->debugfs_edid_dir = edid_dir;
+
+	return 0;
+
+error_remove_dir:
+	debugfs_remove_recursive(dir);
+error:
+	return rc;
+}
+
+static int dp_sim_parse(struct dp_sim_device *sim_dev)
+{
+	int rc;
+
+	sim_dev->label = of_get_property(sim_dev->bridge.of_node,
+			"label", NULL);
+
+	rc = dp_sim_parse_dpcd(sim_dev);
+	if (rc) {
+		DP_ERR("failed to parse DPCD nodes\n");
+		return rc;
+	}
+
+	rc = dp_sim_parse_edid(sim_dev);
+	if (rc) {
+		DP_ERR("failed to parse EDID nodes\n");
+		return rc;
+	}
+
+	rc = dp_sim_parse_misc(sim_dev);
+	if (rc) {
+		DP_ERR("failed to parse misc nodes\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+int dp_sim_create_bridge(struct device *dev, struct dp_aux_bridge **bridge)
+{
+	struct dp_sim_device *dp_sim_dev;
+	struct dp_mst_sim_cfg cfg;
+	int ret;
+
+	dp_sim_dev = devm_kzalloc(dev, sizeof(*dp_sim_dev), GFP_KERNEL);
+	if (!dp_sim_dev)
+		return -ENOMEM;
+
+	dp_sim_dev->dev = dev;
+	dp_sim_dev->bridge.of_node = dev->of_node;
+	dp_sim_dev->bridge.register_hpd = dp_sim_register_hpd;
+	dp_sim_dev->bridge.transfer = dp_sim_transfer;
+	dp_sim_dev->bridge.dev_priv = dp_sim_dev;
+	dp_sim_dev->bridge.flag = DP_AUX_BRIDGE_MST | DP_SIM_BRIDGE_PRIV_FLAG;
+	INIT_LIST_HEAD(&dp_sim_dev->dpcd_reg_list);
+	mutex_init(&dp_sim_dev->lock);
+
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.host_dev = dp_sim_dev;
+	cfg.host_hpd_irq = dp_sim_host_hpd_irq;
+
+	ret = dp_mst_sim_create(&cfg, &dp_sim_dev->bridge.mst_ctx);
+	if (ret) {
+		devm_kfree(dev, dp_sim_dev);
+		return ret;
+	}
+
+	/* default dpcd reg value */
+	dp_sim_dev->dpcd_reg[DP_DPCD_REV] = 0x12;
+	dp_sim_dev->dpcd_reg[DP_MAX_LINK_RATE] = 0x14;
+	dp_sim_dev->dpcd_reg[DP_MAX_LANE_COUNT] = 0xc4;
+	dp_sim_dev->dpcd_reg[DP_SINK_COUNT] = 0x1;
+	dp_sim_dev->dpcd_reg[DP_LANE0_1_STATUS] = 0x77;
+	dp_sim_dev->dpcd_reg[DP_LANE2_3_STATUS] = 0x77;
+	dp_sim_dev->dpcd_reg[DP_LANE_ALIGN_STATUS_UPDATED] = 0x1;
+	dp_sim_dev->dpcd_reg[DP_SINK_STATUS] = 0x3;
+	dp_sim_dev->dpcd_reg[DP_PAYLOAD_TABLE_UPDATE_STATUS] = 0x3;
+
+	/* enable link training by default */
+	dp_sim_dev->skip_link_training = true;
+	dp_sim_dev->link_training_cnt = (u32)-1;
+
+	*bridge = &dp_sim_dev->bridge;
+	return 0;
+}
+
+int dp_sim_destroy_bridge(struct dp_aux_bridge *bridge)
+{
+	struct dp_sim_device *dp_sim_dev;
+	struct dp_sim_dpcd_reg *reg, *p;
+
+	if (!bridge || !(bridge->flag & DP_SIM_BRIDGE_PRIV_FLAG))
+		return -EINVAL;
+
+	dp_sim_dev = to_dp_sim_dev(bridge);
+
+	dp_mst_sim_destroy(dp_sim_dev->bridge.mst_ctx);
+
+	list_for_each_entry_safe(reg, p, &dp_sim_dev->dpcd_reg_list, head) {
+		list_del(&reg->head);
+		devm_kfree(dp_sim_dev->dev, reg);
+	}
+
+	if (dp_sim_dev->ports)
+		devm_kfree(dp_sim_dev->dev, dp_sim_dev->ports);
+
+	devm_kfree(dp_sim_dev->dev, dp_sim_dev);
+
+	return 0;
+}
+
+int dp_sim_probe(struct platform_device *pdev)
+{
+	struct dp_sim_device *dp_sim_dev;
+	struct dp_aux_bridge *bridge;
+	int ret;
+
+	ret = dp_sim_create_bridge(&pdev->dev, &bridge);
+	if (ret)
+		return ret;
+
+	dp_sim_dev = to_dp_sim_dev(bridge);
+
+	ret = dp_sim_parse(dp_sim_dev);
+	if (ret)
+		goto fail;
+
+	if (dp_sim_dev->skip_hpd)
+		dp_sim_dev->bridge.flag |= DP_AUX_BRIDGE_HPD;
+
+	ret = dp_mst_sim_update(dp_sim_dev->bridge.mst_ctx,
+			dp_sim_dev->port_num, dp_sim_dev->ports);
+	if (ret)
+		goto fail;
+
+	ret = dp_sim_debug_init(dp_sim_dev);
+	if (ret)
+		goto fail;
+
+	ret = dp_aux_add_bridge(&dp_sim_dev->bridge);
+	if (ret)
+		goto fail;
+
+	platform_set_drvdata(pdev, dp_sim_dev);
+
+	return 0;
+
+fail:
+	dp_sim_destroy_bridge(bridge);
+	return ret;
+}
+
+int dp_sim_remove(struct platform_device *pdev)
+{
+	struct dp_sim_device *dp_sim_dev;
+
+	dp_sim_dev = platform_get_drvdata(pdev);
+	if (!dp_sim_dev)
+		return 0;
+
+	debugfs_remove_recursive(dp_sim_dev->debugfs_dir);
+
+	dp_sim_destroy_bridge(&dp_sim_dev->bridge);
+
+	return 0;
+}
+
+#if 0
+static const struct of_device_id dt_match[] = {
+	{ .compatible = "qcom,dp-mst-sim"},
+	{},
+};
+
+static struct platform_driver dp_sim_driver = {
+	.probe = dp_sim_probe,
+	.remove = dp_sim_remove,
+	.driver = {
+		.name = "dp_sim",
+		.of_match_table = dt_match,
+		.suppress_bind_attrs = true,
+	},
+};
+
+static int __init dp_sim_register(void)
+{
+	return platform_driver_register(&dp_sim_driver);
+}
+
+static void __exit dp_sim_unregister(void)
+{
+	platform_driver_unregister(&dp_sim_driver);
+}
+
+module_init(dp_sim_register);
+module_exit(dp_sim_unregister);
+
+#endif

+ 51 - 0
msm/dp/dp_mst_sim.h

@@ -0,0 +1,51 @@
+/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DP_MST_SIM_H_
+#define _DP_MST_SIM_H_
+
+#include "dp_aux_bridge.h"
+#include "dp_mst_sim_helper.h"
+#include <drm/drm_connector.h>
+#include <drm/drm_modes.h>
+
+enum dp_sim_mode_type {
+	DP_SIM_MODE_EDID       = 0x00000001,
+	DP_SIM_MODE_DPCD_READ  = 0x00000002,
+	DP_SIM_MODE_DPCD_WRITE = 0x00000004,
+	DP_SIM_MODE_LINK_TRAIN = 0x00000008,
+	DP_SIM_MODE_MST        = 0x00000010,
+	DP_SIM_MODE_ALL        = 0x0000001F,
+};
+
+int dp_sim_create_bridge(struct device *dev,
+		struct dp_aux_bridge **bridge);
+
+int dp_sim_destroy_bridge(struct dp_aux_bridge *bridge);
+
+int dp_sim_set_sim_mode(struct dp_aux_bridge *bridge, u32 sim_mode);
+
+int dp_sim_update_port_num(struct dp_aux_bridge *bridge, u32 port_num);
+
+int dp_sim_update_port_status(struct dp_aux_bridge *bridge,
+		int port, enum drm_connector_status status);
+
+int dp_sim_update_port_edid(struct dp_aux_bridge *bridge,
+		int port, const u8 *edid, u32 size);
+
+int dp_sim_write_dpcd_reg(struct dp_aux_bridge *bridge,
+		const u8 *dpcd, u32 size, u32 offset);
+
+int dp_sim_read_dpcd_reg(struct dp_aux_bridge *bridge,
+		u8 *dpcd, u32 size, u32 offset);
+
+#endif /* _DP_MST_SIM_H_ */

+ 1175 - 0
msm/dp/dp_mst_sim_helper.c

@@ -0,0 +1,1175 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Copyright © 2014 Red Hat
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <drm/drm_fixed.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_dp_mst_helper.h>
+#include "dp_mst_sim_helper.h"
+#include "dp_debug.h"
+
+#define DP_MST_DEBUG(fmt, ...) DP_DEBUG(fmt, ##__VA_ARGS__)
+#define DP_MST_INFO(fmt, ...) DP_DEBUG(fmt, ##__VA_ARGS__)
+
+#define DDC_SEGMENT_ADDR 0x30
+
+struct dp_mst_sim_context {
+	void *host_dev;
+	void (*host_hpd_irq)(void *host_dev);
+	void (*host_req)(void *host_dev, const u8 *in, int in_size,
+			u8 *out, int *out_size);
+
+	struct dp_mst_sim_port *ports;
+	u32 port_num;
+
+	struct drm_dp_sideband_msg_rx down_req;
+	struct drm_dp_sideband_msg_rx down_rep;
+
+	struct mutex session_lock;
+	struct completion session_comp;
+	struct workqueue_struct *wq;
+	int reset_cnt;
+
+	u8 esi[16];
+	u8 guid[16];
+	u8 dpcd[1024];
+};
+
+struct dp_mst_sim_work {
+	struct work_struct base;
+	struct dp_mst_sim_context *ctx;
+	unsigned int address;
+	u8 buffer[256];
+	size_t size;
+};
+
+struct dp_mst_notify_work {
+	struct work_struct base;
+	struct dp_mst_sim_context *ctx;
+	u32 port_mask;
+};
+
+#ifdef CONFIG_DYNAMIC_DEBUG
+static void dp_sideband_hex_dump(const char *name,
+		u32 address, u8 *buffer, size_t size)
+{
+	char prefix[64];
+	int i, linelen, remaining = size;
+	const int rowsize = 16;
+	u8 linebuf[64];
+
+	snprintf(prefix, sizeof(prefix), "%s(%d) %4xh(%2zu): ",
+		name, current->pid, address, size);
+
+	for (i = 0; i < size; i += rowsize) {
+		linelen = min(remaining, rowsize);
+		remaining -= rowsize;
+
+		hex_dump_to_buffer(buffer + i, linelen, rowsize, 1,
+			linebuf, sizeof(linebuf), false);
+
+		DP_MST_DEBUG("%s%s\n", prefix, linebuf);
+	}
+}
+#else
+static void dp_sideband_hex_dump(const char *name,
+		u32 address, u8 *buffer, size_t size)
+{
+}
+#endif
+
+static u8 dp_mst_sim_msg_header_crc4(const uint8_t *data, size_t num_nibbles)
+{
+	u8 bitmask = 0x80;
+	u8 bitshift = 7;
+	u8 array_index = 0;
+	int number_of_bits = num_nibbles * 4;
+	u8 remainder = 0;
+
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		remainder |= (data[array_index] & bitmask) >> bitshift;
+		bitmask >>= 1;
+		bitshift--;
+		if (bitmask == 0) {
+			bitmask = 0x80;
+			bitshift = 7;
+			array_index++;
+		}
+		if ((remainder & 0x10) == 0x10)
+			remainder ^= 0x13;
+	}
+
+	number_of_bits = 4;
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		if ((remainder & 0x10) != 0)
+			remainder ^= 0x13;
+	}
+
+	return remainder;
+}
+
+static u8 dp_mst_sim_msg_data_crc4(const uint8_t *data, u8 number_of_bytes)
+{
+	u8 bitmask = 0x80;
+	u8 bitshift = 7;
+	u8 array_index = 0;
+	int number_of_bits = number_of_bytes * 8;
+	u16 remainder = 0;
+
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		remainder |= (data[array_index] & bitmask) >> bitshift;
+		bitmask >>= 1;
+		bitshift--;
+		if (bitmask == 0) {
+			bitmask = 0x80;
+			bitshift = 7;
+			array_index++;
+		}
+		if ((remainder & 0x100) == 0x100)
+			remainder ^= 0xd5;
+	}
+
+	number_of_bits = 8;
+	while (number_of_bits != 0) {
+		number_of_bits--;
+		remainder <<= 1;
+		if ((remainder & 0x100) != 0)
+			remainder ^= 0xd5;
+	}
+
+	return remainder & 0xff;
+}
+
+static bool dp_mst_sim_decode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr,
+					   u8 *buf, int buflen, u8 *hdrlen)
+{
+	u8 crc4;
+	u8 len;
+	int i;
+	u8 idx;
+
+	if (buf[0] == 0)
+		return false;
+	len = 3;
+	len += ((buf[0] & 0xf0) >> 4) / 2;
+	if (len > buflen)
+		return false;
+	crc4 = dp_mst_sim_msg_header_crc4(buf, (len * 2) - 1);
+
+	if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) {
+		DP_MST_DEBUG("crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]);
+		return false;
+	}
+
+	hdr->lct = (buf[0] & 0xf0) >> 4;
+	hdr->lcr = (buf[0] & 0xf);
+	idx = 1;
+	for (i = 0; i < (hdr->lct / 2); i++)
+		hdr->rad[i] = buf[idx++];
+	hdr->broadcast = (buf[idx] >> 7) & 0x1;
+	hdr->path_msg = (buf[idx] >> 6) & 0x1;
+	hdr->msg_len = buf[idx] & 0x3f;
+	idx++;
+	hdr->somt = (buf[idx] >> 7) & 0x1;
+	hdr->eomt = (buf[idx] >> 6) & 0x1;
+	hdr->seqno = (buf[idx] >> 4) & 0x1;
+	idx++;
+	*hdrlen = idx;
+	return true;
+}
+
+static bool dp_mst_sim_sideband_msg_build(struct drm_dp_sideband_msg_rx *msg,
+				      u8 *replybuf, u8 replybuflen, bool hdr)
+{
+	int ret;
+	u8 crc4;
+
+	if (hdr) {
+		u8 hdrlen;
+		struct drm_dp_sideband_msg_hdr recv_hdr;
+
+		ret = dp_mst_sim_decode_sideband_msg_hdr(&recv_hdr,
+			replybuf, replybuflen, &hdrlen);
+		if (ret == false)
+			return false;
+
+		/*
+		 * ignore out-of-order messages or messages that are part of a
+		 * failed transaction
+		 */
+		if (!recv_hdr.somt && !msg->have_somt)
+			return false;
+
+		/* get length contained in this portion */
+		msg->curchunk_len = recv_hdr.msg_len;
+		msg->curchunk_hdrlen = hdrlen;
+
+		/* we have already gotten an somt - don't bother parsing */
+		if (recv_hdr.somt && msg->have_somt)
+			return false;
+
+		if (recv_hdr.somt) {
+			memcpy(&msg->initial_hdr, &recv_hdr,
+				sizeof(struct drm_dp_sideband_msg_hdr));
+			msg->have_somt = true;
+		}
+		if (recv_hdr.eomt)
+			msg->have_eomt = true;
+
+		/* copy the bytes for the remainder of this header chunk */
+		msg->curchunk_idx = min(msg->curchunk_len,
+			(u8)(replybuflen - hdrlen));
+		memcpy(&msg->chunk[0], replybuf + hdrlen, msg->curchunk_idx);
+	} else {
+		memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen);
+		msg->curchunk_idx += replybuflen;
+	}
+
+	if (msg->curchunk_idx >= msg->curchunk_len) {
+		/* do CRC */
+		crc4 = dp_mst_sim_msg_data_crc4(msg->chunk, msg->curchunk_len - 1);
+		/* copy chunk into bigger msg */
+		memcpy(&msg->msg[msg->curlen], msg->chunk,
+			msg->curchunk_len - 1);
+		msg->curlen += msg->curchunk_len - 1;
+	}
+	return true;
+}
+
+static void dp_mst_sim_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr,
+					   u8 *buf, int *len)
+{
+	int idx = 0;
+	int i;
+	u8 crc4;
+
+	buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf);
+	for (i = 0; i < (hdr->lct / 2); i++)
+		buf[idx++] = hdr->rad[i];
+	buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) |
+		(hdr->msg_len & 0x3f);
+	buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4);
+
+	crc4 = dp_mst_sim_msg_header_crc4(buf, (idx * 2) - 1);
+	buf[idx - 1] |= (crc4 & 0xf);
+
+	*len = idx;
+}
+
+static bool dp_get_one_sb_msg(struct drm_dp_sideband_msg_rx *msg,
+		struct drm_dp_aux_msg *aux_msg)
+{
+	int ret;
+
+	if (!msg->have_somt) {
+		ret = dp_mst_sim_sideband_msg_build(msg,
+			aux_msg->buffer, aux_msg->size, true);
+		if (!ret) {
+			DP_ERR("sideband hdr build failed\n");
+			return false;
+		}
+	} else {
+		ret = dp_mst_sim_sideband_msg_build(msg,
+			aux_msg->buffer, aux_msg->size, false);
+		if (!ret) {
+			DP_ERR("sideband msg build failed\n");
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static int dp_sideband_build_nak_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	struct drm_dp_sideband_msg_rx *msg = &ctx->down_req;
+	u8 *buf = ctx->down_rep.msg;
+	int idx = 0;
+
+	buf[idx] = msg->msg[0] | 0x80;
+	idx++;
+
+	memcpy(&buf[idx], ctx->guid, 16);
+	idx += 16;
+
+	buf[idx] = 0x4;
+	idx++;
+
+	buf[idx] = 0;
+	idx++;
+
+	return idx;
+}
+
+
+static int dp_sideband_build_link_address_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	struct dp_mst_sim_port *port;
+	u8 *buf = ctx->down_rep.msg;
+	int idx = 0;
+	u32 i, tmp;
+
+	buf[idx] = DP_LINK_ADDRESS;
+	idx++;
+
+	memcpy(&buf[idx], ctx->guid, 16);
+	idx += 16;
+
+	buf[idx] = ctx->port_num;
+	idx++;
+
+	for (i = 0; i < ctx->port_num; i++) {
+		port = &ctx->ports[i];
+
+		tmp = 0;
+		if (port->input)
+			tmp |= 0x80;
+		tmp |= port->pdt << 4;
+		tmp |= i & 0xF;
+		buf[idx] = tmp;
+		idx++;
+
+		tmp = 0;
+		if (port->mcs)
+			tmp |= 0x80;
+		if (port->ddps)
+			tmp |= 0x40;
+
+		if (port->input) {
+			buf[idx] = tmp;
+			idx++;
+			continue;
+		}
+
+		if (port->ldps)
+			tmp |= 0x20;
+		buf[idx] = tmp;
+		idx++;
+
+		buf[idx] = port->dpcd_rev;
+		idx++;
+
+		memcpy(&buf[idx], port->peer_guid, 16);
+		idx += 16;
+
+		buf[idx] = (port->num_sdp_streams << 4) |
+			(port->num_sdp_stream_sinks);
+		idx++;
+	}
+
+	return idx;
+}
+
+static int dp_sideband_build_remote_i2c_read_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	struct dp_mst_sim_port *port;
+	struct drm_dp_remote_i2c_read i2c_read;
+	u8 *buf;
+	int idx;
+	u32 i, start, len;
+
+	buf = ctx->down_req.msg;
+	idx = 1;
+
+	i2c_read.num_transactions = buf[idx] & 0x3;
+	i2c_read.port_number = buf[idx] >> 4;
+	idx++;
+
+	if (i2c_read.port_number >= ctx->port_num)
+		goto err;
+
+	for (i = 0; i < i2c_read.num_transactions; i++) {
+		i2c_read.transactions[i].i2c_dev_id = buf[idx] & 0x7f;
+		idx++;
+
+		i2c_read.transactions[i].num_bytes = buf[idx];
+		idx++;
+
+		i2c_read.transactions[i].bytes = &buf[idx];
+		idx += i2c_read.transactions[i].num_bytes;
+
+		i2c_read.transactions[i].no_stop_bit = (buf[idx] >> 4) & 0x1;
+		i2c_read.transactions[i].i2c_transaction_delay = buf[idx] & 0xf;
+		idx++;
+	}
+
+	i2c_read.read_i2c_device_id = buf[idx];
+	idx++;
+
+	i2c_read.num_bytes_read = buf[idx];
+	idx++;
+
+	port = &ctx->ports[i2c_read.port_number];
+
+	if (i2c_read.num_transactions == 1) {
+		if (i2c_read.transactions[0].i2c_dev_id != DDC_ADDR ||
+		    i2c_read.transactions[0].num_bytes != 1) {
+			DP_ERR("unsupported i2c address\n");
+			goto err;
+		}
+
+		start = i2c_read.transactions[0].bytes[0];
+	} else if (i2c_read.num_transactions == 2) {
+		if (i2c_read.transactions[0].i2c_dev_id != DDC_SEGMENT_ADDR ||
+		    i2c_read.transactions[0].num_bytes != 1 ||
+		    i2c_read.transactions[1].i2c_dev_id != DDC_ADDR ||
+		    i2c_read.transactions[1].num_bytes != 1) {
+			DP_ERR("unsupported i2c address\n");
+			goto err;
+		}
+
+		start = i2c_read.transactions[0].bytes[0] * EDID_LENGTH * 2 +
+			i2c_read.transactions[1].bytes[0];
+	} else {
+		DP_ERR("unsupported i2c transaction\n");
+		goto err;
+	}
+
+	len = i2c_read.num_bytes_read;
+
+	if (start + len > port->edid_size) {
+		DP_ERR("edid data exceeds maximum\n");
+		goto err;
+	}
+
+	buf = ctx->down_rep.msg;
+	idx = 0;
+
+	buf[idx] = DP_REMOTE_I2C_READ;
+	idx++;
+
+	buf[idx] = i2c_read.port_number;
+	idx++;
+
+	buf[idx] = len;
+	idx++;
+
+	memcpy(&buf[idx], &port->edid[start], len);
+	idx += len;
+
+	return idx;
+err:
+	return dp_sideband_build_nak_rep(ctx);
+}
+
+static int dp_sideband_build_enum_path_resources_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	struct dp_mst_sim_port *port;
+	u8 port_num;
+	u8 *buf;
+	int idx;
+
+	buf = ctx->down_req.msg;
+	port_num = buf[1] >> 4;
+
+	if (port_num >= ctx->port_num) {
+		DP_ERR("invalid port num\n");
+		goto err;
+	}
+
+	port = &ctx->ports[port_num];
+
+	buf = ctx->down_rep.msg;
+	idx = 0;
+
+	buf[idx] = DP_ENUM_PATH_RESOURCES;
+	idx++;
+
+	buf[idx] = port_num << 4;
+	idx++;
+
+	buf[idx] = port->full_pbn >> 8;
+	idx++;
+
+	buf[idx] = port->full_pbn & 0xFF;
+	idx++;
+
+	buf[idx] = port->avail_pbn >> 8;
+	idx++;
+
+	buf[idx] = port->avail_pbn & 0xFF;
+	idx++;
+
+	return idx;
+err:
+	return dp_sideband_build_nak_rep(ctx);
+}
+
+static int dp_sideband_build_allocate_payload_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	struct drm_dp_allocate_payload allocate_payload;
+	u8 *buf;
+	int idx;
+	u32 i;
+
+	buf = ctx->down_req.msg;
+	idx = 1;
+
+	allocate_payload.port_number = buf[idx] >> 4;
+	allocate_payload.number_sdp_streams = buf[idx] & 0xF;
+	idx++;
+
+	allocate_payload.vcpi = buf[idx];
+	idx++;
+
+	allocate_payload.pbn = (buf[idx] << 8) | buf[idx+1];
+	idx += 2;
+
+	for (i = 0; i <  allocate_payload.number_sdp_streams / 2; i++) {
+		allocate_payload.sdp_stream_sink[i * 2] = buf[idx] >> 4;
+		allocate_payload.sdp_stream_sink[i * 2 + 1] = buf[idx] & 0xf;
+		idx++;
+	}
+	if (allocate_payload.number_sdp_streams & 1) {
+		i =  allocate_payload.number_sdp_streams - 1;
+		allocate_payload.sdp_stream_sink[i] = buf[idx] >> 4;
+		idx++;
+	}
+
+	if (allocate_payload.port_number >= ctx->port_num) {
+		DP_ERR("invalid port num\n");
+		goto err;
+	}
+
+	buf = ctx->down_rep.msg;
+	idx = 0;
+
+	buf[idx] = DP_ALLOCATE_PAYLOAD;
+	idx++;
+
+	buf[idx] = allocate_payload.port_number;
+	idx++;
+
+	buf[idx] = allocate_payload.vcpi;
+	idx++;
+
+	buf[idx] = allocate_payload.pbn >> 8;
+	idx++;
+
+	buf[idx] = allocate_payload.pbn & 0xFF;
+	idx++;
+
+	return idx;
+err:
+	return dp_sideband_build_nak_rep(ctx);
+}
+
+static int dp_sideband_build_power_updown_phy_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	u8 port_num;
+	u8 *buf;
+	int idx;
+
+	buf = ctx->down_req.msg;
+	port_num = buf[1] >> 4;
+
+	if (port_num >= ctx->port_num) {
+		DP_ERR("invalid port num\n");
+		goto err;
+	}
+
+	buf = ctx->down_rep.msg;
+	idx = 0;
+
+	buf[idx] = ctx->down_req.msg[0];
+	idx++;
+
+	buf[idx] = port_num;
+	idx++;
+
+	return idx;
+err:
+	return dp_sideband_build_nak_rep(ctx);
+}
+
+static int dp_sideband_build_clear_payload_id_table_rep(
+		struct dp_mst_sim_context *ctx)
+{
+	u8 *buf = ctx->down_rep.msg;
+	int idx = 0;
+
+	buf[idx] = DP_CLEAR_PAYLOAD_ID_TABLE;
+	idx++;
+
+	return idx;
+}
+
+static int dp_sideband_build_connection_notify_req(
+		struct dp_mst_sim_context *ctx, int port_idx)
+{
+	struct dp_mst_sim_port *port = &ctx->ports[port_idx];
+	u8 *buf = ctx->down_rep.msg;
+	int idx = 0;
+
+	buf[idx] = DP_CONNECTION_STATUS_NOTIFY;
+	idx++;
+
+	buf[idx] = port_idx << 4;
+	idx++;
+
+	memcpy(&buf[idx], &port->peer_guid, 16);
+	idx += 16;
+
+	buf[idx] = (port->ldps << 6) |
+			(port->ddps << 5) |
+			(port->mcs << 4) |
+			(port->input << 3) |
+			(port->pdt & 0x7);
+	idx++;
+
+	return idx;
+}
+
+static inline int dp_sideband_update_esi(
+		struct dp_mst_sim_context *ctx, u8 val)
+{
+	ctx->esi[0] = ctx->port_num;
+	ctx->esi[1] = val;
+	ctx->esi[2] = 0;
+
+	return 0;
+}
+
+static inline bool dp_sideband_pending_esi(
+		struct dp_mst_sim_context *ctx, u8 val)
+{
+	return !!(ctx->esi[1] & val);
+}
+
+static int dp_mst_sim_clear_esi(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *msg)
+{
+	size_t i;
+	u8 old_esi = ctx->esi[1];
+	u32 addr = msg->address - DP_SINK_COUNT_ESI;
+
+	if (msg->size - addr >= 16) {
+		msg->reply = DP_AUX_NATIVE_REPLY_NACK;
+		return 0;
+	}
+
+	mutex_lock(&ctx->session_lock);
+
+	for (i = 0; i < msg->size; i++)
+		ctx->esi[addr + i] &= ~((u8 *)msg->buffer)[i];
+
+	if (old_esi != ctx->esi[1])
+		complete(&ctx->session_comp);
+
+	mutex_unlock(&ctx->session_lock);
+
+	msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+	return 0;
+}
+
+static int dp_mst_sim_read_esi(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *msg)
+{
+	u32 addr = msg->address - DP_SINK_COUNT_ESI;
+
+	if (msg->size - addr >= 16) {
+		msg->reply = DP_AUX_NATIVE_REPLY_NACK;
+		return 0;
+	}
+
+	memcpy(msg->buffer, &ctx->esi[addr], msg->size);
+	msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+
+	return 0;
+}
+
+static int dp_mst_sim_down_req_internal(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *aux_msg)
+{
+	struct drm_dp_sideband_msg_rx *msg = &ctx->down_req;
+	struct drm_dp_sideband_msg_hdr hdr;
+	bool seqno;
+	int ret, size, len, hdr_len;
+
+	ret = dp_get_one_sb_msg(msg, aux_msg);
+	if (!ret)
+		return -EINVAL;
+
+	if (!msg->have_eomt)
+		return 0;
+
+	seqno = msg->initial_hdr.seqno;
+
+	switch (msg->msg[0]) {
+	case DP_LINK_ADDRESS:
+		size = dp_sideband_build_link_address_rep(ctx);
+		break;
+	case DP_REMOTE_I2C_READ:
+		size = dp_sideband_build_remote_i2c_read_rep(ctx);
+		break;
+	case DP_ENUM_PATH_RESOURCES:
+		size = dp_sideband_build_enum_path_resources_rep(ctx);
+		break;
+	case DP_ALLOCATE_PAYLOAD:
+		size = dp_sideband_build_allocate_payload_rep(ctx);
+		break;
+	case DP_POWER_DOWN_PHY:
+	case DP_POWER_UP_PHY:
+		size = dp_sideband_build_power_updown_phy_rep(ctx);
+		break;
+	case DP_CLEAR_PAYLOAD_ID_TABLE:
+		size = dp_sideband_build_clear_payload_id_table_rep(ctx);
+		break;
+	default:
+		size = dp_sideband_build_nak_rep(ctx);
+		break;
+	}
+
+	if (ctx->host_req)
+		ctx->host_req(ctx->host_dev,
+			ctx->down_req.msg, ctx->down_req.curlen,
+			ctx->down_rep.msg, &size);
+
+	memset(msg, 0, sizeof(*msg));
+	msg = &ctx->down_rep;
+	msg->curlen = 0;
+
+	mutex_lock(&ctx->session_lock);
+
+	while (msg->curlen < size) {
+		if (ctx->reset_cnt)
+			break;
+
+		/* copy data */
+		len = min(size - msg->curlen, 44);
+		memcpy(&ctx->dpcd[3], &msg->msg[msg->curlen], len);
+		msg->curlen += len;
+
+		/* build header */
+		memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr));
+		hdr.broadcast = 0;
+		hdr.path_msg = 0;
+		hdr.lct = 1;
+		hdr.lcr = 0;
+		hdr.seqno = seqno;
+		hdr.msg_len = len + 1;
+		hdr.eomt = (msg->curlen == size);
+		hdr.somt = (msg->curlen == len);
+		dp_mst_sim_encode_sideband_msg_hdr(&hdr, ctx->dpcd, &hdr_len);
+
+		/* build crc */
+		ctx->dpcd[len + 3] = dp_mst_sim_msg_data_crc4(&ctx->dpcd[3], len);
+
+		/* update esi */
+		dp_sideband_update_esi(ctx, DP_DOWN_REP_MSG_RDY);
+
+		/* notify host */
+		mutex_unlock(&ctx->session_lock);
+		ctx->host_hpd_irq(ctx->host_dev);
+		mutex_lock(&ctx->session_lock);
+
+		/* wait until esi is cleared */
+		while (dp_sideband_pending_esi(ctx, DP_DOWN_REP_MSG_RDY)) {
+			if (ctx->reset_cnt)
+				break;
+			mutex_unlock(&ctx->session_lock);
+			wait_for_completion(&ctx->session_comp);
+			mutex_lock(&ctx->session_lock);
+		}
+	}
+
+	mutex_unlock(&ctx->session_lock);
+
+	return 0;
+}
+
+static void dp_mst_sim_down_req_work(struct work_struct *work)
+{
+	struct dp_mst_sim_work *sim_work =
+		container_of(work, struct dp_mst_sim_work, base);
+	struct drm_dp_aux_msg msg;
+
+	msg.address = sim_work->address;
+	msg.buffer = sim_work->buffer;
+	msg.size = sim_work->size;
+
+	dp_mst_sim_down_req_internal(sim_work->ctx, &msg);
+
+	kfree(sim_work);
+}
+
+static int dp_mst_sim_down_req(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *aux_msg)
+{
+	struct dp_mst_sim_work *work;
+
+	if (aux_msg->size >= 256) {
+		aux_msg->reply = DP_AUX_NATIVE_REPLY_NACK;
+		return 0;
+	}
+
+	dp_sideband_hex_dump("request",
+		aux_msg->address, aux_msg->buffer, aux_msg->size);
+
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work) {
+		aux_msg->reply = DP_AUX_NATIVE_REPLY_NACK;
+		return 0;
+	}
+
+	work->ctx = ctx;
+	work->address = aux_msg->address;
+	work->size = aux_msg->size;
+	memcpy(work->buffer, aux_msg->buffer, aux_msg->size);
+
+	INIT_WORK(&work->base, dp_mst_sim_down_req_work);
+	queue_work(ctx->wq, &work->base);
+
+	aux_msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+	return 0;
+}
+
+static int dp_mst_sim_down_rep(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *msg)
+{
+	u32 addr = msg->address - DP_SIDEBAND_MSG_DOWN_REP_BASE;
+
+	memcpy(msg->buffer, &ctx->dpcd[addr], msg->size);
+	msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+
+	dp_sideband_hex_dump("reply",
+		addr, msg->buffer, msg->size);
+
+	return 0;
+}
+
+static int dp_mst_sim_up_req(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *msg)
+{
+	u32 addr = msg->address - DP_SIDEBAND_MSG_UP_REQ_BASE;
+
+	memcpy(msg->buffer, &ctx->dpcd[addr], msg->size);
+	msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+
+	dp_sideband_hex_dump("up_req",
+		addr, msg->buffer, msg->size);
+
+	return 0;
+}
+
+static void dp_mst_sim_reset_work(struct work_struct *work)
+{
+	struct dp_mst_notify_work *notify_work =
+		container_of(work, struct dp_mst_notify_work, base);
+	struct dp_mst_sim_context *ctx = notify_work->ctx;
+
+	mutex_lock(&ctx->session_lock);
+	--ctx->reset_cnt;
+	reinit_completion(&ctx->session_comp);
+	mutex_unlock(&ctx->session_lock);
+}
+
+static int dp_mst_sim_reset(struct dp_mst_sim_context *ctx,
+		struct drm_dp_aux_msg *msg)
+{
+	struct dp_mst_notify_work *work;
+
+	if (!msg->size || ((u8 *)msg->buffer)[0])
+		return msg->size;
+
+	mutex_lock(&ctx->session_lock);
+	++ctx->reset_cnt;
+	complete(&ctx->session_comp);
+	mutex_unlock(&ctx->session_lock);
+
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return msg->size;
+
+	work->ctx = ctx;
+	INIT_WORK(&work->base, dp_mst_sim_reset_work);
+	queue_work(ctx->wq, &work->base);
+
+	return msg->size;
+}
+
+int dp_mst_sim_transfer(void *mst_sim_context, struct drm_dp_aux_msg *msg)
+{
+	struct dp_mst_sim_context *ctx = mst_sim_context;
+
+	if (!ctx || !ctx->port_num || !msg)
+		return -ENOENT;
+
+	if (msg->request == DP_AUX_NATIVE_WRITE) {
+		if (msg->address >= DP_SIDEBAND_MSG_DOWN_REQ_BASE &&
+		    msg->address < DP_SIDEBAND_MSG_DOWN_REQ_BASE + 256)
+			return dp_mst_sim_down_req(mst_sim_context, msg);
+
+		if (msg->address >= DP_SIDEBAND_MSG_UP_REP_BASE &&
+		    msg->address < DP_SIDEBAND_MSG_UP_REP_BASE + 256)
+			return 0;
+
+		if (msg->address >= DP_SINK_COUNT_ESI &&
+		    msg->address < DP_SINK_COUNT_ESI + 14)
+			return dp_mst_sim_clear_esi(mst_sim_context, msg);
+
+		if (msg->address == DP_MSTM_CTRL)
+			return dp_mst_sim_reset(mst_sim_context, msg);
+
+	} else if (msg->request == DP_AUX_NATIVE_READ) {
+		if (msg->address >= DP_SIDEBAND_MSG_DOWN_REP_BASE &&
+		    msg->address < DP_SIDEBAND_MSG_DOWN_REP_BASE + 256)
+			return dp_mst_sim_down_rep(mst_sim_context, msg);
+
+		if (msg->address >= DP_SIDEBAND_MSG_UP_REQ_BASE &&
+		    msg->address < DP_SIDEBAND_MSG_UP_REQ_BASE + 256)
+			return dp_mst_sim_up_req(mst_sim_context, msg);
+
+		if (msg->address >= DP_SINK_COUNT_ESI &&
+		    msg->address < DP_SINK_COUNT_ESI + 14)
+			return dp_mst_sim_read_esi(mst_sim_context, msg);
+	}
+
+	return -EINVAL;
+}
+
+static void dp_mst_sim_up_req_work(struct work_struct *work)
+{
+	struct dp_mst_notify_work *notify_work =
+		container_of(work, struct dp_mst_notify_work, base);
+	struct dp_mst_sim_context *ctx = notify_work->ctx;
+	struct drm_dp_sideband_msg_rx *msg = &ctx->down_rep;
+	struct drm_dp_sideband_msg_hdr hdr;
+	int len, hdr_len, i;
+
+	mutex_lock(&ctx->session_lock);
+
+	for (i = 0; i < ctx->port_num; i++) {
+		if (ctx->reset_cnt)
+			break;
+
+		if (!(notify_work->port_mask & (1 << i)))
+			continue;
+
+		len = dp_sideband_build_connection_notify_req(ctx, i);
+
+		/* copy data */
+		memcpy(&ctx->dpcd[3], msg->msg, len);
+
+		/* build header */
+		memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr));
+		hdr.broadcast = 0;
+		hdr.path_msg = 0;
+		hdr.lct = 1;
+		hdr.lcr = 0;
+		hdr.seqno = 0;
+		hdr.msg_len = len + 1;
+		hdr.eomt = 1;
+		hdr.somt = 1;
+		dp_mst_sim_encode_sideband_msg_hdr(&hdr, ctx->dpcd, &hdr_len);
+
+		/* build crc */
+		ctx->dpcd[len + 3] = dp_mst_sim_msg_data_crc4(&ctx->dpcd[3], len);
+
+		/* update esi */
+		dp_sideband_update_esi(ctx, DP_UP_REQ_MSG_RDY);
+
+		/* notify host */
+		mutex_unlock(&ctx->session_lock);
+		ctx->host_hpd_irq(ctx->host_dev);
+		mutex_lock(&ctx->session_lock);
+
+		/* wait until esi is cleared */
+		while (dp_sideband_pending_esi(ctx, DP_UP_REQ_MSG_RDY)) {
+			if (ctx->reset_cnt)
+				break;
+			mutex_unlock(&ctx->session_lock);
+			wait_for_completion(&ctx->session_comp);
+			mutex_lock(&ctx->session_lock);
+		}
+	}
+
+	mutex_unlock(&ctx->session_lock);
+
+	kfree(notify_work);
+}
+
+static void dp_mst_sim_notify(struct dp_mst_sim_context *ctx,
+		u32 port_mask)
+{
+	struct dp_mst_notify_work *work;
+
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return;
+
+	work->ctx = ctx;
+	work->port_mask = port_mask;
+
+	INIT_WORK(&work->base, dp_mst_sim_up_req_work);
+	queue_work(ctx->wq, &work->base);
+}
+
+int dp_mst_sim_update(void *mst_sim_context, u32 port_num,
+		struct dp_mst_sim_port *ports)
+{
+	struct dp_mst_sim_context *ctx = mst_sim_context;
+	u8 *edid;
+	int rc = 0;
+	u32 update_mask = 0;
+	u32 i;
+
+	if (!ctx || port_num >= 15)
+		return -EINVAL;
+
+	mutex_lock(&ctx->session_lock);
+
+	/* get update mask */
+	if (port_num && ctx->port_num == port_num) {
+		for (i = 0; i < port_num; i++) {
+			if (ports[i].pdt != ctx->ports[i].pdt ||
+			    ports[i].input != ctx->ports[i].input ||
+			    ports[i].ldps != ctx->ports[i].ldps ||
+			    ports[i].ddps != ctx->ports[i].ddps ||
+			    ports[i].mcs != ctx->ports[i].mcs)
+				update_mask |= (1 << i);
+		}
+	}
+
+	for (i = 0; i < ctx->port_num; i++)
+		kfree(ctx->ports[i].edid);
+	kfree(ctx->ports);
+	ctx->port_num = 0;
+
+	ctx->ports = kcalloc(port_num, sizeof(*ports), GFP_KERNEL);
+	if (!ctx->ports) {
+		rc = -ENOMEM;
+		goto fail;
+	}
+
+	ctx->port_num = port_num;
+	for (i = 0; i < port_num; i++) {
+		ctx->ports[i] = ports[i];
+		if (ports[i].edid_size) {
+			if (!ports[i].edid) {
+				rc = -EINVAL;
+				goto fail;
+			}
+
+			edid = kzalloc(ports[i].edid_size,
+					GFP_KERNEL);
+			if (!edid) {
+				rc = -ENOMEM;
+				goto fail;
+			}
+
+			memcpy(edid, ports[i].edid, ports[i].edid_size);
+			ctx->ports[i].edid = edid;
+		}
+	}
+
+fail:
+	if (rc) {
+		for (i = 0; i < ctx->port_num; i++)
+			kfree(ctx->ports[i].edid);
+		kfree(ctx->ports);
+	}
+
+	mutex_unlock(&ctx->session_lock);
+
+	if (update_mask)
+		dp_mst_sim_notify(ctx, update_mask);
+
+	return rc;
+}
+
+int dp_mst_sim_create(const struct dp_mst_sim_cfg *cfg,
+		void **mst_sim_context)
+{
+	struct dp_mst_sim_context *ctx;
+
+	if (!cfg || !mst_sim_context)
+		return -EINVAL;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->host_dev = cfg->host_dev;
+	ctx->host_hpd_irq = cfg->host_hpd_irq;
+	ctx->host_req = cfg->host_req;
+	memcpy(ctx->guid, cfg->guid, 16);
+
+	mutex_init(&ctx->session_lock);
+	init_completion(&ctx->session_comp);
+
+	ctx->wq = create_singlethread_workqueue("dp_mst_sim");
+	if (IS_ERR_OR_NULL(ctx->wq)) {
+		DP_ERR("Error creating wq\n");
+		kfree(ctx);
+		return -EPERM;
+	}
+
+	*mst_sim_context = ctx;
+	return 0;
+}
+
+int dp_mst_sim_destroy(void *mst_sim_context)
+{
+	struct dp_mst_sim_context *ctx = mst_sim_context;
+	u32 i;
+
+	if (!ctx)
+		return -EINVAL;
+
+	for (i = 0; i < ctx->port_num; i++)
+		kfree(ctx->ports[i].edid);
+	kfree(ctx->ports);
+
+	destroy_workqueue(ctx->wq);
+
+	return 0;
+}
+

+ 146 - 0
msm/dp/dp_mst_sim_helper.h

@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#ifndef _DP_MST_SIM_HELPER_H_
+#define _DP_MST_SIM_HELPER_H_
+
+#include <linux/types.h>
+#include <drm/drm_dp_helper.h>
+
+/**
+ * struct dp_mst_sim_port - MST port configuration
+ * @input: if this port is an input port.
+ * @mcs: message capability status - DP 1.2 spec.
+ * @ddps: DisplayPort Device Plug Status - DP 1.2
+ * @pdt: Peer Device Type
+ * @ldps: Legacy Device Plug Status
+ * @dpcd_rev: DPCD revision of device on this port
+ * @peer_guid: Peer GUID on this port
+ * @num_sdp_streams: Number of simultaneous streams
+ * @num_sdp_stream_sinks: Number of stream sinks
+ * @full_pbn: Full bandwidth for this port.
+ * @avail_pbn: Available bandwidth for this port.
+ * @edid: EDID data on this port.
+ * @edid_size: size of EDID data on this port.
+ */
+struct dp_mst_sim_port {
+	bool input;
+	bool mcs;
+	bool ddps;
+	u8 pdt;
+	bool ldps;
+	u8 dpcd_rev;
+	u8 peer_guid[16];
+	u8 num_sdp_streams;
+	u8 num_sdp_stream_sinks;
+	u16 full_pbn;
+	u16 avail_pbn;
+	const u8 *edid;
+	u32 edid_size;
+};
+
+/**
+ * struct dp_mst_sim_cfg - MST simulator configuration
+ * @host_dev: host device pointer used in callback functions
+ * @guid: GUID of the top MST branch.
+ */
+struct dp_mst_sim_cfg {
+	void *host_dev;
+	u8 guid[16];
+
+	/**
+	 * @host_hpd_irq:
+	 *
+	 * This callback is invoked whenever simulator need to
+	 * notify host that there is a HPD_IRQ.
+	 * @host_dev: host_dev pointer
+	 */
+	void (*host_hpd_irq)(void *host_dev);
+
+	/**
+	 * @host_req:
+	 *
+	 * This callback is invoked whenever simulator's reply is ready
+	 * to response downstream request. Host can use this function
+	 * to replace the reply generated by simulator.
+	 * @host_dev: host_dev pointer
+	 * @in: pointer of downstream request buffer to simulator
+	 * @in_size: size of downstream request buffer to simulator
+	 * @out: pointer of downstream reply from simulator
+	 * @out_size: pointer of size of downstream reply from simulator
+	 *
+	 * This callback is optional.
+	 */
+	void (*host_req)(void *host_dev, const u8 *in, int in_size,
+			u8 *out, int *out_size);
+};
+
+/**
+ * dp_mst_sim_create - Create simulator context
+ * @cfg: see dp_mst_sim_cfg
+ * @mst_sim_context: simulator context returned
+ * return: 0 if successful
+ */
+int dp_mst_sim_create(const struct dp_mst_sim_cfg *cfg,
+		void **mst_sim_context);
+
+/**
+ * dp_mst_sim_destroy - Destroy simulator context
+ * @mst_sim_context: simulator context
+ * return: 0 if successful
+ */
+int dp_mst_sim_destroy(void *mst_sim_context);
+
+/**
+ * dp_mst_sim_transfer - Send aux message to simulator context
+ * @mst_sim_context: simulator context
+ * @msg: aux message
+ * return: 0 if successful
+ */
+int dp_mst_sim_transfer(void *mst_sim_context, struct drm_dp_aux_msg *msg);
+
+/**
+ * dp_mst_sim_update - Update port configuration
+ * @mst_sim_context: simulator context
+ * @port_num: number of ports
+ * @ports: ports configuration
+ * return: 0 if successful
+ */
+int dp_mst_sim_update(void *mst_sim_context, u32 port_num,
+		struct dp_mst_sim_port *ports);
+
+#endif /* _DP_MST_SIM_HELPER_H_ */
+

+ 12 - 98
msm/dp/dp_panel.c

@@ -69,8 +69,6 @@ struct dp_panel_private {
 	struct dp_link *link;
 	struct dp_parser *parser;
 	struct dp_catalog_panel *catalog;
-	bool custom_edid;
-	bool custom_dpcd;
 	bool panel_on;
 	bool vsc_supported;
 	bool vscext_supported;
@@ -1489,11 +1487,6 @@ static int dp_panel_read_dpcd(struct dp_panel *dp_panel, bool multi_func)
 	panel->vscext_supported = false;
 	panel->vscext_chaining_supported = false;
 
-	if (panel->custom_dpcd) {
-		DP_DEBUG("skip dpcd read in debug mode\n");
-		goto skip_dpcd_read;
-	}
-
 	rlen = drm_dp_dpcd_read(drm_aux, DP_TRAINING_AUX_RD_INTERVAL, &temp, 1);
 	if (rlen != 1) {
 		DP_ERR("error reading DP_TRAINING_AUX_RD_INTERVAL\n");
@@ -1527,26 +1520,22 @@ static int dp_panel_read_dpcd(struct dp_panel *dp_panel, bool multi_func)
 	if (rlen != 1) {
 		DP_DEBUG("failed to read DPRX_FEATURE_ENUMERATION_LIST\n");
 		rx_feature = 0;
-	}
-
-skip_dpcd_read:
-	if (panel->custom_dpcd)
-		rx_feature = dp_panel->dpcd[DP_RECEIVER_CAP_SIZE + 1];
-
-	panel->vsc_supported = !!(rx_feature &
-		VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED);
-	panel->vscext_supported = !!(rx_feature & VSC_EXT_VESA_SDP_SUPPORTED);
-	panel->vscext_chaining_supported = !!(rx_feature &
-			VSC_EXT_VESA_SDP_CHAINING_SUPPORTED);
+	} else {
+		panel->vsc_supported = !!(rx_feature &
+				VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED);
+		panel->vscext_supported = !!(rx_feature &
+		 		VSC_EXT_VESA_SDP_SUPPORTED);
+		panel->vscext_chaining_supported = !!(rx_feature &
+				VSC_EXT_VESA_SDP_CHAINING_SUPPORTED);
 
-	DP_DEBUG("vsc=%d, vscext=%d, vscext_chaining=%d\n",
-		panel->vsc_supported, panel->vscext_supported,
-		panel->vscext_chaining_supported);
+		DP_DEBUG("vsc=%d, vscext=%d, vscext_chaining=%d\n",
+				panel->vsc_supported, panel->vscext_supported,
+				panel->vscext_chaining_supported);
+	}
 
 	link_info->revision = dpcd[DP_DPCD_REV];
 	panel->major = (link_info->revision >> 4) & 0x0f;
 	panel->minor = link_info->revision & 0x0f;
-
 	/* override link params updated in dp_panel_init_panel_info */
 	link_info->rate = min_t(unsigned long, panel->parser->max_lclk_khz,
 			drm_dp_bw_code_to_link_rate(dpcd[DP_MAX_LINK_RATE]));
@@ -1617,74 +1606,6 @@ static int dp_panel_set_default_link_params(struct dp_panel *dp_panel)
 	return 0;
 }
 
-static bool dp_panel_validate_edid(struct edid *edid, size_t edid_size)
-{
-	if (!edid || (edid_size < EDID_LENGTH))
-		return false;
-
-	if (EDID_LENGTH * (edid->extensions + 1) > edid_size) {
-		DP_ERR("edid size does not match allocated.\n");
-		return false;
-	}
-
-	if (!drm_edid_is_valid(edid)) {
-		DP_ERR("invalid edid.\n");
-		return false;
-	}
-	return true;
-}
-
-static int dp_panel_set_edid(struct dp_panel *dp_panel, u8 *edid,
-		size_t edid_size)
-{
-	struct dp_panel_private *panel;
-
-	if (!dp_panel) {
-		DP_ERR("invalid input\n");
-		return -EINVAL;
-	}
-
-	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
-
-	if (edid && dp_panel_validate_edid((struct edid *)edid, edid_size)) {
-		dp_panel->edid_ctrl->edid = (struct edid *)edid;
-		panel->custom_edid = true;
-	} else {
-		panel->custom_edid = false;
-		dp_panel->edid_ctrl->edid = NULL;
-	}
-
-	DP_DEBUG("%d\n", panel->custom_edid);
-	return 0;
-}
-
-static int dp_panel_set_dpcd(struct dp_panel *dp_panel, u8 *dpcd)
-{
-	struct dp_panel_private *panel;
-	u8 *dp_dpcd;
-
-	if (!dp_panel) {
-		DP_ERR("invalid input\n");
-		return -EINVAL;
-	}
-
-	dp_dpcd = dp_panel->dpcd;
-
-	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
-
-	if (dpcd) {
-		memcpy(dp_dpcd, dpcd, DP_RECEIVER_CAP_SIZE +
-				DP_RECEIVER_EXT_CAP_SIZE + 1);
-		panel->custom_dpcd = true;
-	} else {
-		panel->custom_dpcd = false;
-	}
-
-	DP_DEBUG("%d\n", panel->custom_dpcd);
-
-	return 0;
-}
-
 static int dp_panel_read_edid(struct dp_panel *dp_panel,
 	struct drm_connector *connector)
 {
@@ -1699,11 +1620,6 @@ static int dp_panel_read_edid(struct dp_panel *dp_panel,
 
 	panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
 
-	if (panel->custom_edid) {
-		DP_DEBUG("skip edid read in debug mode\n");
-		goto end;
-	}
-
 	sde_get_edid(connector, &panel->aux->drm_aux->ddc,
 		(void **)&dp_panel->edid_ctrl);
 	if (!dp_panel->edid_ctrl->edid) {
@@ -2347,7 +2263,7 @@ static int dp_panel_deinit_panel_info(struct dp_panel *dp_panel, u32 flags)
 	shdr_if_sdp = &panel->catalog->shdr_if_sdp;
 	vsc_colorimetry = &panel->catalog->vsc_colorimetry;
 
-	if (!panel->custom_edid && dp_panel->edid_ctrl->edid)
+	if (dp_panel->edid_ctrl->edid)
 		sde_free_edid((void **)&dp_panel->edid_ctrl);
 
 	dp_panel_set_stream_info(dp_panel, DP_STREAM_MAX, 0, 0, 0, 0);
@@ -3083,8 +2999,6 @@ struct dp_panel *dp_panel_get(struct dp_panel_in *in)
 	dp_panel->get_mode_bpp = dp_panel_get_mode_bpp;
 	dp_panel->get_modes = dp_panel_get_modes;
 	dp_panel->handle_sink_request = dp_panel_handle_sink_request;
-	dp_panel->set_edid = dp_panel_set_edid;
-	dp_panel->set_dpcd = dp_panel_set_dpcd;
 	dp_panel->tpg_config = dp_panel_tpg_config;
 	dp_panel->spd_config = dp_panel_spd_config;
 	dp_panel->setup_hdr = dp_panel_setup_hdr;

+ 9 - 3
msm/dp/dp_panel.h

@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
  */
 
 #ifndef _DP_PANEL_H_
@@ -151,6 +151,14 @@ struct dp_panel {
 	bool dsc_continuous_pps;
 	bool mst_state;
 
+	/* override debug option */
+	bool mst_hide;
+	bool mode_override;
+	int hdisplay;
+	int vdisplay;
+	int vrefresh;
+	int aspect_ratio;
+
 	s64 fec_overhead_fp;
 
 	int (*init)(struct dp_panel *dp_panel);
@@ -163,8 +171,6 @@ struct dp_panel {
 	int (*get_modes)(struct dp_panel *dp_panel,
 		struct drm_connector *connector, struct dp_display_mode *mode);
 	void (*handle_sink_request)(struct dp_panel *dp_panel);
-	int (*set_edid)(struct dp_panel *dp_panel, u8 *edid, size_t edid_size);
-	int (*set_dpcd)(struct dp_panel *dp_panel, u8 *dpcd);
 	int (*setup_hdr)(struct dp_panel *dp_panel,
 		struct drm_msm_ext_hdr_metadata *hdr_meta,
 			bool dhdr_update, u64 core_clk_rate, bool flush);