From 58762f9b598f6a4ce62f9bf414a8ffd46e030058 Mon Sep 17 00:00:00 2001 From: Giyora Haroon Date: Sun, 8 Jun 2025 23:00:29 +0300 Subject: [PATCH] drivers: display: Introduce ST7565R Introduce support for Sitronix's ST7565R display controller. Tested on JHD12864 LCD display. Signed-off-by: Giyora Haroon --- drivers/display/CMakeLists.txt | 1 + drivers/display/Kconfig | 1 + drivers/display/Kconfig.st7565r | 11 + drivers/display/display_st7565r.c | 542 ++++++++++++++++++++ drivers/display/display_st7565r.h | 127 +++++ dts/bindings/display/sitronix,st7565r.yaml | 134 +++++ tests/drivers/build_all/display/app.overlay | 12 + 7 files changed, 828 insertions(+) create mode 100644 drivers/display/Kconfig.st7565r create mode 100644 drivers/display/display_st7565r.c create mode 100644 drivers/display/display_st7565r.h create mode 100644 dts/bindings/display/sitronix,st7565r.yaml diff --git a/drivers/display/CMakeLists.txt b/drivers/display/CMakeLists.txt index 32e8bc190b9a2..02626069a586f 100644 --- a/drivers/display/CMakeLists.txt +++ b/drivers/display/CMakeLists.txt @@ -27,6 +27,7 @@ zephyr_library_sources_ifdef(CONFIG_SSD1331 display_ssd1331.c) zephyr_library_sources_ifdef(CONFIG_SSD135X display_ssd135x.c) zephyr_library_sources_ifdef(CONFIG_ST730X display_st730x.c) zephyr_library_sources_ifdef(CONFIG_ST75256 display_st75256.c) +zephyr_library_sources_ifdef(CONFIG_ST7565R display_st7565r.c) zephyr_library_sources_ifdef(CONFIG_ST7567 display_st7567.c) zephyr_library_sources_ifdef(CONFIG_ST7789V display_st7789v.c) zephyr_library_sources_ifdef(CONFIG_ST7735R display_st7735r.c) diff --git a/drivers/display/Kconfig b/drivers/display/Kconfig index 7d4ada3bd6ab0..1de47a59d99a9 100644 --- a/drivers/display/Kconfig +++ b/drivers/display/Kconfig @@ -37,6 +37,7 @@ source "drivers/display/Kconfig.ssd135x" source "drivers/display/Kconfig.ssd1363" source "drivers/display/Kconfig.st730x" source "drivers/display/Kconfig.st75256" +source "drivers/display/Kconfig.st7565r" source "drivers/display/Kconfig.st7567" source "drivers/display/Kconfig.st7735r" source "drivers/display/Kconfig.st7789v" diff --git a/drivers/display/Kconfig.st7565r b/drivers/display/Kconfig.st7565r new file mode 100644 index 0000000000000..5d3102eac171b --- /dev/null +++ b/drivers/display/Kconfig.st7565r @@ -0,0 +1,11 @@ +# Copyright (c) Giyora Haroon +# SPDX-License-Identifier: Apache-2.0 + +config ST7565R + bool "ST7565R display controller driver" + default y + depends on DT_HAS_SITRONIX_ST7565R_ENABLED + select SPI + select GPIO + help + Enable driver for the ST7565R display controller. diff --git a/drivers/display/display_st7565r.c b/drivers/display/display_st7565r.c new file mode 100644 index 0000000000000..952d6d0a272c0 --- /dev/null +++ b/drivers/display/display_st7565r.c @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2025 Giyora Haroon + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(st7565r, CONFIG_DISPLAY_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include + +#include "display_st7565r.h" + +/* Default contrast (midrange) */ +#define ST7565R_DEFAULT_CONTRAST 0x1F + +/* + * Default power control: Booster, Regulator, Follower ON. + * In this setting, only the internal power supply is used. Refer to Table 8 in + * the 'The Power Supply Circuits' section. + */ +#define ST7565R_DEFAULT_POWER_CONTROL_VAL \ + (ST7565R_POWER_CONTROL_VB_MASK | ST7565R_POWER_CONTROL_VR_MASK | \ + ST7565R_POWER_CONTROL_VF_MASK) + +/* Default resistor ratio: 4 (adjust based on display characteristics) */ +#define ST7565R_DEFAULT_RESISTOR_RATIO 4 + +/* Default booster ratio */ +#define ST7565R_DEFAULT_BOOSTER_RATIO ST7565R_SET_BOOSTER_RATIO_2X_3X_4X + +/* + * Default Time in milliseconds to wait before initializing the + * controller after power-on. + */ +#define ST7565R_DEFAULT_READY_TIME_MS 10 + +/* Function pointer types */ +typedef bool (*st7565r_bus_ready_fn)(const struct device *dev); +typedef int (*st7565r_write_bus_fn)(const struct device *dev, uint8_t *buf, size_t len, + bool command); +typedef const char *(*st7565r_bus_name_fn)(const struct device *dev); + +/* Configuration struct */ +struct st7565r_config { + struct spi_dt_spec bus; + struct gpio_dt_spec data_cmd; + struct gpio_dt_spec reset; + st7565r_bus_ready_fn bus_ready; + st7565r_write_bus_fn write_bus; + st7565r_bus_name_fn bus_name; + uint16_t height; + uint16_t width; + uint8_t segment_offset; + uint8_t lcd_bias; /* 7 or 9 */ + uint8_t power_control_val; /* 3-bit mask for power circuits */ + uint8_t resistor_ratio; /* 0-7 */ + uint8_t booster_ratio; /* 0x00, 0x01, or 0x03 */ + bool segment_remap; + bool com_invdir; + bool color_inversion; + int ready_time_ms; +}; + +/* Runtime data struct */ +struct st7565r_data { + enum display_pixel_format pf; +}; + +/* SPI Bus implementation */ +static bool st7565r_bus_ready_spi(const struct device *dev) +{ + const struct st7565r_config *config = dev->config; + + if (config->data_cmd.port) { + if (!gpio_is_ready_dt(&config->data_cmd)) { + LOG_ERR("Data/Command GPIO %s not ready!", config->data_cmd.port->name); + return false; + } + + if (gpio_pin_configure_dt(&config->data_cmd, GPIO_OUTPUT_INACTIVE) < 0) { + LOG_ERR("Could not configure Data/Command GPIO!"); + return false; + } + } + + if (!spi_is_ready_dt(&config->bus)) { + LOG_ERR("SPI bus %s not ready", config->bus.bus->name); + return false; + } + + return true; +} + +static int st7565r_write_bus_spi(const struct device *dev, uint8_t *buf, size_t len, bool command) +{ + const struct st7565r_config *config = dev->config; + int ret; + + /* Set D/C pin based on command/data */ + if (config->data_cmd.port) { + gpio_pin_set_dt(&config->data_cmd, command ? 0 : 1); + } else { + LOG_ERR("Data/Command GPIO not specified for SPI!"); + return -ENODEV; + } + + const struct spi_buf tx_buf = {.buf = buf, .len = len}; + + const struct spi_buf_set tx_bufs = {.buffers = &tx_buf, .count = 1}; + + ret = spi_write_dt(&config->bus, &tx_bufs); + if (ret < 0) { + LOG_ERR("SPI write failed: %d", ret); + } + + return ret; +} + +static const char *st7565r_bus_name_spi(const struct device *dev) +{ + const struct st7565r_config *config = dev->config; + + return config->bus.bus->name; +} + +/* Generic bus functions */ +static inline bool st7565r_bus_ready(const struct device *dev) +{ + const struct st7565r_config *config = dev->config; + + return config->bus_ready(dev); +} + +static inline int st7565r_write_bus(const struct device *dev, uint8_t *buf, size_t len, + bool command) +{ + const struct st7565r_config *config = dev->config; + + return config->write_bus(dev, buf, len, command); +} + +/* Helper to send a single command byte */ +static int st7565r_send_cmd(const struct device *dev, uint8_t cmd) +{ + return st7565r_write_bus(dev, &cmd, 1, true); +} + +/* Helper to send a command byte followed by a data byte */ +static int st7565r_send_cmd_data(const struct device *dev, uint8_t cmd, uint8_t data) +{ + uint8_t buf[2] = {cmd, data}; + + return st7565r_write_bus(dev, buf, 2, true); +} + +/* Helper to clear the display's RAM */ +static int st7565r_clear_ram(const struct device *dev) +{ + const struct st7565r_config *config = dev->config; + + if (config->width > ST7565R_LCDWIDTH) { + LOG_ERR("Configured width %d exceeds maximum supported (%d)", config->width, + ST7565R_LCDWIDTH); + return -EINVAL; + } + + if (config->height > ST7565R_LCDHEIGHT) { + LOG_ERR("Configured height %d exceeds maximum supported (%d)", config->height, + ST7565R_LCDHEIGHT); + return -EINVAL; + } + + uint8_t zero_buf[ST7565R_LCDWIDTH] = {0}; + uint8_t col_cmds[2] = {ST7565R_SET_HIGHER_COL_ADDRESS_CMD, + ST7565R_SET_LOWER_COL_ADDRESS_CMD}; + const uint8_t num_pages = config->height / 8; + + for (uint8_t page = 0; page < num_pages; page++) { + uint8_t page_cmd = ST7565R_SET_PAGE_START_ADDRESS_CMD | + (page & ST7565R_SET_PAGE_START_ADDRESS_VAL_MASK); + if (st7565r_send_cmd(dev, page_cmd) != 0) { + LOG_ERR("Failed to set page address"); + return -EIO; + } + + /* Reset column address to 0 for each new page. */ + if (st7565r_write_bus(dev, col_cmds, sizeof(col_cmds), true) != 0) { + LOG_ERR("Failed to set column address"); + return -EIO; + } + + /* Write config->width zero bytes to clear the page's columns. */ + if (st7565r_write_bus(dev, zero_buf, config->width, false) != 0) { + LOG_ERR("Failed to clear page %d", page); + return -EIO; + } + } + + return 0; +} + +/* Display API implementations */ + +/* Turn LCD on (restore the frame buffer content to the display) */ +static int st7565r_resume(const struct device *dev) +{ + return st7565r_send_cmd(dev, ST7565R_DISPLAY_ON); +} + +/* + * Turn LCD off. + * This function blanks the complete display. The content of the frame + * buffer will be retained while blanking is enabled. + */ +static int st7565r_suspend(const struct device *dev) +{ + return st7565r_send_cmd(dev, ST7565R_DISPLAY_OFF); +} + +static int st7565r_write(const struct device *dev, const uint16_t x, const uint16_t y, + const struct display_buffer_descriptor *desc, const void *buf) +{ + const struct st7565r_config *config = dev->config; + size_t buf_len; + uint8_t x_offset = x + config->segment_offset; + uint8_t *buf_ptr = (uint8_t *)buf; + uint8_t cmd_buf[2]; + + if (desc == NULL || buf == NULL) { + LOG_ERR("Display buffer is not available"); + return -EINVAL; + } + + if (desc->pitch < desc->width) { + LOG_ERR("Pitch is smaller than width"); + return -EINVAL; + } + + buf_len = MIN(desc->buf_size, desc->height * desc->width / 8); + if (buf_len == 0U) { + LOG_ERR("Display buffer is empty"); + return -EINVAL; + } + + if (desc->pitch > desc->width) { + LOG_ERR("Unsupported mode: pitch > width"); + return -ENOTSUP; + } + + if (x + desc->width > config->width) { + LOG_ERR("Buffer width out of bounds: %u + %u > %u", x, desc->width, config->width); + return -EINVAL; + } + + if (y + desc->height > config->height) { + LOG_ERR("Buffer height out of bounds: %u + %u > %u", y, desc->height, + config->height); + return -EINVAL; + } + + if ((y % 8) != 0U) { + LOG_ERR("Y coordinate must be page-aligned (multiple of 8)"); + return -EINVAL; + } + if ((desc->height % 8) != 0U) { + LOG_ERR("Buffer height must be page-aligned (multiple of 8)"); + return -EINVAL; + } + + uint8_t start_page = y / 8; + uint8_t end_page = (y + desc->height - 1) / 8; + + for (uint8_t page = start_page; page <= end_page; page++) { + uint8_t current_col = x_offset; + uint8_t page_cmd = ST7565R_SET_PAGE_START_ADDRESS_CMD | + (page & ST7565R_SET_PAGE_START_ADDRESS_VAL_MASK); + + /* Set the page address */ + if (st7565r_send_cmd(dev, page_cmd) != 0) { + LOG_ERR("Failed to set page address"); + return -EIO; + } + + /* Set column address high nibble */ + cmd_buf[0] = ST7565R_SET_HIGHER_COL_ADDRESS_CMD | + ((current_col >> 4) & ST7565R_SET_HIGHER_COL_ADDRESS_VAL_MASK); + /* Set column address low nibble */ + cmd_buf[1] = ST7565R_SET_LOWER_COL_ADDRESS_CMD | + (current_col & ST7565R_SET_LOWER_COL_ADDRESS_VAL_MASK); + + if (st7565r_write_bus(dev, cmd_buf, 2, true) != 0) { + LOG_ERR("Failed to set column address"); + return -EIO; + } + + /* Write data for this page */ + if (st7565r_write_bus(dev, buf_ptr, desc->width, false) != 0) { + LOG_ERR("Failed to write data"); + return -EIO; + } + + /* Advance buffer pointer to the next page (assumes pitch == width) */ + buf_ptr += desc->width; + if (buf_ptr >= ((uint8_t *)buf + buf_len)) { + LOG_ERR("Exceeded buffer length"); + return -EIO; + } + } + + return 0; +} + +static int st7565r_set_contrast(const struct device *dev, const uint8_t contrast) +{ + uint8_t val = contrast & ST7565R_SET_CONTRAST_VALUE_MASK; + + return st7565r_send_cmd_data(dev, ST7565R_SET_CONTRAST_CTRL_CMD, val); +} + +static void st7565r_get_capabilities(const struct device *dev, struct display_capabilities *caps) +{ + const struct st7565r_config *config = dev->config; + struct st7565r_data *data = dev->data; + + memset(caps, 0, sizeof(struct display_capabilities)); + caps->x_resolution = config->width; + caps->y_resolution = config->height; + caps->supported_pixel_formats = PIXEL_FORMAT_MONO10 | PIXEL_FORMAT_MONO01; + caps->current_pixel_format = data->pf; + caps->screen_info = SCREEN_INFO_MONO_VTILED; + caps->current_orientation = DISPLAY_ORIENTATION_NORMAL; +} + +static int st7565r_set_pixel_format(const struct device *dev, const enum display_pixel_format pf) +{ + struct st7565r_data *data = dev->data; + uint8_t cmd; + int ret; + + if (pf == data->pf) { + return 0; + } + + if (pf == PIXEL_FORMAT_MONO10) { /* 1=black, 0=white */ + cmd = ST7565R_SET_NORMAL_DISPLAY; + } else if (pf == PIXEL_FORMAT_MONO01) { /* 0=black, 1=white */ + cmd = ST7565R_SET_REVERSE_DISPLAY; + } else { + LOG_WRN("Unsupported pixel format: 0x%x", pf); + return -ENOTSUP; + } + + ret = st7565r_send_cmd(dev, cmd); + if (ret) { + LOG_ERR("Failed to set pixel format: %d", ret); + return ret; + } + + data->pf = pf; + + return 0; +} + +static int st7565r_init_device(const struct device *dev) +{ + const struct st7565r_config *config = dev->config; + struct st7565r_data *data = dev->data; + int ret; + + /* Follow initialization sequence from datasheet (Page 51) */ + + /* 1. Reset if pin connected */ + if (config->reset.port) { + gpio_pin_set_dt(&config->reset, 1); + k_sleep(K_MSEC(ST7565R_RESET_DELAY)); /* 1ms */ + + gpio_pin_set_dt(&config->reset, 0); + k_sleep(K_MSEC(ST7565R_RESET_DELAY)); /* 1ms */ + } + + /* 2. Set LCD Bias */ + ret = st7565r_send_cmd(dev, (config->lcd_bias == 9) ? ST7565R_SET_LCD_BIAS_9 + : ST7565R_SET_LCD_BIAS_7); + if (ret != 0) { + LOG_ERR("Failed to set LCD Bias: %d", ret); + return ret; + } + + /* 3. Set ADC / Segment Remap */ + ret = st7565r_send_cmd(dev, config->segment_remap ? ST7565R_SET_SEGMENT_MAP_REVERSED + : ST7565R_SET_SEGMENT_MAP_NORMAL); + if (ret != 0) { + LOG_ERR("Failed to set segment map: %d", ret); + return ret; + } + + /* 4. Set COM Scan Direction */ + ret = st7565r_send_cmd(dev, config->com_invdir ? ST7565R_SET_COM_OUTPUT_SCAN_REVERSED + : ST7565R_SET_COM_OUTPUT_SCAN_NORMAL); + if (ret != 0) { + LOG_ERR("Failed to set COM scan direction: %d", ret); + return ret; + } + + /* 5. Set Resistor Ratio */ + uint8_t rr_cmd = ST7565R_SET_RESISTOR_RATIO_CMD | + (config->resistor_ratio & ST7565R_SET_RESISTOR_RATIO_VAL_MASK); + ret = st7565r_send_cmd(dev, rr_cmd); + if (ret != 0) { + LOG_ERR("Failed to set resistor ratio: %d", ret); + return ret; + } + + /* 6. Set Electronic Volume (contrast; can be set externally using the API) */ + ret = st7565r_set_contrast(dev, ST7565R_DEFAULT_CONTRAST); + if (ret != 0) { + LOG_ERR("Failed to set default contrast: %d", ret); + return ret; + } + + /* 7. Set Power Control - enable circuits */ + uint8_t pc_cmd = ST7565R_POWER_CONTROL_CMD | + (config->power_control_val & ST7565R_POWER_CONTROL_ALL_ON_MASK); + ret = st7565r_send_cmd(dev, pc_cmd); + if (ret != 0) { + LOG_ERR("Failed to set power control: %d", ret); + return ret; + } + + /* Set Booster Ratio */ + ret = st7565r_send_cmd_data(dev, ST7565R_SET_BOOSTER_RATIO_SET_CMD, config->booster_ratio); + if (ret != 0) { + LOG_ERR("Failed to set booster ratio: %d", ret); + return ret; + } + + /* Set Initial Pixel Format (can be set externally using the API) */ + data->pf = config->color_inversion ? PIXEL_FORMAT_MONO01 : PIXEL_FORMAT_MONO10; + ret = st7565r_send_cmd(dev, config->color_inversion ? ST7565R_SET_REVERSE_DISPLAY + : ST7565R_SET_NORMAL_DISPLAY); + if (ret != 0) { + LOG_ERR("Failed to set initial display mode: %d", ret); + return ret; + } + + /* Initialize DDRAM (Pages 0 - 8) */ + ret = st7565r_clear_ram(dev); + if (ret != 0) { + LOG_ERR("Failed to clear display RAM: %d", ret); + return ret; + } + + return 0; +} + +static int st7565r_init(const struct device *dev) +{ + int ret; + const struct st7565r_config *config = dev->config; + + /* Specify timeout as an absolute time since system boot */ + k_sleep(K_TIMEOUT_ABS_MS(config->ready_time_ms)); + + if (!st7565r_bus_ready(dev)) { + LOG_ERR("Bus device %s not ready!", config->bus_name(dev)); + return -ENODEV; + } + + if (config->reset.port) { + if (!gpio_is_ready_dt(&config->reset)) { + LOG_ERR("Reset GPIO %s not ready!", config->reset.port->name); + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT); + if (ret < 0) { + LOG_ERR("Could not configure reset GPIO!"); + return ret; + } + } + + if (st7565r_init_device(dev) != 0) { + LOG_ERR("Failed to initialize device!"); + return -EIO; + } + + return 0; +} + +static const struct display_driver_api st7565r_driver_api = { + .blanking_on = st7565r_suspend, + .blanking_off = st7565r_resume, + .write = st7565r_write, + .set_contrast = st7565r_set_contrast, + .get_capabilities = st7565r_get_capabilities, + .set_pixel_format = st7565r_set_pixel_format, +}; + +#define DT_DRV_COMPAT sitronix_st7565r + +#if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 0 +#warning "ST7565R display driver enabled without any devices" +#endif + +/* Zephyr Device Instantiation Macro */ +#define ST7565R_DEFINE(node_id) \ + static struct st7565r_data data_##node_id; \ + static const struct st7565r_config config_##node_id = { \ + .bus = SPI_DT_SPEC_GET( \ + node_id, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0), \ + .bus_ready = st7565r_bus_ready_spi, \ + .write_bus = st7565r_write_bus_spi, \ + .bus_name = st7565r_bus_name_spi, \ + .data_cmd = GPIO_DT_SPEC_GET(node_id, data_cmd_gpios), \ + .reset = GPIO_DT_SPEC_GET_OR(node_id, reset_gpios, {0}), \ + .height = DT_PROP(node_id, height), \ + .width = DT_PROP(node_id, width), \ + .segment_offset = DT_PROP_OR(node_id, segment_offset, 0), \ + .lcd_bias = DT_PROP(node_id, lcd_bias), \ + .power_control_val = \ + DT_PROP_OR(node_id, power_control_val, ST7565R_DEFAULT_POWER_CONTROL_VAL), \ + .resistor_ratio = \ + DT_PROP_OR(node_id, resistor_ratio, ST7565R_DEFAULT_RESISTOR_RATIO), \ + .booster_ratio = \ + DT_PROP_OR(node_id, booster_ratio, ST7565R_DEFAULT_BOOSTER_RATIO), \ + .segment_remap = DT_PROP(node_id, segment_remap), \ + .com_invdir = DT_PROP(node_id, com_invdir), \ + .color_inversion = DT_PROP(node_id, inversion_on), \ + .ready_time_ms = \ + DT_PROP_OR(node_id, ready_time_ms, ST7565R_DEFAULT_READY_TIME_MS), \ + }; \ + \ + DEVICE_DT_DEFINE(node_id, st7565r_init, NULL, &data_##node_id, &config_##node_id, \ + POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &st7565r_driver_api); + +DT_FOREACH_STATUS_OKAY(sitronix_st7565r, ST7565R_DEFINE) diff --git a/drivers/display/display_st7565r.h b/drivers/display/display_st7565r.h new file mode 100644 index 0000000000000..2088053106bf2 --- /dev/null +++ b/drivers/display/display_st7565r.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2025 Giyora Haroon + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ZEPHYR_DRIVERS_DISPLAY_ST7565R_H_ +#define _ZEPHYR_DRIVERS_DISPLAY_ST7565R_H_ + +#include + +/* + * Display Control Commands + */ + +#define ST7565R_DISPLAY_OFF 0xAE +#define ST7565R_DISPLAY_ON 0xAF + +/* Sets the display RAM display start line address (0-63). Command format: 01xxxxxx */ +#define ST7565R_SET_START_LINE_CMD 0x40 +#define ST7565R_SET_START_LINE_VAL_MASK GENMASK(5, 0) + +/* When RAM data is 1, the pixel is ON */ +#define ST7565R_SET_NORMAL_DISPLAY 0xA6 +/* When RAM data is 0, the pixel is ON */ +#define ST7565R_SET_REVERSE_DISPLAY 0xA7 + +/* Forces the entire display ON, regardless of RAM content */ +#define ST7565R_SET_ENTIRE_DISPLAY_ON 0xA5 +/* Returns display to normal RAM content dependent mode */ +#define ST7565R_SET_ENTIRE_DISPLAY_OFF 0xA4 + +/* + * Addressing Setting Commands + */ + +#define ST7565R_LCDWIDTH 132 +#define ST7565R_LCDHEIGHT 64 + +/* Sets the display RAM page address (PAGE0 ~ PAGE8). Command format: 1011xxxx */ +#define ST7565R_SET_PAGE_START_ADDRESS_CMD 0xB0 +#define ST7565R_SET_PAGE_START_ADDRESS_VAL_MASK GENMASK(3, 0) + +/* Sets the display RAM column address upper 4 bits (A7-A4). Command format: 0001xxxx */ +#define ST7565R_SET_HIGHER_COL_ADDRESS_CMD 0x10 +#define ST7565R_SET_HIGHER_COL_ADDRESS_VAL_MASK GENMASK(3, 0) + +/* Sets the display RAM column address lower 4 bits (A3-A0). Command format: 0000xxxx */ +#define ST7565R_SET_LOWER_COL_ADDRESS_CMD 0x00 +#define ST7565R_SET_LOWER_COL_ADDRESS_VAL_MASK GENMASK(3, 0) + +/* + * Hardware Configuration Commands + */ + +/* Select Normal (ADC=0) or Reversed (ADC=1) segment driver direction */ +#define ST7565R_SET_SEGMENT_MAP_NORMAL 0xA0 /* SEG0 -> SEG131 */ +#define ST7565R_SET_SEGMENT_MAP_REVERSED 0xA1 /* SEG131 -> SEG0 */ + +/* Select the scan direction of the COM output terminal */ +#define ST7565R_SET_COM_OUTPUT_SCAN_NORMAL 0xC0 /* COM0 -> COM63 */ +#define ST7565R_SET_COM_OUTPUT_SCAN_REVERSED 0xC8 /* COM63 -> COM0 */ + +/* Select LCD Bias Ratio */ +#define ST7565R_SET_LCD_BIAS_9 0xA2 /* 1/9 Bias */ +#define ST7565R_SET_LCD_BIAS_7 0xA3 /* 1/7 Bias */ + +/* + * Timing and Driving Scheme Setting Commands + */ + +/* Control internal power supply circuits. Command format: 00101xxx */ +#define ST7565R_POWER_CONTROL_CMD 0x28 +#define ST7565R_POWER_CONTROL_VB_MASK GENMASK(2, 2) /* Bit D2: Voltage Booster */ +#define ST7565R_POWER_CONTROL_VR_MASK GENMASK(1, 1) /* Bit D1: Voltage Regulator */ +#define ST7565R_POWER_CONTROL_VF_MASK GENMASK(0, 0) /* Bit D0: Voltage Follower */ +#define ST7565R_POWER_CONTROL_ALL_ON_MASK GENMASK(2, 0) + +/* Set internal resistor ratio for Vo regulation. Command format: 00100xxx */ +#define ST7565R_SET_RESISTOR_RATIO_CMD 0x20 +#define ST7565R_SET_RESISTOR_RATIO_VAL_MASK GENMASK(2, 0) + +/* + * This command makes it possible to adjust the contrast of the liquid + * crystal display by controlling the LCD drive voltage V0 through the + * output from the voltage regulator circuits of the internal liquid + * crystal power supply. This command is a double byte command used as + * a pair with the electronic volume mode set command and the electronic + * volume register set command, and both commands must be issued one + * after the other. + */ +#define ST7565R_SET_CONTRAST_CTRL_CMD 0x81 +/* Second byte is 6-bit value 0-63 (0x00 - 0x3F) */ +#define ST7565R_SET_CONTRAST_VALUE_MASK GENMASK(5, 0) + +/* Set Booster Ratio. Double byte command. */ +#define ST7565R_SET_BOOSTER_RATIO_SET_CMD 0xF8 +/* Second byte sets booster ratio */ +#define ST7565R_SET_BOOSTER_RATIO_2X_3X_4X 0x00 /* Value for 2x, 3x, 4x (default) */ +#define ST7565R_SET_BOOSTER_RATIO_5X 0x01 /* Value for 5x */ +#define ST7565R_SET_BOOSTER_RATIO_6X 0x03 /* Value for 6x */ + +/* + * Sleep Mode Commands (Double Byte) + */ + +/* Preceding command byte for Sleep Mode Set */ +#define ST7565R_SLEEP_MODE_ENTER_CMD 0xAC /* Enter Sleep Mode */ +#define ST7565R_SLEEP_MODE_EXIT_CMD 0xAD /* Exit Sleep Mode (Normal) */ +/* Following command byte for Sleep Mode Set */ +#define ST7565R_SLEEP_MODE_FOLLOW_BYTE 0x00 + +/* + * Other Commands + */ + +/* Software Reset */ +#define ST7565R_RESET 0xE2 + +/* NOP (No Operation) */ +#define ST7565R_NOP 0xE3 + +/* time constants in ms */ + +#define ST7565R_RESET_DELAY 1 + +#endif /* _ZEPHYR_DRIVERS_DISPLAY_ST7565R_H_ */ diff --git a/dts/bindings/display/sitronix,st7565r.yaml b/dts/bindings/display/sitronix,st7565r.yaml new file mode 100644 index 0000000000000..f67f30edf6ad1 --- /dev/null +++ b/dts/bindings/display/sitronix,st7565r.yaml @@ -0,0 +1,134 @@ +# Copyright (c) 2025, Giyora Haroon +# SPDX-License-Identifier: Apache-2.0 + +title: Sitronix ST7565R dot matrix LCD controller (SPI) + +description: | + Sitronix ST7565R dot matrix LCD controller on SPI bus. + +compatible: "sitronix,st7565r" + +include: spi-device.yaml + +properties: + reset-gpios: + type: phandle-array + description: | + Reset pin. The driver requires this for hardware reset. + The pin is active low. + + data-cmd-gpios: + type: phandle-array + required: true + description: | + Data/Command pin (D/C). Controls whether SPI data is interpreted + as data (high) or command (low). + + width: + type: int + required: true + description: Display width in pixels. + + height: + type: int + required: true + description: Display height in pixels. + + segment-offset: + type: int + default: 0 + description: | + Offset for the first segment (column) line. + + segment-remap: + type: boolean + description: | + Reverse the segment (column) mapping direction. + Maps SEG131..SEG0 instead of SEG0..SEG131. + Corresponds to ADC command (0xA0/0xA1). + Set based on display module wiring. + + com-invdir: + type: boolean + description: | + Reverse the COM scan direction. + Scans COM63..COM0 instead of COM0..COM63. + Corresponds to COM output scan direction command (0xC0/0xC8). + Set based on display module wiring. + + lcd-bias: + type: int + required: true + description: | + LCD bias setting. + enum: + - 7 # 1/7 Bias (0xA3) + - 9 # 1/9 Bias (0xA2) + + power-control-val: + type: int + default: 7 + description: | + Controls which internal power circuits (Voltage Booster, Regulator, + Follower) are enabled. + + The default value enables all internal power circuits. In this configuration, + the voltage levels required for the LCD drivers are generated internally. + + Bit 0: Voltage Follower (VF) + Bit 1: Voltage Regulator (VR) + Bit 2: Voltage Booster (VB) + + resistor-ratio: + type: int + default: 4 + description: | + Selects the internal resistor ratio, which sets the main LCD driver + voltage (Vo). + + The default value selects a physical ratio of 5.0, which provides a + versatile Vo voltage range. + + This property is tightly coupled with 'booster-ratio'. If either + property is changed from its default, the other must be re-evaluated + to ensure a consistent and working power configuration for the + specific LCD module. + + Corresponds to the lower 3 bits of the Resistor Ratio Set command + (0x20 | value). Valid values are 0-7. + + booster-ratio: + type: int + default: 0 + description: | + Selects the voltage booster step-up ratio. + + The default value selects the mode supporting 2x, 3x, or 4x boost. + In this mode, the specific ratio is determined by the hardware + module's capacitor wiring, making this the most flexible default + for common hardware designs. + + This property must be overridden for modules requiring 5x or 6x boost. + As this property is tightly coupled with 'resistor-ratio', ensure + both are set appropriately for your hardware. + + Corresponds to the second byte of the Booster Ratio Set command (0xF8 + value). + + enum: + - 0 # 2x, 3x, 4x + - 1 # 5x + - 3 # 6x + + inversion-on: + type: boolean + description: | + Sets the display mode to inverted (pixel on when RAM=0). + Corresponds to Display Normal/Reverse command (0xA6/0xA7). + + ready-time-ms: + type: int + default: 10 + description: | + Time in milliseconds to wait before initializing the controller + after power-on. Defaults to 10ms (found by trial and error) if not + provided. diff --git a/tests/drivers/build_all/display/app.overlay b/tests/drivers/build_all/display/app.overlay index 99aba3132a20e..d3f9c069b70ec 100644 --- a/tests/drivers/build_all/display/app.overlay +++ b/tests/drivers/build_all/display/app.overlay @@ -526,6 +526,18 @@ reset-delay = <280>; }; + test_st7565r: st7565r@2 { + compatible = "sitronix,st7565r"; + reg = <2>; + spi-max-frequency = <1000000>; + reset-gpios = <&test_gpio 0 0>; + data-cmd-gpios = <&test_gpio 0 0>; + width = <128>; + height = <64>; + lcd-bias = <9>; + com-invdir; + }; + test_ls0xx: ls0xx@7 { compatible = "sharp,ls0xx"; spi-max-frequency = <2000000>;