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