From c66f93198492cfdcae2cb0c167ec0b15f322bbe8 Mon Sep 17 00:00:00 2001
From: q3k <q3k@q3k.org>
Date: Wed, 31 Jul 2019 20:59:43 +0000
Subject: [PATCH] feat(display): Add API call for direct framebuffer

---
 epicardium/api/genapi.py     | 48 ++++++++++++++----------------
 epicardium/epicardium.h      | 57 ++++++++++++++++++++++++++++++++++++
 epicardium/modules/display.c | 17 +++++++++--
 lib/gfx/LCD/LCD_Driver.c     |  5 ++++
 lib/gfx/LCD/LCD_Driver.h     |  1 +
 5 files changed, 99 insertions(+), 29 deletions(-)

diff --git a/epicardium/api/genapi.py b/epicardium/api/genapi.py
index 12f26b2d..a7698a79 100644
--- a/epicardium/api/genapi.py
+++ b/epicardium/api/genapi.py
@@ -15,13 +15,10 @@ MATCH_ISR_EXPANSION = re.compile(
     re.DOTALL | re.MULTILINE,
 )
 
-MATCH_DECLARATION = re.compile(
-    r"^(?P<typename>.*?)\s*\((?P<args>.*)\)$",
-    re.DOTALL,
-)
+MATCH_DECLARATION = re.compile(r"^(?P<typename>.*?)\s*\((?P<args>.*)\)$", re.DOTALL)
 
 MATCH_TYPENAME = re.compile(
-    r"^(?P<type>(?:const )?(?:struct |enum )?\w+[*\s]+)(?P<name>\w+)$",
+    r"^(?P<type>(?:const )?(?:struct |enum |union )?\w+[*\s]+)(?P<name>\w+)$"
 )
 
 
@@ -63,20 +60,24 @@ def parse_declarations(source):
             if arg is None:
                 bailout("Failed to parse argument '{}'", arg_str.strip())
 
-            args.append({
-                "type": arg.group("type").strip(),
-                "name": arg.group("name"),
-                "sizeof": "sizeof({})".format(arg.group("type").strip()),
-                "offset": sizeof(args),
-            })
-
-        declarations.append({
-            "id": id,
-            "return_type": typename.group("type").strip(),
-            "name": typename.group("name"),
-            "args": args,
-            "args_str": args_str,
-        })
+            args.append(
+                {
+                    "type": arg.group("type").strip(),
+                    "name": arg.group("name"),
+                    "sizeof": "sizeof({})".format(arg.group("type").strip()),
+                    "offset": sizeof(args),
+                }
+            )
+
+        declarations.append(
+            {
+                "id": id,
+                "return_type": typename.group("type").strip(),
+                "name": typename.group("name"),
+                "args": args,
+                "args_str": args_str,
+            }
+        )
 
     return declarations
 
@@ -88,10 +89,7 @@ def parse_interrupts(source):
         id = exp.group("id")
         isr = exp.group("isr")
 
-        interrupts.append({
-            "id": id,
-            "isr": isr,
-        })
+        interrupts.append({"id": id, "isr": isr})
 
     return interrupts
 
@@ -132,9 +130,7 @@ def main():
     declarations = parse_declarations(source)
     interrupts = parse_interrupts(source)
 
-    fmt_header = {
-        "header": os.path.basename(args.header)
-    }
+    fmt_header = {"header": os.path.basename(args.header)}
 
     # Generate Client {{{
     with open(args.client, "w") as f_client:
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index b90c04bb..b4b72dd5 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -48,6 +48,7 @@ typedef unsigned int size_t;
 #define API_DISP_RECT          0x16
 #define API_DISP_CIRC          0x17
 #define API_DISP_PIXEL         0x18
+#define API_DISP_FRAMEBUFFER   0x19
 
 #define API_FILE_OPEN          0x30
 #define API_FILE_CLOSE         0x31
@@ -249,6 +250,13 @@ API(API_VIBRA_VIBRATE, void epic_vibra_vibrate(int millis));
 /**
  * Display
  * =======
+ * The card10 has an LCD screen that can be accessed from user code.
+ *
+ * There are two main ways to access the display:
+ *  - immediate mode, where you ask epicardium to draw shapes and text for you
+ *  - framebuffer mode, where you provide epicardium with a memory range where
+ *    you already drew graphics whichever way you wanted, and epicardium will
+ *    copy them to the display.
  */
 
 /** Line-Style */
@@ -267,6 +275,40 @@ enum disp_fillstyle {
   FILLSTYLE_FILLED = 1
 };
 
