board/neo1973/bootmenu.c: simple configurable boot menu
board/neo1973/neo1973.c (neo1973_new_second): return 1 if a new second has
  started since the last call
board/neo1973/neo1973.c (neo1973_on_key_pressed): return 1 if the $POWER key is
  pressed
board/neo1973/neo1973.c (board_late_init): make use of neo1973_new_second and
  neo1973_on_key_pressed
board/neo1973/neo1973.h: added function prototypes
u-boot/board/neo1973/neo1973.c (board_late_init): enter the boot menu when 
  "AUX" was pressed at least half the time
u-boot/board/neo1973/neo1973.c (board_late_init): minor code cleanup
u-boot/common/console.c, include/console.h: added "console_poll_hook" to be
  called when waiting for console in put in "getc" and "tstc"
board/neo1973/neo1973.c (board_late_init): poll for the boot menu also on RAM
  boot, reset, or unknown cause
board/neo1973/neo1973.c (board_late_init): don't look for the power key if
  woken up by the charger
board/neo1973/neo1973.h, board/neo1973/neo1973.c, board/neo1973/bootmenu.c:
  renamed neo1973_911_key_pressed to neo1973_aux_key_pressed

- Werner Almesberger <werner@openmoko.org>

Index: u-boot/board/neo1973/common/bootmenu.c
===================================================================
--- /dev/null
+++ u-boot/board/neo1973/common/bootmenu.c
@@ -0,0 +1,120 @@
+/*
+ * bootmenu.c - Boot menu
+ *
+ * Copyright (C) 2006-2007 by OpenMoko, Inc.
+ * Written by Werner Almesberger <werner@openmoko.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <common.h>
+#include <environment.h>
+#include <bootmenu.h>
+#include <asm/atomic.h>
+
+#ifdef CONFIG_USBD_DFU
+#include "usbdcore.h"
+#include "usb_dfu.h"
+#endif
+
+#include "neo1973.h"
+
+
+#define DEBOUNCE_LOOPS		1000	/* wild guess */
+
+
+static int debounce(int (*fn)(void), int *last)
+{
+	int on, i;
+
+again:
+	on = fn();
+	if (on != *last)
+		for (i = DEBOUNCE_LOOPS; i; i--)
+			if (on != fn())
+				goto again;
+	*last = on;
+	return on;
+}
+
+
+static int aux_key(void *user)
+{
+	static int last_aux = -1;
+
+	return debounce(neo1973_aux_key_pressed, &last_aux);
+}
+
+
+static int on_key(void *user)
+{
+	static int last_on = -1;
+
+	return debounce(neo1973_on_key_pressed, &last_on);
+}
+
+
+static void factory_reset(void *user)
+{
+	default_env();
+	run_command("dynpart", 0);
+	run_command("bootd", 0);
+}
+
+
+static int seconds(void *user)
+{
+	return neo1973_new_second();
+}
+
+
+static int system_idle(void)
+{
+#ifdef  CONFIG_USBD_DFU
+	if (system_dfu_state)
+		return *system_dfu_state == DFU_STATE_appIDLE;
+#endif
+        return 1;
+}
+
+
+static void poweroff_if_idle(void *user)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+	if (system_idle())
+		neo1973_poweroff();
+	local_irq_restore(flags);
+}
+
+
+static struct bootmenu_setup bootmenu_setup = {
+	.next_key = aux_key,
+	.enter_key = on_key,
+	.seconds = seconds,
+	.idle_action = poweroff_if_idle,
+};
+
+
+void neo1973_bootmenu(void)
+{
+	bootmenu_add("Boot", NULL, "bootd");
+	bootmenu_init(&bootmenu_setup);
+	bootmenu_add("Factory reset", factory_reset, NULL);
+	bootmenu();
+}
Index: u-boot/board/neo1973/gta01/gta01.c
===================================================================
--- u-boot.orig/board/neo1973/gta01/gta01.c
+++ u-boot/board/neo1973/gta01/gta01.c
@@ -229,10 +229,15 @@ int board_late_init(void)
 	extern unsigned char booted_from_nand;
 	unsigned char tmp;
 	char buf[32];
+	int menu_vote = 0; /* <= 0: no, > 0: yes */
+	int seconds = 0;
 
 	/* Initialize the Power Management Unit with a safe register set */
 	pcf50606_init();
 
+	/* if there's no other reason, must be regular reset */
+	neo1973_wakeup_cause = NEO1973_WAKEUP_RESET;
+
 	if (!booted_from_nand)
 		goto woken_by_reset;
 
