diff --git a/components/drivers/graphic/graphic.c b/components/drivers/graphic/graphic.c index d1692c851e1..90bdde28bbd 100644 --- a/components/drivers/graphic/graphic.c +++ b/components/drivers/graphic/graphic.c @@ -107,14 +107,25 @@ static rt_err_t plane_fb_remap(struct rt_graphic_plane *plane, 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; + rt_size_t offset, row_bytes, span; - framebuffer_end += rect->x * byte_per_pixel; - framebuffer_end += rect->y * plane->line_length; - framebuffer_end += rect->width * rect->height * byte_per_pixel; + if (!plane->framebuffer || !byte_per_pixel || !rect->width || !rect->height) + { + return -RT_EINVAL; + } + + row_bytes = rect->width * byte_per_pixel; - if (framebuffer_end < plane->framebuffer + plane->framebuffer_len) + if (rect->x * byte_per_pixel + row_bytes > plane->line_length) + { + return -RT_EINVAL; + } + + offset = rect->y * plane->line_length + rect->x * byte_per_pixel; + span = (rect->height - 1) * plane->line_length + row_bytes; + + if (offset <= plane->framebuffer_len && span <= plane->framebuffer_len - offset) { return plane->ops->fb_pan_display(plane, rect); } @@ -203,6 +214,7 @@ 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; + rt_bool_t wait_vsync = RT_FALSE; struct rt_graphic_device *gdev = raw_to_graphic(dev); _retry: @@ -520,7 +532,7 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) case RTGRAPHIC_CTRL_WAIT_VSYNC: if (gdev->ops->wait_vsync) { - err = gdev->ops->wait_vsync(gdev); + wait_vsync = RT_TRUE; } break; @@ -555,6 +567,8 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) var->yres = plane->height; var->xres_virtual = plane->width; var->yres_virtual = plane->height * (plane->framebuffer_len / plane->screen_len); + var->xoffset = plane->x; + var->yoffset = plane->y; var->bits_per_pixel = plane->bits_per_pixel; if (plane == gdev->primary_plane) @@ -668,10 +682,16 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) for (int i = 0; i < plane->modes_nr; ++i) { - /* Check supported and commit */ - if (plane->modes[i] == mode && plane->mode != mode) + if (plane->modes[i] == mode) { - err = plane_fb_remap(plane, mode, &rect); + err = RT_EOK; + + if (plane->mode != mode) + { + err = plane_fb_remap(plane, mode, &rect); + } + + break; } } @@ -685,6 +705,11 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) err = plane_fb_pan_display(plane, &rect); } + if (!err && (var->activate & FB_ACTIVATE_VBL) && gdev->ops->wait_vsync) + { + wait_vsync = RT_TRUE; + } + if (!err && var->rotate && plane->ops->prop_set) { err = plane->ops->prop_set(plane, RT_GRAPHIC_PLANE_PROP_ROTATE, @@ -733,6 +758,11 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) fix->mmio_start = fix->smem_start; fix->mmio_len = plane->screen_len; fix->line_length = plane->line_length; + + if (plane->ops->fb_pan_display && plane->framebuffer_len > plane->screen_len) + { + fix->ypanstep = 1; + } } else { @@ -769,6 +799,11 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) rect.height = var->yres; err = plane_fb_pan_display(plane, &rect); + + if (!err && (var->activate & FB_ACTIVATE_VBL) && gdev->ops->wait_vsync) + { + wait_vsync = RT_TRUE; + } } else { @@ -901,7 +936,7 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) case FBIO_WAITFORVSYNC: if (gdev->ops->wait_vsync) { - err = gdev->ops->wait_vsync(gdev); + wait_vsync = RT_TRUE; } break; @@ -930,6 +965,11 @@ static rt_err_t _graphic_control(rt_device_t dev, int cmd, void *args) spin_unlock(&gdev->lock); + if (!err && wait_vsync) + { + err = gdev->ops->wait_vsync(gdev); + } + return err; } diff --git a/components/drivers/include/drivers/lcd.h b/components/drivers/include/drivers/lcd.h index 11fa8e6dccc..35530b8f374 100644 --- a/components/drivers/include/drivers/lcd.h +++ b/components/drivers/include/drivers/lcd.h @@ -40,6 +40,15 @@ #define FBIOGET_DISPINFO 0x4618 #define FBIO_WAITFORVSYNC 0x4620 +/* fb_var_screeninfo.activate */ +#define FB_ACTIVATE_NOW 0 +#define FB_ACTIVATE_NXTOPEN 1 +#define FB_ACTIVATE_TEST 2 +#define FB_ACTIVATE_MASK 15 +#define FB_ACTIVATE_VBL 16 +#define FB_ACTIVATE_ALL 64 +#define FB_ACTIVATE_FORCE 128 + struct fb_bitfield { uint32_t offset; /* beginning of bitfield */ diff --git a/examples/test/dm_graphic_test.c b/examples/test/dm_graphic_test.c index a6894ddc6e6..9017994ab31 100644 --- a/examples/test/dm_graphic_test.c +++ b/examples/test/dm_graphic_test.c @@ -11,6 +11,7 @@ #include #include +#include #include #ifdef RT_USING_GRAPHIC @@ -225,7 +226,10 @@ rt_err_t graphic_start(const char *gdev, int count) { rt_err_t err; rt_uint8_t *vfb, *fb, *pixel, bpp; + rt_size_t frame_len, page_count, front_page; + struct rt_device_rect_info rect; struct rt_device_graphic_info info; + struct fb_var_screeninfo var; 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); @@ -244,7 +248,21 @@ rt_err_t graphic_start(const char *gdev, int count) goto _end; } - if (!(vfb = rt_malloc(info.smem_len))) + if ((err = rt_device_control(dev, FBIOGET_VSCREENINFO, &var))) + { + goto _end; + } + + frame_len = (rt_size_t)info.pitch * info.height; + page_count = frame_len ? info.smem_len / frame_len : 0; + + if (!page_count) + { + err = -RT_EINVAL; + goto _end; + } + + if (!(vfb = rt_malloc(frame_len))) { err = -RT_ENOMEM; goto _end; @@ -252,6 +270,19 @@ rt_err_t graphic_start(const char *gdev, int count) bpp = info.bits_per_pixel / 8; conv_func = conv_funcs[info.pixel_format]; + front_page = var.yoffset / info.height; + + if (page_count > 1) + { + /* Explicit page flips do not need the legacy periodic update timer. */ + var.pixclock = 0; + var.activate = FB_ACTIVATE_NOW; + + if ((err = rt_device_control(dev, FBIOPUT_VSCREENINFO, &var))) + { + goto _free_vfb; + } + } for (int frame = 0; frame < count; ++frame) { @@ -275,9 +306,39 @@ rt_err_t graphic_start(const char *gdev, int count) fb += info.pitch; } - rt_memcpy(info.framebuffer, vfb, info.smem_len); + if (page_count > 1) + { + front_page = (front_page + 1) % page_count; + rt_memcpy((rt_uint8_t *)info.framebuffer + front_page * frame_len, + vfb, frame_len); + + var.xoffset = 0; + var.yoffset = front_page * info.height; + var.activate = FB_ACTIVATE_VBL; + err = rt_device_control(dev, FBIOPAN_DISPLAY, &var); + } + else + { + rt_memcpy(info.framebuffer, vfb, frame_len); + rect.x = 0; + rect.y = 0; + rect.width = info.width; + rect.height = info.height; + err = rt_device_control(dev, RTGRAPHIC_CTRL_RECT_UPDATE, &rect); + + if (!err) + { + err = rt_device_control(dev, RTGRAPHIC_CTRL_WAIT_VSYNC, RT_NULL); + } + } + + if (err) + { + break; + } } +_free_vfb: rt_free(vfb); _end: diff --git a/examples/test/dm_hmi_test.c b/examples/test/dm_hmi_test.c index b8fea78ad6c..359bf19294c 100644 --- a/examples/test/dm_hmi_test.c +++ b/examples/test/dm_hmi_test.c @@ -1,23 +1,28 @@ /* - * Copyright (c) 2006-2021, RT-Thread Development Team + * Copyright (c) 2006-2026, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-02-25 GuEe-GUI the first version + * 2026-07-23 GuEe-GUI support touch input and page flipping */ #include #include #include +#include #include #if defined(RT_USING_GRAPHIC) && defined(RT_USING_INPUT) -#define CURSOR_WIDTH 64 -#define CURSOR_HEIGHT 64 +#define HMI_CURSOR_WIDTH 64 +#define HMI_CURSOR_HEIGHT 64 +#define HMI_MAX_MT_SLOTS 16 +#define HMI_MAX_INPUT_DEVICES 32 +#define HMI_MAX_GRAPHIC_DEVICES 8 struct hmi_info { @@ -25,342 +30,823 @@ struct hmi_info struct rt_device *idev; struct rt_device_graphic_info info; + struct fb_var_screeninfo var; struct rt_device_notify event_notify; struct rt_input_handler handler; - - rt_bool_t event; - rt_bool_t vsync; + struct rt_semaphore wakeup; + + volatile rt_bool_t running; + volatile rt_bool_t dirty; + volatile rt_bool_t reconfigure; + rt_bool_t powered; + rt_bool_t notify_installed; + rt_bool_t handler_installed; rt_bool_t keydown; + rt_bool_t multitouch; - rt_uint32_t x, y; - rt_uint32_t dx, dy; + rt_uint32_t dx; + rt_uint32_t dy; rt_uint32_t bytes_per_pixel; + rt_uint32_t mt_x[HMI_MAX_MT_SLOTS]; + rt_uint32_t mt_y[HMI_MAX_MT_SLOTS]; + rt_int32_t mt_slot; + rt_ubase_t mt_active; rt_ubase_t line[2]; rt_ubase_t colors[4]; - void *backend_framebuffer; + rt_size_t frame_len; + rt_size_t page_count; + rt_size_t front_page; + void *framebuffer; }; -static rt_bool_t hmi_working; +static struct hmi_info *hmi_active; 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) +static rt_ubase_t hmi_color_mask(rt_uint8_t length) { - return (rt_ubase_t)color255 * color_max / 255; + if (!length) + { + return 0; + } + + if (length >= RT_BITS_PER_TYPE(rt_ubase_t)) + { + return ~0UL; + } + + return (1UL << length) - 1; } -static void hmi_reset(struct hmi_info *hmi) +static rt_ubase_t hmi_color_component(rt_uint8_t value, rt_ubase_t mask) { - 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; + return (rt_ubase_t)value * mask / 255; +} - if (hmi->backend_framebuffer) +static rt_bool_t hmi_color_field_valid(const struct fb_bitfield *field, + rt_uint32_t bits_per_pixel) +{ + if (!field->length) { - rt_free(hmi->backend_framebuffer); + return RT_TRUE; } - rt_device_control(hmi->gdev, FBIOGET_VSCREENINFO, &var); - rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_GET_INFO, &hmi->info); + return field->offset < bits_per_pixel && + field->length <= bits_per_pixel - field->offset && + field->length <= RT_BITS_PER_TYPE(rt_ubase_t); +} - hmi->backend_framebuffer = rt_malloc(hmi->info.smem_len); +static rt_ubase_t hmi_make_color(struct hmi_info *hmi, + rt_uint8_t red, rt_uint8_t green, rt_uint8_t blue, + rt_uint8_t alpha) +{ + rt_ubase_t red_mask = hmi_color_mask(hmi->var.red.length); + rt_ubase_t green_mask = hmi_color_mask(hmi->var.green.length); + rt_ubase_t blue_mask = hmi_color_mask(hmi->var.blue.length); + rt_ubase_t alpha_mask = hmi_color_mask(hmi->var.transp.length); + + return (hmi_color_component(red, red_mask) << hmi->var.red.offset) | + (hmi_color_component(green, green_mask) << hmi->var.green.offset) | + (hmi_color_component(blue, blue_mask) << hmi->var.blue.offset) | + (hmi_color_component(alpha, alpha_mask) << hmi->var.transp.offset); +} - 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); +static void hmi_swap_lines(struct hmi_info *hmi) +{ + rt_ubase_t color = hmi->line[0]; + + hmi->line[0] = hmi->line[1]; + hmi->line[1] = color; +} + +static void hmi_signal(struct hmi_info *hmi) +{ + rt_bool_t wakeup = !hmi->dirty; - if (var.transp.length) + hmi->dirty = RT_TRUE; + rt_hw_wmb(); + + if (wakeup) { - alpha_off = var.transp.offset; - alpha_mask = RT_GENMASK(var.transp.length - 1, 0); + rt_sem_release(&hmi->wakeup); } - else +} + +static rt_bool_t hmi_input_supported(struct rt_device *dev, + rt_bool_t *multitouch) +{ + const char *name; + rt_bool_t mt; + struct rt_input_device *idev; + + if (!dev) + { + return RT_FALSE; + } + + name = rt_dm_dev_get_name(dev); + + if (!name || rt_strncmp(name, "input", sizeof("input") - 1)) + { + return RT_FALSE; + } + + idev = to_input_device(dev); + + if (!rt_bitmap_test_bit(idev->cap, EV_ABS)) + { + return RT_FALSE; + } + + mt = rt_bitmap_test_bit(idev->abs_map, ABS_MT_SLOT) && + rt_bitmap_test_bit(idev->abs_map, ABS_MT_TRACKING_ID) && + rt_bitmap_test_bit(idev->abs_map, ABS_MT_POSITION_X) && + rt_bitmap_test_bit(idev->abs_map, ABS_MT_POSITION_Y); + + if (!mt && !(rt_bitmap_test_bit(idev->abs_map, ABS_X) && + rt_bitmap_test_bit(idev->abs_map, ABS_Y))) + { + return RT_FALSE; + } + + if (multitouch) { - alpha_off = 0; - alpha_mask = 0; + *multitouch = mt; } - if ((cursor = rt_malloc(CURSOR_WIDTH * CURSOR_HEIGHT * hmi->bytes_per_pixel))) + return RT_TRUE; +} + +static struct rt_device *hmi_find_graphic(const char *name) +{ + char device_name[16]; + struct rt_device *dev; + int i; + + if (name && rt_strcmp(name, "auto")) { - 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); + return rt_device_find(name); + } + + for (i = 0; i < HMI_MAX_GRAPHIC_DEVICES; ++i) + { + rt_snprintf(device_name, sizeof(device_name), "fb%d", i); - for (int y = 0; y < CURSOR_HEIGHT; ++y) + if ((dev = rt_device_find(device_name))) { - for (int x = 0; x < CURSOR_WIDTH; ++x) - { - rt_memcpy(stream, &color, hmi->bytes_per_pixel); - stream += hmi->bytes_per_pixel; - } + return dev; } + } + + return RT_NULL; +} + +static struct rt_device *hmi_find_input(const char *name, + rt_bool_t *multitouch) +{ + char device_name[16]; + struct rt_device *dev; + int i; - rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_TYPE, cursor); - rt_free(cursor); + if (name && rt_strcmp(name, "auto")) + { + dev = rt_device_find(name); + + return hmi_input_supported(dev, multitouch) ? dev : RT_NULL; } - none_alpha = alpha_mask << alpha_off; + for (i = 0; i < HMI_MAX_INPUT_DEVICES; ++i) + { + rt_snprintf(device_name, sizeof(device_name), "input%d", i); + dev = rt_device_find(device_name); - hmi->line[0] = ~0UL; - hmi->line[1] = none_alpha; + if (hmi_input_supported(dev, multitouch)) + { + return dev; + } + } + + return RT_NULL; +} + +static rt_uint32_t hmi_scale_axis(struct rt_input_device *idev, + rt_uint16_t axis, rt_int32_t value, rt_uint32_t size) +{ + rt_int32_t min, max; + struct rt_input_absinfo *absinfo; + + if (!idev->absinfo || axis >= ABS_CNT || size <= 1) + { + return 0; + } + + absinfo = &idev->absinfo[axis]; + min = absinfo->minimum; + max = absinfo->maximum; + + if (max <= min) + { + return 0; + } - 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; + value = rt_clamp(value, min, max); - hmi->event = RT_FALSE; + return (rt_uint64_t)(value - min) * (size - 1) / (max - min); } -static void hmi_graphic_notify(rt_device_t dev) +static void hmi_select_mt_position(struct hmi_info *hmi) { - struct hmi_info *hmi = (void *)dev; + int slot; - hmi->event = RT_TRUE; + for (slot = 0; slot < HMI_MAX_MT_SLOTS; ++slot) + { + if (hmi->mt_active & RT_BIT(slot)) + { + hmi->dx = hmi->mt_x[slot]; + hmi->dy = hmi->mt_y[slot]; + break; + } + } } static rt_bool_t hmi_input_callback(struct rt_input_handler *handler, struct rt_input_event *ev) { + rt_bool_t down; + rt_ubase_t slot_mask; struct hmi_info *hmi = handler->priv; if (ev->type == EV_ABS) { - if (ev->code == 0) + if (hmi->multitouch && ev->code == ABS_MT_SLOT) + { + hmi->mt_slot = ev->value >= 0 && + ev->value < HMI_MAX_MT_SLOTS ? ev->value : -1; + } + else if (hmi->multitouch && ev->code == ABS_MT_TRACKING_ID && + hmi->mt_slot >= 0) { - hmi->dx = (ev->value * hmi->info.width) / - (handler->idev->absinfo->maximum - handler->idev->absinfo->minimum); + slot_mask = RT_BIT(hmi->mt_slot); + + if (ev->value < 0) + { + hmi->mt_active &= ~slot_mask; + } + else + { + hmi->mt_active |= slot_mask; + } } - else if (ev->code == 1) + else if (ev->code == ABS_X) { - hmi->dy = (ev->value * hmi->info.height) / - (handler->idev->absinfo->maximum - handler->idev->absinfo->minimum); + hmi->dx = hmi_scale_axis(handler->idev, ABS_X, + ev->value, hmi->info.width); + } + else if (ev->code == ABS_Y) + { + hmi->dy = hmi_scale_axis(handler->idev, ABS_Y, + ev->value, hmi->info.height); + } + else if (hmi->multitouch && hmi->mt_slot >= 0 && + ev->code == ABS_MT_POSITION_X) + { + hmi->mt_x[hmi->mt_slot] = hmi_scale_axis(handler->idev, + ABS_MT_POSITION_X, ev->value, hmi->info.width); + } + else if (hmi->multitouch && hmi->mt_slot >= 0 && + ev->code == ABS_MT_POSITION_Y) + { + hmi->mt_y[hmi->mt_slot] = hmi_scale_axis(handler->idev, + ABS_MT_POSITION_Y, ev->value, hmi->info.height); } } - else if (ev->type == EV_KEY) + else if (ev->type == EV_KEY && !hmi->multitouch && + (ev->code == BTN_LEFT || ev->code == BTN_TOUCH)) { - if (ev->code == BTN_LEFT) + if (hmi->keydown && ev->value == 0) { - 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_swap_lines(hmi); + } + + hmi->keydown = ev->value != 0; + } + else if (ev->type == EV_SYN && ev->code == SYN_REPORT) + { + if (hmi->multitouch) + { + down = hmi->mt_active != 0; - hmi->keydown = RT_FALSE; - hmi->vsync = RT_FALSE; + if (hmi->keydown && !down) + { + hmi_swap_lines(hmi); } - else + + hmi->keydown = down; + + if (down) { - hmi->keydown = RT_TRUE; + hmi_select_mt_position(hmi); } } + + hmi_signal(hmi); + } + + return RT_FALSE; +} + +static void hmi_graphic_notify(rt_device_t dev) +{ + struct hmi_info *hmi = (void *)dev; + + hmi->reconfigure = RT_TRUE; + hmi_signal(hmi); +} + +static void hmi_write_pixel(rt_uint8_t *pixel, rt_ubase_t color, + rt_uint32_t bytes_per_pixel) +{ + rt_memcpy(pixel, &color, bytes_per_pixel); +} + +static void hmi_fill_span(rt_uint8_t *pixel, rt_uint32_t width, + rt_ubase_t color, rt_uint32_t bytes_per_pixel) +{ + rt_uint32_t x; + + switch (bytes_per_pixel) + { + case 1: + rt_memset(pixel, (rt_uint8_t)color, width); + break; + + case 2: + for (x = 0; x < width; ++x) + { + ((rt_uint16_t *)pixel)[x] = (rt_uint16_t)color; + } + break; + + case 4: + for (x = 0; x < width; ++x) + { + ((rt_uint32_t *)pixel)[x] = (rt_uint32_t)color; + } + break; + + default: + for (x = 0; x < width; ++x) + { + hmi_write_pixel(pixel, color, bytes_per_pixel); + pixel += bytes_per_pixel; + } + break; } - else if (ev->type == EV_SYN) +} + +static void hmi_fill_rect(struct hmi_info *hmi, rt_uint32_t x, + rt_uint32_t y, rt_uint32_t width, rt_uint32_t height, + rt_ubase_t color) +{ + rt_uint8_t *row; + rt_uint32_t line; + + if (!width || !height) { - hmi->vsync = RT_FALSE; + return; } - return RT_TRUE; + row = (rt_uint8_t *)hmi->framebuffer + + (rt_size_t)y * hmi->info.pitch + + (rt_size_t)x * hmi->bytes_per_pixel; + + for (line = 0; line < height; ++line) + { + hmi_fill_span(row, width, color, hmi->bytes_per_pixel); + row += hmi->info.pitch; + } } -static void hmi_loop(void *param) +static void hmi_draw_lines(struct hmi_info *hmi, + rt_uint32_t x, rt_uint32_t y) { - struct hmi_info *hmi = param; - struct rt_device_rect_info rect; - struct rt_device_graphic_ops *gops; + rt_uint8_t *pixel; + rt_uint32_t line; - /* 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); + pixel = (rt_uint8_t *)hmi->framebuffer + + (rt_size_t)y * hmi->info.pitch; + hmi_fill_span(pixel, hmi->info.width, hmi->line[0], + hmi->bytes_per_pixel); - /* 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); + pixel = (rt_uint8_t *)hmi->framebuffer + + (rt_size_t)x * hmi->bytes_per_pixel; - hmi->backend_framebuffer = RT_NULL; - hmi_reset(hmi); + for (line = 0; line < hmi->info.height; ++line) + { + hmi_write_pixel(pixel, hmi->line[1], hmi->bytes_per_pixel); + pixel += hmi->info.pitch; + } +} + +static void hmi_setup_cursor(struct hmi_info *hmi) +{ + rt_ubase_t color; + rt_uint8_t *cursor; + rt_uint8_t *pixel; + rt_size_t cursor_len; + rt_uint32_t count; + + cursor_len = HMI_CURSOR_WIDTH * HMI_CURSOR_HEIGHT * + hmi->bytes_per_pixel; + cursor = rt_malloc(cursor_len); + + if (!cursor) + { + return; + } + + color = hmi_make_color(hmi, 0x82, 0x50, 0xdf, 0xcc); + pixel = cursor; - hmi->dx = hmi->info.width >> 1; - hmi->dy = hmi->info.height >> 1; + for (count = 0; count < HMI_CURSOR_WIDTH * HMI_CURSOR_HEIGHT; ++count) + { + hmi_write_pixel(pixel, color, hmi->bytes_per_pixel); + pixel += hmi->bytes_per_pixel; + } - rect.x = 0; - rect.y = 0; + rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_TYPE, cursor); + rt_free(cursor); +} - gops = rt_graphix_ops(hmi->gdev); - rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_POWERON, RT_NULL); +static rt_err_t hmi_configure(struct hmi_info *hmi) +{ + rt_err_t err; + rt_uint32_t slot; + rt_size_t frame_len, page_count, front_page; + struct fb_fix_screeninfo fix; + void *framebuffer; - while (hmi_working) + if ((err = rt_device_control(hmi->gdev, + RTGRAPHIC_CTRL_GET_INFO, &hmi->info))) { - rt_ubase_t pos; + return err; + } - /* Wait graphic change */ - if (hmi->event) + if ((err = rt_device_control(hmi->gdev, + FBIOGET_VSCREENINFO, &hmi->var))) + { + return err; + } + + if (!hmi->info.framebuffer || !hmi->info.width || !hmi->info.height || + !hmi->info.pitch || !hmi->info.bits_per_pixel || + hmi->info.bits_per_pixel % 8) + { + return -RT_EINVAL; + } + + hmi->bytes_per_pixel = hmi->info.bits_per_pixel / 8; + + if (!hmi->bytes_per_pixel || + hmi->bytes_per_pixel > sizeof(rt_ubase_t) || + hmi->info.width > hmi->info.pitch / hmi->bytes_per_pixel || + !hmi_color_field_valid(&hmi->var.red, + hmi->info.bits_per_pixel) || + !hmi_color_field_valid(&hmi->var.green, + hmi->info.bits_per_pixel) || + !hmi_color_field_valid(&hmi->var.blue, + hmi->info.bits_per_pixel) || + !hmi_color_field_valid(&hmi->var.transp, + hmi->info.bits_per_pixel) || + hmi->info.pitch > (rt_size_t)-1 / hmi->info.height) + { + return -RT_EINVAL; + } + + frame_len = (rt_size_t)hmi->info.pitch * hmi->info.height; + page_count = hmi->info.smem_len / frame_len; + + if (!page_count) + { + return -RT_EINVAL; + } + + if (page_count > 1) + { + rt_memset(&fix, 0, sizeof(fix)); + + if (rt_device_control(hmi->gdev, FBIOGET_FSCREENINFO, &fix) || + !fix.ypanstep) { - hmi_reset(hmi); + page_count = 1; } + } - hmi->x = hmi->dx; - hmi->y = hmi->dy; + framebuffer = rt_malloc(frame_len); - rect.width = hmi->info.width; - rect.height = hmi->info.height; - pos = RTGRAPHIC_PIXEL_POSITION(hmi->x, hmi->y); + if (!framebuffer) + { + return -RT_ENOMEM; + } - 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; + front_page = hmi->var.yoffset / hmi->info.height; - switch (i) - { - case 0: - x1 = 0; - y1 = 0; - x2 = hmi->x; - y2 = hmi->y; - break; + if (front_page >= page_count) + { + front_page = 0; + } - case 1: - x1 = hmi->x; - y1 = 0; - x2 = hmi->info.width; - y2 = hmi->y; - break; + if (page_count > 1) + { + hmi->var.pixclock = 0; + hmi->var.activate = FB_ACTIVATE_NOW; - case 2: - x1 = 0; - y1 = hmi->y; - x2 = hmi->x; - y2 = hmi->info.height; - break; + if ((err = rt_device_control(hmi->gdev, + FBIOPUT_VSCREENINFO, &hmi->var))) + { + rt_free(framebuffer); + return err; + } + } - case 3: - x1 = hmi->x; - y1 = hmi->y; - x2 = hmi->info.width; - y2 = hmi->info.height; - break; - } + if (hmi->framebuffer) + { + rt_free(hmi->framebuffer); + } - fb += x1 * hmi->bytes_per_pixel + y1 * hmi->info.pitch; + hmi->framebuffer = framebuffer; + hmi->frame_len = frame_len; + hmi->page_count = page_count; + hmi->front_page = front_page; - for (int y = y1; y < y2; ++y) - { - void *fb_entry = fb; + hmi->line[0] = hmi_make_color(hmi, 0xff, 0xff, 0xff, 0xff); + hmi->line[1] = hmi_make_color(hmi, 0x00, 0x00, 0x00, 0xff); + hmi->colors[0] = hmi_make_color(hmi, 0xff, 0x4b, 0x00, 0xff); + hmi->colors[1] = hmi_make_color(hmi, 0x7f, 0xdb, 0x3b, 0xff); + hmi->colors[2] = hmi_make_color(hmi, 0x00, 0xa4, 0xef, 0xff); + hmi->colors[3] = hmi_make_color(hmi, 0xff, 0xb8, 0x1c, 0xff); + + if (hmi->dx >= hmi->info.width || hmi->dy >= hmi->info.height) + { + hmi->dx = hmi->info.width / 2; + hmi->dy = hmi->info.height / 2; + } - for (int x = x1; x < x2; ++x) - { - rt_memcpy(fb, &hmi->colors[i], hmi->bytes_per_pixel); - fb += hmi->bytes_per_pixel; - } + for (slot = 0; slot < HMI_MAX_MT_SLOTS; ++slot) + { + if (hmi->mt_x[slot] >= hmi->info.width) + { + hmi->mt_x[slot] = hmi->dx; + } - fb = fb_entry + hmi->info.pitch; - } + if (hmi->mt_y[slot] >= hmi->info.height) + { + hmi->mt_y[slot] = hmi->dy; } + } + + rt_memset(hmi->framebuffer, 0, hmi->frame_len); + hmi_setup_cursor(hmi); - if (hmi->backend_framebuffer) + return RT_EOK; +} + +static rt_err_t hmi_present(struct hmi_info *hmi) +{ + rt_err_t err; + rt_ubase_t cursor_position; + rt_uint32_t x, y; + struct rt_device_rect_info rect; + struct fb_var_screeninfo var; + + x = rt_min(hmi->dx, (rt_uint32_t)hmi->info.width - 1); + y = rt_min(hmi->dy, (rt_uint32_t)hmi->info.height - 1); + + hmi_fill_rect(hmi, 0, 0, x, y, hmi->colors[0]); + hmi_fill_rect(hmi, x, 0, hmi->info.width - x, y, hmi->colors[1]); + hmi_fill_rect(hmi, 0, y, x, hmi->info.height - y, hmi->colors[2]); + hmi_fill_rect(hmi, x, y, hmi->info.width - x, + hmi->info.height - y, hmi->colors[3]); + hmi_draw_lines(hmi, x, y); + + if (hmi->page_count > 1) + { + rt_size_t back_page = (hmi->front_page + 1) % hmi->page_count; + rt_uint8_t *framebuffer = hmi->info.framebuffer; + + rt_memcpy(framebuffer + back_page * hmi->frame_len, + hmi->framebuffer, hmi->frame_len); + + var = hmi->var; + var.xoffset = 0; + var.yoffset = back_page * hmi->info.height; + var.activate = FB_ACTIVATE_VBL; + err = rt_device_control(hmi->gdev, FBIOPAN_DISPLAY, &var); + + if (err) { - rt_memcpy(hmi->info.framebuffer, hmi->backend_framebuffer, hmi->info.smem_len); + return err; } - gops->draw_hline((void *)&hmi->line[0], 0, rect.width, hmi->y); - gops->draw_vline((void *)&hmi->line[1], hmi->x, 0, rect.height); + hmi->var = var; + hmi->front_page = back_page; + } + else + { + rt_memcpy(hmi->info.framebuffer, hmi->framebuffer, hmi->frame_len); - rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_RECT_UPDATE, &rect); - rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_POSITION, (void *)pos); + rect.x = 0; + rect.y = 0; + rect.width = hmi->info.width; + rect.height = hmi->info.height; - /* Next position */ - hmi->vsync = RT_TRUE; - rt_hw_wmb(); + if ((err = rt_device_control(hmi->gdev, + RTGRAPHIC_CTRL_RECT_UPDATE, &rect))) + { + return err; + } - while (hmi_working && hmi->vsync) + if ((err = rt_device_control(hmi->gdev, + RTGRAPHIC_CTRL_WAIT_VSYNC, RT_NULL))) { - rt_thread_mdelay(1); + return err; } } - rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_POWEROFF, RT_NULL); + cursor_position = RTGRAPHIC_PIXEL_POSITION(x, y); + rt_device_control(hmi->gdev, RT_DEVICE_CTRL_CURSOR_SET_POSITION, + (void *)cursor_position); - rt_memset(&hmi->event_notify, 0, sizeof(hmi->event_notify)); - rt_device_control(hmi->gdev, RT_DEVICE_CTRL_NOTIFY_SET, &hmi->event_notify); + return RT_EOK; +} - rt_input_del_handler(&hmi->handler); +static void hmi_cleanup(struct hmi_info *hmi) +{ + struct rt_device_notify notify = { 0 }; + + if (hmi->handler_installed) + { + rt_input_del_handler(&hmi->handler); + } + + if (hmi->notify_installed) + { + rt_device_control(hmi->gdev, RT_DEVICE_CTRL_NOTIFY_SET, ¬ify); + } + + if (hmi->powered) + { + rt_device_control(hmi->gdev, RTGRAPHIC_CTRL_POWEROFF, RT_NULL); + } - rt_device_close(hmi->gdev); rt_device_close(hmi->idev); + rt_device_close(hmi->gdev); - if (hmi->backend_framebuffer) + if (hmi->framebuffer) { - rt_free(hmi->backend_framebuffer); + rt_free(hmi->framebuffer); } + + rt_sem_detach(&hmi->wakeup); + hmi_active = RT_NULL; rt_free(hmi); +} + +static void hmi_loop(void *param) +{ + rt_err_t err = RT_EOK; + rt_uint32_t slot; + struct hmi_info *hmi = param; + + hmi->event_notify.notify = hmi_graphic_notify; + hmi->event_notify.dev = (void *)hmi; + + if ((err = rt_device_control(hmi->gdev, + RT_DEVICE_CTRL_NOTIFY_SET, &hmi->event_notify))) + { + goto _exit; + } + hmi->notify_installed = RT_TRUE; + + if ((err = rt_device_control(hmi->gdev, + RTGRAPHIC_CTRL_POWERON, RT_NULL))) + { + goto _exit; + } + hmi->powered = RT_TRUE; + + if ((err = hmi_configure(hmi))) + { + goto _exit; + } + hmi->dx = hmi->info.width / 2; + hmi->dy = hmi->info.height / 2; + + for (slot = 0; slot < HMI_MAX_MT_SLOTS; ++slot) + { + hmi->mt_x[slot] = hmi->dx; + hmi->mt_y[slot] = hmi->dy; + } + + hmi->handler.idev = to_input_device(hmi->idev); + hmi->handler.callback = hmi_input_callback; + hmi->handler.priv = hmi; + + if ((err = rt_input_add_handler(&hmi->handler))) + { + goto _exit; + } + hmi->handler_installed = RT_TRUE; + + while (hmi->running) + { + if (hmi->reconfigure) + { + hmi->reconfigure = RT_FALSE; + rt_hw_wmb(); + + if ((err = hmi_configure(hmi))) + { + break; + } + + hmi->dirty = RT_TRUE; + } + + if (hmi->dirty) + { + hmi->dirty = RT_FALSE; + rt_hw_wmb(); + + if ((err = hmi_present(hmi))) + { + break; + } + } + + if (hmi->running) + { + rt_sem_take(&hmi->wakeup, RT_WAITING_FOREVER); + } + } + +_exit: + if (err) + { + rt_kprintf("HMI stopped: %s\n", rt_strerror(err)); + } + + hmi_cleanup(hmi); rt_thread_delete(rt_thread_self()); } -rt_err_t hmi_start(const char *gdev, const char *idev) +rt_err_t hmi_start(const char *gdev_name, const char *idev_name) { rt_err_t err; struct hmi_info *hmi; struct rt_thread *loop; - if (hmi_working) + if (hmi_active) { rt_kprintf("HMI is running\n"); return -RT_EBUSY; } - hmi = rt_malloc(sizeof(*hmi)); + hmi = rt_calloc(1, sizeof(*hmi)); if (!hmi) { return -RT_ENOMEM; } - hmi->gdev = rt_device_find(gdev); - hmi->idev = rt_device_find(idev); + hmi->gdev = hmi_find_graphic(gdev_name); + hmi->idev = hmi_find_input(idev_name, &hmi->multitouch); if (!hmi->gdev || !hmi->idev) { + rt_kprintf("No compatible graphic/input device found\n"); rt_free(hmi); - return -RT_EINVAL; + return -RT_ENOENT; } - if (!rt_bitmap_test_bit(to_input_device(hmi->idev)->cap, EV_ABS)) + hmi->mt_slot = -1; + hmi->running = RT_TRUE; + hmi->dirty = RT_TRUE; + + if ((err = rt_sem_init(&hmi->wakeup, "hmi", 0, RT_IPC_FLAG_FIFO))) { - rt_kprintf("%s is not a ABS input\n", idev); rt_free(hmi); - return -RT_EINVAL; + return err; } if ((err = rt_device_open(hmi->gdev, 0))) { + rt_sem_detach(&hmi->wakeup); rt_free(hmi); return err; } @@ -368,54 +854,91 @@ rt_err_t hmi_start(const char *gdev, const char *idev) if ((err = rt_device_open(hmi->idev, 0))) { rt_device_close(hmi->gdev); + rt_sem_detach(&hmi->wakeup); 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)); + DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 10); if (!loop) { - rt_device_close(hmi->gdev); rt_device_close(hmi->idev); + rt_device_close(hmi->gdev); + rt_sem_detach(&hmi->wakeup); rt_free(hmi); return -RT_ENOMEM; } - hmi_working = RT_TRUE; - rt_thread_startup(loop); + hmi_active = hmi; + + if ((err = rt_thread_startup(loop))) + { + hmi_active = RT_NULL; + rt_thread_delete(loop); + rt_device_close(hmi->idev); + rt_device_close(hmi->gdev); + rt_sem_detach(&hmi->wakeup); + rt_free(hmi); + return err; + } + + rt_kprintf("HMI: %s + %s (%s input)\n", + rt_dm_dev_get_name(hmi->gdev), + rt_dm_dev_get_name(hmi->idev), + hmi->multitouch ? "multitouch" : "pointer"); return RT_EOK; } rt_err_t hmi_stop(void) { - hmi_working = RT_FALSE; + struct hmi_info *hmi = hmi_active; + + if (!hmi) + { + return -RT_ENOSYS; + } + + hmi->running = RT_FALSE; + rt_hw_wmb(); + rt_sem_release(&hmi->wakeup); + return RT_EOK; } #ifdef RT_USING_FINSH -static int _hmi_start(int argc, char**argv) +static int _hmi_start(int argc, char **argv) { - const char *gdev = "fb0", *idev = "input0"; + const char *gdev = RT_NULL; + const char *idev = RT_NULL; + + if (argc > 3) + { + rt_kprintf("Usage: hmi_start [fbN|auto] [inputN|auto]\n"); + return -RT_EINVAL; + } - if (argc == 3) + if (argc > 1) { gdev = argv[1]; + } + + if (argc > 2) + { idev = argv[2]; } return (int)hmi_start(gdev, idev); } -MSH_CMD_EXPORT_ALIAS(_hmi_start, hmi_start, e.g: hmi_start("fb0", "input0")); +MSH_CMD_EXPORT_ALIAS(_hmi_start, hmi_start, + auto select devices e.g. hmi_start or hmi_start fb0 input1); static int _hmi_stop(void) { return (int)hmi_stop(); } -MSH_CMD_EXPORT_ALIAS(_hmi_stop, hmi_stop, e.g: hmi_exit()); +MSH_CMD_EXPORT_ALIAS(_hmi_stop, hmi_stop, stop HMI test); #endif /* RT_USING_FINSH */ #endif /* RT_USING_GRAPHIC && RT_USING_INPUT */