From e36a857b65dd7d01fac44c551029ee722cf36c9e Mon Sep 17 00:00:00 2001 From: Ondrej Jirman Date: Mon, 9 Jul 2018 08:04:51 +0200 Subject: [PATCH] cmd: tmenu: Add tmenu command for touch menu user interface Shows a menu of items that can be controlled using touch panel. This is useful for touch based devices like tablets to select alternative boot options (multi-boot). Signed-off-by: Ondrej Jirman --- boot/bootmeth_extlinux.c | 2 +- boot/pxe_utils.c | 5 + cmd/Kconfig | 8 + cmd/Makefile | 1 + cmd/tmenu.c | 759 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 cmd/tmenu.c diff --git a/boot/bootmeth_extlinux.c b/boot/bootmeth_extlinux.c index ae0ad1d53e3..a31b37dcbf2 100644 --- a/boot/bootmeth_extlinux.c +++ b/boot/bootmeth_extlinux.c @@ -35,7 +35,7 @@ static int extlinux_get_state_desc(struct udevice *dev, char *buf, int maxsize) return 0; } -static int extlinux_getfile(struct pxe_context *ctx, const char *file_path, +int extlinux_getfile(struct pxe_context *ctx, const char *file_path, char *file_addr, ulong *sizep) { struct extlinux_info *info = ctx->userdata; diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c index 5c1c962ff4c..8d4c9835fd7 100644 --- a/boot/pxe_utils.c +++ b/boot/pxe_utils.c @@ -783,6 +783,11 @@ cleanup: return 1; } +int pxe_label_boot(struct pxe_context *ctx, struct pxe_label *label) +{ + return label_boot(ctx, label); +} + /** enum token_type - Tokens for the pxe file parser */ enum token_type { T_EOL, diff --git a/cmd/Kconfig b/cmd/Kconfig index a033c7666a1..47d366df25c 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2301,6 +2301,14 @@ config CMD_TIMER help Access the system timer. +config CMD_TMENU + bool "tmenu - Display boot menu and respond to touch" + depends on DM_TOUCHPANEL && VIDEO + help + Shows a menu of items that can be controlled using touch panel. + This is useful for touch based devices like tablets to select + alternative boot options (multi-boot). + config CMD_SOUND bool "sound" depends on SOUND diff --git a/cmd/Makefile b/cmd/Makefile index ebfb23fc21c..a3926e86a23 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -177,6 +177,7 @@ obj-$(CONFIG_CMD_TERMINAL) += terminal.o obj-$(CONFIG_CMD_TIME) += time.o obj-$(CONFIG_CMD_TIMER) += timer.o obj-$(CONFIG_CMD_TOUCH) += touch.o +obj-$(CONFIG_CMD_TMENU) += tmenu.o obj-$(CONFIG_CMD_TRACE) += trace.o obj-$(CONFIG_HUSH_PARSER) += test.o obj-$(CONFIG_CMD_TPM) += tpm-common.o diff --git a/cmd/tmenu.c b/cmd/tmenu.c new file mode 100644 index 00000000000..c246d2d2f00 --- /dev/null +++ b/cmd/tmenu.c @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018-2024 Ondrej Jirman + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VIDEO_FONT_HEIGHT 16 +#define VIDEO_FONT_WIDTH 8 +#define video_fontdata video_fontdata_8x16 + +// first some generic drawing primitives + +struct painter { + u8* fb; + u8* fb_end; + u8* cur; + u32 line_length; + u32 bpp; + u32 rows; + u32 cols; +}; + +static void painter_set_xy(struct painter* p, uint x, uint y) +{ + p->cur = p->fb + min(y, p->rows - 1) * p->line_length + min(x, p->cols - 1) * p->bpp; +} + +static void painter_move_dxy(struct painter* p, int dx, int dy) +{ + p->cur += dy * p->line_length + dx * p->bpp; + + if (p->cur >= p->fb_end) + p->cur = p->fb_end - 1; + + if (p->cur < p->fb) + p->cur = p->fb; +} + +static void painter_rect_fill(struct painter* p, uint w, uint h, u32 color) +{ + int x, y; + u32* cur; + + for (y = 0; y < h; y++) { + cur = (u32*)(p->cur + p->line_length * y); + + for (x = 0; x < w; x++) + *(cur++) = color; + } +} + +__maybe_unused +static void painter_line_h(struct painter* p, int dx, u32 color) +{ + if (dx < 0) { + painter_move_dxy(p, 0, dx); + painter_rect_fill(p, 1, -dx, color); + } else { + painter_rect_fill(p, 1, dx, color); + painter_move_dxy(p, 0, dx); + } +} + +__maybe_unused +static void painter_line_v(struct painter* p, int dy, u32 color) +{ + if (dy < 0) { + painter_move_dxy(p, 0, dy); + painter_rect_fill(p, 1, -dy, color); + } else { + painter_rect_fill(p, 1, dy, color); + painter_move_dxy(p, 0, dy); + } +} + +static void painter_bigchar(struct painter* p, char ch, u32 color) +{ + int i, row; + void *line = p->cur; + + for (row = 0; row < VIDEO_FONT_HEIGHT * 2; row++) { + uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row / 2]; + uint32_t *dst = line; + + for (i = 0; i < VIDEO_FONT_WIDTH; i++) { + if (bits & 0x80) { + *dst = color; + *(dst+1) = color; + } + + bits <<= 1; + dst+=2; + } + + line += p->line_length; + } + + painter_move_dxy(p, VIDEO_FONT_WIDTH * 2, 0); +} + +__maybe_unused +static void painter_char(struct painter* p, char ch, u32 color) +{ + int i, row; + void *line = p->cur; + + for (row = 0; row < VIDEO_FONT_HEIGHT; row++) { + uchar bits = video_fontdata[ch * VIDEO_FONT_HEIGHT + row]; + uint32_t *dst = line; + + for (i = 0; i < VIDEO_FONT_WIDTH; i++) { + if (bits & 0x80) + *dst = color; + + bits <<= 1; + dst++; + } + + line += p->line_length; + } + + painter_move_dxy(p, VIDEO_FONT_WIDTH, 0); +} + +// menu command + +struct ui_item { + int x, y, w, h; + char text[40]; + uint32_t fill; + uint32_t text_color; + int id; +}; + +static void ui_draw(struct ui_item *items, int n_items, struct painter *p) +{ + for (int idx = 0; idx < n_items; idx++) { + struct ui_item* i = items + idx; + + painter_set_xy(p, i->x, i->y); + painter_rect_fill(p, i->w, i->h, i->fill); + + size_t max_chars = i->w / (VIDEO_FONT_WIDTH * 2) - 1; + + max_chars = min(max_chars, strlen(i->text)); + + int text_w = i->w - max_chars * VIDEO_FONT_WIDTH * 2; + int text_h = i->h - VIDEO_FONT_HEIGHT * 2; + + painter_set_xy(p, + i->x + text_w / 2, + i->y + text_h / 2); + + for (int j = 0; j < max_chars; j++) + painter_bigchar(p, i->text[j], i->text_color); + } +} + +static struct ui_item* ui_hit_find(struct ui_item* items, int n_items, int x, int y) +{ + for (int idx = 0; idx < n_items; idx++) { + struct ui_item* i = items + idx; + + if (x >= i->x && x <= i->x + i->w && y >= i->y && y <= i->y + i->h) + return i; + } + + return NULL; +} + +static int handle_tmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[], int no_touch) +{ + struct udevice *vdev, *tdev; + struct video_priv *vpriv; + struct touchpanel_touch touches[10]; + int ret; + + if (argc < 2) + return CMD_RET_USAGE; + + // set some params: (parse from argv in the future) + char* const* items = argv + 1; + int n_items = argc - 1; + + ret = uclass_first_device_err(UCLASS_VIDEO, &vdev); + if (ret) + return CMD_RET_FAILURE; + + if (!no_touch) { + ret = uclass_first_device_err(UCLASS_TOUCHPANEL, &tdev); + if (ret) + return CMD_RET_FAILURE; + } + + vpriv = dev_get_uclass_priv(vdev); + + if (vpriv->bpix != VIDEO_BPP32) { + printf("tmenu requires 32BPP video device\n"); + return CMD_RET_FAILURE; + } + + struct painter p = { + .fb = vpriv->fb, + .fb_end = vpriv->fb + vpriv->fb_size, + .cur = vpriv->fb, + .line_length = vpriv->line_length, + .bpp = VNBYTES(vpriv->bpix), + .cols = vpriv->xsize, + .rows = vpriv->ysize, + }; + + // prepare ui_items and lay them out + struct ui_item ui_items[n_items]; + int border = 40, max_total_h = min(800, vpriv->ysize - 2 * border); + int gap = 10; + + int cols = 1; + while ((max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / DIV_ROUND_UP(n_items, cols) < 100) + cols++; + int rows = DIV_ROUND_UP(n_items, cols); + + int item_h = (max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / rows; + int item_w = (vpriv->xsize - 2 * border - (cols - 1) * gap) / cols; + + int top = vpriv->ysize - border - rows * (item_h + gap) - gap; + + for (int idx = 0; idx < n_items; idx++) { + struct ui_item *i = ui_items + idx; + + int col = idx % cols; + int row = idx / cols; + + i->x = border + col * (gap + item_w); + i->y = top + row * (gap + item_h); + i->w = item_w; + i->h = item_h; + i->fill = 0xff755f10; + i->text_color = 0xffffffff; + i->id = idx; + + snprintf(i->text, sizeof i->text, "%s", items[idx]); + + //printf("ui_item[%d] x=%d y=%d w=%d h=%d text=%s\n", idx, i->x, i->y, i->w, i->h, i->text); + } + + int selected = -1; + int redraw = 1; + + if (!no_touch) { + ret = touchpanel_start(tdev); + if (ret < 0) { + printf("Failed to start %s, err=%d\n", tdev->name, ret); + return CMD_RET_FAILURE; + } + } + +next: + while (1) { + if (redraw) { + ui_draw(ui_items, n_items, &p); + video_sync(vdev, true); + redraw = 0; + } + + if (no_touch) + return CMD_RET_SUCCESS; + + // don't be too busy reading i2c + udelay(50 * 1000); + + // handle input + ret = touchpanel_get_touches(tdev, touches, ARRAY_SIZE(touches)); + if (ret < 0) { + printf("Failed to get touches from %s, err=%d\n", tdev->name, ret); + return CMD_RET_FAILURE; + } + + /* find first matching tap down */ + for (int idx = 0; idx < ret; idx++) { + int tx = touches[idx].x; + int ty = touches[idx].y; + + struct ui_item* hit = ui_hit_find(ui_items, n_items, tx, ty); + if (hit) { + selected = hit->id; + hit->fill = 0xffb19019; + redraw = 1; + goto next; + } + } + + if (selected != -1) { + // we are done + char buf[16]; + snprintf(buf, sizeof buf, "ret=%d", selected); + set_local_var(buf, 1); + selected = -1; + redraw = 1; + break; + } + } + + ret = touchpanel_stop(tdev); + if (ret < 0) { + printf("Failed to stop %s, err=%d\n", tdev->name, ret); + return CMD_RET_FAILURE; + } + + return CMD_RET_SUCCESS; +} + +static int do_tmenu_render(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return handle_tmenu(cmdtp, flag, argc, argv, 1); +} + +static int do_tmenu_input(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return handle_tmenu(cmdtp, flag, argc, argv, 0); +} + +static int do_tmenu(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + return handle_tmenu(cmdtp, flag, argc, argv, 0); +} + +U_BOOT_CMD(tmenu, 30, 1, do_tmenu, "tmenu", "tmenu item1 [item2...] - show touch menu and wait for input"); +U_BOOT_CMD(tmenu_render, 30, 1, do_tmenu_render, "tmenu_render", "tmenu_render item1 [item2...] - show touch menu"); +U_BOOT_CMD(tmenu_input, 30, 1, do_tmenu_input, "tmenu_input", "tmenu_input item1 [item2...] - wait for touch menu input"); + +#include +#include +#include +#include +#include +#include +#include +#include + +int pxe_label_boot(struct pxe_context *ctx, struct pxe_label *label); + +int extlinux_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep); + +enum { + ACTION_BOOT = 1, + ACTION_POWEROFF, + ACTION_CONSOLE, + ACTION_USB_STORAGE_EMMC, + ACTION_USB_STORAGE_SD, +}; + +struct tmenu_boot_item { + int id; + char* label; + struct bootflow *bflow; + + struct pxe_context *pxe_ctx; + struct pxe_label *pxe_label; + int action; +}; + +#define MENU_COLOR_CHOSEN 0xff15801Fu +#define MENU_COLOR_HIGHLIGHT 0xffb19019u +#define MENU_COLOR_INACTIVE 0xff755f10u +#define MENU_COLOR_TEXT 0xffffffffu + +static int do_tmenu_bootflow(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct udevice *vdev, *tdev, *cdev; + struct video_priv *vpriv; + struct touchpanel_touch touches[10]; + int cmd_ret; + int ret; + +start_again: + cmd_ret = CMD_RET_FAILURE; + + struct bootstd_priv *std; + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + // how many items to reserve + int extra_items = 4; + + struct tmenu_boot_item items[64] = {}; + int n_items = 0; + int bmp_loaded = 0; + int timeout = 0; // 0 = no timeout, >0 number of 0.1s to wait + int autoselect = -1; // which item is autoselected by extlinux.conf (for timeout) + + struct bootflow *bflow; + for (ret = bootflow_first_glob(&bflow); !ret; ret = bootflow_next_glob(&bflow)) { + if (bflow->state == BOOTFLOWST_READY) { + //printf("flow: (%s) %s - %s\n", dev_get_parent(bflow->dev)->name, bflow->os_name ? bflow->os_name : bflow->name, bflow->method->name); + + if (!strcmp(bflow->method->name, "extlinux")) { + struct cmd_tbl *cmdtp = calloc(1, sizeof(*cmdtp)); + struct extlinux_info *pxe_info = calloc(1, sizeof(*pxe_info)); + struct pxe_context *ctx = calloc(1, sizeof(*ctx)); + + if (!ctx || !cmdtp ||!pxe_info) + continue; + + ulong addr = map_to_sysmem(bflow->buf); + pxe_info->dev = bflow->method; + pxe_info->bflow = bflow; + int ret = pxe_setup_ctx(ctx, cmdtp, extlinux_getfile, pxe_info, true, bflow->fname, false); + if (ret) + continue; + + struct pxe_menu *cfg = parse_pxefile(ctx, addr); + if (!cfg) { + printf("Error parsing config file\n"); + continue; + } + + if (cfg->bmp && !bmp_loaded && get_pxe_file(ctx, cfg->bmp, image_load_addr) == 1) + bmp_loaded = 1; + + if (timeout < cfg->timeout) + timeout = cfg->timeout; + + struct list_head *pos; + list_for_each(pos, &cfg->labels) { + struct pxe_label *label = list_entry(pos, struct pxe_label, list); + struct tmenu_boot_item *it = &items[n_items++]; + + // select default option for boot + if (autoselect < 0 && label->name && cfg->default_label && !strcmp(label->name, cfg->default_label)) + autoselect = n_items - 1; + + //printf("label %s - %s %s\n", label->num, label->name, label->menu); + + it->id = n_items; + it->label = label->menu ? label->menu : label->name; + it->bflow = bflow; + it->pxe_ctx = ctx; + it->pxe_label = label; + it->action = ACTION_BOOT; + + if (n_items == (ARRAY_SIZE(items) - extra_items)) + break; + } + } else if (!strcmp(bflow->method->name, "efi_mgr")) { + // skip efi_mgr + } else { + struct tmenu_boot_item *it = &items[n_items++]; + char name[64]; + + snprintf(name, sizeof name, "%s", bflow->os_name ? bflow->os_name : bflow->name); + + if (!strcmp(bflow->method->name, "efi")) { + // get better name for EFI bootflows + if (strstr(bflow->name, "mmc@fe320000")) { + snprintf(name, sizeof name, "EFI OS on SD/%d", bflow->part); + } else if (strstr(bflow->name, "mmc@fe330000")) { + snprintf(name, sizeof name, "EFI OS on eMMC/%d", bflow->part); + } + } + + it->id = n_items; + it->label = strdup(name); + it->bflow = bflow; + it->action = ACTION_BOOT; + } + + if (n_items == (ARRAY_SIZE(items) - extra_items)) + break; + } + } + + struct tmenu_boot_item *it = &items[n_items++]; + + it->id = n_items; + it->label = "U-Boot Console"; + it->action = ACTION_CONSOLE; + + it = &items[n_items++]; + + it->id = n_items; + it->label = "eMMC over USB"; + it->action = ACTION_USB_STORAGE_EMMC; + + it = &items[n_items++]; + + //XXX: only if SD card is detected + it->id = n_items; + it->label = "SD over USB"; + it->action = ACTION_USB_STORAGE_SD; + + it = &items[n_items++]; + + it->id = n_items; + it->label = "Power off"; + it->action = ACTION_POWEROFF; + + ret = uclass_first_device_err(UCLASS_VIDEO, &vdev); + if (ret) + return CMD_RET_FAILURE; + + ret = uclass_first_device_err(UCLASS_TOUCHPANEL, &tdev); + if (ret) + return CMD_RET_FAILURE; + + vpriv = dev_get_uclass_priv(vdev); + + if (vpriv->bpix != VIDEO_BPP32) { + printf("tmenu requires 32BPP video device\n"); + return CMD_RET_FAILURE; + } + + /* prep done, start doing the UI work */ + + env_set("stdout", "serial"); + env_set("stderr", "serial"); + + if (bmp_loaded) { + video_clear(vdev); + bmp_display(image_load_addr, BMP_ALIGN_CENTER, BMP_ALIGN_CENTER); + } + + struct painter p = { + .fb = vpriv->fb, + .fb_end = vpriv->fb + vpriv->fb_size, + .cur = vpriv->fb, + .line_length = vpriv->line_length, + .bpp = VNBYTES(vpriv->bpix), + .cols = vpriv->xsize, + .rows = vpriv->ysize, + }; + + // prepare ui_items and lay them out + struct ui_item ui_items[n_items]; + int border = 40, max_total_h = min(700, vpriv->ysize - 2 * border); + int gap = 10; + + int cols = 1; + while ((max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / DIV_ROUND_UP(n_items, cols) < 100) + cols++; + int rows = DIV_ROUND_UP(n_items, cols); + + int item_h = (max_total_h - gap * (DIV_ROUND_UP(n_items, cols) - 1)) / rows; + int item_w = (vpriv->xsize - 2 * border - (cols - 1) * gap) / cols; + + int top = vpriv->ysize - border - rows * (item_h + gap) - gap; + + for (int idx = 0; idx < n_items; idx++) { + struct ui_item *i = ui_items + idx; + + int col = idx % cols; + int row = idx / cols; + + i->x = border + col * (gap + item_w); + i->y = top + row * (gap + item_h); + i->w = item_w; + i->h = item_h; + i->fill = MENU_COLOR_INACTIVE; + i->text_color = MENU_COLOR_TEXT; + i->id = idx; + + snprintf(i->text, sizeof i->text, "%s", items[idx].label); + + //printf("ui_item[%d] x=%d y=%d w=%d h=%d text=%s\n", idx, i->x, i->y, i->w, i->h, i->text); + } + + int selected = -1; + int redraw = 1; + int highlighted = autoselect >= 0 ? autoselect : 0; + + ret = touchpanel_start(tdev); + if (ret < 0) { + printf("Failed to start %s, err=%d\n", tdev->name, ret); + goto out_restore_console; + } + + struct udevice *led_g = NULL, *led_r = NULL, *led_b = NULL; + uclass_get_device_by_name(UCLASS_LED, "led-red", &led_r); + uclass_get_device_by_name(UCLASS_LED, "led-green", &led_g); + uclass_get_device_by_name(UCLASS_LED, "led-blue", &led_b); + + enum { VOL_UP, VOL_DOWN, POWER }; + struct btn { struct udevice *dev; int prev; int cur; int press; int release; } btns[3] = {}; + button_get_by_label("Volume Up", &btns[VOL_UP].dev); + button_get_by_label("Volume Down", &btns[VOL_DOWN].dev); + button_get_by_label("Power", &btns[POWER].dev); + + int cycles = 0; +next: + while (1) { + for (int i = 0; i < ARRAY_SIZE(btns); i++) { + struct btn* b = &btns[i]; + b->prev = b->cur; + b->cur = b->dev && button_get_state(b->dev) == BUTTON_ON; + b->press = b->cur && !b->prev; + b->release = !b->cur && b->prev; + + // any button press cancels the timeout + if (b->cur) + timeout = 0; + } + + // menu up/down navigation feedback + if (btns[VOL_UP].press) { + highlighted--; + if (highlighted < 0) + highlighted = n_items - 1; + redraw = true; + } else if (btns[VOL_DOWN].press) { + highlighted = (highlighted + 1) % n_items; + redraw = true; + } + + if (redraw) + for (int i = 0; i < n_items; i++) + ui_items[i].fill = i == highlighted ? MENU_COLOR_HIGHLIGHT : MENU_COLOR_INACTIVE; + + // power button press + if (btns[POWER].press) { + ui_items[highlighted].fill = MENU_COLOR_CHOSEN; + redraw = true; + if (led_g) + led_set_state(led_g, LEDST_ON); + } else if (btns[POWER].release) { + selected = highlighted; + redraw = true; + } + + // handle autoselect timeout + if (autoselect >= 0 && timeout > 0 && timeout == cycles / 2) { + selected = autoselect; + ui_items[selected].fill = MENU_COLOR_CHOSEN; + redraw = true; + } + + cycles++; + + // UI drawing + if (redraw) { + ui_draw(ui_items, n_items, &p); + video_sync(vdev, true); + redraw = 0; + } + + // don't be too busy reading i2c + udelay(50 * 1000); + + // handle input + ret = touchpanel_get_touches(tdev, touches, ARRAY_SIZE(touches)); + if (ret < 0) { + printf("Failed to get touches from %s, err=%d\n", tdev->name, ret); + break; + } + + // cancel timeout on touch + if (ret > 0) + timeout = 0; + + // find first matching tap down + for (int idx = 0; idx < ret; idx++) { + int tx = touches[idx].x; + int ty = touches[idx].y; + + struct ui_item* hit = ui_hit_find(ui_items, n_items, tx, ty); + if (hit) { + selected = hit->id; + hit->fill = MENU_COLOR_CHOSEN; + redraw = 1; + goto next; + } + } + + // final selection processing + if (selected != -1) { + struct tmenu_boot_item *it = &items[selected]; + + if (it->action == ACTION_CONSOLE) { + cmd_ret = CMD_RET_SUCCESS; + break; + } + + else if (it->action == ACTION_USB_STORAGE_EMMC || it->action == ACTION_USB_STORAGE_SD) { + cmd_ret = CMD_RET_SUCCESS; + break; + } + + else if (it->action == ACTION_POWEROFF) { + ret = sysreset_walk(SYSRESET_POWER_OFF); + if (ret == -EINPROGRESS) + mdelay(1000); + break; + } + + if (bmp_loaded) { + video_clear(vdev); + bmp_display(image_load_addr, BMP_ALIGN_CENTER, BMP_ALIGN_CENTER); + } + + std->cur_bootflow = it->bflow; + + if (it->pxe_label) { + pxe_label_boot(it->pxe_ctx, it->pxe_label); + } else { + bootflow_boot(it->bflow); + } + + selected = -1; + redraw = 1; + break; + } + } + +//out_stop_touch: + ret = touchpanel_stop(tdev); + if (ret < 0) + printf("Failed to stop %s, err=%d\n", tdev->name, ret); + +out_restore_console: + //video_clear(vdev); + if (!uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &cdev)) + vidconsole_clear_and_reset(cdev); + + env_set("stdout", "serial,vidconsole"); + env_set("stderr", "serial,vidconsole"); + + if (selected >= 0) { + struct tmenu_boot_item *it = &items[selected]; + + if (it->action == ACTION_USB_STORAGE_EMMC) { + cli_simple_run_command("ums 0 mmc 0", 0); + cli_simple_run_command("bootflow scan", 0); + goto start_again; + } else if (it->action == ACTION_USB_STORAGE_SD) { + cli_simple_run_command("ums 0 mmc 1", 0); + cli_simple_run_command("bootflow scan", 0); + goto start_again; + } + } + + return cmd_ret; +} + +U_BOOT_CMD(tmenu_bootflow, 4, 1, do_tmenu_bootflow, "tmenu_bootflow", "tmenu_bootflow - show bootflow menu");