dri-devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] drm/tiny: Add RAiO RA8875 display controller driver support
@ 2026-09-24 14:53 Adam Azuddin
  2026-09-24 14:53 ` [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
  2026-09-24 14:53 ` [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
  0 siblings, 2 replies; 6+ messages in thread
From: Adam Azuddin @ 2026-09-24 14:53 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, Adam Azuddin

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 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.

Link to v1: https://lore.kernel.org/all/20260916175942.3186804-1-azuddinadam@gmail.com/
---
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/

---
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   |  78 ++
 .../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                 | 808 ++++++++++++++++++
 6 files changed, 908 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] 6+ messages in thread

* [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller
  2026-09-24 14:53 [PATCH v2 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
@ 2026-09-24 14:53 ` Adam Azuddin
  2026-09-24 15:00   ` sashiko-bot
  2026-09-25  1:46   ` Rob Herring (Arm)
  2026-09-24 14:53 ` [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
  1 sibling, 2 replies; 6+ messages in thread
From: Adam Azuddin @ 2026-09-24 14:53 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, Adam Azuddin

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   | 78 +++++++++++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
 2 files changed, 80 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..b50fd95c5753
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
@@ -0,0 +1,78 @@
+# 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 controller via 8080/6800 parallel interface,
+  SPI or I2C. This binding describes the SPI-connected device.
+
+allOf:
+  - $ref: panel-common.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    const: raio,ra8875
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 25000000
+
+  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
+
+required:
+  - compatible
+  - reg
+  - display-timings
+  - vdd-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        panel: display@0 {
+            compatible = "raio,ra8875";
+            reg = <0>;
+            spi-max-frequency = <16000000>;
+            vdd-supply = <&vcc_3v3>;
+            reset-gpios = <&gpio 25 GPIO_ACTIVE_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] 6+ messages in thread

* [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver
  2026-09-24 14:53 [PATCH v2 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
  2026-09-24 14:53 ` [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
@ 2026-09-24 14:53 ` Adam Azuddin
  2026-09-24 15:03   ` sashiko-bot
  1 sibling, 1 reply; 6+ messages in thread
From: Adam Azuddin @ 2026-09-24 14:53 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, Adam Azuddin

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 | 808 ++++++++++++++++++++++++++++++++++
 4 files changed, 828 insertions(+)
 create mode 100644 drivers/gpu/drm/tiny/ra8875.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 4d108339f44e..0dfe8bbea97e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8449,6 +8449,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/display/panel/raydium,rm67191.yaml
 F:	drivers/gpu/drm/panel/panel-raydium-rm67191.c
 
+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 SAMSUNG DB7430 PANELS
 M:	Linus Walleij <linusw@kernel.org>
 S:	Maintained
diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig
index f0e72d4b6a47..7901ffce490b 100644
--- a/drivers/gpu/drm/tiny/Kconfig
+++ b/drivers/gpu/drm/tiny/Kconfig
@@ -195,6 +195,19 @@ config TINYDRM_REPAPER
 
 	  If M is selected the module will be called repaper.
 
+config DRM_RA8875
+	tristate "DRM support for RA8875 display panels"
+	depends on DRM && SPI
+	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-TFTM070-5V4)
+
+	  If M is selected the module will be called ra8875.
+
 config TINYDRM_SHARP_MEMORY
 	tristate "DRM support for Sharp Memory LCD 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..53bd319138e3
--- /dev/null
+++ b/drivers/gpu/drm/tiny/ra8875.c
@@ -0,0 +1,808 @@
+// 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_state_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>
+#include <drm/drm_rect.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define RA8875_TXBUF_ROWS	 16
+#define RA8875_CMD_DATA_BUF_SIZE 2
+
+#define RA8875_MAX_SPI_SPEED 25000000
+#define RA8875_REG_SPI_SPEED 1000000
+#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)
+{
+	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
+	ra8875->cmd_buf[1] = reg;
+	ra8875->data_buf[0] = RA8875_DATAWRITE;
+	ra8875->data_buf[1] = val;
+	int ret;
+
+	struct spi_transfer t_cmd = {
+		.tx_buf = ra8875->cmd_buf,
+		.len = 2,
+		.speed_hz = RA8875_REG_SPI_SPEED,
+		.cs_change = 1
+	};
+	struct spi_transfer t_data = {
+		.tx_buf = ra8875->data_buf,
+		.len = 2,
+		.speed_hz = RA8875_REG_SPI_SPEED,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t_cmd, &m);
+	spi_message_add_tail(&t_data, &m);
+
+	ret = spi_sync(ra8875->spi, &m);
+	if (ret)
+		dev_err(&ra8875->spi->dev,
+			"write_reg(0x%02x, 0x%02x) failed: %d\n", reg, val,
+			ret);
+	return ret;
+}
+
+static int ra8875_read_reg(struct ra8875_device *ra8875, u8 reg, u8 *val)
+{
+	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
+	ra8875->cmd_buf[1] = reg;
+	int ret;
+
+	u8 tx[2] = { 0x40, 0x00 };
+	u8 rx[2] = { 0x00, 0x00 };
+
+	struct spi_transfer t_cmd = {
+		.tx_buf = ra8875->cmd_buf,
+		.len = 2,
+		.speed_hz = RA8875_REG_SPI_SPEED,
+		.cs_change = 1
+	};
+	struct spi_transfer t_read = {
+		.tx_buf = tx,
+		.rx_buf = rx,
+		.len = 2,
+		.speed_hz = RA8875_REG_SPI_SPEED,
+	};
+	struct spi_message m;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t_cmd, &m);
+	spi_message_add_tail(&t_read, &m);
+
+	ret = spi_sync(ra8875->spi, &m);
+	if (ret)
+		return ret;
+
+	*val = rx[1];
+	return 0;
+}
+
+static int ra8875_hw_init(struct ra8875_device *ra8875,
+			  struct drm_display_mode *mode)
+{
+	u8 hdwr, hndr, hstr, hpwr, vpwr;
+	u16 vdhr, vndr, vstr;
+	u8 readback;
+	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);
+
+	if (drm_debug_enabled(DRM_UT_KMS)) {
+		ra8875_read_reg(ra8875, RA8875_PLLC1, &readback);
+		drm_dbg_kms(&ra8875->drm, "ra8875: PLLC1 = 0x%02x (expected 0x0B)\n", readback);
+
+		ra8875_read_reg(ra8875, RA8875_SYSR, &readback);
+		drm_dbg_kms(&ra8875->drm, "ra8875: SYSR  = 0x%02x (expected 0x0C)\n", readback);
+
+		ra8875_read_reg(ra8875, RA8875_P1CR, &readback);
+		drm_dbg_kms(&ra8875->drm, "ra8875: P1CR  = 0x%02x (expected 0x81)\n", readback);
+
+		ra8875_read_reg(ra8875, RA8875_PWRR, &readback);
+		drm_dbg_kms(&ra8875->drm, "ra8875: PWRR  = 0x%02x (expected 0x80)\n", readback);
+	}
+
+	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;) {
+		rows = min(rows_per_chunk, height - y);
+
+		u8 cmd[2] = { RA8875_CMDWRITE, RA8875_MRWC };
+		struct spi_transfer t_cmd = {
+			.tx_buf = cmd,
+			.len = 2,
+			.speed_hz = RA8875_REG_SPI_SPEED,
+		};
+		struct spi_transfer t_data = {
+			.tx_buf = txbuf,
+			.len = 1 + rows * width * 2,
+			.speed_hz = RA8875_MAX_SPI_SPEED,
+		};
+		struct spi_message m;
+
+		spi_message_init(&m);
+		spi_message_add_tail(&t_cmd, &m);
+		spi_message_add_tail(&t_data, &m);
+		ret = spi_sync(ra8875->spi, &m);
+		if (ret)
+			return ret;
+		y += rows;
+	}
+
+	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);
+	unsigned int height = drm_rect_height(rect);
+	struct iosys_map dst;
+	struct drm_format_conv_state fmtcnv_state = DRM_FORMAT_CONV_STATE_INIT;
+
+	struct spi_transfer t_data;
+	struct spi_message m;
+
+	DRM_DEBUG_KMS("Flushing [FB:%d] " DRM_RECT_FMT "\n", fb->base.id, DRM_RECT_ARG(rect));
+
+	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;
+
+	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
+	ra8875->cmd_buf[1] = RA8875_MRWC;
+	if (spi_write(ra8875->spi, ra8875->cmd_buf, 2))
+		goto err;
+
+	ra8875->txbuf[0] = RA8875_DATAWRITE;
+	iosys_map_set_vaddr(&dst, ra8875->txbuf + 1);
+	drm_fb_swab(&dst, NULL, src, fb, rect, false, &fmtcnv_state);
+
+	memset(&t_data, 0, sizeof(t_data));
+	t_data.tx_buf = ra8875->txbuf;
+	t_data.len = 1 + height * width * 2;
+	t_data.speed_hz = ra8875->spi->max_speed_hz;
+
+	spi_message_init(&m);
+	spi_message_add_tail(&t_data, &m);
+	if (spi_sync(ra8875->spi, &m))
+		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 (drm_WARN_ON_ONCE(&ra8875->drm, !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 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 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;
+	spi->bits_per_word = 8;
+
+	spi->cs_setup.value = 10;
+	spi->cs_setup.unit = SPI_DELAY_UNIT_USECS;
+	spi->cs_inactive.value = 10;
+	spi->cs_inactive.unit = SPI_DELAY_UNIT_USECS;
+	spi->cs_hold.value = 10;
+	spi->cs_hold.unit = SPI_DELAY_UNIT_USECS;
+
+	ret = spi_setup(spi);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to setup SPI:\n");
+
+	buf_size = 1 + RA8875_MAX_HEIGHT * 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 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] 6+ messages in thread

* Re: [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller
  2026-09-24 14:53 ` [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
@ 2026-09-24 15:00   ` sashiko-bot
  2026-09-25  1:46   ` Rob Herring (Arm)
  1 sibling, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-24 15:00 UTC (permalink / raw)
  To: Adam Azuddin; +Cc: robh, conor+dt, devicetree, dri-devel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] dt-bindings: missing <dt-bindings/gpio/gpio.h> in example causes dtc build failure
--

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

dt-bindings: display: panel: Add RAiO RA8875 display controller

This commit introduces a new device tree binding for the RAiO RA8875 TFT
display controller. It documents properties for its SPI interface,
including required supplies and GPIO resets.

> 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 0000000000000..b50fd95c57539
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml

[ ... ]

> +examples:
> +  - |
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        panel: display@0 {
> +            compatible = "raio,ra8875";
> +            reg = <0>;
> +            spi-max-frequency = <16000000>;
> +            vdd-supply = <&vcc_3v3>;
> +            reset-gpios = <&gpio 25 GPIO_ACTIVE_LOW>;

[Severity: Low]
Does this example fail to compile during the make dt_binding_check target?

The GPIO_ACTIVE_LOW macro is used here without the required header
inclusion. This causes dtc compilation to fail with a syntax error
because the macro remains unexpanded.

Could we include <dt-bindings/gpio/gpio.h> at the beginning of the
example block?

> +
> +            display-timings {

[ ... ]

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

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

* Re: [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver
  2026-09-24 14:53 ` [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
@ 2026-09-24 15:03   ` sashiko-bot
  0 siblings, 0 replies; 6+ messages in thread
From: sashiko-bot @ 2026-09-24 15:03 UTC (permalink / raw)
  To: Adam Azuddin; +Cc: robh, conor+dt, dri-devel, devicetree

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] drm/tiny: atomic context violation by calling sleeping functions
- [High] spi: DMA-unsafe stack buffers passed to spi_sync
- [High] drm: missing reset callback in drm_crtc_funcs leads to null deref
- [High] drm: missing cleanup_fb causes GEM object pin leaks
- [Medium] spi: massive contiguous allocation and unbounded spi transfers
- [Medium] drm: missing atomic_disable hook causes intentional warnings
- [Medium] spi: missing cs_change=1 breaks RA8875 spi protocol
--

commit 594ac08dfc437ba3494bf3b5226a6268a7e028be
Author: Adam Azuddin <azuddinadam@gmail.com>

    drm/tiny: Add RAiO RA8875 display controller driver
    
    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.

> diff --git a/drivers/gpu/drm/tiny/ra8875.c b/drivers/gpu/drm/tiny/ra8875.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/tiny/ra8875.c

[ ... ]

> +static int ra8875_read_reg(struct ra8875_device *ra8875, u8 reg, u8 *val)
> +{
> +	ra8875->cmd_buf[0] = RA8875_CMDWRITE;
> +	ra8875->cmd_buf[1] = reg;
> +	int ret;
> +
> +	u8 tx[2] = { 0x40, 0x00 };
> +	u8 rx[2] = { 0x00, 0x00 };
> +
> +	struct spi_transfer t_cmd = {
> +		.tx_buf = ra8875->cmd_buf,
> +		.len = 2,
> +		.speed_hz = RA8875_REG_SPI_SPEED,
> +		.cs_change = 1
> +	};
> +	struct spi_transfer t_read = {
> +		.tx_buf = tx,
> +		.rx_buf = rx,
> +		.len = 2,
> +		.speed_hz = RA8875_REG_SPI_SPEED,
> +	};
> +	struct spi_message m;
> +
> +	spi_message_init(&m);
> +	spi_message_add_tail(&t_cmd, &m);
> +	spi_message_add_tail(&t_read, &m);
> +
> +	ret = spi_sync(ra8875->spi, &m);

[Severity: High]
Does this code pass stack memory into a DMA-mapped SPI transfer?

The Linux SPI subsystem uses the DMA API to map transfer buffers if the SPI
controller supports it. Stack memory allocated for tx and rx might not be
physically contiguous, leading to memory corruption or cache coherency
failures.

> +	if (ret)
> +		return ret;

[ ... ]

> +static int ra8875_hw_init(struct ra8875_device *ra8875,
> +			  struct drm_display_mode *mode)
> +{
> +	u8 hdwr, hndr, hstr, hpwr, vpwr;
> +	u16 vdhr, vndr, vstr;
> +	u8 readback;
> +	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);
> +	}

[Severity: High]
Does this execute sleeping functions in an atomic context?

The global review guidelines dictate that CRTC and plane atomic update
callbacks execute in an atomic context during non-blocking commits, where
hardware sequence paths must not sleep.

Since ra8875_hw_init() is called by ra8875_crtc_atomic_enable(), executing
msleep(), usleep_range(), and gpiod_set_value_cansleep() here can lead to
system instability, lockups, and kernel warnings for scheduling while atomic.

> +
> +	/* Soft Reset Sequence */
> +	ret = ra8875_write_reg(ra8875, RA8875_PWRR, RA8875_PWRR_SOFTRST);

[ ... ]

> +static int ra8875_clear_screen(struct ra8875_device *ra8875,
> +			       struct drm_display_mode *mode)
> +{

[ ... ]

> +	for (y = 0; y < height;) {
> +		rows = min(rows_per_chunk, height - y);
> +
> +		u8 cmd[2] = { RA8875_CMDWRITE, RA8875_MRWC };
> +		struct spi_transfer t_cmd = {
> +			.tx_buf = cmd,
> +			.len = 2,
> +			.speed_hz = RA8875_REG_SPI_SPEED,
> +		};

[Severity: Medium]
Will the missing cs_change = 1 break the RA8875 SPI protocol here?

Unlike ra8875_write_reg() which sets cs_change = 1 for command transfers
to de-assert the chip select before sending the data payload, this transfer
omits it. Without toggling the CS pin between the command and data phases,
the hardware might misinterpret the incoming data.

[Severity: High]
Does this code use stack memory for an SPI transfer buffer?

Passing the local stack array cmd into spi_sync() is unsafe because the SPI
subsystem may use DMA to map the buffer, which is explicitly prohibited for
stack variables.

> +		struct spi_transfer t_data = {
> +			.tx_buf = txbuf,
> +			.len = 1 + rows * width * 2,
> +			.speed_hz = RA8875_MAX_SPI_SPEED,
> +		};
> +		struct spi_message m;

[ ... ]

> +static void ra8875_fb_dirty(struct ra8875_device *ra8875, struct iosys_map *src,
> +			    struct drm_framebuffer *fb, struct drm_rect *rect)
> +{

[ ... ]

> +	memset(&t_data, 0, sizeof(t_data));
> +	t_data.tx_buf = ra8875->txbuf;
> +	t_data.len = 1 + height * width * 2;
> +	t_data.speed_hz = ra8875->spi->max_speed_hz;

[Severity: Medium]
Will this result in unbounded SPI transfers that exceed the host controller's
limits?

The driver attempts to send up to the entire 768 KB buffer (for an 800x480
resolution) in a single spi_transfer depending on the damage rect size.
Many SPI controllers impose maximum transfer limits, such as 65535 bytes,
and will reject oversized transfers.

> +
> +	spi_message_init(&m);
> +	spi_message_add_tail(&t_data, &m);
> +	if (spi_sync(ra8875->spi, &m))

[Severity: High]
Does this call a sleeping function from the plane's atomic update callback?

spi_sync() sleeps while waiting for hardware I/O to complete.
Because ra8875_fb_dirty() is invoked from ra8875_plane_atomic_update(),
this executes sleeping routines in an atomic context, leading to kernel
warnings and system lockups.

> +		goto err;
> +
> +	drm_format_conv_state_release(&fmtcnv_state);

[ ... ]

> +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 (drm_WARN_ON_ONCE(&ra8875->drm, !fb))
> +		return;

[Severity: Medium]
Is this code triggering intentional warnings during plane disables?

Because the driver does not provide an atomic_disable callback in
ra8875_plane_helper_funcs, the DRM atomic helpers fall back to calling
atomic_update with a NULL fb. This warning will immediately fire on valid
plane disable requests, generating a stack trace that could crash the system
if panic_on_warn is enabled.

> +
> +	if (!drm_dev_enter(&ra8875->drm, &idx))

[ ... ]

> +static const struct drm_crtc_funcs ra8875_crtc_funcs = {
> +	.atomic_create_state = drm_atomic_helper_crtc_create_state,

[Severity: High]
Does this structure omit the required .reset callback and mistakenly assign
to .atomic_create_state instead of .atomic_duplicate_state (or similar)?

There is no .atomic_create_state member in drm_crtc_funcs, causing a build
failure. Furthermore, omitting the required .reset callback (typically
drm_atomic_helper_crtc_reset) leaves crtc->state as NULL, leading to a
kernel panic upon the first atomic check or commit.

> +	.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,
> +};

[Severity: High]
Does this code leak GEM object pins by missing a cleanup_fb hook?

The driver specifies .prepare_fb = drm_gem_plane_helper_prepare_fb, which
pins the framebuffer during a page flip or mode set. Without providing a
matching .cleanup_fb hook (such as drm_gem_plane_helper_cleanup_fb), the
framebuffer is never unpinned. The GEM object reference count will perpetually
increase, and the memory cannot be freed.

> +
> +static const struct drm_driver ra8875_driver = {

[ ... ]

> +static int ra8875_probe(struct spi_device *spi)
> +{

[ ... ]

> +	ret = spi_setup(spi);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "Failed to setup SPI:\n");
> +
> +	buf_size = 1 + RA8875_MAX_HEIGHT * RA8875_MAX_WIDTH * 2;
> +	ra8875->txbuf = devm_kzalloc(&spi->dev, buf_size, GFP_KERNEL);

[Severity: Medium]
Will this fail to allocate memory on fragmented systems?

The driver allocates ~768 KB of memory for a maximum 800x480 resolution using
devm_kzalloc. Because this requires an order-8 contiguous physical allocation,
it is highly likely to fail on systems with memory fragmentation.

> +	if (!ra8875->txbuf)
> +		return -ENOMEM;

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

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

* Re: [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller
  2026-09-24 14:53 ` [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
  2026-09-24 15:00   ` sashiko-bot
@ 2026-09-25  1:46   ` Rob Herring (Arm)
  1 sibling, 0 replies; 6+ messages in thread
From: Rob Herring (Arm) @ 2026-09-25  1:46 UTC (permalink / raw)
  To: Adam Azuddin
  Cc: jesszhan0024, dri-devel, conor+dt, maarten.lankhorst, tzimmermann,
	simona, neil.armstrong, mripard, airlied, linux-kernel, andersson,
	devicetree, krzk+dt


On Thu, 24 Sep 2026 22:53:55 +0800, Adam Azuddin wrote:
> 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   | 78 +++++++++++++++++++
>  .../devicetree/bindings/vendor-prefixes.yaml  |  2 +
>  2 files changed, 80 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/panel/raio,ra8875.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Lexical error: Documentation/devicetree/bindings/display/panel/raio,ra8875.example.dts:27.41-56 Unexpected 'GPIO_ACTIVE_LOW'
FATAL ERROR: Syntax error parsing input tree
make[2]: *** [scripts/Makefile.dtbs:140: Documentation/devicetree/bindings/display/panel/raio,ra8875.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/linux-dt-review/Makefile:1714: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260924145400.1385815-2-azuddinadam@gmail.com

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

end of thread, other threads:[~2026-09-25  1:46 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-24 14:53 [PATCH v2 0/2] drm/tiny: Add RAiO RA8875 display controller driver support Adam Azuddin
2026-09-24 14:53 ` [PATCH v2 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin
2026-09-24 15:00   ` sashiko-bot
2026-09-25  1:46   ` Rob Herring (Arm)
2026-09-24 14:53 ` [PATCH v2 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin
2026-09-24 15:03   ` sashiko-bot

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