[PATCH v2 06/14] drm/solomon: ssd16xx: Add support for Solomon SSD1677 controller

From: Devarsh Thakkar

Date: Sun Sep 27 2026 - 14:26:08 EST


Add infrastructure to support the Solomon SSD1677 e-paper controller
(up to 960x680 pixels, 10-bit pixel-level X/Y RAM addressing).

The SSD1677 shares the same command address space as SSD1683 but
differs in several controller-level characteristics:

- RAM X/Y addressing: SSD1677 uses 10-bit pixel-level addresses
whereas SSD1683 uses 6-bit byte-level X and 9-bit Y. The
existing ram_x_address_bits / ram_y_address_bits fields in
ssd16xx_controller_config already handle the different format;
ssd16xx_fb_dirty is updated to compute the correct end address
from device->width using the addressing model:
byte-addressed: XEnd = panel_width/8 - 1
pixel-addressed: XEnd = panel_width - 1

- Deep sleep: only mode 0x03 is documented in SSD1677 (no level-1
RAM-retain mode 0x01 as in SSD1683). Both level1/level2 point
to SSD1683_DEEP_SLEEP_MODE_2 (0x03).

- ctrl2 full refresh uses 0xF4 (omits DISABLE_ANALOG/DISABLE_CLK
bits) per the reference implementation. 0xFF for partial refresh
is listed as valid in the SSD1677 datasheet; BIT(3) semantics are
undocumented — included pending hardware verification.

- Border waveform byte encoding is identical to SSD1683; the same
ssd1683_border_waveform_table[] is reused.

A new booster_soft_start_data / booster_soft_start_len field is added
to ssd16xx_device_config for panels that require BOOSTER_SOFT_START
(0x0C) initialisation. The command exists in both SSD1683 and SSD1677
but the tuning values are board/panel-specific; panels that do not need
it set the pointer to NULL. hw_init sends the command when non-NULL.

Controller limit validation is added in probe: panel dimensions must
not exceed the controller hardware maximum, and for byte-addressed X
controllers the panel width must be a multiple of 8.

Driver Output Control MUX, tx_buf allocation, and fb_dirty RAM window
end addresses now use the actual panel dimensions rather than the
controller maximum, correctly handling panels smaller than the
controller's rated capacity.

Co-developed-by: LiangCheng Wang <zaq14760@xxxxxxxxx>
Signed-off-by: Devarsh Thakkar <devarsht@xxxxxx>
---
Changes from v1:
- New patch: Extracted SSD1677 controller support into dedicated patch

drivers/gpu/drm/solomon/ssd16xx.c | 78 +++++++++++++++++++++++++++----
1 file changed, 68 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/solomon/ssd16xx.c b/drivers/gpu/drm/solomon/ssd16xx.c
index c478309d08e9..93fa064cf589 100644
--- a/drivers/gpu/drm/solomon/ssd16xx.c
+++ b/drivers/gpu/drm/solomon/ssd16xx.c
@@ -39,6 +39,7 @@

/* SPI command codes (common) */
#define SSD16XX_CMD_DRIVER_OUTPUT_CONTROL 0x01
+#define SSD16XX_CMD_BOOSTER_SOFT_START 0x0C
#define SSD16XX_CMD_DATA_ENTRY_MODE 0x11
#define SSD16XX_CMD_SW_RESET 0x12
#define SSD16XX_CMD_MASTER_ACTIVATION 0x20
@@ -181,6 +182,7 @@

enum ssd16xx_controller {
SSD1683 = 1,
+ SSD1677,
};

