[RFC PATCH 07/16] drm/rockchip: ebc: Add CRTC buffer management

From: Samuel Holland
Date: Wed Apr 13 2022 - 18:22:05 EST


This commit adds the "context" structure which holds all buffers needed
to refresh the display. It is allocated separately from the CRTC state
because it is reused as long as no mode change occurs.

There are three buffers holding Y4 (grayscale 4 bits/pixel) pixel data:
- "prev" - contents of the display at the beginning of a refresh.
- "next" - contents of the display at the end of that refresh. When a
refresh finishes, the "next" buffer is copied into "prev".
- "final" - contents of the display at the end of the final refresh.
This buffer is necessary because a refresh waveform cannot be
modified or interrupted once it is started. If a pixel's value is
changed while it is already being refreshed, the "next" buffer
cannot be updated until the first waveform completes. At that time,
the "final" buffer is copied into "next", and another refresh is
started. The name "final" refers to the write-combining behavior of
this buffer; any number of plane updates can change this buffer
while waiting for the current refresh to complete.

Then there are two buffers holding "phase" data. These are only used
during partial refreshes. The phase represents the time component (the X
coordinate) of the waveform. Since the EBC supports a maximum of 256
phases in a waveform, the phase number requires one byte per pixel. The
driver swaps between two buffers to minimize the delay between phases,
as these buffers must be updated for every phase in the waveform.

Signed-off-by: Samuel Holland <samuel@xxxxxxxxxxxx>
---

drivers/gpu/drm/rockchip/rockchip_ebc.c | 166 +++++++++++++++++++++++-
1 file changed, 163 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.c b/drivers/gpu/drm/rockchip/rockchip_ebc.c
index ebe60d5e011a..095d66e67c2f 100644
--- a/drivers/gpu/drm/rockchip/rockchip_ebc.c
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc.c
@@ -17,6 +17,7 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_drv.h>
+#include <drm/drm_epd_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
@@ -141,6 +142,10 @@ struct rockchip_ebc {
bool reset_complete;
};

+static int default_waveform = DRM_EPD_WF_GC16;
+module_param(default_waveform, int, 0644);
+MODULE_PARM_DESC(default_waveform, "waveform to use for display updates");
+
static bool skip_reset;
module_param(skip_reset, bool, 0444);
MODULE_PARM_DESC(skip_reset, "skip the initial display reset");
@@ -165,12 +170,86 @@ static const struct drm_mode_config_funcs rockchip_ebc_mode_config_funcs = {
.atomic_commit = drm_atomic_helper_commit,
};

+/**
+ * struct rockchip_ebc_ctx - context for performing display refreshes
+ *
+ * @kref: Reference count, maintained as part of the CRTC's atomic state
+ * @prev: Display contents (Y4) before this refresh
+ * @next: Display contents (Y4) after this refresh
+ * @final: Display contents (Y4) after all pending refreshes
+ * @phase: Buffers for selecting a phase from the EBC's LUT, 1 byte/pixel
+ * @gray4_pitch: Horizontal line length of a Y4 pixel buffer in bytes
+ * @gray4_size: Size of a Y4 pixel buffer in bytes
+ * @phase_pitch: Horizontal line length of a phase buffer in bytes
+ * @phase_size: Size of a phase buffer in bytes
+ */
+struct rockchip_ebc_ctx {
+ struct kref kref;
+ u8 *prev;
+ u8 *next;
+ u8 *final;
+ u8 *phase[2];
+ u32 gray4_pitch;
+ u32 gray4_size;
+ u32 phase_pitch;
+ u32 phase_size;
+};
+
+static void rockchip_ebc_ctx_free(struct rockchip_ebc_ctx *ctx)
+{
+ kfree(ctx->prev);
+ kfree(ctx->next);
+ kfree(ctx->final);
+ kfree(ctx->phase[0]);
+ kfree(ctx->phase[1]);
+ kfree(ctx);
+}
+
+static struct rockchip_ebc_ctx *rockchip_ebc_ctx_alloc(u32 width, u32 height)
+{
+ u32 gray4_size = width * height / 2;
+ u32 phase_size = width * height;
+ struct rockchip_ebc_ctx *ctx;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return NULL;
+
+ ctx->prev = kmalloc(gray4_size, GFP_KERNEL);
+ ctx->next = kmalloc(gray4_size, GFP_KERNEL);
+ ctx->final = kmalloc(gray4_size, GFP_KERNEL);
+ ctx->phase[0] = kmalloc(phase_size, GFP_KERNEL);
+ ctx->phase[1] = kmalloc(phase_size, GFP_KERNEL);
+ if (!ctx->prev || !ctx->next || !ctx->final ||
+ !ctx->phase[0] || !ctx->phase[1]) {
+ rockchip_ebc_ctx_free(ctx);
+ return NULL;
+ }
+
+ kref_init(&ctx->kref);
+ ctx->gray4_pitch = width / 2;
+ ctx->gray4_size = gray4_size;
+ ctx->phase_pitch = width;
+ ctx->phase_size = phase_size;
+
+ return ctx;
+}
+
+static void rockchip_ebc_ctx_release(struct kref *kref)
+{
+ struct rockchip_ebc_ctx *ctx =
+ container_of(kref, struct rockchip_ebc_ctx, kref);
+
+ return rockchip_ebc_ctx_free(ctx);
+}
+
/*
* CRTC
*/

struct ebc_crtc_state {
struct drm_crtc_state base;
+ struct rockchip_ebc_ctx *ctx;
};

