mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Thomas Zimmermann <tzimmermann@suse.de>
To: LiangCheng Wang <zaq14760@gmail.com>,
	Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>, Wig Cheng <onlywig@gmail.com>
Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
Date: Wed, 17 Jun 2026 17:31:53 +0200	[thread overview]
Message-ID: <9fb7915b-dc46-45af-bba1-a3d3a59b5e49@suse.de> (raw)
In-Reply-To: <20260529-bar-v3-2-5c2ac1c751ee@gmail.com>

Hi,

apologies for being late with the review.

Am 29.05.26 um 12:31 schrieb LiangCheng Wang:
> Introduce a DRM driver for the Mayqueen Pixpaper 4.26
> monochrome e-ink display panel, which is controlled via SPI.
> The driver supports an 800x480 display with XRGB8888
> framebuffer input.
>
> Also, add Kconfig and Makefile entries for the driver and
> update MAINTAINERS for the Pixpaper DRM drivers and binding.
>
> Signed-off-by: LiangCheng Wang <zaq14760@gmail.com>
> ---
>   MAINTAINERS                          |   3 +-
>   drivers/gpu/drm/tiny/Kconfig         |  16 +
>   drivers/gpu/drm/tiny/Makefile        |   1 +
>   drivers/gpu/drm/tiny/pixpaper-426m.c | 817 +++++++++++++++++++++++++++++++++++
>   4 files changed, 836 insertions(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 882214b0e7db53bb8cc8e75b5d2269ee0591ea20..eebd73ee1f531d3785ec963da03fbab265c2d188 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8234,11 +8234,12 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
>   F:	Documentation/devicetree/bindings/display/repaper.txt
>   F:	drivers/gpu/drm/tiny/repaper.c
>   
> -DRM DRIVER FOR PIXPAPER E-INK PANEL
> +DRM DRIVER FOR PIXPAPER E-INK PANELS
>   M:	LiangCheng Wang <zaq14760@gmail.com>
>   L:	dri-devel@lists.freedesktop.org
>   S:	Maintained
>   F:	Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml
> +F:	drivers/gpu/drm/tiny/pixpaper-426m.c
>   F:	drivers/gpu/drm/tiny/pixpaper.c
>   
>   DRM DRIVER FOR QEMU'S CIRRUS DEVICE
> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
> index f0e72d4b6a4709564e63c758e857bdb4a320dbe7..028c4314106ac31dfa717f6433c28e58b34c21e8 100644
> --- a/drivers/gpu/drm/tiny/Kconfig
> +++ b/drivers/gpu/drm/tiny/Kconfig
> @@ -98,6 +98,22 @@ config DRM_PIXPAPER
>   
>   	  If M is selected, the module will be built as pixpaper.ko.
>   
> +config DRM_PIXPAPER_426M
> +	tristate "DRM support for PIXPAPER 4.26 monochrome display panel"
> +	depends on DRM && SPI
> +	depends on MMU
> +	select DRM_CLIENT_SELECTION
> +	select DRM_GEM_SHMEM_HELPER
> +	select DRM_KMS_HELPER
> +	help
> +	  DRM driver for the Mayqueen Pixpaper 4.26 monochrome e-ink
> +	  display panel.
> +
> +	  This driver supports SPI-connected 800x480 monochrome panels
> +	  with an XRGB8888 framebuffer input format.
> +
> +	  If M is selected, the module will be built as pixpaper-426m.ko.
> +
>   config TINYDRM_HX8357D
>   	tristate "DRM support for HX8357D display panels"
>   	depends on DRM && SPI
> diff --git a/drivers/gpu/drm/tiny/Makefile b/drivers/gpu/drm/tiny/Makefile
> index 48d30bf6152f979404ac1004174587823a30109e..037b751a1a851cc2f86f701ff71008bcb9c59f29 100644
> --- a/drivers/gpu/drm/tiny/Makefile
> +++ b/drivers/gpu/drm/tiny/Makefile
> @@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_CIRRUS_QEMU)		+= cirrus-qemu.o
>   obj-$(CONFIG_DRM_GM12U320)		+= gm12u320.o
>   obj-$(CONFIG_DRM_PANEL_MIPI_DBI)	+= panel-mipi-dbi.o
>   obj-$(CONFIG_DRM_PIXPAPER)              += pixpaper.o
> +obj-$(CONFIG_DRM_PIXPAPER_426M)         += pixpaper-426m.o
>   obj-$(CONFIG_TINYDRM_HX8357D)		+= hx8357d.o
>   obj-$(CONFIG_TINYDRM_ILI9163)		+= ili9163.o
>   obj-$(CONFIG_TINYDRM_ILI9225)		+= ili9225.o
> diff --git a/drivers/gpu/drm/tiny/pixpaper-426m.c b/drivers/gpu/drm/tiny/pixpaper-426m.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..99464d564f315543037a3621ff85f98f1bd8f34c
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/pixpaper-426m.c
> @@ -0,0 +1,817 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * DRM driver for PIXPAPER 4.26 monochrome e-ink panel
> + *
> + * Author: LiangCheng Wang <zaq14760@gmail.com>,
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/string.h>
> +
> +#include <drm/clients/drm_client_setup.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_shmem.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_gem_shmem_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +
> +MODULE_IMPORT_NS("DMA_BUF");
> +
> +/* Panel visible resolution */
> +#define PIXPAPER_WIDTH    800
> +#define PIXPAPER_HEIGHT   480

