Devicetree
 help / color / mirror / Atom feed
From: Devarsh Thakkar <devarsht@ti.com>
To: Thomas Zimmermann <tzimmermann@suse.de>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	Maxime Ripard <mripard@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	Neil Armstrong <neil.armstrong@linaro.org>,
	Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>,
	<dri-devel@lists.freedesktop.org>, <devicetree@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>
Cc: <praneeth@ti.com>, <vigneshr@ti.com>, <s-jain1@ti.com>,
	<r-donadkar@ti.com>, <r-sharma3@ti.com>, <afd@ti.com>,
	Sen Wang <sen@ti.com>, LiangCheng Wang <zaq14760@gmail.com>,
	"Aldea, Andrei" <andrei@ti.com>, "Judith Mendez" <jm@ti.com>,
	"D, Yashas" <y-d@ti.com>
Subject: Re: [PATCH 3/6] drm/tiny: Add DRM driver for Solomon SSD16xx e-paper display controllers
Date: Wed, 24 Jun 2026 18:49:42 +0530	[thread overview]
Message-ID: <eef355bd-6310-4138-b49d-8a8184d150e7@ti.com> (raw)
In-Reply-To: <0920e4a7-9619-42b6-ba59-160f38a5c090@suse.de>

Hi Thomas,

On 23/06/26 14:47, Thomas Zimmermann wrote:
> Hi,
> 
> sorry, I've lost track of what the most recent status is here.
> 

Thanks for the response.

> Am 18.06.26 um 17:41 schrieb Devarsh Thakkar:
> [...]
>>>>>> +config DRM_PANEL_SSD16XX
>>>>>
>>>>> Just call it DRM_SSD16XX without the panel. In DRM, things named 
>>>>> 'panel' are usually built around struct drm_panel, which doesn't 
>>>>> seem the case here.
>>>>>
>>>>
>>
>> I see drivers/gpu/drm/tiny/panel-mipi-dbi.c [0] using 
>> CONFIG_DRM_PANEL_MIPI_DBI [1] in the same directory, and that driver 
>> does not use struct drm_panel either - the only drm_panel reference 
>> there is a call to of_get_drm_panel_display_mode(), which is a DT 
>> display-mode helper unrelated to the panel framework.
> 
> It's either a misnomer or there for historical reasons IMHO. Just mipi- 
> dbi would have been better. Or maybe it should use the framework around 
> drm_panel. As it is now, the naming is somwhat misleading.
> 
>>

Ah ok thanks for sharing, I would then stick to ssd16xx.c and 
DRM_SSD16XX as you earlier suggested.

>> Given this existing precedent in drm/tiny/ itself and also for the 
>> reasons explained below as this driver houses both controller specific 
>> and panel specific logic, I would prefer to keep DRM_PANEL_SSD16XX.
> 
> No need to duplicate bad decisions. Also, can your driver use the panel 
> framework?
> 

As I understand, the driver cannot use the panel framework as struct 
drm_panel requires a hardware host (DSI/DBI/DPI controller) that calls 
drm_panel_prepare(), drm_panel_enable() etc. This is a standalone SPI 
driver, similar to the other e-paper drivers in drivers/gpu/drm/tiny/. 
  
  

  
  

You had also asked to check directory for this driver, I am thinking to 
keep driver in drm/tiny/ only instead of the solomon/ directory, since 
solomon/ currently only has OLED SSD130x drivers which use I2C or SPI to 
drive an emissive display with a completely different programming model 
and no common factor with this e-paper driver. Hope this is fine. 

  
  

There were also some open discussion points for concerns you raised 
regarding the DRM properties - rotation, refresh_mode, clear_on_* and 
color_mode and other props - for which I have elaborated my reasoning 
and had also listed the modifications I would be making in my earlier 
reply [0]. I am not sure you had a chance to review that, but in V2 I 
will be keeping these as separate patches so they can be reviewed 
independently. 
  
  

  
  
                                                           [0]: 
https://lore.kernel.org/all/eccf407a-c469-4744-a56f-aa7366c58be3@ti.com/
  
  

Best regards, 
  

Devarsh Thakkar