static inline struct ebc_crtc_state *
@@ -179,11 +258,70 @@ to_ebc_crtc_state(struct drm_crtc_state *crtc_state)
return container_of(crtc_state, struct ebc_crtc_state, base);
}

+static void rockchip_ebc_global_refresh(struct rockchip_ebc *ebc,
+ const struct rockchip_ebc_ctx *ctx)
+{
+ struct drm_device *drm = &ebc->drm;
+ u32 gray4_size = ctx->gray4_size;
+
+ drm_dbg(drm, "global refresh\n");
+
+ memcpy(ctx->prev, ctx->next, gray4_size);
+}
+
+static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
+ struct rockchip_ebc_ctx *ctx)
+{
+ struct drm_device *drm = &ebc->drm;
+
+ drm_dbg(drm, "partial refresh\n");
+}
+
+static void rockchip_ebc_refresh(struct rockchip_ebc *ebc,
+ struct rockchip_ebc_ctx *ctx,
+ bool global_refresh,
+ enum drm_epd_waveform waveform)
+{
+ if (global_refresh)
+ rockchip_ebc_global_refresh(ebc, ctx);
+ else
+ rockchip_ebc_partial_refresh(ebc, ctx);
+}
+
static int rockchip_ebc_refresh_thread(void *data)
{
struct rockchip_ebc *ebc = data;
+ struct rockchip_ebc_ctx *ctx;

while (!kthread_should_stop()) {
+ /* The context will change each time the thread is unparked. */
+ ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx;
+
+ /*
+ * Initialize the buffers before use. This is deferred to the
+ * kthread to avoid slowing down atomic_check.
+ *
+ * ctx->prev and ctx->next are set to 0xff, all white, because:
+ * 1) the display is set to white by the reset waveform, and
+ * 2) the driver maintains the invariant that the display is
+ * all white whenever the CRTC is disabled.
+ *
+ * ctx->final is initialized by the first plane update.
+ *
+ * ctx->phase is set to 0xff, the number of the last possible
+ * phase, because the LUT for that phase is known to be all
+ * zeroes. (The last real phase in a waveform is zero in order
+ * to discharge the display, and unused phases in the LUT are
+ * zeroed out.) This prevents undesired driving of the display
+ * in 3-window mode between when the framebuffer is blitted
+ * (and thus prev != next) and when the refresh thread starts
+ * counting phases for that region.
+ */
+ memset(ctx->prev, 0xff, ctx->gray4_size);
+ memset(ctx->next, 0xff, ctx->gray4_size);
+ memset(ctx->phase[0], 0xff, ctx->phase_size);
+ memset(ctx->phase[1], 0xff, ctx->phase_size);
+
/*
* LUTs use both the old and the new pixel values as inputs.
* However, the initial contents of the display are unknown.
@@ -192,11 +330,11 @@ static int rockchip_ebc_refresh_thread(void *data)
*/
if (!ebc->reset_complete) {
ebc->reset_complete = true;
- drm_dbg(&ebc->drm, "display reset\n");
+ rockchip_ebc_refresh(ebc, ctx, true, DRM_EPD_WF_RESET);
}

while (!kthread_should_park()) {
- drm_dbg(&ebc->drm, "display update\n");
+ rockchip_ebc_refresh(ebc, ctx, false, default_waveform);

set_current_state(TASK_IDLE);
schedule();
@@ -207,7 +345,8 @@ static int rockchip_ebc_refresh_thread(void *data)
* Clear the display before disabling the CRTC. Use the
* highest-quality waveform to minimize visible artifacts.
*/
- drm_dbg(&ebc->drm, "display clear\n");
+ memset(ctx->next, 0xff, ctx->gray4_size);
+ rockchip_ebc_refresh(ebc, ctx, true, DRM_EPD_WF_GC16);

kthread_parkme();
}
@@ -312,7 +451,9 @@ static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+ struct ebc_crtc_state *ebc_crtc_state;
struct drm_crtc_state *crtc_state;
+ struct rockchip_ebc_ctx *ctx;

crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
if (!crtc_state->mode_changed)
@@ -320,14 +461,26 @@ static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc,

if (crtc_state->enable) {
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+
long rate = mode->clock * 1000;

rate = clk_round_rate(ebc->dclk, rate);
if (rate < 0)
return rate;
mode->clock = rate / 1000;
+
+ ctx = rockchip_ebc_ctx_alloc(mode->hdisplay, mode->vdisplay);
+ if (!ctx)
+ return -ENOMEM;
+ } else {
+ ctx = NULL;
}

+ ebc_crtc_state = to_ebc_crtc_state(crtc_state);
+ if (ebc_crtc_state->ctx)
+ kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+ ebc_crtc_state->ctx = ctx;
+
return 0;
}

@@ -397,6 +550,10 @@ rockchip_ebc_crtc_duplicate_state(struct drm_crtc *crtc)

__drm_atomic_helper_crtc_duplicate_state(crtc, &ebc_crtc_state->base);

+ ebc_crtc_state->ctx = to_ebc_crtc_state(crtc->state)->ctx;
+ if (ebc_crtc_state->ctx)
+ kref_get(&ebc_crtc_state->ctx->kref);
+
return &ebc_crtc_state->base;
}

@@ -405,6 +562,9 @@ static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc,
{
struct ebc_crtc_state *ebc_crtc_state = to_ebc_crtc_state(crtc_state);

+ if (ebc_crtc_state->ctx)
+ kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+
__drm_atomic_helper_crtc_destroy_state(&ebc_crtc_state->base);

kfree(ebc_crtc_state);
--
2.35.1