@@ -242,45 +247,41 @@ int board_late_init(void)
 	setenv("pcf50606_int1", buf);
 
 	if (tmp & PCF50606_INT1_ALARM) {
-		/* we've been woken up by RTC alarm or charger insert, boot */
+		/* we've been woken up by RTC alarm, boot */
 		neo1973_wakeup_cause = NEO1973_WAKEUP_ALARM;
 		goto continue_boot;
 	}
 	if (tmp & PCF50606_INT1_EXTONR) {
+		/* we've been woken up by charger insert */
 		neo1973_wakeup_cause = NEO1973_WAKEUP_CHARGER;
 	}
 
 	if (tmp & PCF50606_INT1_ONKEYF) {
-		int seconds = 0;
-		neo1973_wakeup_cause = NEO1973_WAKEUP_POWER_KEY;
 		/* we've been woken up by a falling edge of the onkey */
+		neo1973_wakeup_cause = NEO1973_WAKEUP_POWER_KEY;
+	}
 
-		/* we can't just setenv(bootdelay,-1) because that would
-		 * accidentially become permanent if the user does saveenv */
-		if (neo1973_911_key_pressed())
-			nobootdelay = 1;
-
-		while (1) {
-			u_int8_t int1, oocs;
-
-			oocs = pcf50606_reg_read(PCF50606_REG_OOCS);
-			if (oocs & PFC50606_OOCS_ONKEY)
-				break;
-
-			int1 = pcf50606_reg_read(PCF50606_REG_INT1);
-			if (int1 & PCF50606_INT1_SECOND)
-				seconds++;
-
-			if (seconds >= POWER_KEY_SECONDS)
-				goto continue_boot;
-		}
-		/* Power off if minimum number of seconds not reached */
-		neo1973_poweroff();
+	if (neo1973_wakeup_cause == NEO1973_WAKEUP_CHARGER) {
+		/* if we still think it was only a charger insert, boot */
+		goto continue_boot;
 	}
 
 woken_by_reset:
-	/* if there's no other reason, must be regular reset */
-	neo1973_wakeup_cause = NEO1973_WAKEUP_RESET;
+
+	while (neo1973_wakeup_cause == NEO1973_WAKEUP_RESET ||
+	    neo1973_on_key_pressed()) {
+		if (neo1973_aux_key_pressed())
+			menu_vote++;
+		else
+			menu_vote--;
+
+		if (neo1973_new_second())
+			seconds++;
+		if (seconds >= POWER_KEY_SECONDS)
+			goto continue_boot;
+	}
+	/* Power off if minimum number of seconds not reached */
+	neo1973_poweroff();
 
 continue_boot:
 	jbt6k74_init();
@@ -304,6 +305,11 @@ continue_boot:
 	}
 #endif
 
+	if (menu_vote > 0) {
+		neo1973_bootmenu();
+		nobootdelay = 1;
+	}
+
 	return 0;
 }
 
@@ -369,7 +375,17 @@ void neo1973_vibrator(int on)
 #endif
 }
 
