Skip to content

Commit

Permalink
drivers: display: ssd16xx: Add support for the ssd1677 EPD driver chip
Browse files Browse the repository at this point in the history
Add support for the SSD1677 EPD driver chip with support for up to
960x680 pixel displays.

Tested with the Waveshare 4.26" 800x480 display with XIAO BLE board.
I believe it is the same as Good Display GDEQ0426T82.
Tested with the samples/drivers/display sample.

The SSD1677 requires x address to be full address, instead of the byte
address used by SSD16XX. Added a new quirk to handle this.

The display requires a different GDO control flag as the panel layout
might be different, add an option to set this.

The display also requires the scan direction for y axis to be reversed,
add an option to set this as well.

Signed-off-by: Nisarg Jhaveri <nisargjhaveri@gmail.com>
  • Loading branch information
nisargjhaveri committed Sep 16, 2024
1 parent 6700a15 commit 5e33de5
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 39 deletions.
1 change: 1 addition & 0 deletions drivers/display/Kconfig.ssd16xx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ config SSD16XX
DT_HAS_SOLOMON_SSD1608_ENABLED || \
DT_HAS_SOLOMON_SSD1673_ENABLED || \
DT_HAS_SOLOMON_SSD1675A_ENABLED || \
DT_HAS_SOLOMON_SSD1677_ENABLED || \
DT_HAS_SOLOMON_SSD1680_ENABLED || \
DT_HAS_SOLOMON_SSD1681_ENABLED
select MIPI_DBI
Expand Down
159 changes: 120 additions & 39 deletions drivers/display/ssd16xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ struct ssd16xx_quirks {
* SSD16XX_CMD_UPDATE_CTRL2 for a partial refresh.
*/
uint8_t ctrl2_partial;

/*
* Device specific flag deciding whether to pass in the byte address or the bit address
* when setting the RAM x position window or counter in SSD16XX_CMD_RAM_XPOS_CTRL or
* SSD16XX_CMD_RAM_XPOS_CNTR.
* Expected to be `true` for the devices that expects the byte address, `false` otherwise.
*/
bool x_addr_in_bytes;
};