+/** Width of display in pixels */
+#define DISP_WIDTH 160
+
+/** Height of display in pixels */
+#define DISP_HEIGHT 80
+
+/** Raw framebuffer */
+union disp_framebuffer {
+  /**
+   * The frambuffer stores pixels as RGB565, but byte swapped.
+   * That is, for every (x, y) coordinate, there are two uint8_ts
+   * storing 16 bits of pixel data.
+   *
+   * TODO(q3k): document (x, y) in relation to chirality
+   *
+   * **Example: fill framebuffer with red**:
+   *
+   * .. code-block:: cpp
+   *
+   * 	union disp_framebuffer fb;
+   * 	uint16_t red = 0b1111100000000000;
+   * 	for (int y = 0; y < DISP_HEIGHT; y++) {
+   * 		for (int x = 0; x < DISP_WIDTH; x++) {
+   * 			fb.fb[y][x][0] = red >> 8;
+   * 			fb.fb[y][x][1] = red & 0xFF;
+   * 		}
+   * 	}
+   * 	epic_disp_framebuffer(&fb);
+   *
+   */
+  uint8_t fb[DISP_HEIGHT][DISP_WIDTH][2];
+  uint8_t raw[DISP_HEIGHT*DISP_WIDTH*2];
+};
+
 /**
  * Locks the display.
  *
@@ -288,6 +330,9 @@ API(API_DISP_CLOSE, int epic_disp_close());
 /**
  * Causes the changes that have been written to the framebuffer
  * to be shown on the display
+ * :return: ``0`` on success or a negative value in case of an error:
+ *
+ *    - ``-EBUSY``: Display was already locked from another task.
  */
 API(API_DISP_UPDATE, int epic_disp_update());
 
@@ -412,6 +457,18 @@ API(API_DISP_CIRC,
 	    uint16_t pixelsize)
     );
 
+/**
+ * Immediately send the contents of a framebuffer to the display. This overrides
+ * anything drawn by immediate mode graphics and displayed using ``epic_disp_update``.
+ *
+ * :param fb: framebuffer to display
+ * :return: ``0`` on success or negative value in case of an error:
+ *
+ *    - ``-EBUSY``: Display was already locked from another task.
+ */
+API(API_DISP_FRAMEBUFFER, int epic_disp_framebuffer(union disp_framebuffer *fb));
+
+
 /**
  * Start continuous readout of the light sensor. Will read light level
  * at preconfigured interval and make it available via `epic_light_sensor_get()`.
diff --git a/epicardium/modules/display.c b/epicardium/modules/display.c
index 1786d805..1f50acae 100644
--- a/epicardium/modules/display.c
+++ b/epicardium/modules/display.c
@@ -119,10 +119,21 @@ int epic_disp_update()
 	int cl = check_lock();
 	if (cl < 0) {
 		return cl;
-	} else {
-		LCD_Update();
-		return 0;
 	}
+
+	LCD_Update();
+	return 0;
+}
+
+int epic_disp_framebuffer(union disp_framebuffer *fb)
+{
+	int cl = check_lock();
+	if (cl < 0) {
+		return cl;
+	}
+
+	LCD_Set(fb->raw, sizeof(fb->raw));
+	return 0;
 }
 
 int epic_disp_open()
diff --git a/lib/gfx/LCD/LCD_Driver.c b/lib/gfx/LCD/LCD_Driver.c
index 7cc1d15f..af5d50e3 100644
--- a/lib/gfx/LCD/LCD_Driver.c
+++ b/lib/gfx/LCD/LCD_Driver.c
@@ -310,6 +310,11 @@ void LCD_Set(uint8_t *data, int len)
 	lcd_write(data, len);
 }
 
+uint8_t *LCD_Framebuffer(void)
+{
+    return (uint8_t*)screen;
+}
+
 void LCD_Update(void)
 {
 	LCD_Set((uint8_t *)screen, sizeof(screen));
diff --git a/lib/gfx/LCD/LCD_Driver.h b/lib/gfx/LCD/LCD_Driver.h
index 54980b0c..597956b2 100644
--- a/lib/gfx/LCD/LCD_Driver.h
+++ b/lib/gfx/LCD/LCD_Driver.h
@@ -47,6 +47,7 @@ void LCD_Init(void);
 void LCD_SetBacklight(UWORD Value);
 void LCD_Clear(UWORD Color);
 void LCD_ClearWindow(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD UWORD);
+uint8_t *LCD_Framebuffer(void);
 
 void LCD_Set(uint8_t *data, int len);
 void LCD_Update(void);
-- 
GitLab