-int neo1973_911_key_pressed(void)
+int neo1973_new_second(void)
+{
+	return pcf50606_reg_read(PCF50606_REG_INT1) & PCF50606_INT1_SECOND;
+}
+
+int neo1973_on_key_pressed(void)
+{
+	return !(pcf50606_reg_read(PCF50606_REG_OOCS) & PFC50606_OOCS_ONKEY);
+}
+
+int neo1973_aux_key_pressed(void)
 {
 	S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
 	if (gpio->GPFDAT & (1 << 6))
Index: u-boot/board/neo1973/gta01/Makefile
===================================================================
--- u-boot.orig/board/neo1973/gta01/Makefile
+++ u-boot/board/neo1973/gta01/Makefile
@@ -25,7 +25,7 @@ include $(TOPDIR)/config.mk
 
 LIB	= lib$(BOARD).a
 
-OBJS	:= gta01.o pcf50606.o ../common/cmd_neo1973.o ../common/jbt6k74.o ../common/udc.o
+OBJS	:= gta01.o pcf50606.o ../common/cmd_neo1973.o ../common/jbt6k74.o ../common/udc.o ../common/bootmenu.o
 SOBJS	:= ../common/lowlevel_init.o
 
 .PHONY:	all
Index: u-boot/board/neo1973/common/neo1973.h
===================================================================
--- u-boot.orig/board/neo1973/common/neo1973.h
+++ u-boot/board/neo1973/common/neo1973.h
@@ -29,4 +29,10 @@ int neo1973_911_key_pressed(void);
 const char *neo1973_get_charge_status(void);
 int neo1973_set_charge_mode(enum neo1973_charger_cmd cmd);
 
+int neo1973_new_second(void);
+int neo1973_on_key_pressed(void);
+int neo1973_aux_key_pressed(void);
+
+void neo1973_bootmenu(void);
+
 #endif
Index: u-boot/common/console.c
===================================================================
--- u-boot.orig/common/console.c
+++ u-boot/common/console.c
@@ -160,8 +160,12 @@ void fprintf (int file, const char *fmt,
 
 /** U-Boot INITIAL CONSOLE-COMPATIBLE FUNCTION *****************************/
 
+void (*console_poll_hook)(int activity);
+
 int getc (void)
 {
+	while (console_poll_hook && !tstc());
+
 	if (gd->flags & GD_FLG_DEVINIT) {
 		/* Get from the standard input */
 		return fgetc (stdin);
@@ -171,7 +175,7 @@ int getc (void)
 	return serial_getc ();
 }
 
-int tstc (void)
+static int do_tstc (void)
 {
 	if (gd->flags & GD_FLG_DEVINIT) {
 		/* Test the standard input */
@@ -182,6 +186,16 @@ int tstc (void)
 	return serial_tstc ();
 }
 
+int tstc (void)
+{
+	int ret;
+
+	ret = do_tstc();
+	if (console_poll_hook)
+		console_poll_hook(ret);
+	return ret;
+}
+
 void putc (const char c)
 {
 #ifdef CONFIG_SILENT_CONSOLE
Index: u-boot/include/console.h
===================================================================
--- u-boot.orig/include/console.h
+++ u-boot/include/console.h
@@ -33,6 +33,8 @@
 extern device_t	*stdio_devices[] ;
 extern char *stdio_names[MAX_FILES] ;
 
+extern void (*console_poll_hook)(int activity);
+
 int console_realloc(int top);
 
 #endif
Index: u-boot/common/Makefile
===================================================================
--- u-boot.orig/common/Makefile
+++ u-boot/common/Makefile
@@ -50,7 +50,8 @@ COBJS	= main.o ACEX1K.o altera.o bedbug.
 	  memsize.o miiphybb.o miiphyutil.o \
 	  s_record.o serial.o soft_i2c.o soft_spi.o spartan2.o spartan3.o \
 	  usb.o usb_kbd.o usb_storage.o \
-	  virtex2.o xilinx.o crc16.o xyzModem.o cmd_mac.o cmd_mfsl.o
+	  virtex2.o xilinx.o crc16.o xyzModem.o cmd_mac.o cmd_mfsl.o \
+	  bootmenu.o
 
 SRCS	:= $(AOBJS:.o=.S) $(COBJS:.o=.c)
 OBJS	:= $(addprefix $(obj),$(AOBJS) $(COBJS))
Index: u-boot/common/bootmenu.c
===================================================================
--- /dev/null
+++ u-boot/common/bootmenu.c
@@ -0,0 +1,311 @@
+/*
+ * bootmenu.c - Boot menu
+ *
+ * Copyright (C) 2006-2007 by OpenMoko, Inc.
+ * Written by Werner Almesberger <werner@openmoko.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#include <common.h>
+
+#ifdef CFG_BOOTMENU
+
+#include <malloc.h>
+#include <devices.h>
+#include <console.h>
+#include <bootmenu.h>
+
+
+extern const char version_string[];
+
+
+#define ANSI_CLEAR	"\e[2J"
+#define ANSI_REVERSE	"\e[7m"
+#define	ANSI_NORMAL	"\e[m"
+#define ANSI_GOTOYX	"\e[%d;%dH"
+
+/*
+ * MIN_BOOT_MENU_TIMEOUT ensures that users can't by accident set the timeout
+ * unusably short.
+ */
+#define MIN_BOOT_MENU_TIMEOUT	10	/* 10 seconds */
+#define BOOT_MENU_TIMEOUT	60	/* 60 seconds */
+#define AFTER_COMMAND_WAIT	3	/* wait (2,3] after running commands */
+#define	MAX_MENU_ITEMS		10	/* cut off after that many */
+
+#define	TOP_ROW		2
+#define	MENU_0_ROW	(TOP_ROW+5)
+
+
+struct option {
+	const char *label;
+	void (*fn)(void *user);	/* run_command if NULL */
+	void *user;
+};
+
+
+static const struct bootmenu_setup *setup;
+static struct option options[MAX_MENU_ITEMS];
+static int num_options = 0;
+static int max_width = 0;
+
+static device_t *bm_con;
+
+
+static void bm_printf(const char *fmt, ...)
+{
+	va_list args;
+	char printbuffer[CFG_PBSIZE];
+
+	va_start(args, fmt);
+	vsprintf(printbuffer, fmt, args);
+	va_end(args);
+
+	bm_con->puts(printbuffer);
+}
+
+
+static char *get_option(int n)
+{
+	char name[] = "menu_XX";
+
+	sprintf(name+5, "%d", n);
+	return getenv(name);
+}
+
+
+static void print_option(const struct option *option, int reverse)
+{
+	int n = option-options;
+
+	bm_printf(ANSI_GOTOYX, MENU_0_ROW+n, 1);
+	if (reverse)
+		bm_printf(ANSI_REVERSE);
+	bm_printf("  %-*s  ", max_width, option->label);
+	if (reverse)
+		bm_printf(ANSI_NORMAL);
+}
+
+
+static int get_var_positive_int(char *var, int default_value)
+{
+	const char *s;
+	char *end;
+	int n;
+
+	s = getenv(var);
+	if (!s)
+		return default_value;
+        n = simple_strtoul(s, &end, 0);
+	if (!*s || *end || n < 1)
+		return default_value;
+	return n;
+}
+
+
+static void show_bootmenu(void)
+{
+	const struct option *option;
+
+	bm_printf(ANSI_CLEAR ANSI_GOTOYX "%s", TOP_ROW, 1, version_string);
+	bm_printf(ANSI_GOTOYX "*** BOOT MENU ***", TOP_ROW+3, 1);
+	bm_printf(ANSI_GOTOYX, MENU_0_ROW, 1);
+
+	for (option = options; option != options+num_options; option++)
+		print_option(option, option == options);
+
+	bm_printf("\n\nPress [AUX] to select, [POWER] to execute.\n");
+}
+
+
+static void redirect_console(int grab)
+{
+	static device_t *orig_stdout, *orig_stderr;
+
+	if (grab) {
+		orig_stdout = stdio_devices[stdout];
+		orig_stderr = stdio_devices[stderr];
+		stdio_devices[stdout] = bm_con;
+		stdio_devices[stderr] = bm_con;
+	}
+	else {
+		/*
+		 * Make this conditional, because the command may also change
+		 * the console.
+		 */
+		if (stdio_devices[stdout] == bm_con)
+			stdio_devices[stdout] = orig_stdout;
+		if (stdio_devices[stderr] == bm_con)
+			stdio_devices[stderr] = orig_stderr;
+	}
+}
+
+
+static void do_option(const struct option *option)
+{
+	int seconds, aux;
+
+	bm_printf(ANSI_CLEAR ANSI_GOTOYX, 1, 1);
+	redirect_console(1);
+
+	if (option->fn)
+		option->fn(option->user);
+	else
+		run_command(option->user, 0);
+
+	redirect_console(0);
+	seconds = get_var_positive_int("after_command_wait",
+	    AFTER_COMMAND_WAIT);
+	if (seconds)
+		bm_printf("\nPress [AUX] to %s.",
+		    option ? "return to boot menu" : "power off");
+	aux = 1; /* require up-down transition */
+	while (seconds) {
+		int tmp;
+
+		tmp = setup->next_key(setup->user);
+		if (tmp && !aux)
+			break;
+		aux = tmp;
+		if (setup->seconds(setup->user))
+			seconds--;
+	}
+	if (!option)
+		setup->idle_action(setup->idle_action);
+	show_bootmenu();
+}
+
+
+static void bootmenu_hook(int activity)
+{
+	static int aux = 1, on = 1;
+	static const struct option *option = options;
+	static int seconds = 0;
+	int tmp;
+
+	if (activity)
+		seconds = 0;
+	tmp = setup->next_key(setup->user);
+	if (tmp && !aux) {
+		print_option(option, 0);
+		option++;
+		if (option == options+num_options)
+			option = options;
+		print_option(option, 1);
+		seconds = 0;
+	}
+	aux = tmp;
+	tmp = setup->enter_key(setup->user);
+	if (tmp && !on) {
+		do_option(option);
+		option = options;
+		seconds = 0;
+	}
+	on = tmp;
+	if (setup->seconds(setup->user)) {
+		int timeout;
+
+		timeout = get_var_positive_int("boot_menu_timeout",
+		    BOOT_MENU_TIMEOUT);
+		if (timeout < MIN_BOOT_MENU_TIMEOUT)
+			timeout = MIN_BOOT_MENU_TIMEOUT;
+		if (++seconds > timeout) {
+			setup->idle_action(setup->idle_action);
+			seconds = 0;
+		}
+	}
+}
+
+
+static device_t *find_console(const char *name)
+{
+	int i;
+
+	for (i = 1; i != ListNumItems(devlist); i++) {
+		device_t *dev = ListGetPtrToItem(devlist, i);
+
+		if (!strcmp(name, dev->name))
+			if (dev->flags & DEV_FLAGS_OUTPUT)
+				return dev;
+	}
+	return NULL;
+}
+
+
+void bootmenu_add(const char *label, void (*fn)(void *user), void *user)
+{
+	int len;
+
+	options[num_options].label = label;
+	options[num_options].fn = fn;
+	options[num_options].user = user;
+	num_options++;
+
+	len = strlen(label);
+	if (len > max_width)
+		max_width = len;
+}
+
+
+void bootmenu_init(struct bootmenu_setup *__setup)
+{
+	int n;
+
+	setup = __setup;
+	for (n = 1; n != MAX_MENU_ITEMS+1; n++) {
+		const char *spec, *colon;
+
+		spec = get_option(n);
+		if (!spec)
+			continue;
+		colon = strchr(spec, ':');
+		if (!colon)
+			bootmenu_add(spec, NULL, (char *) spec);
+		else {
+			char *label;
+			int len = colon-spec;
+
+			label = malloc(len+1);
+			if (!label)
+				return;
+			memcpy(label, spec, len);
+			label[len] = 0;
+			bootmenu_add(label, NULL, (char *) colon+1);
+		}
+	}
+}
+
+
+void bootmenu(void)
+{
+	bm_con = find_console("vga");
+	if (bm_con && bm_con->start && bm_con->start() < 0)
+		bm_con = NULL;
+	if (!bm_con)
+		bm_con = stdio_devices[stdout];
+	if (!bm_con)
+		return;
+#if 0
+	console_assign(stdout, "vga");
+	console_assign(stderr, "vga");
+#endif
+	show_bootmenu();
+	console_poll_hook = bootmenu_hook;
+}
+
+#endif /* CFG_BOOTMENU */
Index: u-boot/include/bootmenu.h
===================================================================
--- /dev/null
+++ u-boot/include/bootmenu.h
@@ -0,0 +1,71 @@
+/*
+ * bootmenu.h - Boot menu
+ *
+ * Copyright (C) 2006-2007 by OpenMoko, Inc.
+ * Written by Werner Almesberger <werner@openmoko.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef BOOTMENU_H
+#define BOOTMENU_H
+
+#define MIN_BOOT_MENU_TIMEOUT	10	/* 10 seconds */
+#define BOOT_MENU_TIMEOUT	60	/* 60 seconds */
+#define AFTER_COMMAND_WAIT	3	/* wait (2,3] after running commands */
+#define MAX_MENU_ITEMS		10	/* cut off after that many */
+
+
+struct bootmenu_setup {
+	/* non-zero while the "next" key is being pressed */
+	int (*next_key)(void *user);
+
+	/* non-zero while the "enter" key is being pressed */
+	int (*enter_key)(void *user);
+
+	/* return the number of seconds that have passed since the last call
+	   to "seconds". It's okay to limit the range to [0, 1]. */
+	int (*seconds)(void *user);
+
+	/* action to take if the boot menu times out */
+	void (*idle_action)(void *user);
+
+	/* user-specific data, passes "as is" to the functions above */
+	void *user;
+};
+
+
+/*
+ * Initialize the menu from the environment.
+ */
+
+void bootmenu_init(struct bootmenu_setup *setup);
+
+/*
+ * To add entries on top of the boot menu, call bootmenu_add before
+ * bootmenu_init. To add entries at the end, call it after bootmenu_init.
+ * If "fn" is NULL, the command specified in "user" is executed.
+ */
+
+void bootmenu_add(const char *label, void (*fn)(void *user), void *user);
+
+/*
+ * Run the boot menu.
+ */
+
+void bootmenu(void);
+
+#endif /* !BOOTMENU_H */
Index: u-boot/include/configs/neo1973_gta01.h
===================================================================
--- u-boot.orig/include/configs/neo1973_gta01.h
+++ u-boot/include/configs/neo1973_gta01.h
@@ -160,6 +160,8 @@
 /* valid baudrates */
 #define CFG_BAUDRATE_TABLE	{ 9600, 19200, 38400, 57600, 115200 }
 
+#define CFG_BOOTMENU
+
 /*-----------------------------------------------------------------------
  * Stack sizes
  *
