The 480x800 parallel RGB panel of the HiBy R1. It is set up over a 3-wire
SPI bus with 16-bit words, each carrying a register address byte or a
data byte, which no existing panel driver speaks. The set-up sequence is
the vendor kernel's; prepare sends it up to sleep-out, and enable and
disable switch the display on and off. It asks for BGR666: in RGB order the
R1 shows red and blue swapped.

Signed-off-by: Olivier Louvignes <[email protected]>
---
 MAINTAINERS                                |   1 +
 drivers/gpu/drm/panel/Kconfig              |  10 +
 drivers/gpu/drm/panel/Makefile             |   1 +
 drivers/gpu/drm/panel/panel-hiby-lg35583.c | 342 +++++++++++++++++++++
 4 files changed, 354 insertions(+)
 create mode 100644 drivers/gpu/drm/panel/panel-hiby-lg35583.c

diff --git a/MAINTAINERS b/MAINTAINERS
index e65500a70c9d..994847735999 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8156,6 +8156,7 @@ DRM DRIVER FOR HIBY LG35583 PANEL
 M:     Olivier Louvignes <[email protected]>
 S:     Maintained
 F:     Documentation/devicetree/bindings/display/panel/hiby,lg35583.yaml
+F:     drivers/gpu/drm/panel/panel-hiby-lg35583.c
 
 DRM DRIVER FOR HIMAX HX8394 MIPI-DSI LCD panels
 M:     Ondrej Jirman <[email protected]>
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 7a520b4d60fe..164ac349abd6 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -209,6 +209,16 @@ config DRM_PANEL_LVDS
          handling of power supplies or control signals. It implements automatic
          backlight handling if the panel is attached to a backlight controller.
 
+config DRM_PANEL_HIBY_LG35583
+       tristate "HiBy LG35583 RGB/SPI panel"
+       depends on OF && SPI
+       help
+         Say Y here if you want to enable support for the LG35583, the
+         480x800 parallel RGB panel of the HiBy R1 digital audio player,
+         which is set up over a 3-wire SPI bus.
+
+         To compile this driver as a module, choose M here.
+
 config DRM_PANEL_HIMAX_HX8279
        tristate "Himax HX8279-based panels"
        depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index da5175909d54..a2b34ed7cdba 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += 
panel-elida-kd35t133.o
 obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o
 obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += 
panel-feiyang-fy07024di26a30d.o
 obj-$(CONFIG_DRM_PANEL_FOCALTECH_OTA7290B) += panel-focaltech-ota7290b.o