We already use the prefixes PIXPAPER_, and pixpaper, and variants 
thereof in the current pixpaper driver. You have to choose a different 
prefix.  If I may suggest one, pix426m_ would be fine.

> +
> +/*
> + * The panel datasheet specifies an active area of 92.8 mm x 55.68 mm.
> + * Round to whole millimeters for drm_display_info.
> + */
> +#define PIXPAPER_WIDTH_MM   93
> +#define PIXPAPER_HEIGHT_MM  56
> +
> +/*
> + * According to the panel datasheet, no RGB-style timing parameters
> + * (porches, sync widths, or a dot clock) are provided. Define a minimal
> + * fixed mode only to satisfy the DRM mode API for this SPI-driven
> + * e-paper panel.
> + */
> +#define PIXPAPER_HSYNC_LEN     1
> +#define PIXPAPER_HFRONT_PORCH  1
> +#define PIXPAPER_HBACK_PORCH   1
> +#define PIXPAPER_VSYNC_LEN     1
> +#define PIXPAPER_VFRONT_PORCH  1
> +#define PIXPAPER_VBACK_PORCH   1
> +#define PIXPAPER_MODE_REFRESH_HZ 1
> +#define PIXPAPER_MODE_CLOCK_KHZ \
> +	(((PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN + \
> +	   PIXPAPER_HBACK_PORCH) * \
> +	  (PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN + \
> +	   PIXPAPER_VBACK_PORCH) * \
> +	  PIXPAPER_MODE_REFRESH_HZ) / 1000)

You can, I think, drop all these constants. Further below where you 
define a display mode, just use DRM_SIMPLE_MODE() with the pixel 
resolution and physical dimensions.  This is how other drivers make up 
display modes.

> +
> +#define PIXPAPER_SPI_BITS_PER_WORD 8
> +#define PIXPAPER_SPI_SPEED_DEFAULT 1000000
> +
> +#define PIXPAPER_TX_BUF_SIZE 8
> +
> +#define PIXPAPER_PIXEL_THRESHOLD 128
> +
> +#define PIXPAPER_BUSY_TIMEOUT_MS 10000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MIN 1000
> +#define PIXPAPER_BUSY_POLL_INITIAL_US_MAX 1500
> +#define PIXPAPER_BUSY_POLL_US_MIN 100
> +#define PIXPAPER_BUSY_POLL_US_MAX 200
> +
> +#define PIXPAPER_RAM_START_ADDR 0x00
> +
> +#define PIXPAPER_LUMA_R_WEIGHT 299
> +#define PIXPAPER_LUMA_G_WEIGHT 587
> +#define PIXPAPER_LUMA_B_WEIGHT 114
> +#define PIXPAPER_LUMA_DIVISOR 1000
> +#define PIXPAPER_LUMA_ROUNDING_BIAS 500
> +
> +#define PIXPAPER_CMD_DRIVER_OUTPUT_CTRL      0x01
> +#define PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL 0x0C
> +#define PIXPAPER_CMD_TEMP_SENSOR_CONTROL     0x18
> +#define PIXPAPER_CMD_MASTER_ACTIVATION       0x20
> +#define PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2    0x22
> +#define PIXPAPER_CMD_WRITE_RAM_BW            0x24
> +#define PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL 0x3C
> +#define PIXPAPER_CMD_SET_RAM_X_START_END     0x44
> +#define PIXPAPER_CMD_SET_RAM_Y_START_END     0x45
> +#define PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER  0x4E
> +#define PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER  0x4F
> +
> +#define PIXPAPER_DRIVER_OUTPUT_SM                BIT(1)
> +
> +#define PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION   (0x0 << 6)
> +#define PIXPAPER_BORDER_WAVEFORM_LUT1_SEL        0x1
> +
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_CLK         BIT(7)
> +#define PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG      BIT(6)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_TEMP          BIT(5)
> +#define PIXPAPER_UPDATE_CTRL2_LOAD_LUT           BIT(4)
> +#define PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY    BIT(2)
> +
> +#define PIXPAPER_TEMP_SENSOR_INTERNAL           0x80
> +#define PIXPAPER_SOFTSTART_A                    0xAE
> +#define PIXPAPER_SOFTSTART_B                    0xC7
> +#define PIXPAPER_SOFTSTART_C                    0xC3
> +#define PIXPAPER_SOFTSTART_D                    0xC0
> +#define PIXPAPER_SOFTSTART_E                    0x80
> +#define PIXPAPER_DRIVER_OUTPUT_GD_SM_TB         PIXPAPER_DRIVER_OUTPUT_SM
> +#define PIXPAPER_BORDER_LUT1                    \
> +	(PIXPAPER_BORDER_WAVEFORM_GS_TRANSITION | \
> +	 PIXPAPER_BORDER_WAVEFORM_LUT1_SEL)
> +#define PIXPAPER_UPDATE_INITIAL                 \
> +	(PIXPAPER_UPDATE_CTRL2_ENABLE_CLK | \
> +	PIXPAPER_UPDATE_CTRL2_ENABLE_ANALOG | \
> +	PIXPAPER_UPDATE_CTRL2_LOAD_TEMP | \
> +	PIXPAPER_UPDATE_CTRL2_LOAD_LUT | \
> +	PIXPAPER_UPDATE_CTRL2_PATTERN_DISPLAY)

