Skip to content

Commit

Permalink
drivers: add SSD1327 display controller driver
Browse files Browse the repository at this point in the history
Implements the driver for the OLED SSD1327 controller.
This driver is based on the ssd1306 driver due to their similarities.
Only the SPI control bus is supported.

(cherry picked from commit ad3e941)

Original-Signed-off-by: Luc BEAUFILS <luc.beaufils@savoirfairelinux.com>
GitOrigin-RevId: ad3e941
Cr-Build-Id: 8739182204762414273
Cr-Build-Url: https://cr-buildbucket.appspot.com/build/8739182204762414273
Copybot-Job-Name: zephyr-main-copybot-downstream
Change-Id: I74d8764e03341d2cf291d60dff18f814d9b18a1e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/zephyr/+/5795250
Commit-Queue: Dawid Niedźwiecki <dawidn@google.com>
Tested-by: Dawid Niedźwiecki <dawidn@google.com>
Tested-by: ChromeOS Prod (Robot) <chromeos-ci-prod@chromeos-bot.iam.gserviceaccount.com>
Reviewed-by: Dawid Niedźwiecki <dawidn@google.com>
  • Loading branch information
lucbeaufils authored and Chromeos LUCI committed Aug 19, 2024
1 parent 501f411 commit 032cf28
Show file tree
Hide file tree
Showing 6 changed files with 475 additions and 0 deletions.
1 change: 1 addition & 0 deletions drivers/display/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_LS0XX ls0xx.c)
zephyr_library_sources_ifdef(CONFIG_MAX7219 display_max7219.c)
zephyr_library_sources_ifdef(CONFIG_OTM8009A display_otm8009a.c)
zephyr_library_sources_ifdef(CONFIG_SSD1306 ssd1306.c)
zephyr_library_sources_ifdef(CONFIG_SSD1327 ssd1327.c)
zephyr_library_sources_ifdef(CONFIG_SSD16XX ssd16xx.c)
zephyr_library_sources_ifdef(CONFIG_ST7789V display_st7789v.c)
zephyr_library_sources_ifdef(CONFIG_ST7735R display_st7735r.c)
Expand Down
1 change: 1 addition & 0 deletions drivers/display/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ source "drivers/display/Kconfig.nrf_led_matrix"
source "drivers/display/Kconfig.ili9xxx"
source "drivers/display/Kconfig.sdl"
source "drivers/display/Kconfig.ssd1306"
source "drivers/display/Kconfig.ssd1327"
source "drivers/display/Kconfig.ssd16xx"
source "drivers/display/Kconfig.st7735r"
source "drivers/display/Kconfig.st7789v"
Expand Down
23 changes: 23 additions & 0 deletions drivers/display/Kconfig.ssd1327
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SSD1327 display controller configuration options

# Copyright (c) 2024 Savoir-faire Linux
# SPDX-License-Identifier: Apache-2.0

menuconfig SSD1327
bool "SSD1327 display driver"
default y
depends on DT_HAS_SOLOMON_SSD1327FB_ENABLED
select MIPI_DBI
help
Enable driver for SSD1327 display.

if SSD1327

config SSD1327_DEFAULT_CONTRAST
int "SSD1327 default contrast"
default 128
range 0 255
help
SSD1327 default contrast.

endif # SSD1327
352 changes: 352 additions & 0 deletions drivers/display/ssd1327.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
/*
* Copyright (c) 2024 Savoir-faire Linux
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ssd1327, CONFIG_DISPLAY_LOG_LEVEL);

#include <string.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/mipi_dbi.h>
#include <zephyr/kernel.h>

#include "ssd1327_regs.h"

#define SSD1327_ENABLE_VDD 0x01
#define SSD1327_ENABLE_SECOND_PRECHARGE 0x62
#define SSD1327_VCOMH_VOLTAGE 0x0f
#define SSD1327_PHASES_VALUE 0xf1
#define SSD1327_DEFAULT_PRECHARGE_V 0x08
#define SSD1327_UNLOCK_COMMAND 0x12

struct ssd1327_config {
const struct device *mipi_dev;
const struct mipi_dbi_config dbi_config;
uint16_t height;
uint16_t width;
uint8_t oscillator_freq;
uint8_t start_line;
uint8_t display_offset;
uint8_t multiplex_ratio;
uint8_t prechargep;
uint8_t remap_value;
bool color_inversion;
};

struct ssd1327_data {
uint8_t contrast;
uint8_t scan_mode;
};

static inline int ssd1327_write_bus_cmd(const struct device *dev, const uint8_t cmd,
const uint8_t *data, size_t len)
{
const struct ssd1327_config *config = dev->config;
int err;

/* Values given after the memory register must be sent with pin D/C set to 0. */
/* Data is sent as a command following the mipi_cbi api */
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, cmd, NULL, 0);
if (err) {
return err;
}
for (size_t i = 0; i < len; i++) {
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
data[i], NULL, 0);
if (err) {
return err;
}
}
mipi_dbi_release(config->mipi_dev, &config->dbi_config);

