summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomi Valkeinen <tomi.valkeinen@ideasonboard.com>2023-06-16 12:02:42 +0300
committerFrancesco Dolcini <francesco.dolcini@toradex.com>2023-06-19 15:54:30 +0200
commit976881c4059ff675a9ed9447a06ae15d6b93f13b (patch)
tree06cbc388ebb720916c769b28078ba908f8e66884
parent80b2ca52d8237aaddeb5725b3e51c030ac6512d1 (diff)
drm/bridge: tc358768: Attempt to fix DSI horizontal timings
The DSI horizontal timing calculations done by the driver seem to often lead to underflows or overflows, depending on the videomode. There are two main things the current driver doesn't seem to get right: DSI HSW and HFP, and VSDly. However, even following Toshiba's documentation it seems we don't always get a working display. This patch attems to fix the horizontal timings for DSI event mode, and on a system with a DSI->HDMI encoder, a lot of standard HDMI modes now seem to work. The work relies on Toshiba's documentation, but also quite a bit on empirical testing. Also add quite a bit timing related debug prints to make it easier to improve on this later. I am not able to test pulse mode, and this patch doesn't attempt to fix it. However, the VSDly calculation also affects pulse mode, so this might cause a regression. Upstream-Status: Pending Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> Signed-off-by: Francesco Dolcini <francesco.dolcini@toradex.com>
-rw-r--r--drivers/gpu/drm/bridge/tc358768.c211
1 files changed, 183 insertions, 28 deletions
diff --git a/drivers/gpu/drm/bridge/tc358768.c b/drivers/gpu/drm/bridge/tc358768.c
index 0afb17c42ce2..9052a75aa70b 100644
--- a/drivers/gpu/drm/bridge/tc358768.c
+++ b/drivers/gpu/drm/bridge/tc358768.c
@@ -9,6 +9,7 @@
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/media-bus-format.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -158,6 +159,7 @@ struct tc358768_priv {
u32 frs; /* PLL Freqency range for HSCK (post divider) */
u32 dsiclk; /* pll_clk / 2 */
+ u32 pclk; /* incoming pclk rate */
};
static inline struct tc358768_priv *dsi_host_to_tc358768(struct mipi_dsi_host
@@ -377,6 +379,7 @@ found:
priv->prd = best_prd;
priv->frs = frs;
priv->dsiclk = best_pll / 2;
+ priv->pclk = mode->clock * 1000;
return 0;
}
@@ -635,6 +638,28 @@ static u32 tc358768_ps_to_ns(u32 ps)
return ps / 1000;
}
+static u32 tc358768_dpi_to_ns(u32 val, u32 pclk)
+{
+ return (u32)div_u64((u64)val * NANO, pclk);
+}
+
+/* Convert value in DPI pixel clock units to DSI byte count */
+static u32 tc358768_dpi_to_dsi_bytes(struct tc358768_priv *priv, u32 val)
+{
+ u64 m = (u64)val * priv->dsiclk / 4 * priv->dsi_lanes;
+ u64 n = priv->pclk;
+
+ return (u32)div_u64(m + n - 1, n);
+}
+
+static u32 tc358768_dsi_bytes_to_ns(struct tc358768_priv *priv, u32 val)
+{
+ u64 m = (u64)val * NANO;
+ u64 n = priv->dsiclk / 4 * priv->dsi_lanes;
+
+ return (u32)div_u64(m, n);
+}
+
static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
{
struct tc358768_priv *priv = bridge_to_tc358768(bridge);
@@ -644,11 +669,19 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
s32 raw_val;
const struct drm_display_mode *mode;
u32 hsbyteclk_ps, dsiclk_ps, ui_ps;
- u32 dsiclk, hsbyteclk, video_start;
- const u32 internal_delay = 40;
+ u32 dsiclk, hsbyteclk;
int ret, i;
struct videomode vm;
struct device *dev = priv->dev;
+ /* In pixelclock units */
+ u32 dpi_htot, dpi_data_start;
+ /* In byte units */
+ u32 dsi_dpi_htot, dsi_dpi_data_start;
+ u32 dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp;
+ const u32 dsi_hss = 4; /* HSS is a short packet (4 bytes) */
+ /* In hsbyteclk units */
+ u32 dsi_vsdly;
+ const u32 internal_dly = 40;
if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
dev_warn_once(dev, "Non-continuous mode unimplemented, falling back to continuous\n");
@@ -683,27 +716,23 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
case MIPI_DSI_FMT_RGB888:
val |= (0x3 << 4);
hact = vm.hactive * 3;
- video_start = (vm.hsync_len + vm.hback_porch) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
break;
case MIPI_DSI_FMT_RGB666:
val |= (0x4 << 4);
hact = vm.hactive * 3;
- video_start = (vm.hsync_len + vm.hback_porch) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
break;
case MIPI_DSI_FMT_RGB666_PACKED:
val |= (0x4 << 4) | BIT(3);
hact = vm.hactive * 18 / 8;
- video_start = (vm.hsync_len + vm.hback_porch) * 18 / 8;
data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
break;
case MIPI_DSI_FMT_RGB565:
val |= (0x5 << 4);
hact = vm.hactive * 2;
- video_start = (vm.hsync_len + vm.hback_porch) * 2;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
break;
default:
@@ -713,9 +742,150 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
return;
}
+ /*
+ * There are three important things to make TC358768 work correctly,
+ * which are not trivial to manage:
+ *
+ * 1. Keep the DPI line-time and the DSI line-time as close to each
+ * other as possible.
+ * 2. TC358768 goes to LP mode after each line's active area. The DSI
+ * HFP period has to be long enough for entering and exiting LP mode.
+ * But it is not clear how to calculate this.
+ * 3. VSDly (video start delay) has to be long enough to ensure that the
+ * DSI TX does not start transmitting util we have started receiving
+ * pixel data from the DPI input. It is not clear how to calculate
+ * this either.
+ */
+
+ dpi_htot = vm.hactive + vm.hfront_porch + vm.hsync_len + vm.hback_porch;
+ dpi_data_start = vm.hsync_len + vm.hback_porch;
+
+ dev_dbg(dev, "dpi horiz timing (pclk): %u + %u + %u + %u = %u\n",
+ vm.hsync_len, vm.hback_porch, vm.hactive, vm.hfront_porch,
+ dpi_htot);
+
+ dev_dbg(dev, "dpi horiz timing (ns): %u + %u + %u + %u = %u\n",
+ tc358768_dpi_to_ns(vm.hsync_len, vm.pixelclock),
+ tc358768_dpi_to_ns(vm.hback_porch, vm.pixelclock),
+ tc358768_dpi_to_ns(vm.hactive, vm.pixelclock),
+ tc358768_dpi_to_ns(vm.hfront_porch, vm.pixelclock),
+ tc358768_dpi_to_ns(dpi_htot, vm.pixelclock));
+
+ dev_dbg(dev, "dpi data start (ns): %u + %u = %u\n",
+ tc358768_dpi_to_ns(vm.hsync_len, vm.pixelclock),
+ tc358768_dpi_to_ns(vm.hback_porch, vm.pixelclock),
+ tc358768_dpi_to_ns(dpi_data_start, vm.pixelclock));
+
+ dsi_dpi_htot = tc358768_dpi_to_dsi_bytes(priv, dpi_htot);
+ dsi_dpi_data_start = tc358768_dpi_to_dsi_bytes(priv, dpi_data_start);
+
+ if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ dsi_hsw = tc358768_dpi_to_dsi_bytes(priv, vm.hsync_len);
+ dsi_hbp = tc358768_dpi_to_dsi_bytes(priv, vm.hback_porch);
+ } else {
+ /* HBP is included in HSW in event mode */
+ dsi_hbp = 0;
+ dsi_hsw = tc358768_dpi_to_dsi_bytes(priv,
+ vm.hsync_len + vm.hback_porch);
+
+ /*
+ * The pixel packet includes the actual pixel data, and:
+ * DSI packet header = 4 bytes
+ * DCS code = 1 byte
+ * DSI packet footer = 2 bytes
+ */
+ dsi_hact = hact + 4 + 1 + 2;
+
+ dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+ /*
+ * Here we should check if HFP is long enough for entering LP
+ * and exiting LP, but it's not clear how to calculate that.
+ * Instead, this is a naive algorithm that just adjusts the HFP
+ * and HSW so that HFP is (at least) roughly 2/3 of the total
+ * blanking time.
+ */
+ if (dsi_hfp < (dsi_hfp + dsi_hsw + dsi_hss) * 2 / 3) {
+ u32 old_hfp = dsi_hfp;
+ u32 old_hsw = dsi_hsw;
+ u32 tot = dsi_hfp + dsi_hsw + dsi_hss;
+
+ dsi_hsw = tot / 3;
+
+ /*
+ * Seems like sometimes HSW has to be divisible by num-lanes, but
+ * not always...
+ */
+ dsi_hsw = roundup(dsi_hsw, priv->dsi_lanes);
+
+ dsi_hfp = dsi_dpi_htot - dsi_hact - dsi_hsw - dsi_hss;
+
+ dev_dbg(dev,
+ "hfp too short, adjusting dsi hfp and dsi hsw from %u, %u to %u, %u\n",
+ old_hfp, old_hsw, dsi_hfp, dsi_hsw);
+ }
+
+ dev_dbg(dev,
+ "dsi horiz timing (bytes): %u, %u + %u + %u + %u = %u\n",
+ dsi_hss, dsi_hsw, dsi_hbp, dsi_hact, dsi_hfp,
+ dsi_hss + dsi_hsw + dsi_hbp + dsi_hact + dsi_hfp);
+
+ dev_dbg(dev, "dsi horiz timing (ns): %u + %u + %u + %u + %u = %u\n",
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hact),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hfp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss + dsi_hsw + dsi_hbp + dsi_hact + dsi_hfp));
+ }
+
+ /* VSDly calculation */
+
+ /* Start with the HW internal delay */
+ dsi_vsdly = internal_dly;
+
+ /* Convert to byte units as the other variables are in byte units */
+ dsi_vsdly *= priv->dsi_lanes;
+
+ /* Do we need more delay, in addition to the internal? */
+ if (dsi_dpi_data_start > dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp) {
+ dsi_vsdly = dsi_dpi_data_start - dsi_hss - dsi_hsw - dsi_hbp;
+ dsi_vsdly = roundup(dsi_vsdly, priv->dsi_lanes);
+ }
+
+ dev_dbg(dev, "dsi data start (bytes) %u + %u + %u + %u = %u\n",
+ dsi_vsdly, dsi_hss, dsi_hsw, dsi_hbp,
+ dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp);
+
+ dev_dbg(dev, "dsi data start (ns) %u + %u + %u + %u = %u\n",
+ tc358768_dsi_bytes_to_ns(priv, dsi_vsdly),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hss),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hsw),
+ tc358768_dsi_bytes_to_ns(priv, dsi_hbp),
+ tc358768_dsi_bytes_to_ns(priv, dsi_vsdly + dsi_hss + dsi_hsw + dsi_hbp));
+
+ /* Convert back to hsbyteclk */
+ dsi_vsdly /= priv->dsi_lanes;
+
+ /*
+ * The docs say that there is an internal delay of 40 cycles.
+ * However, we get underflows if we follow that rule. If we
+ * instead ignore the internal delay, things work. So either
+ * the docs are wrong or the calculations are wrong.
+ *
+ * As a temporary fix, add the internal delay here, to counter
+ * the subtraction when writing the register.
+ */
+ dsi_vsdly += internal_dly;
+
+ /* Clamp to the register max */
+ if (dsi_vsdly - internal_dly > 0x3ff) {
+ dev_warn(dev, "VSDly too high, underflows likely\n");
+ dsi_vsdly = 0x3ff + internal_dly;
+ }
+
/* VSDly[9:0] */
- video_start = max(video_start, internal_delay + 1) - internal_delay;
- tc358768_write(priv, TC358768_VSDLY, video_start);
+ tc358768_write(priv, TC358768_VSDLY, dsi_vsdly - internal_dly);
tc358768_write(priv, TC358768_DATAFMT, val);
tc358768_write(priv, TC358768_DSITX_DT, data_type);
@@ -823,18 +993,6 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
/* vbp */
tc358768_write(priv, TC358768_DSI_VBPR, vm.vback_porch);
-
- /* hsw * byteclk * ndl / pclk */
- val = (u32)div_u64(vm.hsync_len *
- (u64)hsbyteclk * priv->dsi_lanes,
- vm.pixelclock);
- tc358768_write(priv, TC358768_DSI_HSW, val);
-
- /* hbp * byteclk * ndl / pclk */
- val = (u32)div_u64(vm.hback_porch *
- (u64)hsbyteclk * priv->dsi_lanes,
- vm.pixelclock);
- tc358768_write(priv, TC358768_DSI_HBPR, val);
} else {
/* Set event mode */
tc358768_write(priv, TC358768_DSI_EVENT, 1);
@@ -848,16 +1006,13 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
/* vbp (not used in event mode) */
tc358768_write(priv, TC358768_DSI_VBPR, 0);
+ }
- /* (hsw + hbp) * byteclk * ndl / pclk */
- val = (u32)div_u64((vm.hsync_len + vm.hback_porch) *
- (u64)hsbyteclk * priv->dsi_lanes,
- vm.pixelclock);
- tc358768_write(priv, TC358768_DSI_HSW, val);
+ /* hsw (bytes) */
+ tc358768_write(priv, TC358768_DSI_HSW, dsi_hsw);
- /* hbp (not used in event mode) */
- tc358768_write(priv, TC358768_DSI_HBPR, 0);
- }
+ /* hbp (bytes) */
+ tc358768_write(priv, TC358768_DSI_HBPR, dsi_hbp);
/* hact (bytes) */
tc358768_write(priv, TC358768_DSI_HACT, hact);