diff --git a/Documentation/gpu/drm-mm.rst b/Documentation/gpu/drm-mm.rst
index 99d56015e077..eac12bd0ea3f 100644
--- a/Documentation/gpu/drm-mm.rst
+++ b/Documentation/gpu/drm-mm.rst
@@ -379,15 +379,12 @@ GEM CMA Helper Functions Reference
 .. kernel-doc:: drivers/gpu/drm/drm_gem_cma_helper.c
    :export:
 
-VRAM Helper Function Reference
-==============================
+VRAM Helper Library
+===================
 
 .. kernel-doc:: drivers/gpu/drm/drm_vram_helper_common.c
    :doc: overview
 
-.. kernel-doc:: include/drm/drm_gem_vram_helper.h
-   :internal:
-
 GEM VRAM Helper Functions Reference
 -----------------------------------
 
@@ -400,6 +397,18 @@ GEM VRAM Helper Functions Reference
 .. kernel-doc:: drivers/gpu/drm/drm_gem_vram_helper.c
    :export:
 
+VRAM Buffer Function Reference
+------------------------------
+
+.. kernel-doc:: drivers/gpu/drm/drm_vram_helper_common.c
+   :doc: vbuf overview
+
+.. kernel-doc:: include/drm/drm_vram_helper.h
+   :internal:
+
+.. kernel-doc:: drivers/gpu/drm/drm_vram_helper_common.c
+   :export:
+
 GEM TTM Helper Functions Reference
 -----------------------------------
 
diff --git a/drivers/gpu/drm/drm_vram_helper_common.c b/drivers/gpu/drm/drm_vram_helper_common.c
index 2000d9b33fd5..ec55fa374edc 100644
--- a/drivers/gpu/drm/drm_vram_helper_common.c
+++ b/drivers/gpu/drm/drm_vram_helper_common.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <drm/drm_vram_helper.h>
 #include <linux/module.h>
 
 /**
@@ -8,9 +9,11 @@
  * This library provides &struct drm_gem_vram_object (GEM VRAM), a GEM
  * buffer object that is backed by video RAM. It can be used for
  * framebuffer devices with dedicated memory. The video RAM is managed
- * by &struct drm_vram_mm (VRAM MM).
+ * by &struct drm_vram_mm (VRAM MM). For very simple use cases,
+ * &struct drm_vram_buffer provides management of memory buffers with
+ * static size.
  *
- * With the GEM interface userspace applications create, manage and destroy
+ * With the GEM interface, userspace applications create, manage and destroy
  * graphics buffers, such as an on-screen framebuffer. GEM does not provide
  * an implementation of these interfaces. It's up to the DRM driver to
  * provide an implementation that suits the hardware. If the hardware device
@@ -88,7 +91,259 @@
  * drm_gem_vram_kmap(). It (optionally) maps the buffer into kernel address
  * space and returns the memory address. Use drm_gem_vram_kunmap() to
  * release the mapping.
+ *
+ * Occasionally, it is easier and more efficient to dedicate a memory
+ * buffer to certain functionality and not bother with GEM. One example is
+ * hardware cursor support. Such buffers are typically small or have uncommon
+ * pixel formats, such as a small color palette or a reduced number of bits
+ * per pixel. DRM GEM objects and framebuffers can often not be used directly
+ * with such functionality, as an additional dithering step is required.
+ * Maintaining such buffers with GEM objects can lead to memory fragmentation,
+ * which is a problem for devices with only a small amount of VRAM.
+ *
+ * The data structure &struct drm_vram_buffer and its helpers provide memory
+ * management for these use cases. Each instance of &struct drm_vram_buffer
+ * handles a dedicated area of VRAM. It offers double buffering (or more)
+ * and access to the current front and back buffer.
+ *
+ * For example, assume a hardware cursor with 64 x 64 pixels and 2 bytes per
+ * pixel. Storing this cursor's image requires 8 KiB of VRAM. The VRAM buffer
+ * could manage this memory as shown below.
+ *
+ * .. code-block:: c
+ *
+ *      static const struct drm_vram_buffer_funcs funcs = {
+ *              .update = do_update, // see below
+ *              .set_base = do_set_base, // see below
+ *      };
+ *
+ *      struct drm_vram_buffer vbuf;
+ *
+ *      drm_vram_buffer_init(&vbuf, 0x0ul, 16 * 1024 * 1024, 0, 2,
+ *                           &funcs);
+ *
+ * A call to drm_vram_buffer_init() initializes the VRAM buffer. The
+ * cursor-image buffer is located at VRAM address 0x0. It is 16 KiB in
+ * size to allow for double buffering. The implementation auto-detects the
+ * frame size (i.e., the size of each individual sub-buffer) from the number
+ * of frames, which is 2. Finally there's a structure of call-back functions,
+ * which are explained below. The rest of VRAM memory can be managed with
+ * other memory managers, such as other VRAM buffers or GEM VRAM helpers.
+ *
+ * A call to drm_vram_buffer_cleanup() releases the VRAM buffer.
+ *
+ * .. code-block:: c
+ *
+ *      drm_vram_buffer_cleanup(&vbuf);
+ *
+ * Displaying a VRAM buffer is a two-step process. The first step fills the
+ * current back buffer, the second step swaps buffers and sends the new front
+ * buffer's offset to the display's scanout engine. The first step should be
+ * performed in the cursor plane's &struct drm_plane_helper_funcs.prepare_fb()
+ * callback, the second step should be located in the plane's
+ * &struct drm_plane_helper_funcs.atomic_update() callback.
+ *
+ * .. code-block:: c
+ *
+ *      int prepare_fb(struct drm_plane *plane,
+ *                     struct drm_plane_state *new_state)
+ *      {
+ *              int width = new_state->fb->width;
+ *              int height = new_state->fb->height;
+ *              void *src = ...; // memory buffer of new_state->fb
+ *
+ *              drm_vram_buffer_update(&vbuf, src, width, height);
+ *      }
+ *
+ *      void atomic_update(struct drm_plane *plane,
+ *                         struct drm_plane_state *old_state)
+ *      {
+ *              drm_vram_buffer_swap(&vbuf);
+ *      }
+ *
+ * The call to drm_vram_buffer_update() looks up the next available back
+ * buffer and gives its address to the VRAM buffer's
+ * &struct drm_vram_buffer_funcs.update() callback; together with the other
+ * arguments. After a successful call the back buffer is ready for displaying.
+ * This is done by the call to drm_vram_buffer_swap().
+ *
+ * The implementation of do_update() and do_set_base() looks something like
+ * shown below.
+ *
+ * .. code-block:: c
+ *
+ *      int do_update(struct drm_vram_buffer *vbuf, void *dst, void *src,
+ *                    unsigned int width, unsigned int height)
+ *      {
+ *              unsigned int x, y;
+ *              unsigned int i = 0;
+ *
+ *              for (y = 0; y < height; ++y) {
+ *                      for (x = 0; x < width; ++x, ++i) {
+ *                              dst[i] = convert_to_cursor_format(src[i]);
+ *                      }
+ *              }
+ *              return 0;
+ *      }
+ *
+ *      void do_set_base(struct vram_buffer *vbuf, u64 addr)
+ *      {
+ *              // write addr to device registers and enable HW cursor
+ *      }
+ *
+ * The implementation of do_update() does the format conversion and sets up
+ * the destination buffer (i.e., the VRAM buffer's current back-buffer frame).
+ * The implementation if do_set_base() writes the back buffer's address to
+ * the device and hence makes it the new front buffer.
  */
 
 MODULE_DESCRIPTION("DRM VRAM memory-management helpers");
 MODULE_LICENSE("GPL");
