The X1600's display controller has none of the registers ingenic-drm
programs: it fetches each frame through a descriptor whose address it
takes from SRD_CHAIN_ADDR, and keeps its interface, TFT timings and smart
panel set-up in separate register blocks (X1600 programming manual,
chapter 10; the X2000's controller without the composer). This driver
handles parallel RGB panels on a 24-bit bus, with one full-screen RGB565
plane, and takes the colour order (RGB or BGR) from the panel's bus
format.

Each descriptor points at itself, so a frame repeats until a flip hands
the controller the other one. The controller takes it "at appropriate
time", so a flip's event waits until the descriptor-site register
reports the new descriptor at the end of a frame.

Tested on a HiBy R1 with its 480x800 panel: the fbdev console and
r1player, blanking and unblanking, and 60 fbdev pans in 0.97 s, one flip
per frame at about 61 Hz, none timing out.

Signed-off-by: Olivier Louvignes <[email protected]>
---
 MAINTAINERS                                 |   1 +
 drivers/gpu/drm/Makefile                    |   1 +
 drivers/gpu/drm/ingenic/Kconfig             |  22 +
 drivers/gpu/drm/ingenic/Makefile            |   1 +
 drivers/gpu/drm/ingenic/ingenic-x1600-drm.c | 694 ++++++++++++++++++++
 5 files changed, 719 insertions(+)
 create mode 100644 drivers/gpu/drm/ingenic/ingenic-x1600-drm.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 98d46b1cc029..57313163e59e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12931,6 +12931,7 @@ INGENIC X1600 DRM DRIVER
 M:     Olivier Louvignes <[email protected]>
 S:     Maintained
 F:     Documentation/devicetree/bindings/display/ingenic,x1600-lcd.yaml
+F:     drivers/gpu/drm/ingenic/ingenic-x1600-drm.c
 
 INJOINIC IP5xxx POWER BANK IC DRIVER
 M:     Samuel Holland <[email protected]>
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 2c6bc093a923..37dc781761d6 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -206,6 +206,7 @@ obj-$(CONFIG_DRM_STM) += stm/
 obj-$(CONFIG_DRM_STI) += sti/
 obj-y                  += imx/
 obj-$(CONFIG_DRM_INGENIC) += ingenic/
+obj-$(CONFIG_DRM_INGENIC_X1600) += ingenic/
 obj-$(CONFIG_DRM_LOGICVC) += logicvc/
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
 obj-$(CONFIG_DRM_MESON)        += meson/
diff --git a/drivers/gpu/drm/ingenic/Kconfig b/drivers/gpu/drm/ingenic/Kconfig
index 987ed42f6f7a..206e26be143d 100644
--- a/drivers/gpu/drm/ingenic/Kconfig
+++ b/drivers/gpu/drm/ingenic/Kconfig
@@ -20,6 +20,28 @@ config DRM_INGENIC
 
          If M is selected the module will be called ingenic-drm.
 
+config DRM_INGENIC_X1600
+       tristate "DRM support for the Ingenic X1600 LCD controller"
+       depends on MIPS || COMPILE_TEST
+       depends on DRM
+       depends on OF
+       depends on COMMON_CLK
+       select APERTURE_HELPERS
+       select DRM_BRIDGE
+       select DRM_CLIENT_SELECTION
+       select DRM_PANEL_BRIDGE
+       select DRM_KMS_HELPER
+       select DRM_DISPLAY_HELPER
+       select DRM_BRIDGE_CONNECTOR
+       select DRM_GEM_DMA_HELPER
+       help
+         Choose this option for DRM support for the display controller of
+         the Ingenic X1600 SoC. It drives parallel RGB panels on a 24-bit
+         bus from one full-screen RGB565 plane; the controller's smart panel
+         interface is not supported.
+
+         If M is selected the module will be called ingenic-x1600-drm.
+
 if DRM_INGENIC
 
 config DRM_INGENIC_IPU
diff --git a/drivers/gpu/drm/ingenic/Makefile b/drivers/gpu/drm/ingenic/Makefile
index f10cc1c5a5f2..90072ad13158 100644
--- a/drivers/gpu/drm/ingenic/Makefile
+++ b/drivers/gpu/drm/ingenic/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_DRM_INGENIC) += ingenic-drm.o
 ingenic-drm-y = ingenic-drm-drv.o
 ingenic-drm-$(CONFIG_DRM_INGENIC_IPU) += ingenic-ipu.o
 obj-$(CONFIG_DRM_INGENIC_DW_HDMI) += ingenic-dw-hdmi.o
