Browse Source

[dm][graphic] support dm mode

1. Add backlight framework for graphic.
2. Add framebuffer and plane, power, EDID for graphic framework
3. Add boot logo render for graphic
4. Update lcd.h

Signed-off-by: GuEe-GUI <2991707448@qq.com>
GuEe-GUI 1 tháng trước cách đây
mục cha
commit
5abecc1fd0

+ 12 - 1
components/drivers/graphic/Kconfig

@@ -1,3 +1,14 @@
 config RT_USING_LCD
-    bool "Using LCD graphic drivers"
+    bool "Using LCD graphic drivers" if !RT_USING_DM
     default n
+
+menuconfig RT_USING_GRAPHIC
+    bool "Using Graphics device drivers"
+    depends on RT_USING_DM
+    default n
+
+if RT_USING_GRAPHIC
+    rsource "backlight/Kconfig"
+    rsource "framebuffer/Kconfig"
+    rsource "logo/Kconfig"
+endif

+ 23 - 0
components/drivers/graphic/SConscript

@@ -0,0 +1,23 @@
+from building import *
+
+group = []
+objs = []
+
+if not GetDepend(['RT_USING_GRAPHIC']):
+    Return('group')
+
+cwd = GetCurrentDir()
+list = os.listdir(cwd)
+CPPPATH = [cwd + '/../include']
+
+src = ['graphic.c', 'graphic_primary.c', 'graphic_simple.c']
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+
+for d in list:
+    path = os.path.join(cwd, d)
+    if os.path.isfile(os.path.join(path, 'SConscript')):
+        objs = objs + SConscript(os.path.join(d, 'SConscript'))
+objs = objs + group
+
+Return('objs')

+ 7 - 0
components/drivers/graphic/backlight/Kconfig

@@ -0,0 +1,7 @@
+menuconfig RT_GRAPHIC_BACKLIGHT
+    bool "Backlight support"
+    default n
+
+if RT_GRAPHIC_BACKLIGHT
+    osource "$(SOC_DM_GRAPHIC_BACKLIGHT_DIR)/Kconfig"
+endif

+ 14 - 0
components/drivers/graphic/backlight/SConscript

@@ -0,0 +1,14 @@
+from building import *
+
+group   = []
+
+if not GetDepend(['RT_GRAPHIC_BACKLIGHT']):
+    Return('group')
+
+cwd     = GetCurrentDir()
+CPPPATH = [cwd + '/../../include']
+
+src     = ['backlight.c']
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+Return('group')

+ 231 - 0
components/drivers/graphic/backlight/backlight.c

