devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Robert Chiras (OSS)" <robert.chiras@oss.nxp.com>
To: "Rob Herring" <robh+dt@kernel.org>,
	"Andrzej Hajda" <a.hajda@samsung.com>,
	"Neil Armstrong" <narmstrong@baylibre.com>,
	"Laurent Pinchart" <Laurent.pinchart@ideasonboard.com>,
	"Jonas Karlman" <jonas@kwiboo.se>,
	"Jernej Skrabec" <jernej.skrabec@siol.net>,
	"Sam Ravnborg" <sam@ravnborg.org>,
	"Guido Günther" <agx@sigxcpu.org>,
	"Fabio Estevam" <festevam@gmail.com>,
	"Ondrej Jirman" <megous@megous.com>
Cc: David Airlie <airlied@linux.ie>, Daniel Vetter <daniel@ffwll.ch>,
	dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-imx@nxp.com
Subject: [PATCH 1/5] drm/bridge: nwl-dsi: Add support for video_pll
Date: Fri, 28 Aug 2020 14:13:28 +0300	[thread overview]
Message-ID: <1598613212-1113-2-git-send-email-robert.chiras@oss.nxp.com> (raw)
In-Reply-To: <1598613212-1113-1-git-send-email-robert.chiras@oss.nxp.com>

From: Robert Chiras <robert.chiras@nxp.com>

This patch adds support for a new clock 'video_pll' in order to better
set the video_pll clock to a clock-rate that satisfies a mode's clock.
The video PLL, on i.MX8MQ, can drive both DC pixel-clock and DSI phy_ref
clock. When used with a bridge that can drive multiple modes, like a DSI
to HDMI bridge, the DSI can't be statically configured for a specific
mode in the DTS file.
So, this patch adds access to the video PLL inside the DSI driver, so
that modes can be filtered-out if the required combination of phy_ref
and pixel-clock can't be achieved using the video PLL.

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
---
 drivers/gpu/drm/bridge/nwl-dsi.c | 318 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 313 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index ce94f79..1228466 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -39,6 +39,20 @@
 
 #define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
 
