* [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; 6+ 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] 6+ 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-11-03 10:49 ` Krzysztof Kozlowski 2026-10-30 8:37 ` [PATCH v5 2/2] drm/tiny: Add RAiO RA8875 display controller driver Adam Azuddin 1 sibling, 1 reply; 6+ 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] 6+ messages in thread
* Re: [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller 2026-10-30 8:37 ` [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller Adam Azuddin @ 2026-11-03 10:49 ` Krzysztof Kozlowski 2026-11-07 11:12 ` Adam Azuddin 0 siblings, 1 reply; 6+ messages in thread From: Krzysztof Kozlowski @ 2026-11-03 10:49 UTC (permalink / raw) To: Adam Azuddin Cc: robh, krzk+dt, conor+dt, neil.armstrong, maarten.lankhorst, mripard, tzimmermann, jesszhan0024, airlied, simona, andersson, dri-devel, devicetree, linux-kernel On Fri, Oct 30, 2026 at 04:37:35PM +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 You can drop this. > 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. Last sentence is not correct. What driver can do is here less relevant. Question is whether hardware datasheet allows interrupt being not connected. > + maxItems: 1 > + > +required: > + - compatible > + - reg > + - display-timings > + - vdd-supply Best regards, Krzysztof ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v5 1/2] dt-bindings: display: panel: Add RAiO RA8875 display controller 2026-11-03 10:49 ` Krzysztof Kozlowski @ 2026-11-07 11:12 ` Adam Azuddin 0 siblings, 0 replies; 6+ messages in thread From: Adam Azuddin @ 2026-11-07 11:12 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: robh, krzk+dt, conor+dt, neil.armstrong, maarten.lankhorst, mripard, tzimmermann, jesszhan0024, airlied, simona, andersson, dri-devel, devicetree, linux-kernel On Tue, Nov 03, 2026 at 11:49:07AM +0100, Krzysztof Kozlowski wrote: > On Fri, Oct 30, 2026 at 04:37:35PM +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 > > You can drop this. > Will drop. > > + 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. > > Last sentence is not correct. What driver can do is here less relevant. > Question is whether hardware datasheet allows interrupt being not > connected. > Understood. I will drop the sentence referencing driver polling. Thanks for the review. Regards, Adam ^ permalink raw reply [flat|nested] 6+ 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; 6+ 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] 6+ 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; 6+ 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] 6+ messages in thread
end of thread, other threads:[~2026-11-07 11:12 UTC | newest] Thread overview: 6+ 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-11-03 10:49 ` Krzysztof Kozlowski 2026-11-07 11:12 ` 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