+obj-$(CONFIG_DRM_PANEL_HIBY_LG35583) += panel-hiby-lg35583.o
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX8279) += panel-himax-hx8279.o
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX83102) += panel-himax-hx83102.o
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112A) += panel-himax-hx83112a.o
diff --git a/drivers/gpu/drm/panel/panel-hiby-lg35583.c 
b/drivers/gpu/drm/panel/panel-hiby-lg35583.c
new file mode 100644
index 000000000000..47aba09e91a4
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-hiby-lg35583.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The LG35583 panel of the HiBy R1
+ *
+ * Copyright (c) 2026 Olivier Louvignes <[email protected]>
+ *
+ * A 480x800 parallel RGB panel, set up over a 3-wire SPI bus with 16-bit
+ * words: 0x20 and a register's high byte, 0x00 and its low byte, then 0x40
+ * and each data byte. HiBy calls it the LG35583; its controller is not
+ * identified. The set-up sequence is the one the vendor kernel sends.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#define LG35583_ADDR_HI                0x2000
+#define LG35583_ADDR_LO                0x0000
+#define LG35583_DATA           0x4000
+
+#define LG35583_SLPIN          0x1000
+#define LG35583_SLPOUT         0x1100
+#define LG35583_DISPOFF                0x2800
+#define LG35583_DISPON         0x2900
+#define LG35583_RAMWR          0x2c00
+
+struct lg35583_write {
+       u16 reg;        /* 0: wait val milliseconds */
+       s16 val;        /* -1: no data */
+};
+
+#define REG(r, v)      { (r), (v) }
+#define CMD(r)         { (r), -1 }
+#define DELAY(ms)      { 0, (ms) }
+
+static const struct lg35583_write lg35583_init[] = {
+       REG(0xb100, 0x06), REG(0xb101, 0x01), REG(0xb102, 0x54),
+       REG(0xb104, 0x07), REG(0xb105, 0x06), REG(0xb106, 0x06),
+       REG(0xb107, 0x4a), REG(0xb108, 0x21), REG(0xb109, 0x08),
+       REG(0xf101, 0xc0), REG(0xf103, 0x01),
+       DELAY(5),
+       REG(0xb400, 0x01), REG(0xb600, 0x10),
+       REG(0x3a00, 0x77), REG(0x3b00, 0x03), REG(0x5e04, 0x02),
+       REG(0x3600, 0x08),
+       DELAY(5),
+       /* Gamma */
+       REG(0xe000, 0x00), REG(0xe001, 0x2f), REG(0xe002, 0x3f),
+       REG(0xe003, 0x1f), REG(0xe004, 0x21), REG(0xe005, 0x2c),
+       REG(0xe006, 0x2c), REG(0xe007, 0x61), REG(0xe008, 0x1c),
+       REG(0xe009, 0x18), REG(0xe00a, 0xbc), REG(0xe00b, 0x27),
+       REG(0xe00c, 0x59), REG(0xe00d, 0x6a), REG(0xe00e, 0xbb),
+       REG(0xe00f, 0xbe), REG(0xe010, 0x3e), REG(0xe011, 0x48),
+       REG(0xe100, 0x00), REG(0xe101, 0x32), REG(0xe102, 0x45),
+       REG(0xe103, 0x26), REG(0xe104, 0x21), REG(0xe105, 0x2c),
+       REG(0xe106, 0x5c), REG(0xe107, 0x63), REG(0xe108, 0x1c),
+       REG(0xe109, 0x19), REG(0xe10a, 0xd3), REG(0xe10b, 0x28),
+       REG(0xe10c, 0x59), REG(0xe10d, 0x6b), REG(0xe10e, 0xdb),
+       REG(0xe10f, 0xde), REG(0xe110, 0x5f), REG(0xe111, 0x6b),
+       REG(0xe200, 0x7f), REG(0xe201, 0x7d), REG(0xe202, 0x86),
+       REG(0xe203, 0x89), REG(0xe204, 0x10), REG(0xe205, 0x1e),
+       REG(0xe206, 0x41), REG(0xe207, 0x77), REG(0xe208, 0x0d),
+       REG(0xe209, 0x1d), REG(0xe20a, 0xc8), REG(0xe20b, 0x30),
+       REG(0xe20c, 0x5f), REG(0xe20d, 0x71), REG(0xe20e, 0xc3),
+       REG(0xe20f, 0xbe), REG(0xe210, 0x3e), REG(0xe211, 0x48),
+       REG(0xe300, 0x7f), REG(0xe301, 0x7f), REG(0xe302, 0x86),
+       REG(0xe303, 0x93), REG(0xe304, 0x10), REG(0xe305, 0x1e),
+       REG(0xe306, 0x41), REG(0xe307, 0x89), REG(0xe308, 0x0d),
+       REG(0xe309, 0x1d), REG(0xe30a, 0xe0), REG(0xe30b, 0x30),
+       REG(0xe30c, 0x5f), REG(0xe30d, 0x70), REG(0xe30e, 0xe4),
+       REG(0xe30f, 0xdf), REG(0xe310, 0x5f), REG(0xe311, 0x6b),
+       REG(0xe400, 0x00), REG(0xe401, 0x43), REG(0xe402, 0x6f),
+       REG(0xe403, 0x60), REG(0xe404, 0x21), REG(0xe405, 0x2c),
+       REG(0xe406, 0x5a), REG(0xe407, 0x6d), REG(0xe408, 0x1c),
+       REG(0xe409, 0x18), REG(0xe40a, 0xc6), REG(0xe40b, 0x27),
+       REG(0xe40c, 0x59), REG(0xe40d, 0x6a), REG(0xe40e, 0xbb),
+       REG(0xe40f, 0xbe), REG(0xe410, 0x3e), REG(0xe411, 0x48),
+       REG(0xe500, 0x00), REG(0xe501, 0x46), REG(0xe502, 0x75),
+       REG(0xe503, 0x67), REG(0xe504, 0x21), REG(0xe505, 0x2c),
+       REG(0xe506, 0x5a), REG(0xe507, 0x7e), REG(0xe508, 0x1c),
+       REG(0xe509, 0x19), REG(0xe50a, 0xdd), REG(0xe50b, 0x28),
+       REG(0xe50c, 0x59), REG(0xe50d, 0x6b), REG(0xe50e, 0xdb),
+       REG(0xe50f, 0xde), REG(0xe510, 0x5f), REG(0xe511, 0x6b),
+       DELAY(5),
+       REG(0xc200, 0x70), REG(0xc202, 0x70), REG(0xc203, 0x44),
+       DELAY(20),
+       REG(0xc200, 0x51), REG(0xc201, 0x06), REG(0xc202, 0x51),
+       REG(0xc203, 0x66),
+       DELAY(20),
+       REG(0xc100, 0x45),
+       DELAY(5),
+       REG(0xc700, 0x46), REG(0xc000, 0xa3), REG(0xc002, 0x8a),
+       DELAY(5),
+       CMD(LG35583_SLPOUT),
+       DELAY(130),
+};
+
+struct lg35583 {
+       struct drm_panel panel;
+       struct spi_device *spi;
+       struct gpio_desc *reset;
+       struct regulator *vcc;
+       struct regulator *vccio;
+};
+
+static const struct drm_display_mode lg35583_mode = {
+       .clock = 28000,
+       .hdisplay = 480,
+       .hsync_start = 480 + 24,
+       .hsync_end = 480 + 24 + 24,
+       .htotal = 480 + 24 + 24 + 24,
+       .vdisplay = 800,
+       .vsync_start = 800 + 14,
+       .vsync_end = 800 + 14 + 5,
+       .vtotal = 800 + 14 + 5 + 8,
+       .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
+       .type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static struct lg35583 *to_lg35583(struct drm_panel *panel)
+{
+       return container_of(panel, struct lg35583, panel);
+}
+
+static int lg35583_word(struct lg35583 *ctx, u16 word)
+{
+       /* Copied to a DMA-safe buffer. */
+       return spi_write_then_read(ctx->spi, &word, sizeof(word), NULL, 0);
+}
+
+static int lg35583_cmd(struct lg35583 *ctx, u16 reg)
+{
+       int ret;
+
+       ret = lg35583_word(ctx, LG35583_ADDR_HI | (reg >> 8));
+       if (ret)
+               return ret;
+       return lg35583_word(ctx, LG35583_ADDR_LO | (reg & 0xff));
+}
+
+static int lg35583_write(struct lg35583 *ctx, const struct lg35583_write *w)
+{
+       int ret;
+
+       if (!w->reg) {
+               msleep(w->val);
+               return 0;
+       }
+
+       ret = lg35583_cmd(ctx, w->reg);
+       if (ret || w->val < 0)
+               return ret;
+       return lg35583_word(ctx, LG35583_DATA | w->val);
+}
+
+static int lg35583_prepare(struct drm_panel *panel)
+{
+       struct lg35583 *ctx = to_lg35583(panel);
+       unsigned int i;
+       int ret;
+
+       ret = regulator_enable(ctx->vccio);
+       if (ret)
+               return ret;
+       usleep_range(5000, 6000);
+
+       ret = regulator_enable(ctx->vcc);
+       if (ret)
+               goto err_vccio;
+       usleep_range(5000, 6000);
+
+       gpiod_set_value_cansleep(ctx->reset, 0);
+       usleep_range(5000, 6000);
+       gpiod_set_value_cansleep(ctx->reset, 1);
+       msleep(50);
+       gpiod_set_value_cansleep(ctx->reset, 0);
+       msleep(30);
+
+       for (i = 0; i < ARRAY_SIZE(lg35583_init); i++) {
+               ret = lg35583_write(ctx, &lg35583_init[i]);
+               if (ret) {
+                       dev_err(panel->dev, "Unable to set up the panel: %d\n", 
ret);
+                       goto err_vcc;
+               }
+       }
+
+       return 0;
+
+err_vcc:
+       gpiod_set_value_cansleep(ctx->reset, 1);
+       regulator_disable(ctx->vcc);
+err_vccio:
+       regulator_disable(ctx->vccio);
+       return ret;
+}
+
+static int lg35583_enable(struct drm_panel *panel)
+{
+       struct lg35583 *ctx = to_lg35583(panel);
+       int ret;
+
+       ret = lg35583_cmd(ctx, LG35583_DISPON);
+       if (ret)
+               return ret;
+
+       return lg35583_cmd(ctx, LG35583_RAMWR);
+}
+
+static int lg35583_disable(struct drm_panel *panel)
+{
+       struct lg35583 *ctx = to_lg35583(panel);
+       int ret;
+
+       ret = lg35583_cmd(ctx, LG35583_DISPOFF);
+       usleep_range(5000, 6000);
+       return ret;
+}
+
+static int lg35583_unprepare(struct drm_panel *panel)
+{
+       struct lg35583 *ctx = to_lg35583(panel);
+
+       lg35583_cmd(ctx, LG35583_SLPIN);
+       usleep_range(5000, 6000);
+
+       gpiod_set_value_cansleep(ctx->reset, 1);
+       regulator_disable(ctx->vccio);
+       regulator_disable(ctx->vcc);
+       return 0;
+}
+
+static int lg35583_get_modes(struct drm_panel *panel, struct drm_connector 
*connector)
+{
+       /* Red and blue are swapped on the R1 without it. */
+       static const u32 bus_format = MEDIA_BUS_FMT_BGR666_1X18;
+       struct drm_display_mode *mode;
+
+       mode = drm_mode_duplicate(connector->dev, &lg35583_mode);
+       if (!mode)
+               return -ENOMEM;
+
+       drm_mode_set_name(mode);
+       drm_mode_probed_add(connector, mode);
+
+       connector->display_info.bpc = 6;
+       connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH |
+                                           DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;
+       drm_display_info_set_bus_formats(&connector->display_info, &bus_format, 
1);
+
+       return 1;
+}
+
+static const struct drm_panel_funcs lg35583_funcs = {
+       .prepare = lg35583_prepare,
+       .enable = lg35583_enable,
+       .disable = lg35583_disable,
+       .unprepare = lg35583_unprepare,
+       .get_modes = lg35583_get_modes,
+};
+
+static int lg35583_probe(struct spi_device *spi)
+{
+       struct device *dev = &spi->dev;
+       struct lg35583 *ctx;
+       int ret;
+
+       ctx = devm_drm_panel_alloc(dev, struct lg35583, panel, &lg35583_funcs,
+                                  DRM_MODE_CONNECTOR_DPI);
+       if (IS_ERR(ctx))
+               return PTR_ERR(ctx);
+
+       ctx->spi = spi;
+       spi_set_drvdata(spi, ctx);
+
+       spi->bits_per_word = 16;
+       ret = spi_setup(spi);
+       if (ret)
+               return dev_err_probe(dev, ret, "Unable to set up SPI\n");
+
+       ctx->vcc = devm_regulator_get(dev, "vcc");
+       if (IS_ERR(ctx->vcc))
+               return dev_err_probe(dev, PTR_ERR(ctx->vcc), "Unable to get 
vcc\n");
+
+       ctx->vccio = devm_regulator_get(dev, "vccio");
+       if (IS_ERR(ctx->vccio))
+               return dev_err_probe(dev, PTR_ERR(ctx->vccio), "Unable to get 
vccio\n");
+
+       /*
+        * Deasserted, which keeps a panel the boot loader left on as it is, but
+        * as an output: after a reset the pin is an input, and prepare's reset
+        * pulse would never reach the panel.
+        */
+       ctx->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(ctx->reset))
+               return dev_err_probe(dev, PTR_ERR(ctx->reset), "Unable to get 
reset\n");
+
+       ret = drm_panel_of_backlight(&ctx->panel);
+       if (ret)
+               return ret;
+
+       drm_panel_add(&ctx->panel);
+       return 0;
+}
+
+static void lg35583_remove(struct spi_device *spi)
+{
+       struct lg35583 *ctx = spi_get_drvdata(spi);
+
+       drm_panel_remove(&ctx->panel);
+}
+
+static const struct of_device_id lg35583_of_match[] = {
+       { .compatible = "hiby,lg35583" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, lg35583_of_match);
+
+static const struct spi_device_id lg35583_ids[] = {
+       { "lg35583" },
+       { }
+};
+MODULE_DEVICE_TABLE(spi, lg35583_ids);
+
+static struct spi_driver lg35583_driver = {
+       .probe = lg35583_probe,
+       .remove = lg35583_remove,
+       .id_table = lg35583_ids,
+       .driver = {
+               .name = "panel-hiby-lg35583",
+               .of_match_table = lg35583_of_match,
+       },
+};
+module_spi_driver(lg35583_driver);
+
+MODULE_AUTHOR("Olivier Louvignes <[email protected]>");
+MODULE_DESCRIPTION("HiBy R1 LG35583 panel driver");
+MODULE_LICENSE("GPL");
-- 
2.39.5

Reply via email to