[5/7] drm/panel: sitronix-st7789v: parse device tree to override timing mode

Message ID 20230314115644.3775169-6-gerald.loacker@wolfvision.net
State New
Headers
Series Add timing override to sitronix,st7789v |

Commit Message

Gerald Loacker March 14, 2023, 11:56 a.m. UTC
  Parse device tree for panel-timing and allow to override the typical
timing. This requires the timing to be defined as display_timing instead
of drm_display_mode.

Signed-off-by: Gerald Loacker <gerald.loacker@wolfvision.net>
---
 .../gpu/drm/panel/panel-sitronix-st7789v.c    | 174 +++++++++++++++---
 1 file changed, 144 insertions(+), 30 deletions(-)
  

Patch

diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
index 1ca04585aff2..ebde8a70deee 100644
--- a/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7789v.c
@@ -10,6 +10,9 @@ 
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
 
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
 #include <video/mipi_display.h>
 
 #include <drm/drm_device.h>
@@ -28,6 +31,8 @@ 
 #define ST7789V_RGBCTRL_CMD		0xb1
 #define ST7789V_RGBCTRL_WO			BIT(7)
 #define ST7789V_RGBCTRL_RCM(n)			(((n) & 3) << 5)
+#define ST7789V_RGBCTRL_RCM_DE			2
+#define ST7789V_RGBCTRL_RCM_HV			3
 #define ST7789V_RGBCTRL_VSYNC_HIGH		BIT(3)
 #define ST7789V_RGBCTRL_HSYNC_HIGH		BIT(2)
 #define ST7789V_RGBCTRL_PCLK_HIGH		BIT(1)
@@ -117,6 +122,7 @@  struct st7789v {
 	struct spi_device *spi;
 	struct gpio_desc *reset;
 	struct regulator *power;
+	struct drm_display_mode override_mode;
 	enum drm_panel_orientation orientation;
 };
 
@@ -157,39 +163,84 @@  static int st7789v_write_data(struct st7789v *ctx, u8 cmd)
 	return st7789v_spi_write(ctx, ST7789V_DATA, cmd);
 }
 
-static const struct drm_display_mode default_mode = {
-	.clock = 7000,
-	.hdisplay = 240,
-	.hsync_start = 240 + 38,
-	.hsync_end = 240 + 38 + 10,
-	.htotal = 240 + 38 + 10 + 10,
-	.vdisplay = 320,
-	.vsync_start = 320 + 8,
-	.vsync_end = 320 + 8 + 4,
-	.vtotal = 320 + 8 + 4 + 4,
-	.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+static const struct display_timing st7789v_timing = {
+	.pixelclock = { 1, 7000000, 8333333 },
+	.hactive = { 240, 240, 240 },
+	.hfront_porch = { 2, 38, 500 },
+	.hback_porch = { 4, 10, 21 },
+	.hsync_len = { 2, 10, 10 },
+	.vactive = { 320, 320, 320 },
+	.vfront_porch = { 8, 8, 500 },
+	.vback_porch = { 4, 4, 117 },
+	.vsync_len = { 4, 4, 10 },
+	.flags = DISPLAY_FLAGS_HSYNC_HIGH | DISPLAY_FLAGS_VSYNC_HIGH |
+		 DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE |
+		 DISPLAY_FLAGS_SYNC_POSEDGE,
 };
 
-static int st7789v_get_modes(struct drm_panel *panel,
-			     struct drm_connector *connector)
+struct panel_desc {
+	u32 bus_format;
+	u32 bus_flags;
+};
+
+static struct panel_desc st7789v_desc = {
+	.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE,
+	.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+static unsigned int st7789v_get_timings_modes(struct drm_panel *panel,
+					      struct drm_connector *connector)
 {
-	struct st7789v *ctx = panel_to_st7789v(panel);
 	struct drm_display_mode *mode;
-	u32 bus_format = MEDIA_BUS_FMT_RGB666_1X18;
 
-	mode = drm_mode_duplicate(connector->dev, &default_mode);
+	const struct display_timing *dt = &st7789v_timing;
+	struct videomode vm;
+
+	videomode_from_timing(dt, &vm);
+	mode = drm_mode_create(connector->dev);
 	if (!mode) {
-		dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
-			default_mode.hdisplay, default_mode.vdisplay,
-			drm_mode_vrefresh(&default_mode));
-		return -ENOMEM;
+		dev_err(panel->dev, "failed to add timing %ux%ux\n",
+			dt->hactive.typ, dt->vactive.typ);
+		return 0;
 	}
 
-	drm_mode_set_name(mode);
+	drm_display_mode_from_videomode(&vm, mode);
+
+	mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 
-	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
 	drm_mode_probed_add(connector, mode);
 
+	return 1;
+}
+
+static int st7789v_get_modes(struct drm_panel *panel,
+			     struct drm_connector *connector)
+{
+	struct st7789v *ctx = panel_to_st7789v(panel);
+	struct drm_display_mode *mode;
+	bool has_override = ctx->override_mode.type;
+	u32 bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+	unsigned int num;
+
+	if (has_override) {
+		mode = drm_mode_duplicate(connector->dev, &ctx->override_mode);
+		if (!mode) {
+			dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
+				ctx->override_mode.hdisplay,
+				ctx->override_mode.vdisplay,
+				drm_mode_vrefresh(&ctx->override_mode));
+			return 0;
+		}
+
+		drm_mode_set_name(mode);
+
+		mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+		drm_mode_probed_add(connector, mode);
+		num = 1;
+	} else {
+		num = st7789v_get_timings_modes(panel, connector);
+	}
+
 	connector->display_info.width_mm = 61;
 	connector->display_info.height_mm = 103;
 
@@ -204,7 +255,7 @@  static int st7789v_get_modes(struct drm_panel *panel,
 	 */
 	drm_connector_set_panel_orientation(connector, ctx->orientation);
 
-	return 1;
+	return num;
 }
 
 static enum drm_panel_orientation st7789v_get_orientation(struct drm_panel *p)
@@ -217,6 +268,8 @@  static enum drm_panel_orientation st7789v_get_orientation(struct drm_panel *p)
 static int st7789v_prepare(struct drm_panel *panel)
 {
 	struct st7789v *ctx = panel_to_st7789v(panel);
+	struct drm_display_mode mode = ctx->override_mode;
+	u16 hbp, vbp, rcm, hsync = 0, vsync = 0, pclk = 0;
 	int ret;
 
 	ret = regulator_enable(ctx->power);
@@ -327,15 +380,29 @@  static int st7789v_prepare(struct drm_panel *panel)
 	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RAMCTRL_EPF(3) |
 					     ST7789V_RAMCTRL_MAGIC));
 