+/* Maximum Video PLL frequency: 1.2GHz */
+#define MAX_PLL_FREQ 1200000000
+
+#define MBPS(x) ((x) * 1000000)
+#define MIN_PHY_RATE MBPS(24)
+#define MAX_PHY_RATE MBPS(30)
+
+/* Possible valid PHY reference clock rates*/
+static u32 phyref_rates[] = {
+	27000000,
+	25000000,
+	24000000,
+};
+
 enum transfer_direction {
 	DSI_PACKET_SEND,
 	DSI_PACKET_RECEIVE,
@@ -67,6 +81,17 @@ struct nwl_dsi_transfer {
 	size_t rx_len; /* in bytes */
 };
 
+struct mode_config {
+	int				clock;
+	int				crtc_clock;
+	unsigned int			lanes;
+	unsigned long			bitclock;
+	unsigned long			phy_rates[3];
+	unsigned long			pll_rates[3];
+	int				phy_rate_idx;
+	struct list_head		list;
+};
+
 struct nwl_dsi {
 	struct drm_bridge bridge;
 	struct mipi_dsi_host dsi_host;
@@ -101,6 +126,7 @@ struct nwl_dsi {
 	struct clk *rx_esc_clk;
 	struct clk *tx_esc_clk;
 	struct clk *core_clk;
+	struct clk *pll_clk;
 	/*
 	 * hardware bug: the i.MX8MQ needs this clock on during reset
 	 * even when not using LCDIF.
@@ -115,6 +141,7 @@ struct nwl_dsi {
 	int error;
 
 	struct nwl_dsi_transfer *xfer;
+	struct list_head valid_modes;
 };
 
 static const struct regmap_config nwl_dsi_regmap_config = {
@@ -778,6 +805,207 @@ static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
 	pm_runtime_put(dsi->dev);
 }
 
+static unsigned long nwl_dsi_get_bit_clock(struct nwl_dsi *dsi,
+		unsigned long pixclock, u32 lanes)
+{
+	int bpp;
+
+	if (lanes < 1 || lanes > 4)
+		return 0;
+
+	bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+
+	return (pixclock * bpp) / lanes;
+}
+
+/*
+ * Utility function to calculate least commom multiple, using an improved
+ * version of the Euclidean algorithm for greatest common factor.
+ */
+static unsigned long nwl_dsi_get_lcm(unsigned long a, unsigned long b)
+{
+	u32 gcf = 0; /* greatest common factor */
+	unsigned long tmp_a = a;
+	unsigned long tmp_b = b;
+
+	if (!a || !b)
+		return 0;
+
+	while (tmp_a % tmp_b) {
+		gcf = tmp_a % tmp_b;
+		tmp_a = tmp_b;
+		tmp_b = gcf;
+	}
+
+	if (!gcf)
+		return a;
+
+	return ((unsigned long long)a * b) / gcf;
+}
+
+/*
+ * This function tries to adjust the crtc_clock for a DSI device in such a way
+ * that the video pll will be able to satisfy both Display Controller pixel
+ * clock (feeding out DPI interface) and our input phy_ref clock.
+ */
+static void nwl_dsi_setup_pll_config(struct mode_config *config)
+{
+	unsigned long pll_rate;
+	int div;
+	size_t i, num_rates = ARRAY_SIZE(config->phy_rates);
+
+	config->crtc_clock = 0;
+
+	for (i = 0; i < num_rates; i++) {
+		int crtc_clock;
+
+		if (!config->phy_rates[i])
+			break;
+		/*
+		 * First, we need to check if phy_ref can actually be obtained
+		 * from pixel clock. To do this, we check their lowest common
+		 * multiple, which has to be in PLL range.
+		 */
+		pll_rate = nwl_dsi_get_lcm(config->clock, config->phy_rates[i]);
+		if (pll_rate > MAX_PLL_FREQ) {
+			/* Drop pll_rate to a realistic value */
+			while (pll_rate > MAX_PLL_FREQ)
+				pll_rate >>= 1;
+			/* Make sure pll_rate can provide phy_ref rate */
+			div = DIV_ROUND_UP(pll_rate, config->phy_rates[i]);
+			pll_rate = config->phy_rates[i] * div;
+		} else {
+			/*
+			 * Increase the pll rate to highest possible rate for
+			 * better accuracy.
+			 */
+			while (pll_rate <= MAX_PLL_FREQ)
+				pll_rate <<= 1;
+			pll_rate >>= 1;
+		}
+
+		/*
+		 * Next, we need to tweak the pll_rate to a value that can also
+		 * satisfy the crtc_clock.
+		 */
+		div = DIV_ROUND_CLOSEST(pll_rate, config->clock);
+		if (lvl)
+			pll_rate -= config->phy_rates[i] * lvl;
+		crtc_clock = pll_rate / div;
+		config->pll_rates[i] = pll_rate;
+
+		/*
+		 * Pick a crtc_clock which is closest to pixel clock.
+		 * Also, make sure that the pixel clock is a multiply of
+		 * 50Hz.
+		 */
+		if (!(crtc_clock % 50) &&
+		    abs(config->clock - crtc_clock) <
+		    abs(config->clock - config->crtc_clock)) {
+			config->crtc_clock = crtc_clock;
+			config->phy_rate_idx = i;
+		}
+	}
+}
+
+
+/*
+ * This function will try the required phy speed for current mode
+ * If the phy speed can be achieved, the phy will save the speed
+ * configuration
+ */
+static struct mode_config *nwl_dsi_mode_probe(struct nwl_dsi *dsi,
+			    const struct drm_display_mode *mode)
+{
+	struct device *dev = dsi->dev;
+	struct mode_config *config;
+	union phy_configure_opts phy_opts;
+	unsigned long clock = mode->clock * 1000;
+	unsigned long bit_clk = 0;
+	unsigned long phy_rates[3] = {0};
+	int match_rates = 0;
+	u32 lanes = dsi->lanes;
+	size_t i = 0, num_rates = ARRAY_SIZE(phyref_rates);
+
+	list_for_each_entry(config, &dsi->valid_modes, list)
+		if (config->clock == clock)
+			return config;
+
+	phy_mipi_dphy_get_default_config(clock,
+			mipi_dsi_pixel_format_to_bpp(dsi->format),
+			lanes, &phy_opts.mipi_dphy);
+	phy_opts.mipi_dphy.lp_clk_rate = clk_get_rate(dsi->tx_esc_clk);
+
+	while (i < num_rates) {
+		int ret;
+
+		bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
+
+		clk_set_rate(dsi->pll_clk, phyref_rates[i] * 32);
+		clk_set_rate(dsi->phy_ref_clk, phyref_rates[i]);
+		ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, &phy_opts);
+
+		/* Pick the non-failing rate, and search for more */
+		if (!ret) {
+			phy_rates[match_rates++] = phyref_rates[i++];
+			continue;
+		}
+
+		if (match_rates)
+			break;
+
+		/* Reached the end of phyref_rates, try another lane config */
+		if ((i++ == num_rates - 1) && (--lanes > 2)) {
+			i = 0;
+			continue;
+		}
+	}
+
+	/*
+	 * Try swinging between min and max pll rates and see what rate (in terms
+	 * of kHz) we can custom use to get the required bit-clock.
+	 */
+	if (!match_rates) {
+		int min_div, max_div;
+		int bit_clk_khz;
+
+		lanes = dsi->lanes;
+		bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
+
+		min_div = DIV_ROUND_UP(bit_clk, MAX_PHY_RATE);
+		max_div = DIV_ROUND_DOWN_ULL(bit_clk, MIN_PHY_RATE);
+		bit_clk_khz = bit_clk / 1000;
+
+		for (i = max_div; i > min_div; i--) {
+			if (!(bit_clk_khz % i)) {
+				phy_rates[0] = bit_clk / i;
+				match_rates = 1;
+				break;
+			}
+		}
+	}
+
+	if (!match_rates) {
+		DRM_DEV_DEBUG_DRIVER(dev,
+			"Cannot setup PHY for mode: %ux%u @%d kHz\n",
+			mode->hdisplay,
+			mode->vdisplay,
+			mode->clock);
+
+		return NULL;
+	}
+
+	config = devm_kzalloc(dsi->dev, sizeof(struct mode_config), GFP_KERNEL);
+	config->clock = clock;
+	config->lanes = lanes;
+	config->bitclock = bit_clk;
+	memcpy(&config->phy_rates, &phy_rates, sizeof(phy_rates));
+	list_add(&config->list, &dsi->valid_modes);
+
+	return config;
+}
+
+
 static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
 				   const struct drm_display_mode *mode,
 				   union phy_configure_opts *phy_opts)
@@ -809,6 +1037,38 @@ static bool nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
 				      const struct drm_display_mode *mode,
 				      struct drm_display_mode *adjusted_mode)
 {
+	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
+	struct mode_config *config;
+	unsigned long pll_rate;
+
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "Fixup mode:\n");
+	drm_mode_debug_printmodeline(adjusted_mode);
+
+	config = nwl_dsi_mode_probe(dsi, adjusted_mode);
+	if (!config)
+		return false;
+
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "lanes=%u, data_rate=%lu\n",
+			     config->lanes, config->bitclock);
+	if (config->lanes < 2 || config->lanes > 4)
+		return false;
+
+	/* Max data rate for this controller is 1.5Gbps */
+	if (config->bitclock > 1500000000)
+		return false;
+
+	pll_rate = config->pll_rates[config->phy_rate_idx];
+	if (dsi->pll_clk && pll_rate) {
+		clk_set_rate(dsi->pll_clk, pll_rate);
+		DRM_DEV_DEBUG_DRIVER(dsi->dev,
+				     "Video pll rate: %lu (actual: %lu)",
+				     pll_rate, clk_get_rate(dsi->pll_clk));
+	}
+	/* Update the crtc_clock to be used by display controller */
+	if (config->crtc_clock)
+		adjusted_mode->crtc_clock = config->crtc_clock / 1000;
+
+
 	/* At least LCDIF + NWL needs active high sync */
 	adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
 	adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
