expo: cedit: Support reading settings from CMOS RAM

Add a command to read edit settings from CMOS RAM, using the cedit
definition to indicate which registers and bits are used.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/boot/cedit.c b/boot/cedit.c
index 725745a..73645f7 100644
--- a/boot/cedit.c
+++ b/boot/cedit.c
@@ -39,6 +39,7 @@
  * @mask: Mask bits for the CMOS RAM. If a bit is set the byte containing it
  * will be written
  * @value: Value bits for CMOS RAM. This is the actual value written
+ * @dev: RTC device to write to
  */
 struct cedit_iter_priv {
 	struct abuf *buf;
@@ -46,6 +47,7 @@
 	bool verbose;
 	u8 *mask;
 	u8 *value;
+	struct udevice *dev;
 };
 
 int cedit_arange(struct expo *exp, struct video_priv *vpriv, uint scene_id)
@@ -619,3 +621,100 @@
 	free(priv.value);
 	return ret;
 }
+
+static int h_read_settings_cmos(struct scene_obj *obj, void *vpriv)
+{
+	struct cedit_iter_priv *priv = vpriv;
+	const struct scene_menitem *mi;
+	struct scene_obj_menu *menu;
+	int val, ret;
+	uint i;
+
+	if (obj->type != SCENEOBJT_MENU)
+		return 0;
+
+	menu = (struct scene_obj_menu *)obj;
+
+	/* figure out where to place this item */
+	if (!obj->bit_length)
+		return log_msg_ret("len", -EINVAL);
+	if (obj->start_bit + obj->bit_length > CMOS_MAX_BITS)
+		return log_msg_ret("bit", -E2BIG);
+
+	val = 0;
+	for (i = 0; i < obj->bit_length; i++) {
+		uint bitnum = obj->start_bit + i;
+		uint offset = CMOS_BYTE(bitnum);
+
+		/* read the byte if not already read */
+		if (!priv->mask[offset]) {
+			ret = rtc_read8(priv->dev, offset);
+			if (ret < 0)
+				return  log_msg_ret("rea", ret);
+			priv->value[offset] = ret;
+
+			/* mark it as read */
+			priv->mask[offset] = 0xff;
+		}
+
+		if (priv->value[offset] & BIT(CMOS_BIT(bitnum)))
+			val |= BIT(i);
+		log_debug("bit %x %x\n", bitnum, val);
+	}
+
+	/* update the current item */
+	mi = scene_menuitem_find_seq(menu, val);
+	if (!mi)
+		return log_msg_ret("seq", -ENOENT);
+
+	menu->cur_item_id = mi->id;
+	log_debug("Update menu %d cur_item_id %d\n", menu->obj.id, mi->id);
+
+	return 0;
+}
+
+int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
+			     bool verbose)
+{
+	struct cedit_iter_priv priv;
+	int ret, i, count, first, last;
+
+	/* read in the items */
+	priv.mask = calloc(1, CMOS_MAX_BYTES);
+	if (!priv.mask)
+		return log_msg_ret("mas", -ENOMEM);
+	priv.value = calloc(1, CMOS_MAX_BYTES);
+	if (!priv.value) {
+		free(priv.mask);
+		return log_msg_ret("val", -ENOMEM);
+	}
+	priv.dev = dev;
+
+	ret = expo_iter_scene_objs(exp, h_read_settings_cmos, &priv);
+	if (ret) {
+		log_debug("Failed to read CMOS (err=%d)\n", ret);
+		ret = log_msg_ret("set", ret);
+		goto done;
+	}
+
+	/* read the data to the RTC */
+	first = CMOS_MAX_BYTES;
+	last = -1;
+	for (i = 0, count = 0; i < CMOS_MAX_BYTES; i++) {
+		if (priv.mask[i]) {
+			log_debug("Read byte %x: %x\n", i, priv.value[i]);
+			count++;
+			first = min(first, i);
+			last = max(last, i);
+		}
+	}
+	if (verbose) {
+		printf("Read %d bytes from offset %x to %x\n", count, first,
+		       last);
+	}
+
+done:
+	free(priv.mask);
+	free(priv.value);
+	return ret;
+}
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
index 23e29cb..695a907 100644
--- a/boot/scene_internal.h
+++ b/boot/scene_internal.h
@@ -234,4 +234,16 @@
 struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
 					  int id);
 
+/**
+ * scene_menuitem_find_seq() - Find the menu item at a sequential position
+ *
+ * This numbers the items from 0 and returns the seq'th one
+ *
+ * @menu: Menu to check
+ * @seq: Sequence number to look for
+ * Return: menu item if found, else NULL
+ */
+struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
+					      uint seq);
+
 #endif /* __SCENE_INTERNAL_H */
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
index 602fe24..e0dcd0a 100644
--- a/boot/scene_menu.c
+++ b/boot/scene_menu.c
@@ -46,6 +46,22 @@
 	return NULL;
 }
 
+struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
+					      uint seq)
+{
+	struct scene_menitem *item;
+	uint i;
+
+	i = 0;
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		if (i == seq)
+			return item;
+		i++;
+	}
+
+	return NULL;
+}
+
 /**
  * update_pointers() - Update the pointer object and handle highlights
  *
diff --git a/cmd/cedit.c b/cmd/cedit.c
index 95d5c22..2ff284f 100644
--- a/cmd/cedit.c
+++ b/cmd/cedit.c
@@ -210,6 +210,40 @@
 	return 0;
 }
 
+static int do_cedit_read_cmos(struct cmd_tbl *cmdtp, int flag, int argc,
+			      char *const argv[])
+{
+	struct udevice *dev;
+	bool verbose = false;
+	int ret;
+
+	if (check_cur_expo())
+		return CMD_RET_FAILURE;
+
+	if (argc > 1 && !strcmp(argv[1], "-v")) {
+		verbose = true;
+		argc--;
+		argv++;
+	}
+
+	if (argc > 1)
+		ret = uclass_get_device_by_name(UCLASS_RTC, argv[1], &dev);
+	else
+		ret = uclass_first_device_err(UCLASS_RTC, &dev);
+	if (ret) {
+		printf("Failed to get RTC device: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	ret = cedit_read_settings_cmos(cur_exp, dev, verbose);
+	if (ret) {
+		printf("Failed to read settings from CMOS: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	return 0;
+}
+
 static int do_cedit_run(struct cmd_tbl *cmdtp, int flag, int argc,
 			char *const argv[])
 {
@@ -243,6 +277,7 @@
 	"cedit write_fdt <i/f> <dev[:part]> <filename>    - write settings\n"
 	"cedit read_env [-v]                              - read settings from env vars\n"
 	"cedit write_env [-v]                             - write settings to env vars\n"
+	"cedit read_cmos [-v] [dev]                       - read settings from CMOS RAM\n"
 	"cedit write_cmos [-v] [dev]                      - write settings to CMOS RAM\n"
 	"cedit run                                        - run config editor";
 #endif /* CONFIG_SYS_LONGHELP */
@@ -253,6 +288,7 @@
 	U_BOOT_SUBCMD_MKENT(write_fdt, 5, 1, do_cedit_write_fdt),
 	U_BOOT_SUBCMD_MKENT(read_env, 2, 1, do_cedit_read_env),
 	U_BOOT_SUBCMD_MKENT(write_env, 2, 1, do_cedit_write_env),
+	U_BOOT_SUBCMD_MKENT(read_cmos, 2, 1, do_cedit_read_cmos),
 	U_BOOT_SUBCMD_MKENT(write_cmos, 2, 1, do_cedit_write_cmos),
 	U_BOOT_SUBCMD_MKENT(run, 1, 1, do_cedit_run),
 );
diff --git a/doc/usage/cmd/cedit.rst b/doc/usage/cmd/cedit.rst
index 3d6f26e..f415b48 100644
--- a/doc/usage/cmd/cedit.rst
+++ b/doc/usage/cmd/cedit.rst
@@ -135,7 +135,14 @@
 
     => rtc read 80 8
     00000080: 00 00 00 00 00 2f 2a 08                          ...../*.
-    =>  cedit write_cmos
+    =>  cedit write_cmos -v
     Write 2 bytes from offset 80 to 84
     => rtc read 80 8
     00000080: 01 00 00 00 08 2f 2a 08                          ...../*.
+    => cedit read_cmos -v
+    Read 2 bytes from offset 80 to 84
+
+Here is an example with the device specified::
+
+    => cedit write_cmos rtc@43
+    =>
diff --git a/include/cedit.h b/include/cedit.h
index 2970965..f43cafa 100644
--- a/include/cedit.h
+++ b/include/cedit.h
@@ -110,4 +110,16 @@
 int cedit_write_settings_cmos(struct expo *exp, struct udevice *dev,
 			      bool verbose);
 
+/**
+ * cedit_read_settings_cmos() - Read settings from CMOS RAM
+ *
+ * Read settings from the defined places in CMO RAM
+ *
+ * @exp: Expo to read settings into
+ * @dev: RTC device to read settings from
+ * @verbose: true to print a summary at the end
+ */
+int cedit_read_settings_cmos(struct expo *exp, struct udevice *dev,
+			     bool verbose);
+
 #endif /* __CEDIT_H */
diff --git a/test/boot/cedit.c b/test/boot/cedit.c
index 010aae6..ab2b8a1 100644
--- a/test/boot/cedit.c
+++ b/test/boot/cedit.c
@@ -182,6 +182,17 @@
 	ut_assert_nextlinen("Write 2 bytes from offset 80 to 84");
 	ut_assert_console_end();
 
+	/* reset the expo */
+	menu->cur_item_id = ID_CPU_SPEED_1;
+	menu2->cur_item_id = ID_AC_OFF;
+
+	ut_assertok(run_command("cedit read_cmos -v", 0));
+	ut_assert_nextlinen("Read 2 bytes from offset 80 to 84");
+	ut_assert_console_end();
+
+	ut_asserteq(ID_CPU_SPEED_2, menu->cur_item_id);
+	ut_asserteq(ID_AC_MEMORY, menu2->cur_item_id);
+
 	return 0;
 }
 BOOTSTD_TEST(cedit_cmos, 0);