enum ssd16xx_model {
@@ -332,6 +334,10 @@ struct ssd16xx_device_config {
*/
enum ssd16xx_color_mode default_color_mode;

+ /* Optional BOOSTER_SOFT_START (0x0C) tuning bytes; NULL if unused. */
+ const u8 *booster_soft_start_data;
+ u8 booster_soft_start_len;
+
/* Panel-specific display mode (resolution and physical dimensions) */
const struct drm_display_mode *mode;
};
@@ -420,6 +426,29 @@ static const struct ssd16xx_controller_config ssd16xx_controller_configs[] = {
},
.ctrl2_load_temp_lut = SSD1683_CTRL2_LOAD_TEMP_LUT,
},
+ [SSD1677] = {
+ /*
+ * 10-bit pixel-level X/Y addressing; only deep sleep mode
+ * 0x03 is supported (no RAM-retain mode). Border waveform
+ * encoding matches SSD1683.
+ */
+ .max_width = 960,
+ .max_height = 680,
+ .ram_x_address_bits = 10,
+ .ram_y_address_bits = 10,
+ .has_temp_sensor_ctrl = true,
+ .deep_sleep_mode_level1 = SSD1683_DEEP_SLEEP_MODE_2, /* 0x03 */
+ .deep_sleep_mode_level2 = SSD1683_DEEP_SLEEP_MODE_2, /* 0x03 */
+ .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] = 0xFF,
+ [SSD16XX_REFRESH_FULL] = 0xF4,
+ [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 */
@@ -553,28 +582,44 @@ static void ssd16xx_send_data(struct ssd16xx_device *device, u8 data,
static void ssd16xx_send_x_param(struct ssd16xx_device *device, u16 x,
int *err)
{
+ u8 bits = device->controller_cfg->ram_x_address_bits;
+ u16 max_val = (1U << bits) - 1;
+
if (*err)
return;

- if (device->controller_cfg->ram_x_address_bits == 8) {
+ if (x > max_val)
+ x = max_val;
+
+ if (bits <= 8) {
ssd16xx_send_data(device, (u8)x, err);
} else {
+ u8 hi_mask = (1U << (bits - 8)) - 1;
+
ssd16xx_send_data(device, x & 0xFF, err);
- ssd16xx_send_data(device, (x >> 8) & 0xFF, err);
+ ssd16xx_send_data(device, (x >> 8) & hi_mask, err);
}
}

static void ssd16xx_send_y_param(struct ssd16xx_device *device, u16 y,
int *err)
{
+ u8 bits = device->controller_cfg->ram_y_address_bits;
+ u16 max_val = (1U << bits) - 1;
+
if (*err)
return;

- if (device->controller_cfg->ram_y_address_bits == 8) {
+ if (y > max_val)
+ y = max_val;
+
+ if (bits <= 8) {
ssd16xx_send_data(device, (u8)y, err);
} else {
+ u8 hi_mask = (1U << (bits - 8)) - 1;
+
ssd16xx_send_data(device, y & 0xFF, err);
- ssd16xx_send_data(device, (y >> 8) & 0xFF, err);
+ ssd16xx_send_data(device, (y >> 8) & hi_mask, err);
}
}

@@ -691,21 +736,34 @@ static int ssd16xx_hw_init(struct ssd16xx_device *device)

ssd16xx_hw_reset(device);

- /* Software reset */
+ /* Software reset (0x12): resets command/parameter registers to defaults. */
ssd16xx_send_cmd(device, SSD16XX_CMD_SW_RESET, &err);
ssd16xx_wait_for_device(device, &err);

- /* Driver output control (0x01): MUX ratio and scan direction. */
- ssd16xx_send_cmd(device, SSD16XX_CMD_DRIVER_OUTPUT_CONTROL, &err);
- ssd16xx_send_y_param(device, device->height - 1, &err);
- ssd16xx_send_data(device, device->device_cfg->driver_output_ctrl_byte3, &err);
-
/* Internal temperature sensor (SSD1683/SSD1680 only; not present in SSD1673) */
if (device->controller_cfg->has_temp_sensor_ctrl) {
ssd16xx_send_cmd(device, SSD1683_CMD_TEMPERATURE_SENSOR_CONTROL, &err);
ssd16xx_send_data(device, SSD1683_TEMP_SENSOR_INTERNAL, &err);
}

+ /*
+ * Booster soft-start (0x0C): panel-specific charge pump tuning.
+ * Some panels (e.g. PIXPAPER 4.26m on SSD1677) require this step;
+ * others (e.g. GDEY042T81 on SSD1683) omit it.
+ */
+ if (device->device_cfg->booster_soft_start_data) {
+ ssd16xx_send_cmd(device, SSD16XX_CMD_BOOSTER_SOFT_START, &err);
+ ssd16xx_send_data_bulk(device,
+ device->device_cfg->booster_soft_start_data,
+ device->device_cfg->booster_soft_start_len,
+ &err);
+ }
+
+ /* Driver output control (0x01): MUX ratio and scan direction. */
+ ssd16xx_send_cmd(device, SSD16XX_CMD_DRIVER_OUTPUT_CONTROL, &err);
+ ssd16xx_send_y_param(device, device->height - 1, &err);
+ ssd16xx_send_data(device, device->device_cfg->driver_output_ctrl_byte3, &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
--
2.39.1