@@ -822,14 +1082,29 @@ nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
 			  const struct drm_display_mode *mode)
 {
 	struct nwl_dsi *dsi = bridge_to_dsi(bridge);
-	int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+	struct mode_config *config;
+	unsigned long pll_rate;
+	int bit_rate;
 
-	if (mode->clock * bpp > 15000000 * dsi->lanes)
+	bit_rate = nwl_dsi_get_bit_clock(dsi, mode->clock * 1000, dsi->lanes);
+
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "Validating mode:");
+	drm_mode_debug_printmodeline(mode);
+
+	if (bit_rate > MBPS(1500))
 		return MODE_CLOCK_HIGH;
 
-	if (mode->clock * bpp < 80000 * dsi->lanes)
+	if (bit_rate < MBPS(80))
 		return MODE_CLOCK_LOW;
 
+	config = nwl_dsi_mode_probe(dsi, mode);
+	if (!config)
+		return MODE_NOCLOCK;
+
+	pll_rate = config->pll_rates[config->phy_rate_idx];
+	if (dsi->pll_clk && !pll_rate)
+		nwl_dsi_setup_pll_config(config);
+
 	return MODE_OK;
 }
 
@@ -842,8 +1117,22 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
 	struct device *dev = dsi->dev;
 	union phy_configure_opts new_cfg;
 	unsigned long phy_ref_rate;
+	struct mode_config *config;
 	int ret;
 