+	if ((st7789v_desc.bus_flags & DRM_BUS_FLAG_DE_HIGH) ||
+	    (st7789v_desc.bus_flags & DRM_BUS_FLAG_DE_LOW))
+		rcm = ST7789V_RGBCTRL_RCM_DE;
+	else
+		rcm = ST7789V_RGBCTRL_RCM_HV;
+
+	if (st7789v_desc.bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE)
+		pclk = ST7789V_RGBCTRL_PCLK_HIGH;
+
+	if (mode.flags & DRM_MODE_FLAG_PHSYNC)
+		hsync = ST7789V_RGBCTRL_HSYNC_HIGH;
+
+	if (mode.flags & DRM_MODE_FLAG_PVSYNC)
+		vsync = ST7789V_RGBCTRL_VSYNC_HIGH;
+
+	vbp = mode.vtotal - mode.vsync_start;
+	hbp = mode.htotal - mode.hsync_start;
 	ST7789V_TEST(ret, st7789v_write_command(ctx, ST7789V_RGBCTRL_CMD));
 	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_WO |
-					     ST7789V_RGBCTRL_RCM(2) |
-					     ST7789V_RGBCTRL_VSYNC_HIGH |
-					     ST7789V_RGBCTRL_HSYNC_HIGH |
-					     ST7789V_RGBCTRL_PCLK_HIGH));
-	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_VBP(8)));
-	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_HBP(20)));
-
+					     ST7789V_RGBCTRL_RCM(rcm) |
+					     vsync | hsync | pclk));
+	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_VBP(vbp)));
+	ST7789V_TEST(ret, st7789v_write_data(ctx, ST7789V_RGBCTRL_HBP(hbp)));
 	return 0;
 }
 
@@ -377,8 +444,50 @@  static const struct drm_panel_funcs st7789v_drm_funcs = {
 	.unprepare = st7789v_unprepare,
 };
 
+static void st7789v_set_videomode(struct device *dev, struct st7789v *ctx,
+				  const struct display_timing *dt)
+{
+	struct videomode vm;
+
+	videomode_from_timing(dt, &vm);
+	drm_display_mode_from_videomode(&vm, &ctx->override_mode);
+	ctx->override_mode.type |= DRM_MODE_TYPE_DRIVER |
+				   DRM_MODE_TYPE_PREFERRED;
+
+	drm_bus_flags_from_videomode(&vm, &st7789v_desc.bus_flags);
+}
+
+#define ST7789V_BOUNDS_CHECK(to_check, bounds, field) \
+	(to_check->field.typ >= bounds->field.min &&  \
+	 to_check->field.typ <= bounds->field.max)
+static void st7789v_parse_panel_timing_node(struct device *dev,
+					    struct st7789v *ctx,
+					    const struct display_timing *ot)
+{
+	const struct display_timing *dt;
+	bool ot_timing_valid = true;
+
+	dt = &st7789v_timing;
+
+	if (!ST7789V_BOUNDS_CHECK(ot, dt, hactive) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, hfront_porch) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, hback_porch) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, hsync_len) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, vactive) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, vfront_porch) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, vback_porch) ||
+	    !ST7789V_BOUNDS_CHECK(ot, dt, vsync_len))
+		ot_timing_valid = false;
+
+	if (ot_timing_valid)
+		dt = ot;
+
+	st7789v_set_videomode(dev, ctx, dt);
+}
+
 static int st7789v_probe(struct spi_device *spi)
 {
+	struct display_timing dt;
 	struct st7789v *ctx;
 	int ret;
 
@@ -408,6 +517,11 @@  static int st7789v_probe(struct spi_device *spi)
 
 	of_drm_get_panel_orientation(spi->dev.of_node, &ctx->orientation);
 
+	if (!of_get_display_timing(spi->dev.of_node, "panel-timing", &dt))
+		st7789v_parse_panel_timing_node(&spi->dev, ctx, &dt);
+	else
+		st7789v_set_videomode(&spi->dev, ctx, &st7789v_timing);
+
 	drm_panel_add(&ctx->panel);
 
 	return 0;