{"id":2228882,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2228882/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260427124738.966578-40-peter.maydell@linaro.org/","project":{"id":14,"url":"http://patchwork.ozlabs.org/api/1.1/projects/14/?format=json","name":"QEMU Development","link_name":"qemu-devel","list_id":"qemu-devel.nongnu.org","list_email":"qemu-devel@nongnu.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260427124738.966578-40-peter.maydell@linaro.org>","date":"2026-04-27T12:47:13","name":"[PULL,39/63] hw/display: Add i.MX6UL LCDIF device model","commit_ref":null,"pull_url":null,"state":"not-applicable","archived":false,"hash":"9503d3bdeaa7dd6a8b710aa2e4230de166efe9fa","submitter":{"id":5111,"url":"http://patchwork.ozlabs.org/api/1.1/people/5111/?format=json","name":"Peter Maydell","email":"peter.maydell@linaro.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260427124738.966578-40-peter.maydell@linaro.org/mbox/","series":[{"id":501642,"url":"http://patchwork.ozlabs.org/api/1.1/series/501642/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/list/?series=501642","date":"2026-04-27T12:46:34","name":"[PULL,01/63] docs/system: add FEAT_AA32 and FEAT_AA64 to emulation list","version":1,"mbox":"http://patchwork.ozlabs.org/series/501642/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2228882/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2228882/checks/","tags":{},"headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=J5siPrjK;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"],"Received":["from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g43Zs2MNVz1yJX\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 27 Apr 2026 23:00:37 +1000 (AEST)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wHLUa-0008HR-3D; Mon, 27 Apr 2026 08:55:16 -0400","from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>)\n id 1wHLNw-00087G-R2\n for qemu-devel@nongnu.org; Mon, 27 Apr 2026 08:48:31 -0400","from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <peter.maydell@linaro.org>)\n id 1wHLNq-0005hk-Nl\n for qemu-devel@nongnu.org; Mon, 27 Apr 2026 08:48:22 -0400","by mail-wm1-x32a.google.com with SMTP id\n 5b1f17b1804b1-48896199cbaso101474605e9.1\n for <qemu-devel@nongnu.org>; Mon, 27 Apr 2026 05:48:17 -0700 (PDT)","from lanath.. (wildly.archaic.org.uk. [81.2.115.145])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-488ffc5e3f4sm448974115e9.2.2026.04.27.05.48.15\n for <qemu-devel@nongnu.org>\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 27 Apr 2026 05:48:15 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=linaro.org; s=google; t=1777294096; x=1777898896; darn=nongnu.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:to:from:from:to:cc:subject:date:message-id\n :reply-to; bh=xLiDu/iz5AsiqDXEjMsCtuoCWzBBhHTKjCH3+u/R2jY=;\n b=J5siPrjKN43LRkWd+ubuzJ413Fl4Z2hJ6VuS1n0u6U56GUR16DOWISMCVKPPv2BkGq\n I1TOvwAl7QsSNWVLxMKM8BF4dmEAuqCl5J6RIqZVO47l8NK5nmNp2F1Ozr0pwuhovHzI\n UlwhfPIGDH9yIFFZaMh1Or5i5C8A/EjLPfKv5CGjk924nfqX58ksf3u6/spSvus41qmd\n d0m0czGXYK1XG3fOmdL0CJdbS2QOpfl7k8wGmWlp/uwo7XNoFza6uwwCGseapZGSmchM\n isPUTtr+eH6iZmvdrMMRQ01TdnsqTw3RK3jIdCzVILQIIgGFhAQ9nuXxFbC0tiL4OEai\n 2ylw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777294096; x=1777898896;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to\n :cc:subject:date:message-id:reply-to;\n bh=xLiDu/iz5AsiqDXEjMsCtuoCWzBBhHTKjCH3+u/R2jY=;\n b=UxUtuI6RSWeA92lk17wNaVz18MaIGc8qJU2DBw/ndawT1fb/Jm3Ch7T4FLMoKRlyEz\n aqIC6Mj9lp5xPxKdFPgVdD1djP1mLSw+Fhc2yFGKm1CUENx1TMGloiPzzqWELxpmq+oV\n FK6wh7/kheHdw55W3iE4w1b6p99Wmlay1q3Qi8sO3hOcdJTI3XRQctoEGm0JKyOTGXN6\n OQObpcJiXZE8lUn3ZxBPSDiw2riT0raIa9warqhtt6Oh+K9GuVJXzoN9QOAnKCaXp7WD\n wY3N7LRuv/i4wn54A3WTCWA3Pf7KfcQElhsfo9NcNdcsEEQfBmar+aZVHR9a9VVOHf1j\n 2jVw==","X-Gm-Message-State":"AOJu0YwvqrJdk3RlmsoL4pRkWbm335FAFOFp1O3dcYhmXUpmlYZ6v8fz\n rcGLHwFjl0E8y338cQnyeEG9bU7dZ4rzubtabyxE7wIEW98THCve2ia27HstPbDLe/mVAtiIprs\n gzwqA","X-Gm-Gg":"AeBDieskSek9DxD2qDbIl9W8ePHTGKtM20S+YYXBDCgmqYPyrlbD+GDsJC7CzO18uXy\n Hgm85cAALNMVkKAhrC3lfaS0YcRYpLxkMPjFOgBDquYhs/ToXvMmMpEKIfxSO/1Y+MgYGln3XjJ\n LP/hneK6JdLn1rKqRG1BOMCvZd9gAVGjXwBRG1K8wOCf7Z51euCUeRRH5oGBeQK7AtqnB4M1gwR\n rZY16eaMbpl2b13kbMAEtYarvtbbhy/DewOx6kF56rCUlLMCKP0bt+69RXB84YBjFqRnEFCSsT9\n 3NHHgJFZHWzmh3bn7nNOwj6YBBE8Ykguu6fYZU/3fwHQ/BIL6xprQcrnnCpzUWgGsiv4tRL+vV6\n q3GjLYvLSHi3gkkL9tNfuvLguD27tWdikJrWHY45QAsRXQXi2dKFh/xYm64b7laleLcr2EQFqeI\n zcElSBdaqh7v/IPg8sIflAI69GtdbTSraR1loHgjomXALBcIJl+3dO3LFG2Cplf4WhFfACi+gTd\n Lzzf4Gm9BSZB4mOefU8BW71MWo1ubhZqchfqcfZPlTPq+LIqHt2","X-Received":"by 2002:a05:600c:859a:b0:48a:53ea:13df with SMTP id\n 5b1f17b1804b1-48a53ea1500mr320879035e9.2.1777294095819;\n Mon, 27 Apr 2026 05:48:15 -0700 (PDT)","From":"Peter Maydell <peter.maydell@linaro.org>","To":"qemu-devel@nongnu.org","Subject":"[PULL 39/63] hw/display: Add i.MX6UL LCDIF device model","Date":"Mon, 27 Apr 2026 13:47:13 +0100","Message-ID":"<20260427124738.966578-40-peter.maydell@linaro.org>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<20260427124738.966578-1-peter.maydell@linaro.org>","References":"<20260427124738.966578-1-peter.maydell@linaro.org>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Received-SPF":"pass client-ip=2a00:1450:4864:20::32a;\n envelope-from=peter.maydell@linaro.org; helo=mail-wm1-x32a.google.com","X-Spam_score_int":"-20","X-Spam_score":"-2.1","X-Spam_bar":"--","X-Spam_report":"(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=ham autolearn_force=no","X-Spam_action":"no action","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"qemu development <qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<https://lists.nongnu.org/archive/html/qemu-devel>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"},"content":"From: Yucai Liu <1486344514@qq.com>\n\nImplement a basic i.MX6UL LCDIF controller model with MMIO registers,\nframe-done interrupt behavior, and framebuffer-backed display updates\nfor RGB565 and XRGB8888 input formats.\n\nPlace the LCDIF device under hw/display and build it via a dedicated\nCONFIG_IMX6UL_LCDIF symbol. Model register fields with\nregisterfields.h helpers and provide migration support via vmstate.\n\nSigned-off-by: Yucai Liu <1486344514@qq.com>\nMessage-id: 20260412110240.93116-2-yangyanglan718@gmail.com\nReviewed-by: Peter Maydell <peter.maydell@linaro.org>\nSigned-off-by: Peter Maydell <peter.maydell@linaro.org>\n---\n MAINTAINERS                       |   2 +\n hw/display/Kconfig                |   4 +\n hw/display/imx6ul_lcdif.c         | 453 ++++++++++++++++++++++++++++++\n hw/display/meson.build            |   1 +\n include/hw/display/imx6ul_lcdif.h |  37 +++\n 5 files changed, 497 insertions(+)\n create mode 100644 hw/display/imx6ul_lcdif.c\n create mode 100644 include/hw/display/imx6ul_lcdif.h","diff":"diff --git a/MAINTAINERS b/MAINTAINERS\nindex 50a8e161c6..a23ff5279e 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -894,8 +894,10 @@ L: qemu-arm@nongnu.org\n S: Odd Fixes\n F: hw/arm/mcimx6ul-evk.c\n F: hw/arm/fsl-imx6ul.c\n+F: hw/display/imx6ul_lcdif.c\n F: hw/misc/imx6ul_ccm.c\n F: include/hw/arm/fsl-imx6ul.h\n+F: include/hw/display/imx6ul_lcdif.h\n F: include/hw/misc/imx6ul_ccm.h\n F: docs/system/arm/mcimx6ul-evk.rst\n \ndiff --git a/hw/display/Kconfig b/hw/display/Kconfig\nindex 1e95ab28ef..b3593fe981 100644\n--- a/hw/display/Kconfig\n+++ b/hw/display/Kconfig\n@@ -25,6 +25,10 @@ config PL110\n     bool\n     select FRAMEBUFFER\n \n+config IMX6UL_LCDIF\n+    bool\n+    select FRAMEBUFFER\n+\n config SII9022\n     bool\n     depends on I2C\ndiff --git a/hw/display/imx6ul_lcdif.c b/hw/display/imx6ul_lcdif.c\nnew file mode 100644\nindex 0000000000..33cd00fbe1\n--- /dev/null\n+++ b/hw/display/imx6ul_lcdif.c\n@@ -0,0 +1,453 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * i.MX6UL LCDIF controller\n+ *\n+ * Copyright (c) 2026 Yucai Liu <1486344514@qq.com>\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"hw/display/imx6ul_lcdif.h\"\n+#include \"hw/core/irq.h\"\n+#include \"hw/core/registerfields.h\"\n+#include \"hw/display/framebuffer.h\"\n+#include \"migration/vmstate.h\"\n+#include \"system/address-spaces.h\"\n+#include \"qemu/module.h\"\n+#include \"qemu/units.h\"\n+#include \"ui/pixel_ops.h\"\n+\n+#define LCDIF_MMIO_SIZE             (16 * KiB)\n+#define LCDIF_RESET_CTRL1           0x000f0000\n+\n+REG32(CTRL, 0x00)\n+    FIELD(CTRL, RUN, 0, 1)\n+    FIELD(CTRL, WORD_LENGTH, 8, 2)\n+REG32(CTRL1, 0x10)\n+    FIELD(CTRL1, CUR_FRAME_DONE_IRQ, 9, 1)\n+    FIELD(CTRL1, CUR_FRAME_DONE_IRQ_EN, 13, 1)\n+    FIELD(CTRL1, BYTE_PACKING_FORMAT, 16, 4)\n+REG32(V4_TRANSFER_COUNT, 0x30)\n+    FIELD(V4_TRANSFER_COUNT, H_COUNT, 0, 16)\n+    FIELD(V4_TRANSFER_COUNT, V_COUNT, 16, 16)\n+REG32(V4_CUR_BUF, 0x40)\n+REG32(V4_NEXT_BUF, 0x50)\n+REG32(AS_NEXT_BUF, 0x230)\n+\n+#define REG_SET                     0x4\n+#define REG_CLR                     0x8\n+#define REG_TOG                     0xc\n+\n+#define CTRL_WORD_LENGTH_16         0\n+#define CTRL_WORD_LENGTH_24         3\n+\n+#define FRAME_PERIOD_NS             (16 * 1000 * 1000ULL)\n+\n+enum IMX6ULLCDIFReg {\n+    IMX6UL_LCDIF_REG_CTRL = A_CTRL >> 4,\n+    IMX6UL_LCDIF_REG_CTRL1 = A_CTRL1 >> 4,\n+    IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT = A_V4_TRANSFER_COUNT >> 4,\n+    IMX6UL_LCDIF_REG_V4_CUR_BUF = A_V4_CUR_BUF >> 4,\n+    IMX6UL_LCDIF_REG_V4_NEXT_BUF = A_V4_NEXT_BUF >> 4,\n+    IMX6UL_LCDIF_REG_AS_NEXT_BUF = A_AS_NEXT_BUF >> 4,\n+};\n+\n+static inline bool imx6ul_lcdif_reg_exists(hwaddr reg)\n+{\n+    return (reg >> 4) < IMX6UL_LCDIF_REGS_NUM;\n+}\n+\n+static inline bool imx6ul_lcdif_reg_has_setclr(hwaddr reg)\n+{\n+    switch (reg) {\n+    case A_CTRL:\n+    case A_CTRL1:\n+        return true;\n+    default:\n+        return false;\n+    }\n+}\n+\n+static inline bool imx6ul_lcdif_is_running(IMX6ULLCDIFState *s)\n+{\n+    uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL];\n+\n+    return FIELD_EX32(ctrl, CTRL, RUN);\n+}\n+\n+static inline bool imx6ul_lcdif_frame_done_pending(IMX6ULLCDIFState *s)\n+{\n+    uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];\n+\n+    return FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ);\n+}\n+\n+static void imx6ul_lcdif_schedule_frame(IMX6ULLCDIFState *s)\n+{\n+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+\n+    timer_mod(s->frame_timer, now + FRAME_PERIOD_NS);\n+}\n+\n+static void imx6ul_lcdif_maybe_schedule_frame(IMX6ULLCDIFState *s)\n+{\n+    if (imx6ul_lcdif_is_running(s) && !imx6ul_lcdif_frame_done_pending(s)) {\n+        imx6ul_lcdif_schedule_frame(s);\n+    } else {\n+        timer_del(s->frame_timer);\n+    }\n+}\n+\n+static void imx6ul_lcdif_update_irq(IMX6ULLCDIFState *s)\n+{\n+    uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];\n+    bool level = FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ_EN) &&\n+                 FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ);\n+\n+    qemu_set_irq(s->irq, level);\n+}\n+\n+static void imx6ul_lcdif_frame_done(IMX6ULLCDIFState *s)\n+{\n+    uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];\n+\n+    ctrl1 = FIELD_DP32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ, 1);\n+    s->regs[IMX6UL_LCDIF_REG_CTRL1] = ctrl1;\n+    imx6ul_lcdif_update_irq(s);\n+}\n+\n+static void imx6ul_lcdif_draw_line_rgb565(void *opaque, uint8_t *dst,\n+                                          const uint8_t *src, int width,\n+                                          int dststep)\n+{\n+    uint32_t *dst32 = (uint32_t *)dst;\n+    int i;\n+\n+    for (i = 0; i < width; i++) {\n+        uint16_t pixel = lduw_le_p(src);\n+        uint8_t r = ((pixel >> 11) & 0x1f) << 3;\n+        uint8_t g = ((pixel >> 5) & 0x3f) << 2;\n+        uint8_t b = (pixel & 0x1f) << 3;\n+\n+        *dst32++ = rgb_to_pixel32(r, g, b);\n+        src += 2;\n+    }\n+}\n+\n+static void imx6ul_lcdif_draw_line_xrgb8888(void *opaque, uint8_t *dst,\n+                                            const uint8_t *src, int width,\n+                                            int dststep)\n+{\n+    uint32_t *dst32 = (uint32_t *)dst;\n+    int i;\n+\n+    for (i = 0; i < width; i++) {\n+        uint32_t pixel = ldl_le_p(src);\n+        uint8_t r = (pixel >> 16) & 0xff;\n+        uint8_t g = (pixel >> 8) & 0xff;\n+        uint8_t b = pixel & 0xff;\n+\n+        *dst32++ = rgb_to_pixel32(r, g, b);\n+        src += 4;\n+    }\n+}\n+\n+static void imx6ul_lcdif_update_display(void *opaque)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+    DisplaySurface *surface = qemu_console_surface(s->con);\n+    uint32_t transfer_count = s->regs[IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT];\n+    uint32_t width = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, H_COUNT);\n+    uint32_t height = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, V_COUNT);\n+    uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL];\n+    uint32_t frame_base = s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF];\n+    drawfn fn;\n+    int first = 0;\n+    int last = 0;\n+    int src_width;\n+\n+    if (!imx6ul_lcdif_is_running(s) || width == 0 || height == 0) {\n+        return;\n+    }\n+\n+    switch (FIELD_EX32(ctrl, CTRL, WORD_LENGTH)) {\n+    case CTRL_WORD_LENGTH_16:\n+        s->src_bpp = 2;\n+        fn = imx6ul_lcdif_draw_line_rgb565;\n+        break;\n+    case CTRL_WORD_LENGTH_24:\n+        s->src_bpp = 4;\n+        fn = imx6ul_lcdif_draw_line_xrgb8888;\n+        break;\n+    default:\n+        return;\n+    }\n+\n+    if (surface_width(surface) != width || surface_height(surface) != height) {\n+        qemu_console_resize(s->con, width, height);\n+        surface = qemu_console_surface(s->con);\n+        s->invalidate = true;\n+    }\n+\n+    src_width = width * s->src_bpp;\n+    if (s->invalidate || s->fb_base != frame_base ||\n+        s->src_width != src_width || s->rows != height) {\n+        framebuffer_update_memory_section(&s->fbsection, get_system_memory(),\n+                                          frame_base, height, src_width);\n+        s->fb_base = frame_base;\n+        s->src_width = src_width;\n+        s->rows = height;\n+    }\n+\n+    framebuffer_update_display(surface, &s->fbsection, width, height,\n+                               src_width, surface_stride(surface), 0,\n+                               s->invalidate, fn, s, &first, &last);\n+    if (first >= 0) {\n+        dpy_gfx_update(s->con, 0, first, width, last - first + 1);\n+    }\n+\n+    s->invalidate = false;\n+}\n+\n+static void imx6ul_lcdif_invalidate_display(void *opaque)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+\n+    s->invalidate = true;\n+}\n+\n+static const GraphicHwOps imx6ul_lcdif_graphic_ops = {\n+    .invalidate = imx6ul_lcdif_invalidate_display,\n+    .gfx_update = imx6ul_lcdif_update_display,\n+};\n+\n+static void imx6ul_lcdif_frame_timer_cb(void *opaque)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+\n+    if (!imx6ul_lcdif_is_running(s) || imx6ul_lcdif_frame_done_pending(s)) {\n+        return;\n+    }\n+\n+    imx6ul_lcdif_frame_done(s);\n+}\n+\n+static uint64_t imx6ul_lcdif_read(void *opaque, hwaddr offset, unsigned size)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+    hwaddr reg = offset & ~0xf;\n+    uint32_t idx;\n+\n+    assert(size == 4);\n+    assert(!(offset & 0x3));\n+    assert(offset < LCDIF_MMIO_SIZE);\n+\n+    idx = reg >> 4;\n+    if (idx >= ARRAY_SIZE(s->regs)) {\n+        return 0;\n+    }\n+\n+    return s->regs[idx];\n+}\n+\n+static void imx6ul_lcdif_write(void *opaque, hwaddr offset,\n+                               uint64_t value, unsigned size)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+    hwaddr reg = offset & ~0xf;\n+    uint32_t idx;\n+    uint32_t oldv;\n+\n+    assert(size == 4);\n+    assert(!(offset & 0x3));\n+    assert(offset < LCDIF_MMIO_SIZE);\n+\n+    if (!imx6ul_lcdif_reg_exists(reg)) {\n+        return;\n+    }\n+\n+    idx = reg >> 4;\n+    oldv = s->regs[idx];\n+\n+    switch (offset & 0xf) {\n+    case 0:\n+        s->regs[idx] = (uint32_t)value;\n+        break;\n+    case REG_SET:\n+        if (!imx6ul_lcdif_reg_has_setclr(reg)) {\n+            return;\n+        }\n+        s->regs[idx] = oldv | (uint32_t)value;\n+        break;\n+    case REG_CLR:\n+        if (!imx6ul_lcdif_reg_has_setclr(reg)) {\n+            return;\n+        }\n+        s->regs[idx] = oldv & ~(uint32_t)value;\n+        break;\n+    case REG_TOG:\n+        if (!imx6ul_lcdif_reg_has_setclr(reg)) {\n+            return;\n+        }\n+        s->regs[idx] = oldv ^ (uint32_t)value;\n+        break;\n+    default:\n+        g_assert_not_reached();\n+    }\n+\n+    switch (reg) {\n+    case A_CTRL:\n+        if (!FIELD_EX32(oldv, CTRL, RUN) &&\n+            FIELD_EX32(s->regs[idx], CTRL, RUN)) {\n+            s->invalidate = true;\n+            graphic_hw_invalidate(s->con);\n+            imx6ul_lcdif_maybe_schedule_frame(s);\n+            break;\n+        }\n+        if (FIELD_EX32(oldv, CTRL, RUN) &&\n+            !FIELD_EX32(s->regs[idx], CTRL, RUN)) {\n+            timer_del(s->frame_timer);\n+        }\n+        break;\n+    case A_CTRL1:\n+        if (FIELD_EX32(oldv, CTRL1, CUR_FRAME_DONE_IRQ) &&\n+            !FIELD_EX32(s->regs[idx], CTRL1, CUR_FRAME_DONE_IRQ)) {\n+            imx6ul_lcdif_maybe_schedule_frame(s);\n+        }\n+        break;\n+    case A_V4_TRANSFER_COUNT:\n+        s->invalidate = true;\n+        graphic_hw_invalidate(s->con);\n+        break;\n+    case A_V4_CUR_BUF:\n+        s->invalidate = true;\n+        graphic_hw_invalidate(s->con);\n+        break;\n+    case A_V4_NEXT_BUF:\n+        s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF] = s->regs[idx];\n+        imx6ul_lcdif_frame_done(s);\n+        s->invalidate = true;\n+        graphic_hw_invalidate(s->con);\n+        imx6ul_lcdif_maybe_schedule_frame(s);\n+        return;\n+    case A_AS_NEXT_BUF:\n+        imx6ul_lcdif_frame_done(s);\n+        imx6ul_lcdif_maybe_schedule_frame(s);\n+        return;\n+    default:\n+        break;\n+    }\n+\n+    imx6ul_lcdif_update_irq(s);\n+}\n+\n+static const MemoryRegionOps imx6ul_lcdif_ops = {\n+    .read = imx6ul_lcdif_read,\n+    .write = imx6ul_lcdif_write,\n+    .endianness = DEVICE_LITTLE_ENDIAN,\n+    .valid = {\n+        .min_access_size = 4,\n+        .max_access_size = 4,\n+        .unaligned = false,\n+    },\n+};\n+\n+static void imx6ul_lcdif_reset(DeviceState *dev)\n+{\n+    IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);\n+\n+    memset(s->regs, 0, sizeof(s->regs));\n+    s->regs[IMX6UL_LCDIF_REG_CTRL1] = LCDIF_RESET_CTRL1;\n+    s->fb_base = 0;\n+    s->src_width = 0;\n+    s->rows = 0;\n+    s->src_bpp = 0;\n+    s->invalidate = true;\n+    timer_del(s->frame_timer);\n+    imx6ul_lcdif_update_irq(s);\n+}\n+\n+static int imx6ul_lcdif_post_load(void *opaque, int version_id)\n+{\n+    IMX6ULLCDIFState *s = opaque;\n+\n+    s->fb_base = 0;\n+    s->src_width = 0;\n+    s->rows = 0;\n+    s->src_bpp = 0;\n+    s->invalidate = true;\n+\n+    imx6ul_lcdif_update_irq(s);\n+    if (imx6ul_lcdif_is_running(s) &&\n+        !imx6ul_lcdif_frame_done_pending(s) &&\n+        !timer_pending(s->frame_timer)) {\n+        imx6ul_lcdif_schedule_frame(s);\n+    }\n+\n+    return 0;\n+}\n+\n+static const VMStateDescription vmstate_imx6ul_lcdif = {\n+    .name = TYPE_IMX6UL_LCDIF,\n+    .version_id = 1,\n+    .minimum_version_id = 1,\n+    .post_load = imx6ul_lcdif_post_load,\n+    .fields = (const VMStateField[]) {\n+        VMSTATE_UINT32_ARRAY(regs, IMX6ULLCDIFState, IMX6UL_LCDIF_REGS_NUM),\n+        VMSTATE_TIMER_PTR(frame_timer, IMX6ULLCDIFState),\n+        VMSTATE_END_OF_LIST()\n+    },\n+};\n+\n+static void imx6ul_lcdif_realize(DeviceState *dev, Error **errp)\n+{\n+    IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);\n+\n+    s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,\n+                                  imx6ul_lcdif_frame_timer_cb, s);\n+    s->invalidate = true;\n+    memory_region_init_io(&s->iomem, OBJECT(dev), &imx6ul_lcdif_ops, s,\n+                          TYPE_IMX6UL_LCDIF, LCDIF_MMIO_SIZE);\n+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);\n+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);\n+    s->con = graphic_console_init(dev, 0, &imx6ul_lcdif_graphic_ops, s);\n+}\n+\n+static void imx6ul_lcdif_unrealize(DeviceState *dev)\n+{\n+    IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);\n+\n+    timer_del(s->frame_timer);\n+    timer_free(s->frame_timer);\n+    s->frame_timer = NULL;\n+\n+    if (s->con) {\n+        graphic_console_close(s->con);\n+        s->con = NULL;\n+    }\n+}\n+\n+static void imx6ul_lcdif_class_init(ObjectClass *klass, const void *data)\n+{\n+    DeviceClass *dc = DEVICE_CLASS(klass);\n+\n+    dc->realize = imx6ul_lcdif_realize;\n+    dc->unrealize = imx6ul_lcdif_unrealize;\n+    dc->vmsd = &vmstate_imx6ul_lcdif;\n+    device_class_set_legacy_reset(dc, imx6ul_lcdif_reset);\n+    dc->desc = \"i.MX6UL LCDIF\";\n+}\n+\n+static const TypeInfo imx6ul_lcdif_info = {\n+    .name = TYPE_IMX6UL_LCDIF,\n+    .parent = TYPE_SYS_BUS_DEVICE,\n+    .instance_size = sizeof(IMX6ULLCDIFState),\n+    .class_init = imx6ul_lcdif_class_init,\n+};\n+\n+static void imx6ul_lcdif_register_types(void)\n+{\n+    type_register_static(&imx6ul_lcdif_info);\n+}\n+\n+type_init(imx6ul_lcdif_register_types)\ndiff --git a/hw/display/meson.build b/hw/display/meson.build\nindex e730c289b1..ffecedbf70 100644\n--- a/hw/display/meson.build\n+++ b/hw/display/meson.build\n@@ -12,6 +12,7 @@ system_ss.add(when: ['CONFIG_VGA_CIRRUS', 'CONFIG_VGA_ISA'], if_true: files('cir\n system_ss.add(when: 'CONFIG_G364FB', if_true: files('g364fb.c'))\n system_ss.add(when: 'CONFIG_JAZZ_LED', if_true: files('jazz_led.c'))\n system_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c'))\n+system_ss.add(when: 'CONFIG_IMX6UL_LCDIF', if_true: files('imx6ul_lcdif.c'))\n system_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c'))\n system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c'))\n system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c'))\ndiff --git a/include/hw/display/imx6ul_lcdif.h b/include/hw/display/imx6ul_lcdif.h\nnew file mode 100644\nindex 0000000000..42fee2fd1d\n--- /dev/null\n+++ b/include/hw/display/imx6ul_lcdif.h\n@@ -0,0 +1,37 @@\n+/*\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ *\n+ * i.MX6UL LCDIF controller\n+ *\n+ * Copyright (c) 2026 Yucai Liu <1486344514@qq.com>\n+ */\n+\n+#ifndef IMX6UL_LCDIF_H\n+#define IMX6UL_LCDIF_H\n+\n+#include \"hw/core/sysbus.h\"\n+#include \"qom/object.h\"\n+#include \"qemu/timer.h\"\n+#include \"ui/console.h\"\n+\n+#define TYPE_IMX6UL_LCDIF \"imx6ul-lcdif\"\n+#define IMX6UL_LCDIF_REGS_NUM ((0x230 >> 4) + 1)\n+OBJECT_DECLARE_SIMPLE_TYPE(IMX6ULLCDIFState, IMX6UL_LCDIF)\n+\n+struct IMX6ULLCDIFState {\n+    SysBusDevice parent_obj;\n+\n+    MemoryRegion iomem;\n+    MemoryRegionSection fbsection;\n+    qemu_irq irq;\n+    QemuConsole *con;\n+    QEMUTimer *frame_timer;\n+    uint32_t fb_base;\n+    uint32_t src_width;\n+    uint32_t rows;\n+    uint8_t src_bpp;\n+    bool invalidate;\n+    uint32_t regs[IMX6UL_LCDIF_REGS_NUM];\n+};\n+\n+#endif /* IMX6UL_LCDIF_H */\n","prefixes":["PULL","39/63"]}