@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rtthread.h>
+
+#define DBG_TAG "rtdm.backlight"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#include <stdlib.h>
+#include <drivers/core/dm.h>
+#include <drivers/backlight.h>
+
+static struct rt_dm_ida backlight_ida = RT_DM_IDA_INIT(GRAPHIC_BACKLIGHT);
+
+static rt_ssize_t _backlight_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
+{
+    rt_ssize_t res;
+    int brightness_len;
+    rt_uint32_t brightness;
+    char string[sizeof("4294967295")];
+    struct rt_backlight_device *bl = rt_container_of(dev, struct rt_backlight_device, parent);
+
+    if ((res = rt_backlight_get_brightness(bl, &brightness)))
+    {
+        return res;
+    }
+
+    brightness_len = rt_sprintf(string, "%u", brightness);
+
+    if (pos < brightness_len)
+    {
+        size = rt_min_t(rt_size_t, size, brightness_len - pos);
+        rt_strncpy(buffer, &string[pos], size);
+
+        return size;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+static rt_ssize_t _backlight_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
+{
+    rt_ssize_t res;
+    rt_uint32_t brightness = atoi(buffer);
+    struct rt_backlight_device *bl = rt_container_of(dev, struct rt_backlight_device, parent);
+
+    if (brightness > bl->props.max_brightness)
+    {
+        LOG_D("%s: brightness(%u) > max_brightness(%u)",
+                rt_dm_dev_get_name(dev), brightness, bl->props.max_brightness);
+
+        return -RT_EINVAL;
+    }
+
+    if ((res = rt_backlight_set_brightness(bl, brightness)))
+    {
+        return res;
+    }
+
+    LOG_D("%s: brightness to %u", rt_dm_dev_get_name(dev), brightness);
+
+    return size;
+}
+
+#ifdef RT_USING_DEVICE_OPS
+const static struct rt_device_ops _backlight_ops =
+{
+    .read = _backlight_read,
+    .write = _backlight_write,
+};
+#endif
+
+rt_err_t rt_backlight_register(struct rt_backlight_device *bl)
+{
+    rt_err_t err;
+    int device_id;
+    const char *dev_name;
+
+    if (!bl || !bl->ops)
+    {
+        return -RT_EINVAL;
+    }
+
+    if ((device_id = rt_dm_ida_alloc(&backlight_ida)) < 0)
+    {
+        return -RT_EFULL;
+    }
+
+    rt_dm_dev_set_name(&bl->parent, "backlight%u", device_id);
+    dev_name = rt_dm_dev_get_name(&bl->parent);
+
+    rt_mutex_init(&bl->lock, dev_name, RT_IPC_FLAG_PRIO);
+
+    bl->parent.type = RT_Device_Class_Char;
+#ifdef RT_USING_DEVICE_OPS
+    bl->parent.ops = &_backlight_ops;
+#else
+    bl->parent.read = _backlight_read;
+    bl->parent.write = _backlight_write;
+#endif
+    bl->parent.master_id = backlight_ida.master_id;
+    bl->parent.device_id = device_id;
+
+    if ((err = rt_device_register(&bl->parent, dev_name, RT_DEVICE_FLAG_RDWR)))
+    {
+        rt_dm_ida_free(&backlight_ida, device_id);
+
+        return err;
+    }
+
+    return RT_EOK;
+}
+
+rt_err_t rt_backlight_unregister(struct rt_backlight_device *bl)
+{
+    if (!bl)
+    {
+        return -RT_EINVAL;
+    }
+
+    rt_backlight_set_power(bl, RT_BACKLIGHT_POWER_POWERDOWN);
+
+    rt_dm_ida_free(&backlight_ida, bl->parent.device_id);
+
+    rt_device_unregister(&bl->parent);
+
+    return RT_EOK;
+}
+
+rt_err_t rt_backlight_set_power(struct rt_backlight_device *bl, enum rt_backlight_power power)
+{
+    rt_err_t err;
+    enum rt_backlight_power old_power;
+
+    if (!bl || power >= RT_BACKLIGHT_POWER_NR)
+    {
+        return -RT_EINVAL;
+    }
+
+    rt_mutex_take(&bl->lock, RT_WAITING_FOREVER);
+
+    old_power = bl->props.power;
+    bl->props.power = power;
+
+    if ((err = bl->ops->update_status(bl)))
+    {
+        bl->props.power = old_power;
+    }
+
+    rt_mutex_release(&bl->lock);
+
+    return err;
+}
+
+rt_err_t rt_backlight_get_power(struct rt_backlight_device *bl, enum rt_backlight_power *out_power)
+{
+    if (!bl || !out_power)
+    {
+        return -RT_EINVAL;
+    }
+
+    rt_mutex_take(&bl->lock, RT_WAITING_FOREVER);
+
+    *out_power = bl->props.power;
+
+    rt_mutex_release(&bl->lock);
+
+    return RT_EOK;
+}
+
+rt_err_t rt_backlight_set_brightness(struct rt_backlight_device *bl, rt_uint32_t brightness)
+{
+    rt_err_t err;
+    rt_uint32_t old_brightness;
+
+    if (!bl || brightness > bl->props.max_brightness)
+    {
+        return -RT_EINVAL;
+    }
+
+    rt_mutex_take(&bl->lock, RT_WAITING_FOREVER);
+
+    old_brightness = bl->props.brightness;
+    bl->props.brightness = brightness;
+
+    if ((err = bl->ops->update_status(bl)))
+    {
+        bl->props.brightness = old_brightness;
+    }
+
+    rt_mutex_release(&bl->lock);
+
+    return err;
+}
+
+rt_err_t rt_backlight_get_brightness(struct rt_backlight_device *bl, rt_uint32_t *out_brightness)
+{
+    rt_err_t err;
+
+    if (!bl || !out_brightness)
+    {
+        return -RT_EINVAL;
+    }
+
+    rt_mutex_take(&bl->lock, RT_WAITING_FOREVER);
+
+    if (bl->ops->get_brightness)
+    {
+        err = bl->ops->get_brightness(bl, out_brightness);
+    }
+    else
+    {
+        *out_brightness = rt_backlight_power_brightness(bl);
+
+        err = RT_EOK;
+    }
+
+    rt_mutex_release(&bl->lock);
+
+    return err;
+}

+ 8 - 0
components/drivers/graphic/framebuffer/Kconfig

@@ -0,0 +1,8 @@
+menuconfig RT_GRAPHIC_FB
+    bool "LCD and Frame buffer support"
+    select RT_USING_LCD
+    default y
+
+if RT_GRAPHIC_FB
+    osource "$(SOC_DM_GRAPHIC_FB_DIR)/Kconfig"
+endif

+ 16 - 0
components/drivers/graphic/framebuffer/SConscript

@@ -0,0 +1,16 @@
+from building import *
+
+group   = []
+
+if not GetDepend(['RT_GRAPHIC_FB']):
+    Return('group')
+
+cwd     = GetCurrentDir()
+list    = os.listdir(cwd)
+CPPPATH = [cwd + '/../../include']
+
+src     = []
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+
+Return('group')

+ 1495 - 0
components/drivers/graphic/graphic.c

@@ -0,0 +1,1495 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "rtdm.graphic"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define raw_to_graphic(dev) rt_container_of(dev, struct rt_graphic_device, parent)
+
+struct fb_format
+{
+    rt_uint32_t mode;
+    rt_uint32_t bits_per_pixel;
+    struct fb_bitfield red;
+    struct fb_bitfield green;
+    struct fb_bitfield blue;
+    struct fb_bitfield transp;
+};
+
+static const struct fb_format graphic_formats[] =
+{
+    { RTGRAPHIC_PIXEL_FORMAT_GRAY4,         4,  },
+    { RTGRAPHIC_PIXEL_FORMAT_GRAY16,        16, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB332,        8,  {  5, 3 }, { 2, 3 }, {  0, 2 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB444,        12, {  8, 4 }, { 4, 4 }, {  0, 4 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB565,        16, { 11, 5 }, { 5, 6 }, {  0, 5 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB565P,       16, {  0, 5 }, { 5, 6 }, { 11, 5 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_BGR565,        16, {  0, 5 }, { 5, 6 }, { 11, 5 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB666,        18, { 12, 6 }, { 6, 6 }, {  0, 6 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_RGB888,        24, { 16, 8 }, { 8, 8 }, {  0, 8 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_BGR888,        24, {  0, 8 }, { 8, 8 }, { 16, 8 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_ARGB888,       32, { 16, 8 }, { 8, 8 }, {  0, 8 }, { 24, 8 }, },
+    { RTGRAPHIC_PIXEL_FORMAT_ABGR888,       32, {  0, 8 }, { 8, 8 }, { 16, 8 }, { 24, 8 }, },
+};
+
+/* RT-Thread device max id is 255 */
+static rt_uint8_t fbcon_map[256] = {};
+
+static struct rt_dm_ida graphic_ida = RT_DM_IDA_INIT(GRAPHIC_FRAMEBUFFER);
+
+rt_inline void spin_lock(struct rt_spinlock *spinlock)
+{
+    rt_hw_spin_lock(&spinlock->lock);
+}
+
+rt_inline void spin_unlock(struct rt_spinlock *spinlock)
+{
+    rt_hw_spin_unlock(&spinlock->lock);
+}
+
+static rt_bool_t plane_need_update(struct rt_graphic_plane *plane)
+{
+    if (plane->ops->update)
+    {
+        if (!plane->graphic->update_timer)
+        {
+            return RT_TRUE;
+        }
+    }
+
+    return RT_FALSE;
+}
+
+static struct rt_graphic_plane *plane_get_current(struct rt_graphic_device *gdev)
+{
+    if (gdev->ops->current_plane)
+    {
+        return gdev->ops->current_plane(gdev);
+    }
+
+    return gdev->primary_plane;
+}
+
+static rt_err_t plane_fb_remap(struct rt_graphic_plane *plane,
+        rt_uint32_t mode, struct rt_device_rect_info *rect)
+{
+    rt_err_t err;
+
+    if (plane->ops->fb_cleanup && (err = plane->ops->fb_cleanup(plane)))
+    {
+        return err;
+    }
+
+    if (!(err = plane->ops->fb_remap(plane, mode, rect)))
+    {
+        plane->mode = mode;
+        plane->x = rect->x;
+        plane->y = rect->y;
+        plane->width = rect->width;
+        plane->height = rect->height;
+    }
+
+    return err;
+}
+
+static rt_err_t plane_fb_pan_display(struct rt_graphic_plane *plane,
+        struct rt_device_rect_info *rect)
+{
+    void *framebuffer_end = plane->framebuffer;
+    rt_size_t byte_per_pixel = plane->bits_per_pixel / 8;
+
+    framebuffer_end += rect->x * byte_per_pixel;
+    framebuffer_end += rect->y * plane->line_length;
+    framebuffer_end += rect->width * rect->height * byte_per_pixel;
+
+    if (framebuffer_end < plane->framebuffer + plane->framebuffer_len)
+    {
+        return plane->ops->fb_pan_display(plane, rect);
+    }
+
+    return -RT_EINVAL;
+}
+
+static rt_err_t graphic_dpms_switch(struct rt_graphic_device *gdev, rt_uint32_t dpms)
+{
+    rt_err_t err;
+
+    if (!(err = gdev->ops->dpms_switch(gdev, dpms)))
+    {
+        gdev->dpms = dpms;
+    }
+
+    return err;
+}
+
+static rt_err_t _graphic_open(rt_device_t dev, rt_uint16_t oflag)
+{
+    struct rt_graphic_device *gdev = raw_to_graphic(dev);
+    struct rt_graphic_plane *plane = gdev->primary_plane;
+
+    if (dev->ref_count > 0 && (oflag & RT_DEVICE_OFLAG_WRONLY))
+    {
+        return -RT_EBUSY;
+    }
+
+    if (plane->ops->fb_pan_display)
+    {
+        struct rt_device_rect_info rect;
+
+        rect.x = 0;
+        rect.y = 0;
+        rect.width = plane->width;
+        rect.height = plane->height;
+
+        return plane->ops->fb_pan_display(plane, &rect);
+    }
+
+    return RT_EOK;
+}
+
+static rt_ssize_t _graphic_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
+{
+    rt_ssize_t res;
+    struct rt_graphic_device *gdev = raw_to_graphic(dev);
+    struct rt_graphic_plane *plane = gdev->primary_plane;
+
+    res = rt_min_t(rt_ssize_t, plane->framebuffer_len - pos, size);
+
+    if (res > 0)
+    {
+        rt_memcpy(buffer, plane->framebuffer + pos, res);
+    }
+    else
+    {
+        res = 0;
+    }
+
+    return res;
+}
+
+static rt_ssize_t _graphic_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
+{
+    rt_ssize_t res;
+    struct rt_graphic_device *gdev = raw_to_graphic(dev);
+    struct rt_graphic_plane *plane = gdev->primary_plane;
+
+    res = rt_min_t(rt_ssize_t, plane->framebuffer_len - pos, size);
+
+    if (res > 0)
+    {
+        rt_memcpy(plane->framebuffer + pos, buffer, res);
+    }
+    else
+    {
+        res = 0;
+    }
+
+    return res;
+}
+
+static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args)
+{
+    rt_err_t err = RT_EOK;
+    rt_bool_t need_schedule = RT_FALSE;
+    struct rt_graphic_device *gdev = raw_to_graphic(dev);
+
+_retry:
+    if (need_schedule)
+    {
+        rt_thread_yield();
+    }
+
+    spin_lock(&gdev->lock);
+
+    switch (cmd)
+    {
+    case RT_DEVICE_CTRL_CURSOR_SET_POSITION:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = gdev->cursor_plane;
+
+            if (plane)
+            {
+                rt_uint32_t position = (rt_uint32_t)(rt_ubase_t)args;
+
+                plane->x = position >> 16;
+                plane->y = position & 0xffff;
+
+                if (plane_need_update(plane))
+                {
+                    struct rt_device_rect_info rect;
+
+                    rect.x = plane->x;
+                    rect.y = plane->y;
+                    /* Ask driver to update position only */
+                    rect.width = 0;
+                    rect.height = 0;
+
+                    err = plane->ops->update(plane, &rect);
+                }
+            }
+            else
+            {
+                err = -RT_ENOSYS;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RT_DEVICE_CTRL_CURSOR_SET_TYPE:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = gdev->cursor_plane;
+
+            if (plane)
+            {
+                struct rt_device_rect_info rect =
+                {
+                    .x = 0,
+                    .y = 0,
+                    .width = plane->width,
+                    .height = plane->height,
+                };
+
+                if (!plane->framebuffer)
+                {
+                    rt_uint32_t mode;
+
+                    if (plane->mode == RTGRAPHIC_PIXEL_FORMAT_MONO)
+                    {
+                        mode = plane->modes[0];
+                    }
+                    else
+                    {
+                        mode = plane->mode;
+                    }
+
+                    if ((err = plane_fb_remap(plane, mode, &rect)))
+                    {
+                        break;
+                    }
+                }
+
+                rt_memcpy(plane->framebuffer, args, plane->screen_len);
+
+                /* Force to update */
+                err = plane->ops->update(plane, &rect);
+            }
+            else
+            {
+                err = -RT_ENOSYS;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_RECT_UPDATE:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            if (plane_need_update(plane))
+            {
+                err = plane->ops->update(plane, args);
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_POWERON:
+        if (gdev->ops->dpms_switch)
+        {
+            err = graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_ON);
+        }
+    #ifdef RT_GRAPHIC_BACKLIGHT
+        if (!err && gdev->backlight)
+        {
+            spin_unlock(&gdev->lock);
+            err = rt_backlight_set_power(gdev->backlight, RT_BACKLIGHT_POWER_NORMAL);
+            spin_lock(&gdev->lock);
+        }
+    #endif /* RT_GRAPHIC_BACKLIGHT */
+        break;
+
+    case RTGRAPHIC_CTRL_POWEROFF:
+        if (gdev->ops->dpms_switch)
+        {
+            err = graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_OFF);
+        }
+    #ifdef RT_GRAPHIC_BACKLIGHT
+        if (!err && gdev->backlight)
+        {
+            spin_unlock(&gdev->lock);
+            err = rt_backlight_set_power(gdev->backlight, RT_BACKLIGHT_POWER_POWERDOWN);
+            spin_lock(&gdev->lock);
+        }
+    #endif /* RT_GRAPHIC_BACKLIGHT */
+        break;
+
+    case RTGRAPHIC_CTRL_GET_INFO:
+        if (args)
+        {
+            struct rt_device_graphic_info *info = args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            info->pixel_format = plane->mode;
+            info->bits_per_pixel = plane->bits_per_pixel;
+            info->pitch = plane->line_length;
+            info->width = plane->width;
+            info->height = plane->height;
+            info->framebuffer = plane->framebuffer;
+            info->smem_len = plane->framebuffer_len;
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_SET_MODE:
+        if (args)
+        {
+            rt_uint32_t mode = (rt_uint32_t)(rt_ubase_t)args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            if (mode != plane->mode)
+            {
+                err = -RT_ENOSYS;
+
+                if (plane->modes_nr > 1)
+                {
+                    for (int i = 0; i < plane->modes_nr; ++i)
+                    {
+                        if (mode == plane->modes[i])
+                        {
+                            struct rt_device_rect_info rect =
+                            {
+                                .x = plane->x,
+                                .y = plane->y,
+                                .width = plane->width,
+                                .height = plane->height,
+                            };
+
+                            err = plane_fb_remap(plane, mode, &rect);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_GET_EXT:
+        if (args)
+        {
+            rt_memcpy(args, &gdev->edid, sizeof(gdev->edid));
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_SET_BRIGHTNESS:
+        if (gdev->ops->set_brightness)
+        {
+            err = gdev->ops->set_brightness(gdev, (rt_uint32_t)(rt_ubase_t)args);
+        }
+    #ifdef RT_GRAPHIC_BACKLIGHT
+        if (!err && gdev->backlight)
+        {
+            spin_unlock(&gdev->lock);
+
+            return rt_backlight_set_brightness(gdev->backlight, (rt_uint32_t)(rt_ubase_t)args);
+        }
+    #endif /* RT_GRAPHIC_BACKLIGHT */
+        break;
+
+    case RTGRAPHIC_CTRL_GET_BRIGHTNESS:
+        if (args)
+        {
+            if (gdev->ops->get_brightness)
+            {
+                err = gdev->ops->get_brightness(gdev, args);
+            }
+            else
+            {
+                *(rt_uint32_t *)args = RT_UINT32_MAX >> 1;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+    #ifdef RT_GRAPHIC_BACKLIGHT
+        if (!err && gdev->backlight)
+        {
+            spin_unlock(&gdev->lock);
+
+            return rt_backlight_get_brightness(gdev->backlight, args);
+        }
+    #endif /* RT_GRAPHIC_BACKLIGHT */
+        break;
+
+    case RTGRAPHIC_CTRL_GET_MODE:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            *(rt_uint32_t *)args = plane->mode;
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_GET_STATUS:
+        if (args)
+        {
+            if (gdev->ops->get_status)
+            {
+                err = gdev->ops->get_status(gdev, args);
+            }
+            else
+            {
+                err = -RT_ENOSYS;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_PAN_DISPLAY:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            if (plane->ops->fb_pan_display)
+            {
+                rt_size_t offset;
+                struct rt_device_rect_info rect;
+
+                offset = (rt_size_t)(args - plane->framebuffer);
+
+                rect.x = (offset % plane->line_length) / (plane->bits_per_pixel / 8);
+                rect.y = offset / plane->line_length;
+                rect.width = plane->width;
+                rect.height = plane->height;
+
+                err = plane_fb_pan_display(plane, &rect);
+            }
+            else
+            {
+                err = -RT_ENOSYS;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case RTGRAPHIC_CTRL_WAIT_VSYNC:
+        if (gdev->ops->wait_vsync)
+        {
+            err = gdev->ops->wait_vsync(gdev);
+        }
+        break;
+
+    case RT_DEVICE_CTRL_NOTIFY_SET:
+        if (args)
+        {
+            if (rt_atomic_load(&gdev->event_notifying) == RT_TRUE)
+            {
+                need_schedule = RT_TRUE;
+
+                spin_unlock(&gdev->lock);
+                goto _retry;
+            }
+
+            rt_memcpy(&gdev->event_notify, args, sizeof(gdev->event_notify));
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOGET_VSCREENINFO:
+        if (args)
+        {
+            struct fb_var_screeninfo *var = args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            rt_memset(var, 0, sizeof(*var));
+
+            var->xres = plane->width;
+            var->yres = plane->height;
+            var->xres_virtual = plane->width;
+            var->yres_virtual = plane->height * (plane->framebuffer_len / plane->screen_len);
+            var->bits_per_pixel = plane->bits_per_pixel;
+
+            if (plane == gdev->primary_plane)
+            {
+                var->width = gdev->edid.width_cm ? gdev->edid.width_cm * 10 : -1;
+                var->height = gdev->edid.height_cm ? gdev->edid.height_cm * 10 : -1;
+            }
+            else
+            {
+                var->width = -1;
+                var->height = -1;
+            }
+
+            if (plane->mode == RTGRAPHIC_PIXEL_FORMAT_GRAY4 ||
+                plane->mode == RTGRAPHIC_PIXEL_FORMAT_GRAY16)
+            {
+                var->grayscale = 1;
+            }
+            else
+            {
+                const struct fb_format *fmt = &graphic_formats[0];
+
+                for (int i = 0; i < RT_ARRAY_SIZE(graphic_formats); ++i, ++fmt)
+                {
+                    if (fmt->mode == plane->mode)
+                    {
+                        rt_memcpy(&var->red, &fmt->red, sizeof(fmt->red));
+                        rt_memcpy(&var->green, &fmt->green, sizeof(fmt->green));
+                        rt_memcpy(&var->blue, &fmt->blue, sizeof(fmt->blue));
+                        rt_memcpy(&var->transp, &fmt->transp, sizeof(fmt->transp));
+
+                        break;
+                    }
+                }
+            }
+
+            if (gdev->update_timer)
+            {
+                rt_uint64_t update_ps;
+                rt_tick_t update_tick;
+
+                rt_timer_control(gdev->update_timer, RT_TIMER_CTRL_GET_TIME, &update_tick);
+                /*
+                 *           1s            update_ms * 1000
+                 *  -------------------- = ----------------- (second/tick)
+                 *   RT_TICK_PER_SECOND       update_tick
+                 *
+                 *  1000000ps = 1ms
+                 */
+                update_ps = (update_tick * 1000000) / (RT_TICK_PER_SECOND * 1000);
+
+                var->pixclock = update_ps / (var->xres * var->yres);
+            }
+            else
+            {
+                var->pixclock = (RT_GRAPHIC_UPDATE_MS * 1000000) / (var->xres * var->yres);
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOPUT_VSCREENINFO:
+        if (args)
+        {
+            rt_uint32_t mode;
+            struct fb_format fmt;
+            struct rt_device_rect_info rect;
+            struct fb_var_screeninfo *var = args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+            const rt_size_t cmp_offset = rt_offsetof(struct fb_format, bits_per_pixel);
+            const rt_size_t cmp_size = sizeof(struct fb_format) - cmp_offset;
+
+            if (!plane->ops->fb_pan_display)
+            {
+                if (var->xres != plane->width || var->yres != plane->height ||
+                    var->xoffset != plane->x || var->yoffset != plane->y)
+                {
+                    err = -RT_ENOSYS;
+                    break;
+                }
+            }
+
+            mode = plane->mode;
+            rect.x = plane->x;
+            rect.y = plane->y;
+            rect.width = plane->width;
+            rect.height = plane->height;
+
+            fmt.bits_per_pixel = var->bits_per_pixel;
+            rt_memcpy(&fmt.red, &var->red, sizeof(fmt.red));
+            rt_memcpy(&fmt.green, &var->green, sizeof(fmt.green));
+            rt_memcpy(&fmt.blue, &var->blue, sizeof(fmt.blue));
+            rt_memcpy(&fmt.transp, &var->transp, sizeof(fmt.transp));
+
+            for (int i = 0; i < RT_ARRAY_SIZE(graphic_formats); ++i)
+            {
+                void *cmp_to = ((void *)&fmt) + cmp_offset;
+                void *cmp_from = ((void *)&graphic_formats[i]) + cmp_offset;
+
+                if (!rt_memcmp(cmp_to, cmp_from, cmp_size))
+                {
+                    mode = graphic_formats[i].mode;
+                    break;
+                }
+            }
+
+            err = -RT_ENOSYS;
+
+            for (int i = 0; i < plane->modes_nr; ++i)
+            {
+                /* Check supported and commit */
+                if (plane->modes[i] == mode && plane->mode != mode)
+                {
+                    err = plane_fb_remap(plane, mode, &rect);
+                }
+            }
+
+            if (!err && plane->ops->fb_pan_display)
+            {
+                rect.x = var->xoffset;
+                rect.y = var->yoffset;
+                rect.width = var->xres;
+                rect.height = var->yres;
+
+                err = plane_fb_pan_display(plane, &rect);
+            }
+
+            if (!err && var->rotate && plane->ops->prop_set)
+            {
+                err = plane->ops->prop_set(plane, RT_GRAPHIC_PLANE_PROP_ROTATE,
+                        (void *)(rt_ubase_t)((var->rotate % 360) / 90));
+            }
+
+            if (!err && plane == gdev->primary_plane && plane->ops->update)
+            {
+                rt_uint32_t update_ms = 0;
+
+                if (var->pixclock)
+                {
+                    rt_uint64_t clock_cycles;
+
+                    clock_cycles = var->pixclock;
+                    clock_cycles *= rect.width;
+                    clock_cycles *= rect.height;
+                    /* Seconds in pico seconds */
+                    clock_cycles /= 1000000000000ULL;
+
+                    update_ms = (rt_uint32_t)clock_cycles * 1000;
+                }
+
+                err = rt_graphic_device_update_auto(gdev, update_ms);
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOGET_FSCREENINFO:
+        if (args)
+        {
+            struct fb_fix_screeninfo *fix = args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            rt_memset(fix, 0, sizeof(*fix));
+
+            rt_snprintf(fix->id, rt_min_t(int, sizeof(fix->id), RT_NAME_MAX),
+                    "%s", rt_dm_dev_get_name(&gdev->parent));
+
+            fix->smem_start = (unsigned long)rt_kmem_v2p(plane->framebuffer);
+            fix->smem_len = plane->framebuffer_len;
+            fix->mmio_start = fix->smem_start;
+            fix->mmio_len = plane->screen_len;
+            fix->line_length = plane->line_length;
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOGET_PIXELINFO:
+        if (args)
+        {
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            *(rt_uint32_t *)args = plane->mode;
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOPAN_DISPLAY:
+        if (args)
+        {
+            struct fb_var_screeninfo *var = args;
+            struct rt_graphic_plane *plane = plane_get_current(gdev);
+
+            if (plane->ops->fb_pan_display)
+            {
+                struct rt_device_rect_info rect;
+
+                rect.x = var->xoffset;
+                rect.y = var->yoffset;
+                rect.width = var->xres;
+                rect.height = var->yres;
+
+                err = plane_fb_pan_display(plane, &rect);
+            }
+            else
+            {
+                err = -RT_ENOSYS;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIO_CURSOR:
+        err = -RT_EINVAL;
+        break;
+
+    case FBIOGET_CON2FBMAP:
+        if (args)
+        {
+            struct fb_con2fbmap *con2fbmap = args;
+
+            if (con2fbmap->console < RT_ARRAY_SIZE(fbcon_map))
+            {
+                con2fbmap->framebuffer = fbcon_map[con2fbmap->console];
+            }
+            else
+            {
+                err = -RT_EFULL;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOPUT_CON2FBMAP:
+        if (args)
+        {
+            struct fb_con2fbmap *con2fbmap = args;
+
+            if (con2fbmap->console < RT_ARRAY_SIZE(fbcon_map) &&
+                con2fbmap->framebuffer < RT_ARRAY_SIZE(fbcon_map))
+            {
+                struct rt_device *vt;
+
+                vt = rt_dm_device_find(MASTER_ID_TTY, con2fbmap->console);
+
+            #ifdef RT_SERIAL_VIRTUAL
+                if (!vt)
+                {
+                    vt = rt_device_find("vuart");
+                }
+            #endif
+
+                if (vt)
+                {
+                    if (!(err = rt_device_open(vt, RT_DEVICE_OFLAG_RDWR)))
+                    {
+                        err = rt_device_control(vt, FBIOPUT_CON2FBMAP, con2fbmap);
+
+                        if (!err)
+                        {
+                            fbcon_map[con2fbmap->console] = con2fbmap->framebuffer;
+                        }
+
+                        rt_device_close(vt);
+                    }
+                }
+                else
+                {
+                    err = -RT_EEMPTY;
+                }
+            }
+            else
+            {
+                err = -RT_EFULL;
+            }
+        }
+        else
+        {
+            err = -RT_EINVAL;
+        }
+        break;
+
+    case FBIOBLANK:
+        if (gdev->ops->dpms_switch)
+        {
+            rt_uint32_t dpms;
+
+            switch ((rt_uint32_t)(rt_ubase_t)args)
+            {
+            case FB_BLANK_UNBLANK:
+                /* Display: On, HSync: On, VSync: On */
+                dpms = RT_GRAPHIC_DPMS_ON;
+                break;
+
+            case FB_BLANK_NORMAL:
+                /* Display: Off, HSync: On, VSync: On */
+                dpms = RT_GRAPHIC_DPMS_STANDBY;
+                break;
+
+            case FB_BLANK_HSYNC_SUSPEND:
+                /* Display: Off, HSync: Off, VSync: On */
+                dpms = RT_GRAPHIC_DPMS_STANDBY;
+                break;
+
+            case FB_BLANK_VSYNC_SUSPEND:
+                /* Display: Off, HSync: On, VSync: Off */
+                dpms = RT_GRAPHIC_DPMS_SUSPEND;
+                break;
+
+            case FB_BLANK_POWERDOWN:
+                /* Display: Off, HSync: Off, VSync: Off */
+                dpms = RT_GRAPHIC_DPMS_OFF;
+                break;
+
+            default:
+                err = -RT_EINVAL;
+                break;
+            }
+
+            if (!err)
+            {
+                graphic_dpms_switch(gdev, dpms);
+            }
+        }
+        break;
+
+    case FBIO_WAITFORVSYNC:
+        if (gdev->ops->wait_vsync)
+        {
+            err = gdev->ops->wait_vsync(gdev);
+        }
+        break;
+
+    case FBIO_ALLOC:
+    case FBIO_FREE:
+    case FBIOGET_GLYPH:
+    case FBIOGET_HWCINFO:
+    case FBIOPUT_MODEINFO:
+    case FBIOGET_DISPINFO:
+        LOG_D("FB IOCTL (%x) only used for SiS 300/630/730/540/315/550/650/740 frame buffer device", cmd);
+        /* Fall through */
+    case FBIOGET_VBLANK:
+    case FBIOGETCMAP: /* fb_cmap */
+    case FBIOPUTCMAP: /* fb_cmap */
+        /* Fall through */
+    default:
+        if (gdev->ops->control)
+        {
+            err = gdev->ops->control(gdev, cmd, args);
+        }
+        else
+        {
+            err = -RT_ENOSYS;
+        }
+    }
+
+    spin_unlock(&gdev->lock);
+
+    return err;
+}
+
+#ifdef RT_USING_DEVICE_OPS
+const static struct rt_device_ops _graphic_ops =
+{
+    .open = _graphic_open,
+    .read = _graphic_read,
+    .write = _graphic_write,
+    .control = _graphic_control,
+};
+#endif
+
+static void graphic_ofw_init(struct rt_graphic_device *gdev)
+{
+#ifdef RT_USING_OFW
+    struct rt_ofw_node *np = gdev->parent.ofw_node;
+
+#ifdef RT_GRAPHIC_BACKLIGHT
+    if (!gdev->backlight)
+    {
+        struct rt_ofw_node *bl_np = rt_ofw_parse_phandle(np, "backlight", 0);
+
+        if (bl_np && (gdev->backlight = rt_ofw_data(bl_np)))
+        {
+            rt_device_open(&gdev->backlight->parent, RT_DEVICE_OFLAG_RDWR);
+        }
+        rt_ofw_node_put(bl_np);
+    }
+#endif /* RT_GRAPHIC_BACKLIGHT */
+
+    (void)np;
+#endif /* RT_USING_OFW */
+}
+
+static void graphic_edid_res(struct rt_graphic_device *gdev,
+        rt_uint32_t *out_width, rt_uint32_t *out_height)
+{
+    struct edid *edid = &gdev->edid;
+    struct detailed_timing *dt = &edid->detailed_timings[0];
+    struct detailed_pixel_timing *dpt = &dt->data.pixel_data;
+
+    *out_width = dpt->hactive_lo + ((dpt->hactive_hblank_hi & 0xf0) << 4);
+    *out_height = dpt->vactive_lo + ((dpt->vactive_vblank_hi & 0xf0) << 4);
+}
+
+#ifdef RT_USING_PM
+static rt_err_t _graphic_pm_dpms_switch(struct rt_graphic_device *gdev, rt_uint8_t mode)
+{
+    rt_err_t err;
+
+    spin_lock(&gdev->lock);
+
+    switch (mode)
+    {
+    case PM_SLEEP_MODE_NONE:
+    case PM_SLEEP_MODE_IDLE:
+    case PM_SLEEP_MODE_LIGHT:
+    case PM_SLEEP_MODE_DEEP:
+        err = graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_ON);
+        break;
+
+    case PM_SLEEP_MODE_STANDBY:
+        err = graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_STANDBY);
+        break;
+
+    case PM_SLEEP_MODE_SHUTDOWN:
+        err = graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_OFF);
+        break;
+
+    default:
+        err = -RT_EINVAL;
+        break;
+    }
+
+    spin_unlock(&gdev->lock);
+
+    return err;
+}
+
+static rt_err_t _graphic_pm_suspend(const struct rt_device *device, rt_uint8_t mode)
+{
+    struct rt_graphic_device *gdev = raw_to_graphic(device);
+
+    return _graphic_pm_dpms_switch(gdev, mode);
+}
+
+static void _graphic_pm_resume(const struct rt_device *device, rt_uint8_t mode)
+{
+    struct rt_graphic_device *gdev = raw_to_graphic(device);
+
+    _graphic_pm_dpms_switch(gdev, mode);
+}
+
+static const struct rt_device_pm_ops _graphic_pm_ops =
+{
+    .suspend = _graphic_pm_suspend,
+    .resume = _graphic_pm_resume,
+};
+#endif /* RT_USING_PM */
+
+rt_err_t rt_graphic_device_register(struct rt_graphic_device *gdev)
+{
+    rt_err_t err;
+    int device_id;
+    const char *dev_name;
+
+    if (!gdev || !gdev->ops)
+    {
+        return -RT_EINVAL;
+    }
+
+    if (!gdev->primary_plane)
+    {
+        LOG_E("%s: Not %s found", rt_dm_dev_get_name(&gdev->parent), "primary plane");
+
+        return -RT_EINVAL;
+    }
+
+    if ((device_id = rt_dm_ida_alloc(&graphic_ida)) < 0)
+    {
+        return -RT_EFULL;
+    }
+
+    rt_dm_dev_set_name(&gdev->parent, "fb%u", device_id);
+    dev_name = rt_dm_dev_get_name(&gdev->parent);
+
+    rt_list_init(&gdev->overlay_nodes);
+    rt_dm_ida_init(&gdev->plane_ida, CUSTOM);
+    rt_spin_lock_init(&gdev->lock);
+
+    graphic_ofw_init(gdev);
+
+    if (!gdev->primary_plane->width || !gdev->primary_plane->height)
+    {
+        rt_uint32_t mode, width, height;
+        struct rt_device_rect_info rect;
+
+        graphic_edid_res(gdev, &width, &height);
+        rect.x = 0;
+        rect.y = 0;
+        rect.width = width;
+        rect.height = height;
+
+        if (gdev->primary_plane->mode == RTGRAPHIC_PIXEL_FORMAT_MONO)
+        {
+            mode = gdev->primary_plane->modes[0];
+        }
+        else
+        {
+            mode = gdev->primary_plane->mode;
+        }
+
+        err = plane_fb_remap(gdev->primary_plane, mode, &rect);
+
+        if (err)
+        {
+            LOG_E("%s: Set %s error = %s", rt_dm_dev_get_name(&gdev->parent),
+                    "primary plane", rt_strerror(err));
+
+            goto _fail;
+        }
+    }
+
+    gdev->parent.type = RT_Device_Class_Graphic;
+#ifdef RT_USING_DEVICE_OPS
+    gdev->parent.ops = &_graphic_ops;
+#else
+    gdev->parent.open = _graphic_open;
+    gdev->parent.read = _graphic_read;
+    gdev->parent.write = _graphic_write;
+    gdev->parent.control = _graphic_control;
+#endif
+    gdev->parent.master_id = graphic_ida.master_id;
+    gdev->parent.device_id = device_id;
+
+    if ((err = rt_device_register(&gdev->parent, dev_name, RT_DEVICE_FLAG_RDWR)))
+    {
+        goto _fail;
+    }
+
+#ifdef RT_USING_PM
+    rt_pm_device_register(&gdev->parent, &_graphic_pm_ops);
+#endif
+
+    if ((err = rt_graphic_logo_render(gdev)))
+    {
+        LOG_D("Logo render error = %s", rt_strerror(err));
+    }
+
+    return RT_EOK;
+
+_fail:
+    rt_dm_ida_free(&graphic_ida, device_id);
+
+    return err;
+}
+
+rt_err_t rt_graphic_device_unregister(struct rt_graphic_device *gdev)
+{
+    const char *dev_name;
+    struct rt_graphic_plane *plane, *plane_next;
+
+    if (!gdev)
+    {
+        return -RT_EINVAL;
+    }
+
+    dev_name = rt_dm_dev_get_name(&gdev->parent);
+
+    if (gdev->parent.ref_count)
+    {
+        LOG_E("%s: there is %u user", dev_name, gdev->parent.ref_count);
+        return -RT_EINVAL;
+    }
+
+#ifdef RT_USING_PM
+    rt_pm_device_unregister(&gdev->parent);
+#endif
+
+    rt_graphic_device_update_auto(gdev, 0);
+
+    if (gdev->ops->dpms_switch)
+    {
+        graphic_dpms_switch(gdev, RT_GRAPHIC_DPMS_OFF);
+    }
+#ifdef RT_GRAPHIC_BACKLIGHT
+    if (gdev->backlight)
+    {
+        rt_backlight_set_power(gdev->backlight, RT_BACKLIGHT_POWER_POWERDOWN);
+        rt_device_close(&gdev->backlight->parent);
+    }
+#endif
+
+    rt_list_for_each_entry_safe(plane, plane_next, &gdev->overlay_nodes, list)
+    {
+        rt_graphic_device_del_plane(gdev, plane);
+        rt_graphic_device_free_plane(plane);
+    }
+
+    rt_graphic_device_del_plane(gdev, gdev->primary_plane);
+    rt_graphic_device_free_plane(gdev->primary_plane);
+
+    if (gdev->cursor_plane)
+    {
+        rt_graphic_device_del_plane(gdev, gdev->cursor_plane);
+        rt_graphic_device_free_plane(gdev->cursor_plane);
+    }
+
+    rt_dm_ida_free(&graphic_ida, gdev->parent.device_id);
+
+    rt_device_unregister(&gdev->parent);
+
+    return RT_EOK;
+}
+
+struct rt_graphic_plane *rt_graphic_device_alloc_plane(struct rt_graphic_device *gdev,
+        rt_size_t priv_size, const struct rt_graphic_plane_ops *ops,
+        const rt_uint32_t *modes, rt_uint32_t modes_nr, rt_uint8_t type)
+{
+    struct rt_graphic_plane *plane = RT_NULL;
+
+    if (!gdev || !ops || !modes || !modes_nr || type > RT_GRAPHIC_PLANE_TYPE_CURSOR)
+    {
+        return RT_NULL;
+    }
+
+    plane = rt_calloc(1, sizeof(*plane) + priv_size);
+
+    if (plane)
+    {
+        rt_list_init(&plane->list);
+        plane->type = type;
+        plane->modes_nr = modes_nr;
+        plane->modes = modes;
+        plane->mode = RTGRAPHIC_PIXEL_FORMAT_MONO;
+        plane->graphic = gdev;
+        plane->ops = ops;
+    }
+
+    return plane;
+}
+
+void rt_graphic_device_free_plane(struct rt_graphic_plane *plane)
+{
+    if (!plane)
+    {
+        return;
+    }
+
+    rt_free(plane);
+}
+
+rt_err_t rt_graphic_device_add_plane(struct rt_graphic_device *gdev,
+        struct rt_graphic_plane *plane)
+{
+    rt_err_t err = RT_EOK;
+
+    if (!gdev || !plane)
+    {
+        return -RT_EINVAL;
+    }
+
+    if (!plane->ops)
+    {
+        LOG_E("%s: %s have no plane ops",
+                rt_dm_dev_get_name(&gdev->parent), plane->name);
+        return -RT_EINVAL;
+    }
+
+    plane->id = rt_dm_ida_alloc(&gdev->plane_ida);
+
+    if (plane->id == RT_DM_IDA_NUM)
+    {
+        LOG_E("%s: %s is out of plane max(%d)",
+                rt_dm_dev_get_name(&gdev->parent), plane->name, RT_DM_IDA_NUM - 1);
+        return -RT_EFULL;
+    }
+
+    if (plane->type == RT_GRAPHIC_PLANE_TYPE_PRIMARY)
+    {
+        if (gdev->primary_plane)
+        {
+            err = -RT_EINVAL;
+            goto _free_ida;
+        }
+        if (!plane->name[0])
+        {
+            rt_strncpy(plane->name, "primary", sizeof(plane->name));
+        }
+        gdev->primary_plane = plane;
+    }
+    else if (plane->type == RT_GRAPHIC_PLANE_TYPE_CURSOR)
+    {
+        if (gdev->cursor_plane)
+        {
+            err = -RT_EINVAL;
+            goto _free_ida;
+        }
+        if (!plane->name[0])
+        {
+            rt_strncpy(plane->name, "cursor", sizeof(plane->name));
+        }
+        gdev->cursor_plane = plane;
+    }
+    else if (plane->type == RT_GRAPHIC_PLANE_TYPE_OVERLAY)
+    {
+        if (!plane->name[0])
+        {
+            rt_snprintf(plane->name, sizeof(plane->name), "overlay-%u", plane->id);
+        }
+        spin_lock(&gdev->lock);
+        rt_list_insert_before(&gdev->overlay_nodes, &plane->list);
+        rt_graphic_device_leave(gdev);
+    }
+    else
+    {
+        LOG_E("What the fuck plane type(%u)", plane->type);
+        RT_ASSERT(0);
+    }
+
+_free_ida:
+    if (err)
+    {
+        rt_dm_ida_free(&gdev->plane_ida, plane->id);
+    }
+
+    return err;
+}
+
+rt_err_t rt_graphic_device_del_plane(struct rt_graphic_device *gdev,
+        struct rt_graphic_plane *plane)
+{
+    if (!gdev || !plane)
+    {
+        return -RT_EINVAL;
+    }
+
+    if (plane->ops->fb_cleanup)
+    {
+        /* Ignore error */
+        plane->ops->fb_cleanup(plane);
+    }
+
+    if (plane->type == RT_GRAPHIC_PLANE_TYPE_PRIMARY)
+    {
+        gdev->primary_plane = RT_NULL;
+    }
+    else if (plane->type == RT_GRAPHIC_PLANE_TYPE_CURSOR)
+    {
+        gdev->cursor_plane = RT_NULL;
+    }
+    else if (plane->type == RT_GRAPHIC_PLANE_TYPE_OVERLAY)
+    {
+        spin_lock(&gdev->lock);
+        rt_list_remove(&plane->list);
+        spin_unlock(&gdev->lock);
+    }
+
+    rt_dm_ida_free(&gdev->plane_ida, plane->id);
+
+    return RT_EOK;
+}
+
+void rt_graphic_device_hotplug_event(struct rt_graphic_device *gdev)
+{
+    rt_err_t err;
+    rt_uint32_t width, height;
+    struct rt_device_rect_info rect;
+
+    RT_ASSERT(gdev != RT_NULL);
+
+    rt_graphic_device_enter(gdev);
+
+    graphic_edid_res(gdev, &width, &height);
+    rect.x = 0;
+    rect.y = 0;
+    rect.width = width;
+    rect.height = height;
+
+    err = plane_fb_remap(gdev->primary_plane, gdev->primary_plane->mode, &rect);
+
+    if (err)
+    {
+        /* What the fuck? */
+        LOG_E("%s: hotplug event process error = %s",
+                rt_dm_dev_get_name(&gdev->parent), rt_strerror(err));
+        goto _out_lock;
+    }
+
+_out_lock:
+    rt_graphic_device_leave(gdev);
+
+    rt_atomic_store(&gdev->event_notifying, RT_TRUE);
+
+    if (gdev->event_notify.notify)
+    {
+        gdev->event_notify.notify(gdev->event_notify.dev);
+    }
+
+    rt_atomic_store(&gdev->event_notifying, RT_FALSE);
+}
+
+static void graphic_device_plane_update(struct rt_graphic_plane *plane,
+        struct rt_device_rect_info *rect)
+{
+    if (plane->ops->update)
+    {
+        plane->ops->update(plane, rect);
+    }
+}
+
+static void graphic_device_update(void *param)
+{
+    struct rt_device_rect_info rect;
+    struct rt_graphic_plane *plane;
+    struct rt_graphic_device *gdev = param;
+
+    rect.x = 0;
+    rect.y = 0;
+
+    spin_lock(&gdev->lock);
+
+    rect.width = gdev->primary_plane->width;
+    rect.height = gdev->primary_plane->height;
+    graphic_device_plane_update(gdev->primary_plane, &rect);
+
+    rt_list_for_each_entry(plane, &gdev->overlay_nodes, list)
+    {
+        rect.width = plane->width;
+        rect.height = plane->height;
+
+        graphic_device_plane_update(plane, &rect);
+    }
+
+    if ((plane = gdev->cursor_plane))
+    {
+        rect.x = plane->x;
+        rect.y = plane->y;
+        /* Ask driver to update position only */
+        rect.width = 0;
+        rect.height = 0;
+
+        graphic_device_plane_update(plane, &rect);
+    }
+
+    spin_unlock(&gdev->lock);
+}
+
+rt_err_t rt_graphic_device_update_auto(struct rt_graphic_device *gdev, rt_uint32_t update_ms)
+{
+    if (!gdev)
+    {
+        return -RT_EINVAL;
+    }
+
+    if (update_ms)
+    {
+        if (!gdev->update_timer)
+        {
+            char name[RT_NAME_MAX];
+
+            rt_snprintf(name, sizeof(name), "update-%s", rt_dm_dev_get_name(&gdev->parent));
+
+            gdev->update_timer = rt_timer_create(name, &graphic_device_update, gdev,
+                    rt_tick_from_millisecond(update_ms),
+                    RT_TIMER_FLAG_PERIODIC);
+
+            if (!gdev->update_timer)
+            {
+                return -RT_ENOMEM;
+            }
+        }
+
+        rt_timer_start(gdev->update_timer);
+    }
+    else if (gdev->update_timer)
+    {
+        rt_timer_stop(gdev->update_timer);
+        rt_timer_delete(gdev->update_timer);
+
+        gdev->update_timer = RT_NULL;
+    }
+
+    return RT_EOK;
+}
+
+void rt_graphic_device_enter(struct rt_graphic_device *gdev)
+{
+    RT_ASSERT(gdev != RT_NULL);
+
+    spin_lock(&gdev->lock);
+
+    if (gdev->update_timer)
+    {
+        rt_timer_stop(gdev->update_timer);
+    }
+}
+
+void rt_graphic_device_leave(struct rt_graphic_device *gdev)
+{
+    RT_ASSERT(gdev != RT_NULL);
+
+    if (gdev->update_timer)
+    {
+        rt_timer_start(gdev->update_timer);
+    }
+
+    spin_unlock(&gdev->lock);
+}
+
+rt_uint32_t rt_graphic_mode_bpp(rt_uint32_t mode)
+{
+    for (int i = 0; i < RT_ARRAY_SIZE(graphic_formats); ++i)
+    {
+        if (graphic_formats[i].mode == mode)
+        {
+            return graphic_formats[i].bits_per_pixel;
+        }
+    }
+
+    return 0;
+}

+ 381 - 0
components/drivers/graphic/graphic_primary.c

@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "graphic.primary"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+static struct rt_graphic_device *primary_gdev = RT_NULL;
+
+#define framebuffer_drift(plane, x, y, bpp) \
+    ((plane)->framebuffer + (x) * ((bpp) / 8) + (y) * (plane)->line_length)
+
+#define fixup_dir(a1, a2) \
+    if ((a1) > (a2)) { (a1) ^= (a2); (a2) ^= (a1); (a1) ^= (a2); }
+
+static void graphic_primary_set_pixel_8bit(const char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint8_t *)framebuffer_drift(plane, x, y, 8) = *(rt_uint8_t *)pixel;
+}
+
+static void graphic_primary_get_pixel_8bit(char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint8_t *)pixel = *(rt_uint8_t *)framebuffer_drift(plane, x, y, 8);
+}
+
+static void graphic_primary_draw_hline_8bit(const char *pixel, int x1, int x2, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(x1, x2);
+    rt_memset(framebuffer_drift(plane, x1, y, 8), *(rt_uint8_t *)pixel, x2 - x1);
+}
+
+static void graphic_primary_draw_vline_8bit(const char *pixel, int x, int y1, int y2)
+{
+    rt_uint8_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(y1, y2);
+    fb = framebuffer_drift(plane, x, y1, 8);
+
+    for (; y1 < y2; ++y1)
+    {
+        *fb = *(rt_uint8_t *)pixel;
+        fb += plane->line_length;
+    }
+}
+
+static void graphic_primary_blit_line_8bit(const char *pixel, int x, int y, rt_size_t size)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    rt_memcpy(framebuffer_drift(plane, x, y, 8), pixel, size);
+}
+
+static struct rt_device_graphic_ops graphic_primary_8bit_ops =
+{
+    .set_pixel = graphic_primary_set_pixel_8bit,
+    .get_pixel = graphic_primary_get_pixel_8bit,
+    .draw_hline = graphic_primary_draw_hline_8bit,
+    .draw_vline = graphic_primary_draw_vline_8bit,
+    .blit_line = graphic_primary_blit_line_8bit,
+};
+
+static void graphic_primary_set_pixel_16bit(const char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint16_t *)framebuffer_drift(plane, x, y, 16) = *(rt_uint16_t *)pixel;
+}
+
+static void graphic_primary_get_pixel_16bit(char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint16_t *)pixel = *(rt_uint16_t *)framebuffer_drift(plane, x, y, 16);
+}
+
+static void graphic_primary_draw_hline_16bit(const char *pixel, int x1, int x2, int y)
+{
+    rt_uint16_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(x1, x2);
+
+    fb = framebuffer_drift(plane, x1, y, 16);
+
+    for (; x1 < x2; ++x1)
+    {
+        *fb++ = *(rt_uint16_t *)pixel;
+    }
+}
+
+static void graphic_primary_draw_vline_16bit(const char *pixel, int x, int y1, int y2)
+{
+    rt_uint16_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(y1, y2);
+
+    fb = framebuffer_drift(plane, x, y1, 16);
+
+    for (; y1 < y2; ++y1)
+    {
+        *fb = *(rt_uint16_t *)pixel;
+        fb = (void *)fb + plane->line_length;
+    }
+}
+
+static void graphic_primary_blit_line_16bit(const char *pixel, int x, int y, rt_size_t size)
+{
+    rt_uint16_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fb = framebuffer_drift(plane, x, y, 16);
+
+    while (size --> 0)
+    {
+        *fb++ = *(rt_uint16_t *)pixel;
+        pixel += 2;
+    }
+}
+
+static struct rt_device_graphic_ops graphic_primary_16bit_ops =
+{
+    .set_pixel = graphic_primary_set_pixel_16bit,
+    .get_pixel = graphic_primary_get_pixel_16bit,
+    .draw_hline = graphic_primary_draw_hline_16bit,
+    .draw_vline = graphic_primary_draw_vline_16bit,
+    .blit_line = graphic_primary_blit_line_16bit,
+};
+
+static void graphic_primary_set_pixel_24bit(const char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    rt_memcpy(framebuffer_drift(plane, x, y, 24), pixel, 3);
+}
+
+static void graphic_primary_get_pixel_24bit(char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    rt_memcpy(pixel, framebuffer_drift(plane, x, y, 24), 3);
+}
+
+static void graphic_primary_draw_hline_24bit(const char *pixel, int x1, int x2, int y)
+{
+    rt_uint8_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(x1, x2);
+
+    fb = framebuffer_drift(plane, x1, y, 24);
+
+    for (; x1 < x2; ++x1)
+    {
+        *fb++ = ((rt_uint8_t *)pixel)[0];
+        *fb++ = ((rt_uint8_t *)pixel)[1];
+        *fb++ = ((rt_uint8_t *)pixel)[2];
+    }
+}
+
+static void graphic_primary_draw_vline_24bit(const char *pixel, int x, int y1, int y2)
+{
+    rt_uint8_t *fb;
+    rt_size_t xlate;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(y1, y2);
+
+    fb = framebuffer_drift(plane, x, y1, 24);
+    xlate = plane->line_length - 3;
+
+    for (; y1 < y2; ++y1)
+    {
+        *fb++ = ((rt_uint8_t *)pixel)[0];
+        *fb++ = ((rt_uint8_t *)pixel)[1];
+        *fb++ = ((rt_uint8_t *)pixel)[2];
+
+        fb = (void *)fb + xlate;
+    }
+}
+
+static void graphic_primary_blit_line_24bit(const char *pixel, int x, int y, rt_size_t size)
+{
+    rt_uint8_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fb = framebuffer_drift(plane, x, y, 24);
+
+    while (size --> 0)
+    {
+        *fb++ = *(rt_uint8_t *)pixel++;
+        *fb++ = *(rt_uint8_t *)pixel++;
+        *fb++ = *(rt_uint8_t *)pixel++;
+    }
+}
+
+static struct rt_device_graphic_ops graphic_primary_24bit_ops =
+{
+    .set_pixel = graphic_primary_set_pixel_24bit,
+    .get_pixel = graphic_primary_get_pixel_24bit,
+    .draw_hline = graphic_primary_draw_hline_24bit,
+    .draw_vline = graphic_primary_draw_vline_24bit,
+    .blit_line = graphic_primary_blit_line_24bit,
+};
+
+static void graphic_primary_set_pixel_32bit(const char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint32_t *)framebuffer_drift(plane, x, y, 32) = *(rt_uint32_t *)pixel;
+}
+
+static void graphic_primary_get_pixel_32bit(char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint32_t *)pixel = *(rt_uint32_t *)framebuffer_drift(plane, x, y, 32);
+}
+
+static void graphic_primary_draw_hline_32bit(const char *pixel, int x1, int x2, int y)
+{
+    rt_uint32_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(x1, x2);
+
+    fb = framebuffer_drift(plane, x1, y, 32);
+
+    for (; x1 < x2; ++x1)
+    {
+        *fb++ = *(rt_uint32_t *)pixel;
+    }
+}
+
+static void graphic_primary_draw_vline_32bit(const char *pixel, int x, int y1, int y2)
+{
+    rt_uint32_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(y1, y2);
+
+    fb = framebuffer_drift(plane, x, y1, 32);
+
+    for (; y1 < y2; ++y1)
+    {
+        *fb = *(rt_uint32_t *)pixel;
+        fb = (void *)fb + plane->line_length;
+    }
+}
+
+static void graphic_primary_blit_line_32bit(const char *pixel, int x, int y, rt_size_t size)
+{
+    rt_uint32_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fb = framebuffer_drift(plane, x, y, 32);
+
+    while (size --> 0)
+    {
+        *fb++ = *(rt_uint32_t *)pixel;
+        pixel += 4;
+    }
+}
+
+static struct rt_device_graphic_ops graphic_primary_32bit_ops =
+{
+    .set_pixel = graphic_primary_set_pixel_32bit,
+    .get_pixel = graphic_primary_get_pixel_32bit,
+    .draw_hline = graphic_primary_draw_hline_32bit,
+    .draw_vline = graphic_primary_draw_vline_32bit,
+    .blit_line = graphic_primary_blit_line_32bit,
+};
+
+static void graphic_primary_set_pixel_64bit(const char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint64_t *)framebuffer_drift(plane, x, y, 64) = *(rt_uint64_t *)pixel;
+}
+
+static void graphic_primary_get_pixel_64bit(char *pixel, int x, int y)
+{
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    *(rt_uint64_t *)pixel = *(rt_uint64_t *)framebuffer_drift(plane, x, y, 64);
+}
+
+static void graphic_primary_draw_hline_64bit(const char *pixel, int x1, int x2, int y)
+{
+    rt_uint64_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(x1, x2);
+
+    fb = framebuffer_drift(plane, x1, y, 64);
+
+    for (; x1 < x2; ++x1)
+    {
+        *fb++ = *(rt_uint64_t *)pixel;
+    }
+}
+
+static void graphic_primary_draw_vline_64bit(const char *pixel, int x, int y1, int y2)
+{
+    rt_uint64_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fixup_dir(y1, y2);
+
+    fb = framebuffer_drift(plane, x, y1, 64);
+
+    for (; y1 < y2; ++y1)
+    {
+        *fb = *(rt_uint64_t *)pixel;
+        fb = (void *)fb + plane->line_length;
+    }
+}
+
+static void graphic_primary_blit_line_64bit(const char *pixel, int x, int y, rt_size_t size)
+{
+    rt_uint64_t *fb;
+    struct rt_graphic_plane *plane = primary_gdev->primary_plane;
+
+    fb = framebuffer_drift(plane, x, y, 64);
+
+    while (size --> 0)
+    {
+        *fb++ = *(rt_uint64_t *)pixel;
+        pixel += 8;
+    }
+}
+
+static struct rt_device_graphic_ops graphic_primary_64bit_ops =
+{
+    .set_pixel = graphic_primary_set_pixel_64bit,
+    .get_pixel = graphic_primary_get_pixel_64bit,
+    .draw_hline = graphic_primary_draw_hline_64bit,
+    .draw_vline = graphic_primary_draw_vline_64bit,
+    .blit_line = graphic_primary_blit_line_64bit,
+};
+
+struct rt_device_graphic_ops *rt_graphic_device_switch_primary(struct rt_graphic_device *gdev)
+{
+    primary_gdev = gdev;
+
+    RT_ASSERT(primary_gdev != RT_NULL);
+    RT_ASSERT(primary_gdev->primary_plane != RT_NULL);
+
+    switch (rt_graphic_mode_bpp(primary_gdev->primary_plane->mode))
+    {
+    case 32: return &graphic_primary_32bit_ops;
+    case 24: return &graphic_primary_24bit_ops;
+    case 16: return &graphic_primary_16bit_ops;
+    case 64: return &graphic_primary_64bit_ops;
+    case 8:  return &graphic_primary_8bit_ops;
+    default:
+        LOG_E("What the fuck format(%u)", primary_gdev->primary_plane->mode);
+        RT_ASSERT(0);
+        break;
+    }
+
+    return RT_NULL;
+}

+ 295 - 0
components/drivers/graphic/graphic_simple.c

@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "graphic.simple"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+static rt_uint32_t edid_dpi_to_mm(rt_uint32_t dpi, rt_uint32_t res)
+{
+    return res * 254 / 10 / dpi;
+}
+
+static void edid_checksum(rt_uint8_t *edid, rt_size_t len)
+{
+    rt_uint32_t sum = 0;
+
+    for (int i = 0; i < len; ++i)
+    {
+        sum += edid[i];
+    }
+
+    sum &= 0xff;
+
+    if (sum)
+    {
+        edid[len] = 0x100 - sum;
+    }
+}
+
+static void fill_edid(struct rt_graphic_device *gdev,
+        rt_uint32_t width, rt_uint32_t height, rt_uint32_t refresh_hz)
+{
+    union
+    {
+        rt_uint32_t u32;
+        rt_uint16_t u16;
+    } value;
+    int dt_idx = 0;
+    int width_mm, height_mm;
+    rt_uint64_t clock;
+    rt_uint32_t xfront, xsync, xblank, yfront, ysync, yblank;
+    struct edid *edid = &gdev->edid;
+    struct detailed_timing *dt;
+    struct detailed_pixel_timing *dpt;
+    struct detailed_non_pixel *dnp;
+
+    refresh_hz = refresh_hz ? : 75000;
+    width_mm = edid_dpi_to_mm(100, width);
+    height_mm = edid_dpi_to_mm(100, height);
+
+    /* EDID: timings */
+    xfront = width * 25 / 100;
+    xsync = width * 3 / 100;
+    xblank = width * 35 / 100;
+    yfront = height * 5 / 1000;
+    ysync = height * 5 / 1000;
+    yblank = height * 35 / 1000;
+    clock = ((rt_uint64_t)refresh_hz * (width + xblank) * (height + yblank)) / 10000000;
+
+    if (width >= 4096 || height >= 4096 || clock >= 65536)
+    {
+        LOG_E("%s: Large screen %ux%u@%uHz is not supported in simple",
+                rt_dm_dev_get_name(&gdev->parent), width, height, clock);
+
+        RT_ASSERT(0);
+    }
+
+    /* EDID: extensions */
+
+    /* EDID: header information */
+    edid->header[0] = 0x00;
+    edid->header[1] = 0xff;
+    edid->header[2] = 0xff;
+    edid->header[3] = 0xff;
+    edid->header[4] = 0xff;
+    edid->header[5] = 0xff;
+    edid->header[6] = 0xff;
+    edid->header[7] = 0x00;
+
+    /* Vendor id */
+    value.u16 = rt_cpu_to_be16(
+            ((('R' - '@') & 0x1f) << 10) |
+            ((('T' - '@') & 0x1f) <<  5) |
+            ((('T' - '@') & 0x1f) <<  0));
+    rt_memcpy(edid->mfg_id, &value.u16, sizeof(edid->mfg_id));
+
+    /* Product code */
+    value.u16 = rt_cpu_to_le16(0x1234);
+    rt_memcpy(edid->prod_code, &value.u16, sizeof(edid->prod_code));
+
+    /* Serial number */
+    edid->serial = rt_cpu_to_le32(0);
+
+    /* Manufacture week and year */
+    edid->mfg_week = 42;
+    edid->mfg_year = 2014 - 1990;
+
+    /* Version */
+    edid->version = 1;
+    edid->revision = 4;
+
+    /* EDID: basic display parameters */
+
+    /* Video input: digital, 8bpc, displayport */
+    edid->input = 0xa5;
+
+    /* Screen size */
+    edid->width_cm = width_mm / 10;
+    edid->height_cm = height_mm / 10;
+
+    /* Gamma: 2.2 */
+    edid->gamma = 220 - 100;
+
+    /* Features: STD sRGB, preferred timing */
+    edid->features = 0x06;
+
+    /* EDID: chromaticity coordinates */
+
+    /*
+     * STD sRGB colorspace:
+     *                      X       Y
+     *      red:            0.6400, 0.3300
+     *      green:          0.3000, 0.6000
+     *      blue:           0.1500, 0.0600
+     *      white point:    0.3127, 0.3290
+     *
+     *  value = (uint32_t)(value * 1024 + 0.5)
+     *
+     *      red_x   = 0.6400 * 1024 + 0.5 = 655.86      => 655
+     *      red_y   = 0.3300 * 1024 + 0.5 = 338.42      => 338
+     *      green_x = 0.3000 * 1024 + 0.5 = 307.7       => 307
+     *      green_y = 0.6000 * 1024 + 0.5 = 614.9       => 614
+     *      blue_x  = 0.1500 * 1024 + 0.5 = 154.1       => 154
+     *      blue_y  = 0.0600 * 1024 + 0.5 = 61.94       => 61
+     *      white_x = 0.3127 * 1024 + 0.5 = 320.7048    => 320
+     *      white_y = 0.3290 * 1024 + 0.5 = 337.396     => 337
+     */
+    edid->red_green_lo = (((655 & 0x03) << 6) |     /* red_x */
+                          ((338 & 0x03) << 4) |     /* red_y */
+                          ((307 & 0x03) << 2) |     /* green_x */
+                          ((614 & 0x03) << 0));     /* green_y */
+    edid->black_white_lo = (((154 & 0x03) << 6) |   /* blue_x */
+                            ((154 & 0x03) << 4) |   /* blue_y */
+                            ((320 & 0x03) << 2) |   /* white_x */
+                            ((337 & 0x03) << 0));   /* white_y */
+    edid->red_x = 655 >> 2;                         /* red_x */
+    edid->red_y = 338 >> 2;                         /* red_y */
+    edid->green_x = 307 >> 2;                       /* green_x */
+    edid->green_y = 614 >> 2;                       /* green_y */
+    edid->blue_x = 154 >> 2;                        /* blue_x */
+    edid->blue_y = 154 >> 2;                        /* blue_y */
+    edid->white_x = 320 >> 2;                       /* white_x */
+    edid->white_y = 337 >> 2;                       /* white_y */
+
+    /* EDID: established timing bitmap */
+    /* EDID: standard timing information */
+
+    /* EDID: descriptor blocks */
+    dt = &edid->detailed_timings[dt_idx++];
+    dpt = &dt->data.pixel_data;
+
+    dt->pixel_clock = rt_cpu_to_le16(clock);
+    dpt->hactive_lo = width & 0xff;
+    dpt->hblank_lo = xblank & 0xff;
+    dpt->hactive_hblank_hi = (((width & 0xf00) >> 4) | ((xblank & 0xf00) >> 8));
+
+    dpt->vactive_lo = height & 0xff;
+    dpt->vblank_lo = yblank & 0xff;
+    dpt->vactive_vblank_hi = (((height & 0xf00) >> 4) | ((yblank & 0xf00) >> 8));
+
+    dpt->hsync_offset_lo = xfront & 0xff;
+    dpt->hsync_pulse_width_lo = xsync  & 0xff;
+
+    dpt->vsync_offset_pulse_width_lo = (((yfront & 0x00f) << 4) |
+                                        ((ysync & 0x00f) << 0));
+    dpt->hsync_vsync_offset_pulse_width_hi = (((xfront & 0x300) >> 2) |
+                                              ((xsync  & 0x300) >> 4) |
+                                              ((yfront & 0x030) >> 2) |
+                                              ((ysync  & 0x030) >> 4));
+
+    dpt->width_mm_lo = width_mm & 0xff;
+    dpt->height_mm_lo = height_mm & 0xff;
+    dpt->width_height_mm_hi = (((width_mm & 0xf00) >> 4) |
+                               ((height_mm & 0xf00) >> 8));
+
+    dpt->misc = 0x18;
+
+    /* XTRA3 STD */
+    dt = &edid->detailed_timings[dt_idx++];
+    dnp = &dt->data.other_data;
+    dnp->type = EDID_DETAIL_EST_TIMINGS;
+    dnp->data.timings[0].hsize = 10;
+
+    /* Ranges */
+    dt = &edid->detailed_timings[dt_idx++];
+    dnp = &dt->data.other_data;
+    dnp->type = EDID_DETAIL_MONITOR_RANGE;
+    dnp->data.range.min_vfreq = 50;
+    dnp->data.range.max_vfreq = 125;
+    dnp->data.range.min_hfreq_khz =  30;
+    dnp->data.range.max_hfreq_khz = 160;
+    dnp->data.range.pixel_clock_mhz = 2550 / 10;
+    dnp->data.range.flags = 0x01;
+    rt_memcpy(&dnp->data.range.flags + 1, "\n      ", 7);
+
+    while (dt_idx < RT_ARRAY_SIZE(edid->detailed_timings))
+    {
+        /* Dummy */
+        dt = &edid->detailed_timings[dt_idx++];
+        dnp = &dt->data.other_data;
+        dnp->type = 0x10;
+    }
+
+    /* EDID: display id extensions */
+
+    /* EDID: checksum */
+    edid_checksum((void *)edid, 127);
+}
+
+rt_err_t rt_graphic_device_simple_edid(struct rt_graphic_device *gdev,
+        rt_uint32_t width, rt_uint32_t height, rt_uint32_t refresh_hz)
+{
+    if (!gdev || !width || !height)
+    {
+        return -RT_EINVAL;
+    }
+
+    fill_edid(gdev, width, height, refresh_hz);
+
+    return RT_EOK;
+}
+
+static const struct rt_graphic_device_ops graphic_device_simple_ops =
+{
+};
+
+rt_err_t rt_graphic_device_simple_register(struct rt_graphic_device *gdev,
+        rt_uint32_t width, rt_uint32_t height, rt_uint32_t refresh_hz,
+        const struct rt_graphic_plane_ops *plane_ops,
+        const rt_uint32_t *modes, rt_uint32_t modes_nr)
+{
+    rt_err_t err;
+    struct rt_graphic_plane *plane;
+
+    if (!gdev || !width || !height || !plane_ops || !modes || !modes_nr)
+    {
+        return -RT_EINVAL;
+    }
+
+    if (!gdev->ops)
+    {
+        gdev->ops = &graphic_device_simple_ops;
+    }
+
+    plane = rt_graphic_device_alloc_plane(gdev, 0, plane_ops, modes, modes_nr,
+            RT_GRAPHIC_PLANE_TYPE_PRIMARY);
+
+    if (!plane)
+    {
+        return -RT_EINVAL;
+    }
+
+    if ((err = rt_graphic_device_add_plane(gdev, plane)))
+    {
+        goto _free_plane;
+    }
+
+    rt_graphic_device_simple_edid(gdev, width, height, refresh_hz);
+
+    err = rt_graphic_device_register(gdev);
+
+_free_plane:
+    if (err)
+    {
+        rt_free(plane);
+    }
+
+    return err;
+}
+
+rt_err_t rt_graphic_device_simple_unregister(struct rt_graphic_device *gdev)
+{
+    return rt_graphic_device_unregister(gdev);
+}

+ 1 - 0
components/drivers/graphic/logo/.gitignore

@@ -0,0 +1 @@
+logo.inc

+ 31 - 0
components/drivers/graphic/logo/Kconfig

@@ -0,0 +1,31 @@
+menuconfig RT_GRAPHIC_LOGO
+    bool "Startup Logo"
+    select RT_GRAPHIC_FB
+    default y
+
+choice
+    prompt "Rendering image(ppm)"
+    default RT_GRAPHIC_LOGO_RT_THREAD_CLUT224
+    depends on RT_GRAPHIC_LOGO
+
+    config RT_GRAPHIC_LOGO_NONE
+        bool "None logo (Change in runtime)"
+
+    osource "$(SOC_DM_GRAPHIC_LOGO_DIR)/Kconfig"
+endchoice
+
+# Provide the logos path for the BSP path:
+#
+# RT_GRAPHIC_LOGO_<XYZ> in Kconfig:
+#
+#   config RT_GRAPHIC_LOGO_<XYZ>
+#       bool "<XYZ> logo"
+#
+# RT_GRAPHIC_LOGO_<XYZ>_PATH in Kconfig.path:
+#
+#   if RT_GRAPHIC_LOGO_<XYZ>
+#       config RT_GRAPHIC_LOGO_<XYZ>_PATH
+#           string
+#           default "dm/graphic/logo/[filename].ppm"
+#   endif
+osource "$(SOC_DM_GRAPHIC_LOGO_DIR)/Kconfig.path"

+ 84 - 0
components/drivers/graphic/logo/SConscript

@@ -0,0 +1,84 @@
+from building import *
+import os, re
+
+group   = []
+
+if not GetDepend(['RT_GRAPHIC_LOGO']):
+    Return('group')
+
+cwd     = GetCurrentDir()
+CPPPATH = [cwd + '/../../include']
+CPPDEFINES = []
+
+src     = ['logo.c']
+
+logo_path = None
+logo_width = 0
+logo_height = 0
+logo_max_val = 0
+
+if logo_path == None:
+    # Find in BSP
+    paths = None
+    for key in BuildOptions.keys():
+        if re.match(r'RT_GRAPHIC_LOGO_.*_PATH', key):
+            paths = BuildOptions[key]
+            break
+
+    if paths != None and len(paths) > 0:
+        logo_path = Dir('#').abspath + '/' + paths[1:-1]
+        if not os.path.exists(logo_path):
+            print("Logo file '{}' not found!".format(logo_path))
+            exit(-1)
+
+if logo_path != None:
+    with open(logo_path, 'rb') as ppm:
+        data = ppm.read().split(b'\n')
+
+        # PPM: <magic number>
+        magic = data[0].decode('utf-8')
+
+        # PPM: <comment>
+        offset = 1
+        while True:
+            comment = str(data[offset].decode('utf-8'))
+            if comment[0] != '#':
+                break
+            offset += 1
+
+        # PPM: <width> <height>
+        logo_width, logo_height = map(int, data[offset].split())
+
+        # PPM: <max pixel value>
+        logo_max_val = int(data[offset + 1])
+
+        # PPM: <data>
+        ppm.seek(0)
+        pixels = b''.join(ppm.readlines()[offset + 2:])
+        ppm.close()
+
+        if magic == 'P1' or magic == 'P2' or magic == 'P3':
+            # ASCII
+            pixels = re.sub(b'\\s+', b'\n', pixels.strip()).decode('utf-8').split('\n')
+
+    logo = open(cwd + '/logo.inc', "w")
+
+    for dy in range(logo_height):
+        for dx in range(logo_width):
+            index = (dy * logo_width + dx) * 3
+            # Red
+            logo.write(str(pixels[index]).rjust(4) + ",")
+            # Green
+            logo.write(str(pixels[index + 1]).rjust(4) + ",")
+            # Blue
+            logo.write(str(pixels[index + 2]).rjust(4) + ",")
+        logo.write("\n")
+
+    logo.close()
+
+CPPDEFINES += ['__STARTUP_LOGO_WIDTH__=' + str(logo_width)]
+CPPDEFINES += ['__STARTUP_LOGO_HEIGHT__=' + str(logo_height)]
+CPPDEFINES += ['__STARTUP_LOGO_COLOR_MAX__=' + str(logo_max_val)]
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
+Return('group')

+ 216 - 0
components/drivers/graphic/logo/logo.c

@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "graphic.logo"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#if __STARTUP_LOGO_WIDTH__ && __STARTUP_LOGO_HEIGHT__ && __STARTUP_LOGO_COLOR_MAX__
+static rt_uint8_t builtin_logo[] =
+{
+    #include "logo.inc"
+};
+
+static void *startup_logo = builtin_logo;
+#else
+static void *startup_logo = RT_NULL;
+#endif
+static int startup_logo_width = __STARTUP_LOGO_WIDTH__;
+static int startup_logo_height = __STARTUP_LOGO_HEIGHT__;
+static int startup_logo_color_max = __STARTUP_LOGO_COLOR_MAX__;
+
+rt_err_t rt_graphic_logo_change(void *data, int width, int height, int color_max)
+{
+    if (!data && !width && !height && !color_max)
+    {
+        /* Disable logo */
+        startup_logo = RT_NULL;
+    }
+    else if (data && width > 0 && height > 0 && color_max > 0)
+    {
+        startup_logo = data;
+        startup_logo_width = width;
+        startup_logo_height = height;
+        startup_logo_color_max = color_max;
+    }
+    else
+    {
+        return -RT_EINVAL;
+    }
+
+    return RT_EOK;
+}
+
+static rt_ubase_t to_grayscale(rt_ubase_t red, rt_ubase_t green, rt_ubase_t blue)
+{
+    return (299 * red + 587 * green + 114 * blue) / 1000;
+}
+
+static rt_ubase_t to_color(rt_ubase_t color, rt_ubase_t in_color_max, rt_ubase_t out_color_max)
+{
+    return color * out_color_max / in_color_max;
+}
+
+static rt_ubase_t gray_reordering(rt_ubase_t red, rt_ubase_t red_off,
+                                  rt_ubase_t green, rt_ubase_t green_off,
+                                  rt_ubase_t blue, rt_ubase_t blue_off,
+                                  rt_ubase_t in_color_max, rt_ubase_t out_color_max)
+{
+    return to_grayscale(to_color(red, in_color_max, out_color_max),
+                        to_color(green, in_color_max, out_color_max),
+                        to_color(blue, in_color_max, out_color_max));
+}
+
+static rt_ubase_t rgb_reordering(rt_ubase_t red, rt_ubase_t red_off,
+                                 rt_ubase_t green, rt_ubase_t green_off,
+                                 rt_ubase_t blue, rt_ubase_t blue_off,
+                                 rt_ubase_t in_color_max, rt_ubase_t out_color_max)
+{
+    return (to_color(red, in_color_max, out_color_max) << red_off) |
+           (to_color(green, in_color_max, out_color_max) << green_off) |
+           (to_color(blue, in_color_max, out_color_max) << blue_off);
+}
+
+rt_err_t rt_graphic_logo_render(struct rt_graphic_device *gdev)
+{
+    rt_err_t err;
+    int fb_color_max;
+    rt_ubase_t xlate, none_alpha;
+    rt_ubase_t red_off, green_off, blue_off;
+    rt_ubase_t red_mask, green_mask, blue_mask;
+    rt_uint8_t *logo, *fb, bytes_per_pixel;
+    rt_ubase_t (*color_reordering)(rt_ubase_t, rt_ubase_t,
+                                   rt_ubase_t, rt_ubase_t,
+                                   rt_ubase_t, rt_ubase_t,
+                                   rt_ubase_t, rt_ubase_t);
+    struct fb_var_screeninfo var;
+    struct rt_device_rect_info rect;
+    struct rt_device_graphic_info info;
+    struct rt_device *fbdev = &gdev->parent;
+
+    if (!startup_logo)
+    {
+        return RT_EOK;
+    }
+
+    if ((err = rt_device_open(fbdev, 0)))
+    {
+        return err;
+    }
+
+    if ((err = rt_device_control(fbdev, FBIOGET_VSCREENINFO, &var)))
+    {
+        LOG_E("Get framebuffer %s error = %s", "var", rt_strerror(err));
+
+        goto _close_fbdev;
+    }
+
+    if (startup_logo_width > var.xres || startup_logo_height > var.yres)
+    {
+        LOG_E("PPM logo[%u, %u] Out of screen[%u, %u]",
+                startup_logo_width, startup_logo_height, var.xres, var.yres);
+
+        err = -RT_EINVAL;
+        goto _close_fbdev;
+    }
+
+    if ((err = rt_device_control(fbdev, RTGRAPHIC_CTRL_GET_INFO, &info)))
+    {
+        LOG_E("Get framebuffer %s error = %s", "info", rt_strerror(err));
+
+        goto _close_fbdev;
+    }
+
+    if ((err = rt_device_control(fbdev, RTGRAPHIC_CTRL_POWERON, RT_NULL)))
+    {
+        LOG_E("Power on graphic device error = %s", rt_strerror(err));
+
+        goto _close_fbdev;
+    }
+
+    if (var.grayscale)
+    {
+        color_reordering = &gray_reordering;
+    }
+    else
+    {
+        color_reordering = &rgb_reordering;
+    }
+
+    bytes_per_pixel = var.bits_per_pixel / 8;
+    xlate = (var.xres - startup_logo_width) * bytes_per_pixel;
+
+    rect.x = (var.xres - startup_logo_width) >> 1;
+    rect.y = (var.yres - startup_logo_height) >> 1;
+    rect.width = startup_logo_width,
+    rect.height = startup_logo_height,
+
+    fb = (void *)info.framebuffer;
+    fb += rect.x * bytes_per_pixel + rect.y * info.pitch;
+
+    logo = startup_logo;
+
+    red_off = var.red.offset;
+    red_mask = RT_GENMASK(var.red.length - 1, 0);
+    green_off = var.green.offset;
+    green_mask = RT_GENMASK(var.green.length - 1, 0);
+    blue_off = var.blue.offset;
+    blue_mask = RT_GENMASK(var.blue.length - 1, 0);
+
+    fb_color_max = rt_max_t(int, rt_max_t(int, red_mask, green_mask), blue_mask);
+
+    if (var.transp.length)
+    {
+        none_alpha = RT_GENMASK(var.transp.length - 1, 0) << var.transp.offset;
+    }
+    else
+    {
+        none_alpha = 0;
+    }
+
+    for (int dy = 0; dy < startup_logo_height; ++dy)
+    {
+        for (int dx = 0; dx < startup_logo_width; ++dx)
+        {
+            rt_ubase_t color = color_reordering(logo[0], red_off,
+                                                logo[1], green_off,
+                                                logo[2], blue_off,
+                                                fb_color_max, startup_logo_color_max) |
+                                                none_alpha;
+
+            rt_memcpy(fb, &color, bytes_per_pixel);
+
+            fb += bytes_per_pixel;
+            logo += 3;
+        }
+
+        fb += xlate;
+    }
+
+    rt_device_control(fbdev, RTGRAPHIC_CTRL_RECT_UPDATE, &rect);
+    rt_device_control(fbdev, RTGRAPHIC_CTRL_WAIT_VSYNC, RT_NULL);
+
+    /* Complete */
+    startup_logo = RT_NULL;
+
+    /*
+     * Should recycle here, logo takes up too much memory
+     * if builtin is not RT_GRAPHIC_LOGO_NONE.
+     */
+
+_close_fbdev:
+    rt_device_close(fbdev);
+
+    return err;
+}

+ 243 - 0
components/drivers/graphic/logo/logo.html

@@ -0,0 +1,243 @@
+<!--
+	Copyright (c) 2006-2023, RT-Thread Development Team
+
+	SPDX-License-Identifier: Apache-2.0
+
+	Change Logs:
+	Date           Author       Notes
+	2023-02-25     GuEe-GUI     the first version
+ -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<link rel="shortcut icon" href="https://www.rt-thread.org/favicon.ico" type="image/x-icon">
+	<title>Logo PPM Previews</title>
+	<style type="text/css">
+		body {
+			min-width: 860px;
+			margin: 0px;
+			padding: 0px;
+			font-family: Consolas, "Cascadia Code", Monaco;
+			background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgd2lkdGg9IjEwIiBoZWlnaHQ9IjEwIiB2aWV3Qm94PSIwIDAgMTAgMTAiIGlkPSJjb2RlLWJhY2tncm91bmQiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPg0KICAgIDx0aXRsZT5jb2RlLWJhY2tncm91bmQ8L3RpdGxlPg0KICAgIDxnPg0KICAgICAgICA8bGluZSB5Mj0iMTAiIHgyPSIxMCIgeTE9IjAiIHgxPSIxMCIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IiNlNWU1ZTUiLz4NCiAgICAgICAgPGxpbmUgeTI9IjEwIiB4Mj0iMTAiIHkxPSIxMCIgeDE9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSIjZTVlNWU1Ii8+DQogICAgPC9nPg0KPC9zdmc+);
+		}
+		.main {
+			display: flex;
+			justify-content: center;
+		}
+		.controls > div {
+			padding: 10px;
+			height: 52px;
+			line-height: 52px;
+		}
+		label {
+			user-select: none;
+			font-size: 24px;
+			font-weight: 400;
+			color: #494949;
+		}
+		.res-input, .ppm-input, #background-color {
+			cursor: pointer;
+			border: 2px solid #494949;
+			border-radius: 8px;
+			outline: none;
+			font-size: 18px;
+			background-color: #fff;
+		}
+		.res-input {
+			height: 32px;
+			padding: 0 4px;
+			line-height: 52px;
+		}
+		.background-color, .ppm-input {
+			display: block;
+			text-align: center;
+		}
+		#background-color {
+			width: 20%;
+			height: 32px;
+		}
+		.ppm-input:hover {
+			background-color: #eee;
+		}
+		#ppm-input {
+			display: none;
+		}
+		.canvas {
+			justify-content: center;
+			display: flex;
+			margin-top: 20px;
+		}
+	</style>
+</head>
+<body>
+	<div class="main">
+		<div>
+			<div class="controls">
+				<div style="display: flex; justify-content: center;">
+					<div>
+						<label class="resolution">Screen Resolution</label>
+						<input type="number" class="res-input" id="canvas-width" placeholder="width" min="1" step="1">
+						<label class="resolution">&times;</label>
+						<input type="number" class="res-input" id="canvas-height" placeholder="height" min="1" step="1">
+					</div>
+				</div>
+				<div>
+					<label class="background-color" for="background-color">
+						<span>Background Color</span>
+						<input type="color" id="background-color">
+					</label>
+				</div>
+				<div>
+					<label class="ppm-input">
+						<input type="file" id="ppm-input" accept=".ppm" placeholder="PPM">
+						<span>Open PPM File</span>
+					</label>
+				</div>
+			</div>
+			<div class="canvas">
+				<canvas id="canvas"></canvas>
+			</div>
+		</div>
+	</div>
+</body>
+<script type="text/javascript">
+	var Screen = new Object;
+
+	window.onload = function() {
+		Screen.canvas = document.getElementById('canvas');
+		Screen.ctx = canvas.getContext('2d');
+		Screen.ppmWidth = 0;
+		Screen.ppmHeight = 0;
+		Screen.ppmPixels = [];
+		Screen.widthInput = document.getElementById('canvas-width');
+		Screen.heightInput = document.getElementById('canvas-height');
+		Screen.ColorPicker = document.getElementById('background-color');
+
+		Screen.widthInput.value = 1;
+		Screen.heightInput.value = 1;
+		Screen.ColorPicker.value = "#000000";
+
+		document.getElementById('canvas-width').addEventListener('input', function(e) {
+			applyCanvasSize();
+		});
+
+		document.getElementById('canvas-height').addEventListener('input', function(e) {
+			applyCanvasSize();
+		});
+
+		document.getElementById('background-color').addEventListener('input', function(e) {
+			applyBackgroundColor();
+		});
+
+		document.getElementById('ppm-input').addEventListener('change', function(e) {
+			const file = e.target.files[0];
+
+			if (!file) {
+				return;
+			}
+
+			const reader = new FileReader();
+
+			reader.onload = function(e) {
+				const ppm_data = e.target.result;
+				const { width, height, pixels } = parsePPM(ppm_data);
+
+				Screen.ppmWidth = width;
+				Screen.ppmHeight = height;
+				Screen.ppmPixels = pixels;
+
+				Screen.ctx.reset();
+
+				applyBackgroundColor();
+			};
+
+			reader.readAsText(file);
+		});
+
+		function parsePPM(data) {
+			let cursor = 0;
+			const lines = data.split('\n').filter(line => !line.startsWith('#') && line.trim() !== '');
+			const magic = lines[cursor++].trim();
+
+			if (!['P1', 'P2', 'P3'].includes(magic)) {
+				alert('Unsupported format');
+				return;
+			}
+
+			let maxVal = 1;
+			const [width, height] = lines[cursor++].split(/\s+/).map(Number);
+
+			if (magic !== 'P1') {
+				maxVal = parseInt(lines[cursor++]);
+			}
+
+			const pixels = [];
+			const values = lines.slice(cursor).join(' ').split(/\s+/).filter(v => v !== '');
+
+			if (magic === 'P1' || magic === 'P2') {
+				for (var i = 0; i < values.length; ++i) {
+					const val = parseInt(values[i]);
+					const scaled = magic === 'P1' ? val * 255 : Math.round((val / maxVal) * 255);
+					pixels.push([scaled, scaled, scaled]);
+				}
+			} else if (magic === 'P3') {
+				for (var i = 0; i < values.length; i += 3) {
+					const r = Math.round((values[i] / maxVal) * 255);
+					const g = Math.round((values[i+1] / maxVal) * 255);
+					const b = Math.round((values[i+2] / maxVal) * 255);
+					pixels.push([r, g, b]);
+				}
+			}
+
+			return { width, height, pixels };
+		}
+
+		function renderPPM() {
+			if (Screen.ppmWidth == 0 || Screen.ppmHeight == 0) {
+				return;
+			}
+
+			const imageData = Screen.ctx.createImageData(Screen.ppmWidth, Screen.ppmHeight);
+
+			for (var i = 0; i < Screen.ppmPixels.length; ++i) {
+				const [r, g, b] = Screen.ppmPixels[i];
+				const j = i * 4;
+				imageData.data[j] = r;
+				imageData.data[j + 1] = g;
+				imageData.data[j + 2] = b;
+				imageData.data[j + 3] = 255;
+			}
+
+			const offsetX = (Screen.canvas.width - Screen.ppmWidth) / 2;
+			const offsetY = (Screen.canvas.height - Screen.ppmHeight) / 2;
+
+			Screen.ctx.putImageData(imageData, offsetX, offsetY);
+		}
+
+		function applyCanvasSize() {
+			var canvasWidth = parseInt(Screen.widthInput.value);
+			var canvasHeight = parseInt(Screen.heightInput.value);
+
+			if (canvasWidth == 0 || canvasHeight == 0) {
+				return;
+			}
+
+			Screen.canvas.width = canvasWidth;
+			Screen.canvas.height = canvasHeight;
+			Screen.canvas.style.boxShadow = "1px 1px 2px rgba(0, 0, 0, 0.2)";
+
+			applyBackgroundColor();
+		}
+
+		function applyBackgroundColor() {
+			Screen.ctx.fillStyle = Screen.ColorPicker.value;
+			Screen.ctx.fillRect(0, 0, Screen.canvas.width, Screen.canvas.height);
+
+			renderPPM();
+		}
+	}
+</script>
+</html>

+ 367 - 0
components/drivers/include/drivers/graphic.h

@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#ifndef __GRAPHIC_DM_H__
+#define __GRAPHIC_DM_H__
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <drivers/lcd.h>
+#include <drivers/core/dm.h>
+#include <drivers/byteorder.h>
+#include <drivers/classes/graphic.h>
+
+#undef rt_graphix_ops
+#define rt_graphix_ops(dev) \
+    rt_graphic_device_switch_primary(rt_container_of(dev, struct rt_graphic_device, parent))
+
+rt_packed(struct est_timings
+{
+    rt_uint8_t t1;
+    rt_uint8_t t2;
+    rt_uint8_t mfg_rsvd;
+});
+
+rt_packed(struct std_timing
+{
+    /* Need to multiply by 8 then add 248 */
+    rt_uint8_t hsize;
+    rt_uint8_t vfreq_aspect;
+});
+
+rt_packed(struct detailed_pixel_timing
+{
+    rt_uint8_t hactive_lo;
+    rt_uint8_t hblank_lo;
+    rt_uint8_t hactive_hblank_hi;
+    rt_uint8_t vactive_lo;
+    rt_uint8_t vblank_lo;
+    rt_uint8_t vactive_vblank_hi;
+    rt_uint8_t hsync_offset_lo;
+    rt_uint8_t hsync_pulse_width_lo;
+    rt_uint8_t vsync_offset_pulse_width_lo;
+    rt_uint8_t hsync_vsync_offset_pulse_width_hi;
+    rt_uint8_t width_mm_lo;
+    rt_uint8_t height_mm_lo;
+    rt_uint8_t width_height_mm_hi;
+    rt_uint8_t hborder;
+    rt_uint8_t vborder;
+    rt_uint8_t misc;
+});
+
+rt_packed(struct detailed_data_string
+{
+    rt_uint8_t str[13];
+});
+
+rt_packed(struct detailed_data_monitor_range
+{
+    rt_uint8_t min_vfreq;
+    rt_uint8_t max_vfreq;
+    rt_uint8_t min_hfreq_khz;
+    rt_uint8_t max_hfreq_khz;
+    /* Need to multiply by 10 */
+    rt_uint8_t pixel_clock_mhz;
+    rt_uint8_t flags;
+
+    union
+    {
+        rt_packed(struct
+        {
+            rt_uint8_t reserved;
+            /* Need to multiply by 2 */
+            rt_uint8_t hfreq_start_khz;
+            /* Need to divide by 2 */
+            rt_uint8_t c;
+            rt_le16_t m;
+            rt_uint8_t k;
+            /* Need to divide by 2 */
+            rt_uint8_t j;
+        }) gtf2;
+        rt_packed(struct
+        {
+            rt_uint8_t version;
+            /* High 6 bits: extra clock resolution */
+            rt_uint8_t data1;
+            /* Plus low 2 of above: max hactive */
+            rt_uint8_t data2;
+            rt_uint8_t supported_aspects;
+            /* Preferred aspect and blanking support */
+            rt_uint8_t flags;
+            rt_uint8_t supported_scalings;
+            rt_uint8_t preferred_refresh;
+        }) cvt;
+    } formula;
+});
+
+rt_packed(struct detailed_data_wpindex
+{
+    /* Lower 2 bits each */
+    rt_uint8_t white_yx_lo;
+    rt_uint8_t white_x_hi;
+    rt_uint8_t white_y_hi;
+    /* Need to divide by 100 then add 1 */
+    rt_uint8_t gamma;
+});
+
+rt_packed(struct cvt_timing
+{
+    rt_uint8_t code[3];
+});
+
+rt_packed(struct detailed_non_pixel
+{
+    rt_uint8_t pad1;
+#define EDID_DETAIL_EST_TIMINGS     0xf7
+#define EDID_DETAIL_CVT_3BYTE       0xf8
+#define EDID_DETAIL_COLOR_MGMT_DATA 0xf9
+#define EDID_DETAIL_STD_MODES       0xfa
+#define EDID_DETAIL_MONITOR_CPDATA  0xfb
+#define EDID_DETAIL_MONITOR_NAME    0xfc
+#define EDID_DETAIL_MONITOR_RANGE   0xfd
+#define EDID_DETAIL_MONITOR_STRING  0xfe
+#define EDID_DETAIL_MONITOR_SERIAL  0xff
+    rt_uint8_t type;
+    rt_uint8_t pad2;
+    union
+    {
+        struct detailed_data_string str;
+        struct detailed_data_monitor_range range;
+        struct detailed_data_wpindex color;
+        struct std_timing timings[6];
+        struct cvt_timing cvt[4];
+    } data;
+});
+
+rt_packed(struct detailed_timing
+{
+    /* Need to multiply by 10 KHz */
+    rt_le16_t pixel_clock;
+
+    union
+    {
+        struct detailed_pixel_timing pixel_data;
+        struct detailed_non_pixel other_data;
+    } data;
+});
+
+rt_packed(struct edid
+{
+    rt_uint8_t header[8];
+
+    /* Vendor & product info */
+    rt_uint8_t mfg_id[2];
+    rt_uint8_t prod_code[2];
+    rt_le32_t serial;
+    rt_uint8_t mfg_week;
+    rt_uint8_t mfg_year;
+
+    /* EDID version */
+    rt_uint8_t version;
+    rt_uint8_t revision;
+
+    /* Display info */
+    rt_uint8_t input;
+    rt_uint8_t width_cm;
+    rt_uint8_t height_cm;
+    rt_uint8_t gamma;
+    rt_uint8_t features;
+
+    /* Color characteristics */
+    rt_uint8_t red_green_lo;
+    rt_uint8_t black_white_lo;
+    rt_uint8_t red_x;
+    rt_uint8_t red_y;
+    rt_uint8_t green_x;
+    rt_uint8_t green_y;
+    rt_uint8_t blue_x;
+    rt_uint8_t blue_y;
+    rt_uint8_t white_x;
+    rt_uint8_t white_y;
+
+    /* Est. timings and mfg rsvd timings */
+    struct est_timings established_timings;
+
+    /* Standard timings 1-8 */
+    struct std_timing standard_timings[8];
+
+    /* Detailing timings 1-4, 18 * 4 = 72 bytes */
+    struct detailed_timing detailed_timings[4];
+
+    /* Number of 128 byte ext. blocks */
+    rt_uint8_t extensions;
+
+    /* Checksum */
+    rt_uint8_t checksum;
+});
+
+struct rt_graphic_device;
+struct rt_graphic_device_ops;
+struct rt_graphic_plane_ops;
+
+enum rt_graphic_plane_prop
+{
+    RT_GRAPHIC_PLANE_PROP_Z = 0,
+    RT_GRAPHIC_PLANE_PROP_ROTATE,
+    RT_GRAPHIC_PLANE_PROP_ALPHA,
+
+    RT_GRAPHIC_PLANE_PROP_MAX,
+};
+
+struct rt_graphic_plane
+{
+    rt_list_t list;
+    char name[RT_NAME_MAX];
+
+    int id;
+#define RT_GRAPHIC_PLANE_TYPE_OVERLAY   0
+#define RT_GRAPHIC_PLANE_TYPE_PRIMARY   1   /* Only one, add before register */
+#define RT_GRAPHIC_PLANE_TYPE_CURSOR    2   /* Only one */
+    rt_uint8_t type;
+
+    rt_uint32_t x;
+    rt_uint32_t y;
+    rt_uint32_t z;
+    rt_uint32_t width;
+    rt_uint32_t height;
+#define RT_GRAPHIC_PLANE_ROTATE_0       0   /*   +0 degrees */
+#define RT_GRAPHIC_PLANE_ROTATE_90      1   /*  +90 degrees */
+#define RT_GRAPHIC_PLANE_ROTATE_180     2   /* +180 degrees */
+#define RT_GRAPHIC_PLANE_ROTATE_270     3   /* +270 degrees */
+    rt_uint8_t rotate;
+    rt_uint8_t alpha;                       /* 0 ~ 100 */
+
+    rt_uint32_t line_length;
+    rt_uint32_t bits_per_pixel;
+
+    /* Support color modes: RTGRAPHIC_PIXEL_FORMAT_* */
+    rt_uint32_t mode;
+    rt_uint32_t modes_nr;
+    const rt_uint32_t *modes;
+
+    /* fb count = framebuffer_len / screen_len */
+    void *framebuffer;
+    rt_size_t screen_len;
+    rt_size_t framebuffer_len;
+
+    struct rt_graphic_device *graphic;
+    const struct rt_graphic_plane_ops *ops;
+
+    rt_uint8_t priv[0];
+};
+
+struct rt_graphic_plane_ops
+{
+    rt_err_t (*update)(struct rt_graphic_plane *plane, struct rt_device_rect_info *rect);
+    rt_err_t (*fb_remap)(struct rt_graphic_plane *plane, rt_uint32_t mode, struct rt_device_rect_info *rect);
+    rt_err_t (*fb_pan_display)(struct rt_graphic_plane *plane, struct rt_device_rect_info *rect);
+    rt_err_t (*fb_cleanup)(struct rt_graphic_plane *plane);
+    rt_err_t (*prop_set)(struct rt_graphic_plane *plane, enum rt_graphic_plane_prop prop, void *value);
+};
+
+struct rt_graphic_device
+{
+    struct rt_device parent;
+
+    const struct rt_graphic_device_ops *ops;
+
+    /* Display Power Manage System */
+#define RT_GRAPHIC_DPMS_ON        0
+#define RT_GRAPHIC_DPMS_STANDBY   1
+#define RT_GRAPHIC_DPMS_SUSPEND   2
+#define RT_GRAPHIC_DPMS_OFF       3
+    rt_uint32_t dpms;
+
+    rt_list_t overlay_nodes;
+    struct rt_graphic_plane *primary_plane;
+    struct rt_graphic_plane *cursor_plane;
+    struct rt_dm_ida plane_ida;
+
+    /* Display information */
+    struct edid edid;
+
+#ifdef RT_GRAPHIC_BACKLIGHT
+    struct rt_backlight_device *backlight;
+#endif
+#define RT_GRAPHIC_UPDATE_MS 16
+    struct rt_timer *update_timer;
+
+    rt_atomic_t event_notifying;
+    struct rt_device_notify event_notify;
+
+    struct rt_spinlock lock;
+};
+
+struct rt_graphic_device_ops
+{
+    rt_err_t (*dpms_switch)(struct rt_graphic_device *gdev, rt_uint32_t dpms);
+
+    rt_err_t (*set_brightness)(struct rt_graphic_device *gdev, rt_uint32_t brightness);
+    rt_err_t (*get_brightness)(struct rt_graphic_device *gdev, rt_uint32_t *out_brightness);
+
+    rt_err_t (*get_status)(struct rt_graphic_device *gdev, rt_uint32_t *out_status);
+
+    rt_err_t (*wait_vsync)(struct rt_graphic_device *gdev);
+
+    rt_err_t (*control)(struct rt_graphic_device *gdev, int cmd, void *args);
+
+    /* Switching planes supported by device driver */
+    struct rt_graphic_plane *(*current_plane)(struct rt_graphic_device *gdev);
+};
+
+rt_err_t rt_graphic_device_register(struct rt_graphic_device *gdev);
+rt_err_t rt_graphic_device_unregister(struct rt_graphic_device *gdev);
+
+struct rt_graphic_plane *rt_graphic_device_alloc_plane(struct rt_graphic_device *gdev,
+        rt_size_t priv_size, const struct rt_graphic_plane_ops *ops,
+        const rt_uint32_t *modes, rt_uint32_t modes_nr, rt_uint8_t type);
+void rt_graphic_device_free_plane(struct rt_graphic_plane *plane);
+
+rt_err_t rt_graphic_device_add_plane(struct rt_graphic_device *gdev,
+        struct rt_graphic_plane *plane);
+rt_err_t rt_graphic_device_del_plane(struct rt_graphic_device *gdev,
+        struct rt_graphic_plane *plane);
+
+void rt_graphic_device_hotplug_event(struct rt_graphic_device *gdev);
+
+rt_err_t rt_graphic_device_update_auto(struct rt_graphic_device *gdev, rt_uint32_t update_ms);
+
+void rt_graphic_device_enter(struct rt_graphic_device *gdev);
+void rt_graphic_device_leave(struct rt_graphic_device *gdev);
+
+rt_uint32_t rt_graphic_mode_bpp(rt_uint32_t mode);
+
+struct rt_device_graphic_ops *rt_graphic_device_switch_primary(struct rt_graphic_device *gdev);
+
+rt_err_t rt_graphic_device_simple_edid(struct rt_graphic_device *gdev,
+        rt_uint32_t width, rt_uint32_t height, rt_uint32_t refresh_hz);
+
+rt_err_t rt_graphic_device_simple_register(struct rt_graphic_device *gdev,
+        rt_uint32_t width, rt_uint32_t height, rt_uint32_t refresh_hz,
+        const struct rt_graphic_plane_ops *plane_ops,
+        const rt_uint32_t *modes, rt_uint32_t modes_nr);
+rt_err_t rt_graphic_device_simple_unregister(struct rt_graphic_device *gdev);
+
+#ifdef RT_GRAPHIC_LOGO
+rt_err_t rt_graphic_logo_change(void *data, int width, int height, int color_max);
+rt_err_t rt_graphic_logo_render(struct rt_graphic_device *gdev);
+#else
+rt_inline rt_err_t rt_graphic_logo_change(void *data, int width, int height, int color_max)
+{
+    return RT_EOK;
+}
+
+rt_inline rt_err_t rt_graphic_logo_render(struct rt_graphic_device *gdev)
+{
+    return RT_EOK;
+}
+#endif /* RT_GRAPHIC_LOGO */
+
+#endif /* __GRAPHIC_DM_H__ */

+ 33 - 0
components/drivers/include/drivers/lcd.h

@@ -11,6 +11,8 @@
 #ifndef RT_LCD_H__
 #define RT_LCD_H__
 
+#include <stdint.h>
+
 /* ioctls
    0x46 is 'F'                                                          */
 
@@ -109,4 +111,35 @@ struct fb_fix_screeninfo
     uint16_t reserved[2];      /* Reserved for future compatibility */
 };
 
+struct fb_cmap
+{
+    uint32_t start;             /* First entry  */
+    uint32_t len;               /* Number of entries */
+    uint16_t *red;              /* Red values   */
+    uint16_t *green;
+    uint16_t *blue;
+    uint16_t *transp;           /* transparency, can be NULL */
+};
+
+struct fb_con2fbmap
+{
+    uint32_t console;
+    uint32_t framebuffer;
+};
+
+/* VESA Blanking Levels */
+#define VESA_NO_BLANKING        0
+#define VESA_VSYNC_SUSPEND      1
+#define VESA_HSYNC_SUSPEND      2
+#define VESA_POWERDOWN          3
+
+enum
+{
+    FB_BLANK_UNBLANK       = VESA_NO_BLANKING,          /* screen: unblanked, hsync: on,  vsync: on */
+    FB_BLANK_NORMAL        = VESA_NO_BLANKING + 1,      /* screen: blanked,   hsync: on,  vsync: on */
+    FB_BLANK_VSYNC_SUSPEND = VESA_VSYNC_SUSPEND + 1,    /* screen: blanked,   hsync: on,  vsync: off */
+    FB_BLANK_HSYNC_SUSPEND = VESA_HSYNC_SUSPEND + 1,    /* screen: blanked,   hsync: off, vsync: on */
+    FB_BLANK_POWERDOWN     = VESA_POWERDOWN + 1,        /* screen: blanked,   hsync: off, vsync: off */
+};
+
 #endif

+ 310 - 0
examples/test/dm_graphic_test.c

@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2006-2021, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include <drivers/misc.h>
+
+#ifdef RT_USING_GRAPHIC
+typedef rt_int32_t fixed;       /* Q16.16 */
+
+#define FIX_ONE     65536
+#define FIX_06      19661      /* 0.3 */
+#define FIX_35      22938      /* 0.35 */
+#define FIX_14      917504     /* 14.0 */
+#define FIX_286     187432     /* 2.86 */
+#define FIX_75      49152      /* 0.75 */
+#define FIX_1_3     21845
+#define FIX_2_3     43690
+#define FIX_6       393216
+#define FIX_3       196608
+#define FIX_255     16711680   /* 255 */
+
+#define FMUL(a,b)    ((fixed)(((rt_int64_t)(a) * (b)) >> 16))
+#define FDIV(a,b)    ((fixed)(((rt_int64_t)(a) << 16) / (b)))
+
+typedef struct { fixed x, y, z; } vec3f;
+
+rt_inline fixed fix_floor(fixed x)
+{
+    return x & 0xffff0000;
+}
+
+rt_inline fixed fix_fract(fixed x)
+{
+    return x & 0x0000ffff;
+}
+
+rt_inline fixed fix_abs(fixed x)
+{
+    return rt_abs(x);
+}
+
+rt_inline fixed fix_clamp(fixed x, fixed a, fixed b)
+{
+    return rt_clamp(x, a, b);
+}
+
+static vec3f clamp3(vec3f v, fixed a, fixed b)
+{
+    vec3f r =
+    {
+        .x = fix_clamp(v.x, a, b),
+        .y = fix_clamp(v.y, a, b),
+        .z = fix_clamp(v.z, a, b),
+    };
+
+    return r;
+}
+
+static vec3f mix3(vec3f A, vec3f B, fixed t)
+{
+    fixed omt = FIX_ONE - t;
+    vec3f r =
+    {
+        .x = FMUL(A.x, omt) + FMUL(B.x, t),
+        .y = FMUL(A.y, omt) + FMUL(B.y, t),
+        .z = FMUL(A.z, omt) + FMUL(B.z, t),
+    };
+
+    return r;
+}
+
+static vec3f hsv2rgb(vec3f c)
+{
+    vec3f t, r, base, mixed;
+    fixed pX = FMUL(fix_fract(c.x + FIX_ONE), FIX_6);
+    fixed pY = FMUL(fix_fract(c.x + FIX_2_3), FIX_6);
+    fixed pZ = FMUL(fix_fract(c.x + FIX_1_3), FIX_6);
+
+    pX = fix_abs(pX - FIX_3);
+    pY = fix_abs(pY - FIX_3);
+    pZ = fix_abs(pZ - FIX_3);
+
+    t.x = pX - FIX_ONE;
+    t.y = pY - FIX_ONE;
+    t.z = pZ - FIX_ONE;
+    t = clamp3(t, 0, FIX_ONE);
+
+    base.x = FIX_ONE;
+    base.y = FIX_ONE;
+    base.z = FIX_ONE;
+
+    mixed = mix3(base, t, c.y);
+
+    r.x = FMUL(c.z, mixed.x);
+    r.y = FMUL(c.z, mixed.y);
+    r.z = FMUL(c.z, mixed.z);
+
+    return r;
+}
+
+static void shader_frame(int px, int py, int width, int height, int frame,
+    rt_uint8_t *r, rt_uint8_t *g, rt_uint8_t *b)
+{
+    vec3f col;
+    fixed uv_x, uv_y, shift, size_computed, st, x;
+
+    uv_x = FDIV((fixed)(px << 16), (fixed)(width << 16));
+    uv_y = FDIV((fixed)(py << 16), (fixed)(width << 16));
+
+    shift = fix_fract(FDIV(FMUL((frame << 16), FIX_06), FIX_286));
+    shift = FMUL(shift, FIX_286);
+
+    size_computed = FMUL(uv_x + shift - uv_y, FIX_35);
+
+    st = FMUL(size_computed, FIX_14);
+    x = FDIV(fix_floor(st), FIX_14);
+
+    col = hsv2rgb((vec3f){ x, FIX_75, FIX_ONE });
+
+    *r = (rt_uint8_t)((FMUL(col.x, FIX_255) >> 16) & 255);
+    *g = (rt_uint8_t)((FMUL(col.y, FIX_255) >> 16) & 255);
+    *b = (rt_uint8_t)((FMUL(col.z, FIX_255) >> 16) & 255);
+}
+
+static void conv_gray4(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    rt_uint8_t gray = (r * 30 + g * 59 + b * 11) / 100;
+    *(rt_uint8_t *)pixel = gray >> 4;
+}
+
+static void conv_gray16(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    rt_uint16_t gray = (r * 30 + g * 59 + b * 11) / 100;
+    rt_uint16_t out  = (gray << 8) | gray;
+    *(rt_uint16_t *)pixel = out;
+}
+
+static void conv_rgb332(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint8_t*)pixel = ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6);
+}
+
+static void conv_rgb444(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint16_t *)pixel = ((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4);
+}
+
+static void conv_rgb565(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint16_t *)pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
+}
+
+static void conv_rgb565p(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint16_t *)pixel = ((g >> 2) << 10) | ((r >> 3) << 5) | (b >> 3);
+}
+
+static void conv_bgr565(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint16_t *)pixel = ((b >> 3) << 11) | ((g >> 2) << 5) | (r >> 3);
+}
+
+static void conv_rgb666(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    rt_uint8_t *p = (rt_uint8_t *)pixel;
+
+    p[0] = r & 0xfc;
+    p[1] = g & 0xfc;
+    p[2] = b & 0xfc;
+}
+
+static void conv_rgb888(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    rt_uint8_t *p = (rt_uint8_t *)pixel;
+
+    p[0] = r;
+    p[1] = g;
+    p[2] = b;
+}
+
+static void conv_bgr888(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    rt_uint8_t *p = (rt_uint8_t*)pixel;
+    p[0] = b;
+    p[1] = g;
+    p[2] = r;
+}
+
+static void conv_argb888(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint32_t *)pixel = (0xffU << 24) | (r << 16) | (g << 8) | b;
+}
+
+static void conv_abgr888(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel)
+{
+    *(rt_uint32_t *)pixel = (0xffU << 24) | (b << 16) | (g << 8) | r;
+}
+
+static void (*conv_funcs[])(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel) =
+{
+    [RTGRAPHIC_PIXEL_FORMAT_GRAY4]      = conv_gray4,
+    [RTGRAPHIC_PIXEL_FORMAT_GRAY16]     = conv_gray16,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB332]     = conv_rgb332,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB444]     = conv_rgb444,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB565]     = conv_rgb565,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB565P]    = conv_rgb565p,
+    [RTGRAPHIC_PIXEL_FORMAT_BGR565]     = conv_bgr565,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB666]     = conv_rgb666,
+    [RTGRAPHIC_PIXEL_FORMAT_RGB888]     = conv_rgb888,
+    [RTGRAPHIC_PIXEL_FORMAT_BGR888]     = conv_bgr888,
+    [RTGRAPHIC_PIXEL_FORMAT_ARGB888]    = conv_argb888,
+    [RTGRAPHIC_PIXEL_FORMAT_ABGR888]    = conv_abgr888,
+};
+
+rt_err_t graphic_start(const char *gdev, int count)
+{
+    rt_err_t err;
+    rt_uint8_t *vfb, *fb, *pixel, bpp;
+    struct rt_device_graphic_info info;
+    struct rt_device *dev = rt_device_find(gdev);
+    void (*conv_func)(rt_uint8_t r, rt_uint8_t g, rt_uint8_t b, void *pixel);
+
+    if (!dev)
+    {
+        return -RT_EINVAL;
+    }
+
+    if ((err = rt_device_open(dev, 0)))
+    {
+        return err;
+    }
+
+    if ((err = rt_device_control(dev, RTGRAPHIC_CTRL_GET_INFO, &info)))
+    {
+        goto _end;
+    }
+
+    if (!(vfb = rt_malloc(info.smem_len)))
+    {
+        err = -RT_ENOMEM;
+        goto _end;
+    }
+
+    bpp = info.bits_per_pixel / 8;
+    conv_func = conv_funcs[info.pixel_format];
+
+    for (int frame = 0; frame < count; ++frame)
+    {
+        fb = vfb;
+
+        for (int y = 0; y < info.height; ++y)
+        {
+            pixel = fb;
+
+            for (int x = 0; x < info.width; ++x)
+            {
+                rt_uint8_t r, g, b;
+
+                shader_frame(x, y, info.width, info.height, frame, &r, &g, &b);
+
+                conv_func(r, g, b, pixel);
+
+                pixel += bpp;
+            }
+
+            fb += info.pitch;
+        }
+
+        rt_memcpy(info.framebuffer, vfb, info.smem_len);
+    }
+
+    rt_free(vfb);
+
+_end:
+    rt_device_close(dev);
+
+    return err;
+}
+
+#ifdef RT_USING_FINSH
+#include <stdlib.h>
+
+static int _graphic_start(int argc, char**argv)
+{
+    int count = 10;
+    const char *gdev = "fb0";
+
+    if (argc > 1)
+    {
+        gdev = argv[1];
+    }
+    if (argc > 2)
+    {
+        count = atoi(argv[2]);
+    }
+
+    return (int)graphic_start(gdev, count);
+}
+MSH_CMD_EXPORT_ALIAS(_graphic_start, graphic_start, fixed resolution only e.g: graphic_start("fb0", 10));
+#endif /* RT_USING_FINSH */
+#endif /* RT_USING_GRAPHIC */

+ 421 - 0
examples/test/dm_hmi_test.c

@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2006-2021, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include <cpuport.h>
+#include <drivers/misc.h>
+
+#if defined(RT_USING_GRAPHIC) && defined(RT_USING_INPUT)
+
+#define CURSOR_WIDTH    64
+#define CURSOR_HEIGHT   64
+
+struct hmi_info
+{
+    struct rt_device *gdev;
+    struct rt_device *idev;
+
+    struct rt_device_graphic_info info;
+    struct rt_device_notify event_notify;
+    struct rt_input_handler handler;
+
+    rt_bool_t event;
+    rt_bool_t vsync;
+    rt_bool_t keydown;
+
+    rt_uint32_t x, y;
+    rt_uint32_t dx, dy;
+    rt_uint32_t bytes_per_pixel;
+    rt_ubase_t line[2];
+    rt_ubase_t colors[4];
+
+    void *backend_framebuffer;
+};
+
+static rt_bool_t hmi_working;
+
+static struct rt_input_device *to_input_device(struct rt_device *idev)
+{
+    return rt_container_of(idev, struct rt_input_device, parent);
+}
+
+static rt_ubase_t to_color(rt_uint8_t color255, rt_ubase_t color_max)
+{
+    return (rt_ubase_t)color255 * color_max / 255;
+}
+
+static void hmi_reset(struct hmi_info *hmi)
+{
+    void *cursor;
+    rt_ubase_t none_alpha;
+    rt_ubase_t red_off, green_off, blue_off, alpha_off;
+    rt_ubase_t red_mask, green_mask, blue_mask, alpha_mask;
+    struct fb_var_screeninfo var;
+
+    if (hmi->backend_framebuffer)
+    {
+        rt_free(hmi->backend_framebuffer);
+    }
+
+    rt_device_control(hmi->gdev, FBIOGET_VSCREENINFO, &var);
+    rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_GET_INFO, &hmi->info);
+
+    hmi->backend_framebuffer = rt_malloc(hmi->info.smem_len);
+
+    hmi->bytes_per_pixel = hmi->info.bits_per_pixel / 8;
+    red_off = var.red.offset;
+    red_mask = RT_GENMASK(var.red.length - 1, 0);
+    green_off = var.green.offset;
+    green_mask = RT_GENMASK(var.green.length - 1, 0);
+    blue_off = var.blue.offset;
+    blue_mask = RT_GENMASK(var.blue.length - 1, 0);
+
+    if (var.transp.length)
+    {
+        alpha_off = var.transp.offset;
+        alpha_mask = RT_GENMASK(var.transp.length - 1, 0);
+    }
+    else
+    {
+        alpha_off = 0;
+        alpha_mask = 0;
+    }
+
+    if ((cursor = rt_malloc(CURSOR_WIDTH * CURSOR_HEIGHT * hmi->bytes_per_pixel)))
+    {
+        rt_uint8_t *stream = cursor;
+        rt_ubase_t color = ((to_color(0x82, red_mask)) << red_off) |
+                            (to_color(0x50, green_mask) << green_off) |
+                            (to_color(0xdf, blue_mask) << blue_off) |
+                            (to_color(0xcc, alpha_mask) << alpha_off);
+
+        for (int y = 0; y < CURSOR_HEIGHT; ++y)
+        {
+            for (int x = 0; x < CURSOR_WIDTH; ++x)
+            {
+                rt_memcpy(stream, &color, hmi->bytes_per_pixel);
+                stream += hmi->bytes_per_pixel;
+            }
+        }
+
+        rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_TYPE, cursor);
+        rt_free(cursor);
+    }
+
+    none_alpha = alpha_mask << alpha_off;
+
+    hmi->line[0] = ~0UL;
+    hmi->line[1] = none_alpha;
+
+    hmi->colors[0] = ((to_color(0xff, red_mask)) << red_off) |
+                      (to_color(0x4b, green_mask) << green_off) |
+                      (to_color(0x00, blue_mask) << blue_off) | none_alpha;
+    hmi->colors[1] = ((to_color(0x7f, red_mask)) << red_off) |
+                      (to_color(0xdb, green_mask) << green_off) |
+                      (to_color(0x3b, blue_mask) << blue_off) | none_alpha;
+    hmi->colors[2] = ((to_color(0x00, red_mask)) << red_off) |
+                      (to_color(0xa4, green_mask) << green_off) |
+                      (to_color(0xef, blue_mask) << blue_off) | none_alpha;
+    hmi->colors[3] = ((to_color(0xff, red_mask)) << red_off) |
+                      (to_color(0xb8, green_mask) << green_off) |
+                      (to_color(0x1c, blue_mask) << blue_off) | none_alpha;
+
+    hmi->event = RT_FALSE;
+}
+
+static void hmi_graphic_notify(rt_device_t dev)
+{
+    struct hmi_info *hmi = (void *)dev;
+
+    hmi->event = RT_TRUE;
+}
+
+static rt_bool_t hmi_input_callback(struct rt_input_handler *handler,
+        struct rt_input_event *ev)
+{
+    struct hmi_info *hmi = handler->priv;
+
+    if (ev->type == EV_ABS)
+    {
+        if (ev->code == 0)
+        {
+            hmi->dx = (ev->value * hmi->info.width) /
+                (handler->idev->absinfo->maximum - handler->idev->absinfo->minimum);
+        }
+        else if (ev->code == 1)
+        {
+            hmi->dy = (ev->value * hmi->info.height) /
+                (handler->idev->absinfo->maximum - handler->idev->absinfo->minimum);
+        }
+    }
+    else if (ev->type == EV_KEY)
+    {
+        if (ev->code == BTN_LEFT)
+        {
+            if (hmi->keydown && ev->value == 0)
+            {
+                /* Swap lines color */
+                hmi->line[0] ^= hmi->line[1];
+                hmi->line[1] ^= hmi->line[0];
+                hmi->line[0] ^= hmi->line[1];
+
+                hmi->keydown = RT_FALSE;
+                hmi->vsync = RT_FALSE;
+            }
+            else
+            {
+                hmi->keydown = RT_TRUE;
+            }
+        }
+    }
+    else if (ev->type == EV_SYN)
+    {
+        hmi->vsync = RT_FALSE;
+    }
+
+    return RT_TRUE;
+}
+
+static void hmi_loop(void *param)
+{
+    struct hmi_info *hmi = param;
+    struct rt_device_rect_info rect;
+    struct rt_device_graphic_ops *gops;
+
+    /* Graphic device event */
+    hmi->event_notify.notify = &hmi_graphic_notify;
+    hmi->event_notify.dev = (void *)hmi;
+    rt_device_control(hmi->gdev, RT_DEVICE_CTRL_NOTIFY_SET, &hmi->event_notify);
+
+    /* Input device event */
+    hmi->handler.idev = to_input_device(hmi->idev);
+    hmi->handler.identify = RT_NULL;
+    hmi->handler.callback = &hmi_input_callback;
+    hmi->handler.priv = hmi;
+    rt_input_add_handler(&hmi->handler);
+
+    hmi->backend_framebuffer = RT_NULL;
+    hmi_reset(hmi);
+
+    hmi->dx = hmi->info.width >> 1;
+    hmi->dy = hmi->info.height >> 1;
+
+    rect.x = 0;
+    rect.y = 0;
+
+    gops = rt_graphix_ops(hmi->gdev);
+    rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_POWERON, RT_NULL);
+
+    while (hmi_working)
+    {
+        rt_ubase_t pos;
+
+        /* Wait graphic change */
+        if (hmi->event)
+        {
+            hmi_reset(hmi);
+        }
+
+        hmi->x = hmi->dx;
+        hmi->y = hmi->dy;
+
+        rect.width = hmi->info.width;
+        rect.height = hmi->info.height;
+        pos = RTGRAPHIC_PIXEL_POSITION(hmi->x, hmi->y);
+
+        for (int i = 0; i < RT_ARRAY_SIZE(hmi->colors); ++i)
+        {
+            rt_uint32_t x1, y1, x2, y2;
+            void *fb = hmi->backend_framebuffer ? : hmi->info.framebuffer;
+
+            switch (i)
+            {
+            case 0:
+                x1 = 0;
+                y1 = 0;
+                x2 = hmi->x;
+                y2 = hmi->y;
+                break;
+
+            case 1:
+                x1 = hmi->x;
+                y1 = 0;
+                x2 = hmi->info.width;
+                y2 = hmi->y;
+                break;
+
+            case 2:
+                x1 = 0;
+                y1 = hmi->y;
+                x2 = hmi->x;
+                y2 = hmi->info.height;
+                break;
+
+            case 3:
+                x1 = hmi->x;
+                y1 = hmi->y;
+                x2 = hmi->info.width;
+                y2 = hmi->info.height;
+                break;
+            }
+
+            fb += x1 * hmi->bytes_per_pixel + y1 * hmi->info.pitch;
+
+            for (int y = y1; y < y2; ++y)
+            {
+                void *fb_entry = fb;
+
+                for (int x = x1; x < x2; ++x)
+                {
+                    rt_memcpy(fb, &hmi->colors[i], hmi->bytes_per_pixel);
+                    fb += hmi->bytes_per_pixel;
+                }
+
+                fb = fb_entry + hmi->info.pitch;
+            }
+        }
+
+        if (hmi->backend_framebuffer)
+        {
+            rt_memcpy(hmi->info.framebuffer, hmi->backend_framebuffer, hmi->info.smem_len);
+        }
+
+        gops->draw_hline((void *)&hmi->line[0], 0, rect.width, hmi->y);
+        gops->draw_vline((void *)&hmi->line[1], hmi->x, 0, rect.height);
+
+        rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_RECT_UPDATE, &rect);
+        rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_POSITION, (void *)pos);
+
+        /* Next position */
+        hmi->vsync = RT_TRUE;
+        rt_hw_wmb();
+
+        while (hmi_working && hmi->vsync)
+        {
+            rt_thread_mdelay(1);
+        }
+    }
+
+    rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_POWEROFF, RT_NULL);
+
+    rt_memset(&hmi->event_notify, 0, sizeof(hmi->event_notify));
+    rt_device_control(hmi->gdev, RT_DEVICE_CTRL_NOTIFY_SET, &hmi->event_notify);
+
+    rt_input_del_handler(&hmi->handler);
+
+    rt_device_close(hmi->gdev);
+    rt_device_close(hmi->idev);
+
+    if (hmi->backend_framebuffer)
+    {
+        rt_free(hmi->backend_framebuffer);
+    }
+    rt_free(hmi);
+
+    rt_thread_delete(rt_thread_self());
+}
+
+rt_err_t hmi_start(const char *gdev, const char *idev)
+{
+    rt_err_t err;
+    struct hmi_info *hmi;
+    struct rt_thread *loop;
+
+    if (hmi_working)
+    {
+        rt_kprintf("HMI is running\n");
+        return -RT_EBUSY;
+    }
+
+    hmi = rt_malloc(sizeof(*hmi));
+
+    if (!hmi)
+    {
+        return -RT_ENOMEM;
+    }
+
+    hmi->gdev = rt_device_find(gdev);
+    hmi->idev = rt_device_find(idev);
+
+    if (!hmi->gdev || !hmi->idev)
+    {
+        rt_free(hmi);
+        return -RT_EINVAL;
+    }
+
+    if (!rt_bitmap_test_bit(to_input_device(hmi->idev)->cap, EV_ABS))
+    {
+        rt_kprintf("%s is not a ABS input\n", idev);
+        rt_free(hmi);
+        return -RT_EINVAL;
+    }
+
+    if ((err = rt_device_open(hmi->gdev, 0)))
+    {
+        rt_free(hmi);
+        return err;
+    }
+
+    if ((err = rt_device_open(hmi->idev, 0)))
+    {
+        rt_device_close(hmi->gdev);
+        rt_free(hmi);
+        return err;
+    }
+
+    loop = rt_thread_create("HMI", hmi_loop, hmi,
+            DM_THREAD_STACK_SIZE,
+            RT_THREAD_PRIORITY_MAX / 3,
+            rt_tick_from_millisecond(RT_GRAPHIC_UPDATE_MS));
+
+    if (!loop)
+    {
+        rt_device_close(hmi->gdev);
+        rt_device_close(hmi->idev);
+        rt_free(hmi);
+        return -RT_ENOMEM;
+    }
+
+    hmi_working = RT_TRUE;
+    rt_thread_startup(loop);
+
+    return RT_EOK;
+}
+
+rt_err_t hmi_stop(void)
+{
+    hmi_working = RT_FALSE;
+    return RT_EOK;
+}
+
+#ifdef RT_USING_FINSH
+static int _hmi_start(int argc, char**argv)
+{
+    const char *gdev = "fb0", *idev = "input0";
+
+    if (argc == 3)
+    {
+        gdev = argv[1];
+        idev = argv[2];
+    }
+
+    return (int)hmi_start(gdev, idev);
+}
+MSH_CMD_EXPORT_ALIAS(_hmi_start, hmi_start, e.g: hmi_start("fb0", "input0"));
+
+static int _hmi_stop(void)
+{
+    return (int)hmi_stop();
+}
+MSH_CMD_EXPORT_ALIAS(_hmi_stop, hmi_stop, e.g: hmi_exit());
+#endif /* RT_USING_FINSH */
+#endif /* RT_USING_GRAPHIC && RT_USING_INPUT */