struct ssd16xx_data {
Expand Down Expand Up @@ -105,6 +113,8 @@ struct ssd16xx_config {
uint16_t height;
uint16_t width;
uint8_t tssv;
uint8_t gdo_flags;
bool scan_y_reverse;
};

static int ssd16xx_set_profile(const struct device *dev,
Expand Down Expand Up @@ -369,33 +379,69 @@ static int ssd16xx_set_window(const struct device *dev,
}
}

switch (data->orientation) {
case DISPLAY_ORIENTATION_NORMAL:
x_start = (panel_h - 1 - y) / SSD16XX_PIXELS_PER_BYTE;
x_end = (panel_h - 1 - (y + desc->height - 1)) / SSD16XX_PIXELS_PER_BYTE;
y_start = x;
y_end = (x + desc->width - 1);
break;
case DISPLAY_ORIENTATION_ROTATED_90:
x_start = (panel_h - 1 - x) / SSD16XX_PIXELS_PER_BYTE;
x_end = (panel_h - 1 - (x + desc->width - 1)) / SSD16XX_PIXELS_PER_BYTE;
y_start = (config->width - 1 - y);
y_end = (config->width - 1 - (y + desc->height - 1));
break;
case DISPLAY_ORIENTATION_ROTATED_180:
x_start = y / SSD16XX_PIXELS_PER_BYTE;
x_end = (y + desc->height - 1) / SSD16XX_PIXELS_PER_BYTE;
y_start = (x + desc->width - 1);
y_end = x;
break;
case DISPLAY_ORIENTATION_ROTATED_270:
x_start = x / SSD16XX_PIXELS_PER_BYTE;
x_end = (x + desc->width - 1) / SSD16XX_PIXELS_PER_BYTE;
y_start = y;
y_end = (y + desc->height - 1);
break;
default:
return -EINVAL;
if (config->scan_y_reverse) {
switch (data->orientation) {
case DISPLAY_ORIENTATION_NORMAL:
x_start = (panel_h - 1 - y);
x_end = (panel_h - 1 - (y + desc->height - 1));
y_start = (config->width - 1 - x);
y_end = (config->width - 1 - (x + desc->width - 1));
break;
case DISPLAY_ORIENTATION_ROTATED_90:
x_start = x;
x_end = (x + desc->width - 1);
y_start = (config->width - 1 - y);
y_end = (config->width - 1 - (y + desc->height - 1));
break;
case DISPLAY_ORIENTATION_ROTATED_180:
x_start = y;
x_end = (y + desc->height - 1);
y_start = x;
y_end = (x + desc->width - 1);
break;
case DISPLAY_ORIENTATION_ROTATED_270:
x_start = (panel_h - 1 - x);
x_end = (panel_h - 1 - (x + desc->width - 1));
y_start = y;
y_end = (y + desc->height - 1);
break;
default:
return -EINVAL;
}
} else {
switch (data->orientation) {
case DISPLAY_ORIENTATION_NORMAL:
x_start = (panel_h - 1 - y);
x_end = (panel_h - 1 - (y + desc->height - 1));
y_start = x;
y_end = (x + desc->width - 1);
break;
case DISPLAY_ORIENTATION_ROTATED_90:
x_start = (panel_h - 1 - x);
x_end = (panel_h - 1 - (x + desc->width - 1));
y_start = (config->width - 1 - y);
y_end = (config->width - 1 - (y + desc->height - 1));
break;
case DISPLAY_ORIENTATION_ROTATED_180:
x_start = y;
x_end = (y + desc->height - 1);
y_start = (x + desc->width - 1);
y_end = x;
break;
case DISPLAY_ORIENTATION_ROTATED_270:
x_start = x;
x_end = (x + desc->width - 1);
y_start = y;
y_end = (y + desc->height - 1);
break;
default:
return -EINVAL;
}
}

if (config->quirks->x_addr_in_bytes) {
x_start /= SSD16XX_PIXELS_PER_BYTE;
x_end /= SSD16XX_PIXELS_PER_BYTE;
}

err = ssd16xx_set_ram_param(dev, x_start, x_end, y_start, y_end);
Expand Down Expand Up @@ -588,17 +634,30 @@ static int ssd16xx_set_pixel_format(const struct device *dev,
static int ssd16xx_set_orientation(const struct device *dev,
const enum display_orientation orientation)
{
const struct ssd16xx_config *config = dev->config;
struct ssd16xx_data *data = dev->data;
int err;

if (orientation == DISPLAY_ORIENTATION_NORMAL) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYIY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_90) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYDX;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_180) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYDY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_270) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYIX;
if (config->scan_y_reverse) {
if (orientation == DISPLAY_ORIENTATION_NORMAL) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYDY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_90) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYDX;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_180) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYIY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_270) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYIX;
}
} else {
if (orientation == DISPLAY_ORIENTATION_NORMAL) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYIY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_90) {
data->scan_mode = SSD16XX_DATA_ENTRY_XDYDX;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_180) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYDY;
} else if (orientation == DISPLAY_ORIENTATION_ROTATED_270) {
data->scan_mode = SSD16XX_DATA_ENTRY_XIYIX;
}
}