> Best regards
> Thomas
> 
> 
> 
>>
>>
>>
>>>> Oh ok, I preferred DRM_PANEL_SSD16XX since it also enumerates and 
>>>> uses panel specific data/compatible such as this driver supporting 
>>>> gooddisplay,gdey042t81 and more can be added too (just like panel- 
>>>> ilitek* for e.g.) unlike controller only drivers which need to be 
>>>> linked to separate panel drivers.
>>>>
>>>> Do you prefer to change it to DRM_SSD16XX_PANEL to not conflict with 
>>>> DRM_PANEL* drivers and for better context or still prefer to keep it 
>>>> as DRM_SSD16XX ?
>>>>
>>>>>> + 
>>
>> <snip>
>>
>>>>>> diff --git a/drivers/gpu/drm/tiny/panel-ssd16xx.c b/drivers/gpu/ 
>>>>>> drm/ tiny/panel-ssd16xx.c
>>>>>> new file mode 100644
>>>>>> inde`x 000000000000..b232837c54ff
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/tiny/panel-ssd16xx.c
>>>>>
>>>>> Again, remove 'panel'.
>>>>
>>
>> Also for the naming too, I'd prefer to keep panel-ssd16xx.c similar to 
>> panel-mipi-dbi.c [0] for the reasons mentioned below.
>>
>>>> Yes I can remove the panel, but I am just concerned if it won't 
>>>> mislead folks to understand ssd16xx as a controller only driver, 
>>>> requiring a separate panel driver to interface with ?
>>>>
>>>> Basically panel-ssd16xx naming was chosen since this driver houses 
>>>> both the ssd16xx controller context and also the panel being used 
>>>> along with that (similar to panel-ilitek-ili9881c.c) and i did not 
>>>> want to confuse it with a controller only driver (similar to 
>>>> tc358775.c), if it is overalapping a known pattern reserved for 
>>>> drm_panel drivers do you think we should rename it to ssd16xx- 
>>>> panel.c instead or you prefer ssd16xx.c as more appropriate one ?
>>>>
>>
>> Kindly let me know if it sounds okay.
>>
>> [0] : https://gitlab.com/linux-kernel/linux-next/-/blob/next-20260618/ 
>> drivers/gpu/drm/tiny/panel-mipi-dbi.c?ref_type=tags
>> [1] : https://gitlab.com/linux-kernel/linux-next/-/blob/next-20260618/ 
>> drivers/gpu/drm/tiny/Makefile?ref_type=tags#L8
>>
>> Regards
>> Devarsh
>>
>>>>>
>>>>>> @@ -0,0 +1,2548 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>>>> +/*
>>>>>> + * DRM driver for e-paper display panels using Solomon SSD16xx 
>>>>>> family controllers
>>>>>> + *
>>>>>> + * Copyright (C) 2026 Texas Instruments Incorporated - https:// 
>>>>>> www.ti.com/
>>>>>> + *
>>>>>> + * Author: Devarsh Thakkar <devarsht@ti.com>
>>>>>> + *
>>>>>> + * References: https://github.com/Lesords/epaper
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/delay.h>
>>>>>> +#include <linux/module.h>
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/property.h>
>>>>>> +#include <linux/spi/spi.h>
>>>>>> +
>>>>>> +#include <drm/clients/drm_client_setup.h>
>>>>>> +#include <drm/drm_atomic.h>
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_damage_helper.h>
>>>>>> +#include <drm/drm_drv.h>
>>>>>> +#include <drm/drm_fb_helper.h>
>>>>>> +#include <drm/drm_fbdev_dma.h>
>>>>>> +#include <drm/drm_fb_dma_helper.h>
>>>>>> +#include <drm/drm_framebuffer.h>
>>>>>> +#include <drm/drm_gem_dma_helper.h>
>>>>>> +#include <drm/drm_gem_framebuffer_helper.h>
>>>>>> +#include <drm/drm_probe_helper.h>
>>>>>
>>>>>> +#include <drm/drm_simple_kms_helper.h>
>>>>>
>>>>> Obsolete. Anything you use from this header should be open-coded in 
>>>>> the driver.
>>>>>
>>>>
>>>> Agreed, will remove it in V2.
>>>>
>>>>>> +#include <drm/drm_print.h>
>>>>>
>>>>>
>>>>> Please remove all of the parameters below. They might be nice for 
>>>>> your debugging, but they do not belong in the upstream driver.
>>>>>
>>>>
>>>> As mentioned previously, had kept these params mainly for legacy 
>>>> non- drm fbdev based applications.
>>>>
>>>>>> +
>>>>>> +static int rotation = -1;
>>>>>> +module_param(rotation, int, 0644);
>>>>>> +MODULE_PARM_DESC(rotation,
>>>>>> +         "Display rotation (-1=use DT, 0/180=landscape, 
>>>>>> 90/270=portrait)");
>>>>>
>>>>> Please remove this. There is a rotation property in struct 
>>>>> drm_connector, which stores the rotation. IIRC it can be overridden 
>>>>> on the kernel command line.
>>>>>
>>>>
>>>> As I understand you are referring to below fields from drm_connector 
>>>> struct, please correct me if I am wrong here but I think the 
>>>> rotation/ orientation functionality supported by ssd16xx controller 
>>>> does not match much with below model but instead matches what is 
>>>> done in drivers/gpu/ drm/drm_mipi_dbi.c (although that does not 
>>>> support runtime rotation) as explained below  :
>>>>
>>>> drm_connector (rotation specific members):
>>>>
>>>>
>>>> 1. panel_orientation (display_info.panel_orientation):
>>>> Readable from DT via of_drm_get_panel_orientation(), overridable from
>>>> cmdline. However it is not writable by userspace at runtime (which 
>>>> we require). More importantly, when Weston reads panel_orientation 
>>>> it applies an output transform and then attempts to offload rotation 
>>>> to the plane via plane.rotation. This model assumes the plane can 
>>>> geometrically map a 300x400 source framebuffer to a 400x300 CRTC 
>>>> i.e. hardware scan- out rotation. Our driver has no such hardware as 
>>>> explained in detail below.
>>>>
>>>>
>>>> 2. rotation_reflection (cmdline_mode.rotation_reflection):
>>>> Cmdline-only (video=...:rotate=N), no DT path (we require both DT- 
>>>> path and runtime suport). Also I think this is strictly for in- 
>>>> kernel drm_clients and also It currently returns false for 90/270 
>>>> unless
>>>> the plane has a hardware rotation property.
>>>>
>>>>
>>>> Both paths therefore ultimately require hardware plane rotation that
>>>> this driver does not have and both seem to be supported just 
>>>> statically i.e. cmdline or dt property.
>>>>
>>>> Our use-case needs to support runtime rotation configuration ours is 
>>>> not a mounted display but a portable hand-held device (https:// 
>>>> www.beagleboard.org/boards/beaglebadge) and we have an accelerometer 
>>>> in our device which can detect panel orientation and based on 
>>>> accelerometer reading the drm app can runtime set the custom drm 
>>>> rotation property to switch to new orientation dynamically.
>>>>
>>>> Also our driver is fundamentally different from a GPU display 
>>>> pipeline or controllers supporting transpose function. The SSD16xx 
>>>> display controller has no transpose or rotation function but instead 
>>>> supports different scan-modes, so there is no hardware path that can 
>>>> take a 400x300 plane and transpose it to a 300x400 display output. 
>>>> The controller is a simple RAM writer: the CPU writes a byte stream 
>>>> over SPI, and the controller's internal cursor
>>>> advances sequentially according to the data entry mode register 
>>>> (command
>>>> 0x11), which selects between X++/Y++ and X--/Y-- scan directions with a
>>>> configurable start position.
>>>>
>>>>
>>>> For portrait orientation we therefore change the DRM mode itself to
>>>> 300x400 from the original 400x300, so the application is asked to 
>>>> provide a 300x400 framebuffer.
>>>> The driver then writes this buffer column-by-column over SPI to the
>>>> display controller's RAM. Since the controller supports different scan
>>>> start positions (cursor at origin vs cursor at maximum address) 
>>>> combined
>>>> with the appropriate X/Y scan direction, we are able to correctly 
>>>> render
>>>> the 300x400 buffer onto the panel when it is held in portrait 
>>>> orientation (90*, 270*).
>>>>
>>>>
>>>> This means the CRTC mode must reflect the logical dimensions directly,
>>>> exactly as drm_mipi_dbi_dev_init() does via mipi_dbi_rotate_mode() for
>>>> MIPI DBI drivers. Accepting a 300x400 framebuffer onto a 400x300 CRTC
>>>> (as the panel_orientation + plane.rotation model requires) is not
>>>> possible: drm_atomic_helper_check_plane_state(DRM_PLANE_NO_SCALING)
>>>> enforces src_w == crtc_w and src_h == crtc_h, and there is no hardware
>>>> to perform the geometric remapping between the two sizes.
>>>>
>>>>
>>>> For runtime rotation changes (which are required as the panel is not
>>>> physically fixed), we therefor wanted to use a custom drm connector 
>>>> property. We can look to use the standard DRM_MODE_ROTATE_* bitmask 
>>>> (not a custom enum, that was used in v1), we can also look to check 
>>>> if driver can triggers a full modeset through the normal DRM path, 
>>>> connector_get_modes returns the correctly dimensioned mode for the 
>>>> new orientation, and userspace receives a mode-changed event with 
>>>> the new dimensions.
>>>>
>>>>
>>>> This is semantically what MIPI DBI tiny drivers do at boot (fixed 
>>>> from DT), made runtime-changeable via the custom drm connector 
>>>> property in this driver.
>>>>
>>>> Maybe, I can try to use standard bitmask instead of custom enum to 
>>>> re- use standard macros :
>>>>
>>>> drm_property_create_bitmask(drm, 0, "rotation",
>>>>                  rotation_props,
>>>>                  ARRAY_SIZE(rotation_props),
>>>>                  DRM_MODE_ROTATE_0  DRM_MODE_ROTATE_90 |
>>>>                  DRM_MODE_ROTATE_180 |DRM_MODE_ROTATE_270);
>>>>
>>>> but keep it as connector property?
>>>>
>>>>>> +
>>>>>> +static int refresh_mode = -1;
>>>>>> +module_param(refresh_mode, int, 0644);
>>>>>> +MODULE_PARM_DESC(refresh_mode,
>>>>>> +         "Refresh mode (-1=panel default, 0=partial ~300-500ms, 
>>>>>> 1=full ~1.5-2s, 2=fast ~1.0-1.5s)");
>>>>>> +
>>>>>> +static int border_waveform_init_lut = -1;
>>>>>> +module_param(border_waveform_init_lut, int, 0644);
>>>>>> +MODULE_PARM_DESC(border_waveform_init_lut,
>>>>>> +         "Border waveform index during clear/init (-1=panel 
>>>>>> default, 0-9=enum index)");
>>>>>> +
>>>>>> +static int border_waveform_lut = -1;
>>>>>> +module_param(border_waveform_lut, int, 0644);
>>>>>> +MODULE_PARM_DESC(border_waveform_lut,
>>>>>> +         "Border waveform index during display updates (-1=panel 
>>>>>> default, 0-9=enum index)");
>>>>>> +
>>>>>
>>>>> Please remove it. Only the panel default. If you have panels where 
>>>>> the default is known to be incorrect, you can add specific 
>>>>> workarounds in the driver.
>>>>>
>>>>
>>>> I think the most of these params are kept to sane defaults but they 
>>>> may change w.r.t use-cases and each panel can be used in context of 
>>>> multiple use-cases.
>>>>
>>>>>
>>>>>> +static bool border_refresh_on_every_update;
>>>>>> +module_param(border_refresh_on_every_update, bool, 0644);
>>>>>> +MODULE_PARM_DESC(border_refresh_on_every_update,
>>>>>> +         "Re-send border waveform command before each display 
>>>>>> update (default: false)");
>>>>>
>>>>> Pick a sane default.
>>>>>
>>>>
>>>> Yes driver is picking a sane default already for this (refresh 
>>>> border on init once with white border and keep it as floating in 
>>>> later updates), but just a back-door for the application in case it 
>>>> wants to avoid ghosting totally altogether or has specific needs 
>>>> w.r.t border handling.
>>>>
>>>>>> +
>>>>>> +static int clear_on_init = -1;
>>>>>> +module_param(clear_on_init, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_init,
>>>>>> +         "Clear display on first app launch (-1=disabled, 
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int clear_on_close = -1;
>>>>>> +module_param(clear_on_close, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_close,
>>>>>> +         "Clear display on app close/CRTC disable (-1=disabled, 
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int clear_on_disable = -1;
>>>>>> +module_param(clear_on_disable, int, 0644);
>>>>>> +MODULE_PARM_DESC(clear_on_disable,
>>>>>> +         "Clear display on CRTC disable/DPMS off (-1=disabled, 
>>>>>> 0=partial, 1=full, 2=fast)");
>>>>>> +
>>>>>> +static int refresh_mode_init = -1;
>>>>>> +module_param(refresh_mode_init, int, 0644);
>>>>>> +MODULE_PARM_DESC(refresh_mode_init,
>>>>>> +         "Skip baseline establishment on first enable 
>>>>>> (-1=disabled, 0=partial, 1=full, 2=fast)");
>>>>>
>>>>> Use 'disabled' for all of them.
>>>>>
>>>>>> +
>>>>>> +static int color_mode = -1;
>>>>>> +module_param(color_mode, int, 0644);
>>>>>> +MODULE_PARM_DESC(color_mode,
>>>>>> +         "Color mode (-1=panel default, 0=black-white, 1=3-color; 
>>>>>> 3- color only valid for panels with red plane support)");
>>>>>
>>>>> 'Panel default.'  Colors should be controlled by DRM clients via 
>>>>> the framebuffer.
>>>>>
>>>>
>>>> As mentioned previously, say user-space is only supporting and 
>>>> giving XR24 or XR32 format, from that we can't infer whether user- 
>>>> space want to drive display in B/W mode or color-mode.
>>>>
>>>>>> +
>>>>>> +/* 
>>>>>> -----------------------------------------------------------------------
>>>>>> + * SSD16xx family common: commands, data values, and bit 
>>>>>> definitions.
>>>>>> + * These apply equally to SSD1673, SSD1680, and SSD1683.
>>>>>> + * 
>>>>>> -----------------------------------------------------------------------
>>>>>> + */
>>>>>> +
>>>>>> +/* SPI command codes (common) */
>>>>>> +#define SSD16XX_CMD_DRIVER_OUTPUT_CONTROL        0x01
>>>>>> +#define SSD16XX_CMD_DATA_ENTRY_MODE            0x11
>>>>>> +#define SSD16XX_CMD_SW_RESET                0x12
>>>>>> +#define SSD16XX_CMD_MASTER_ACTIVATION            0x20
>>>>>> +#define SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1        0x21
>>>>>> +#define SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2        0x22
>>>>>> +#define SSD16XX_CMD_WRITE_RAM_BW            0x24
>>>>>> +#define SSD16XX_CMD_BORDER_WAVEFORM_CONTROL        0x3C
>>>>>> +#define SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END 0x44
>>>>>> +#define SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END 0x45
>>>>>> +#define SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER        0x4E
>>>>>> +#define SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER        0x4F
>>>>>> +
>>>>>> +/*
>>>>>> + * Data Entry Mode (command 0x11) AM/IDY/IDX bit encoding (common).
>>>>>> + *
>>>>>> + * Bit 2 (AM): Address update direction: 0 = X direction, 1 = Y 
>>>>>> direction
>>>>>> + * ID[1:0] when AM=0 (X-direction modes, address counter advances 
>>>>>> in X):
>>>>>> + *   00 = X decrement, Y decrement   01 = X increment, Y decrement
>>>>>> + *   10 = X decrement, Y increment   11 = X increment, Y 
>>>>>> increment (default)
>>>>>> + *
>>>>>> + * Rotation to data entry mode mapping (actual implementation 
>>>>>> uses two modes,
>>>>>> + * with scan direction controlled via RAM cursor positioning and 
>>>>>> manual tweaking):
>>>>>> + *   0°/270° → 0x03 (X++, Y++)   Landscape/Portrait-CW: cursor at 
>>>>>> (0, 0)
>>>>>> + *   90°/180° → 0x00 (X--, Y--) Portrait-CCW/Upside-down: cursor 
>>>>>> at (max, max)
>>>>>> + *
>>>>>> + * The pixel packing in convert_fb_to_1bpp is grouped by physical 
>>>>>> layout:
>>>>>> + *   - Portrait (90°/270°): column-major packing, rightmost 
>>>>>> column first
>>>>>> + *   - Landscape (0°/180°): row-major packing, top to bottom, 
>>>>>> left to right
>>>>>> + * Hardware cursor position and scan mode handle the final 
>>>>>> orientation.
>>>>>> + */
>>>>>> +#define SSD16XX_DATA_ENTRY_XDEC_YDEC        0x00  /* X--, Y-- (X- 
>>>>>> mode) */
>>>>>> +#define SSD16XX_DATA_ENTRY_XINC_YINC        0x03  /* X++, Y++ (X- 
>>>>>> mode, default) */
>>>>>> +
>>>>>> +/* POR reset value: GD=0 (G0 first), SM=0 (interlaced), TB=0 (G0- 
>>>>>> >G299) */
>>>>>> +#define SSD16XX_DRIVER_OUTPUT_CTRL_DEFAULT    0x00
>>>>>> +
>>>>>> +/* Display Update Control 1 (0x21) byte 2 default (common) */
>>>>>> +#define SSD16XX_CTRL1_BYTE2_DEFAULT        0x00
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 2 (0x22) individual bit definitions 
>>>>>> (common).
>>>>>> + * NOTE: BIT(3) is NOT common — see SSD1683_CTRL2_MODE2 in the 
>>>>>> SSD1683
>>>>>> + * section below; it has a completely different meaning in SSD1673.
>>>>>> + */
>>>>>> +#define SSD16XX_CTRL2_ENABLE_CLK        BIT(7)
>>>>>> +#define SSD16XX_CTRL2_ENABLE_ANALOG        BIT(6)
>>>>>> +#define SSD16XX_CTRL2_LOAD_TEMPERATURE        BIT(5)
>>>>>> +#define SSD16XX_CTRL2_LOAD_LUT            BIT(4)
>>>>>> +#define SSD16XX_CTRL2_DISPLAY            BIT(2)
>>>>>> +#define SSD16XX_CTRL2_DISABLE_ANALOG        BIT(1)
>>>>>> +#define SSD16XX_CTRL2_DISABLE_CLK        BIT(0)
>>>>>> +
>>>>>> +#define SSD16XX_SPI_BITS_PER_WORD        8
>>>>>> +#define SSD16XX_SPI_SPEED_DEFAULT        1000000
>>>>>> +
>>>>>> +/* Maximum time to wait for the BUSY pin to deassert after a 
>>>>>> display update */
>>>>>> +#define SSD16XX_BUSY_WAIT_TIMEOUT_MS        6000
>>>>>> +
>>>>>> +/* 
>>>>>> -----------------------------------------------------------------------
>>>>>> + * SSD1683 / SSD1680 specific: commands, data values, and bit 
>>>>>> definitions.
>>>>>> + * 
>>>>>> -----------------------------------------------------------------------
>>>>>> + */
>>>>>> +
>>>>>> +/*
>>>>>> + * Deep Sleep Mode values (command 0x10).
>>>>>> + */
>>>>>> +#define SSD1683_DEEP_SLEEP_MODE_1            0x01  /* RAM 
>>>>>> retained */
>>>>>> +#define SSD1683_DEEP_SLEEP_MODE_2            0x03  /* RAM lost 
>>>>>> (max power) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Temperature Sensor Selection (command 0x18).
>>>>>> + */
>>>>>> +#define SSD1683_CMD_TEMPERATURE_SENSOR_CONTROL 0x18
>>>>>> +#define SSD1683_TEMP_SENSOR_INTERNAL            0x80  /* Bit 7: 
>>>>>> use internal sensor */
>>>>>> +
>>>>>> +/*
>>>>>> + * Write RED RAM (command 0x26).
>>>>>> + */
>>>>>> +#define SSD1683_CMD_WRITE_RAM_RED            0x26
>>>>>> +
>>>>>> +/*
>>>>>> + * Border Waveform Control (command 0x3C) byte values.
>>>>>> + */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT0        0x00  /* GS 
>>>>>> Transition LUT0 (black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT1        0x01  /* GS 
>>>>>> Transition LUT1 (white) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT2        0x02  /* GS 
>>>>>> Transition LUT2 (black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_LUT3        0x03  /* GS 
>>>>>> Transition LUT3 (gray) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSS    0x40  /* Fix Level 
>>>>>> VSS (0V, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSH1    0x50  /* Fix Level 
>>>>>> VSH1 (+15V, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSL    0x60  /* Fix Level 
>>>>>> VSL (-15V, white) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_FIXLVL_VSH2    0x70  /* Fix Level 
>>>>>> VSH2 (+15V alt, black) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_VCOM        0x80  /* Follow VCOM 
>>>>>> (-2V~-3V, preserve) */
>>>>>> +#define SSD1683_BORDER_WAVEFORM_HIZ        0xC0  /* HiZ 
>>>>>> (floating, default) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 1 (0x21) byte 1 — RED RAM control.
>>>>>> + */
>>>>>> +#define SSD1683_CTRL1_NORMAL            0x00  /* Both BW and RED 
>>>>>> RAMs enabled */
>>>>>> +#define SSD1683_CTRL1_BYPASS_RED_RAM        0x40  /* Bypass RED 
>>>>>> RAM (force RED=0) */
>>>>>> +
>>>>>> +/*
>>>>>> + * Display Update Control 2 (0x22) BIT(3) — "Display Mode 
>>>>>> 2" (partial/BW).
>>>>>> + */
>>>>>> +#define SSD1683_CTRL2_MODE2            BIT(3)
>>>>>> +
>>>>>> +/* Composite CTRL2 sequences for each refresh mode */
>>>>>> +#define SSD1683_CTRL2_FULL_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> +                    SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> +                    SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> +                    SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> +                    SSD16XX_CTRL2_DISPLAY | \
>>>>>> +                    SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> +                    SSD16XX_CTRL2_DISABLE_CLK)  /* 0xF7, ~1.5-2s */
>>>>>> +
>>>>>> +#define SSD1683_CTRL2_FAST_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> +                    SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> +                    SSD16XX_CTRL2_DISPLAY | \
>>>>>> +                    SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> +                    SSD16XX_CTRL2_DISABLE_CLK)  /* 0xC7, 
>>>>>> ~1.0-1.5s */
>>>>>> +
>>>>>> +#define SSD1683_CTRL2_PARTIAL_REFRESH (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> +                       SSD16XX_CTRL2_ENABLE_ANALOG | \
>>>>>> +                       SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> +                       SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> +                       SSD1683_CTRL2_MODE2 | \
>>>>>> +                       SSD16XX_CTRL2_DISPLAY | \
>>>>>> +                       SSD16XX_CTRL2_DISABLE_ANALOG | \
>>>>>> +                       SSD16XX_CTRL2_DISABLE_CLK)  /* 0xFF, 
>>>>>> ~300-500ms */
>>>>>> +
>>>>>> +/*
>>>>>> + * Standalone LUT pre-load sequence (0x91 = ENABLE_CLK | LOAD_LUT 
>>>>>> | LOAD_TEMPERATURE |
>>>>>> + *                                          DISABLE_CLK).
>>>>>> + * Pre-loads the OTP LUT without triggering a display update. 
>>>>>> Required for
>>>>>> + * FAST refresh mode (0xC7) which omits LOAD_LUT from each update 
>>>>>> cycle.
>>>>>> + */
>>>>>> +#define SSD1683_CTRL2_LOAD_TEMP_LUT (SSD16XX_CTRL2_ENABLE_CLK | \
>>>>>> +                         SSD16XX_CTRL2_LOAD_LUT | \
>>>>>> +                         SSD16XX_CTRL2_LOAD_TEMPERATURE | \
>>>>>> +                         SSD16XX_CTRL2_DISABLE_CLK)  /* 0xB1 */
>>>>>> +
>>>>>> +MODULE_IMPORT_NS("DMA_BUF");
>>>>>> +
>>>>>> +enum ssd16xx_controller {
>>>>>> +    SSD1683 = 1,
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_model {
>>>>>> +    GDEY042T81 = 1,
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_refresh_mode {
>>>>>> +    SSD16XX_REFRESH_PARTIAL = 0,  /* Partial refresh (~300-500ms) */
>>>>>> +    SSD16XX_REFRESH_FULL,         /* Full refresh (~1.5-2s) */
>>>>>> +    SSD16XX_REFRESH_FAST,         /* Fast refresh, skip temp load 
>>>>>> (~1.0-1.5s) */
>>>>>> +};
>>>>>> +
>>>>>> +enum ssd16xx_color_mode {
>>>>>> +    SSD16XX_COLOR_MODE_BW = 0,     /* Black/white only; RED RAM 
>>>>>> always bypassed */
>>>>>> +    SSD16XX_COLOR_MODE_3COLOR = 1, /* 3-colour BWR; RED RAM used 
>>>>>> for red pixels */
>>>>>> +};
>>>>>> +
>>>>>> +/* Border waveform enum indices (0-9); mapped to HW bytes via
>>>>>> + * controller_cfg->border_waveform_table[]
>>>>>> + */
>>>>>> +enum ssd16xx_border_waveform {
>>>>>> +    SSD16XX_BORDER_LUT0 = 0,  /* GS Transition LUT0 (black) */
>>>>>> +    SSD16XX_BORDER_LUT1,      /* GS Transition LUT1 (white) */
>>>>>> +    SSD16XX_BORDER_LUT2,      /* GS Transition LUT2 (black) */
>>>>>> +    SSD16XX_BORDER_LUT3,      /* GS Transition LUT3 (gray) */
>>>>>> +    SSD16XX_BORDER_VSS,       /* Fix Level VSS (black) */
>>>>>> +    SSD16XX_BORDER_VSH1,      /* Fix Level VSH1 (black) */
>>>>>> +    SSD16XX_BORDER_VSL,       /* Fix Level VSL (white) */
>>>>>> +    SSD16XX_BORDER_VSH2,      /* Fix Level VSH2 (black) */
>>>>>> +    SSD16XX_BORDER_VCOM,      /* Follow VCOM (preserve) */
>>>>>> +    SSD16XX_BORDER_HIZ,       /* HiZ (floating, default) */
>>>>>> +};
>>>>>> +
>>>>>> +/* SSD1683/SSD1680 border waveform byte encoding for command 0x3C */
>>>>>> +static const u8 ssd1683_border_waveform_table[] = {
>>>>>> +    [SSD16XX_BORDER_LUT0] = SSD1683_BORDER_WAVEFORM_LUT0,
>>>>>> +    [SSD16XX_BORDER_LUT1] = SSD1683_BORDER_WAVEFORM_LUT1,
>>>>>> +    [SSD16XX_BORDER_LUT2] = SSD1683_BORDER_WAVEFORM_LUT2,
>>>>>> +    [SSD16XX_BORDER_LUT3] = SSD1683_BORDER_WAVEFORM_LUT3,
>>>>>> +    [SSD16XX_BORDER_VSS]  = SSD1683_BORDER_WAVEFORM_FIXLVL_VSS,
>>>>>> +    [SSD16XX_BORDER_VSH1] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSH1,
>>>>>> +    [SSD16XX_BORDER_VSL]  = SSD1683_BORDER_WAVEFORM_FIXLVL_VSL,
>>>>>> +    [SSD16XX_BORDER_VSH2] = SSD1683_BORDER_WAVEFORM_FIXLVL_VSH2,
>>>>>> +    [SSD16XX_BORDER_VCOM] = SSD1683_BORDER_WAVEFORM_VCOM,
>>>>>> +    [SSD16XX_BORDER_HIZ]  = SSD1683_BORDER_WAVEFORM_HIZ,
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_controller_config {
>>>>>> +    u16 max_width;
>>>>>> +    u16 max_height;
>>>>>> +    u8 ram_x_address_bits;
>>>>>> +    u8 ram_y_address_bits;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * has_temp_sensor_ctrl: controller supports command 0x18 
>>>>>> (Temperature
>>>>>> +     * Sensor Selection).  Present in SSD1683/SSD1680; absent in 
>>>>>> SSD1673
>>>>>> +     * which uses command 0x1A (direct temperature write) instead.
>>>>>> +     */
>>>>>> +    bool has_temp_sensor_ctrl;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Deep sleep mode byte values for command 0x10.
>>>>>> +     *   deep_sleep_mode_level1: lower-power sleep, RAM content 
>>>>>> retained
>>>>>> +     *     (MODE_1 on SSD1683/SSD1680; used for runtime idle / 
>>>>>> app- close).
>>>>>> +     *   deep_sleep_mode_level2: maximum power savings, RAM may 
>>>>>> be lost
>>>>>> +     *     (MODE_2 on SSD1683/SSD1680; used for system suspend).
>>>>>> +     * Chips with a single sleep mode set both fields to the same 
>>>>>> value.
>>>>>> +     */
>>>>>> +    u8 deep_sleep_mode_level1;
>>>>>> +    u8 deep_sleep_mode_level2;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * border_waveform_table: chip-specific byte values for the 
>>>>>> 10 logical
>>>>>> +     * border waveform modes (indexed by enum 
>>>>>> ssd16xx_border_waveform).
>>>>>> +     * The encoding of command 0x3C differs between SSD1683/ 
>>>>>> SSD1680 and
>>>>>> +     * SSD1673, so each controller provides its own translation 
>>>>>> table.
>>>>>> +     */
>>>>>> +    const u8 *border_waveform_table;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Display Update Control 1 (cmd 0x21) byte 1 values.
>>>>>> +     * ctrl1_normal:         both BW and RED RAMs participate in 
>>>>>> the waveform.
>>>>>> +     * ctrl1_bypass_red_ram: RED RAM bypassed; waveform driven 
>>>>>> from BW RAM only.
>>>>>> +     * SSD1673 has no RED RAM so both fields carry the same value.
>>>>>> +     */
>>>>>> +    u8 ctrl1_normal;
>>>>>> +    u8 ctrl1_bypass_red_ram;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Display Update Control 2 (cmd 0x22) composite sequences 
>>>>>> for each
>>>>>> +     * refresh mode (indexed by enum ssd16xx_refresh_mode) and the
>>>>>> +     * standalone LUT pre-load sequence used before fast refresh.
>>>>>> +     * Values differ between SSD1683/SSD1680 and SSD1673 (MODE2 
>>>>>> bit, etc.).
>>>>>> +     */
>>>>>> +    u8 ctrl2_refresh[3];     /* indexed by 
>>>>>> SSD16XX_REFRESH_PARTIAL/ FULL/FAST */
>>>>>> +    u8 ctrl2_load_temp_lut;  /* standalone LUT pre-load (no 
>>>>>> display update) */
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_panel_config {
>>>>>> +    /* Data Entry Mode - controls X/Y increment direction for 
>>>>>> landscape (0°) */
>>>>>> +    u8 data_entry_mode;
>>>>>> +
>>>>>> +    /* Driver Output Control - third byte (scan direction) */
>>>>>> +    u8 driver_output_ctrl_byte3;
>>>>>> +
>>>>>> +    /* Default refresh mode for this panel */
>>>>>> +    enum ssd16xx_refresh_mode default_refresh_mode;
>>>>>> +
>>>>>> +    /* Default border waveform during clear/init (enum index 0-9) */
>>>>>> +    enum ssd16xx_border_waveform default_border_waveform_init;
>>>>>> +
>>>>>> +    /* Default border waveform during display updates (enum index 
>>>>>> 0-9) */
>>>>>> +    enum ssd16xx_border_waveform default_border_waveform_update;
>>>>>> +
>>>>>> +    /* Whether to re-send border waveform command before each 
>>>>>> display update */
>>>>>> +    bool default_border_refresh_on_every_update;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Default clear-on-init behaviour.
>>>>>> +     * -1=disabled, 0=partial, 1=full, 2=fast (matches enum 
>>>>>> ssd16xx_refresh_mode)
>>>>>> +     */
>>>>>> +    int default_clear_on_init;
>>>>>> +
>>>>>> +    /* Default clear-on-close behaviour (-1=disabled, 0=partial, 
>>>>>> 1=full, 2=fast) */
>>>>>> +    int default_clear_on_close;
>>>>>> +
>>>>>> +    /* Default clear-on-disable behaviour (-1=disabled, 
>>>>>> 0=partial, 1=full, 2=fast) */
>>>>>> +    int default_clear_on_disable;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Default refresh-mode-init: -1=disabled, else skip baseline 
>>>>>> establishment
>>>>>> +     * and start directly in this refresh mode.
>>>>>> +     */
>>>>>> +    int default_refresh_mode_init;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Whether this panel has a physical red colour plane (3- 
>>>>>> colour BWR).
>>>>>> +     * false: 2-colour black/white only; the RED RAM is always 
>>>>>> bypassed.
>>>>>> +     * true:  3-colour panel; full-refresh writes to the RED RAM 
>>>>>> so that
>>>>>> +     *        red pixels are driven through the red waveform.
>>>>>> +     */
>>>>>> +    bool red_supported;
>>>>>> +
>>>>>> +    /* Panel-specific display mode (resolution and physical 
>>>>>> dimensions) */
>>>>>> +    const struct drm_display_mode *mode;
>>>>>> +};
>>>>>> +
>>>>>> +struct ssd16xx_panel {
>>>>>
>>>>> Better call this 'struct ssd16xx_device' and the rsp variables 
>>>>> 'ssd16xx'.  As mentioned, the name 'panel' already has a specific 
>>>>> meaning in DRM.
>>>>>
>>>>
>>>> Alright I can do that, I thought folks won't confuse it since this 
>>>> is not importing drm_panel struct.
>>>>
>>>>>
>>>>>> +    struct drm_device drm;
>>>>>> +
>>>>>> +    struct drm_plane primary_plane;
>>>>>> +    struct drm_crtc crtc;
>>>>>> +    struct drm_encoder encoder;
>>>>>> +    struct drm_connector connector;
>>>>>> +
>>>>>> +    struct spi_device *spi;
>>>>>> +    struct gpio_desc *reset;
>>>>>> +    struct gpio_desc *busy;
>>>>>> +    struct gpio_desc *dc;
>>>>>> +
>>>>>> +    enum ssd16xx_model model;
>>>>>> +    enum ssd16xx_controller controller;
>>>>>> +    const struct ssd16xx_controller_config *controller_cfg;
>>>>>> +    const struct ssd16xx_panel_config *panel_cfg;
>>>>>> +    struct drm_display_mode *mode;
>>>>>> +    u32 width;
>>>>>> +    u32 height;
>>>>>> +
>>>>>> +    bool initialized;
>>>>>> +    bool reinit_pending;      /* HW re-init required after 
>>>>>> orientation change */
>>>>>> +    bool init_refresh_pending; /* First frame after 
>>>>>> refresh_mode_init enable */
>>>>>> +    bool first_clear_done;  /* clear_on_init has already fired 
>>>>>> once */
>>>>>> +    bool display_cleared_on_deinit; /* Avoid redundant clear in 
>>>>>> atomic_disable/master_drop */
>>>>>> +
>>>>>> +    int orientation; /* Display orientation in degrees: 
>>>>>> 0/90/180/270 */
>>>>>> +    enum ssd16xx_refresh_mode refresh_mode; /* Active refresh 
>>>>>> mode */
>>>>>> +    enum ssd16xx_color_mode color_mode;     /* Active color mode 
>>>>>> (BW or 3-color) */
>>>>>> +    bool fast_lut_pending; /* LUT pre-load needed before next 
>>>>>> fast refresh */
>>>>>> +
>>>>>> +    /* Border waveform (as enum indices) */
>>>>>> +    int border_waveform_init_idx;   /* Border waveform during 
>>>>>> clear/ init */
>>>>>> +    int border_waveform_update_idx; /* Border waveform during 
>>>>>> display updates */
>>>>>> +    bool border_refresh_on_every_update; /* Re-send border cmd 
>>>>>> each display update */
>>>>>> +    bool border_waveform_pending;   /* One-shot: send border cmd 
>>>>>> on next update */
>>>>>> +
>>>>>> +    /* Display control */
>>>>>> +    int clear_on_init;    /* -1=disabled, 0=partial, 1=full, 
>>>>>> 2=fast */
>>>>>> +    int clear_on_close;   /* -1=disabled, 0=partial, 1=full, 
>>>>>> 2=fast */
>>>>>> +    int clear_on_disable; /* -1=disabled, 0=partial, 1=full, 
>>>>>> 2=fast */
>>>>>> +    int refresh_mode_init; /* -1=disabled, else use this mode for 
>>>>>> the first frame */
>>>>>> +
>>>>>> +    u8  *tx_buf;     /* 1bpp frame buffer (mono + white) */
>>>>>> +    u8  *tx_red_buf; /* 1bpp red-channel buffer (3-color panels 
>>>>>> only) */
>>>>>> +    u16 *tx_buf9;    /* 9-bit SPI expansion buffer (3-wire mode 
>>>>>> only) */
>>>>>> +
>>>>>> +    struct drm_framebuffer *last_fb;        /* Last drawn FB for 
>>>>>> reinit redraws */
>>>>>> +    struct drm_property *rotation_property;
>>>>>> +    struct drm_property *refresh_mode_property;
>>>>>> +    struct drm_property *border_waveform_init_property;
>>>>>> +    struct drm_property *border_waveform_update_property;
>>>>>> +    struct drm_property *border_refresh_on_every_update_property;
>>>>>> +    struct drm_property *clear_on_init_property;
>>>>>> +    struct drm_property *clear_on_close_property;
>>>>>> +    struct drm_property *clear_on_disable_property;
>>>>>> +    struct drm_property *refresh_mode_init_property;
>>>>>> +    struct drm_property *color_mode_property;
>>>>>> +};
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *to_ssd16xx_panel(struct 
>>>>>> drm_device *drm)
>>>>>> +{
>>>>>> +    return container_of(drm, struct ssd16xx_panel, drm);
>>>>>> +}
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *crtc_to_ssd16xx_panel(struct 
>>>>>> drm_crtc *crtc)
>>>>>> +{
>>>>>> +    return container_of(crtc, struct ssd16xx_panel, crtc);
>>>>>> +}
>>>>>> +
>>>>>> +static inline struct ssd16xx_panel *plane_to_ssd16xx_panel(struct 
>>>>>> drm_plane *plane)
>>>>>> +{
>>>>>> +    return container_of(plane, struct ssd16xx_panel, primary_plane);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct ssd16xx_controller_config 
>>>>>> ssd16xx_controller_configs[] = {
>>>>>> +    [SSD1683] = {
>>>>>> +        .max_width = 400,
>>>>>> +        .max_height = 300,
>>>>>> +        .ram_x_address_bits = 8,
>>>>>> +        .ram_y_address_bits = 16,
>>>>>> +        .has_temp_sensor_ctrl    = true,
>>>>>> +        .deep_sleep_mode_level1  = SSD1683_DEEP_SLEEP_MODE_1,
>>>>>> +        .deep_sleep_mode_level2  = SSD1683_DEEP_SLEEP_MODE_2,
>>>>>> +        .border_waveform_table   = ssd1683_border_waveform_table,
>>>>>> +        .ctrl1_normal            = SSD1683_CTRL1_NORMAL,
>>>>>> +        .ctrl1_bypass_red_ram    = SSD1683_CTRL1_BYPASS_RED_RAM,
>>>>>> +        .ctrl2_refresh = {
>>>>>> +            [SSD16XX_REFRESH_PARTIAL] = 
>>>>>> SSD1683_CTRL2_PARTIAL_REFRESH,
>>>>>> +            [SSD16XX_REFRESH_FULL]    = SSD1683_CTRL2_FULL_REFRESH,
>>>>>> +            [SSD16XX_REFRESH_FAST]    = SSD1683_CTRL2_FAST_REFRESH,
>>>>>> +        },
>>>>>> +        .ctrl2_load_temp_lut     = SSD1683_CTRL2_LOAD_TEMP_LUT,
>>>>>> +    },
>>>>>> +};
>>>>>> +
>>>>>> +/* GDEY042T81: 4.2" 400x300 panel, 84.8x63.6mm active area */
>>>>>> +static const struct drm_display_mode gdey042t81_mode = {
>>>>>> +    DRM_SIMPLE_MODE(400, 300, 85, 64),
>>>>>> +};
>>>>>> +
>>>>>> +static const struct ssd16xx_panel_config ssd16xx_panel_configs[] = {
>>>>>> +    [GDEY042T81] = {
>>>>>> +        .data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC,
>>>>>> +        .driver_output_ctrl_byte3 = 
>>>>>> SSD16XX_DRIVER_OUTPUT_CTRL_DEFAULT,
>>>>>> +        .default_refresh_mode = SSD16XX_REFRESH_PARTIAL,
>>>>>> +        .default_border_waveform_init   = SSD16XX_BORDER_LUT1, /* 
>>>>>> white, clean clear */
>>>>>> +        .default_border_waveform_update = SSD16XX_BORDER_HIZ, /* 
>>>>>> floating, preserve */
>>>>>> +        .default_border_refresh_on_every_update = false,
>>>>>> +        .default_clear_on_init    = -1,
>>>>>> +        .default_clear_on_close   = -1,
>>>>>> +        .default_clear_on_disable = -1,
>>>>>> +        .default_refresh_mode_init = SSD16XX_REFRESH_FULL,
>>>>>> +        .red_supported = false,  /* 2-colour black/white panel */
>>>>>> +        .mode = &gdey042t81_mode,
>>>>>> +    },
>>>>>> +};
>>>>>> +
>>>>>> +static void ssd16xx_wait_for_panel(struct ssd16xx_panel *panel,
>>>>>> +                   int *err)
>>>>>> +{
>>>>>> +    unsigned long timeout_jiffies = jiffies +
>>>>>> +        msecs_to_jiffies(SSD16XX_BUSY_WAIT_TIMEOUT_MS);
>>>>>> +    unsigned long start_ms = jiffies_to_msecs(jiffies);
>>>>>> +    int busy_val;
>>>>>> +
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>
>>>>> This is good. It'll simplify error handling in other places.
>>>>>
>>>>>> +
>>>>>> +    busy_val = gpiod_get_value_cansleep(panel->busy);
>>>>>> +    drm_dbg(&panel->drm, "BUSY initial value: %d\n", busy_val);
>>>>>> +
>>>>>> +    while (gpiod_get_value_cansleep(panel->busy) == 1) {
>>>>>> +        if (time_after(jiffies, timeout_jiffies)) {
>>>>>> +            drm_err(&panel->drm, "Busy wait timed out after 
>>>>>> %lums\n",
>>>>>> +                jiffies_to_msecs(jiffies) - start_ms);
>>>>>> +            *err = -ETIMEDOUT;
>>>>>> +            return;
>>>>>> +        }
>>>>>> +        usleep_range(100, 200);
>>>>>> +    }
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "BUSY became ready after %lums\n",
>>>>>> +        jiffies_to_msecs(jiffies) - start_ms);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_spi_sync(struct spi_device *spi, struct 
>>>>>> spi_message *msg,
>>>>>> +                 int *err)
>>>>>> +{
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    ret = spi_sync(spi, msg);
>>>>>> +    if (ret < 0)
>>>>>> +        *err = ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_cmd(struct ssd16xx_panel *panel, u8 cmd,
>>>>>> +                 int *err)
>>>>>> +{
>>>>>> +    u16 word;
>>>>>> +    struct spi_transfer xfer = {};
>>>>>> +    struct spi_message msg;
>>>>>> +
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    spi_message_init(&msg);
>>>>>> +    spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> +    if (panel->dc) {
>>>>>> +        /* 4-wire SPI: D/C# GPIO low selects command mode */
>>>>>> +        xfer.tx_buf = &cmd;
>>>>>> +        xfer.len = 1;
>>>>>> +        gpiod_set_value_cansleep(panel->dc, 0);
>>>>>> +    } else {
>>>>>> +        /*
>>>>>> +         * 3-wire SPI (9-bit): bit 8 is the D/C# bit.
>>>>>> +         * D/C# = 0 means the following 8 bits are a command.
>>>>>> +         */
>>>>>> +        word = cmd; /* bit 8 = 0 for command */
>>>>>> +        xfer.tx_buf = &word;
>>>>>> +        xfer.len = sizeof(u16);
>>>>>> +        xfer.bits_per_word = 9;
>>>>>> +    }
>>>>>> +
>>>>>> +    ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_data(struct ssd16xx_panel *panel, u8 data,
>>>>>> +                  int *err)
>>>>>> +{
>>>>>> +    u16 word;
>>>>>> +    struct spi_transfer xfer = {};
>>>>>> +    struct spi_message msg;
>>>>>> +
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    spi_message_init(&msg);
>>>>>> +    spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> +    if (panel->dc) {
>>>>>> +        /* 4-wire SPI: D/C# GPIO high selects data mode */
>>>>>> +        xfer.tx_buf = &data;
>>>>>> +        xfer.len = 1;
>>>>>> +        gpiod_set_value_cansleep(panel->dc, 1);
>>>>>> +    } else {
>>>>>> +        /*
>>>>>> +         * 3-wire SPI (9-bit): bit 8 is the D/C# bit.
>>>>>> +         * D/C# = 1 means the following 8 bits are data.
>>>>>> +         */
>>>>>> +        word = 0x100 | data;
>>>>>> +        xfer.tx_buf = &word;
>>>>>> +        xfer.len = sizeof(u16);
>>>>>> +        xfer.bits_per_word = 9;
>>>>>> +    }
>>>>>> +
>>>>>> +    ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_x_param(struct ssd16xx_panel *panel, u16 x,
>>>>>> +                 int *err)
>>>>>> +{
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    if (panel->controller_cfg->ram_x_address_bits == 8) {
>>>>>> +        ssd16xx_send_data(panel, (u8)x, err);
>>>>>> +    } else {
>>>>>> +        ssd16xx_send_data(panel, x & 0xFF, err);
>>>>>> +        ssd16xx_send_data(panel, (x >> 8) & 0xFF, err);
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_y_param(struct ssd16xx_panel *panel, u16 y,
>>>>>> +                 int *err)
>>>>>> +{
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    if (panel->controller_cfg->ram_y_address_bits == 8) {
>>>>>> +        ssd16xx_send_data(panel, (u8)y, err);
>>>>>> +    } else {
>>>>>> +        ssd16xx_send_data(panel, y & 0xFF, err);
>>>>>> +        ssd16xx_send_data(panel, (y >> 8) & 0xFF, err);
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_send_data_bulk(struct ssd16xx_panel *panel,
>>>>>> +                   const u8 *data, size_t len,
>>>>>> +                   int *err)
>>>>>> +{
>>>>>> +    struct spi_transfer xfer = {};
>>>>>> +    struct spi_message msg;
>>>>>> +
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    if (!data || !len)
>>>>>> +        return;
>>>>>> +
>>>>>> +    spi_message_init(&msg);
>>>>>> +    spi_message_add_tail(&xfer, &msg);
>>>>>> +
>>>>>> +    if (panel->dc) {
>>>>>> +        /* 4-wire SPI: D/C# GPIO high selects data mode */
>>>>>> +        xfer.tx_buf = data;
>>>>>> +        xfer.len = len;
>>>>>> +        gpiod_set_value_cansleep(panel->dc, 1);
>>>>>> +        ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +    } else {
>>>>>> +        /* 3-wire (9-bit): expand u8 → u16 with D/C#=1 in bit 8. */
>>>>>> +        size_t i;
>>>>>> +        u16 *buf = panel->tx_buf9;
>>>>>> +
>>>>>> +        for (i = 0; i < len; i++)
>>>>>> +            buf[i] = 0x100 | data[i];
>>>>>> +
>>>>>> +        xfer.tx_buf = buf;
>>>>>> +        xfer.len = len * sizeof(u16);
>>>>>> +        xfer.bits_per_word = 9;
>>>>>> +        ssd16xx_spi_sync(panel->spi, &msg, err);
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_display_update(struct ssd16xx_panel *panel,
>>>>>> +                   u8 ctrl1_byte1, u8 ctrl1_byte2, u8 ctrl2_mode,
>>>>>> +                   int *err)
>>>>>> +{
>>>>>> +    if (*err)
>>>>>> +        return;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm,
>>>>>> +        "display_update: Setting ctrl1=0x%02x,0x%02x mode=0x%02x\n",
>>>>>> +        ctrl1_byte1, ctrl1_byte2, ctrl2_mode);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1, 
>>>>>> err);
>>>>>> +    ssd16xx_send_data(panel, ctrl1_byte1, err);
>>>>>> +    ssd16xx_send_data(panel, ctrl1_byte2, err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2, 
>>>>>> err);
>>>>>> +    ssd16xx_send_data(panel, ctrl2_mode, err);
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION, err);
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm,
>>>>>> +        "display_update: Master activation sent, waiting...\n");
>>>>>> +
>>>>>> +    ssd16xx_wait_for_panel(panel, err);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_hw_reset(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    gpiod_set_value_cansleep(panel->reset, 1);
>>>>>> +    usleep_range(10000, 11000);
>>>>>> +    gpiod_set_value_cansleep(panel->reset, 0);
>>>>>> +    usleep_range(10000, 11000);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_preload_fast_lut() - pre-load the OTP LUT for fast 
>>>>>> refresh mode.
>>>>>> + *
>>>>>> + * Fast refresh (CTRL2 = 0xC7) omits the LOAD_LUT step on every 
>>>>>> update to save
>>>>>> + * time.  It relies on the LUT being loaded upfront via this 
>>>>>> standalone sequence
>>>>>> + * (CTRL2 = 0xB1: ENABLE_CLK | LOAD_LUT | 
>>>>>> SSD16XX_CTRL2_LOAD_TEMPERATURE | DISABLE_CLK,
>>>>>> + *  no display update).
>>>>>> + *
>>>>>> + * Must be called when:
>>>>>> + *   a) hw_init runs with refresh_mode == FAST, and
>>>>>> + *   b) switching to fast refresh from a mode that did not leave 
>>>>>> a valid Mode1
>>>>>> + *      LUT in the controller (i.e. previous mode was not FULL 
>>>>>> refresh, which
>>>>>> + *      carries LOAD_LUT in its own CTRL2 sequence).
>>>>>> + */
>>>>>> +static int ssd16xx_preload_fast_lut(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    int err = 0;
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1, 
>>>>>> &err);
>>>>>> +    ssd16xx_send_data(panel, panel->controller_cfg- 
>>>>>> >ctrl1_bypass_red_ram, &err);
>>>>>> +    ssd16xx_send_data(panel, SSD16XX_CTRL1_BYTE2_DEFAULT, &err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2, 
>>>>>> &err);
>>>>>> +    ssd16xx_send_data(panel, panel->controller_cfg- 
>>>>>> >ctrl2_load_temp_lut, &err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION, &err);
>>>>>> +    ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> +    return err;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_hw_init(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    int err = 0;
>>>>>> +    u16 ram_height = panel->controller_cfg->max_height;
>>>>>> +    u8 data_entry_mode;
>>>>>> +
>>>>>> +    ssd16xx_hw_reset(panel);
>>>>>> +
>>>>>> +    /* Software reset */
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_SW_RESET, &err);
>>>>>> +    ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> +    /* Driver output control (0x01): MUX ratio and scan 
>>>>>> direction. */
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DRIVER_OUTPUT_CONTROL, 
>>>>>> &err);
>>>>>> +    ssd16xx_send_y_param(panel, ram_height - 1, &err);
>>>>>> +    ssd16xx_send_data(panel, panel->panel_cfg- 
>>>>>> >driver_output_ctrl_byte3, &err);
>>>>>> +
>>>>>> +    /* Internal temperature sensor (SSD1683/SSD1680 only; not 
>>>>>> present in SSD1673) */
>>>>>> +    if (panel->controller_cfg->has_temp_sensor_ctrl) {
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD1683_CMD_TEMPERATURE_SENSOR_CONTROL, &err);
>>>>>> +        ssd16xx_send_data(panel, SSD1683_TEMP_SENSOR_INTERNAL, 
>>>>>> &err);
>>>>>> +    }
>>>>>> +
>>>>>> +    /*
>>>>>> +     * For FAST refresh mode, pre-load the LUT once here during 
>>>>>> initialization.
>>>>>> +     * FAST mode ctrl2 (0xC7) omits LOAD_LUT on every update for 
>>>>>> speed, so the
>>>>>> +     * LUT must be loaded upfront. FULL (0xF7) and PARTIAL (0xFF) 
>>>>>> load LUT on
>>>>>> +     * every update, so no preload is needed for those modes.
>>>>>> +     */
>>>>>> +    if (panel->refresh_mode == SSD16XX_REFRESH_FAST) {
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_DISPLAY_UPDATE_CONTROL1, &err);
>>>>>> +        ssd16xx_send_data(panel, panel->controller_cfg- 
>>>>>> >ctrl1_bypass_red_ram, &err);
>>>>>> +        ssd16xx_send_data(panel, SSD16XX_CTRL1_BYTE2_DEFAULT, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_DISPLAY_UPDATE_CONTROL2, &err);
>>>>>> +        ssd16xx_send_data(panel, panel->controller_cfg- 
>>>>>> >ctrl2_load_temp_lut, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, SSD16XX_CMD_MASTER_ACTIVATION, 
>>>>>> &err);
>>>>>> +        ssd16xx_wait_for_panel(panel, &err);
>>>>>> +    }
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Set Data Entry Mode (0x11) based on orientation. This 
>>>>>> controls
>>>>>> +     * how the RAM address counter auto-advances after each byte 
>>>>>> write.
>>>>>> +     *
>>>>>> +     * Implementation uses two data entry modes:
>>>>>> +     *   - 90°/180° use XDEC_YDEC (0x00): X--, Y-- with cursor at 
>>>>>> (max, max)
>>>>>> +     *   - 0°/270° use XINC_YINC (0x03): X++, Y++ with cursor at 
>>>>>> (0, 0)
>>>>>> +     *
>>>>>> +     * The convert_fb_to_1bpp packing is grouped by physical layout:
>>>>>> +     *   - Portrait orientations (90°/270°): column-major packing
>>>>>> +     *   - Landscape orientations (0°/180°): row-major packing
>>>>>> +     *
>>>>>> +     * Final scan direction and image orientation are controlled 
>>>>>> by the
>>>>>> +     * combination of data entry mode and RAM cursor position set 
>>>>>> in fb_dirty.
>>>>>> +     *
>>>>>> +     * The RAM address window and cursor are NOT set here; fb_dirty
>>>>>> +     * always programmes them (with the correct end-before-start 
>>>>>> order
>>>>>> +     * for decrement modes) immediately before writing frame data.
>>>>>> +     */
>>>>>> +    switch (panel->orientation) {
>>>>>
>>>>> As mentioned, use the connector property instead.
>>>>>
>>>>>> +    case 90:
>>>>>> +    case 180:
>>>>>> +        data_entry_mode = SSD16XX_DATA_ENTRY_XDEC_YDEC;
>>>>>> +        break;
>>>>>> +    default: /* 0°/270° */
>>>>>> +        data_entry_mode = SSD16XX_DATA_ENTRY_XINC_YINC;
>>>>>> +        break;
>>>>>> +    }
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_DATA_ENTRY_MODE, &err);
>>>>>> +    ssd16xx_send_data(panel, data_entry_mode, &err);
>>>>>> +    drm_dbg(&panel->drm, "hw_init: orientation=%u° 
>>>>>> data_entry=0x%02x\n",
>>>>>> +        panel->orientation, data_entry_mode);
>>>>>> +
>>>>>> +    ssd16xx_wait_for_panel(panel, &err);
>>>>>> +
>>>>>> +    if (err)
>>>>>> +        drm_err(&panel->drm, "Hardware initialization failed: 
>>>>>> %d\n", err);
>>>>>> +
>>>>>> +    return err;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display by writing all-white to both BW and RED RAM.
>>>>>> + * The ctrl2 argument selects the waveform (full/partial/fast 
>>>>>> refresh).
>>>>>> + * Border waveform is set to init value before clearing, then 
>>>>>> restored
>>>>>> + * to the update value to preserve the border during subsequent 
>>>>>> updates.
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display(struct ssd16xx_panel *panel, u8 
>>>>>> ctrl2)
>>>>>> +{
>>>>>> +    const u8 *bw_tbl = panel->controller_cfg->border_waveform_table;
>>>>>> +    int err = 0;
>>>>>> +    unsigned int data_size = (panel->width * panel->height) / 8;
>>>>>> +    u8 *white_buffer = panel->tx_buf;
>>>>>> +
>>>>>> +    memset(white_buffer, 0xFF, data_size);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> +    ssd16xx_send_x_param(panel, 0x00, &err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> +    ssd16xx_send_y_param(panel, 0x00, &err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_WRITE_RAM_BW, &err);
>>>>>> +    ssd16xx_send_data_bulk(panel, white_buffer, data_size, &err);
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> +    ssd16xx_send_data_bulk(panel, white_buffer, data_size, &err);
>>>>>> +
>>>>>> +    /* Set border waveform for the clear operation */
>>>>>> +    drm_dbg(&panel->drm, "clear_display: Set border init 
>>>>>> waveform: 0x%02x\n",
>>>>>> +        bw_tbl[panel->border_waveform_init_idx]);
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_BORDER_WAVEFORM_CONTROL, 
>>>>>> &err);
>>>>>> +    ssd16xx_send_data(panel,
>>>>>> +              bw_tbl[panel->border_waveform_init_idx],
>>>>>> +              &err);
>>>>>> +
>>>>>> +    /* 3-colour mode: CTRL1_NORMAL (read both RAMs); BW mode: 
>>>>>> bypass RED. */
>>>>>> +    ssd16xx_display_update(panel,
>>>>>> +                   panel->color_mode == SSD16XX_COLOR_MODE_3COLOR
>>>>>> +                    ? panel->controller_cfg->ctrl1_normal
>>>>>> +                    : panel->controller_cfg->ctrl1_bypass_red_ram,
>>>>>> +                   SSD16XX_CTRL1_BYTE2_DEFAULT, ctrl2, &err);
>>>>>> +
>>>>>> +    /* Restore border waveform to update/preservation value */
>>>>>> +    drm_dbg(&panel->drm, "clear_display: Restored border update 
>>>>>> waveform: 0x%02x\n",
>>>>>> +        bw_tbl[panel->border_waveform_update_idx]);
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_BORDER_WAVEFORM_CONTROL, 
>>>>>> &err);
>>>>>> +    ssd16xx_send_data(panel,
>>>>>> + bw_tbl[panel->border_waveform_update_idx],
>>>>>> +              &err);
>>>>>> +
>>>>>> +    return err;
>>>>>> +}
>>>>>> +
>>>>>> +static u8 ssd16xx_refresh_mode_to_ctrl2(struct ssd16xx_panel *panel,
>>>>>> +                    enum ssd16xx_refresh_mode mode)
>>>>>> +{
>>>>>> +    if (mode < ARRAY_SIZE(panel->controller_cfg->ctrl2_refresh))
>>>>>> +        return panel->controller_cfg->ctrl2_refresh[mode];
>>>>>> +    return panel->controller_cfg- 
>>>>>> >ctrl2_refresh[SSD16XX_REFRESH_FULL];
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display on new DRM master open (if clear_on_init >= 0).
>>>>>> + * Guarded by panel->first_clear_done; master_drop resets it 
>>>>>> unconditionally
>>>>>> + * so each new client session gets a fresh clear.
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display_on_init(struct ssd16xx_panel 
>>>>>> *panel)
>>>>>> +{
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    if (panel->clear_on_init < 0 || panel->first_clear_done)
>>>>>> +        return 0;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "clear_on_init: running, mode=%d\n",
>>>>>> +        panel->clear_on_init);
>>>>>> +    ret = ssd16xx_clear_display(panel,
>>>>>> +                    ssd16xx_refresh_mode_to_ctrl2(panel, panel- 
>>>>>> >clear_on_init));
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    panel->first_clear_done = true;
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Clear display when the displaying client exits (if 
>>>>>> clear_on_close >= 0).
>>>>>> + * Called from ssd16xx_drm_master_drop().
>>>>>> + */
>>>>>> +static int ssd16xx_clear_display_on_exit(struct ssd16xx_panel 
>>>>>> *panel)
>>>>>> +{
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    if (panel->clear_on_close < 0)
>>>>>> +        return 0;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "clear_on_close: running, mode=%d\n",
>>>>>> +        panel->clear_on_close);
>>>>>> +    ret =  ssd16xx_clear_display(panel,
>>>>>> +                     ssd16xx_refresh_mode_to_ctrl2(panel, panel- 
>>>>>> >clear_on_close));
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_luma() - return ITU-R BT.601 luminance (0-255) 
>>>>>> for one pixel.
>>>>>> + *
>>>>>> + * For colour formats the result is (299*R + 587*G + 114*B) / 1000;
>>>>>> + * for luma-only formats the luma byte is returned directly.
>>>>>> + *
>>>>>> + * R1 is never passed here — it is already 1bpp and is handled 
>>>>>> directly by
>>>>>> + * the callers.
>>>>>> + */
>>>>>> +static u8 ssd16xx_pixel_luma(struct iosys_map *src,
>>>>>> +                 struct drm_framebuffer *fb,
>>>>>> +                 unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> +    switch (fb->format->format) {
>>>>>> +    case DRM_FORMAT_XRGB8888: {
>>>>>> +        u32 *line = (u32 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u32 px = line[x];
>>>>>> +        u8 r = (px >> 16) & 0xFF, g = (px >> 8) & 0xFF, b = px & 
>>>>>> 0xFF;
>>>>>> +
>>>>>> +        return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_RGB888: {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u8 r = line[x * 3], g = line[x * 3 + 1], b = line[x * 3 + 
>>>>>> 2];
>>>>>> +
>>>>>> +        return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_RGB565: {
>>>>>> +        u16 *line = (u16 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u16 px = line[x];
>>>>>> +        u8 r = ((px >> 11) & 0x1F) << 3;
>>>>>> +        u8 g = ((px >> 5) & 0x3F) << 2;
>>>>>> +        u8 b = (px & 0x1F) << 3;
>>>>>> +
>>>>>> +        return (u8)((299u * r + 587u * g + 114u * b) / 1000u);
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_R8: {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> +        return line[x];
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_NV12:
>>>>>> +    case DRM_FORMAT_NV16:
>>>>>> +        return ((u8 *)(src->vaddr))[y * fb->pitches[0] + x];
>>>>>> +    case DRM_FORMAT_YUYV: {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> +        return line[x * 2];
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_UYVY: {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> +        return line[x * 2 + 1];
>>>>>> +    }
>>>>>> +    default:
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_is_white() - test whether a pixel maps to white 
>>>>>> in 1bpp output.
>>>>>> + *
>>>>>> + * Uses fixed threshold of 127. Pixels with luma strictly greater 
>>>>>> than 127
>>>>>> + * are rendered white.
>>>>>> + */
>>>>>> +static bool ssd16xx_pixel_is_white(struct iosys_map *src,
>>>>>> +                   struct drm_framebuffer *fb,
>>>>>> +                   unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> +    /* R1 is already binarised; avoid the luma computation 
>>>>>> entirely. */
>>>>>> +    if (fb->format->format == DRM_FORMAT_R1) {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +
>>>>>> +        return !!(line[x / 8] & (1 << (7 - (x % 8))));
>>>>>> +    }
>>>>>> +    return ssd16xx_pixel_luma(src, fb, x, y) > 127;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_pixel_is_red() - test whether a pixel is dominated by 
>>>>>> the red channel.
>>>>>> + *
>>>>>> + * Only meaningful for formats that carry RGB information 
>>>>>> (XRGB8888, RGB888,
>>>>>> + * RGB565).  For luma-only and monochrome formats there is no red 
>>>>>> channel, so
>>>>>> + * the function always returns false; callers should use 
>>>>>> ssd16xx_pixel_is_white()
>>>>>> + * to obtain the BW value for those formats.
>>>>>> + *
>>>>>> + * Returns true when the red component exceeds 50% intensity AND 
>>>>>> is strictly
>>>>>> + * greater than both green and blue (dominant red hue).
>>>>>> + */
>>>>>> +static bool ssd16xx_pixel_is_red(struct iosys_map *src,
>>>>>> +                 struct drm_framebuffer *fb,
>>>>>> +                 unsigned int x, unsigned int y)
>>>>>> +{
>>>>>> +    u32 format = fb->format->format;
>>>>>> +
>>>>>> +    switch (format) {
>>>>>> +    case DRM_FORMAT_XRGB8888: {
>>>>>> +        u32 *line = (u32 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u32 px = line[x];
>>>>>> +        u8 r = (px >> 16) & 0xFF;
>>>>>> +        u8 g = (px >> 8) & 0xFF;
>>>>>> +        u8 b = px & 0xFF;
>>>>>> +
>>>>>> +        return r > 127 && r > g && r > b;
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_RGB888: {
>>>>>> +        u8 *line = (u8 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u8 r = line[x * 3];
>>>>>> +        u8 g = line[x * 3 + 1];
>>>>>> +        u8 b = line[x * 3 + 2];
>>>>>> +
>>>>>> +        return r > 127 && r > g && r > b;
>>>>>> +    }
>>>>>> +    case DRM_FORMAT_RGB565: {
>>>>>> +        u16 *line = (u16 *)(src->vaddr + y * fb->pitches[0]);
>>>>>> +        u16 px = line[x];
>>>>>> +        u8 r = ((px >> 11) & 0x1F) << 3;
>>>>>> +        u8 g = ((px >> 5) & 0x3F) << 2;
>>>>>> +        u8 b = (px & 0x1F) << 3;
>>>>>> +
>>>>>> +        return r > 127 && r > g && r > b;
>>>>>> +    }
>>>>>> +    default:
>>>>>> +        return false; /* No colour channel information */
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_convert_fb_to_3color() - split a framebuffer into BW 
>>>>>> and RED planes.
>>>>>> + * @bw_dst:  output buffer for the black/white RAM plane 
>>>>>> (1=white, 0=black)
>>>>>> + * @red_dst: output buffer for the red RAM plane (1=red, 0=not red)
>>>>>> + * @src:     mapped framebuffer memory
>>>>>> + * @fb:      DRM framebuffer descriptor
>>>>>> + * @rect:    region to convert (must be aligned to 8-pixel 
>>>>>> boundaries)
>>>>>> + *
>>>>>> + * Each output buffer must be at least rect_width/8 * rect_height 
>>>>>> bytes.
>>>>>> + * Pixels are classified as:
>>>>>> + *   - red:   written to red_dst as 1, bw_dst as 0 (black)
>>>>>> + *   - white: written to bw_dst as 1, red_dst as 0
>>>>>> + *   - black: written to both as 0
>>>>>> + *
>>>>>> + * For monochrome formats (R1) where no colour information is 
>>>>>> available the
>>>>>> + * source data is copied verbatim to bw_dst and red_dst is 
>>>>>> cleared to 0xFF
>>>>>> + * (all-white = no red pixels).
>>>>>> + */
>>>>>> +static void ssd16xx_convert_fb_to_3color(u8 *bw_dst, u8 *red_dst,
>>>>>> +                     struct iosys_map *src,
>>>>>> +                     struct drm_framebuffer *fb,
>>>>>> +                     struct drm_rect *rect)
>>>>>> +{
>>>>>> +    unsigned int x, y;
>>>>>> +    u8 bw_byte = 0, red_byte = 0;
>>>>>> +    unsigned int bit_pos = 0;
>>>>>> +    unsigned int dst_idx = 0;
>>>>>> +
>>>>>> +    drm_dbg(fb->dev,
>>>>>> +        "convert_3color: fmt=%p4cc rect=(%d,%d)-(%d,%d) path=%s\n",
>>>>>> +        &fb->format->format,
>>>>>> +        rect->x1, rect->y1, rect->x2, rect->y2,
>>>>>> +        fb->format->format == DRM_FORMAT_R1 ? "R1-direct" : 
>>>>>> "color- pixel");
>>>>>> +
>>>>>> +    /*
>>>>>> +     * R1 is already monochrome — no colour channel exists.
>>>>>> +     * Copy BW data directly and leave the red plane all-white 
>>>>>> (transparent).
>>>>>> +     */
>>>>>> +    if (fb->format->format == DRM_FORMAT_R1) {
>>>>>> +        unsigned int src_pitch = fb->pitches[0];
>>>>>> +        unsigned int width_bytes = drm_rect_width(rect) / 8;
>>>>>> +        unsigned int data_size = width_bytes * 
>>>>>> drm_rect_height(rect);
>>>>>> +
>>>>>> +        for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +            u8 *line = src->vaddr + y * src_pitch + (rect->x1 / 8);
>>>>>> +
>>>>>> +            memcpy(bw_dst + dst_idx, line, width_bytes);
>>>>>> +            dst_idx += width_bytes;
>>>>>> +        }
>>>>>> +        memset(red_dst, 0xFF, data_size); /* 0xFF = all white: no 
>>>>>> red pixels */
>>>>>> +        return;
>>>>>> +    }
>>>>>> +
>>>>>> +    /* Use fixed threshold of 127 for grayscale to monochrome 
>>>>>> conversion. */
>>>>>> +    for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +        for (x = rect->x1; x < rect->x2; x++) {
>>>>>> +            bool is_red = ssd16xx_pixel_is_red(src, fb, x, y);
>>>>>> +
>>>>>> +            if (is_red)
>>>>>> +                red_byte |= (1 << (7 - bit_pos));
>>>>>> +            else if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> +                bw_byte |= (1 << (7 - bit_pos));
>>>>>> +            /* else: black pixel — both bits remain 0 */
>>>>>> +            if (++bit_pos == 8) {
>>>>>> +                bw_dst[dst_idx] = bw_byte;
>>>>>> +                red_dst[dst_idx] = red_byte;
>>>>>> +                dst_idx++;
>>>>>> +                bw_byte = 0;
>>>>>> +                red_byte = 0;
>>>>>> +                bit_pos = 0;
>>>>>> +            }
>>>>>> +        }
>>>>>> +
>>>>>> +        /* Flush any partial byte at the end of each row */
>>>>>> +        if (bit_pos > 0) {
>>>>>> +            bw_dst[dst_idx] = bw_byte;
>>>>>> +            red_dst[dst_idx] = red_byte;
>>>>>> +            dst_idx++;
>>>>>> +            bw_byte = 0;
>>>>>> +            red_byte = 0;
>>>>>> +            bit_pos = 0;
>>>>>> +        }
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_convert_r8_to_red_only() - map an R8 framebuffer to 
>>>>>> the RED RAM plane.
>>>>>> + *
>>>>>> + * Used when the panel has a physical red colour plane 
>>>>>> (red_supported == true)
>>>>>> + * and the framebuffer format is DRM_FORMAT_R8.  Pixels with 
>>>>>> value >= 128 are
>>>>>> + * treated as red ink; the BW RAM is set to all-white so that 
>>>>>> only red ink
>>>>>> + * appears on the white background.
>>>>>> + *
>>>>>> + * Hardware orientation is handled by the caller via RAM counter 
>>>>>> positioning;
>>>>>> + * data is written in normal row-major order here (same as 
>>>>>> convert_fb_to_3color).
>>>>>> + */
>>>>>> +static void ssd16xx_convert_r8_to_red_only(u8 *bw_dst, u8 *red_dst,
>>>>>> +                       struct iosys_map *src,
>>>>>> +                       struct drm_framebuffer *fb,
>>>>>> +                       struct drm_rect *rect)
>>>>>> +{
>>>>>> +    unsigned int src_pitch = fb->pitches[0];
>>>>>> +    unsigned int width = drm_rect_width(rect);
>>>>>> +    unsigned int height = drm_rect_height(rect);
>>>>>> +    unsigned int data_size = DIV_ROUND_UP(width, 8) * height;
>>>>>> +    unsigned int dst_idx = 0;
>>>>>> +    unsigned int x, y;
>>>>>> +    u8 red_byte = 0;
>>>>>> +    unsigned int bit_pos = 0;
>>>>>> +
>>>>>> +    /* BW RAM: all-white background - no black ink, only red ink 
>>>>>> shows */
>>>>>> +��   memset(bw_dst, 0xFF, data_size);
>>>>>> +
>>>>>> +    /* RED RAM: R8 >= 128 -> red ink (1-bit set) */
>>>>>> +    for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +        u8 *line = src->vaddr + y * src_pitch;
>>>>>> +
>>>>>> +        for (x = rect->x1; x < rect->x2; x++) {
>>>>>> +            if (line[x] >= 128)
>>>>>> +                red_byte |= (1 << (7 - bit_pos));
>>>>>> +            if (++bit_pos == 8) {
>>>>>> +                red_dst[dst_idx++] = red_byte;
>>>>>> +                red_byte = 0;
>>>>>> +                bit_pos = 0;
>>>>>> +            }
>>>>>> +        }
>>>>>> +        if (bit_pos > 0) {
>>>>>> +            red_dst[dst_idx++] = red_byte;
>>>>>> +            red_byte = 0;
>>>>>> +            bit_pos = 0;
>>>>>> +        }
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Convert framebuffer to 1-bit monochrome for e-paper display.
>>>>>> + *
>>>>>> + * Supported formats: XRGB8888, RGB888, RGB565, R8, NV12, NV16, 
>>>>>> YUYV, UYVY, R1.
>>>>>> + * For colour and luma formats, Otsu's global binarisation method 
>>>>>> computes an
>>>>>> + * optimal per-image threshold from the luminance histogram.
>>>>>> + * R1 is the controller's native format and bypasses conversion 
>>>>>> entirely.
>>>>>> + *
>>>>>> + * Output layout:
>>>>>> + *   0°/180°  landscape: row-major, left-to-right, top-to-bottom
>>>>>> + *   90°/270° CW portrait: column-major, rightmost column first
>>>>>> + */
>>>>>> +static void ssd16xx_convert_fb_to_1bpp(u8 *dst, struct iosys_map 
>>>>>> *src,
>>>>>> +                       struct drm_framebuffer *fb,
>>>>>> +                       struct drm_rect *rect,
>>>>>> +                       unsigned int orientation)
>>>>>> +{
>>>>>> +    u32 format = fb->format->format;
>>>>>> +    int x, y;
>>>>>> +    u8 byte = 0;
>>>>>> +    unsigned int bit_pos = 0;
>>>>>> +    unsigned int dst_idx = 0;
>>>>>> +
>>>>>> +    /* Use fixed threshold of 127 for grayscale to monochrome 
>>>>>> conversion. */
>>>>>> +    drm_dbg(fb->dev,
>>>>>> +        "convert_1bpp: fmt=%p4cc rect=(%d,%d)-(%d,%d) orient=%u° 
>>>>>> path=%s\n",
>>>>>> +        &fb->format->format,
>>>>>> +        rect->x1, rect->y1, rect->x2, rect->y2,
>>>>>> +        orientation,
>>>>>> +        (format == DRM_FORMAT_R1 && orientation == 0 && rect->x1 
>>>>>> % 8 == 0) ? "R1-fast" :
>>>>>> +        (orientation == 90 || orientation == 270) ? "portrait" : 
>>>>>> "landscape");
>>>>>> +
>>>>>> +    /*
>>>>>> +     * R1 fast path: 0° landscape with byte-aligned rect.
>>>>>> +     * R1 is already 1bpp so landscape rows map directly to 
>>>>>> output bytes via
>>>>>> +     * memcpy — no per-pixel computation needed. rect->x1 must be a
>>>>>> +     * multiple of 8 so that (rect->x1 / 8) gives the correct 
>>>>>> byte offset;
>>>>>> +     * if not, the generic pixel-by-pixel loop below handles non- 
>>>>>> aligned
>>>>>> +     * rects safely.
>>>>>> +     */
>>>>>> +    if (format == DRM_FORMAT_R1 && orientation == 0 && rect->x1 % 
>>>>>> 8 == 0) {
>>>>>> +        unsigned int src_pitch = fb->pitches[0];
>>>>>> +        unsigned int width_bytes = drm_rect_width(rect) / 8;
>>>>>> +
>>>>>> +        for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +            u8 *src_line = src->vaddr + y * src_pitch + (rect- 
>>>>>> >x1 / 8);
>>>>>> +
>>>>>> +            memcpy(dst + dst_idx, src_line, width_bytes);
>>>>>> +            dst_idx += width_bytes;
>>>>>> +        }
>>>>>> +        return;
>>>>>> +    }
>>>>>> +
>>>>>> +    switch (orientation) {
>>>>>> +    case 90:
>>>>>> +    case 270:
>>>>>> +        /*
>>>>>> +         * Portrait (90° or 270°): column-major packing.
>>>>>> +         * Each portrait source column becomes one physical RAM row.
>>>>>> +         * The data entry mode and cursor position control scan 
>>>>>> direction.
>>>>>> +         */
>>>>>> +        for (x = rect->x2 - 1; x >= (int)rect->x1; x--) {
>>>>>> +            for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +                if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> +                    byte |= (1 << (7 - bit_pos));
>>>>>> +                if (++bit_pos == 8) {
>>>>>> +                    dst[dst_idx++] = byte;
>>>>>> +                    byte = 0;
>>>>>> +                    bit_pos = 0;
>>>>>> +                }
>>>>>> +            }
>>>>>> +            if (bit_pos > 0) {
>>>>>> +                dst[dst_idx++] = byte;
>>>>>> +                byte = 0;
>>>>>> +                bit_pos = 0;
>>>>>> +            }
>>>>>> +        }
>>>>>> +        break;
>>>>>> +
>>>>>> +    case 0:
>>>>>> +    case 180:
>>>>>> +    default:
>>>>>> +        /*
>>>>>> +         * Landscape (0° or 180°): row-major packing.
>>>>>> +         * Each landscape source row becomes one physical RAM row.
>>>>>> +         * The data entry mode and cursor position control scan 
>>>>>> direction.
>>>>>> +         */
>>>>>> +        for (y = rect->y1; y < rect->y2; y++) {
>>>>>> +            for (x = rect->x1; x < rect->x2; x++) {
>>>>>> +                if (ssd16xx_pixel_is_white(src, fb, x, y))
>>>>>> +                    byte |= (1 << (7 - bit_pos));
>>>>>> +                if (++bit_pos == 8) {
>>>>>> +                    dst[dst_idx++] = byte;
>>>>>> +                    byte = 0;
>>>>>> +                    bit_pos = 0;
>>>>>> +                }
>>>>>> +            }
>>>>>> +            if (bit_pos > 0) {
>>>>>> +                dst[dst_idx++] = byte;
>>>>>> +                byte = 0;
>>>>>> +                bit_pos = 0;
>>>>>> +            }
>>>>>> +        }
>>>>>> +        break;
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_fb_dirty(struct drm_framebuffer *fb, struct 
>>>>>> drm_rect *rect,
>>>>>> +                struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    const u8 *ctrl2_tbl = panel->controller_cfg->ctrl2_refresh;
>>>>>> +    struct drm_gem_dma_object *dma_obj = 
>>>>>> drm_fb_dma_get_gem_obj(fb, 0);
>>>>>> +    struct iosys_map map;
>>>>>> +    int err = 0;
>>>>>> +    unsigned int data_size = (panel->width * panel->height) / 8;
>>>>>> +    u8 *mono_buffer = NULL;
>>>>>> +    u8 *red_buffer = NULL;
>>>>>> +    u16 ram_x_start, ram_x_end, ram_y_start, ram_y_end;
>>>>>> +
>>>>>> +    /* Process full display area; convert handles orientation 
>>>>>> traversal. */
>>>>>> +    rect->x1 = 0;
>>>>>> +    rect->y1 = 0;
>>>>>> +    rect->x2 = panel->width;
>>>>>> +    rect->y2 = panel->height;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm,
>>>>>> +        "fb_dirty: fb=%dx%d, refresh_mode=%d, orientation=%d\n",
>>>>>> +        fb->width, fb->height, panel->refresh_mode, panel- 
>>>>>> >orientation);
>>>>>> +
>>>>>> +    mono_buffer = panel->tx_buf;
>>>>>> +    memset(mono_buffer, 0, data_size);
>>>>>> +
>>>>>> +    /* 3-colour FULL/FAST: populate red channel. */
>>>>>> +    if (panel->color_mode == SSD16XX_COLOR_MODE_3COLOR &&
>>>>>> +        (panel->refresh_mode == SSD16XX_REFRESH_FULL ||
>>>>>> +         panel->refresh_mode == SSD16XX_REFRESH_FAST)) {
>>>>>> +        red_buffer = panel->tx_red_buf;
>>>>>> +        memset(red_buffer, 0, data_size);
>>>>>> +    }
>>>>>> +
>>>>>> +    iosys_map_set_vaddr(&map, dma_obj->vaddr);
>>>>>> +
>>>>>> +    if (red_buffer && fb->format->format == DRM_FORMAT_R8)
>>>>>> +        ssd16xx_convert_r8_to_red_only(mono_buffer, red_buffer, 
>>>>>> &map, fb, rect);
>>>>>> +    else if (red_buffer)
>>>>>> +        ssd16xx_convert_fb_to_3color(mono_buffer, red_buffer, 
>>>>>> &map, fb, rect);
>>>>>> +    else
>>>>>> +        ssd16xx_convert_fb_to_1bpp(mono_buffer, &map, fb, rect, 
>>>>>> panel->orientation);
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm,
>>>>>> +        "fb_dirty: mono[0..3]=0x%02x 0x%02x 0x%02x 0x%02x 
>>>>>> (data_size=%u)\n",
>>>>>> +        mono_buffer[0], mono_buffer[1], mono_buffer[2], 
>>>>>> mono_buffer[3],
>>>>>> +        data_size);
>>>>>> +
>>>>>> +    /* Set RAM window and cursor for current orientation. */
>>>>>> +    ram_x_start = 0;
>>>>>> +    ram_x_end = (panel->controller_cfg->max_width / 8) - 1;
>>>>>> +    ram_y_start = 0;
>>>>>> +    ram_y_end = panel->controller_cfg->max_height - 1;
>>>>>> +
>>>>>> +    switch (panel->orientation) {
>>>>>> +    case 90:
>>>>>> +    case 180:
>>>>>> +        /* 90°/180°: XDEC_YDEC mode, send end-before-start; 
>>>>>> cursor at (max, max). */
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> +        break;
>>>>>> +
>>>>>> +    default: /* 0°/270° */
>>>>>> +        /* 0°/270°: XINC_YINC mode, cursor at (0, 0). */
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_START_END, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_end, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_START_END, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_end, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_X_ADDRESS_COUNTER, &err);
>>>>>> +        ssd16xx_send_x_param(panel, ram_x_start, &err);
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_SET_RAM_Y_ADDRESS_COUNTER, &err);
>>>>>> +        ssd16xx_send_y_param(panel, ram_y_start, &err);
>>>>>> +        break;
>>>>>> +    }
>>>>>> +
>>>>>> +    ssd16xx_send_cmd(panel, SSD16XX_CMD_WRITE_RAM_BW, &err);
>>>>>> +    ssd16xx_send_data_bulk(panel, mono_buffer, data_size, &err);
>>>>>> +
>>>>>> +    /* Re-send border waveform when: every-update mode, init 
>>>>>> frame, or
>>>>>> +     * the border_waveform_update property just changed (one-shot).
>>>>>> +     */
>>>>>> +    drm_dbg(&panel->drm,
>>>>>> +        "fb_dirty: border check: every_update=%d init_pending=%d 
>>>>>> border_pending=%d idx=%d hw=0x%02x\n",
>>>>>> +        panel->border_refresh_on_every_update, panel- 
>>>>>> >init_refresh_pending,
>>>>>> +        panel->border_waveform_pending, panel- 
>>>>>> >border_waveform_update_idx,
>>>>>> + panel->controller_cfg->border_waveform_table[panel- 
>>>>>> >border_waveform_update_idx]);
>>>>>> +    if (panel->border_refresh_on_every_update || panel- 
>>>>>> >init_refresh_pending ||
>>>>>> +        panel->border_waveform_pending) {
>>>>>> +        u8 idx = panel->border_waveform_update_idx;
>>>>>> +        u8 border = panel->controller_cfg- 
>>>>>> >border_waveform_table[idx];
>>>>>> +
>>>>>> +        drm_dbg(&panel->drm, "fb_dirty: Sending border waveform: 
>>>>>> 0x%02x\n",
>>>>>> +            border);
>>>>>> +        ssd16xx_send_cmd(panel, 
>>>>>> SSD16XX_CMD_BORDER_WAVEFORM_CONTROL, &err);
>>>>>> +        ssd16xx_send_data(panel, border, &err);
>>>>>> +        panel->border_waveform_pending = false;
>>>>>> +    }
>>>>>> +
>>>>>> +    switch (panel->refresh_mode) {
>>>>>> +    case SSD16XX_REFRESH_FULL:
>>>>>> +        /*
>>>>>> +         * BW full refresh: write RED RAM BEFORE display_update
>>>>>> +         * to avoid a post-BUSY write timing issue on some
>>>>>> +         * controller revisions that silently corrupts RED RAM.
>>>>>> +         * RED RAM is then bypassed (CTRL1_BYPASS_RED_RAM) so
>>>>>> +         * stale RED RAM content does not affect the output.
>>>>>> +         */
>>>>>> +        ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> +        if (red_buffer) {
>>>>>> +            /* 3-colour: write red channel before activating */
>>>>>> +            ssd16xx_send_data_bulk(panel, red_buffer, data_size, 
>>>>>> &err);
>>>>>> +            ssd16xx_display_update(panel, panel->controller_cfg- 
>>>>>> >ctrl1_normal,
>>>>>> +                           SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FULL], &err);
>>>>>> +        } else {
>>>>>> +            ssd16xx_send_data_bulk(panel, mono_buffer, data_size, 
>>>>>> &err);
>>>>>> +            ssd16xx_display_update(panel, panel->controller_cfg- 
>>>>>> >ctrl1_bypass_red_ram,
>>>>>> +                           SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FULL], &err);
>>>>>> +        }
>>>>>> +        break;
>>>>>> +    case SSD16XX_REFRESH_FAST:
>>>>>> +        /*
>>>>>> +         * Fast refresh: LUT pre-loaded during hw_init; 
>>>>>> BYPASS_RED_RAM
>>>>>> +         * so RED RAM does not affect the current output.
>>>>>> +         * Write RED RAM BEFORE display_update (same reasoning as 
>>>>>> FULL)
>>>>>> +         * so it holds the just-displayed frame as a valid 
>>>>>> reference for
>>>>>> +         * any subsequent PARTIAL refresh.
>>>>>> +         */
>>>>>> +
>>>>>> +        ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> +        if (red_buffer) {
>>>>>> +            /* 3-colour: write red channel before activating */
>>>>>> +            ssd16xx_send_data_bulk(panel, red_buffer, data_size, 
>>>>>> &err);
>>>>>> +            ssd16xx_display_update(panel, panel->controller_cfg- 
>>>>>> >ctrl1_normal,
>>>>>> +                           SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FAST], &err);
>>>>>> +        } else {
>>>>>> +            ssd16xx_send_data_bulk(panel, mono_buffer, data_size, 
>>>>>> &err);
>>>>>> +            ssd16xx_display_update(panel, panel->controller_cfg- 
>>>>>> >ctrl1_bypass_red_ram,
>>>>>> +                           SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_FAST], &err);
>>>>>> +        }
>>>>>> +        break;
>>>>>> +    case SSD16XX_REFRESH_PARTIAL:
>>>>>> +    default:
>>>>>> +        /*
>>>>>> +         * Partial refresh: both RAMs used for transition waveforms.
>>>>>> +         * RED RAM must hold the PREVIOUS frame (= current display
>>>>>> +         * content) so the controller can compute pixel transitions.
>>>>>> +         * Write RED RAM AFTER display_update so it captures the
>>>>>> +         * just-displayed frame as the reference for the next 
>>>>>> partial.
>>>>>> +         */
>>>>>> +        drm_dbg(&panel->drm,
>>>>>> +            "fb_dirty: partial pre-update: mono[0]=0x%02x 
>>>>>> (BW=new, RED=prev)\n",
>>>>>> +            mono_buffer[0]);
>>>>>> +        ssd16xx_display_update(panel, panel->controller_cfg- 
>>>>>> >ctrl1_normal,
>>>>>> +                       SSD16XX_CTRL1_BYTE2_DEFAULT,
>>>>>> + ctrl2_tbl[SSD16XX_REFRESH_PARTIAL], &err);
>>>>>> +        ssd16xx_send_cmd(panel, SSD1683_CMD_WRITE_RAM_RED, &err);
>>>>>> +        ssd16xx_send_data_bulk(panel, mono_buffer, data_size, &err);
>>>>>> +        drm_dbg(&panel->drm,
>>>>>> +            "fb_dirty: partial post-update: wrote RED baseline 
>>>>>> mono[0]=0x%02x\n",
>>>>>> +            mono_buffer[0]);
>>>>>> +        break;
>>>>>> +    }
>>>>>> +
>>>>>> +    return err;
>>>>>> +}
>>>>>> +
>>>>>> +/* 
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * Plane Functions
>>>>>> + */
>>>>>> +
>>>>>> +static void ssd16xx_plane_destroy(struct drm_plane *plane)
>>>>>> +{
>>>>>> +    drm_plane_cleanup(plane);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_plane_reset(struct drm_plane *plane)
>>>>>> +{
>>>>>> +    drm_atomic_helper_plane_reset(plane);
>>>>>> +}
>>>>>
>>>>> Please avoid these wrappers.
>>>>>
>>>>
>>>> Understood, will update in V2.
>>>>
>>>>>> +
>>>>>> +static const struct drm_plane_funcs ssd16xx_plane_funcs = {
>>>>>> +    .update_plane = drm_atomic_helper_update_plane,
>>>>>> +    .disable_plane = drm_atomic_helper_disable_plane,
>>>>>> +    .destroy = ssd16xx_plane_destroy,
>>>>>> +    .reset = ssd16xx_plane_reset,
>>>>>> +    .atomic_duplicate_state = 
>>>>>> drm_atomic_helper_plane_duplicate_state,
>>>>>> +    .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_plane_atomic_check(struct drm_plane *plane,
>>>>>> +                      struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    struct drm_plane_state *new_plane_state =
>>>>>> +        drm_atomic_get_new_plane_state(state, plane);
>>>>>> +    struct drm_crtc_state *crtc_state;
>>>>>> +
>>>>>> +    if (!new_plane_state->crtc)
>>>>>> +        return 0;
>>>>>> +
>>>>>> +    crtc_state = drm_atomic_get_new_crtc_state(state, 
>>>>>> new_plane_state->crtc);
>>>>>> +
>>>>>> +    return drm_atomic_helper_check_plane_state(new_plane_state, 
>>>>>> crtc_state,
>>>>>> +                           DRM_PLANE_NO_SCALING,
>>>>>> +                           DRM_PLANE_NO_SCALING,
>>>>>> +                           false, false);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_plane_atomic_update(struct drm_plane *plane,
>>>>>> +                    struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    struct drm_plane_state *old_state = 
>>>>>> drm_atomic_get_old_plane_state(state, plane);
>>>>>> +    struct drm_plane_state *new_state = 
>>>>>> drm_atomic_get_new_plane_state(state, plane);
>>>>>> +    struct ssd16xx_panel *panel = plane_to_ssd16xx_panel(plane);
>>>>>> +    enum ssd16xx_refresh_mode saved_mode;
>>>>>> +    u8 saved_border_waveform_idx;
>>>>>> +    struct drm_framebuffer *fb = new_state->fb;
>>>>>> +    struct drm_rect rect;
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "plane_atomic_update: fb=%p, 
>>>>>> initialized=%d\n",
>>>>>> +        fb, panel->initialized);
>>>>>> +
>>>>>> +    if (!fb || !panel->initialized)
>>>>>> +        return;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * If a rotation change is pending, skip the update here — 
>>>>>> crtc_atomic_flush
>>>>>> +     * will re-init the hardware for the new orientation and redraw.
>>>>>> +     */
>>>>>> +    if (panel->reinit_pending) {
>>>>>> +        drm_dbg(&panel->drm, "plane_atomic_update: skipping 
>>>>>> (reinit pending)\n");
>>>>>> +        return;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (!drm_atomic_helper_damage_merged(old_state, new_state, 
>>>>>> &rect)) {
>>>>>> +        rect.x1 = 0;
>>>>>> +        rect.y1 = 0;
>>>>>> +        rect.x2 = fb->width;
>>>>>> +        rect.y2 = fb->height;
>>>>>> +        drm_dbg(&panel->drm, "plane_atomic_update: no damage, 
>>>>>> using full screen\n");
>>>>>> +    }
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "plane_atomic_update: calling fb_dirty 
>>>>>> rect=(%d,%d)-(%d,%d)\n",
>>>>>> +        rect.x1, rect.y1, rect.x2, rect.y2);
>>>>>> +    /*
>>>>>> +     * When refresh_mode_init was set, use the specified mode for 
>>>>>> this first
>>>>>> +     * frame only, then restore the user-configured refresh_mode so
>>>>>> +     * subsequent updates continue with the configured mode.
>>>>>> +     */
>>>>>> +    saved_mode = panel->refresh_mode;
>>>>>> +    saved_border_waveform_idx = panel->border_waveform_update_idx;
>>>>>> +    if (panel->init_refresh_pending) {
>>>>>> +        panel->refresh_mode = panel->refresh_mode_init;
>>>>>> +        panel->border_waveform_update_idx = panel- 
>>>>>> >border_waveform_init_idx;
>>>>>> +    }
>>>>>> +
>>>>>> +    /*
>>>>>> +     * Fast refresh (0xC7) omits LOAD_LUT on every update cycle 
>>>>>> and relies
>>>>>> +     * on the LUT being pre-loaded upfront.  The property setter 
>>>>>> arms
>>>>>> +     * fast_lut_pending whenever the user switches into fast 
>>>>>> mode. Consume
>>>>>> +     * the flag here (once) before the first fast-refresh frame 
>>>>>> so the
>>>>>> +     * controller's LUT is in the correct state.
>>>>>> +     */
>>>>>> +    if (panel->fast_lut_pending) {
>>>>>> +        ret = ssd16xx_preload_fast_lut(panel);
>>>>>> +        if (ret) {
>>>>>> +            drm_err(&panel->drm,
>>>>>> +                "plane_atomic_update: fast LUT preload failed: 
>>>>>> %d\n", ret);
>>>>>> +        }
>>>>>> +
>>>>>> +        panel->fast_lut_pending = false;
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = ssd16xx_fb_dirty(fb, &rect, panel);
>>>>>> +    if (ret)
>>>>>> +        drm_err(&panel->drm, "plane_atomic_update: display update 
>>>>>> failed: %d\n", ret);
>>>>>> +    else
>>>>>> +        panel->last_fb = fb;
>>>>>> +
>>>>>> +    panel->refresh_mode = saved_mode;
>>>>>> +    panel->border_waveform_update_idx = saved_border_waveform_idx;
>>>>>> +
>>>>>> +    /*
>>>>>> +     * If this was the init frame (which used 
>>>>>> border_waveform_init_idx
>>>>>> +     * inside fb_dirty), arm border_waveform_pending so the normal
>>>>>> +     * (non-init) border value is sent at the start of the next 
>>>>>> update.
>>>>>> +     */
>>>>>> +    if (panel->init_refresh_pending) {
>>>>>> +        panel->init_refresh_pending = false;
>>>>>> +        panel->border_waveform_pending = true;
>>>>>> +    }
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_plane_helper_funcs 
>>>>>> ssd16xx_plane_helper_funcs = {
>>>>>> +    .atomic_check = ssd16xx_plane_atomic_check,
>>>>>> +    .atomic_update = ssd16xx_plane_atomic_update,
>>>>>> +};
>>>>>> +
>>>>>> +/* 
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * CRTC Functions
>>>>>> + */
>>>>>> +
>>>>>> +static void ssd16xx_crtc_destroy(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +    drm_crtc_cleanup(crtc);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_funcs ssd16xx_crtc_funcs = {
>>>>>> +    .reset = drm_atomic_helper_crtc_reset,
>>>>>> +    .destroy = ssd16xx_crtc_destroy,
>>>>>> +    .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 enum drm_mode_status ssd16xx_crtc_mode_valid(struct 
>>>>>> drm_crtc *crtc,
>>>>>> +                            const struct drm_display_mode *mode)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> +
>>>>>> +    /* Accept only our panel's native mode (landscape or 
>>>>>> portrait) */
>>>>>> +    if ((mode->hdisplay == panel->mode->hdisplay &&
>>>>>> +         mode->vdisplay == panel->mode->vdisplay) ||
>>>>>> +        (mode->hdisplay == panel->mode->vdisplay &&
>>>>>> +         mode->vdisplay == panel->mode->hdisplay))
>>>>>> +        return MODE_OK;
>>>>>> +
>>>>>> +    return MODE_BAD;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_crtc_atomic_check(struct drm_crtc *crtc,
>>>>>> +                     struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_crtc_atomic_disable(struct drm_crtc *crtc,
>>>>>> +                    struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> +    int ret, idx;
>>>>>> +
>>>>>> +    if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> +        return;
>>>>>> +
>>>>>> +    if (panel->clear_on_disable < 0 || panel- 
>>>>>> >display_cleared_on_deinit)
>>>>>> +        goto out;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "clear_on_disable: running, mode=%d\n",
>>>>>> +        panel->clear_on_disable);
>>>>>> +    ret = ssd16xx_clear_display(panel,
>>>>>> +                    ssd16xx_refresh_mode_to_ctrl2(panel,
>>>>>> + panel->clear_on_disable));
>>>>>> +    if (ret) {
>>>>>> +        drm_err(&panel->drm, "atomic_disable: clear failed: 
>>>>>> %d\n", ret);
>>>>>> +        goto out;
>>>>>> +    }
>>>>>> +
>>>>>> +    panel->display_cleared_on_deinit = true;
>>>>>> +out:
>>>>>> +    drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_crtc_atomic_enable(struct drm_crtc *crtc,
>>>>>> +                       struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> +    struct drm_crtc_state *crtc_state = 
>>>>>> drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +    int ret, idx;
>>>>>> +
>>>>>> +    if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> +        return;
>>>>>> +
>>>>>> +    panel->display_cleared_on_deinit = false;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "atomic_enable: %dx%d\n",
>>>>>> +        crtc_state->mode.hdisplay, crtc_state->mode.vdisplay);
>>>>>> +
>>>>>> +    panel->width = crtc_state->mode.hdisplay;
>>>>>> +    panel->height = crtc_state->mode.vdisplay;
>>>>>> +
>>>>>> +    ret = ssd16xx_hw_init(panel);
>>>>>> +    if (ret) {
>>>>>> +        drm_err(&panel->drm, "crtc_atomic_enable: HW init failed: 
>>>>>> %d\n", ret);
>>>>>> +        goto out;
>>>>>> +    }
>>>>>> +    panel->initialized = true;
>>>>>> +
>>>>>> +    /* Clear display on first app launch if configured */
>>>>>> +    ret = ssd16xx_clear_display_on_init(panel);
>>>>>> +    if (ret)
>>>>>> +        drm_err(&panel->drm, "crtc_atomic_enable: clear on init 
>>>>>> failed: %d\n", ret);
>>>>>> +
>>>>>> +    /*
>>>>>> +     * If refresh_mode_init is set, arm init_refresh_pending so
>>>>>> +     * plane_atomic_update uses the specified mode for the first 
>>>>>> frame
>>>>>> +     * then restores the user-configured or panel default 
>>>>>> refresh_mode.
>>>>>> +     */
>>>>>> +    if (panel->refresh_mode_init >= 0) {
>>>>>> +        drm_dbg(&panel->drm,
>>>>>> +            "atomic_enable: refresh_mode_init=%d, using for first 
>>>>>> frame\n",
>>>>>> +            panel->refresh_mode_init);
>>>>>> +        panel->init_refresh_pending = true;
>>>>>> +    }
>>>>>> +
>>>>>> +out:
>>>>>> +    drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * Re-initialize hardware and redraw the current framebuffer when 
>>>>>> the
>>>>>> + * display orientation changes at runtime via the rotation 
>>>>>> connector property.
>>>>>> + * Called by the DRM atomic helper after atomic_enable/disable 
>>>>>> have run.
>>>>>> + */
>>>>>> +static void ssd16xx_crtc_atomic_flush(struct drm_crtc *crtc,
>>>>>> +                      struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = crtc_to_ssd16xx_panel(crtc);
>>>>>> +    struct drm_framebuffer *fb;
>>>>>> +    struct drm_rect full;
>>>>>> +    int ret, idx;
>>>>>> +
>>>>>> +    if (!panel->reinit_pending || !panel->initialized)
>>>>>> +        return;
>>>>>> +
>>>>>> +    if (!drm_dev_enter(&panel->drm, &idx))
>>>>>> +        return;
>>>>>> +
>>>>>> +    panel->reinit_pending = false;
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "atomic_flush: reinit, orientation=%u°\n",
>>>>>> +        panel->orientation);
>>>>>> +
>>>>>> +    ret = ssd16xx_hw_init(panel);
>>>>>> +    if (ret) {
>>>>>> +        drm_err(&panel->drm, "Orientation re-init failed: %d\n", 
>>>>>> ret);
>>>>>> +        goto out;
>>>>>> +    }
>>>>>> +
>>>>>> +    fb = panel->primary_plane.state ? panel->primary_plane.state->fb
>>>>>> +                    : panel->last_fb;
>>>>>> +    if (fb) {
>>>>>> +        full.x1 = 0;
>>>>>> +        full.y1 = 0;
>>>>>> +        full.x2 = fb->width;
>>>>>> +        full.y2 = fb->height;
>>>>>> +        ret = ssd16xx_fb_dirty(fb, &full, panel);
>>>>>> +        if (ret)
>>>>>> +            drm_err(&panel->drm, "atomic_flush: display update 
>>>>>> failed: %d\n", ret);
>>>>>> +        else
>>>>>> +            panel->last_fb = fb;
>>>>>> +    }
>>>>>> +
>>>>>> +out:
>>>>>> +    drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_helper_funcs 
>>>>>> ssd16xx_crtc_helper_funcs = {
>>>>>> +    .mode_valid     = ssd16xx_crtc_mode_valid,
>>>>>> +    .atomic_check   = ssd16xx_crtc_atomic_check,
>>>>>> +    .atomic_disable = ssd16xx_crtc_atomic_disable,
>>>>>> +    .atomic_enable  = ssd16xx_crtc_atomic_enable,
>>>>>> +    .atomic_flush   = ssd16xx_crtc_atomic_flush,
>>>>>> +};
>>>>>> +
>>>>>> +/* 
>>>>>> -----------------------------------------------------------------------------
>>>>>> + * Connector Functions
>>>>>> + */
>>>>>> +
>>>>>> +static int ssd16xx_connector_get_modes(struct drm_connector 
>>>>>> *connector)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> +    bool mode_is_portrait = (panel->mode->hdisplay < panel->mode- 
>>>>>> >vdisplay);
>>>>>> +    bool orient_is_portrait = (panel->orientation == 90 || panel- 
>>>>>> >orientation == 270);
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "connector_get_modes: orientation=%u°\n",
>>>>>> +        panel->orientation);
>>>>>> +
>>>>>> +    /* For portrait, swap dimensions so clients see logical size. */
>>>>>> +    if (mode_is_portrait != orient_is_portrait) {
>>>>>> +        struct drm_display_mode *mode;
>>>>>> +
>>>>>> +        mode = drm_mode_duplicate(&panel->drm, panel->mode);
>>>>>> +        if (!mode)
>>>>>> +            return 0;
>>>>>> +        swap(mode->hdisplay, mode->vdisplay);
>>>>>> +        swap(mode->hsync_start, mode->vsync_start);
>>>>>> +        swap(mode->hsync_end, mode->vsync_end);
>>>>>> +        swap(mode->htotal, mode->vtotal);
>>>>>> +        swap(mode->width_mm, mode->height_mm);
>>>>>> +        mode->type |= DRM_MODE_TYPE_PREFERRED;
>>>>>> +        drm_mode_set_name(mode);
>>>>>> +        drm_mode_probed_add(connector, mode);
>>>>>> +        return 1;
>>>>>> +    }
>>>>>> +
>>>>>> +    return drm_connector_helper_get_modes_fixed(connector, panel- 
>>>>>> >mode);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_connector_helper_funcs 
>>>>>> ssd16xx_connector_helper_funcs = {
>>>>>> +    .get_modes = ssd16xx_connector_get_modes,
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the rotation connector property (degrees 
>>>>>> clockwise) */
>>>>>> +static const struct drm_prop_enum_list ssd16xx_rotation_enum[] = {
>>>>>> +    { 0,   "0"   },
>>>>>> +    { 90,  "90"  },
>>>>>> +    { 180, "180" },
>>>>>> +    { 270, "270" },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the refresh_mode connector property */
>>>>>> +static const struct drm_prop_enum_list 
>>>>>> ssd16xx_refresh_mode_enum[] = {
>>>>>> +    { SSD16XX_REFRESH_PARTIAL, "partial" },
>>>>>> +    { SSD16XX_REFRESH_FULL,    "full"    },
>>>>>> +    { SSD16XX_REFRESH_FAST,    "fast"    },
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> + * Enum for clear_on_init, clear_on_close, refresh_mode_init 
>>>>>> properties.
>>>>>> + * Value 0 = disabled; values 1-3 = partial/full/fast (refresh 
>>>>>> mode + 1).
>>>>>> + * The +1 offset allows a single enum to represent both 
>>>>>> "disabled" and the
>>>>>> + * three refresh modes without sign-extending the DRM property 
>>>>>> value.
>>>>>> + */
>>>>>> +static const struct drm_prop_enum_list 
>>>>>> ssd16xx_init_refresh_enum[] = {
>>>>>> +    { 0, "disabled" },
>>>>>> +    { 1, "partial"  },
>>>>>> +    { 2, "full"     },
>>>>>> +    { 3, "fast"     },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for the color_mode connector property */
>>>>>> +static const struct drm_prop_enum_list ssd16xx_color_mode_enum[] = {
>>>>>> +    { SSD16XX_COLOR_MODE_BW,     "black-white" },
>>>>>> +    { SSD16XX_COLOR_MODE_3COLOR, "3-color"     },
>>>>>> +};
>>>>>> +
>>>>>> +/* Enum values for border_waveform connector properties (one per 
>>>>>> HW mode) */
>>>>>> +static const struct drm_prop_enum_list 
>>>>>> ssd16xx_border_waveform_enum[] = {
>>>>>> +    { SSD16XX_BORDER_LUT0, "lut0_black"    },
>>>>>> +    { SSD16XX_BORDER_LUT1, "lut1_white"    },
>>>>>> +    { SSD16XX_BORDER_LUT2, "lut2_black"    },
>>>>>> +    { SSD16XX_BORDER_LUT3, "lut3_gray"     },
>>>>>> +    { SSD16XX_BORDER_VSS,  "vss_black"     },
>>>>>> +    { SSD16XX_BORDER_VSH1, "vsh1_black"    },
>>>>>> +    { SSD16XX_BORDER_VSL,  "vsl_white"     },
>>>>>> +    { SSD16XX_BORDER_VSH2, "vsh2_black"    },
>>>>>> +    { SSD16XX_BORDER_VCOM, "vcom_preserve" },
>>>>>> +    { SSD16XX_BORDER_HIZ,  "hiz_float"     },
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_connector_create_properties(struct 
>>>>>> ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    struct drm_device *drm = &panel->drm;
>>>>>> +    struct drm_connector *connector = &panel->connector;
>>>>>> +
>>>>>> +    panel->rotation_property =
>>>>>> +        drm_property_create_enum(drm, 0, "rotation",
>>>>>> +                     ssd16xx_rotation_enum,
>>>>>> +                     ARRAY_SIZE(ssd16xx_rotation_enum));
>>>>>> +    if (!panel->rotation_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->rotation_property, panel->orientation);
>>>>>> +
>>>>>> +    panel->refresh_mode_property =
>>>>>> +        drm_property_create_enum(drm, 0, "refresh_mode",
>>>>>> +                     ssd16xx_refresh_mode_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_refresh_mode_enum));
>>>>>> +    if (!panel->refresh_mode_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->refresh_mode_property, panel- 
>>>>>> >refresh_mode);
>>>>>> +
>>>>>> +    panel->border_waveform_init_property =
>>>>>> +        drm_property_create_enum(drm, 0, "border_waveform_init",
>>>>>> +                     ssd16xx_border_waveform_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_border_waveform_enum));
>>>>>> +    if (!panel->border_waveform_init_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> + panel->border_waveform_init_property,
>>>>>> +                   panel->border_waveform_init_idx);
>>>>>> +
>>>>>> +    panel->border_waveform_update_property =
>>>>>> +        drm_property_create_enum(drm, 0, "border_waveform_update",
>>>>>> +                     ssd16xx_border_waveform_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_border_waveform_enum));
>>>>>> +    if (!panel->border_waveform_update_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> + panel->border_waveform_update_property,
>>>>>> +                   panel->border_waveform_update_idx);
>>>>>> +
>>>>>> +    panel->border_refresh_on_every_update_property =
>>>>>> +        drm_property_create_bool(drm, 0, 
>>>>>> "border_refresh_on_every_update");
>>>>>> +    if (!panel->border_refresh_on_every_update_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> + panel->border_refresh_on_every_update_property,
>>>>>> + panel->border_refresh_on_every_update);
>>>>>> +
>>>>>> +    panel->clear_on_init_property =
>>>>>> +        drm_property_create_enum(drm, 0, "clear_on_init",
>>>>>> +                     ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> +    if (!panel->clear_on_init_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    /* Property value 0=disabled, 1-3=mode; field is -1/0/1/2 → 
>>>>>> val = field+1 */
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->clear_on_init_property,
>>>>>> +                   panel->clear_on_init + 1);
>>>>>> +
>>>>>> +    panel->clear_on_close_property =
>>>>>> +        drm_property_create_enum(drm, 0, "clear_on_close",
>>>>>> +                     ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> +    if (!panel->clear_on_close_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->clear_on_close_property,
>>>>>> +                   panel->clear_on_close + 1);
>>>>>> +
>>>>>> +    panel->clear_on_disable_property =
>>>>>> +        drm_property_create_enum(drm, 0, "clear_on_disable",
>>>>>> +                     ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> +    if (!panel->clear_on_disable_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->clear_on_disable_property,
>>>>>> +                   panel->clear_on_disable + 1);
>>>>>> +
>>>>>> +    panel->refresh_mode_init_property =
>>>>>> +        drm_property_create_enum(drm, 0, "refresh_mode_init",
>>>>>> +                     ssd16xx_init_refresh_enum,
>>>>>> + ARRAY_SIZE(ssd16xx_init_refresh_enum));
>>>>>> +    if (!panel->refresh_mode_init_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->refresh_mode_init_property,
>>>>>> +                   panel->refresh_mode_init + 1);
>>>>>> +
>>>>>> +    /*
>>>>>> +     * color_mode: only expose 3-color option on panels that 
>>>>>> physically have
>>>>>> +     * a red plane; on BW-only panels the property still exists for
>>>>>> +     * consistency but userspace can only set "black-white".
>>>>>> +     */
>>>>>> +    panel->color_mode_property =
>>>>>> +        drm_property_create_enum(drm, 0, "color_mode",
>>>>>> +                     ssd16xx_color_mode_enum,
>>>>>> + panel->panel_cfg->red_supported
>>>>>> +                        ? ARRAY_SIZE(ssd16xx_color_mode_enum)
>>>>>> +                        : 1);
>>>>>> +    if (!panel->color_mode_property)
>>>>>> +        return -ENOMEM;
>>>>>> +    drm_object_attach_property(&connector->base,
>>>>>> +                   panel->color_mode_property,
>>>>>> +                   panel->color_mode);
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_connector_atomic_get_property(struct 
>>>>>> drm_connector *connector,
>>>>>> +                         const struct drm_connector_state *state,
>>>>>> +                         struct drm_property *property,
>>>>>> +                         uint64_t *val)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "get_property: %s\n", property->name);
>>>>>> +
>>>>>> +    if (property == panel->rotation_property) {
>>>>>> +        *val = panel->orientation;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->refresh_mode_property) {
>>>>>> +        *val = panel->refresh_mode;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->border_waveform_init_property) {
>>>>>> +        *val = panel->border_waveform_init_idx;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->border_waveform_update_property) {
>>>>>> +        *val = panel->border_waveform_update_idx;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel- 
>>>>>> >border_refresh_on_every_update_property) {
>>>>>> +        *val = panel->border_refresh_on_every_update;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_init_property) {
>>>>>> +        *val = panel->clear_on_init + 1;  /* field -1/0/1/2 → val 
>>>>>> 0/1/2/3 */
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_close_property) {
>>>>>> +        *val = panel->clear_on_close + 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_disable_property) {
>>>>>> +        *val = panel->clear_on_disable + 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->refresh_mode_init_property) {
>>>>>> +        *val = panel->refresh_mode_init + 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->color_mode_property) {
>>>>>> +        *val = panel->color_mode;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +
>>>>>> +    return -EINVAL;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_connector_atomic_set_property(struct 
>>>>>> drm_connector *connector,
>>>>>> +                         struct drm_connector_state *state,
>>>>>> +                         struct drm_property *property,
>>>>>> +                         uint64_t val)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = to_ssd16xx_panel(connector->dev);
>>>>>> +
>>>>>> +    drm_dbg(&panel->drm, "set_property: %s = %llu\n", property- 
>>>>>> >name, val);
>>>>>> +
>>>>>> +    if (property == panel->rotation_property) {
>>>>>> +        if (val != 0 && val != 90 && val != 180 && val != 270)
>>>>>> +            return -EINVAL;
>>>>>> +        panel->orientation = val;
>>>>>> +        /*
>>>>>> +         * Flag hardware re-init needed. crtc_atomic_flush will call
>>>>>> +         * ssd16xx_hw_init() with the new orientation and redraw.
>>>>>> +         */
>>>>>> +        panel->reinit_pending = true;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->refresh_mode_property) {
>>>>>> +        if (val > SSD16XX_REFRESH_FAST)
>>>>>> +            return -EINVAL;
>>>>>> +        /*
>>>>>> +         * Fast refresh (0xC7) omits LOAD_LUT on every update and 
>>>>>> relies
>>>>>> +         * on the LUT being pre-loaded upfront.  Arm the one-shot 
>>>>>> flag
>>>>>> +         * when switching into fast mode so the next 
>>>>>> plane_atomic_update
>>>>>> +         * loads the LUT before the first fast-refresh cycle. 
>>>>>> Clear it
>>>>>> +         * when switching away so a fresh pre-load happens if the 
>>>>>> user
>>>>>> +         * returns to fast mode later.
>>>>>> +         */
>>>>>> +        if (val == SSD16XX_REFRESH_FAST &&
>>>>>> +            panel->refresh_mode != SSD16XX_REFRESH_FULL)
>>>>>> +            panel->fast_lut_pending = true;
>>>>>> +        else
>>>>>> +            panel->fast_lut_pending = false;
>>>>>> +        panel->refresh_mode = val;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->border_waveform_init_property) {
>>>>>> +        if (val >= ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> +            return -EINVAL;
>>>>>> +        panel->border_waveform_init_idx = val;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->border_waveform_update_property) {
>>>>>> +        const u8 *bw_tbl = panel->controller_cfg- 
>>>>>> >border_waveform_table;
>>>>>> +        bool changed = (int)val != panel- 
>>>>>> >border_waveform_update_idx;
>>>>>> +
>>>>>> +        if (val >= ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> +            return -EINVAL;
>>>>>> +        drm_dbg(&panel->drm,
>>>>>> +            "set_property: border_waveform_update old=%d new=%llu 
>>>>>> hw=0x%02x -> 0x%02x %s\n",
>>>>>> +            panel->border_waveform_update_idx, val,
>>>>>> +            bw_tbl[panel->border_waveform_update_idx],
>>>>>> +            bw_tbl[val],
>>>>>> +            changed ? "(arming pending)" : "(no change)");
>>>>>> +        /* Arm one-shot flag so the new border value is sent on 
>>>>>> the very
>>>>>> +         * next display update, even if 
>>>>>> border_refresh_on_every_update is
>>>>>> +         * not set.  Cleared in fb_dirty after the command is sent.
>>>>>> +         */
>>>>>> +        if ((int)val != panel->border_waveform_update_idx)
>>>>>> +            panel->border_waveform_pending = true;
>>>>>> +        panel->border_waveform_update_idx = val;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel- 
>>>>>> >border_refresh_on_every_update_property) {
>>>>>> +        panel->border_refresh_on_every_update = !!val;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_init_property) {
>>>>>> +        if (val > 3)
>>>>>> +            return -EINVAL;
>>>>>> +        panel->clear_on_init = (int)val - 1;  /* val 0/1/2/3 → 
>>>>>> field -1/0/1/2 */
>>>>>> +        panel->first_clear_done = false;  /* allow re-fire on 
>>>>>> next enable */
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_close_property) {
>>>>>> +        if (val > 3)
>>>>>> +            return -EINVAL;
>>>>>> +        panel->clear_on_close = (int)val - 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->clear_on_disable_property) {
>>>>>> +        if (val > 3)
>>>>>> +            return -EINVAL;
>>>>>> +        panel->clear_on_disable = (int)val - 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->refresh_mode_init_property) {
>>>>>> +        if (val > 3)
>>>>>> +            return -EINVAL;
>>>>>> +        panel->refresh_mode_init = (int)val - 1;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +    if (property == panel->color_mode_property) {
>>>>>> +        if (val > SSD16XX_COLOR_MODE_3COLOR)
>>>>>> +            return -EINVAL;
>>>>>> +        if (val == SSD16XX_COLOR_MODE_3COLOR && !panel- 
>>>>>> >panel_cfg- >red_supported) {
>>>>>> +            drm_dbg(&panel->drm,
>>>>>> +                "set_property: 3-color mode not supported by this 
>>>>>> panel\n");
>>>>>> +            return -EINVAL;
>>>>>> +        }
>>>>>> +        panel->color_mode = val;
>>>>>> +        return 0;
>>>>>> +    }
>>>>>> +
>>>>>> +    return -EINVAL;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_connector_funcs ssd16xx_connector_funcs = {
>>>>>> +    .reset = drm_atomic_helper_connector_reset,
>>>>>> +    .fill_modes = drm_helper_probe_single_connector_modes,
>>>>>> +    .destroy = drm_connector_cleanup,
>>>>>> +    .atomic_duplicate_state = 
>>>>>> drm_atomic_helper_connector_duplicate_state,
>>>>>> +    .atomic_destroy_state = 
>>>>>> drm_atomic_helper_connector_destroy_state,
>>>>>> +    .atomic_get_property = ssd16xx_connector_atomic_get_property,
>>>>>> +    .atomic_set_property = ssd16xx_connector_atomic_set_property,
>>>>>> +};
>>>>>> +
>>>>>> +static const u32 ssd16xx_formats[] = {
>>>>>> +    DRM_FORMAT_XRGB8888,  /* 32-bit RGB with padding (preferred) */
>>>>>> +    DRM_FORMAT_RGB888,    /* 24-bit packed RGB */
>>>>>> +    DRM_FORMAT_RGB565,    /* 16-bit RGB (5:6:5) */
>>>>>> +    DRM_FORMAT_R8,        /* 8-bit grayscale */
>>>>>> +    DRM_FORMAT_NV12,      /* YUV 4:2:0 planar */
>>>>>> +    DRM_FORMAT_NV16,      /* YUV 4:2:2 planar */
>>>>>> +    DRM_FORMAT_YUYV,      /* Packed YUV 4:2:2 (Y0 U0 Y1 V0) */
>>>>>> +    DRM_FORMAT_UYVY,      /* Packed YUV 4:2:2 (U0 Y0 V0 Y1) */
>>>>>> +    DRM_FORMAT_R1,        /* 1-bit monochrome (native, 8 pixels/ 
>>>>>> byte) */
>>>>>> +};
>>>>>
>>>>> Why do you have all these formats?
>>>>>
>>>>> Only export the modes your panel can do natively; plus maybe 
>>>>> XRGB8888 for compatibility.
>>>>>
>>>>
>>>> I wanted to keep YUV formats too since some apps such as camera apps 
>>>> (in case we want to click a picture and display over on the e-paper 
>>>> badge directly) support only YUV formats but yeah if it's too much I 
>>>> can remove them from driver and instead have the conversion in the 
>>>> app itself.
>>>>
>>>>>> +
>>>>>> +DEFINE_DRM_GEM_FOPS(ssd16xx_fops);
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_drm_master_set - arm init refresh when a new master 
>>>>>> takes control.
>>>>>> + */
>>>>>> +static void ssd16xx_drm_master_set(struct drm_device *drm,
>>>>>> +                   struct drm_file *file, bool from_open)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = to_ssd16xx_panel(drm);
>>>>>> +
>>>>>> +    panel->display_cleared_on_deinit = false;
>>>>>> +    panel->first_clear_done = false;
>>>>>> +
>>>>>> +    if (panel->refresh_mode_init >= 0)
>>>>>> +        panel->init_refresh_pending = true;
>>>>>> +}
>>>>>> +
>>>>>> +/*
>>>>>> + * ssd16xx_drm_master_drop - clear display and disarm init 
>>>>>> refresh when the
>>>>>> + * master client exits.
>>>>>> + */
>>>>>> +static void ssd16xx_drm_master_drop(struct drm_device *drm,
>>>>>> +                    struct drm_file *file)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = to_ssd16xx_panel(drm);
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    panel->init_refresh_pending = false;
>>>>>> +    panel->first_clear_done = false;
>>>>>> +
>>>>>> +    if (panel->clear_on_close < 0 || panel- 
>>>>>> >display_cleared_on_deinit)
>>>>>> +        return;
>>>>>> +
>>>>>> +    ret = ssd16xx_clear_display_on_exit(panel);
>>>>>> +    if (ret)
>>>>>> +        drm_err(drm, "master_drop: clear on close failed: %d\n", 
>>>>>> ret);
>>>>>> +
>>>>>> +    panel->display_cleared_on_deinit = true;
>>>>>> +}
>>>>>
>>>>> No, don't overload these. Just remove all this. Clearing should be 
>>>>> left to the DRM client.
>>>>>
>>>>
>>>> Yes, the choice to clear or not to clear is left to drm client 
>>>> depending on drm property setting done by drm client, the driver 
>>>> clears the display. It would be difficult to update all different 
>>>> apps to pass a blank white buffer to clear the screen and what if 
>>>> the app gets closed abruptly (as master drop callback will get 
>>>> triggered), then in that case the current driver logic ensures that 
>>>> screen gets cleared. In normal LCD displays if app gets closed 
>>>> abruptly, the display would have gone-off automatically as signals 
>>>> would stop getting transmitted but in e-paper panel the last display 
>>>> context would remain and I think it is driver responsibility to 
>>>> clear that if that was the policy communicated by application to the 
>>>> driver.
>>>>
>>>>>> +
>>>>>> +static struct drm_driver ssd16xx_drm_driver = {
>>>>>> +    .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
>>>>>> +    .fops = &ssd16xx_fops,
>>>>>> +    .name = "ssd16xx",
>>>>>> +    .desc = "DRM driver for SSD16xx e-paper controller family",
>>>>>> +    .major = 1,
>>>>>> +    .minor = 0,
>>>>>> +    .master_set  = ssd16xx_drm_master_set,
>>>>>> +    .master_drop = ssd16xx_drm_master_drop,
>>>>>> +    DRM_GEM_DMA_DRIVER_OPS,
>>>>>> +    DRM_FBDEV_DMA_DRIVER_OPS,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct drm_mode_config_funcs 
>>>>>> ssd16xx_mode_config_funcs = {
>>>>>> +    .fb_create = drm_gem_fb_create_with_dirty,
>>>>>> +    .atomic_check = drm_atomic_helper_check,
>>>>>> +    .atomic_commit = drm_atomic_helper_commit,
>>>>>> +};
>>>>>> +
>>>>>> +/*
>>>>>> + * Use the RPM commit-tail variant so that 
>>>>>> drm_atomic_helper_commit_modeset_enables
>>>>>> + * (which calls crtc_atomic_enable) runs before 
>>>>>> drm_atomic_helper_commit_planes.
>>>>>> + * Without this, the standard commit_tail calls commit_planes before
>>>>>> + * modeset_enables, so plane_atomic_update would see initialized 
>>>>>> == false on the
>>>>>> + * first commit and silently drop the frame.
>>>>>> + */
>>>>>> +static const struct drm_mode_config_helper_funcs 
>>>>>> ssd16xx_mode_config_helper_funcs = {
>>>>>> +    .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
>>>>>> +};
>>>>>> +
>>>>>> +static int ssd16xx_alloc_tx_bufs(struct ssd16xx_panel *panel)
>>>>>> +{
>>>>>> +    struct device *dev = &panel->spi->dev;
>>>>>> +    size_t frame_size = (panel->controller_cfg->max_width *
>>>>>> +                 panel->controller_cfg->max_height) / 8;
>>>>>> +
>>>>>> +    panel->tx_buf = devm_kmalloc(dev, frame_size, GFP_KERNEL);
>>>>>
>>>>> drmm_kmalloc() here and for the other buffers.
>>>>>
>>>>
>>>> Understood, thanks for pointing will fix it in V2.
>>>>
>>>> Best Regards
>>>> Devarsh
>>>>
>>>>> Best regards
>>>>> Thomas
>>>>
>>>>>
>>>>>> +    if (!panel->tx_buf)
>>>>>> +        return -ENOMEM;
>>>>>> +
>>>>>> +    if (panel->panel_cfg->red_supported) {
>>>>>> +        panel->tx_red_buf = devm_kmalloc(dev, frame_size, 
>>>>>> GFP_KERNEL);
>>>>>> +        if (!panel->tx_red_buf)
>>>>>> +            return -ENOMEM;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (!panel->dc) {
>>>>>> +        panel->tx_buf9 = devm_kmalloc_array(dev, frame_size,
>>>>>> +                            sizeof(u16), GFP_KERNEL);
>>>>>> +        if (!panel->tx_buf9)
>>>>>> +            return -ENOMEM;
>>>>>> +    }
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int ssd16xx_probe(struct spi_device *spi)
>>>>>> +{
>>>>>> +    struct device *dev = &spi->dev;
>>>>>> +    struct ssd16xx_panel *panel;
>>>>>> +    struct drm_device *drm;
>>>>>> +    const struct spi_device_id *spi_id;
>>>>>> +    struct drm_display_mode *mode;
>>>>>> +    const void *match;
>>>>>> +    enum ssd16xx_model model;
>>>>>> +    u32 dt_rotation = 0;
>>>>>> +    int ret;
>>>>>> +
>>>>>> +    match = device_get_match_data(dev);
>>>>>> +    if (match) {
>>>>>> +        model = (enum ssd16xx_model)(uintptr_t)match;
>>>>>> +    } else {
>>>>>> +        spi_id = spi_get_device_id(spi);
>>>>>> +        model = (enum ssd16xx_model)spi_id->driver_data;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (!dev->coherent_dma_mask) {
>>>>>> +        ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
>>>>>> +        if (ret) {
>>>>>> +            dev_warn(dev, "Failed to set DMA mask: %d\n", ret);
>>>>>> +            return ret;
>>>>>> +        }
>>>>>> +    }
>>>>>> +
>>>>>> +    panel = devm_drm_dev_alloc(dev, &ssd16xx_drm_driver,
>>>>>> +                   struct ssd16xx_panel, drm);
>>>>>> +    if (IS_ERR(panel))
>>>>>> +        return PTR_ERR(panel);
>>>>>> +
>>>>>> +    drm = &panel->drm;
>>>>>> +    panel->spi = spi;
>>>>>> +    panel->model = model;
>>>>>> +    spi_set_drvdata(spi, panel);
>>>>>> +
>>>>>> +    spi->mode = SPI_MODE_0;
>>>>>> +    spi->bits_per_word = SSD16XX_SPI_BITS_PER_WORD;
>>>>>> +
>>>>>> +    if (!spi->max_speed_hz) {
>>>>>> +        drm_warn(drm, "spi-max-frequency not specified, using %u 
>>>>>> Hz\n",
>>>>>> +             SSD16XX_SPI_SPEED_DEFAULT);
>>>>>> +        spi->max_speed_hz = SSD16XX_SPI_SPEED_DEFAULT;
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = spi_setup(spi);
>>>>>> +    if (ret < 0) {
>>>>>> +        drm_err(drm, "SPI setup failed: %d\n", ret);
>>>>>> +        return ret;
>>>>>> +    }
>>>>>> +
>>>>>> +    switch (model) {
>>>>>> +    case GDEY042T81:
>>>>>> +        panel->controller = SSD1683;
>>>>>> +        break;
>>>>>> +    default:
>>>>>> +        drm_err(drm, "Unknown panel model: %d\n", model);
>>>>>> +        return -EINVAL;
>>>>>> +    }
>>>>>> +
>>>>>> +    if (panel->controller >= 
>>>>>> ARRAY_SIZE(ssd16xx_controller_configs) ||
>>>>>> + !ssd16xx_controller_configs[panel->controller].max_width)
>>>>>> +        return -EINVAL;
>>>>>> +    panel->controller_cfg = &ssd16xx_controller_configs[panel- 
>>>>>> >controller];
>>>>>> +
>>>>>> +    if (model >= ARRAY_SIZE(ssd16xx_panel_configs))
>>>>>> +        return -EINVAL;
>>>>>> +    panel->panel_cfg = &ssd16xx_panel_configs[model];
>>>>>> +
>>>>>> +    mode = devm_kmemdup(dev, panel->panel_cfg->mode,
>>>>>> +                sizeof(*panel->panel_cfg->mode), GFP_KERNEL);
>>>>>> +    if (!mode)
>>>>>> +        return -ENOMEM;
>>>>>> +
>>>>>> +    panel->refresh_mode = panel->panel_cfg->default_refresh_mode;
>>>>>> +    /* Default color mode: 3-color for panels with red plane, BW 
>>>>>> otherwise */
>>>>>> +    panel->color_mode = panel->panel_cfg->red_supported
>>>>>> +                ? SSD16XX_COLOR_MODE_3COLOR
>>>>>> +                : SSD16XX_COLOR_MODE_BW;
>>>>>> +    panel->border_waveform_init_idx   = panel->panel_cfg- 
>>>>>> >default_border_waveform_init;
>>>>>> +    panel->border_waveform_update_idx = panel->panel_cfg- 
>>>>>> >default_border_waveform_update;
>>>>>> +    panel->border_refresh_on_every_update =
>>>>>> + panel->panel_cfg->default_border_refresh_on_every_update;
>>>>>> +    panel->clear_on_init    = panel->panel_cfg- 
>>>>>> >default_clear_on_init;
>>>>>> +    panel->clear_on_close   = panel->panel_cfg- 
>>>>>> >default_clear_on_close;
>>>>>> +    panel->clear_on_disable = panel->panel_cfg- 
>>>>>> >default_clear_on_disable;
>>>>>> +    panel->refresh_mode_init = panel->panel_cfg- 
>>>>>> >default_refresh_mode_init;
>>>>>> +
>>>>>> +    /* Module parameter overrides for border/display control */
>>>>>> +    if (border_waveform_init_lut >= 0 &&
>>>>>> +        border_waveform_init_lut < 
>>>>>> (int)ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> +        panel->border_waveform_init_idx = border_waveform_init_lut;
>>>>>> +    if (border_waveform_lut >= 0 &&
>>>>>> +        border_waveform_lut < 
>>>>>> (int)ARRAY_SIZE(ssd1683_border_waveform_table))
>>>>>> +        panel->border_waveform_update_idx = border_waveform_lut;
>>>>>> +    if (border_refresh_on_every_update)
>>>>>> +        panel->border_refresh_on_every_update = true;
>>>>>> +    if (clear_on_init >= 0 && clear_on_init <= 2)
>>>>>> +        panel->clear_on_init = clear_on_init;
>>>>>> +    if (clear_on_close >= 0 && clear_on_close <= 2)
>>>>>> +        panel->clear_on_close = clear_on_close;
>>>>>> +    if (clear_on_disable >= 0 && clear_on_disable <= 2)
>>>>>> +        panel->clear_on_disable = clear_on_disable;
>>>>>> +    if (refresh_mode_init >= 0 && refresh_mode_init <= 2)
>>>>>> +        panel->refresh_mode_init = refresh_mode_init;
>>>>>> +
>>>>>> +    /* Module parameter overrides panel default refresh mode when 
>>>>>> set */
>>>>>> +    if (refresh_mode >= 0) {
>>>>>> +        if (refresh_mode > SSD16XX_REFRESH_FAST)
>>>>>> +            drm_warn(drm, "Invalid refresh_mode module param %d, 
>>>>>> ignored\n",
>>>>>> +                 refresh_mode);
>>>>>> +        else
>>>>>> +            panel->refresh_mode = refresh_mode;
>>>>>> +    }
>>>>>> +
>>>>>> +    /* Module parameter overrides panel default color mode when 
>>>>>> set */
>>>>>> +    if (color_mode >= 0) {
>>>>>> +        if (color_mode > SSD16XX_COLOR_MODE_3COLOR)
>>>>>> +            drm_warn(drm, "Invalid color_mode module param %d, 
>>>>>> ignored\n",
>>>>>> +                 color_mode);
>>>>>> +        else if (color_mode == SSD16XX_COLOR_MODE_3COLOR &&
>>>>>> +             !panel->panel_cfg->red_supported)
>>>>>> +            drm_warn(drm,
>>>>>> +                 "color_mode=3-color requested but panel has no 
>>>>>> red plane, ignored\n");
>>>>>> +        else
>>>>>> +            panel->color_mode = color_mode;
>>>>>> +    }
>>>>>> +
>>>>>> +    /* Parse "rotation" DT property; swap mode dimensions for 
>>>>>> portrait. */
>>>>>> +    device_property_read_u32(dev, "rotation", &dt_rotation);
>>>>>> +    if (dt_rotation != 0 && dt_rotation != 90 && dt_rotation != 
>>>>>> 180 && dt_rotation != 270) {
>>>>>> +        drm_warn(drm, "Invalid DT rotation %u, defaulting to 0° 
>>>>>> \n", dt_rotation);
>>>>>> +        dt_rotation = 0;
>>>>>> +    }
>>>>>> +    panel->orientation = dt_rotation;
>>>>>> +
>>>>>> +    /* Module parameter overrides DT rotation when set */
>>>>>> +    if (rotation >= 0) {
>>>>>> +        if (rotation != 0 && rotation != 90 && rotation != 180 && 
>>>>>> rotation != 270)
>>>>>> +            drm_warn(drm, "Invalid rotation module param %d, 
>>>>>> ignored\n",
>>>>>> +                 rotation);
>>>>>> +        else
>>>>>> +            panel->orientation = rotation;
>>>>>> +    }
>>>>>> +
>>>>>> +    drm_dbg(drm, "Using %s orientation (%u°, %ux%u logical)\n",
>>>>>> +        (panel->orientation == 90 || panel->orientation == 270) ? 
>>>>>> "portrait" : "landscape",
>>>>>> +        panel->orientation, mode->hdisplay, mode->vdisplay);
>>>>>> +
>>>>>> +    /* Swap mode dimensions for portrait so clients see logical 
>>>>>> size. */
>>>>>> +    if (panel->orientation == 90 || panel->orientation == 270) {
>>>>>> +        swap(mode->hdisplay, mode->vdisplay);
>>>>>> +        swap(mode->hsync_start, mode->vsync_start);
>>>>>> +        swap(mode->hsync_end, mode->vsync_end);
>>>>>> +        swap(mode->htotal, mode->vtotal);
>>>>>> +        swap(mode->width_mm, mode->height_mm);
>>>>>> +        drm_dbg(drm, "Mode dimensions swapped for portrait: 
>>>>>> %ux%u\n",
>>>>>> +            mode->hdisplay, mode->vdisplay);
>>>>>> +    } else {
>>>>>> +        drm_dbg(drm, "Mode dimensions unchanged: %ux%u\n",
>>>>>> +            mode->hdisplay, mode->vdisplay);
>>>>>> +    }
>>>>>> +    panel->mode = mode;
>>>>>> +    panel->width = mode->hdisplay;
>>>>>> +    panel->height = mode->vdisplay;
>>>>>> +
>>>>>> +    /* Acquire GPIOs. */
>>>>>> +    panel->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
>>>>>> +    if (IS_ERR(panel->reset))
>>>>>> +        return dev_err_probe(dev, PTR_ERR(panel->reset), "Failed 
>>>>>> to get RESET GPIO\n");
>>>>>> +
>>>>>> +    panel->busy = devm_gpiod_get(dev, "busy", GPIOD_IN);
>>>>>> +    if (IS_ERR(panel->busy))
>>>>>> +        return dev_err_probe(dev, PTR_ERR(panel->busy), "Failed 
>>>>>> to get BUSY GPIO\n");
>>>>>> +
>>>>>> +    panel->dc = devm_gpiod_get_optional(dev, "dc", GPIOD_OUT_LOW);
>>>>>> +    if (IS_ERR(panel->dc))
>>>>>> +        return dev_err_probe(dev, PTR_ERR(panel->dc), "Failed to 
>>>>>> get DC GPIO\n");
>>>>>> +    if (!panel->dc) {
>>>>>> +        if (!spi_is_bpw_supported(spi, 9))
>>>>>> +            return dev_err_probe(dev, -EINVAL,
>>>>>> +                         "3-wire SPI mode requires 9-bit word 
>>>>>> support\n");
>>>>>> +        drm_dbg(drm, "dc-gpios not specified, using 3-wire (9- 
>>>>>> bit) SPI mode\n");
>>>>>> +    }
>>>>>> +
>>>>>> +    ret = ssd16xx_alloc_tx_bufs(panel);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    ssd16xx_hw_reset(panel);
>>>>>> +
>>>>>> +    ret = drmm_mode_config_init(drm);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    drm->mode_config.funcs = &ssd16xx_mode_config_funcs;
>>>>>> +    drm->mode_config.helper_private = 
>>>>>> &ssd16xx_mode_config_helper_funcs;
>>>>>> +    drm->mode_config.min_width = min(panel->width, panel->height);
>>>>>> +    drm->mode_config.max_width = max(panel->width, panel->height);
>>>>>> +    drm->mode_config.min_height = min(panel->width, panel->height);
>>>>>> +    drm->mode_config.max_height = max(panel->width, panel->height);
>>>>>> +
>>>>>> +    drm_connector_helper_add(&panel->connector, 
>>>>>> &ssd16xx_connector_helper_funcs);
>>>>>> +    ret = drm_connector_init(drm, &panel->connector, 
>>>>>> &ssd16xx_connector_funcs,
>>>>>> +                 DRM_MODE_CONNECTOR_SPI);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    ret = drm_universal_plane_init(drm, &panel->primary_plane, 0,
>>>>>> +                       &ssd16xx_plane_funcs,
>>>>>> +                       ssd16xx_formats, ARRAY_SIZE(ssd16xx_formats),
>>>>>> +                       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +    drm_plane_helper_add(&panel->primary_plane, 
>>>>>> &ssd16xx_plane_helper_funcs);
>>>>>> + drm_plane_enable_fb_damage_clips(&panel->primary_plane);
>>>>>> +
>>>>>> +    ret = drm_crtc_init_with_planes(drm, &panel->crtc, &panel- 
>>>>>> >primary_plane,
>>>>>> +                    NULL, &ssd16xx_crtc_funcs, NULL);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +    drm_crtc_helper_add(&panel->crtc, &ssd16xx_crtc_helper_funcs);
>>>>>> +
>>>>>> +    ret = drm_simple_encoder_init(drm, &panel->encoder, 
>>>>>> DRM_MODE_ENCODER_NONE);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +    panel->encoder.possible_crtcs = drm_crtc_mask(&panel->crtc);
>>>>>> +
>>>>>> +    ret = drm_connector_attach_encoder(&panel->connector, &panel- 
>>>>>> >encoder);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    ret = ssd16xx_connector_create_properties(panel);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    drm_mode_config_reset(drm);
>>>>>> +
>>>>>> +    ret = drm_dev_register(drm, 0);
>>>>>> +    if (ret)
>>>>>> +        return ret;
>>>>>> +
>>>>>> +    drm_dbg(drm, "SSD16xx e-paper display initialized (%dx%d, %d° 
>>>>>> rotation)\n",
>>>>>> +        panel->width, panel->height, panel->orientation);
>>>>>> +
>>>>>> +    drm_client_setup(drm, NULL);
>>>>>> +
>>>>>> +    return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_remove(struct spi_device *spi)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = spi_get_drvdata(spi);
>>>>>> +
>>>>>> +    drm_dev_unplug(&panel->drm);
>>>>>> +    drm_atomic_helper_shutdown(&panel->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static void ssd16xx_shutdown(struct spi_device *spi)
>>>>>> +{
>>>>>> +    struct ssd16xx_panel *panel = spi_get_drvdata(spi);
>>>>>> +
>>>>>> +    drm_atomic_helper_shutdown(&panel->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct of_device_id ssd16xx_of_match[] = {
>>>>>> +    { .compatible = "gooddisplay,gdey042t81", .data = (void 
>>>>>> *)GDEY042T81 },
>>>>>> +    { }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(of, ssd16xx_of_match);
>>>>>> +
>>>>>> +static const struct spi_device_id ssd16xx_id[] = {
>>>>>> +    { "gdey042t81", GDEY042T81 },
>>>>>> +    { }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(spi, ssd16xx_id);
>>>>>> +
>>>>>> +static struct spi_driver ssd16xx_spi_driver = {
>>>>>> +    .driver = {
>>>>>> +        .name = "ssd16xx",
>>>>>> +        .of_match_table = ssd16xx_of_match,
>>>>>> +    },
>>>>>> +    .probe = ssd16xx_probe,
>>>>>> +    .remove = ssd16xx_remove,
>>>>>> +    .shutdown = ssd16xx_shutdown,
>>>>>> +    .id_table = ssd16xx_id,
>>>>>> +};
>>>>>> +module_spi_driver(ssd16xx_spi_driver);
>>>>>> +
>>>>>> +MODULE_AUTHOR("Devarsh Thakkar <devarsht@ti.com>");
>>>>>> +MODULE_DESCRIPTION("DRM driver for Solomon SSD16xx e-paper 
>>>>>> display controller family");
>>>>>> +MODULE_LICENSE("GPL");
>>>>>
>>>>
>>>
>>
> 


  reply	other threads:[~2026-06-24 13:20 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-30 18:33 [PATCH 0/6] Add DRM driver for Solomon SSD16xx e-paper display controllers Devarsh Thakkar
2026-04-30 18:33 ` [PATCH 1/6] dt-bindings: vendor-prefixes: Add Dalian Good Display Co., Ltd Devarsh Thakkar
2026-05-04 10:18   ` Krzysztof Kozlowski
2026-04-30 18:33 ` [PATCH 2/6] dt-bindings/display: Add Solomon SSD16xx e-paper controller binding Devarsh Thakkar
2026-05-07 18:34   ` Rob Herring
2026-04-30 18:33 ` [PATCH 3/6] drm/tiny: Add DRM driver for Solomon SSD16xx e-paper display controllers Devarsh Thakkar
2026-05-05  7:05   ` Thomas Zimmermann
2026-05-08 16:12     ` Devarsh Thakkar
2026-06-17 11:47       ` Devarsh Thakkar
2026-06-18 15:41         ` Devarsh Thakkar
2026-06-23  9:17           ` Thomas Zimmermann
2026-06-24 13:19             ` Devarsh Thakkar [this message]
2026-04-30 18:33 ` [PATCH 4/6] drm/tiny: panel-ssd16xx: Add power management support Devarsh Thakkar
2026-05-05  7:08   ` Thomas Zimmermann
2026-04-30 18:33 ` [PATCH 5/6] MAINTAINERS: Add entry for Solomon SSD16xx DRM driver Devarsh Thakkar
2026-04-30 18:33 ` [PATCH 6/6] arm64: defconfig: Enable DRM_PANEL_SSD16XX Devarsh Thakkar
2026-05-01  9:30   ` Krzysztof Kozlowski
2026-05-05  6:25     ` Devarsh Thakkar
2026-05-05 12:42       ` Andrew Davis

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=eef355bd-6310-4138-b49d-8a8184d150e7@ti.com \
    --to=devarsht@ti.com \
    --cc=afd@ti.com \
    --cc=airlied@gmail.com \
    --cc=andrei@ti.com \
    --cc=bjorn.andersson@oss.qualcomm.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=jm@ti.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=neil.armstrong@linaro.org \
    --cc=praneeth@ti.com \
    --cc=r-donadkar@ti.com \
    --cc=r-sharma3@ti.com \
    --cc=robh@kernel.org \
    --cc=s-jain1@ti.com \
    --cc=sen@ti.com \
    --cc=simona@ffwll.ch \
    --cc=tzimmermann@suse.de \
    --cc=vigneshr@ti.com \
    --cc=y-d@ti.com \
    --cc=zaq14760@gmail.com \
    /path/to/YOUR_REPLY

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

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