Empty line here?

> +struct pixpaper_error_ctx {
> +	int errno_code;
> +};
> +
> +struct pixpaper_init_seq {
> +	u8 cmd;
> +	const u8 *data;
> +	u8 len;
> +};
> +
> +struct pixpaper_panel {
> +	struct drm_device drm;
> +	struct drm_plane plane;
> +	struct drm_crtc crtc;
> +	struct drm_encoder encoder;
> +	struct drm_connector connector;
> +
> +	struct spi_device *spi;
> +	struct gpio_desc *reset;
> +	struct gpio_desc *busy;
> +	struct gpio_desc *dc;
> +
> +	u8 *tx_buf;
> +};
> +
> +static const uint32_t pixpaper_formats[] = {
> +	DRM_FORMAT_XRGB8888,
> +};
> +
> +static const u8 pixpaper_init_temp_sensor[] = {
> +	PIXPAPER_TEMP_SENSOR_INTERNAL,
> +};
> +
> +static const u8 pixpaper_init_softstart[] = {
> +	PIXPAPER_SOFTSTART_A,
> +	PIXPAPER_SOFTSTART_B,
> +	PIXPAPER_SOFTSTART_C,
> +	PIXPAPER_SOFTSTART_D,
> +	PIXPAPER_SOFTSTART_E,
> +};
> +
> +static const u8 pixpaper_init_driver_output[] = {
> +	(PIXPAPER_HEIGHT - 1) & 0xff,
> +	(PIXPAPER_HEIGHT - 1) >> 8,
> +	PIXPAPER_DRIVER_OUTPUT_GD_SM_TB,
> +};
> +
> +static const u8 pixpaper_init_border[] = {
> +	PIXPAPER_BORDER_LUT1,
> +};
> +
> +static const u8 pixpaper_init_ram_x_window[] = {
> +	PIXPAPER_RAM_START_ADDR,
> +	PIXPAPER_RAM_START_ADDR,
> +	(PIXPAPER_WIDTH - 1) & 0xff,
> +	(PIXPAPER_WIDTH - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_y_window[] = {
> +	PIXPAPER_RAM_START_ADDR,
> +	PIXPAPER_RAM_START_ADDR,
> +	(PIXPAPER_HEIGHT - 1) & 0xff,
> +	(PIXPAPER_HEIGHT - 1) >> 8,
> +};
> +
> +static const u8 pixpaper_init_ram_x_counter[] = {
> +	PIXPAPER_RAM_START_ADDR,
> +	PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const u8 pixpaper_init_ram_y_counter[] = {
> +	PIXPAPER_RAM_START_ADDR,
> +	PIXPAPER_RAM_START_ADDR,
> +};
> +
> +static const struct pixpaper_init_seq pixpaper_init_seqs[] = {
> +	{
> +		.cmd = PIXPAPER_CMD_TEMP_SENSOR_CONTROL,
> +		.data = pixpaper_init_temp_sensor,
> +		.len = ARRAY_SIZE(pixpaper_init_temp_sensor),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_BOOSTER_SOFT_START_CTRL,
> +		.data = pixpaper_init_softstart,
> +		.len = ARRAY_SIZE(pixpaper_init_softstart),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_DRIVER_OUTPUT_CTRL,
> +		.data = pixpaper_init_driver_output,
> +		.len = ARRAY_SIZE(pixpaper_init_driver_output),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_BORDER_WAVEFORM_CONTROL,
> +		.data = pixpaper_init_border,
> +		.len = ARRAY_SIZE(pixpaper_init_border),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_SET_RAM_X_START_END,
> +		.data = pixpaper_init_ram_x_window,
> +		.len = ARRAY_SIZE(pixpaper_init_ram_x_window),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_SET_RAM_Y_START_END,
> +		.data = pixpaper_init_ram_y_window,
> +		.len = ARRAY_SIZE(pixpaper_init_ram_y_window),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER,
> +		.data = pixpaper_init_ram_x_counter,
> +		.len = ARRAY_SIZE(pixpaper_init_ram_x_counter),
> +	},
> +	{
> +		.cmd = PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER,
> +		.data = pixpaper_init_ram_y_counter,
> +		.len = ARRAY_SIZE(pixpaper_init_ram_y_counter),
> +	},
> +};
> +
> +static inline struct pixpaper_panel *to_pixpaper_panel(struct drm_device *drm)
> +{
> +	return container_of(drm, struct pixpaper_panel, drm);
> +}
> +
> +static void pixpaper_wait_for_panel(struct pixpaper_panel *panel)
> +{
> +	unsigned int timeout_ms = PIXPAPER_BUSY_TIMEOUT_MS;
> +	unsigned long timeout_jiffies = jiffies + msecs_to_jiffies(timeout_ms);
> +
> +	usleep_range(PIXPAPER_BUSY_POLL_INITIAL_US_MIN,
> +		     PIXPAPER_BUSY_POLL_INITIAL_US_MAX);
> +	while (gpiod_get_value_cansleep(panel->busy) != 0) {
> +		if (time_after(jiffies, timeout_jiffies)) {
> +			/*
> +			 * Treat a busy timeout as warning-only. Some panels may
> +			 * keep BUSY asserted longer than expected during
> +			 * initialization or refresh.
> +			 */
> +			drm_warn(&panel->drm, "Busy wait timed out\n");
> +			return;
> +		}
> +		usleep_range(PIXPAPER_BUSY_POLL_US_MIN,
> +			     PIXPAPER_BUSY_POLL_US_MAX);
> +	}
> +}
> +
> +static void pixpaper_spi_write(struct pixpaper_panel *panel, int dc,
> +			       const void *buf, size_t len,
> +			       struct pixpaper_error_ctx *err)
> +{
> +	int ret;
> +
> +	if (err->errno_code || !len)
> +		return;
> +
> +	gpiod_set_value_cansleep(panel->dc, dc);
> +	usleep_range(1, 5);
> +
> +	ret = spi_write(panel->spi, buf, len);
> +	if (ret < 0)
> +		err->errno_code = ret;
> +}
> +
> +static void pixpaper_send_cmd(struct pixpaper_panel *panel, u8 cmd,
> +			      struct pixpaper_error_ctx *err)
> +{
> +	panel->tx_buf[0] = cmd;
> +	pixpaper_spi_write(panel, 0, panel->tx_buf, sizeof(cmd), err);
> +}
> +
> +static void pixpaper_send_data(struct pixpaper_panel *panel, u8 data,
> +			       struct pixpaper_error_ctx *err)
> +{
> +	panel->tx_buf[0] = data;
> +	pixpaper_spi_write(panel, 1, panel->tx_buf, sizeof(data), err);
> +}
> +
> +static void pixpaper_reset_ram_counters(struct pixpaper_panel *panel,
> +					struct pixpaper_error_ctx *err)
> +{
> +	if (err->errno_code)
> +		return;
> +
> +	pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_X_ADDR_COUNTER, err);
> +	pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +	pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +
> +	pixpaper_send_cmd(panel, PIXPAPER_CMD_SET_RAM_Y_ADDR_COUNTER, err);
> +	pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +	pixpaper_send_data(panel, PIXPAPER_RAM_START_ADDR, err);
> +}
> +
> +static void pixpaper_write_ram(struct pixpaper_panel *panel, u8 cmd,
> +			       const u8 *buf, u32 len,
> +			       struct pixpaper_error_ctx *err)
> +{
> +	if (err->errno_code || !buf || !len)
> +		return;
> +
> +	pixpaper_reset_ram_counters(panel, err);
> +
> +	pixpaper_send_cmd(panel, cmd, err);
> +	pixpaper_spi_write(panel, 1, buf, len, err);
> +}
> +
> +static void pixpaper_send_init_seq(struct pixpaper_panel *panel,
> +				   const struct pixpaper_init_seq *seq,
> +				   struct pixpaper_error_ctx *err)
> +{
> +	if (err->errno_code || !seq->data || !seq->len)
> +		return;
> +
> +	if (seq->len > PIXPAPER_TX_BUF_SIZE) {
> +		err->errno_code = -EINVAL;
> +		return;
> +	}
> +
> +	pixpaper_send_cmd(panel, seq->cmd, err);
> +	memcpy(panel->tx_buf, seq->data, seq->len);
> +	pixpaper_spi_write(panel, 1, panel->tx_buf, seq->len, err);
> +}
> +
> +static void pixpaper_trigger_update(struct pixpaper_panel *panel,
> +				    struct pixpaper_error_ctx *err)
> +{
> +	if (err->errno_code)
> +		return;
> +
> +	pixpaper_send_cmd(panel, PIXPAPER_CMD_DISPLAY_UPDATE_CTRL2, err);
> +	pixpaper_send_data(panel, PIXPAPER_UPDATE_INITIAL, err);
> +	pixpaper_send_cmd(panel, PIXPAPER_CMD_MASTER_ACTIVATION, err);
> +	pixpaper_wait_for_panel(panel);
> +}
> +
> +static void pixpaper_xrgb8888_to_bw(const void *src, void *dst, u32 height,
> +				    u32 width, u32 src_pitch, u32 dst_pitch)

The code in this function is very similar to drm_fb_xrgb8888_to_mono(); 
just reversed. Unfortunately, there's no simple way of reusing anything.

https://elixir.bootlin.com/linux/v7.1/source/drivers/gpu/drm/drm_format_helper.c#L1204

> +{
> +	const uint8_t *src_base = src;
> +	uint8_t *dst_pixels = dst;
> +
> +	if (dst == NULL || src == NULL)
> +		return;
> +
> +	for (u32 y = 0; y < height; y++) {
> +		uint8_t *dst_row = dst_pixels + y * dst_pitch;
> +		const __le32 *src_pixels =
> +			(const __le32 *)(src_base + y * src_pitch);
> +
> +		for (u32 x = 0; x < width; x++) {
> +			/*
> +			 * The panel RAM X direction is reversed relative to DRM
> +			 * coordinates. Read pixels from right to left so the
> +			 * displayed image matches the expected orientation on
> +			 * the panel.
> +			 */
> +			u32 src_x = width - 1 - x;
> +			uint8_t r, g, b;
> +			u8 bit;
> +			u32 bit_pos = x % 8;
> +			u32 byte_pos = x / 8;
> +			uint32_t gray_val;
> +			uint32_t pixel;
> +
> +			pixel = le32_to_cpu(src_pixels[src_x]);
> +			r = (pixel >> 16) & 0xFF;
> +			g = (pixel >> 8) & 0xFF;
> +			b = pixel & 0xFF;
> +
> +			gray_val = (r * PIXPAPER_LUMA_R_WEIGHT +
> +				    g * PIXPAPER_LUMA_G_WEIGHT +
> +				    b * PIXPAPER_LUMA_B_WEIGHT +
> +				    PIXPAPER_LUMA_ROUNDING_BIAS) /
> +				   PIXPAPER_LUMA_DIVISOR;
> +			bit = gray_val >= PIXPAPER_PIXEL_THRESHOLD;
> +
> +			if (bit)
> +				dst_row[byte_pos] |= BIT(7 - bit_pos);
> +			else
> +				dst_row[byte_pos] &= ~BIT(7 - bit_pos);
> +		}
> +	}
> +}
> +
> +static void *pixpaper_prepare_buffer(const void *vaddr,
> +				     const struct drm_framebuffer *fb,
> +				     u32 *dst_pitch,
> +				     struct pixpaper_error_ctx *err)
> +{
> +	void *dst;
> +
> +	if (err->errno_code)
> +		return NULL;
> +
> +	*dst_pitch = DIV_ROUND_UP(fb->width, 8);
> +	dst = kzalloc(*dst_pitch * fb->height, GFP_KERNEL);
> +	if (!dst) {
> +		err->errno_code = -ENOMEM;
> +		return NULL;
> +	}
> +
> +	pixpaper_xrgb8888_to_bw(vaddr, dst, fb->height, fb->width,
> +				fb->pitches[0], *dst_pitch);
> +
> +	return dst;
> +}
> +
> +static void pixpaper_write_image(struct pixpaper_panel *panel,
> +				 const u8 *buf, u32 len,
> +				 struct pixpaper_error_ctx *err)
> +{
> +	if (err->errno_code)
> +		return;
> +
> +	pixpaper_write_ram(panel, PIXPAPER_CMD_WRITE_RAM_BW, buf, len, err);
> +}
> +
> +static int pixpaper_panel_hw_init(struct pixpaper_panel *panel)
> +{
> +	struct pixpaper_error_ctx err = { .errno_code = 0 };
> +	u8 i;
> +
> +	gpiod_set_value_cansleep(panel->reset, 0);
> +	msleep(50);
> +	gpiod_set_value_cansleep(panel->reset, 1);
> +	msleep(50);
> +
> +	pixpaper_wait_for_panel(panel);
> +
> +	for (i = 0; i < ARRAY_SIZE(pixpaper_init_seqs); i++) {
> +		pixpaper_send_init_seq(panel, &pixpaper_init_seqs[i], &err);
> +		if (err.errno_code)
> +			goto init_fail;
> +	}
> +
> +	return 0;
> +
> +init_fail:
> +	drm_err(&panel->drm, "Hardware initialization failed (err=%d)\n",
> +		err.errno_code);
> +	return err.errno_code;
> +}
> +
> +static int pixpaper_plane_helper_atomic_check(struct drm_plane *plane,
> +					      struct drm_atomic_state *state)
> +{
> +	struct drm_plane_state *new_plane_state =
> +		drm_atomic_get_new_plane_state(state, plane);
> +	struct drm_crtc *new_crtc = new_plane_state->crtc;
> +	struct drm_crtc_state *new_crtc_state = NULL;
> +	int ret;
> +
> +	if (new_crtc)
> +		new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
> +
> +	ret = drm_atomic_helper_check_plane_state(new_plane_state,
> +						  new_crtc_state, DRM_PLANE_NO_SCALING,
> +						  DRM_PLANE_NO_SCALING, false, false);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int pixpaper_crtc_helper_atomic_check(struct drm_crtc *crtc,
> +					     struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *crtc_state =
> +		drm_atomic_get_new_crtc_state(state, crtc);
> +
> +	if (!crtc_state->enable)
> +		return 0;
> +
> +	return drm_atomic_helper_check_crtc_primary_plane(crtc_state);
> +}
> +
> +static void pixpaper_crtc_atomic_enable(struct drm_crtc *crtc,
> +					struct drm_atomic_state *state)
> +{
> +	struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> +	struct drm_device *drm = &panel->drm;
> +	int idx;
> +
> +	if (!drm_dev_enter(drm, &idx))
> +		return;
> +
> +	drm_dev_exit(idx);
Simply keep this helper empty.

> +}
> +
> +static void pixpaper_crtc_atomic_disable(struct drm_crtc *crtc,
> +					 struct drm_atomic_state *state)
> +{
> +	struct pixpaper_panel *panel = to_pixpaper_panel(crtc->dev);
> +	struct drm_device *drm = &panel->drm;
> +	int idx;
> +
> +	if (!drm_dev_enter(drm, &idx))
> +		return;
> +
> +	drm_dev_exit(idx);

Simply keep this helper empty.

> +}
> +
> +static void pixpaper_plane_atomic_update(struct drm_plane *plane,
> +					 struct drm_atomic_state *state)
> +{
> +	struct drm_plane_state *plane_state =
> +		drm_atomic_get_new_plane_state(state, plane);
> +	struct drm_shadow_plane_state *shadow_plane_state =
> +		to_drm_shadow_plane_state(plane_state);
> +	struct pixpaper_panel *panel = to_pixpaper_panel(plane->dev);
> +
> +	if (!plane_state->crtc || !plane_state->fb || !plane_state->visible)
> +		return;
> +
> +	{

No such blocks, please.

> +		struct drm_device *drm = &panel->drm;
> +		struct drm_framebuffer *fb = plane_state->fb;
> +		struct iosys_map map = shadow_plane_state->data[0];
> +		const void *vaddr = map.vaddr;
> +		int idx;
> +		struct pixpaper_error_ctx err = { .errno_code = 0 };
> +		uint32_t dst_pitch;
> +		void *dst = NULL;
> +		u32 dst_len;
> +
> +		if (!drm_dev_enter(drm, &idx))
> +			return;
> +
> +		if (fb->format->format != DRM_FORMAT_XRGB8888) {

No need to do a format check. The DRM framework checks this for you.

> +			err.errno_code = -EINVAL;
> +			drm_err_once(drm, "Unsupported framebuffer format: 0x%08x\n",
> +				    fb->format->format);
> +			goto update_cleanup;
> +		}
> +
> +		dst = pixpaper_prepare_buffer(vaddr, fb, &dst_pitch, &err);
> +		if (err.errno_code) {
> +			drm_err_once(drm, "Failed to allocate temporary buffer\n");
> +			goto update_cleanup;
> +		}

This call allocates memory. atomic_update is not a good place to do that.

While you operate on the framebuffer memory, you need to use 
drm_gem_fb_begin_cpu_access() and drm_gem_fb_end_cpu_access(). 
Otherwise, another DMA and other drivers can mess around with your 
buffer content.  See the other drivers for examples.

> +
> +		dst_len = dst_pitch * fb->height;
> +		pixpaper_write_image(panel, dst, dst_len, &err);
> +		if (err.errno_code)
> +			goto update_cleanup;

And this call always writes out the full image, I think.  Is it possible 
to use damage clipping like the other drivers do?

> +
> +		pixpaper_trigger_update(panel, &err);
> +		if (err.errno_code)
> +			goto update_cleanup;
> +update_cleanup:
> +		if (err.errno_code && err.errno_code != -ETIMEDOUT)
> +			drm_err_once(drm, "Frame update failed: %d\n",
> +				     err.errno_code);
> +
> +		kfree(dst);

And here you free the allocated memory. There should never be a reason 
to allocate more than 800x400 bits. I think you can pre-allocate the 
buffer in the main device structure and re-use it on each transfer.

Best regards
Thomas

> +		drm_dev_exit(idx);
> +	}
> +}
> +
> +static const struct drm_display_mode pixpaper_mode = {
> +	.clock = PIXPAPER_MODE_CLOCK_KHZ,
> +	.hdisplay = PIXPAPER_WIDTH,
> +	.hsync_start = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH,
> +	.hsync_end = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN,
> +	.htotal = PIXPAPER_WIDTH + PIXPAPER_HFRONT_PORCH + PIXPAPER_HSYNC_LEN +
> +		  PIXPAPER_HBACK_PORCH,
> +	.vdisplay = PIXPAPER_HEIGHT,
> +	.vsync_start = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH,
> +	.vsync_end = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN,
> +	.vtotal = PIXPAPER_HEIGHT + PIXPAPER_VFRONT_PORCH + PIXPAPER_VSYNC_LEN +
> +		  PIXPAPER_VBACK_PORCH,
> +	.width_mm = PIXPAPER_WIDTH_MM,
> +	.height_mm = PIXPAPER_HEIGHT_MM,
> +	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
> +};
> +
> +static int pixpaper_connector_get_modes(struct drm_connector *connector)
> +{
> +	return drm_connector_helper_get_modes_fixed(connector, &pixpaper_mode);
> +}
> +
> +static enum drm_mode_status
> +pixpaper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> +	if (mode->hdisplay == PIXPAPER_WIDTH &&
> +	    mode->vdisplay == PIXPAPER_HEIGHT)
> +		return MODE_OK;
> +
> +	return MODE_BAD;
> +}
> +
> +static const struct drm_plane_funcs pixpaper_plane_funcs = {
> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = drm_plane_cleanup,
> +	DRM_GEM_SHADOW_PLANE_FUNCS,
> +};
> +
> +static const struct drm_plane_helper_funcs pixpaper_plane_helper_funcs = {
> +	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
> +	.atomic_check = pixpaper_plane_helper_atomic_check,
> +	.atomic_update = pixpaper_plane_atomic_update,
> +};
> +
> +static const struct drm_crtc_funcs pixpaper_crtc_funcs = {
> +	.set_config = drm_atomic_helper_set_config,
> +	.page_flip = drm_atomic_helper_page_flip,
> +	.reset = drm_atomic_helper_crtc_reset,
> +	.destroy = drm_crtc_cleanup,
> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +};
> +
> +static const struct drm_crtc_helper_funcs pixpaper_crtc_helper_funcs = {
> +	.mode_valid = pixpaper_mode_valid,
> +	.atomic_check = pixpaper_crtc_helper_atomic_check,
> +	.atomic_enable = pixpaper_crtc_atomic_enable,
> +	.atomic_disable = pixpaper_crtc_atomic_disable,
> +};
> +
> +static const struct drm_encoder_funcs pixpaper_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static const struct drm_connector_funcs pixpaper_connector_funcs = {
> +	.reset = drm_atomic_helper_connector_reset,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_connector_cleanup,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs pixpaper_connector_helper_funcs = {
> +	.get_modes = pixpaper_connector_get_modes,
> +};
> +
> +DEFINE_DRM_GEM_FOPS(pixpaper_fops);
> +
> +static struct drm_driver pixpaper_drm_driver = {
> +	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> +	.fops = &pixpaper_fops,
> +	.name = "pixpaper-426m",
> +	.desc = "DRM driver for PIXPAPER 4.26 monochrome e-ink panel",
> +	.major = 1,
> +	.minor = 0,
> +	DRM_GEM_SHMEM_DRIVER_OPS,
> +	DRM_FBDEV_SHMEM_DRIVER_OPS,
> +};
> +
> +static const struct drm_mode_config_funcs pixpaper_mode_config_funcs = {
> +	.fb_create = drm_gem_fb_create_with_dirty,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int pixpaper_probe(struct spi_device *spi)
> +{
> +	struct device *dev = &spi->dev;
> +	struct pixpaper_panel *panel;
> +	struct drm_device *drm;
> +	int ret;
> +
> +	panel = devm_drm_dev_alloc(dev, &pixpaper_drm_driver,
> +				   struct pixpaper_panel, drm);
> +	if (IS_ERR(panel))
> +		return PTR_ERR(panel);
> +
> +	drm = &panel->drm;
> +	panel->spi = spi;
> +	spi_set_drvdata(spi, panel);
> +
> +	panel->tx_buf = devm_kzalloc(dev, PIXPAPER_TX_BUF_SIZE, GFP_KERNEL);
> +	if (!panel->tx_buf)
> +		return -ENOMEM;
> +
> +	ret = drmm_mode_config_init(drm);
> +	if (ret)
> +		return ret;
> +
> +	spi->mode = SPI_MODE_0;
> +	spi->bits_per_word = PIXPAPER_SPI_BITS_PER_WORD;
> +
> +	if (!spi->max_speed_hz) {
> +		drm_warn(drm,
> +			 "spi-max-frequency not specified in DT, using default %u Hz\n",
> +			 PIXPAPER_SPI_SPEED_DEFAULT);
> +		spi->max_speed_hz = PIXPAPER_SPI_SPEED_DEFAULT;
> +	}
> +
> +	ret = spi_setup(spi);
> +	if (ret < 0) {
> +		drm_err(drm, "SPI setup failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (!dev->dma_mask)
> +		dev->dma_mask = &dev->coherent_dma_mask;
> +	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
> +	if (ret) {
> +		drm_err(drm, "Failed to set DMA mask: %d\n", ret);
> +		return ret;
> +	}
> +
> +	panel->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(panel->reset))
> +		return PTR_ERR(panel->reset);
> +
> +	panel->busy = devm_gpiod_get(dev, "busy", GPIOD_IN);
> +	if (IS_ERR(panel->busy))
> +		return PTR_ERR(panel->busy);
> +
> +	panel->dc = devm_gpiod_get(dev, "dc", GPIOD_OUT_HIGH);
> +	if (IS_ERR(panel->dc))
> +		return PTR_ERR(panel->dc);
> +
> +	ret = pixpaper_panel_hw_init(panel);
> +	if (ret) {
> +		drm_err(drm, "Panel hardware initialization failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	drm->mode_config.funcs = &pixpaper_mode_config_funcs;
> +	drm->mode_config.min_width = PIXPAPER_WIDTH;
> +	drm->mode_config.max_width = PIXPAPER_WIDTH;
> +	drm->mode_config.min_height = PIXPAPER_HEIGHT;
> +	drm->mode_config.max_height = PIXPAPER_HEIGHT;
> +
> +	ret = drm_universal_plane_init(drm, &panel->plane, 1, &pixpaper_plane_funcs,
> +				       pixpaper_formats, ARRAY_SIZE(pixpaper_formats), NULL,
> +				       DRM_PLANE_TYPE_PRIMARY, NULL);
> +	if (ret)
> +		return ret;
> +	drm_plane_helper_add(&panel->plane, &pixpaper_plane_helper_funcs);
> +
> +	ret = drm_crtc_init_with_planes(drm, &panel->crtc, &panel->plane, NULL,
> +					&pixpaper_crtc_funcs, NULL);
> +	if (ret)
> +		return ret;
> +	drm_crtc_helper_add(&panel->crtc, &pixpaper_crtc_helper_funcs);
> +
> +	ret = drm_encoder_init(drm, &panel->encoder, &pixpaper_encoder_funcs,
> +			       DRM_MODE_ENCODER_NONE, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_connector_init(drm, &panel->connector,
> +				 &pixpaper_connector_funcs,
> +				 DRM_MODE_CONNECTOR_SPI);
> +	if (ret)
> +		return ret;
> +
> +	drm_connector_helper_add(&panel->connector,
> +				 &pixpaper_connector_helper_funcs);
> +	drm_connector_attach_encoder(&panel->connector, &panel->encoder);
> +	panel->encoder.possible_crtcs = drm_crtc_mask(&panel->crtc);
> +
> +	drm_mode_config_reset(drm);
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret)
> +		return ret;
> +
> +	drm_client_setup(drm, NULL);
> +
> +	return 0;
> +}
> +
> +static void pixpaper_remove(struct spi_device *spi)
> +{
> +	struct pixpaper_panel *panel = spi_get_drvdata(spi);
> +
> +	if (!panel)
> +		return;
> +
> +	drm_dev_unplug(&panel->drm);
> +	drm_atomic_helper_shutdown(&panel->drm);
> +}
> +
> +static const struct spi_device_id pixpaper_ids[] = { { "pixpaper-426m", 0 }, {} };
> +MODULE_DEVICE_TABLE(spi, pixpaper_ids);
> +
> +static const struct of_device_id pixpaper_dt_ids[] = {
> +	{ .compatible = "mayqueen,pixpaper-426m" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pixpaper_dt_ids);
> +
> +static struct spi_driver pixpaper_spi_driver = {
> +	.driver = {
> +		.name = "pixpaper-426m",
> +		.of_match_table = pixpaper_dt_ids,
> +	},
> +	.id_table = pixpaper_ids,
> +	.probe = pixpaper_probe,
> +	.remove = pixpaper_remove,
> +};
> +
> +module_spi_driver(pixpaper_spi_driver);
> +
> +MODULE_AUTHOR("LiangCheng Wang");
> +MODULE_DESCRIPTION("DRM SPI driver for PIXPAPER 4.26 monochrome e-ink panel");
> +MODULE_LICENSE("GPL");
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)



  parent reply	other threads:[~2026-06-17 15:31 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-29 10:31 [PATCH v3 0/2] " LiangCheng Wang
2026-05-29 10:31 ` [PATCH v3 1/2] dt-bindings: display: mayqueen,pixpaper: add pixpaper-426m LiangCheng Wang
2026-05-29 10:31 ` [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel LiangCheng Wang
2026-06-10  2:10   ` LiangCheng Wang
2026-06-16  7:08   ` Devarsh Thakkar
2026-06-16  8:39     ` LiangCheng Wang
2026-06-16 15:08       ` Devarsh Thakkar
2026-06-17  2:09         ` LiangCheng Wang
2026-07-13 13:35           ` Devarsh Thakkar
2026-07-31  3:57             ` LiangCheng Wang
2026-09-16  8:29               ` LiangCheng Wang
2026-09-16  9:10                 ` Thomas Zimmermann
2026-09-16  9:44                   ` LiangCheng Wang
2026-09-16 11:40                     ` Thomas Zimmermann
2026-09-17  2:56                       ` LiangCheng Wang
2026-09-17 10:49                         ` Javier Martinez Canillas
2026-09-17 11:34                           ` Thomas Zimmermann
2026-09-17 12:15                             ` Javier Martinez Canillas
2026-09-18  4:55                               ` Thakkar, Devarsh
2026-09-18  6:25                                 ` LiangCheng Wang
2026-09-18 17:05                                   ` Devarsh Thakkar
2026-09-25 17:53                                     ` Thakkar, Devarsh
2026-06-17 15:31   ` Thomas Zimmermann [this message]
2026-06-18  2:33     ` LiangCheng Wang
2026-06-18  7:30       ` Thomas Zimmermann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9fb7915b-dc46-45af-bba1-a3d3a59b5e49@suse.de \
    --to=tzimmermann@suse.de \
    --cc=airlied@gmail.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=onlywig@gmail.com \
    --cc=robh@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=zaq14760@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®