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

Devarsh Thakkar devarsht at ti.com
Mon Oct 19 14:37:18 PDT 2026


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 at gmail.com>
Signed-off-by: Devarsh Thakkar <devarsht at ti.com>
---
Changes in v3:
- Whitespace fix in a comment, no functional changes
---
 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 7b0096448768..ff0e459082de 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 */
@@ -557,28 +586,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);
 	}
 }
 
@@ -700,21 +745,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 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




More information about the linux-arm-kernel mailing list