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@xxxxxxxxxxxxxx> --- .../gpu/drm/panel/panel-sitronix-st7789v.c | 174 +++++++++++++++--- 1 file changed, 144 insertions(+), 30 deletions(-) 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; -- 2.37.2