@@ -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;