return 0;
}

static inline int ssd1327_set_timing_setting(const struct device *dev)
{
const struct ssd1327_config *config = dev->config;
uint8_t buf;

buf = SSD1327_PHASES_VALUE;
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PHASE_LENGTH, &buf, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_OSC_FREQ, &config->oscillator_freq, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_PERIOD, &config->prechargep, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_LINEAR_LUT, NULL, 0)) {
return -EIO;
}
buf = SSD1327_DEFAULT_PRECHARGE_V;
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_VOLTAGE, &buf, 1)) {
return -EIO;
}
buf = SSD1327_VCOMH_VOLTAGE;
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_VCOMH, &buf, 1)) {
return -EIO;
}
buf = SSD1327_ENABLE_SECOND_PRECHARGE;
if (ssd1327_write_bus_cmd(dev, SSD1327_FUNCTION_SELECTION_B, &buf, 1)) {
return -EIO;
}
buf = SSD1327_UNLOCK_COMMAND;
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COMMAND_LOCK, &buf, 1)) {
return -EIO;
}

return 0;
}

static inline int ssd1327_set_hardware_config(const struct device *dev)
{
const struct ssd1327_config *config = dev->config;
uint8_t buf;

if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_START_LINE, &config->start_line, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_OFFSET, &config->display_offset, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_NORMAL_DISPLAY, NULL, 0)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_MULTIPLEX_RATIO, &config->multiplex_ratio, 1)) {
return -EIO;
}
buf = SSD1327_ENABLE_VDD;
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_FUNCTION_A, &buf, 1)) {
return -EIO;
}

return 0;
}

static int ssd1327_resume(const struct device *dev)
{
return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_ON, NULL, 0);
}

static int ssd1327_suspend(const struct device *dev)
{
return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_OFF, NULL, 0);
}

static int ssd1327_set_display(const struct device *dev)
{
const struct ssd1327_config *config = dev->config;
uint8_t x_position[] = {
0,
config->width - 1
};
uint8_t y_position[] = {
0,
config->height - 1
};

if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position))) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position))) {
return -EIO;
}
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
return -EIO;
}

return 0;
}

static int ssd1327_write(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, const void *buf)
{
const struct ssd1327_config *config = dev->config;
struct display_buffer_descriptor mipi_desc;
int err;
size_t buf_len;
uint8_t x_position[] = { x, x + desc->width - 1 };
uint8_t y_position[] = { y, y + desc->height - 1 };

if (desc->pitch < desc->width) {
LOG_ERR("Pitch is smaller than width");
return -1;
}
mipi_desc.pitch = desc->pitch;

/* Following the datasheet, in the GDDRAM, two segment are split in one register */
buf_len = MIN(desc->buf_size, desc->height * desc->width / 2);
if (buf == NULL || buf_len == 0U) {
LOG_ERR("Display buffer is not available");
return -1;
}
mipi_desc.buf_size = buf_len;

if (desc->pitch > desc->width) {
LOG_ERR("Unsupported mode");
return -1;
}

if ((y & 0x7) != 0U) {
LOG_ERR("Unsupported origin");
return -1;
}
mipi_desc.height = desc->height;
mipi_desc.width = desc->width;

LOG_DBG("x %u, y %u, pitch %u, width %u, height %u, buf_len %u", x, y, desc->pitch,
desc->width, desc->height, buf_len);

err = ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position));
if (err) {
return err;
}

err = ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position));
if (err) {
return err;
}

err = mipi_dbi_write_display(config->mipi_dev, &config->dbi_config, buf, &mipi_desc,
PIXEL_FORMAT_MONO10);
if (err) {
return err;
}
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
}

static int ssd1327_set_contrast(const struct device *dev, const uint8_t contrast)
{
return ssd1327_write_bus_cmd(dev, SSD1327_SET_CONTRAST_CTRL, &contrast, 1);
}

static void ssd1327_get_capabilities(const struct device *dev,
struct display_capabilities *caps)
{
const struct ssd1327_config *config = dev->config;

memset(caps, 0, sizeof(struct display_capabilities));
caps->x_resolution = config->width;
caps->y_resolution = config->height;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
caps->screen_info = SCREEN_INFO_MONO_VTILED;
}

static int ssd1327_set_pixel_format(const struct device *dev,
const enum display_pixel_format pf)
{
if (pf == PIXEL_FORMAT_MONO10) {
return 0;
}
LOG_ERR("Unsupported pixel format");
return -ENOTSUP;
}

static int ssd1327_init_device(const struct device *dev)
{
const struct ssd1327_config *config = dev->config;
uint8_t buf;

/* Turn display off */
if (ssd1327_suspend(dev)) {
return -EIO;
}

if (ssd1327_set_display(dev)) {
return -EIO;
}

if (ssd1327_set_contrast(dev, CONFIG_SSD1327_DEFAULT_CONTRAST)) {
return -EIO;
}

if (ssd1327_set_hardware_config(dev)) {
return -EIO;
}

buf = (config->color_inversion ?
SSD1327_SET_REVERSE_DISPLAY : SSD1327_SET_NORMAL_DISPLAY);
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ENTIRE_DISPLAY_OFF, &buf, 1)) {
return -EIO;
}

if (ssd1327_set_timing_setting(dev)) {
return -EIO;
}

if (ssd1327_resume(dev)) {
return -EIO;
}

return 0;
}

static int ssd1327_init(const struct device *dev)
{
const struct ssd1327_config *config = dev->config;

LOG_DBG("Initializing device");

if (!device_is_ready(config->mipi_dev)) {
LOG_ERR("MIPI Device not ready!");
return -EINVAL;
}

if (mipi_dbi_reset(config->mipi_dev, SSD1327_RESET_DELAY)) {
LOG_ERR("Failed to reset device!");
return -EIO;
}
k_msleep(SSD1327_RESET_DELAY);

if (ssd1327_init_device(dev)) {
LOG_ERR("Failed to initialize device!");
return -EIO;
}

return 0;
}

static struct display_driver_api ssd1327_driver_api = {
.blanking_on = ssd1327_suspend,
.blanking_off = ssd1327_resume,
.write = ssd1327_write,
.set_contrast = ssd1327_set_contrast,
.get_capabilities = ssd1327_get_capabilities,
.set_pixel_format = ssd1327_set_pixel_format,
};

#define SSD1327_DEFINE(node_id) \
static struct ssd1327_data data##node_id; \
static const struct ssd1327_config config##node_id = { \
.mipi_dev = DEVICE_DT_GET(DT_PARENT(node_id)), \
.dbi_config = { .mode = MIPI_DBI_MODE_SPI_4WIRE, \
.config = MIPI_DBI_SPI_CONFIG_DT(node_id, \
SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | \
SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \
}, \
.height = DT_PROP(node_id, height), \
.width = DT_PROP(node_id, width), \
.oscillator_freq = DT_PROP(node_id, oscillator_freq), \
.display_offset = DT_PROP(node_id, display_offset), \
.start_line = DT_PROP(node_id, start_line), \
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
.prechargep = DT_PROP(node_id, prechargep), \
.remap_value = DT_PROP(node_id, remap_value), \
.color_inversion = DT_PROP(node_id, inversion_on), \
}; \
\
DEVICE_DT_DEFINE(node_id, ssd1327_init, NULL, &data##node_id, &config##node_id, \
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1327_driver_api);

DT_FOREACH_STATUS_OKAY(solomon_ssd1327fb, SSD1327_DEFINE)
Loading

0 comments on commit 032cf28

Please sign in to comment.