+	DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setting mode:\n");
+	drm_mode_debug_printmodeline(adjusted_mode);
+
+	config = nwl_dsi_mode_probe(dsi, adjusted_mode);
+	/* New mode? This should NOT happen */
+	if (!config) {
+		DRM_DEV_ERROR(dsi->dev, "Unsupported mode provided:\n");
+		drm_mode_debug_printmodeline(adjusted_mode);
+		return;
+	}
+
+	phy_ref_rate = config->phy_rates[config->phy_rate_idx];
+	clk_set_rate(dsi->phy_ref_clk, phy_ref_rate);
 	ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
 	if (ret < 0)
 		return;
@@ -855,8 +1144,10 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
 	if (new_cfg.mipi_dphy.hs_clk_rate == dsi->phy_cfg.mipi_dphy.hs_clk_rate)
 		return;
 
-	phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
-	DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
+	DRM_DEV_DEBUG_DRIVER(dev,
+			     "PHY at ref rate: %lu (actual: %lu)\n",
+			     phy_ref_rate, clk_get_rate(dsi->phy_ref_clk));
+
 	/* Save the new desired phy config */
 	memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
 
@@ -1014,6 +1305,12 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
 	}
 	dsi->tx_esc_clk = clk;
 
+	/* The video_pll clock is optional */
+	clk = devm_clk_get(dsi->dev, "video_pll");
+	if (!IS_ERR(clk))
+		dsi->pll_clk = clk;
+
+
 	dsi->mux = devm_mux_control_get(dsi->dev, NULL);
 	if (IS_ERR(dsi->mux)) {
 		ret = PTR_ERR(dsi->mux);
@@ -1066,6 +1363,9 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
 			      PTR_ERR(dsi->rst_dpi));
 		return PTR_ERR(dsi->rst_dpi);
 	}
+
+	INIT_LIST_HEAD(&dsi->valid_modes);
+
 	return 0;
 }
 
@@ -1184,6 +1484,14 @@ static int nwl_dsi_probe(struct platform_device *pdev)
 static int nwl_dsi_remove(struct platform_device *pdev)
 {
 	struct nwl_dsi *dsi = platform_get_drvdata(pdev);
+	struct mode_config *config;
+	struct list_head *pos, *tmp;
+
+	list_for_each_safe(pos, tmp, &dsi->valid_modes) {
+		config = list_entry(pos, struct mode_config, list);
+		list_del(pos);
+		devm_kfree(dsi->dev, config);
+	}
 
 	nwl_dsi_deselect_input(dsi);
 	mipi_dsi_host_unregister(&dsi->dsi_host);
-- 
2.7.4


  reply	other threads:[~2020-08-28 11:34 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-28 11:13 [PATCH 0/5] Add new features to nwl-dsi driver Robert Chiras (OSS)
2020-08-28 11:13 ` Robert Chiras (OSS) [this message]
2020-09-04 17:04   ` [PATCH 1/5] drm/bridge: nwl-dsi: Add support for video_pll Guido Günther
2020-08-28 11:13 ` [PATCH 2/5] dt-bindings: display/bridge: nwl-dsi: Document video_pll clock Robert Chiras (OSS)
2020-08-28 21:40   ` Rob Herring
2020-08-28 11:13 ` [PATCH 3/5] drm/bridge: nwl-dsi: Add support for clock-drop-level Robert Chiras (OSS)
2020-09-06 10:28   ` Guido Günther
2020-08-28 11:13 ` [PATCH 4/5] dt-bindings: display/bridge: nwl-dsi: Document fsl,clock-drop-level property Robert Chiras (OSS)
2020-09-04 17:05   ` Guido Günther
2020-09-05 16:07   ` Laurent Pinchart
2020-09-09 20:43   ` Rob Herring
2020-08-28 11:13 ` [PATCH 5/5] drm/bridge: nwl-dsi: add support for DCSS Robert Chiras (OSS)
2020-09-04 17:05   ` Guido Günther
2020-09-04 17:08 ` [PATCH 0/5] Add new features to nwl-dsi driver Guido Günther

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1598613212-1113-2-git-send-email-robert.chiras@oss.nxp.com \
    --to=robert.chiras@oss.nxp.com \
    --cc=Laurent.pinchart@ideasonboard.com \
    --cc=a.hajda@samsung.com \
    --cc=agx@sigxcpu.org \
    --cc=airlied@linux.ie \
    --cc=daniel@ffwll.ch \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=festevam@gmail.com \
    --cc=jernej.skrabec@siol.net \
    --cc=jonas@kwiboo.se \
    --cc=linux-imx@nxp.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=megous@megous.com \
    --cc=narmstrong@baylibre.com \
    --cc=robh+dt@kernel.org \
    --cc=sam@ravnborg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).