* [PATCH v3 0/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
@ 2026-05-29 10:31 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
0 siblings, 2 replies; 9+ messages in thread
From: LiangCheng Wang @ 2026-05-29 10:31 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Wig Cheng
Cc: dri-devel, devicetree, linux-kernel, LiangCheng Wang,
Conor Dooley
This patch series adds support for the Mayqueen Pixpaper 4.26
monochrome e-ink display panel, controlled via SPI.
The series includes:
- Device tree binding updates for the Pixpaper 4.26 panel
- A DRM tiny driver implementation for the Pixpaper 4.26 panel
- A MAINTAINERS update for the Pixpaper DRM drivers and binding
The panel supports 800x480 resolution with XRGB8888 framebuffer
input and uses SPI, along with GPIO lines for reset, busy, and
data/command control.
Tested on:
- Raspberry Pi 5 with Linux kernel 7.1.0-rc1
Feedback is welcome.
Signed-off-by: LiangCheng Wang <zaq14760@gmail.com>
---
Changes in v3:
- Keep Conor's Acked-by on the DT binding patch.
- Avoid passing stack and read-only buffers to spi_write().
- Use le32_to_cpu() when reading XRGB8888 pixels.
- Document the panel RAM X direction used during framebuffer conversion.
- Document why busy-wait timeouts remain warning-only.
- Rename the busy-wait helper to pixpaper_wait_for_panel().
- Drop the forward declaration of pixpaper_xrgb8888_to_bw() by moving its
definition before its first use.
- Use a fixed display mode with drm_connector_helper_get_modes_fixed().
- Drop the redundant mode_config mode_valid callback; resolution is
validated only by the CRTC mode_valid callback.
- Use drm_err_once() for errors in userspace-triggered update paths.
- Link to v2: https://lore.kernel.org/r/20260526-bar-v2-0-c66df9a840c4@gmail.com
Changes in v2:
- Explain why pixpaper-426m requires a distinct compatible string despite
sharing the same SPI and GPIO properties with the existing Pixpaper panel.
- Drop the duplicated pixpaper-426m DT binding example.
- Update the binding description for multiple Pixpaper panels.
- Select DRM_GEM_SHMEM_HELPER instead of DRM_GEM_DMA_HELPER for the
pixpaper-426m driver.
- Link to v1: https://lore.kernel.org/r/20260506-bar-v1-0-12195406f4ef@gmail.com
---
LiangCheng Wang (2):
dt-bindings: display: mayqueen,pixpaper: add pixpaper-426m
drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel
.../bindings/display/mayqueen,pixpaper.yaml | 13 +-
MAINTAINERS | 3 +-
drivers/gpu/drm/tiny/Kconfig | 16 +
drivers/gpu/drm/tiny/Makefile | 1 +
drivers/gpu/drm/tiny/pixpaper-426m.c | 817 +++++++++++++++++++++
5 files changed, 844 insertions(+), 6 deletions(-)
---
base-commit: a293ec25d59dd96309058c70df5a4dd0f889a1e4
change-id: 20260505-bar-523f2c3f6939
Best regards,
--
LiangCheng Wang <zaq14760@gmail.com>
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v3 1/2] dt-bindings: display: mayqueen,pixpaper: add pixpaper-426m 2026-05-29 10:31 [PATCH v3 0/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel LiangCheng Wang @ 2026-05-29 10:31 ` 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 1 sibling, 0 replies; 9+ messages in thread From: LiangCheng Wang @ 2026-05-29 10:31 UTC (permalink / raw) To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, LiangCheng Wang, Conor Dooley Add the compatible string for the PIXPAPER 4.26 monochrome e-ink panel to the Mayqueen Pixpaper binding. The new panel uses the same SPI and GPIO properties as the existing Pixpaper panel, but it is not software-compatible with it. The 4.26-inch panel requires different panel-specific initialization and update command sequences, so use a distinct compatible string. Document the new compatible string and update the binding description accordingly. Signed-off-by: LiangCheng Wang <zaq14760@gmail.com> Acked-by: Conor Dooley <conor.dooley@microchip.com> --- .../devicetree/bindings/display/mayqueen,pixpaper.yaml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml b/Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml index cd27f8ba5ae1d94660818525b5fa71db98c8acb7..68a6157114604a8308259191e55d1e2d15d76c11 100644 --- a/Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml +++ b/Documentation/devicetree/bindings/display/mayqueen,pixpaper.yaml @@ -4,22 +4,25 @@ $id: http://devicetree.org/schemas/display/mayqueen,pixpaper.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Mayqueen Pixpaper e-ink display panel +title: Mayqueen Pixpaper e-ink display panels maintainers: - LiangCheng Wang <zaq14760@gmail.com> description: - The Pixpaper is an e-ink display panel controlled via an SPI interface. - The panel has a resolution of 122x250 pixels and requires GPIO pins for - reset, busy, and data/command control. + Mayqueen Pixpaper e-ink display panels are controlled via an SPI interface + and require GPIO pins for reset, busy, and data/command control. Different + panel models use model-specific command sequences selected via their + compatible strings. allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# properties: compatible: - const: mayqueen,pixpaper + enum: + - mayqueen,pixpaper + - mayqueen,pixpaper-426m reg: maxItems: 1 -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-05-29 10:31 [PATCH v3 0/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 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 ` LiangCheng Wang 2026-05-29 10:54 ` sashiko-bot ` (2 more replies) 1 sibling, 3 replies; 9+ messages in thread From: LiangCheng Wang @ 2026-05-29 10:31 UTC (permalink / raw) To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, 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 + +/* + * 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) + +#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) +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) +{ + 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); +} + +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); +} + +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; + + { + 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) { + 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; + } + + dst_len = dst_pitch * fb->height; + pixpaper_write_image(panel, dst, dst_len, &err); + if (err.errno_code) + goto update_cleanup; + + 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); + 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"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-05-29 10:31 ` [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel LiangCheng Wang @ 2026-05-29 10:54 ` sashiko-bot 2026-06-10 2:10 ` LiangCheng Wang 2026-06-16 7:08 ` Devarsh Thakkar 2 siblings, 0 replies; 9+ messages in thread From: sashiko-bot @ 2026-05-29 10:54 UTC (permalink / raw) To: LiangCheng Wang; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] The `pixpaper_plane_atomic_update` function executes blocking operations and sleeps, which violates DRM atomic context constraints. -- commit 430b1d964958c07f88c16103075446b46aaf5cb1 Author: LiangCheng Wang <zaq14760@gmail.com> drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 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. > diff --git a/drivers/gpu/drm/tiny/pixpaper-426m.c b/drivers/gpu/drm/tiny/pixpaper-426m.c > new file mode 100644 > --- /dev/null > +++ b/drivers/gpu/drm/tiny/pixpaper-426m.c [ ... ] > +static void pixpaper_plane_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ [ ... ] > + 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; > + } > + > + dst_len = dst_pitch * fb->height; > + pixpaper_write_image(panel, dst, dst_len, &err); > + if (err.errno_code) > + goto update_cleanup; > + > + pixpaper_trigger_update(panel, &err); [Severity: High] Does this code sleep in an atomic context? According to the DRM subsystem guidelines, plane atomic update callbacks execute in atomic context where sleeping is strictly forbidden. This function calls pixpaper_prepare_buffer(), which performs a sleeping memory allocation: pixpaper_prepare_buffer() kzalloc(..., GFP_KERNEL); It also drives the SPI panel update through pixpaper_write_image() and pixpaper_trigger_update(). These functions internally invoke blocking operations like usleep_range(), spi_write(), and gpiod_get_value_cansleep(). Furthermore, pixpaper_wait_for_panel() polls the panel for up to 10 seconds using usleep_range(). Executing blocking functions from an atomic context will result in scheduling while atomic kernel panics. Should the hardware screen update logic be deferred to a worker thread, and memory allocations handled in the non-atomic prepare_fb callback? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260529-bar-v3-0-5c2ac1c751ee@gmail.com?part=2 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-05-29 10:31 ` [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel LiangCheng Wang 2026-05-29 10:54 ` sashiko-bot @ 2026-06-10 2:10 ` LiangCheng Wang 2026-06-16 7:08 ` Devarsh Thakkar 2 siblings, 0 replies; 9+ messages in thread From: LiangCheng Wang @ 2026-06-10 2:10 UTC (permalink / raw) To: tzimmermann Cc: dri-devel, maarten.lankhorst, mripard, airlied, simona, linux-kernel, conor+dt, krzk+dt, robh, devicetree, onlywig, zaq14760 Hi, A gentle ping for this driver patch. The DT binding patch has already received Conor's Acked-by, and I would appreciate any further comments on the driver patch. Thanks, LiangCheng ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-05-29 10:31 ` [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel LiangCheng Wang 2026-05-29 10:54 ` sashiko-bot 2026-06-10 2:10 ` LiangCheng Wang @ 2026-06-16 7:08 ` Devarsh Thakkar 2026-06-16 8:39 ` LiangCheng Wang 2 siblings, 1 reply; 9+ messages in thread From: Devarsh Thakkar @ 2026-06-16 7:08 UTC (permalink / raw) To: LiangCheng Wang, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen Hi LiangCheng, Thanks for the patch. On 29/05/26 16:01, LiangCheng Wang wrote: > 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. > 1) Could you please share the datasheet for the display controller used inside this pixpaper version ? The PIXPAPER 4.26 command set matches the Solomon SSD1683 almost exactly as documented in the SSD1683 datasheet [0]. It can also be seen that the macro values match exactly as well with the posted driver [1]. The only material difference is the ctrl2 update byte 0xF4 vs the SSD1683 documented sequences (0xFF for BW full refresh, 0xF7 for 3-color). 0xF4 omits the final Disable Analog + Disable OSC bits that SSD1683 normally expects. 2) Does 0xFF or 0xF7 mode work as well for your display or is it strictly 0xF4 which seems to mean that analog and osc bits are disabled ? 3) Also could you confirm which display controller IC does the PIXPAPER 4.26 use ? If it is SSD1683 (or any other ssd16xx variant), it would be appropriate to add this panel as a new display panel entry in panel-ssd16xx.c rather than a separate driver to avoid code duplication. You can refer how "Good Display GDEY042T81" was added for e.g. it should be something like : [PIXPAPER426M] = { .data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC, .driver_output_ctrl_byte3 = 0x02, /* SM=1 interlaced scan */ .default_refresh_mode = SSD16XX_REFRESH_FULL, /* single mode: 0xF4 */ .default_border_waveform_init = SSD16XX_BORDER_LUT1, .default_border_waveform_update = SSD16XX_BORDER_LUT1, .default_clear_on_init = -1, .default_clear_on_disable = -1, .red_supported = false, .mode = &pixpaper426m_mode, }, and a new compatible entry : static const struct of_device_id ssd16xx_of_match[] = { { .compatible = "gooddisplay,gdey042t81", .data = (void *)GDEY042T81 }, { .compatible = "mayqueen,pixpaper-426m", .data = (void *)PIXPAPER426M }, { } }; [0] : https://www.crystalfontz.com/controllers/SolomonSystech/SSD1683 [1] : https://lore.kernel.org/all/20260430183311.2978142-4-devarsht@ti.com/ Kindly let me know if any queries. Regards Devarsh > 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 > + > +/* > + * 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) > + > +#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) > +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) > +{ > + 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); > +} > + > +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); > +} > + > +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; > + > + { > + 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) { > + 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; > + } > + > + dst_len = dst_pitch * fb->height; > + pixpaper_write_image(panel, dst, dst_len, &err); > + if (err.errno_code) > + goto update_cleanup; > + > + 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); > + 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"); > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-06-16 7:08 ` Devarsh Thakkar @ 2026-06-16 8:39 ` LiangCheng Wang 2026-06-16 15:08 ` Devarsh Thakkar 0 siblings, 1 reply; 9+ messages in thread From: LiangCheng Wang @ 2026-06-16 8:39 UTC (permalink / raw) To: Devarsh Thakkar, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen, LiangCheng Wang Hi Devarsh, Thanks for the detailed review. > 1) Could you please share the datasheet for the display controller used > inside this pixpaper version ? Unfortunately I'm not able to share the controller datasheet publicly; it was provided to us under NDA. > 2) Does 0xFF or 0xF7 mode work as well for your display or is it strictly > 0xF4 which seems to mean that analog and osc bits are disabled ? I'll test whether the standard 0xF7/0xFF sequences also work on this panel and follow up with the result. > 3) Also could you confirm which display controller IC does the PIXPAPER > 4.26 use ? The PIXPAPER 4.26 uses the Solomon SSD1677, so you're right that it is an SSD16xx-family controller -- the commands it uses (0x01, 0x0C, 0x18, 0x20, 0x22, 0x24, 0x3C, 0x44/0x45, 0x4E/0x4F) match the standard SSD16xx set. > it would be appropriate to add this panel as a new display panel entry in > panel-ssd16xx.c rather than a separate driver to avoid code duplication. I agree that consolidating SSD16xx panels under panel-ssd16xx.c is the right long-term direction, and I'd be glad to converge there. I'm not yet sure how SSD1677 would fit with the controllers your series currently targets (SSD1673/SSD1680/SSD1681/SSD1683) -- ours is a larger 4.26" 800x480 panel, so I suspect it may need a new controller variant. Would you expect panel-ssd16xx.c to be able to support SSD1677? In the meantime, would it be reasonable to take this smaller standalone driver, and migrate the panel into panel-ssd16xx.c once that driver lands with SSD1677 support? I'd be happy to help with the migration, and of course I'll defer to your and the maintainers' preference here. Regards, LiangCheng ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-06-16 8:39 ` LiangCheng Wang @ 2026-06-16 15:08 ` Devarsh Thakkar 2026-06-17 2:09 ` LiangCheng Wang 0 siblings, 1 reply; 9+ messages in thread From: Devarsh Thakkar @ 2026-06-16 15:08 UTC (permalink / raw) To: LiangCheng Wang, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen Hi LiangCheng, On 16/06/26 14:09, LiangCheng Wang wrote: > Hi Devarsh, > > Thanks for the detailed review. > >> 1) Could you please share the datasheet for the display controller used >> inside this pixpaper version ? > > Unfortunately I'm not able to share the controller datasheet publicly; it > was provided to us under NDA. > >> 2) Does 0xFF or 0xF7 mode work as well for your display or is it strictly >> 0xF4 which seems to mean that analog and osc bits are disabled ? > > I'll test whether the standard 0xF7/0xFF sequences also work on this panel > and follow up with the result. > >> 3) Also could you confirm which display controller IC does the PIXPAPER >> 4.26 use ? > > The PIXPAPER 4.26 uses the Solomon SSD1677, so you're right that it is an > SSD16xx-family controller -- the commands it uses (0x01, 0x0C, 0x18, 0x20, > 0x22, 0x24, 0x3C, 0x44/0x45, 0x4E/0x4F) match the standard SSD16xx set. > >> it would be appropriate to add this panel as a new display panel entry in >> panel-ssd16xx.c rather than a separate driver to avoid code duplication. > > I agree that consolidating SSD16xx panels under panel-ssd16xx.c is the right > long-term direction, and I'd be glad to converge there. > > I'm not yet sure how SSD1677 would fit with the controllers your series > currently targets (SSD1673/SSD1680/SSD1681/SSD1683) Thanks for sharing this information, yes it makes sense now that it is using ssd1677.I have the SSD1677 datasheet and I think there should be minimal change in the driver to support this controller, I will be adding that in v2. -- ours is a larger > 4.26" 800x480 panel, so I suspect it may need a new controller variant. > Would you expect panel-ssd16xx.c to be able to support SSD1677? > Yes, I will be adding SSD1677 controller support in V2 of my series, hopefully that should help and after that it's just a matter of adding panel entry for your pixpaper panel. I can share you my branch with ssd1677 support once I have it ready. Also, I don't have PIXPAPER 4.26 panel but if you want I can share you my branch having ssd1677 support and additionally I can quickly add boilerplate pixpaper 4.26 panel entries on top of my V2 series referring from your patch so that it switches to using standard ssd16xx commands and ssd1677 quirks wherever necessary and you can then validate and modify. > In the meantime, would it be reasonable to take this smaller standalone > driver, and migrate the panel into panel-ssd16xx.c once that driver lands > with SSD1677 support? I'd be happy to help with the migration, and of course > I'll defer to your and the maintainers' preference here. > I think it makes more sense to use already posted unified ssd16xx driver which already supports standard controller flow for the ssd16xx family with additional controller/panel specific quirks and has interface to support different panels thus avoiding massive code duplication and leveraging already developed functionalities. Regards Devarsh > Regards, > LiangCheng ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 2/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 2026-06-16 15:08 ` Devarsh Thakkar @ 2026-06-17 2:09 ` LiangCheng Wang 0 siblings, 0 replies; 9+ messages in thread From: LiangCheng Wang @ 2026-06-17 2:09 UTC (permalink / raw) To: Devarsh Thakkar, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Wig Cheng Cc: dri-devel, devicetree, linux-kernel, Tomi Valkeinen, LiangCheng Wang Hi Devarsh, Thanks, that sounds great -- let's converge on panel-ssd16xx.c. > Yes, I will be adding SSD1677 controller support in V2 of my series, > hopefully that should help and after that it's just a matter of adding > panel entry for your pixpaper panel. I can share you my branch with > ssd1677 support once I have it ready. That would be very helpful, please do share the branch once it's ready. I have the PIXPAPER 4.26 hardware here, so I can validate the SSD1677 support and the panel on real hardware and report back any quirks. > additionally I can quickly add boilerplate pixpaper 4.26 panel entries on > top of my V2 series referring from your patch so that it switches to using > standard ssd16xx commands and ssd1677 quirks wherever necessary and you can > then validate and modify. Thanks for offering. If it's alright with you, I'd like to take the pixpaper-426m panel entry through review myself on top of your series, using your boilerplate as a starting point, since I can carry the validation on real hardware. I'll credit your help with Co-developed-by/Suggested-by as appropriate. Happy to arrange it whichever way is easiest for you. I'll also follow up with the 0xF7/0xFF test result so we can capture the correct SSD1677 update-sequence quirk. Regards, LiangCheng ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-06-17 2:09 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-29 10:31 [PATCH v3 0/2] drm/tiny: add support for PIXPAPER 4.26 monochrome e-ink panel 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-05-29 10:54 ` sashiko-bot 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
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox