From 63ce3d4f31c7b3d034579d9acfcbdb208a3ae1ec Mon Sep 17 00:00:00 2001 From: Nisarg Jhaveri Date: Wed, 3 Jul 2024 22:08:27 +0530 Subject: [PATCH 1/2] drivers: display: ssd16xx: Add support for the ssd1677 EPD driver chip 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 --- drivers/display/Kconfig.ssd16xx | 1 + drivers/display/ssd16xx.c | 159 +++++++++++++----- dts/bindings/display/solomon,ssd1677.yaml | 8 + .../display/solomon,ssd16xx-common.yaml | 14 ++ 4 files changed, 143 insertions(+), 39 deletions(-) create mode 100644 dts/bindings/display/solomon,ssd1677.yaml diff --git a/drivers/display/Kconfig.ssd16xx b/drivers/display/Kconfig.ssd16xx index 097351fec5ea16..c9fe019f32604a 100644 --- a/drivers/display/Kconfig.ssd16xx +++ b/drivers/display/Kconfig.ssd16xx @@ -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 diff --git a/drivers/display/ssd16xx.c b/drivers/display/ssd16xx.c index 7dfaa1211771be..da6d3ca026ccaa 100644 --- a/drivers/display/ssd16xx.c +++ b/drivers/display/ssd16xx.c @@ -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 { @@ -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, @@ -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); @@ -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); @@ -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; @@ -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, @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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] = \ @@ -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, diff --git a/dts/bindings/display/solomon,ssd1677.yaml b/dts/bindings/display/solomon,ssd1677.yaml new file mode 100644 index 00000000000000..6867c2c004842e --- /dev/null +++ b/dts/bindings/display/solomon,ssd1677.yaml @@ -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 diff --git a/dts/bindings/display/solomon,ssd16xx-common.yaml b/dts/bindings/display/solomon,ssd16xx-common.yaml index 343f9a9c69b488..f83f95d8bd5e02 100644 --- a/dts/bindings/display/solomon,ssd16xx-common.yaml +++ b/dts/bindings/display/solomon,ssd16xx-common.yaml @@ -39,6 +39,20 @@ properties: 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: | Child nodes describe refresh profiles. Each refresh profile From f8d4fa152862b0dfa9cfee3f85d2304aaacc9a9c Mon Sep 17 00:00:00 2001 From: Nisarg Jhaveri Date: Mon, 16 Sep 2024 10:13:42 +0530 Subject: [PATCH 2/2] drivers: display: ssd16xx: clang format parts of ssd16xx.c Run `clang format` on parts of `ssd16xx.c` to format some recent changes correctly. Signed-off-by: Nisarg Jhaveri --- drivers/display/ssd16xx.c | 96 ++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/drivers/display/ssd16xx.c b/drivers/display/ssd16xx.c index da6d3ca026ccaa..3a614ecdb18aa2 100644 --- a/drivers/display/ssd16xx.c +++ b/drivers/display/ssd16xx.c @@ -1122,55 +1122,47 @@ static struct ssd16xx_quirks quirks_solomon_ssd1681 = { (_SSD16XX_PROFILE_PTR(n)), \ NULL) -#define SSD16XX_DEFINE(n, quirks_ptr) \ - SSD16XX_MAKE_ARRAY_OPT(n, softstart); \ - \ - DT_FOREACH_CHILD(n, SSD16XX_PROFILE); \ - \ - static const struct ssd16xx_config ssd16xx_cfg_ ## n = { \ - .mipi_dev = DEVICE_DT_GET(DT_PARENT(n)), \ - .dbi_config = { \ - .mode = MIPI_DBI_MODE_SPI_4WIRE, \ - .config = MIPI_DBI_SPI_CONFIG_DT(n, \ - SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | \ - SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \ - }, \ - .busy_gpio = GPIO_DT_SPEC_GET(n, busy_gpios), \ - .quirks = quirks_ptr, \ - .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] = \ - SSD16XX_PROFILE_PTR(DT_CHILD(n, full)), \ - [SSD16XX_PROFILE_PARTIAL] = \ - SSD16XX_PROFILE_PTR(DT_CHILD(n, partial)),\ - }, \ - }; \ - \ - static struct ssd16xx_data ssd16xx_data_ ## n; \ - \ - DEVICE_DT_DEFINE(n, \ - ssd16xx_init, NULL, \ - &ssd16xx_data_ ## n, \ - &ssd16xx_cfg_ ## n, \ - POST_KERNEL, \ - CONFIG_DISPLAY_INIT_PRIORITY, \ - &ssd16xx_driver_api) - -DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1608, SSD16XX_DEFINE, - &quirks_solomon_ssd1608); -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, - &quirks_solomon_ssd1681); +#define SSD16XX_DEFINE(n, quirks_ptr) \ + SSD16XX_MAKE_ARRAY_OPT(n, softstart); \ + \ + DT_FOREACH_CHILD(n, SSD16XX_PROFILE); \ + \ + static const struct ssd16xx_config ssd16xx_cfg_##n = { \ + .mipi_dev = DEVICE_DT_GET(DT_PARENT(n)), \ + .dbi_config = \ + { \ + .mode = MIPI_DBI_MODE_SPI_4WIRE, \ + .config = MIPI_DBI_SPI_CONFIG_DT( \ + n, \ + SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_HOLD_ON_CS | \ + SPI_LOCK_ON, \ + 0), \ + }, \ + .busy_gpio = GPIO_DT_SPEC_GET(n, busy_gpios), \ + .quirks = quirks_ptr, \ + .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] = SSD16XX_PROFILE_PTR(DT_CHILD(n, full)), \ + [SSD16XX_PROFILE_PARTIAL] = \ + SSD16XX_PROFILE_PTR(DT_CHILD(n, partial)), \ + }, \ + }; \ + \ + static struct ssd16xx_data ssd16xx_data_##n; \ + \ + DEVICE_DT_DEFINE(n, ssd16xx_init, NULL, &ssd16xx_data_##n, &ssd16xx_cfg_##n, POST_KERNEL, \ + CONFIG_DISPLAY_INIT_PRIORITY, &ssd16xx_driver_api) + +DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1608, SSD16XX_DEFINE, &quirks_solomon_ssd1608); +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, &quirks_solomon_ssd1681);