From 0f98ecec93b71f28da00b87338a3659eaa509be2 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Fri, 19 Jun 2026 11:24:47 +0200 Subject: [PATCH] eink full refresh patch --- zephcore/Kconfig | 2 +- zephcore/helpers/ui/display.c | 52 +++++++++++----- .../0009-display-ssd16xx-fill-ram-white.patch | 60 +++++++++++++++++++ 3 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 zephcore/patches/zephyr/0009-display-ssd16xx-fill-ram-white.patch diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 4e119c3..0de4eba 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -762,7 +762,7 @@ config ZEPHCORE_DISPLAY_INSET config ZEPHCORE_DISPLAY_EPD_FULL_REFRESH_INTERVAL int "E-paper full-refresh interval (partial refreshes)" - default 8 + default 16 depends on ZEPHCORE_UI_DISPLAY help On e-paper panels, partial refreshes never fully clear the previous diff --git a/zephcore/helpers/ui/display.c b/zephcore/helpers/ui/display.c index bf7da8c..0a1eba0 100644 --- a/zephcore/helpers/ui/display.c +++ b/zephcore/helpers/ui/display.c @@ -26,6 +26,19 @@ #include #include +/* The SSD16xx EPD driver exposes ssd16xx_clear_red_ram() to reset the + * partial-refresh old-frame reference after a periodic full refresh. It is + * only compiled when an SSD16xx-family panel is present in devicetree. */ +#define ZEPHCORE_DISPLAY_HAS_SSD16XX \ + (DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1608) || \ + DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1673) || \ + DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1675a) || \ + DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1680) || \ + DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1681)) +#if ZEPHCORE_DISPLAY_HAS_SSD16XX +#include +#endif + #include LOG_MODULE_REGISTER(zephcore_display, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL); @@ -522,27 +535,36 @@ void mc_display_finalize(void) if (is_epd && full_interval > 0 && ++epd_partial_count >= (uint32_t)full_interval) { /* Periodic full refresh to clear accumulated ghosting. - * blanking_on selects the FULL profile and loads RAM without - * updating the panel; cfb_framebuffer_finalize writes the - * current framebuffer into both RAM banks; blanking_off then - * triggers the full-refresh update with that content. */ + * + * Replicate the clean post-boot sequence: full-refresh the panel + * to WHITE, then redraw the current page as a partial. blanking_on + * selects the FULL profile; ssd16xx_fill_ram_white clears BOTH RAM + * banks to white; blanking_off runs the full-refresh waveform, + * leaving the panel (and both RAM banks) white. The trailing + * cfb_framebuffer_finalize then redraws the current frame — still + * held in the CFB buffer — as a partial against the all-white + * reference: every content pixel is actively driven and there is + * nothing stale to erase. + * + * Going through white is required. A partial after a full refresh + * of *content* leaves unchanged regions (e.g. the top bar) on the + * neutral waveform → they fade; and forcing the old-frame reference + * white while the panel still shows content fails to erase pixels + * that should clear → letters overlap. The brief white flash is + * the intended clean between frames. */ display_blanking_on(disp_dev); - cfb_framebuffer_finalize(disp_dev); +#if ZEPHCORE_DISPLAY_HAS_SSD16XX + ssd16xx_fill_ram_white(disp_dev); +#endif display_blanking_off(disp_dev); - - /* Prime the partial-refresh reference frame. After a full - * refresh the SSD1681's "old frame" RAM is stale, so the first - * partial diff produces a wrong delta and the partial waveform - * smears static regions (e.g. the top bar). One extra partial - * finalize with the same content lets the controller record the - * post-full-refresh state as its reference; it's a visual no-op - * (zero-diff → no pixel transitions) but fixes subsequent diffs. */ cfb_framebuffer_finalize(disp_dev); epd_partial_count = 0; - } else { - cfb_framebuffer_finalize(disp_dev); + epd_last_frame_hash = epd_frame_hash; + return; } + cfb_framebuffer_finalize(disp_dev); + if (is_epd) { epd_last_frame_hash = epd_frame_hash; } diff --git a/zephcore/patches/zephyr/0009-display-ssd16xx-fill-ram-white.patch b/zephcore/patches/zephyr/0009-display-ssd16xx-fill-ram-white.patch new file mode 100644 index 0000000..434e70c --- /dev/null +++ b/zephcore/patches/zephyr/0009-display-ssd16xx-fill-ram-white.patch @@ -0,0 +1,60 @@ +diff --git a/drivers/display/ssd16xx.c b/drivers/display/ssd16xx.c +index 0b9a3559dd5..e7f52c19ec3 100644 +--- a/drivers/display/ssd16xx.c ++++ b/drivers/display/ssd16xx.c +@@ -672,6 +672,27 @@ static int ssd16xx_clear_cntlr_mem(const struct device *dev, uint8_t ram_cmd) + return 0; + } + ++int ssd16xx_fill_ram_white(const struct device *dev) ++{ ++ /* ++ * Clear both RAM banks (Black and Red) to white, identical to the ++ * post-boot ssd16xx_controller_init state, WITHOUT updating the panel. ++ * Used by the ZephCore periodic full refresh: the caller then triggers ++ * a full refresh (panel goes white) and redraws the current page as a ++ * partial against the all-white reference, so every content pixel is ++ * driven and nothing stale is left behind — matching the clean ++ * post-boot first partial. Leaves entry mode at XIYDY; the following ++ * set_profile()/set_window() restore it. ++ */ ++ int err = ssd16xx_clear_cntlr_mem(dev, SSD16XX_CMD_WRITE_RAM); ++ ++ if (err < 0) { ++ return err; ++ } ++ ++ return ssd16xx_clear_cntlr_mem(dev, SSD16XX_CMD_WRITE_RED_RAM); ++} ++ + static inline int ssd16xx_load_ws_from_otp_tssv(const struct device *dev) + { + const struct ssd16xx_config *config = dev->config; +diff --git a/include/zephyr/display/ssd16xx.h b/include/zephyr/display/ssd16xx.h +index 53eb05b07ca..7e6690f82e8 100644 +--- a/include/zephyr/display/ssd16xx.h ++++ b/include/zephyr/display/ssd16xx.h +@@ -58,6 +58,23 @@ int ssd16xx_read_ram(const struct device *dev, enum ssd16xx_ram ram_type, + const struct display_buffer_descriptor *desc, + void *buf); + ++/** ++ * @brief Clear both RAM banks (Black and Red) to white without refreshing. ++ * ++ * Fills the controller's Black and Red RAM with white, matching the state ++ * established by ssd16xx_controller_init() at boot, without updating the ++ * panel. Intended for a full-refresh-to-white followed by a partial redraw: ++ * the all-white reference makes the next partial drive every content pixel ++ * with nothing stale to erase, avoiding both the fade (unchanged regions left ++ * on the neutral waveform) and the overlap (stale pixels not cleared) that ++ * occur when a partial follows a full refresh of non-white content. ++ * ++ * @param dev Pointer to device structure ++ * ++ * @return 0 on success, negative errno on failure. ++ */ ++int ssd16xx_fill_ram_white(const struct device *dev); ++ + /** @} */ + + #endif /* ZEPHYR_INCLUDE_DISPLAY_SSD16XX_H_ */