err = ssd16xx_write_uint8(dev, SSD16XX_CMD_ENTRY_MODE, data->scan_mode);
Expand All @@ -614,7 +673,7 @@ static int ssd16xx_set_orientation(const struct device *dev,
static int ssd16xx_clear_cntlr_mem(const struct device *dev, uint8_t ram_cmd)
{
const struct ssd16xx_config *config = dev->config;
uint16_t panel_h = config->height / EPD_PANEL_NUMOF_ROWS_PER_PAGE;
uint16_t panel_h = config->height;
uint16_t last_gate = config->width - 1;
uint8_t clear_page[64];
int err;
Expand All @@ -623,8 +682,9 @@ static int ssd16xx_clear_cntlr_mem(const struct device *dev, uint8_t ram_cmd)
* Clear unusable memory area when the resolution of the panel is not
* multiple of an octet.
*/
if (config->height % EPD_PANEL_NUMOF_ROWS_PER_PAGE) {
panel_h += 1;
if (config->quirks->x_addr_in_bytes) {
panel_h = (config->height + (EPD_PANEL_NUMOF_ROWS_PER_PAGE - 1)) /
EPD_PANEL_NUMOF_ROWS_PER_PAGE;
}

err = ssd16xx_write_uint8(dev, SSD16XX_CMD_ENTRY_MODE,
Expand Down Expand Up @@ -762,7 +822,7 @@ static int ssd16xx_set_profile(const struct device *dev,
}

gdo_len = push_y_param(dev, gdo, last_gate);
gdo[gdo_len++] = 0U;
gdo[gdo_len++] = config->gdo_flags;
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_GDO_CTRL, gdo, gdo_len);
if (err < 0) {
return err;
Expand Down Expand Up @@ -955,6 +1015,7 @@ static struct ssd16xx_quirks quirks_solomon_ssd1608 = {
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.x_addr_in_bytes = true,
};
#endif

Expand All @@ -966,6 +1027,7 @@ static struct ssd16xx_quirks quirks_solomon_ssd1673 = {
.pp_height_bits = 8,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.x_addr_in_bytes = true,
};
#endif

Expand All @@ -977,6 +1039,19 @@ static struct ssd16xx_quirks quirks_solomon_ssd1675a = {
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.x_addr_in_bytes = true,
};
#endif

#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1677)
static const struct ssd16xx_quirks quirks_solomon_ssd1677 = {
.max_width = 680,
.max_height = 960,
.pp_width_bits = 16,
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN2_CTRL2_DISPLAY,
.ctrl2_partial = SSD16XX_GEN2_CTRL2_DISPLAY | SSD16XX_GEN2_CTRL2_MODE2,
.x_addr_in_bytes = false,
};
#endif

Expand All @@ -988,6 +1063,7 @@ static const struct ssd16xx_quirks quirks_solomon_ssd1680 = {
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN2_CTRL2_DISPLAY,
.ctrl2_partial = SSD16XX_GEN2_CTRL2_DISPLAY | SSD16XX_GEN2_CTRL2_MODE2,
.x_addr_in_bytes = true,
};
#endif

Expand All @@ -999,6 +1075,7 @@ static struct ssd16xx_quirks quirks_solomon_ssd1681 = {
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN2_CTRL2_DISPLAY,
.ctrl2_partial = SSD16XX_GEN2_CTRL2_DISPLAY | SSD16XX_GEN2_CTRL2_MODE2,
.x_addr_in_bytes = true,
};
#endif

Expand Down Expand Up @@ -1063,7 +1140,9 @@ static struct ssd16xx_quirks quirks_solomon_ssd1681 = {
.height = DT_PROP(n, height), \
.width = DT_PROP(n, width), \
.rotation = DT_PROP(n, rotation), \
.scan_y_reverse = DT_PROP(n, scan_y_reverse), \
.tssv = DT_PROP_OR(n, tssv, 0), \
.gdo_flags = DT_PROP_OR(n, gdo_flags, 0), \
.softstart = SSD16XX_ASSIGN_ARRAY(n, softstart), \
.profiles = { \
[SSD16XX_PROFILE_FULL] = \
Expand All @@ -1089,6 +1168,8 @@ DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1673, SSD16XX_DEFINE,
&quirks_solomon_ssd1673);
DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1675a, SSD16XX_DEFINE,
&quirks_solomon_ssd1675a);
DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1677, SSD16XX_DEFINE,
&quirks_solomon_ssd1677);
DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1680, SSD16XX_DEFINE,
&quirks_solomon_ssd1680);
DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1681, SSD16XX_DEFINE,
Expand Down
8 changes: 8 additions & 0 deletions dts/bindings/display/solomon,ssd1677.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2022 Andreas Sandberg
# SPDX-License-Identifier: Apache-2.0

description: Solomon Systech SSD1677 960x680 EPD display controller

compatible: "solomon,ssd1677"

include: solomon,ssd16xx-common.yaml
14 changes: 14 additions & 0 deletions dts/bindings/display/solomon,ssd16xx-common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ properties:
description:
Display rotation (CW) in degrees.
If not defined, rotation is off by default.

scan-y-reverse:
type: boolean
description: Reverse the scan direction along Y axis for the display.

Some displays have the scan direction reversed along the Y axis.

gdo-flags:
type: int
description: Gate scanning sequence and direction

Additional flags passed into Driver Output control command to
determine the how the output pin layout for the panel is and
what should be scanning sequence and the direction.

child-binding:
description: |
Expand Down

0 comments on commit 5e33de5

Please sign in to comment.