Devicetree
 help / color / mirror / Atom feed
* [PATCH v5 0/2] drm/tiny: Add RAiO RA8875 display controller driver support
@ 2026-10-30  8:37 Adam Azuddin
  2026-10-30  8:37 ` [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
  2026-10-30  8:37 ` [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
  0 siblings, 2 replies; 4+ messages in thread
From: Adam Azuddin @ 2026-10-30  8:37 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, neil.armstrong, maarten.lankhorst,
	mripard, tzimmermann
  Cc: jesszhan0024, airlied, simona, andersson, dri-devel, devicetree,
	linux-kernel, azuddinadam

This series adds a new drm/tiny driver and matching dt-binding for the
RAiO RA8875 SPI display controller. An fbtft driver for this chip
currently exists at drivers/staging/fbtft/fb_ra8875.c, but fbtft is a
legacy fbdev-based staging driver, and nothing in mainline DRM
currently supports this controller.

This driver does not use drm_mipi_dbi. The RA8875 SPI protocol is
structurally incompatible with both Type C options drm_mipi_dbi supports.
Type C1 encodes command/data as a 9th bit within a 9-bit SPI word. The
RA8875 uses a full prefix byte (0x80 = command write, 0x00 = data write,
0x40 = data read, 0xC0 = status read) sent as a separate 8-bit transaction.
These are not the same thing and cannot be mapped to each other.
Type C3 models two transfer types via a D/CX GPIO. The RA8875 has four
transfer cycle types and no D/CX pin.

My ER-TFTM050-3 board has RST tied NC, and VDD is a fixed supply
set via an on-board solder jumper rather than software-controllable,
so I couldn't test reset-gpios/vdd-supply myself, but added them
as optional/required properties respectively per review feedback
for boards that do need them.

Tested on a Raspberry Pi 3B connected over SPI to an East Rising
ER-TFTM050-3, a 5" 800x480 panel driven by the RA8875 in Linux 7.3.0-rc4.

Based on drm-misc-next commit 73df79cceb8c ("drm/virtio: add ARGB8888 support for primary planes")

Link to v1: https://lore.kernel.org/all/20260916175942.3186804-1-azuddinadam@gmail.com/
Link to v2: https://lore.kernel.org/all/20260924145400.1385815-1-azuddinadam@gmail.com/
Link to v3: https://lore.kernel.org/all/20261001144128.2336301-1-azuddinadam@gmail.com/
Link to v4: https://lore.kernel.org/all/20261012064518.3497285-1-azuddinadam@gmail.com/
---
Changes in v5:

Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml:
- Drop the "Add YAML binding schema..." paragraph from the
  commit message (Krzysztof)
- Add interrupts property for the INT# pin

drivers/gpu/drm/tiny/ra8875.c:
- Move bindings table to above ra8875_spi_driver definition

Changes in v4:

Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml:
- Drop the sentence in the description saying the binding covers only the
  SPI-connected device (Conor).
- Drop unused panel label

drivers/gpu/drm/tiny/ra8875.c:
- Catch error value from ra8875_write_pixels
- Use consistent include header format.
- Return if fb is null in atomic_update
- Drop unnecessary extra byte in pixel data spi write.

drivers/gpu/drm/tiny/Kconfig:
- Add depends on OF, which is caught by intel 0-day[3]

Changes in v3:

Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml:
- Add gpio.h include in example to pass dt_binding_check
- Remove spi-max-frequency as there is no reference in datasheet

drivers/gpu/drm/tiny/ra8875.c:
- Remove debug-only ra8875_read_reg function
- Chunk large SPI transfers to 16 rows per chunk
- Remove unnecessary includes and spi cs setups in probe
- Create ra8875_write_pixels function for clear_screen and fb_dirty
- Use 2 spi writes instead of 1 write with 2 transfers and cs_change in write_reg

Changes in v2:

Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml (review by Krzysztof Kozlowski):
- Add hardware description to the binding
- Drop the display-timings property, and instead require it in the required block
- Add a required vdd-supply and an optional reset gpio property
- Rename the example node to a more generic "panel: display@0"

drivers/gpu/drm/tiny/Makefile and Kconfig:
- Remove TINYDRM prefix from the config name (Thomas Zimmermann [1])

drivers/gpu/drm/tiny/ra8875.c:
- Reformat device_id table to match current convention (Krzysztof Kozlowski)
- Drop unneeded linux/mod_devicetable.h include (Uwe Kleine-König)
- Use drm_dev_unplug() instead of drm_dev_unregister() in remove callback
- Move stack-allocated cmd/data buffers into ra8875_device struct
- Add mode_valid() callback to validate height/width against panel maximum
- Add optional reset gpio
- Use dev_err_probe() in probe function
- Remove connector detect callback
- Remove unnecessary |= on register write return value in hw_init
- Migrate from simple display pipe to manually-handled plane/crtc/encoder,
  following the simple-KMS-helper removal effort [2]
- Use defined constants instead of magic values where possible
- Use drm_atomic_helper_damage_merged() and drm_fb_swab() in plane update

[1] https://lore.kernel.org/all/fee2a743-d3e4-46b4-812f-05c3cc3a46ae@suse.de/
[2] https://lore.kernel.org/all/20260705-drm-simple-kms-removal-v1-0-b4e1ca053623@oss.qualcomm.com/
[3] https://lore.kernel.org/all/202610042347.idGisdDW-lkp@intel.com/



Adam Azuddin (2):
  dt-bindings: display: panel: Add RAiO RA8875 display controller
  drm/tiny: Add RAiO RA8875 display controller driver

 .../bindings/display/panel/raio,ra8875.yaml   |  87 +++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   6 +
 drivers/gpu/drm/tiny/Kconfig                  |  13 +
 drivers/gpu/drm/tiny/Makefile                 |   1 +
 drivers/gpu/drm/tiny/ra8875.c                 | 719 ++++++++++++++++++
 6 files changed, 828 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
 create mode 100644 drivers/gpu/drm/tiny/ra8875.c

-- 
2.55.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller
  2026-10-30  8:37 [PATCH v5 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
@ 2026-10-30  8:37 ` Adam Azuddin
  2026-10-30  8:37 ` [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
  1 sibling, 0 replies; 4+ messages in thread
From: Adam Azuddin @ 2026-10-30  8:37 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, neil.armstrong, maarten.lankhorst,
	mripard, tzimmermann
  Cc: jesszhan0024, airlied, simona, andersson, dri-devel, devicetree,
	linux-kernel, azuddinadam

The RA8875 is an SPI-connected TFT display controller by RAiO
Technology Inc. It supports display resolutions of up to 800x480.

Assisted-by: LLM
Signed-off-by: Adam Azuddin <azuddinadam@gmail.com>
---
 .../bindings/display/panel/raio,ra8875.yaml   | 87 +++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
 2 files changed, 89 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml

diff --git a/Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml b/Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
new file mode 100644
index 000000000000..661398bda0ee
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/raio,ra8875.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: RAiO RA8875 Display Controller
+
+maintainers:
+  - Adam Azuddin <azuddinadam@gmail.com>
+
+description:
+  The RA8875 is a TFT LCD controller for panels up to 800x480 pixels.
+  It can be connected to the host via 8080/6800 parallel interface,
+  SPI or I2C.
+
+allOf:
+  - $ref: panel-common.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    const: raio,ra8875
+
+  reg:
+    maxItems: 1
+
+  vdd-supply:
+    description:
+      3.3 V supply for the IO pins, the oscillator IO pins, the ADC pins
+      and the internal LDO. The 1.8 V core rail is generated internally.
+
+  reset-gpios:
+    description:
+      GPIO connected to the active-low RST# pin. Optional, since the board
+      may instead use an RC reset circuit.
+    maxItems: 1
+
+  interrupts:
+    description:
+      INT# pin, active low. Signals RA8875 interrupt events such as touch
+      panel, key scan, DMA, BTE and font access completion. Optional, since
+      the host can poll the interrupt status register instead.
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - display-timings
+  - vdd-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        display@0 {
+            compatible = "raio,ra8875";
+            reg = <0>;
+            spi-max-frequency = <16000000>;
+            vdd-supply = <&vcc_3v3>;
+            reset-gpios = <&gpio 25 GPIO_ACTIVE_LOW>;
+            interrupt-parent = <&gpio>;
+            interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
+
+            display-timings {
+                native-mode = <&timing0>;
+                timing0: timing0 {
+                    clock-frequency = <25000000>;
+                    hactive = <800>;
+                    vactive = <480>;
+                    hfront-porch = <40>;
+                    hsync-len = <40>;
+                    hback-porch = <40>;
+                    vfront-porch = <10>;
+                    vsync-len = <10>;
+                    vback-porch = <20>;
+                };
+            };
+        };
+    };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index ba2002969373..99d5113fb035 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1395,6 +1395,8 @@ patternProperties:
     description: Radxa
   "^raidsonic,.*":
     description: RaidSonic Technology GmbH
+  "^raio,.*":
+    description: RAiO Technology Inc.
   "^ralink,.*":
     description: Mediatek/Ralink Technology Corp.
   "^ramtron,.*":
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver
  2026-10-30  8:37 [PATCH v5 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
  2026-10-30  8:37 ` [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
@ 2026-10-30  8:37 ` Adam Azuddin
  2026-10-30  9:09   ` sashiko-bot
  1 sibling, 1 reply; 4+ messages in thread
From: Adam Azuddin @ 2026-10-30  8:37 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, neil.armstrong, maarten.lankhorst,
	mripard, tzimmermann
  Cc: jesszhan0024, airlied, simona, andersson, dri-devel, devicetree,
	linux-kernel, azuddinadam

Add a DRM tiny driver for the RAiO RA8875 SPI-connected TFT display
controller. The driver supports display resolutions up to 800x480
and uses the GEM shmem helper for buffer management.

Assisted-by: LLM
Signed-off-by: Adam Azuddin <azuddinadam@gmail.com>
---
 MAINTAINERS                   |   6 +
 drivers/gpu/drm/tiny/Kconfig  |  13 +
 drivers/gpu/drm/tiny/Makefile |   1 +
 drivers/gpu/drm/tiny/ra8875.c | 719 ++++++++++++++++++++++++++++++++++
 4 files changed, 739 insertions(+)
 create mode 100644 drivers/gpu/drm/tiny/ra8875.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 89ea35a35c68..84d99be03f64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8450,6 +8450,12 @@ T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
 F:	drivers/gpu/drm/qxl/
 F:	include/uapi/drm/qxl_drm.h
 
+DRM DRIVER FOR RAIO RA8875 PANELS
+M:	Adam Azuddin <azuddinadam@gmail.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
+F:	drivers/gpu/drm/tiny/ra8875.c
+
 DRM DRIVER FOR RAYDIUM RM67191 PANELS
 M:	Robert Chiras <robert.chiras@nxp.com>
 S:	Maintained
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index f0e72d4b6a47..2f1c49e31dd6 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -98,6 +98,19 @@ config DRM_PIXPAPER
 
 	  If M is selected, the module will be built as pixpaper.ko.
 
+config DRM_RA8875
+	tristate "DRM support for RA8875 display panels"
+	depends on DRM && SPI && OF
+	select DRM_CLIENT_SELECTION
+	select DRM_KMS_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select VIDEOMODE_HELPERS
+	help
+	  DRM driver for the following RAiO RA8875 based LCD panels:
+	  * East Rising 5.00" TFT LCD (ER-TFTM050-3)
+
+	  If M is selected the module will be called ra8875.
+
 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 48d30bf6152f..ed4602097fbb 100644
--- a/drivers/gpu/drm/tiny/Makefile
+++ b/drivers/gpu/drm/tiny/Makefile
@@ -14,4 +14,5 @@ obj-$(CONFIG_TINYDRM_ILI9341)		+= ili9341.o
 obj-$(CONFIG_TINYDRM_ILI9486)		+= ili9486.o
 obj-$(CONFIG_TINYDRM_MI0283QT)		+= mi0283qt.o
 obj-$(CONFIG_TINYDRM_REPAPER)		+= repaper.o
+obj-$(CONFIG_DRM_RA8875)		+= ra8875.o
 obj-$(CONFIG_TINYDRM_SHARP_MEMORY)	+= sharp-memory.o
diff --git a/drivers/gpu/drm/tiny/ra8875.c b/drivers/gpu/drm/tiny/ra8875.c
new file mode 100644
index 000000000000..937bb7fdbd91
--- /dev/null
+++ b/drivers/gpu/drm/tiny/ra8875.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DRM driver for RAiO ra8875 controller
+ *
+ * Copyright 2026 Adam Azuddin <azuddinadam@gmail.com>
+ */
+
+#include <linux/gfp_types.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/iosys-map.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/string.h>
+#include <video/of_display_timing.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_shmem.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_mode.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
+
+#define RA8875_TXBUF_ROWS	 16
+#define RA8875_CMD_DATA_BUF_SIZE 2
+
+#define RA8875_RESET_WAIT_MS 10 /* datasheet min: 1 ms after RST# rises */
+#define RA8875_MAX_WIDTH     800
+#define RA8875_MAX_HEIGHT    480
+
+#define RA8875_DATAWRITE	 0x00
+#define RA8875_CMDWRITE		 0x80
+#define RA8875_CMDREAD		 0xC0
+#define RA8875_PWRR		 0x01
+#define RA8875_PWRR_DISPON	 BIT(7)
+#define RA8875_PWRR_DISPOFF	 0x00
+#define RA8875_PWRR_SOFTRST	 BIT(0)
+#define RA8875_MRWC		 0x02
+#define RA8875_PLLC1		 0x88
+#define RA8875_PLLC1_PLLDIVN_11	 0x0B
+#define RA8875_PLLC2		 0x89
+#define RA8875_PLLC2_DIVK_4	 0x02
+#define RA8875_SYSR		 0x10
+#define RA8875_SYSR_16BPP	 0x0C
+#define RA8875_PCSR		 0x04
+#define RA8875_PCSR_PCLK_INV	 BIT(7) /* PDAT at falling edge */
+#define RA8875_PCSR_2CLK	 0x01
+#define RA8875_HDWR		 0x14
+#define RA8875_HNDFTR		 0x15
+#define RA8875_HNDFTR_HNDFT(n)	 ((n) & GENMASK(3, 0)) /* fine tune, 2px/step */
+#define RA8875_HNDR		 0x16
+#define RA8875_HSTR		 0x17
+#define RA8875_HPWR		 0x18
+#define RA8875_VDHR0		 0x19
+#define RA8875_VDHR1		 0x1A
+#define RA8875_VNDR0		 0x1B
+#define RA8875_VNDR1		 0x1C
+#define RA8875_VSTR0		 0x1D
+#define RA8875_VSTR1		 0x1E
+#define RA8875_VPWR		 0x1F
+#define RA8875_HSAW0		 0x30
+#define RA8875_HSAW1		 0x31
+#define RA8875_VSAW0		 0x32
+#define RA8875_VSAW1		 0x33
+#define RA8875_HEAW0		 0x34
+#define RA8875_HEAW1		 0x35
+#define RA8875_VEAW0		 0x36
+#define RA8875_VEAW1		 0x37
+#define RA8875_MWCR0		 0x40
+#define RA8875_MWCR0_GFXMODE	 0x00
+#define RA8875_MWCR0_LRTD	 0x00
+#define RA8875_CURH0		 0x46
+#define RA8875_CURH1		 0x47
+#define RA8875_CURV0		 0x48
+#define RA8875_CURV1		 0x49
+#define RA8875_P1CR		 0x8A
+#define RA8875_P1CR_ENABLE	 BIT(7)
+#define RA8875_P1CR_DISABLE	 0x00
+#define RA8875_P1CR_SYS_CLK_DIV2 0x01
+#define RA8875_P1DCR		 0x8B
+#define RA8875_P1DCR_FULL	 0xFF
+#define RA8875_P1DCR_HALF	 0x80
+#define RA8875_P1DCR_MIN	 0x00
+
+DEFINE_DRM_GEM_FOPS(ra8875_fops);
+
+struct ra8875_device {
+	struct drm_device drm;
+	struct spi_device *spi;
+	struct gpio_desc *rst_gpio;
+	struct drm_crtc crtc;
+	struct drm_plane plane;
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+	struct drm_display_mode mode;
+	u8 cmd_buf[RA8875_CMD_DATA_BUF_SIZE];
+	u8 data_buf[RA8875_CMD_DATA_BUF_SIZE];
+	u8 *txbuf;
+};
+
+static const struct drm_mode_config_funcs ra8875_modeconfig_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_connector_funcs ra8875_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int ra8875_connector_get_modes(struct drm_connector *connector)
+{
+	struct ra8875_device *ra8875 =
+		container_of(connector, struct ra8875_device, connector);
+
+	return drm_connector_helper_get_modes_fixed(connector, &ra8875->mode);
+}
+
+static const struct drm_connector_helper_funcs ra8875_connector_helper_funcs = {
+	.get_modes = ra8875_connector_get_modes
+};
+
+static int ra8875_write_reg(struct ra8875_device *ra8875, u8 reg, u8 val)
+{
+	int ret;
+
+	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
+	ra8875->cmd_buf[1] = reg;
+	ret = spi_write(ra8875->spi, ra8875->cmd_buf, 2);
+	if (ret)
+		goto err;
+
+	ra8875->data_buf[0] = RA8875_DATAWRITE;
+	ra8875->data_buf[1] = val;
+	ret = spi_write(ra8875->spi, ra8875->data_buf, 2);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_err(&ra8875->spi->dev,
+		"write_reg(0x%02x, 0x%02x) failed: %d\n", reg, val, ret);
+	return ret;
+}
+
+static int ra8875_write_pixels(struct ra8875_device *ra8875, size_t len)
+{
+	int ret;
+
+	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
+	ra8875->cmd_buf[1] = RA8875_MRWC;
+	ret = spi_write(ra8875->spi, ra8875->cmd_buf, 2);
+	if (ret)
+		return ret;
+
+	return spi_write(ra8875->spi, ra8875->txbuf, len);
+}
+
+static int ra8875_hw_init(struct ra8875_device *ra8875,
+			  struct drm_display_mode *mode)
+{
+	u8 hdwr, hndr, hstr, hpwr, vpwr;
+	u16 vdhr, vndr, vstr;
+	int ret;
+
+	if (ra8875->rst_gpio) {
+		gpiod_set_value_cansleep(ra8875->rst_gpio, 1);
+		usleep_range(1000, 1200);
+		gpiod_set_value_cansleep(ra8875->rst_gpio, 0);
+		msleep(RA8875_RESET_WAIT_MS);
+	}
+
+	/* Soft Reset Sequence */
+	ret = ra8875_write_reg(ra8875, RA8875_PWRR, RA8875_PWRR_SOFTRST);
+	if (ret)
+		return ret;
+
+	usleep_range(1000, 2000);
+	ret = ra8875_write_reg(ra8875, RA8875_PWRR, 0);
+	if (ret)
+		return ret;
+
+	/* PLL */
+	ret = ra8875_write_reg(ra8875, RA8875_PLLC1, RA8875_PLLC1_PLLDIVN_11);
+	if (ret)
+		return ret;
+	usleep_range(200, 500);
+
+	ret = ra8875_write_reg(ra8875, RA8875_PLLC2, RA8875_PLLC2_DIVK_4);
+	if (ret)
+		return ret;
+	usleep_range(200, 500);
+
+	/* PCLK period = 2x System Clock period */
+	ret = ra8875_write_reg(ra8875, RA8875_PCSR, RA8875_PCSR_PCLK_INV | RA8875_PCSR_2CLK);
+	if (ret)
+		return ret;
+
+	/* 16bpp, 8-bit MCU interface */
+	ret = ra8875_write_reg(ra8875, RA8875_SYSR, RA8875_SYSR_16BPP);
+	if (ret)
+		return ret;
+	usleep_range(1000, 2000);
+
+	/* Graphics mode, left-to-right top-to-down write direction */
+	ret = ra8875_write_reg(ra8875, RA8875_MWCR0,
+			       RA8875_MWCR0_GFXMODE | RA8875_MWCR0_LRTD);
+	if (ret)
+		return ret;
+
+	/* Horizontal timing */
+	hdwr = (mode->hdisplay / 8) - 1;
+	hndr = ((mode->htotal - mode->hdisplay) / 8) - 1;
+	hstr = ((mode->hsync_start - mode->hdisplay) / 8) - 1;
+	hpwr = ((mode->hsync_end - mode->hsync_start) / 8) - 1;
+
+	ret = ra8875_write_reg(ra8875, RA8875_HDWR, hdwr);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_HNDFTR, RA8875_HNDFTR_HNDFT(0));
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_HNDR, hndr);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_HSTR, hstr);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_HPWR, hpwr);
+	if (ret)
+		return ret;
+
+	/* Vertical timing */
+	vdhr = mode->vdisplay - 1;
+	vndr = mode->vtotal - mode->vdisplay - 1;
+	vstr = mode->vsync_start - mode->vdisplay - 1;
+	vpwr = mode->vsync_end - mode->vsync_start - 1;
+
+	ret = ra8875_write_reg(ra8875, RA8875_VDHR0, vdhr & 0xFF);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VDHR1, (vdhr >> 8) & 0x01);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VNDR0, vndr & 0xFF);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VNDR1, (vndr >> 8) & 0x01);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VSTR0, vstr & 0xFF);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VSTR1, (vstr >> 8) & 0x01);
+	if (ret)
+		return ret;
+	ret = ra8875_write_reg(ra8875, RA8875_VPWR, vpwr);
+	if (ret)
+		return ret;
+
+	/* PWM1: enabled, PWM output, full brightness */
+	ret = ra8875_write_reg(ra8875, RA8875_P1CR,
+			 RA8875_P1CR_ENABLE | RA8875_P1CR_SYS_CLK_DIV2);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_P1DCR, RA8875_P1DCR_FULL);
+	if (ret)
+		return ret;
+	usleep_range(10000, 11000);
+
+	/* Display on */
+	ret = ra8875_write_reg(ra8875, RA8875_PWRR, RA8875_PWRR_DISPON);
+	if (ret)
+		return ret;
+	usleep_range(10000, 11000);
+
+	return 0;
+}
+
+static int ra8875_clear_screen(struct ra8875_device *ra8875,
+			       struct drm_display_mode *mode)
+{
+	int width = mode->hdisplay;
+	int height = mode->vdisplay;
+	int y, rows, ret;
+	u8 *txbuf = ra8875->txbuf;
+	int rows_per_chunk = RA8875_TXBUF_ROWS;
+
+	/* Set active window to full screen */
+	ret = ra8875_write_reg(ra8875, RA8875_HSAW0, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_HSAW1, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_VSAW0, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_VSAW1, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_HEAW0, (width - 1) & 0xFF);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_HEAW1, ((width - 1) >> 8) & 0x03);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_VEAW0, (height - 1) & 0xFF);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_VEAW1,
+			       ((height - 1) >> 8) & 0x01);
+	if (ret)
+		return ret;
+
+	/* Set cursor to 0,0 */
+	ret = ra8875_write_reg(ra8875, RA8875_CURH0, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_CURH1, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_CURV0, 0);
+	if (ret)
+		return ret;
+
+	ret = ra8875_write_reg(ra8875, RA8875_CURV1, 0);
+	if (ret)
+		return ret;
+
+	memset(txbuf, 0, 1 + rows_per_chunk * width * 2);
+	txbuf[0] = RA8875_DATAWRITE;
+
+	for (y = 0; y < height; y += rows) {
+		rows = min(rows_per_chunk, height - y);
+		ret = ra8875_write_pixels(ra8875, 1 + rows * width * 2);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void ra8875_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state)
+{
+	struct ra8875_device *ra8875 =
+		container_of(crtc->dev, struct ra8875_device, drm);
+	struct drm_crtc_state *crtc_state =
+		drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_display_mode *mode = &crtc_state->mode;
+	int ret;
+
+	ret = ra8875_hw_init(ra8875, mode);
+	if (ret) {
+		drm_err(&ra8875->drm, "Failed to initialize hardware\n");
+		return;
+	}
+
+	ret = ra8875_clear_screen(ra8875, mode);
+	if (ret) {
+		drm_err(&ra8875->drm, "Failed to clear screen\n");
+		return;
+	}
+}
+
+static void ra8875_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_commit *state)
+{
+	struct ra8875_device *ra8875 =
+		container_of(crtc->dev, struct ra8875_device, drm);
+
+	ra8875_write_reg(ra8875, RA8875_P1CR, RA8875_P1CR_DISABLE);
+	ra8875_write_reg(ra8875, RA8875_P1DCR, RA8875_P1DCR_MIN);
+
+	ra8875_write_reg(ra8875, RA8875_PWRR, RA8875_PWRR_DISPOFF);
+}
+
+static void ra8875_fb_dirty(struct ra8875_device *ra8875, struct iosys_map *src,
+			    struct drm_framebuffer *fb, struct drm_rect *rect)
+{
+	unsigned int width = drm_rect_width(rect);
+	int rows = 0;
+	struct iosys_map dst;
+	struct drm_format_conv_state fmtcnv_state = DRM_FORMAT_CONV_STATE_INIT;
+
+	if (ra8875_write_reg(ra8875, RA8875_HSAW0, rect->x1 & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_HSAW1, (rect->x1 >> 8) & 0x03))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_VSAW0, rect->y1 & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_VSAW1, (rect->y1 >> 8) & 0x01))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_HEAW0, (rect->x2 - 1) & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_HEAW1, ((rect->x2 - 1) >> 8) & 0x03))
+		goto err;
+
+	if (ra8875_write_reg(ra8875, RA8875_VEAW0, (rect->y2 - 1) & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_VEAW1, ((rect->y2 - 1) >> 8) & 0x01))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_CURH0, rect->x1 & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_CURH1, (rect->x1 >> 8) & 0x03))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_CURV0, rect->y1 & 0xFF))
+		goto err;
+	if (ra8875_write_reg(ra8875, RA8875_CURV1, (rect->y1 >> 8) & 0x01))
+		goto err;
+
+	for (int y = rect->y1; y < rect->y2; y += rows) {
+		struct drm_rect chunk;
+
+		rows = min(RA8875_TXBUF_ROWS, rect->y2 - y);
+		chunk = DRM_RECT_INIT(rect->x1, y, width, rows);
+
+		iosys_map_set_vaddr(&dst, ra8875->txbuf + 1);
+		drm_fb_swab(&dst, NULL, src, fb, &chunk, false, &fmtcnv_state);
+
+		if (ra8875_write_pixels(ra8875, 1 + rows * width * 2))
+			goto err;
+	}
+
+	drm_format_conv_state_release(&fmtcnv_state);
+	return;
+err:
+	drm_format_conv_state_release(&fmtcnv_state);
+	dev_err_once(fb->dev->dev, "Failed to update display\n");
+}
+
+static void ra8875_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state)
+{
+	struct ra8875_device *ra8875 = container_of(plane->dev, struct ra8875_device, drm);
+	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
+	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 drm_framebuffer *fb = plane_state->fb;
+	struct drm_rect rect;
+	struct iosys_map *map = &shadow_plane_state->data[0];
+	int idx;
+
+	if (!fb)
+		return;
+
+	if (!drm_dev_enter(&ra8875->drm, &idx))
+		return;
+
+	if (iosys_map_is_null(map)) {
+		drm_err(&ra8875->drm, "Shadow map is null\n");
+		goto exit;
+	}
+	if (drm_atomic_helper_damage_merged(old_plane_state, plane_state, &rect))
+		ra8875_fb_dirty(ra8875, map, fb, &rect);
+exit:
+	drm_dev_exit(idx);
+}
+
+static enum drm_mode_status ra8875_mode_valid(struct drm_crtc *crtc,
+					      const struct drm_display_mode *mode)
+{
+	if (mode->hdisplay % 8 != 0 || mode->hsync_start % 8 != 0 ||
+	    mode->hsync_end % 8 != 0 || mode->htotal % 8 != 0) {
+		drm_dbg_kms(crtc->dev,
+		   "mode timings must be 8-pixel aligned (hdisplay=%d hsync_start=%d hsync_end=%d htotal=%d)\n",
+			    mode->hdisplay, mode->hsync_start, mode->hsync_end, mode->htotal);
+		return MODE_H_ILLEGAL;
+	}
+	return MODE_OK;
+}
+
+static int ra8875_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state)
+{
+	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+	struct drm_crtc_state *crtc_state;
+
+	if (!new_plane_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, new_plane_state->crtc);
+	if (!crtc_state)
+		return -EINVAL;
+
+	return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+						   DRM_PLANE_NO_SCALING,
+						   DRM_PLANE_NO_SCALING,
+						   false, true);
+}
+
+static const u32 ra8875_formats[] = {
+	DRM_FORMAT_RGB565,
+};
+
+static const struct drm_encoder_funcs ra8875_encoder_funcs_cleanup = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static const struct drm_crtc_funcs ra8875_crtc_funcs = {
+	.atomic_create_state = drm_atomic_helper_crtc_create_state,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.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 ra8875_crtc_helper_funcs = {
+	.mode_valid = ra8875_mode_valid,
+	.atomic_enable = ra8875_crtc_atomic_enable,
+	.atomic_disable = ra8875_crtc_atomic_disable,
+};
+
+static const struct drm_plane_funcs ra8875_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 ra8875_plane_helper_funcs = {
+	.prepare_fb = drm_gem_plane_helper_prepare_fb,
+	.begin_fb_access = drm_gem_begin_shadow_fb_access,
+	.end_fb_access = drm_gem_end_shadow_fb_access,
+	.atomic_check = ra8875_plane_atomic_check,
+	.atomic_update = ra8875_plane_atomic_update,
+};
+
+static const struct drm_driver ra8875_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_FBDEV_SHMEM_DRIVER_OPS,
+	.fops = &ra8875_fops,
+	.name = "ra8875",
+	.desc = "RAiO RA8875",
+	.major = 1,
+	.minor = 0,
+};
+
+static int ra8875_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct ra8875_device *ra8875;
+	struct drm_connector *connector;
+	struct drm_device *drm;
+	struct videomode vm;
+	int ret;
+	size_t buf_size;
+
+	ra8875 = devm_drm_dev_alloc(dev, &ra8875_driver, struct ra8875_device,
+				    drm);
+
+	if (IS_ERR(ra8875))
+		return PTR_ERR(ra8875);
+
+	drm = &ra8875->drm;
+	connector = &ra8875->connector;
+
+	ra8875->rst_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(ra8875->rst_gpio))
+		return dev_err_probe(dev, PTR_ERR(ra8875->rst_gpio),
+			     "failed to get reset GPIO\n");
+
+	ra8875->spi = spi;
+
+	spi->mode = SPI_MODE_0;
+
+	ret = spi_setup(spi);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to setup SPI\n");
+
+	buf_size = 1 + RA8875_TXBUF_ROWS * RA8875_MAX_WIDTH * 2;
+	ra8875->txbuf = devm_kzalloc(&spi->dev, buf_size, GFP_KERNEL);
+	if (!ra8875->txbuf)
+		return -ENOMEM;
+
+	ret = of_get_videomode(dev->of_node, &vm, OF_USE_NATIVE_MODE);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get videomode\n");
+	drm_display_mode_from_videomode(&vm, &ra8875->mode);
+
+	if (!ra8875->mode.hdisplay || !ra8875->mode.vdisplay ||
+	    ra8875->mode.hdisplay > RA8875_MAX_WIDTH ||
+	    ra8875->mode.vdisplay > RA8875_MAX_HEIGHT)
+		return dev_err_probe(dev, -EINVAL,
+				"Invalid display size %ux%u from device tree\n",
+				     ra8875->mode.hdisplay, ra8875->mode.vdisplay);
+
+	ra8875->mode.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_set_name(&ra8875->mode);
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize mode config\n");
+
+	drm->mode_config.min_width = ra8875->mode.hdisplay;
+	drm->mode_config.max_width = ra8875->mode.hdisplay;
+	drm->mode_config.min_height = ra8875->mode.vdisplay;
+	drm->mode_config.max_height = ra8875->mode.vdisplay;
+
+	drm->mode_config.funcs = &ra8875_modeconfig_funcs;
+
+	drm_connector_helper_add(connector, &ra8875_connector_helper_funcs);
+	ret = drmm_connector_init(drm, connector, &ra8875_connector_funcs,
+				  DRM_MODE_CONNECTOR_SPI, NULL);
+
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to init connector:\n");
+
+	drm_plane_helper_add(&ra8875->plane, &ra8875_plane_helper_funcs);
+	ret = drm_universal_plane_init(drm, &ra8875->plane, 0,
+				       &ra8875_plane_funcs,
+				       ra8875_formats, ARRAY_SIZE(ra8875_formats),
+				       NULL,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize plane\n");
+
+	drm_crtc_helper_add(&ra8875->crtc, &ra8875_crtc_helper_funcs);
+	ret = drm_crtc_init_with_planes(drm, &ra8875->crtc, &ra8875->plane, NULL,
+					&ra8875_crtc_funcs, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize CRTC with planes\n");
+
+	ra8875->encoder.possible_crtcs = drm_crtc_mask(&ra8875->crtc);
+	ret = drm_encoder_init(drm, &ra8875->encoder, &ra8875_encoder_funcs_cleanup,
+			       DRM_MODE_ENCODER_NONE, NULL);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize encoder\n");
+
+	ret = drm_connector_attach_encoder(connector, &ra8875->encoder);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to attach connector to encoder\n");
+
+	drm_plane_enable_fb_damage_clips(&ra8875->plane);
+
+	spi_set_drvdata(spi, drm);
+	drm_mode_config_reset(drm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register device\n");
+
+	drm_client_setup(drm, drm_format_info(DRM_FORMAT_RGB565));
+
+	return 0;
+}
+
+static void ra8875_remove(struct spi_device *spi)
+{
+	struct drm_device *drm = spi_get_drvdata(spi);
+
+	drm_dev_unplug(drm);
+	drm_atomic_helper_shutdown(drm);
+}
+
+static void ra8875_shutdown(struct spi_device *spi)
+{
+	drm_atomic_helper_shutdown(spi_get_drvdata(spi));
+}
+
+static const struct of_device_id ra8875_of_match[] = {
+	{ .compatible = "raio,ra8875" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ra8875_of_match);
+
+static const struct spi_device_id ra8875_id[] = {
+	{ "ra8875", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ra8875_id);
+
+static struct spi_driver ra8875_spi_driver = {
+	.driver = {
+		.name = "ra8875",
+		.of_match_table = ra8875_of_match,
+	},
+	.id_table = ra8875_id,
+	.probe = ra8875_probe,
+	.remove = ra8875_remove,
+	.shutdown = ra8875_shutdown,
+};
+module_spi_driver(ra8875_spi_driver);
+
+MODULE_DESCRIPTION("RAiO RA8875 DRM driver");
+MODULE_AUTHOR("Adam Azuddin <azuddinadam@gmail.com>");
+MODULE_LICENSE("GPL");
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver
  2026-10-30  8:37 ` [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
@ 2026-10-30  9:09   ` sashiko-bot
  0 siblings, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-30  9:09 UTC (permalink / raw)
  To: Adam Azuddin; +Cc: dri-devel, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] drm/tiny: ra8875: heap overflow in ra8875_clear_screen() via unvalidated mode
- [High] drm/tiny: ra8875: plane updates run before CRTC enable and screen clear
- [High] drm/tiny: ra8875: miscalculation of HNDR and VNDR back porch in ra8875_hw_init()
- [High] drm/tiny: ra8875: missing drm_dev_enter() guard in ra8875_crtc_atomic_enable()
- [Medium] drm/tiny: ra8875: missing vdd-supply regulator acquisition and enable
- [Medium] drm/tiny: ra8875: missing drm_gem_fb_begin_cpu_access() in ra8875_fb_dirty()
- [Medium] drm/tiny/ra8875: unaligned u16 stores in ra8875_fb_dirty() via drm_fb_swab()
- [Low] drm/tiny: ra8875: missing MMU dependency in Kconfig for DRM_RA8875
--

commit 7ff969ff52235eb3411700feffd794a3e46ceb5a
Author: Adam Azuddin <azuddinadam@gmail.com>

drm/tiny: Add RAiO RA8875 display controller driver

This commit adds a DRM tiny driver for the RAiO RA8875 SPI-connected TFT
display controller supporting resolutions up to 800x480 with GEM shmem
buffer management.

> diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
> index f0e72d4b6a470..2f1c49e31dd60 100644
> --- a/drivers/gpu/drm/tiny/Kconfig
> +++ b/drivers/gpu/drm/tiny/Kconfig
> @@ -98,6 +98,19 @@ config DRM_PIXPAPER
>  
>  	  If M is selected, the module will be built as pixpaper.ko.
>  
> +config DRM_RA8875
> +	tristate "DRM support for RA8875 display panels"
> +	depends on DRM && SPI && OF
> +	select DRM_CLIENT_SELECTION
> +	select DRM_KMS_HELPER
> +	select DRM_GEM_SHMEM_HELPER
> +	select VIDEOMODE_HELPERS

[Severity: Low]
In drivers/gpu/drm/tiny/Kconfig, DRM_RA8875 selects DRM_GEM_SHMEM_HELPER,
which depends on MMU in drivers/gpu/drm/Kconfig.

Without a dependency on MMU, enabling DRM_RA8875 on a !CONFIG_MMU target
triggers an unmet direct dependency warning and fails to build in
drm_gem_shmem_helper.c.

Should DRM_RA8875 depend on MMU as well, matching the other shmem-based
drivers in drivers/gpu/drm/tiny/Kconfig?

[ ... ]

> diff --git a/drivers/gpu/drm/tiny/ra8875.c b/drivers/gpu/drm/tiny/ra8875.c
> new file mode 100644
> index 0000000000000..937bb7fdbd910
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/ra8875.c
[ ... ]
> +static int ra8875_hw_init(struct ra8875_device *ra8875,
> +			  struct drm_display_mode *mode)
> +{
[ ... ]
> +	/* Horizontal timing */
> +	hdwr = (mode->hdisplay / 8) - 1;
> +	hndr = ((mode->htotal - mode->hdisplay) / 8) - 1;
> +	hstr = ((mode->hsync_start - mode->hdisplay) / 8) - 1;
> +	hpwr = ((mode->hsync_end - mode->hsync_start) / 8) - 1;
> +
> +	ret = ra8875_write_reg(ra8875, RA8875_HDWR, hdwr);
> +	if (ret)
> +		return ret;
> +	ret = ra8875_write_reg(ra8875, RA8875_HNDFTR, RA8875_HNDFTR_HNDFT(0));
> +	if (ret)
> +		return ret;
> +	ret = ra8875_write_reg(ra8875, RA8875_HNDR, hndr);
> +	if (ret)
> +		return ret;
> +	ret = ra8875_write_reg(ra8875, RA8875_HSTR, hstr);
> +	if (ret)
> +		return ret;
> +	ret = ra8875_write_reg(ra8875, RA8875_HPWR, hpwr);
> +	if (ret)
> +		return ret;
> +
> +	/* Vertical timing */
> +	vdhr = mode->vdisplay - 1;
> +	vndr = mode->vtotal - mode->vdisplay - 1;
> +	vstr = mode->vsync_start - mode->vdisplay - 1;
> +	vpwr = mode->vsync_end - mode->vsync_start - 1;

[Severity: High]
In drivers/gpu/drm/tiny/ra8875.c, are hndr and vndr in ra8875_hw_init()
being computed from the total blanking interval rather than the back porch?

In drm_display_mode_from_videomode():

    dmode->hsync_start = dmode->hdisplay + vm->hfront_porch;
    dmode->hsync_end = dmode->hsync_start + vm->hsync_len;
    dmode->htotal = dmode->hsync_end + vm->hback_porch;

Since HSTR (and VSTR0/VSTR1) already program the front porch and HPWR (and
VPWR) program the sync pulse width, RA8875_HNDR and RA8875_VNDR0/RA8875_VNDR1
program the horizontal and vertical non-display (back porch) periods.

Using mode->htotal - mode->hdisplay and mode->vtotal - mode->vdisplay counts
the front porch and sync pulse width a second time. Should these be computed
from mode->htotal - mode->hsync_end and mode->vtotal - mode->vsync_end
instead?

[ ... ]

> +static int ra8875_clear_screen(struct ra8875_device *ra8875,
> +			       struct drm_display_mode *mode)
> +{
[ ... ]
> +	memset(txbuf, 0, 1 + rows_per_chunk * width * 2);
> +	txbuf[0] = RA8875_DATAWRITE;
> +
> +	for (y = 0; y < height; y += rows) {
> +		rows = min(rows_per_chunk, height - y);
> +		ret = ra8875_write_pixels(ra8875, 1 + rows * width * 2);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ra8875_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_commit *state)
> +{
> +	struct ra8875_device *ra8875 =
> +		container_of(crtc->dev, struct ra8875_device, drm);
> +	struct drm_crtc_state *crtc_state =
> +		drm_atomic_get_new_crtc_state(state, crtc);
> +	struct drm_display_mode *mode = &crtc_state->mode;
> +	int ret;
> +
> +	ret = ra8875_hw_init(ra8875, mode);
> +	if (ret) {
> +		drm_err(&ra8875->drm, "Failed to initialize hardware\n");
> +		return;
> +	}
> +
> +	ret = ra8875_clear_screen(ra8875, mode);
> +	if (ret) {
> +		drm_err(&ra8875->drm, "Failed to clear screen\n");
> +		return;
> +	}
> +}

[Severity: High]
In drivers/gpu/drm/tiny/ra8875.c, doesn't the atomic commit order cause
ra8875_crtc_atomic_enable() to wipe out the initial plane update on every
modeset?

In ra8875_probe(), ra8875->rst_gpio is requested with GPIOD_OUT_HIGH and
drm->mode_config.helper_private is left unset. When drm_atomic_helper_commit()
runs a modeset (including the initial fbdev modeset from drm_client_setup()),
it uses the default drm_atomic_helper_commit_tail(), which executes:

    drm_atomic_helper_commit_tail()
      drm_atomic_helper_commit_planes(dev, state, 0)
        ra8875_plane_atomic_update()
          ra8875_fb_dirty()
      drm_atomic_helper_commit_modeset_enables(dev, state)
        ra8875_crtc_atomic_enable()
          ra8875_hw_init()
          ra8875_clear_screen()

As a result, ra8875_plane_atomic_update() writes the framebuffer while the
chip is still held in reset, and then ra8875_crtc_atomic_enable() resets the
hardware and zeroes the entire display memory in ra8875_clear_screen().

In addition, ra8875_plane_atomic_check() passes can_update_disabled = true to
drm_atomic_helper_check_plane_state(), and ra8875_plane_atomic_update() does
not check whether the CRTC is active.

Should drm->mode_config.helper_private be configured with
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm (so CRTC enable runs
before plane updates, and plane updates are skipped when the CRTC is
inactive), and can_update_disabled be set to false in
ra8875_plane_atomic_check()?

[Severity: High]
Also in ra8875_crtc_atomic_enable() in drivers/gpu/drm/tiny/ra8875.c, can a
modeset racing with device removal in ra8875_remove() cause a use-after-free
on ra8875->txbuf and ra8875->rst_gpio?

In ra8875_probe(), ra8875->txbuf and ra8875->rst_gpio are allocated with
devm_kzalloc(&spi->dev, ...) and devm_gpiod_get_optional(&spi->dev, ...), so
devres frees them as soon as ra8875_remove() returns, even if a DRM file
descriptor remains open.

Unlike ra8875_plane_atomic_update(), ra8875_crtc_atomic_enable() does not
guard its hardware and buffer accesses with drm_dev_enter() and
drm_dev_exit(). If an atomic commit enters ra8875_crtc_atomic_enable()
concurrently with or after drm_dev_unplug() in ra8875_remove(),
drm_dev_unplug() will not synchronize with it and ra8875_clear_screen() can
memset() and SPI-transfer from the freed ra8875->txbuf buffer.

Should ra8875_crtc_atomic_enable() (and ra8875_crtc_atomic_disable()) be
guarded with drm_dev_enter() and drm_dev_exit()?

[ ... ]

> +static void ra8875_fb_dirty(struct ra8875_device *ra8875, struct iosys_map *src,
> +			    struct drm_framebuffer *fb, struct drm_rect *rect)
> +{
[ ... ]
> +	for (int y = rect->y1; y < rect->y2; y += rows) {
> +		struct drm_rect chunk;
> +
> +		rows = min(RA8875_TXBUF_ROWS, rect->y2 - y);
> +		chunk = DRM_RECT_INIT(rect->x1, y, width, rows);
> +
> +		iosys_map_set_vaddr(&dst, ra8875->txbuf + 1);
> +		drm_fb_swab(&dst, NULL, src, fb, &chunk, false, &fmtcnv_state);
> +
> +		if (ra8875_write_pixels(ra8875, 1 + rows * width * 2))
> +			goto err;
> +	}

[Severity: Medium]
In ra8875_fb_dirty() in drivers/gpu/drm/tiny/ra8875.c, drm_fb_swab() reads
from the shadow-mapped framebuffer with the CPU, but
drm_gem_begin_shadow_fb_access() only vmaps the buffer without calling
dma_buf_begin_cpu_access().

When displaying an imported PRIME DMA-BUF on a non-cache-coherent system, can
omitting drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE) and
drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE) around the CPU read result in
stale cache reads and display corruption?

[Severity: Medium]
Also in ra8875_fb_dirty() in drivers/gpu/drm/tiny/ra8875.c, doesn't passing
ra8875->txbuf + 1 as the destination buffer to drm_fb_swab() perform
unaligned 16-bit stores on every pixel?

In ra8875_probe(), ra8875->txbuf is allocated with devm_kzalloc(), which is
even-aligned, making ra8875->txbuf + 1 an odd address. For DRM_FORMAT_RGB565,
drm_fb_swab() calls drm_fb_swab16_line(), which casts the destination pointer
directly to u16 * and writes *dbuf16++ = swab16(*sbuf16++) without
put_unaligned().

Can this trigger alignment faults on architectures requiring natural
alignment as well as UBSAN alignment warnings?

[ ... ]

> +static enum drm_mode_status ra8875_mode_valid(struct drm_crtc *crtc,
> +					      const struct drm_display_mode *mode)
> +{
> +	if (mode->hdisplay % 8 != 0 || mode->hsync_start % 8 != 0 ||
> +	    mode->hsync_end % 8 != 0 || mode->htotal % 8 != 0) {
> +		drm_dbg_kms(crtc->dev,
> +		   "mode timings must be 8-pixel aligned (hdisplay=%d hsync_start=%d hsync_end=%d htotal=%d)\n",
> +			    mode->hdisplay, mode->hsync_start, mode->hsync_end, mode->htotal);
> +		return MODE_H_ILLEGAL;
> +	}
> +	return MODE_OK;
> +}

[Severity: Critical]
As reported by Sashiko on v4 [1], can a userspace-supplied display mode
overflow ra8875->txbuf in ra8875_clear_screen()?

In ra8875_probe() in drivers/gpu/drm/tiny/ra8875.c, ra8875->txbuf is
allocated for a fixed maximum width of RA8875_MAX_WIDTH (800):

    buf_size = 1 + RA8875_TXBUF_ROWS * RA8875_MAX_WIDTH * 2;
    ra8875->txbuf = devm_kzalloc(&spi->dev, buf_size, GFP_KERNEL);

While drm->mode_config.max_width restricts framebuffer sizes, a userspace
DRM master can submit a custom CRTC mode via KMS (for example with the
primary plane disabled). Because ra8875_mode_valid() only checks that the
horizontal timings are multiples of 8 and does not call
drm_crtc_helper_mode_valid_fixed(crtc, mode, &ra8875->mode) or bound
mode->hdisplay to RA8875_MAX_WIDTH, committing a mode with mode->hdisplay >
800 causes ra8875_crtc_atomic_enable() -> ra8875_clear_screen() to memset()
and SPI-write 1 + 16 * mode->hdisplay * 2 bytes past the end of
ra8875->txbuf.

Additionally, neither ra8875_probe() nor ra8875_mode_valid() verifies that
the horizontal and vertical front porch, sync pulse, and back porch lengths
are strictly positive, so a zero-length porch or sync pulse will underflow
when subtracting 1 in ra8875_hw_init().

Should ra8875_mode_valid() validate the mode with
drm_crtc_helper_mode_valid_fixed() and verify that all porch and sync pulse
intervals are non-zero?

[ ... ]

> +static int ra8875_probe(struct spi_device *spi)
> +{
[ ... ]
> +	ra8875->rst_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(ra8875->rst_gpio))
> +		return dev_err_probe(dev, PTR_ERR(ra8875->rst_gpio),
> +			     "failed to get reset GPIO\n");

[Severity: Medium]
In ra8875_probe() in drivers/gpu/drm/tiny/ra8875.c, the driver requests the
optional reset GPIO, but never acquires or enables the vdd-supply regulator
that is marked as required in
Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml.

If vdd-supply is backed by a switchable regulator that is disabled at boot or
turned off by the regulator core as unused, won't the RA8875 remain unpowered
when ra8875_hw_init() attempts SPI transfers? Should ra8875_probe() enable
the vdd supply (for example via devm_regulator_get_enable(dev, "vdd"))?

[1] https://lore.kernel.org/r/20261012064518.3497285-3-azuddinadam@gmail.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261030083737.47188-1-azuddinadam@gmail.com?part=2

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-30  9:09 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-30  8:37 [PATCH v5 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
2026-10-30  8:37 ` [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
2026-10-30  8:37 ` [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
2026-10-30  9:09   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox