From da3c11dcfc35c3fbe11ffecd224aa6377b66ea91 Mon Sep 17 00:00:00 2001
From: Rahix <rahix@rahix.de>
Date: Sun, 28 Jul 2019 01:50:19 +0200
Subject: [PATCH] feat(bootloader): Add a boot-splash

Signed-off-by: Rahix <rahix@rahix.de>
---
 bootloader/bootloader-display.c |  30 +++++++++++-
 bootloader/main.c               |  19 +++++---
 bootloader/meson.build          |  14 ++++++
 bootloader/splash-screen.png    | Bin 0 -> 6332 bytes
 tools/bootloader-image.py       |  83 ++++++++++++++++++++++++++++++++
 5 files changed, 139 insertions(+), 7 deletions(-)
 create mode 100644 bootloader/splash-screen.png
 create mode 100755 tools/bootloader-image.py

diff --git a/bootloader/bootloader-display.c b/bootloader/bootloader-display.c
index 2c1e34fd..1fe0cae0 100644
--- a/bootloader/bootloader-display.c
+++ b/bootloader/bootloader-display.c
@@ -1,14 +1,42 @@
 #include "bootloader.h"
+/* Autogenerated */
+#include "splash-screen.h"
 
 #include "GUI_Paint.h"
 #include "display.h"
 
+/*
+ * "Decompress" splash-screen image.  The algorithm works as follows:
+ *
+ * Each byte encodes up to 127 pixels in either white or black.  The most
+ * significant bit determines the color, the remaining 7 bits determine the
+ * amount.
+ */
+static void bootloader_display_splash(void)
+{
+	int idx = 0;
+
+	for (int i = 0; i < sizeof(splash); i++) {
+		uint16_t color = (splash[i] & 0x80) ? 0xffff : 0x0000;
+		uint8_t length = splash[i] & 0x7f;
+
+		for (int j = 0; j < length; j++) {
+			uint16_t x = idx % 160;
+			uint16_t y = idx / 160;
+			Paint_SetPixel(x, y, color);
+			idx++;
+		}
+	}
+
+	LCD_Update();
+}
+
 /*
  * Initialize the display.
  */
 void bootloader_display_init(void)
 {
-	;
+	bootloader_display_splash();
 }
 
 /*
diff --git a/bootloader/main.c b/bootloader/main.c
index 1a5f08e0..d3577542 100644
--- a/bootloader/main.c
+++ b/bootloader/main.c
@@ -205,10 +205,11 @@ int main(void)
 	 */
 	pmic_set_button_callback(pmic_button);
 
-	bootloader_display_header();
+	bootloader_display_init();
 
 	// If the button is pressed, we go into MSC mode.
 	if (PB_Get(3)) {
+		bootloader_display_header();
 		bootloader_display_line(2, "USB activated.", 0xffff);
 		bootloader_display_line(3, "Ready.", 0xffff);
 		run_usbmsc();
@@ -224,36 +225,42 @@ int main(void)
 		int res = check_integrity();
 		if (res == -ENOENT) {
 			printf("card10.bin not found!\n");
-			bootloader_display_line(
-				2, "card10.bin not found", 0xffff
-			);
 		} else if (res == -EINVAL) {
 			printf("card10.bin CRC is invalid!\n");
+			bootloader_display_header();
 			bootloader_display_line(
 				2, "Integrity check failed", 0xffff
 			);
+
+			bootloader_display_line(4, "Trying to boot", 0xffff);
 		} else if (res == 0) {
 			printf("Found valid application image\n");
 			if (is_update_needed()) {
 				printf("Trying to update firmware from external flash\n");
+				bootloader_display_header();
 				bootloader_display_line(
-					4, "Updating ...", 0xffff
+					3, "Updating ...", 0xffff
 				);
 				erase_partition();
 				flash_partition();
+				bootloader_display_line(
+					4, "Trying to boot", 0xffff
+				);
 			} else {
 				printf("No update needed\n");
 			}
 		}
 	} else {
+		bootloader_display_header();
 		bootloader_display_line(
 			2, "Failed to mount filesystem", 0xffff
 		);
 		printf("Failed to mount the external flash\n");
+
+		bootloader_display_line(4, "Trying to boot", 0xffff);
 	}
 
 	printf("Trying to boot\n");
-	bootloader_display_line(4, "Trying to boot", 0xffff);
 
 	boot((uintptr_t *)PARTITION_START);
 
diff --git a/bootloader/meson.build b/bootloader/meson.build
index 9a863cdf..23c38794 100644
--- a/bootloader/meson.build
+++ b/bootloader/meson.build
@@ -1,5 +1,18 @@
 name = 'bootloader'
 
+splash_screen = custom_target(
+  'splash-screen.h',
+  output: 'splash-screen.h',
+  input: 'splash-screen.png',
+  command: [
+    python3,
+    meson.current_source_dir() + '../tools/bootloader-image.py',
+    '-n', 'splash',
+    '@INPUT@',
+    '@OUTPUT@',
+  ],
+)
+
 executable(
   name + '.elf',
   'main.c',
@@ -7,6 +20,7 @@ executable(
   'bootloader-display.c',
   'bootloader-usb.c',
   'crc16-ccitt.c',
+  splash_screen,
   dependencies: [
     libcard10,
     max32665_startup_boot,
diff --git a/bootloader/splash-screen.png b/bootloader/splash-screen.png
new file mode 100644
index 0000000000000000000000000000000000000000..02a632049624f5b96e54ac2d4c7d408fd41ae796
GIT binary patch
literal 6332
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

literal 0
HcmV?d00001

diff --git a/tools/bootloader-image.py b/tools/bootloader-image.py
new file mode 100755
index 00000000..f2ce2bf8
--- /dev/null
+++ b/tools/bootloader-image.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+import argparse
+import os
+from PIL import Image
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(
+        description="""\
+\"Compress\" an image for the boot-splash.
+The boot-splash is a two-color image."""
+    )
+
+    parser.add_argument("image", help="Boot-splash image")
+    parser.add_argument("-n", "--name", help="Name of the data-block")
+    parser.add_argument("output", help="Output file name")
+
+    args = parser.parse_args()
+
+    im = Image.open(args.image)
+
+    assert im.size[0] == 160, "Image must be 160 pixels wide"
+    assert im.size[1] == 80, "Image must be 80 pixels high)"
+
+    if args.name is not None:
+        name = args.name
+    else:
+        name = os.path.splitext(os.path.basename(args.image))[0].replace("-", "_")
+
+    with open(args.output, "w") as f:
+        tmp = """\
+#include <stdint.h>
+
+/*
+ * This is the splash-screen image, compressed using a very primitive algorithm:
+ *
+ * Each byte encodes up to 127 pixels in either white or black.  The most
+ * significant bit determines the color, the remaining 7 bits determine the
+ * amount.
+ */
+const uint8_t {name}[] = {{
+"""
+        f.write(tmp.format(name=name))
+
+        total = 0
+        start = 0
+        previous_white = False
+        for i in range(160 * 80):
+            x = i % 160
+            y = i // 160
+            white = im.getpixel((x, y))[0] > 0
+
+            if white != previous_white or i - start == 127:
+                length = i - start
+                assert length < 128, "Internal error"
+                value = (length & 0x7F) | (0x80 if previous_white else 0x00)
+
+                tmp = """\
+    /* {length} pixels in {color} */
+    0x{value:x},
+"""
+                f.write(
+                    tmp.format(
+                        length=length,
+                        color="white" if previous_white else "black",
+                        value=value,
+                    )
+                )
+
+                previous_white = white
+                start = i
+                total += length
+
+        tmp = """\
+}};
+"""
+        f.write(tmp.format())
+
+        assert total < (160 * 80), "Internal error"
+
+
+if __name__ == "__main__":
+    main()
-- 
GitLab