+obj-$(CONFIG_DRM_INGENIC_X1600) += ingenic-x1600-drm.o
diff --git a/drivers/gpu/drm/ingenic/ingenic-x1600-drm.c 
b/drivers/gpu/drm/ingenic/ingenic-x1600-drm.c
new file mode 100644
index 000000000000..34fbfc7de1c4
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-x1600-drm.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DRM driver for the LCD controller of the Ingenic X1600
+ *
+ * Copyright (c) 2026 Olivier Louvignes <[email protected]>
+ *
+ * The X1600's display controller (X1600 programming manual, chapter 10) is
+ * the simple read channel of the X2000's, without its composer. It shares no
+ * registers with the JZ47xx and X1000 controllers that ingenic-drm drives: it
+ * fetches each frame through a descriptor whose address is in SRD_CHAIN_ADDR,
+ * and its interface and timings sit in register blocks of their own. It
+ * drives parallel RGB (TFT) panels and smart panels; this driver handles
+ * parallel RGB panels on a 24-bit bus, from RGB565 framebuffers.
+ */
+
+#include <linux/aperture.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#define X1600_LCD_SRD_CHAIN_ADDR       0x1000
+#define X1600_LCD_SRD_CHAIN_START      0x1004
+#define X1600_LCD_CTRL                 0x2000
+#define X1600_LCD_ST                   0x2004
+#define X1600_LCD_CLR_ST               0x2008
+#define X1600_LCD_INTC                 0x200c
+#define X1600_LCD_INT_FLAG             0x2010
+#define X1600_LCD_COM_CONFIG           0x2014
+#define X1600_LCD_CHAIN_SITE           0x2204
+#define X1600_LCD_DISP_COM             0x8000
+#define X1600_LCD_TFT_HSYNC            0x9000
+#define X1600_LCD_TFT_VSYNC            0x9004
+#define X1600_LCD_TFT_HDE              0x9008
+#define X1600_LCD_TFT_VDE              0x900c
+#define X1600_LCD_TFT_TRAN_CFG         0x9010
+
+/* CTRL: stop the simple read channel at the end of the frame. */
+#define X1600_LCD_CTRL_GEN_STP_SRD     BIT(4)
+
+/* ST; CLR_ST (write 1 to clear), INTC and INT_FLAG have the same bits. */
+#define X1600_LCD_ST_WORKING           BIT(0)
+#define X1600_LCD_ST_STOP_SRD_ACK      BIT(7)
+#define X1600_LCD_ST_TFT_UNDR          BIT(8)
+#define X1600_LCD_ST_DISP_END          BIT(17) /* a frame was displayed */
+
+#define X1600_LCD_COM_CONFIG_BURST_LEN GENMASK(5, 4)
+#define X1600_LCD_BURST_LEN_32         3
+
+#define X1600_LCD_DISP_COM_IF          GENMASK(1, 0)
+#define X1600_LCD_DISP_COM_IF_TFT      1
+#define X1600_LCD_DISP_COM_DITHER_EN   BIT(4)
+#define X1600_LCD_DISP_COM_TFT_CLKGATE BIT(5)
+#define X1600_LCD_DISP_COM_SLCD_CLKGATE        BIT(6)
+
+/* TFT_HSYNC, TFT_VSYNC: sync length and total; TFT_HDE, TFT_VDE: active area. 
*/
+#define X1600_LCD_TIMING_START         GENMASK(27, 16)
+#define X1600_LCD_TIMING_END           GENMASK(11, 0)
+
+#define X1600_LCD_TFT_COLOR_EVEN       GENMASK(21, 19)
+#define X1600_LCD_TFT_COLOR_ODD                GENMASK(18, 16)
+#define X1600_LCD_TFT_PIX_CLK_INV      BIT(10)
+#define X1600_LCD_TFT_DE_DL            BIT(9)
+#define X1600_LCD_TFT_HSYNC_DL         BIT(8)
+#define X1600_LCD_TFT_VSYNC_DL         BIT(7)
+#define X1600_LCD_TFT_MODE             GENMASK(2, 0)
+#define X1600_LCD_TFT_MODE_24BIT       0
+#define X1600_LCD_TFT_COLOR_RGB                0
+#define X1600_LCD_TFT_COLOR_BGR                2
+
+#define X1600_LCD_DESC_CFG_FMT         GENMASK(22, 19)
+#define X1600_LCD_DESC_FMT_RGB565      2
+/* An interrupt at the end of the frame: DISP_END needs this and INTC set. */
+#define X1600_LCD_DESC_CTRL_EOD_MSK    BIT(17)
+
+#define X1600_LCD_MAX_PIXELS           (1280 * 720)
+/* A smaller frame breaks a continuous descriptor chain (manual, 10.7). */
+#define X1600_LCD_MIN_PIXELS           500
+#define X1600_LCD_STOP_TIMEOUT_MS      100
+
+/*
+ * The controller reads a descriptor at the start of each frame and then
+ * follows its next field. Each descriptor points at itself, so a frame
+ * repeats until SRD_CHAIN_ADDR names another one.
+ */
+struct x1600_lcd_desc {
+       u32 next;
+       u32 addr;
+       u32 stride;     /* in pixels */
+       u32 cfg;
+       u32 ctrl;
+} __aligned(64);
+
+struct x1600_lcd {
+       struct drm_device drm;
+       struct drm_plane plane;
+       struct drm_crtc crtc;
+       struct drm_encoder encoder;
+
+       void __iomem *base;
+       struct clk *clk;
+
+       struct x1600_lcd_desc *desc;
+       dma_addr_t desc_phys;
+       unsigned int cur;               /* the descriptor last handed over */
+       bool running;
+       struct completion stopped;
+       /* Serialises the read-modify-writes of INTC. */
+       spinlock_t irq_lock;
+
+       /* Under drm.event_lock: sent once the controller reads event_desc. */
+       struct drm_pending_vblank_event *event;
+       u32 event_desc;
+
+       u32 bus_flags;
+       u32 color_order;
+};
+
+static const u32 x1600_lcd_formats[] = {
+       DRM_FORMAT_RGB565,
+};
+
+static struct x1600_lcd *to_x1600_lcd(struct drm_device *drm)
+{
+       return container_of(drm, struct x1600_lcd, drm);
+}
+
+static dma_addr_t x1600_lcd_desc_addr(struct x1600_lcd *lcd, unsigned int i)
+{
+       return lcd->desc_phys + i * sizeof(*lcd->desc);
+}
+
+static void x1600_lcd_irq_enable(struct x1600_lcd *lcd, u32 mask, bool enable)
+{
+       unsigned long flags;
+       u32 val;
+
+       spin_lock_irqsave(&lcd->irq_lock, flags);
+       val = readl(lcd->base + X1600_LCD_INTC);
+       if (enable)
+               val |= mask;
+       else
+               val &= ~mask;
+       /* Seen on the R1: a bit still set when it is masked keeps the line up. 
*/
+       writel(mask, lcd->base + X1600_LCD_CLR_ST);
+       writel(val, lcd->base + X1600_LCD_INTC);
+       spin_unlock_irqrestore(&lcd->irq_lock, flags);
+}
+
+/* Stops at the end of the frame; false if the controller did not report it. */
+static bool x1600_lcd_stop(struct x1600_lcd *lcd)
+{
+       unsigned long left;
+
+       reinit_completion(&lcd->stopped);
+       x1600_lcd_irq_enable(lcd, X1600_LCD_ST_STOP_SRD_ACK, true);
+       writel(X1600_LCD_CTRL_GEN_STP_SRD, lcd->base + X1600_LCD_CTRL);
+       left = wait_for_completion_timeout(&lcd->stopped,
+                                          
msecs_to_jiffies(X1600_LCD_STOP_TIMEOUT_MS));
+       x1600_lcd_irq_enable(lcd, X1600_LCD_ST_STOP_SRD_ACK, false);
+       return left;
+}
+
+static u32 x1600_lcd_timing(unsigned int start, unsigned int end)
+{
+       return FIELD_PREP(X1600_LCD_TIMING_START, start) |
+              FIELD_PREP(X1600_LCD_TIMING_END, end);
+}
+
+static void x1600_lcd_crtc_atomic_enable(struct drm_crtc *crtc,
+                                        struct drm_atomic_commit *state)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(crtc->dev);
+       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       unsigned int hde = mode->htotal - mode->hsync_start;
+       unsigned int vde = mode->vtotal - mode->vsync_start;
+       u32 val;
+       int ret;
+
+       ret = clk_set_rate(lcd->clk, mode->crtc_clock * 1000);
+       if (ret)
+               dev_warn(lcd->drm.dev, "Unable to set the pixel clock: %d\n", 
ret);
+       ret = clk_prepare_enable(lcd->clk);
+       if (ret) {
+               dev_err(lcd->drm.dev, "Unable to start the pixel clock: %d\n", 
ret);
+               return;
+       }
+
+       writel(readl(lcd->base + X1600_LCD_ST), lcd->base + X1600_LCD_CLR_ST);
+       val = readl(lcd->base + X1600_LCD_COM_CONFIG) & 
~X1600_LCD_COM_CONFIG_BURST_LEN;
+       writel(val | FIELD_PREP(X1600_LCD_COM_CONFIG_BURST_LEN, 
X1600_LCD_BURST_LEN_32),
+              lcd->base + X1600_LCD_COM_CONFIG);
+
+       /* In the manual's order: timings, output format, then the interface. */
+       writel(x1600_lcd_timing(mode->hsync_end - mode->hsync_start, 
mode->htotal),
+              lcd->base + X1600_LCD_TFT_HSYNC);
+       writel(x1600_lcd_timing(mode->vsync_end - mode->vsync_start, 
mode->vtotal),
+              lcd->base + X1600_LCD_TFT_VSYNC);
+       writel(x1600_lcd_timing(hde, hde + mode->hdisplay),
+              lcd->base + X1600_LCD_TFT_HDE);
+       writel(x1600_lcd_timing(vde, vde + mode->vdisplay),
+              lcd->base + X1600_LCD_TFT_VDE);
+
+       val = FIELD_PREP(X1600_LCD_TFT_COLOR_EVEN, lcd->color_order) |
+             FIELD_PREP(X1600_LCD_TFT_COLOR_ODD, lcd->color_order) |
+             FIELD_PREP(X1600_LCD_TFT_MODE, X1600_LCD_TFT_MODE_24BIT);
+       if (lcd->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+               val |= X1600_LCD_TFT_PIX_CLK_INV;
+       if (lcd->bus_flags & DRM_BUS_FLAG_DE_LOW)
+               val |= X1600_LCD_TFT_DE_DL;
+       /* The *_DL bits set the level outside the pulse. */
+       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+               val |= X1600_LCD_TFT_HSYNC_DL;
+       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+               val |= X1600_LCD_TFT_VSYNC_DL;
+       writel(val, lcd->base + X1600_LCD_TFT_TRAN_CFG);
+
+       /* No dithering: it only applies to 888 sources. */
+       val = readl(lcd->base + X1600_LCD_DISP_COM);
+       val &= ~(X1600_LCD_DISP_COM_IF | X1600_LCD_DISP_COM_DITHER_EN);
+       val |= FIELD_PREP(X1600_LCD_DISP_COM_IF, X1600_LCD_DISP_COM_IF_TFT) |
+              X1600_LCD_DISP_COM_TFT_CLKGATE | X1600_LCD_DISP_COM_SLCD_CLKGATE;
+       writel(val, lcd->base + X1600_LCD_DISP_COM);
+
+       writel(x1600_lcd_desc_addr(lcd, lcd->cur), lcd->base + 
X1600_LCD_SRD_CHAIN_ADDR);
+       writel(1, lcd->base + X1600_LCD_SRD_CHAIN_START);
+       lcd->running = true;
+
+       drm_crtc_vblank_on(crtc);
+}
+
+static void x1600_lcd_crtc_atomic_disable(struct drm_crtc *crtc,
+                                         struct drm_atomic_commit *state)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(crtc->dev);
+
+       if (!x1600_lcd_stop(lcd))
+               dev_warn(lcd->drm.dev, "The controller did not stop\n");
+       lcd->running = false;
+
+       spin_lock_irq(&crtc->dev->event_lock);
+       if (lcd->event) {
+               drm_crtc_send_vblank_event(crtc, lcd->event);
+               drm_crtc_vblank_put(crtc);
+               lcd->event = NULL;
+       }
+       spin_unlock_irq(&crtc->dev->event_lock);
+
+       drm_crtc_vblank_off(crtc);
+       clk_disable_unprepare(lcd->clk);
+}
+
+static int x1600_lcd_crtc_atomic_check(struct drm_crtc *crtc,
+                                      struct drm_atomic_commit *state)
+{
+       struct drm_crtc_state *crtc_state = 
drm_atomic_get_new_crtc_state(state, crtc);
+
+       /* Only when enabled: drm_atomic_helper_shutdown() disables both at 
once. */
+       if (!crtc_state->enable)
+               return 0;
+
+       return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
+}
+
+static void x1600_lcd_crtc_atomic_flush(struct drm_crtc *crtc,
+                                       struct drm_atomic_commit *state)
+{
+       struct drm_crtc_state *crtc_state = 
drm_atomic_get_new_crtc_state(state, crtc);
+       struct drm_pending_vblank_event *event = crtc_state->event;
+       struct x1600_lcd *lcd = to_x1600_lcd(crtc->dev);
+
+       if (!event)
+               return;
+
+       crtc_state->event = NULL;
+
+       /*
+        * The controller takes a new descriptor "at appropriate time" (manual,
+        * 10.6.4): wait until it reports reading the one just handed over.
+        */
+       spin_lock_irq(&crtc->dev->event_lock);
+       if (lcd->running && !WARN_ON(lcd->event) && drm_crtc_vblank_get(crtc) 
== 0) {
+               lcd->event = event;
+               lcd->event_desc = x1600_lcd_desc_addr(lcd, lcd->cur);
+       } else {
+               drm_crtc_send_vblank_event(crtc, event);
+       }
+       spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static enum drm_mode_status
+x1600_lcd_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode 
*mode)
+{
+       unsigned int pixels = mode->hdisplay * mode->vdisplay;
+
+       if (mode->htotal > FIELD_MAX(X1600_LCD_TIMING_END) ||
+           mode->vtotal > FIELD_MAX(X1600_LCD_TIMING_END))
+               return MODE_BAD;
+
+       /* The sync pulses' end points must not be 0. */
+       if (mode->hsync_end == mode->hsync_start)
+               return MODE_HSYNC_NARROW;
+       if (mode->vsync_end == mode->vsync_start)
+               return MODE_VSYNC_NARROW;
+
+       if (pixels > X1600_LCD_MAX_PIXELS)
+               return MODE_MEM;
+       if (pixels < X1600_LCD_MIN_PIXELS)
+               return MODE_BAD;
+
+       return MODE_OK;
+}
+
+static int x1600_lcd_enable_vblank(struct drm_crtc *crtc)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(crtc->dev);
+
+       x1600_lcd_irq_enable(lcd, X1600_LCD_ST_DISP_END, true);
+       return 0;
+}
+
+static void x1600_lcd_disable_vblank(struct drm_crtc *crtc)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(crtc->dev);
+
+       x1600_lcd_irq_enable(lcd, X1600_LCD_ST_DISP_END, false);
+}
+
+static irqreturn_t x1600_lcd_irq(int irq, void *data)
+{
+       struct x1600_lcd *lcd = data;
+       u32 st = readl(lcd->base + X1600_LCD_INT_FLAG);
+
+       if (!st)
+               return IRQ_NONE;
+
+       writel(st, lcd->base + X1600_LCD_CLR_ST);
+       if (st & X1600_LCD_ST_STOP_SRD_ACK)
+               complete(&lcd->stopped);
+
+       if (st & X1600_LCD_ST_DISP_END) {
+               drm_crtc_handle_vblank(&lcd->crtc);
+
+               spin_lock(&lcd->drm.event_lock);
+               if (lcd->event &&
+                   readl(lcd->base + X1600_LCD_CHAIN_SITE) == lcd->event_desc) 
{
+                       drm_crtc_send_vblank_event(&lcd->crtc, lcd->event);
+                       drm_crtc_vblank_put(&lcd->crtc);
+                       lcd->event = NULL;
+               }
+               spin_unlock(&lcd->drm.event_lock);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static int x1600_lcd_plane_atomic_check(struct drm_plane *plane,
+                                       struct drm_atomic_commit *state)
+{
+       struct drm_plane_state *plane_state = 
drm_atomic_get_new_plane_state(state, plane);
+       struct drm_crtc_state *crtc_state;
+
+       if (!plane_state->crtc)
+               return 0;
+
+       crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc);
+       if (WARN_ON(!crtc_state))
+               return -EINVAL;
+
+       /* The plane covers the whole screen: no scaling, no position. */
+       return drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+                                                  DRM_PLANE_NO_SCALING,
+                                                  DRM_PLANE_NO_SCALING,
+                                                  false, true);
+}
+
+static void x1600_lcd_plane_atomic_update(struct drm_plane *plane,
+                                         struct drm_atomic_commit *state)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(plane->dev);
+       struct drm_plane_state *plane_state = 
drm_atomic_get_new_plane_state(state, plane);
+       struct drm_framebuffer *fb = plane_state->fb;
+       unsigned int next = lcd->running ? !lcd->cur : lcd->cur;
+       struct x1600_lcd_desc *desc = &lcd->desc[next];
+
+       if (!fb)
+               return;
+
+       desc->next = x1600_lcd_desc_addr(lcd, next);
+       desc->addr = drm_fb_dma_get_gem_addr(fb, plane_state, 0);
+       desc->stride = fb->pitches[0] / fb->format->cpp[0];
+       desc->cfg = FIELD_PREP(X1600_LCD_DESC_CFG_FMT, 
X1600_LCD_DESC_FMT_RGB565);
+       desc->ctrl = X1600_LCD_DESC_CTRL_EOD_MSK;
+
+       if (!lcd->running)
+               return;
+
+       /*
+        * The manual hands over a descriptor by writing SRD_CHAIN_ADDR; the
+        * vendor kernel also sets SRD_CHAIN_START, and so does this.
+        */
+       wmb();
+       writel(x1600_lcd_desc_addr(lcd, next), lcd->base + 
X1600_LCD_SRD_CHAIN_ADDR);
+       writel(1, lcd->base + X1600_LCD_SRD_CHAIN_START);
+       lcd->cur = next;
+}
+
+/*
+ * Panels with 18 data lines take the top six bits of each colour of the
+ * 888 mode: in the 666 mode, the HiBy R1's panel shows wrong colours.
+ */
+static int x1600_lcd_color_order(const struct drm_display_info *info)
+{
+       if (!info->num_bus_formats)
+               return X1600_LCD_TFT_COLOR_RGB;
+
+       switch (info->bus_formats[0]) {
+       case MEDIA_BUS_FMT_RGB666_1X18:
+       case MEDIA_BUS_FMT_RGB888_1X24:
+               return X1600_LCD_TFT_COLOR_RGB;
+       case MEDIA_BUS_FMT_BGR666_1X18:
+       case MEDIA_BUS_FMT_BGR888_1X24:
+               return X1600_LCD_TFT_COLOR_BGR;
+       default:
+               return -EINVAL;
+       }
+}
+
+static int x1600_lcd_encoder_atomic_check(struct drm_encoder *encoder,
+                                         struct drm_crtc_state *crtc_state,
+                                         struct drm_connector_state 
*conn_state)
+{
+       const struct drm_display_info *info = 
&conn_state->connector->display_info;
+
+       if (x1600_lcd_color_order(info) < 0) {
+               drm_dbg_kms(encoder->dev, "Unsupported bus format 0x%04x\n",
+                           info->bus_formats[0]);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void x1600_lcd_encoder_atomic_mode_set(struct drm_encoder *encoder,
+                                             struct drm_crtc_state *crtc_state,
+                                             struct drm_connector_state 
*conn_state)
+{
+       struct x1600_lcd *lcd = to_x1600_lcd(encoder->dev);
+       const struct drm_display_info *info = 
&conn_state->connector->display_info;
+
+       lcd->bus_flags = info->bus_flags;
+       lcd->color_order = x1600_lcd_color_order(info);
+}
+
+static const struct drm_plane_funcs x1600_lcd_plane_funcs = {
+       .update_plane           = drm_atomic_helper_update_plane,
+       .disable_plane          = drm_atomic_helper_disable_plane,
+       .atomic_create_state    = drm_atomic_helper_plane_create_state,
+       .destroy                = drm_plane_cleanup,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
+};
+
+static const struct drm_plane_helper_funcs x1600_lcd_plane_helper_funcs = {
+       .atomic_check           = x1600_lcd_plane_atomic_check,
+       .atomic_update          = x1600_lcd_plane_atomic_update,
+};
+
+static const struct drm_crtc_funcs x1600_lcd_crtc_funcs = {
+       .set_config             = drm_atomic_helper_set_config,
+       .page_flip              = drm_atomic_helper_page_flip,
+       .atomic_create_state    = drm_atomic_helper_crtc_create_state,
+       .destroy                = drm_crtc_cleanup,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
+       .enable_vblank          = x1600_lcd_enable_vblank,
+       .disable_vblank         = x1600_lcd_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs x1600_lcd_crtc_helper_funcs = {
+       .atomic_check           = x1600_lcd_crtc_atomic_check,
+       .atomic_enable          = x1600_lcd_crtc_atomic_enable,
+       .atomic_disable         = x1600_lcd_crtc_atomic_disable,
+       .atomic_flush           = x1600_lcd_crtc_atomic_flush,
+       .mode_valid             = x1600_lcd_crtc_mode_valid,
+};
+
+static const struct drm_encoder_helper_funcs x1600_lcd_encoder_helper_funcs = {
+       .atomic_check           = x1600_lcd_encoder_atomic_check,
+       .atomic_mode_set        = x1600_lcd_encoder_atomic_mode_set,
+};
+
+static const struct drm_mode_config_funcs x1600_lcd_mode_config_funcs = {
+       .fb_create              = drm_gem_fb_create,
+       .atomic_check           = drm_atomic_helper_check,
+       .atomic_commit          = drm_atomic_helper_commit,
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(x1600_lcd_fops);
+
+static const struct drm_driver x1600_lcd_driver = {
+       .driver_features        = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+       .name                   = "ingenic-x1600-drm",
+       .desc                   = "DRM driver for the Ingenic X1600 LCD 
controller",
+       .major                  = 1,
+       .minor                  = 0,
+       .fops                   = &x1600_lcd_fops,
+       DRM_GEM_DMA_DRIVER_OPS,
+       DRM_FBDEV_DMA_DRIVER_OPS,
+};
+
+static int x1600_lcd_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct drm_connector *connector;
+       struct drm_bridge *bridge;
+       struct drm_device *drm;
+       struct x1600_lcd *lcd;
+       int irq, ret;
+
+       lcd = devm_drm_dev_alloc(dev, &x1600_lcd_driver, struct x1600_lcd, drm);
+       if (IS_ERR(lcd))
+               return PTR_ERR(lcd);
+
+       drm = &lcd->drm;
+       platform_set_drvdata(pdev, lcd);
+       init_completion(&lcd->stopped);
+       spin_lock_init(&lcd->irq_lock);
+
+       lcd->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(lcd->base))
+               return PTR_ERR(lcd->base);
+
+       lcd->clk = devm_clk_get(dev, "lcd");
+       if (IS_ERR(lcd->clk))
+               return dev_err_probe(dev, PTR_ERR(lcd->clk), "Unable to get 
clock\n");
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
+       if (IS_ERR(bridge))
+               return dev_err_probe(dev, PTR_ERR(bridge), "Unable to get the 
panel\n");
+
+       lcd->desc = dmam_alloc_coherent(dev, 2 * sizeof(*lcd->desc),
+                                       &lcd->desc_phys, GFP_KERNEL);
+       if (!lcd->desc)
+               return -ENOMEM;
+
+       ret = drmm_mode_config_init(drm);
+       if (ret)
+               return ret;
+
+       /* Framebuffers, not modes: fbdev's double buffer is taller than 720p. 
*/
+       drm->mode_config.max_width = FIELD_MAX(X1600_LCD_TIMING_END);
+       drm->mode_config.max_height = FIELD_MAX(X1600_LCD_TIMING_END);
+       drm->mode_config.funcs = &x1600_lcd_mode_config_funcs;
+
+       ret = drm_universal_plane_init(drm, &lcd->plane, 1, 
&x1600_lcd_plane_funcs,
+                                      x1600_lcd_formats, 
ARRAY_SIZE(x1600_lcd_formats),
+                                      NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+       if (ret)
+               return ret;
+       drm_plane_helper_add(&lcd->plane, &x1600_lcd_plane_helper_funcs);
+
+       ret = drm_crtc_init_with_planes(drm, &lcd->crtc, &lcd->plane, NULL,
+                                       &x1600_lcd_crtc_funcs, NULL);
+       if (ret)
+               return ret;
+       drm_crtc_helper_add(&lcd->crtc, &x1600_lcd_crtc_helper_funcs);
+
+       ret = drmm_encoder_init(drm, &lcd->encoder, NULL, DRM_MODE_ENCODER_DPI, 
NULL);
+       if (ret)
+               return ret;
+       lcd->encoder.possible_crtcs = drm_crtc_mask(&lcd->crtc);
+       drm_encoder_helper_add(&lcd->encoder, &x1600_lcd_encoder_helper_funcs);
+
+       ret = drm_bridge_attach(&lcd->encoder, bridge, NULL,
+                               DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+       if (ret)
+               return ret;
+
+       connector = drm_bridge_connector_init(drm, &lcd->encoder);
+       if (IS_ERR(connector))
+               return PTR_ERR(connector);
+       drm_connector_attach_encoder(connector, &lcd->encoder);
+
+       ret = drm_vblank_init(drm, 1);
+       if (ret)
+               return ret;
+
+       drm_mode_config_reset(drm);
+
+       /* Take over from a boot framebuffer, and stop what it left running. */
+       ret = aperture_remove_all_conflicting_devices(x1600_lcd_driver.name);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(lcd->clk);
+       if (ret)
+               return ret;
+
+       /* No interrupt from what the boot loader left enabled or pending. */
+       writel(0, lcd->base + X1600_LCD_INTC);
+       writel(readl(lcd->base + X1600_LCD_ST), lcd->base + X1600_LCD_CLR_ST);
+
+       ret = devm_request_irq(dev, irq, x1600_lcd_irq, 0, dev_name(dev), lcd);
+       if (ret) {
+               clk_disable_unprepare(lcd->clk);
+               return dev_err_probe(dev, ret, "Unable to request the IRQ\n");
+       }
+
+       /*
+        * Seen on the R1: after an underrun (TFT_UNDR) the controller can stop
+        * answering GEN_STP_SRD, and only a reset brings it back.
+        */
+       if (readl(lcd->base + X1600_LCD_ST) & X1600_LCD_ST_WORKING && 
!x1600_lcd_stop(lcd))
+               dev_err(dev, "The controller did not stop%s\n",
+                       readl(lcd->base + X1600_LCD_ST) & X1600_LCD_ST_TFT_UNDR 
?
+                       " after an underrun" : "");
+       clk_disable_unprepare(lcd->clk);
+
+       ret = drm_dev_register(drm, 0);
+       if (ret)
+               return ret;
+
+       drm_client_setup_with_fourcc(drm, DRM_FORMAT_RGB565);
+       return 0;
+}
+
+static void x1600_lcd_remove(struct platform_device *pdev)
+{
+       struct x1600_lcd *lcd = platform_get_drvdata(pdev);
+
+       drm_dev_unregister(&lcd->drm);
+       drm_atomic_helper_shutdown(&lcd->drm);
+}
+
+static void x1600_lcd_shutdown(struct platform_device *pdev)
+{
+       struct x1600_lcd *lcd = platform_get_drvdata(pdev);
+
+       drm_atomic_helper_shutdown(&lcd->drm);
+}
+
+static const struct of_device_id x1600_lcd_of_match[] = {
+       { .compatible = "ingenic,x1600-lcd" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, x1600_lcd_of_match);
+
+static struct platform_driver x1600_lcd_platform_driver = {
+       .driver = {
+               .name = "ingenic-x1600-drm",
+               .of_match_table = x1600_lcd_of_match,
+       },
+       .probe = x1600_lcd_probe,
+       .remove = x1600_lcd_remove,
+       .shutdown = x1600_lcd_shutdown,
+};
+module_platform_driver(x1600_lcd_platform_driver);
+
+MODULE_AUTHOR("Olivier Louvignes <[email protected]>");
+MODULE_DESCRIPTION("DRM driver for the Ingenic X1600 LCD controller");
+MODULE_LICENSE("GPL");
-- 
2.39.5

Reply via email to