+
+/*
+ * Helpers for dedicated VRAM buffers
+ */
+
+/**
+ * DOC: vbuf overview
+ *
+ * A VRAM buffer manage an area of video memory that has been dedicated
+ * to a specific functionality. Typical use cases are hardware cursors and
+ * sprites.
+ */
+
+/**
+ * drm_vram_buffer_init - Initializes a VRAM buffer
+ * @vbuf:	The VRAM-buffer instance to initialize
+ * @base:	The managed memory's base address
+ * @size:	The managed memory's size in bytes
+ * @frame_size:	The size of each individual sub-buffer
+ * @nframes:	The number of sub buffers
+ * @funcs:	Hardware-specific call-back functions
+ * Returns:	0 on success, or a negative error code otherwise.
+ */
+int drm_vram_buffer_init(struct drm_vram_buffer *vbuf,
+			 resource_size_t base, resource_size_t size,
+			 unsigned int frame_size, unsigned int nframes,
+			 const struct drm_vram_buffer_funcs *funcs)
+{
+	void *mem;
+
+	/* try to auto-detect 0-value arguments */
+	if (!frame_size && !nframes) {
+		frame_size = size;
+		nframes = 1;
+	} else if (!frame_size) {
+		frame_size = size / nframes;
+		if (!frame_size)
+			return -EINVAL;
+	} else if (!nframes) {
+		nframes = size / frame_size;
+		if (!nframes)
+			return -EINVAL;
+	} else if (frame_size * nframes > size) {
+		return -ENOMEM;
+	}
+
+	mem = ioremap(base, size);
+	if (!mem)
+		return -ENOMEM;
+
+	vbuf->funcs = funcs;
+	vbuf->mem_base = base;
+	vbuf->mem_size = size;
+	vbuf->mem = mem;
+	vbuf->frame_size = frame_size;
+	vbuf->nframes = nframes;
+	vbuf->next_index = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_vram_buffer_init);
+
+/**
+ * drm_vram_buffer_cleanup - Releases a VRAM buffer
+ * @vbuf:	The VRAM buffer to release
+ */
+void drm_vram_buffer_cleanup(struct drm_vram_buffer *vbuf)
+{
+	iounmap(vbuf->mem);
+}
+EXPORT_SYMBOL(drm_vram_buffer_cleanup);
+
+/**
+ * drm_vram_buffer_update - Updates the current back buffer
+ * @vbuf:	The VRAM buffer
+ * @src:	The source buffer
+ * @width:	The source buffer's width in pixel
+ * @height:	The source buffer's height in scanlines
+ * Returns:	0 on success, or a negative error code otherwise
+ */
+int drm_vram_buffer_update(struct drm_vram_buffer *vbuf, void *src,
+			   unsigned int width, unsigned int height)
+{
+	unsigned long off;
+	void *dst;
+	int ret;
+
+	if (!vbuf->funcs->update)
+		return 0;
+
+	off = vbuf->frame_size * vbuf->next_index;
+	dst = &vbuf->mem[off];
+
+	ret = vbuf->funcs->update(vbuf, dst, src, width, height);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_vram_buffer_update);
+
+/**
+ * drm_vram_buffer_swap - Replaces the current front buffer wih the back buffer
+ * @vbuf:	The VRAM buffer
+ */
+void drm_vram_buffer_swap(struct drm_vram_buffer *vbuf)
+{
+	if (vbuf->funcs->set_base) {
+		unsigned long off = vbuf->frame_size * vbuf->next_index;
+		u64 addr = vbuf->mem_base + off;
+
+		vbuf->funcs->set_base(vbuf, addr);
+	}
+
+	vbuf->next_index = (vbuf->next_index + 1) % vbuf->nframes;
+}
+EXPORT_SYMBOL(drm_vram_buffer_swap);
+
+/**
+ * drm_vram_buffer_get - Returns the current back buffer
+ * @vbuf:	The VRAM buffer
+ * Returns:	The address of the current back buffer
+ *		in kernel address space
+ */
+void *drm_vram_buffer_get(struct drm_vram_buffer *vbuf)
+{
+	unsigned long off = vbuf->frame_size * vbuf->next_index;
+
+	return &vbuf->mem[off];
+}
+EXPORT_SYMBOL(drm_vram_buffer_get);
+
+/**
+ * drm_vram_buffer_get_current - Returns the current front buffer
+ * @vbuf:	The VRAM buffer
+ * Returns:	The address of the current front buffer
+ *		in kernel address space
+ */
+void *drm_vram_buffer_get_current(struct drm_vram_buffer *vbuf)
+{
+	unsigned int cur_index;
+	unsigned long off;
+
+	cur_index = vbuf->next_index + (vbuf->nframes - 1) % vbuf->nframes;
+	off = vbuf->frame_size * vbuf->next_index;
+	return &vbuf->mem[off];
+}
+EXPORT_SYMBOL(drm_vram_buffer_get_current);
diff --git a/include/drm/drm_vram_helper.h b/include/drm/drm_vram_helper.h
new file mode 100644
index 000000000000..6b5c2381543b
--- /dev/null
+++ b/include/drm/drm_vram_helper.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef DRM_VRAM_HELPER_H
+#define DRM_VRAM_HELPER_H
+
+#include <linux/types.h>
+
+/*
+ * DRM VRAM dedicated memory
+ */
+
+struct drm_vram_buffer_funcs;
+
+/**
+ * struct drm_vram_buffer - Manages a dedicated area of video RAM
+ * @funcs:	Call-back functions
+ * @mem_base:	The base address of the managed memory
+ * @mem_size:	The size of the managed memory
+ * @mem:	Mapping of the VRAM buffer's memory into kernel address space
+ * @frame_size:	The size of each individual frame
+ * @nframes:	The number of frames
+ * @next_index:	The index of the next frame (i.e., the current back buffer)
+ *
+ * The data structure &struct vram_buffer manages a dedicated area of video
+ * RAM.
+ */
+struct drm_vram_buffer {
+	const struct drm_vram_buffer_funcs *funcs;
+
+	resource_size_t mem_base;
+	resource_size_t mem_size;
+
+	u8 *mem;
+
+	unsigned int frame_size;
+	unsigned int nframes;
+	unsigned int next_index;
+};
+
+/**
+ * struct drm_vram_buffer_funcs - Call-back functions for
+ *                                &struct drm_vram_buffer
+ * @update:	Updates the current back buffer from a source buffer
+ * @set_base:	Sets the current back buffer as the new front buffer
+ *              used by hardware
+ */
+struct drm_vram_buffer_funcs {
+	int (*update)(struct drm_vram_buffer *vbuf, void *dst, void *src,
+		      unsigned int width, unsigned int height);
+	void (*set_base)(struct drm_vram_buffer *vbuf, u64 address);
+};
+
+int drm_vram_buffer_init(struct drm_vram_buffer *vbuf,
+			 resource_size_t base, resource_size_t size,
+			 unsigned int frame_size, unsigned int nframes,
+			 const struct drm_vram_buffer_funcs *funcs);
+void drm_vram_buffer_cleanup(struct drm_vram_buffer *vbuf);
+int drm_vram_buffer_update(struct drm_vram_buffer *vbuf, void *src,
+			   unsigned int width, unsigned int height);
+void drm_vram_buffer_swap(struct drm_vram_buffer *vbuf);
+void *drm_vram_buffer_get(struct drm_vram_buffer *vbuf);
+void *drm_vram_buffer_get_current(struct drm_vram_buffer *vbuf);
+
+#endif
