Adds driver for the Jinglitai JLT4013A LCD panel used in the Xiegu X6200. This patch was written against a ~6.1-era kernel. It may require minor forward-porting for 6.6 (panel/Kconfig line offsets may have shifted as new panel drivers were added upstream). The core driver file (panel-jinglitai-jlt4013a.c) should apply cleanly. NOTE: Milestone 1 (serial console boot) does not require this patch. If the patch fails to apply, comment out BR2_LINUX_KERNEL_PATCH in the defconfig and address the LCD driver in Milestone 2. --- -- +++ my/drivers/gpu/drm/panel/Makefile 2023-01-30 03:23:34.824674091 +0300 @@ -73,3 +73,4 @@ obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o +obj-$(CONFIG_DRM_PANEL_JINGLITAI_JLT4013A) += panel-jinglitai-jlt4013a.o diff -Naur orig/drivers/gpu/drm/panel/panel-jinglitai-jlt4013a.c my/drivers/gpu/drm/panel/panel-jinglitai-jlt4013a.c --- orig/drivers/gpu/drm/panel/panel-jinglitai-jlt4013a.c 1970-01-01 03:00:00.000000000 +0300 +++ my/drivers/gpu/drm/panel/panel-jinglitai-jlt4013a.c 2023-01-30 03:22:15.521007934 +0300 @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Jinglitai JLT4013A LCD Panel. + * -- + val); \ + return val; \ + } \ + } while (0) + +static const struct of_device_id jlt4013a_of_match[] = { + { .compatible = "sitronix,st7701s" }, + { .compatible = "jinglitai,jlt4013a" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, jlt4013a_of_match); + +static const struct drm_display_mode jlt4013a_default_display_mode = { + .clock = 27500, + .hdisplay = 480, /* x */ + .hsync_start = 480 + 38, /* x + ri */ + .hsync_end = 480 + 38 + 12, /* x + ri + hs */ + .htotal = 480 + 38 + 12 + 12, /* x + ri + hs + le */ -- + .width_mm = 52, + .height_mm = 86, +}; + + +struct jlt4013a { + struct drm_panel panel; + struct spi_device *spi; + struct gpio_desc *reset; + struct gpio_desc *dcx; + struct regulator *supply; +}; + +static int st7701s_spi_write(struct jlt4013a *ctx, u8 data) +{ + struct spi_transfer xfer = {}; + struct spi_message msg; + + spi_message_init(&msg); -- + + spi_message_add_tail(&xfer, &msg); + return spi_sync(ctx->spi, &msg); +} + +static int st7701s_write_command(struct jlt4013a *ctx, u8 cmd) +{ + // pr_info("Jinglitai JLT4013A: Writing SPI command with value %x\n", cmd); + + gpiod_set_value(ctx->dcx, 0); + + return st7701s_spi_write(ctx, cmd); +} + +static int st7701s_write_data(struct jlt4013a *ctx, u8 cmd) +{ + // pr_info("Jinglitai JLT4013A: Writing SPI data with value %x\n", cmd); + + gpiod_set_value(ctx->dcx, 1); + + return st7701s_spi_write(ctx, cmd); +} + +static inline struct jlt4013a *panel_to_jlt4013a(struct drm_panel *panel) +{ + return container_of(panel, struct jlt4013a, panel); +} + +static int jlt4013a_prepare(struct drm_panel *panel) +{ + int ret; + struct jlt4013a *ctx = panel_to_jlt4013a(panel); + + /* Enable power supply */ + + pr_info("Jinglitai JLT4013A: Trying to enable power supply\n"); + ret = regulator_enable(ctx->supply); -- + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x00)); + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x10)); + + ST7701S_TRY(ret, st7701s_write_command(ctx, ST7701S_PORCTRL)); + ST7701S_TRY(ret, st7701s_write_data(ctx, + jlt4013a_default_display_mode.vtotal + - jlt4013a_default_display_mode.vsync_end)); // mode.vtotal - mode.vsync_end + ST7701S_TRY(ret, st7701s_write_data(ctx, + jlt4013a_default_display_mode.vsync_start + - jlt4013a_default_display_mode.vdisplay)); // mode.vsync_start - mode.vdisplay + + ST7701S_TRY(ret, st7701s_write_command(ctx, ST7701S_INVSET)); + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x31)); + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x03)); + + ST7701S_TRY(ret, st7701s_write_command(ctx, 0xC3)); + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x02)); // DataPolarity: negative (The data is input on the negative edge of DOTCLK) + ST7701S_TRY(ret, st7701s_write_data(ctx, + jlt4013a_default_display_mode.htotal + - jlt4013a_default_display_mode.hsync_end)); // HBP = mode.htotal - mode.hsync_end + ST7701S_TRY(ret, st7701s_write_data(ctx, + jlt4013a_default_display_mode.vsync_start + - jlt4013a_default_display_mode.vdisplay)); // VBP = mode.vsync_start - mode.vdisplay + + /* Something strange */ + + ST7701S_TRY(ret, st7701s_write_command(ctx, 0xCC)); + ST7701S_TRY(ret, st7701s_write_data(ctx, 0x10)); -- + + pr_info("Jinglitai JLT4013A: Panel is initialized\n"); + return ret; +} + +static int jlt4013a_unprepare(struct drm_panel *panel) +{ + struct jlt4013a *ctx = panel_to_jlt4013a(panel); + + int ret = regulator_disable(ctx->supply); + return ret; +} + + +static int jlt4013a_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24; + static const u32 bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE; + + + struct drm_display_mode *mode = drm_mode_duplicate( + connector->dev, &jlt4013a_default_display_mode); + if (mode == NULL) { + dev_err(panel->dev, + "Jinglitai JLT4013A: Failed to add default mode\n"); + return -EAGAIN; + } -- + 1); + + return 1; +} + +static int jlt4013a_enable(struct drm_panel *panel) +{ + return 0; +} + +static int jlt4013a_disable(struct drm_panel *panel) +{ + return 0; +} + +static const struct drm_panel_funcs jlt4013afuncs = { + .prepare = jlt4013a_prepare, + .unprepare = jlt4013a_unprepare, + .get_modes = jlt4013a_get_modes, + .enable = jlt4013a_enable, + .disable = jlt4013a_disable, +}; + +static int jlt4013a_probe(struct spi_device *spi) +{ + int err; + + struct device *dev = &spi->dev; + + struct jlt4013a *ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (ctx == NULL) { + return -EAGAIN; + } + + ctx->spi = spi; -- + if (IS_ERR(ctx->dcx)) { + dev_err(dev, "Jinglitai JLT4013A: Failed to get dcx GPIO\n"); + return PTR_ERR(ctx->dcx); + } + + drm_panel_init(&ctx->panel, dev, &jlt4013afuncs, + DRM_MODE_CONNECTOR_DPI); + + err = drm_panel_of_backlight(&ctx->panel); + if (err) + return err; -- + + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,0,0) +static void jlt4013a_remove(struct spi_device *spi) +{ + struct jlt4013a *ctx = spi_get_drvdata(spi); + + drm_panel_remove(&(ctx->panel)); +} +#else +static int jlt4013a_remove(struct spi_device *spi) +{ + struct jlt4013a *ctx = spi_get_drvdata(spi); + + drm_panel_remove(&(ctx->panel)); + return 0; +} +#endif + +static struct spi_driver jlt4013a_driver = { + .probe = jlt4013a_probe, + .remove = jlt4013a_remove, + .driver = { + .name = "jlt4013a", + .of_match_table = jlt4013a_of_match, + }, +}; +module_spi_driver(jlt4013a_driver); + +MODULE_AUTHOR("Rui Oliveira "); +MODULE_AUTHOR("Oleg Belousov "); +MODULE_DESCRIPTION("Driver for the Jinglitai JLT4013A LCD Panel"); +MODULE_LICENSE("GPL v2");