zephyr/drivers/display/ssd16xx.c
Daniel DeGrasse 8d2dc2f9ef drivers: display: ssd16xx: convert to MIPI DBI API
Convert SSD16XX display driver to use MIPI DBI API. This commit also
updates in tree board devicetrees to include the emulated MIPI DBI SPI
driver.

Signed-off-by: Daniel DeGrasse <daniel.degrasse@nxp.com>
2024-06-13 17:01:16 -04:00

1096 lines
26 KiB
C

/*
* Copyright (c) 2022 Andreas Sandberg
* Copyright (c) 2018-2020 PHYTEC Messtechnik GmbH
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_LEVEL CONFIG_DISPLAY_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ssd16xx);
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/mipi_dbi.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/display/ssd16xx.h>
#include "ssd16xx_regs.h"
/**
* SSD16xx compatible EPD controller driver.
*/
#define EPD_PANEL_NUMOF_ROWS_PER_PAGE 8
#define SSD16XX_PANEL_FIRST_PAGE 0
#define SSD16XX_PANEL_FIRST_GATE 0
#define SSD16XX_PIXELS_PER_BYTE 8
#define SSD16XX_DEFAULT_TR_VALUE 25U
#define SSD16XX_TR_SCALE_FACTOR 256U
enum ssd16xx_profile_type {
SSD16XX_PROFILE_FULL = 0,
SSD16XX_PROFILE_PARTIAL,
SSD16XX_NUM_PROFILES,
SSD16XX_PROFILE_INVALID = SSD16XX_NUM_PROFILES,
};
struct ssd16xx_quirks {
/* Gates */
uint16_t max_width;
/* Sources */
uint16_t max_height;
/* Width (bits) of integer type representing an x coordinate */
uint8_t pp_width_bits;
/* Width (bits) of integer type representing a y coordinate */
uint8_t pp_height_bits;
/*
* Device specific flags to be included in
* SSD16XX_CMD_UPDATE_CTRL2 for a full refresh.
*/
uint8_t ctrl2_full;
/*
* Device specific flags to be included in
* SSD16XX_CMD_UPDATE_CTRL2 for a partial refresh.
*/
uint8_t ctrl2_partial;
};
struct ssd16xx_data {
bool read_supported;
uint8_t scan_mode;
bool blanking_on;
enum ssd16xx_profile_type profile;
enum display_orientation orientation;
};
struct ssd16xx_dt_array {
uint8_t *data;
uint8_t len;
};
struct ssd16xx_profile {
struct ssd16xx_dt_array lut;
struct ssd16xx_dt_array gdv;
struct ssd16xx_dt_array sdv;
uint8_t vcom;
uint8_t bwf;
uint8_t dummy_line;
uint8_t gate_line_width;
bool override_vcom;
bool override_bwf;
bool override_dummy_line;
bool override_gate_line_width;
};
struct ssd16xx_config {
const struct device *mipi_dev;
const struct mipi_dbi_config dbi_config;
struct gpio_dt_spec busy_gpio;
const struct ssd16xx_quirks *quirks;
struct ssd16xx_dt_array softstart;
const struct ssd16xx_profile *profiles[SSD16XX_NUM_PROFILES];
uint16_t rotation;
uint16_t height;
uint16_t width;
uint8_t tssv;
};
static int ssd16xx_set_profile(const struct device *dev,
enum ssd16xx_profile_type type);
static inline void ssd16xx_busy_wait(const struct device *dev)
{
const struct ssd16xx_config *config = dev->config;
int pin = gpio_pin_get_dt(&config->busy_gpio);
while (pin > 0) {
__ASSERT(pin >= 0, "Failed to get pin level");
k_msleep(SSD16XX_BUSY_DELAY);
pin = gpio_pin_get_dt(&config->busy_gpio);
}
}
static inline int ssd16xx_write_cmd(const struct device *dev, uint8_t cmd,
const uint8_t *data, size_t len)
{
const struct ssd16xx_config *config = dev->config;
int err;
ssd16xx_busy_wait(dev);
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
cmd, data, len);
mipi_dbi_release(config->mipi_dev, &config->dbi_config);
return err;
}
static inline int ssd16xx_write_uint8(const struct device *dev, uint8_t cmd,
uint8_t data)
{
return ssd16xx_write_cmd(dev, cmd, &data, 1);
}
static inline int ssd16xx_read_cmd(const struct device *dev, uint8_t cmd,
uint8_t *data, size_t len)
{
const struct ssd16xx_config *config = dev->config;
const struct ssd16xx_data *dev_data = dev->data;
if (!dev_data->read_supported) {
return -ENOTSUP;
}
ssd16xx_busy_wait(dev);
return mipi_dbi_command_read(config->mipi_dev, &config->dbi_config,
&cmd, 1, data, len);
}
static inline size_t push_x_param(const struct device *dev,
uint8_t *data, uint16_t x)
{
const struct ssd16xx_config *config = dev->config;
if (config->quirks->pp_width_bits == 8) {
data[0] = (uint8_t)x;
return 1;
}
if (config->quirks->pp_width_bits == 16) {
sys_put_le16(sys_cpu_to_le16(x), data);
return 2;
}
LOG_ERR("Unsupported pp_width_bits %u",
config->quirks->pp_width_bits);
return 0;
}
static inline size_t push_y_param(const struct device *dev,
uint8_t *data, uint16_t y)
{
const struct ssd16xx_config *config = dev->config;
if (config->quirks->pp_height_bits == 8) {
data[0] = (uint8_t)y;
return 1;
}
if (config->quirks->pp_height_bits == 16) {
sys_put_le16(sys_cpu_to_le16(y), data);
return 2;
}
LOG_ERR("Unsupported pp_height_bitsa %u",
config->quirks->pp_height_bits);
return 0;
}
static inline int ssd16xx_set_ram_param(const struct device *dev,
uint16_t sx, uint16_t ex,
uint16_t sy, uint16_t ey)
{
int err;
uint8_t tmp[4];
size_t len;
len = push_x_param(dev, tmp, sx);
len += push_x_param(dev, tmp + len, ex);
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_RAM_XPOS_CTRL, tmp, len);
if (err < 0) {
return err;
}
len = push_y_param(dev, tmp, sy);
len += push_y_param(dev, tmp + len, ey);
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_RAM_YPOS_CTRL, tmp, len);
if (err < 0) {
return err;
}
return 0;
}
static inline int ssd16xx_set_ram_ptr(const struct device *dev, uint16_t x,
uint16_t y)
{
int err;
uint8_t tmp[2];
size_t len;
len = push_x_param(dev, tmp, x);
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_RAM_XPOS_CNTR, tmp, len);
if (err < 0) {
return err;
}
len = push_y_param(dev, tmp, y);
return ssd16xx_write_cmd(dev, SSD16XX_CMD_RAM_YPOS_CNTR, tmp, len);
}
static int ssd16xx_activate(const struct device *dev, uint8_t ctrl2)
{
int err;
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_UPDATE_CTRL2, ctrl2);
if (err < 0) {
return err;
}
return ssd16xx_write_cmd(dev, SSD16XX_CMD_MASTER_ACTIVATION, NULL, 0);
}
static int ssd16xx_update_display(const struct device *dev)
{
const struct ssd16xx_config *config = dev->config;
const struct ssd16xx_data *data = dev->data;
const struct ssd16xx_profile *p = config->profiles[data->profile];
const struct ssd16xx_quirks *quirks = config->quirks;
const bool load_lut = !p || p->lut.len == 0;
const bool load_temp = load_lut && config->tssv;
const bool partial = data->profile == SSD16XX_PROFILE_PARTIAL;
const uint8_t update_cmd =
SSD16XX_CTRL2_ENABLE_CLK |
SSD16XX_CTRL2_ENABLE_ANALOG |
(load_lut ? SSD16XX_CTRL2_LOAD_LUT : 0) |
(load_temp ? SSD16XX_CTRL2_LOAD_TEMPERATURE : 0) |
(partial ? quirks->ctrl2_partial : quirks->ctrl2_full) |
SSD16XX_CTRL2_DISABLE_ANALOG |
SSD16XX_CTRL2_DISABLE_CLK;
return ssd16xx_activate(dev, update_cmd);
}
static int ssd16xx_blanking_off(const struct device *dev)
{
struct ssd16xx_data *data = dev->data;
if (data->blanking_on) {
data->blanking_on = false;
return ssd16xx_update_display(dev);
}
return 0;
}
static int ssd16xx_blanking_on(const struct device *dev)
{
struct ssd16xx_data *data = dev->data;
if (!data->blanking_on) {
if (ssd16xx_set_profile(dev, SSD16XX_PROFILE_FULL)) {
return -EIO;
}
}
data->blanking_on = true;
return 0;
}
static int ssd16xx_set_window(const struct device *dev,
const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc)
{
const struct ssd16xx_config *config = dev->config;
const struct ssd16xx_data *data = dev->data;
int err;
uint16_t x_start;
uint16_t x_end;
uint16_t y_start;
uint16_t y_end;
uint16_t panel_h = config->height -
config->height % EPD_PANEL_NUMOF_ROWS_PER_PAGE;
if (desc->pitch < desc->width) {
LOG_ERR("Pitch is smaller than width");
return -EINVAL;
}
if (desc->pitch > desc->width) {
LOG_ERR("Unsupported mode");
return -ENOTSUP;
}
if (data->orientation == DISPLAY_ORIENTATION_NORMAL ||
data->orientation == DISPLAY_ORIENTATION_ROTATED_180) {
if ((y + desc->height) > panel_h) {
LOG_ERR("Buffer out of bounds (height)");
return -EINVAL;
}
if ((x + desc->width) > config->width) {
LOG_ERR("Buffer out of bounds (width)");
return -EINVAL;
}
if ((desc->height % EPD_PANEL_NUMOF_ROWS_PER_PAGE) != 0U) {
LOG_ERR("Buffer height not multiple of %d", EPD_PANEL_NUMOF_ROWS_PER_PAGE);
return -EINVAL;
}
if ((y % EPD_PANEL_NUMOF_ROWS_PER_PAGE) != 0U) {
LOG_ERR("Y coordinate not multiple of %d", EPD_PANEL_NUMOF_ROWS_PER_PAGE);
return -EINVAL;
}
} else {
if ((y + desc->height) > config->width) {
LOG_ERR("Buffer out of bounds (height)");
return -EINVAL;
}
if ((x + desc->width) > panel_h) {
LOG_ERR("Buffer out of bounds (width)");
return -EINVAL;
}
if ((desc->width % SSD16XX_PIXELS_PER_BYTE) != 0U) {
LOG_ERR("Buffer width not multiple of %d", SSD16XX_PIXELS_PER_BYTE);
return -EINVAL;
}
if ((x % SSD16XX_PIXELS_PER_BYTE) != 0U) {
LOG_ERR("X coordinate not multiple of %d", SSD16XX_PIXELS_PER_BYTE);
return -EINVAL;
}
}
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;
}
err = ssd16xx_set_ram_param(dev, x_start, x_end, y_start, y_end);
if (err < 0) {
return err;
}
err = ssd16xx_set_ram_ptr(dev, x_start, y_start);
if (err < 0) {
return err;
}
return 0;
}
static int ssd16xx_write(const struct device *dev, const uint16_t x,
const uint16_t y,
const struct display_buffer_descriptor *desc,
const void *buf)
{
const struct ssd16xx_config *config = dev->config;
const struct ssd16xx_data *data = dev->data;
const bool have_partial_refresh =
config->profiles[SSD16XX_PROFILE_PARTIAL] != NULL;
const bool partial_refresh = !data->blanking_on && have_partial_refresh;
const size_t buf_len = MIN(desc->buf_size,
desc->height * desc->width / 8);
int err;
if (buf == NULL || buf_len == 0U) {
LOG_ERR("Display buffer is not available");
return -EINVAL;
}
if (partial_refresh) {
/*
* Request the partial profile. This operation becomes
* a no-op if the profile is already active.
*/
err = ssd16xx_set_profile(dev, SSD16XX_PROFILE_PARTIAL);
if (err < 0) {
return -EIO;
}
}
err = ssd16xx_set_window(dev, x, y, desc);
if (err < 0) {
return err;
}
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_WRITE_RAM, (uint8_t *)buf,
buf_len);
if (err < 0) {
return err;
}
if (!data->blanking_on) {
err = ssd16xx_update_display(dev);
if (err < 0) {
return err;
}
}
if (data->blanking_on && have_partial_refresh) {
/*
* We will trigger a full refresh when blanking is
* turned off. The controller won't keep track of the
* old frame buffer, which is needed to perform a
* partial update, when this happens. Maintain the old
* frame buffer manually here to make sure future
* partial updates will work as expected.
*/
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_WRITE_RED_RAM,
(uint8_t *)buf, buf_len);
if (err < 0) {
return err;
}
} else if (partial_refresh) {
/*
* We just performed a partial refresh. After the
* refresh, the controller swaps the black/red buffers
* containing the current and new image. We need to
* perform a second write here to ensure that future
* updates work on an up-to-date framebuffer.
*/
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_WRITE_RAM,
(uint8_t *)buf, buf_len);
if (err < 0) {
return err;
}
}
return 0;
}
int ssd16xx_read_ram(const struct device *dev, enum ssd16xx_ram ram_type,
const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc,
void *buf)
{
const struct ssd16xx_data *data = dev->data;
const size_t buf_len = MIN(desc->buf_size,
desc->height * desc->width / 8);
int err;
uint8_t ram_ctrl;
if (!data->read_supported) {
return -ENOTSUP;
}
switch (ram_type) {
case SSD16XX_RAM_BLACK:
ram_ctrl = SSD16XX_RAM_READ_CTRL_BLACK;
break;
case SSD16XX_RAM_RED:
ram_ctrl = SSD16XX_RAM_READ_CTRL_RED;
break;
default:
return -EINVAL;
}
if (buf == NULL || buf_len == 0U) {
LOG_ERR("Display buffer is not available");
return -EINVAL;
}
err = ssd16xx_set_window(dev, x, y, desc);
if (err < 0) {
return err;
}
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_RAM_READ_CTRL,
&ram_ctrl, sizeof(ram_ctrl));
if (err < 0) {
return err;
}
err = ssd16xx_read_cmd(dev, SSD16XX_CMD_READ_RAM, (uint8_t *)buf,
buf_len);
if (err < 0) {
return err;
}
return 0;
}
static int ssd16xx_read(const struct device *dev,
const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc,
void *buf)
{
return ssd16xx_read_ram(dev, SSD16XX_RAM_BLACK, x, y, desc, buf);
}
static void ssd16xx_get_capabilities(const struct device *dev,
struct display_capabilities *caps)
{
const struct ssd16xx_config *config = dev->config;
struct ssd16xx_data *data = dev->data;
memset(caps, 0, sizeof(struct display_capabilities));
caps->x_resolution = config->width;
caps->y_resolution = config->height -
config->height % EPD_PANEL_NUMOF_ROWS_PER_PAGE;
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST | SCREEN_INFO_EPD;
if (data->orientation == DISPLAY_ORIENTATION_NORMAL ||
data->orientation == DISPLAY_ORIENTATION_ROTATED_180) {
caps->screen_info |= SCREEN_INFO_MONO_VTILED;
}
caps->current_orientation = data->orientation;
}
static int ssd16xx_set_pixel_format(const struct device *dev,
const enum display_pixel_format pf)
{
if (pf == PIXEL_FORMAT_MONO10) {
return 0;
}
LOG_ERR("not supported");
return -ENOTSUP;
}
static int ssd16xx_set_orientation(const struct device *dev,
const enum display_orientation orientation)
{
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;
}
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_ENTRY_MODE, data->scan_mode);
if (err < 0) {
return err;
}
data->orientation = orientation;
return 0;
}
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 last_gate = config->width - 1;
uint8_t clear_page[64];
int err;
/*
* 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;
}
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_ENTRY_MODE,
SSD16XX_DATA_ENTRY_XIYDY);
if (err < 0) {
return err;
}
err = ssd16xx_set_ram_param(dev, SSD16XX_PANEL_FIRST_PAGE,
panel_h - 1, last_gate,
SSD16XX_PANEL_FIRST_GATE);
if (err < 0) {
return err;
}
err = ssd16xx_set_ram_ptr(dev, SSD16XX_PANEL_FIRST_PAGE, last_gate);
if (err < 0) {
return err;
}
memset(clear_page, 0xff, sizeof(clear_page));
for (int h = 0; h < panel_h; h++) {
size_t x = config->width;
while (x) {
size_t l = MIN(x, sizeof(clear_page));
x -= l;
err = ssd16xx_write_cmd(dev, ram_cmd, clear_page, l);
if (err < 0) {
return err;
}
}
}
return 0;
}
static inline int ssd16xx_load_ws_from_otp_tssv(const struct device *dev)
{
const struct ssd16xx_config *config = dev->config;
/*
* Controller has an integrated temperature sensor or external
* temperature sensor is connected to the controller.
*/
LOG_INF("Select and load WS from OTP");
return ssd16xx_write_uint8(dev, SSD16XX_CMD_TSENSOR_SELECTION,
config->tssv);
}
static inline int ssd16xx_load_ws_from_otp(const struct device *dev)
{
int16_t t = (SSD16XX_DEFAULT_TR_VALUE * SSD16XX_TR_SCALE_FACTOR);
uint8_t tmp[2];
int err;
LOG_INF("Load default WS (25 degrees Celsius) from OTP");
err = ssd16xx_activate(dev, SSD16XX_CTRL2_ENABLE_CLK);
if (err < 0) {
return err;
}
/* Load temperature value */
sys_put_be16(t, tmp);
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_TSENS_CTRL, tmp, 2);
if (err < 0) {
return err;
}
err = ssd16xx_activate(dev, SSD16XX_CTRL2_DISABLE_CLK);
if (err < 0) {
return err;
}
return 0;
}
static int ssd16xx_load_lut(const struct device *dev,
const struct ssd16xx_dt_array *lut)
{
const struct ssd16xx_config *config = dev->config;
if (lut && lut->len) {
LOG_DBG("Using user-provided LUT");
return ssd16xx_write_cmd(dev, SSD16XX_CMD_UPDATE_LUT,
lut->data, lut->len);
} else {
if (config->tssv) {
return ssd16xx_load_ws_from_otp_tssv(dev);
} else {
return ssd16xx_load_ws_from_otp(dev);
}
}
}
static int ssd16xx_set_profile(const struct device *dev,
enum ssd16xx_profile_type type)
{
const struct ssd16xx_config *config = dev->config;
struct ssd16xx_data *data = dev->data;
const struct ssd16xx_profile *p;
const uint16_t last_gate = config->width - 1;
uint8_t gdo[3];
size_t gdo_len;
int err = 0;
if (type >= SSD16XX_NUM_PROFILES) {
return -EINVAL;
}
p = config->profiles[type];
/*
* The full profile is the only one that always exists. If it
* hasn't been specified, we use the defaults.
*/
if (!p && type != SSD16XX_PROFILE_FULL) {
return -ENOENT;
}
if (type == data->profile) {
return 0;
}
/*
* Perform a soft reset to make sure registers are reset. This
* will leave the RAM contents intact.
*/
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_SW_RESET, NULL, 0);
if (err < 0) {
return err;
}
gdo_len = push_y_param(dev, gdo, last_gate);
gdo[gdo_len++] = 0U;
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_GDO_CTRL, gdo, gdo_len);
if (err < 0) {
return err;
}
if (config->softstart.len) {
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_SOFTSTART,
config->softstart.data,
config->softstart.len);
if (err < 0) {
return err;
}
}
err = ssd16xx_load_lut(dev, p ? &p->lut : NULL);
if (err < 0) {
return err;
}
if (p && p->override_dummy_line) {
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_DUMMY_LINE,
p->dummy_line);
if (err < 0) {
return err;
}
}
if (p && p->override_gate_line_width) {
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_GATE_LINE_WIDTH,
p->override_gate_line_width);
if (err < 0) {
return err;
}
}
if (p && p->gdv.len) {
LOG_DBG("Setting GDV");
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_GDV_CTRL,
p->gdv.data, p->gdv.len);
if (err < 0) {
return err;
}
}
if (p && p->sdv.len) {
LOG_DBG("Setting SDV");
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_SDV_CTRL,
p->sdv.data, p->sdv.len);
if (err < 0) {
return err;
}
}
if (p && p->override_vcom) {
LOG_DBG("Setting VCOM");
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_VCOM_VOLTAGE,
&p->vcom, 1);
if (err < 0) {
return err;
}
}
if (p && p->override_bwf) {
LOG_DBG("Setting BWF");
err = ssd16xx_write_cmd(dev, SSD16XX_CMD_BWF_CTRL,
&p->bwf, 1);
if (err < 0) {
return err;
}
}
err = ssd16xx_write_uint8(dev, SSD16XX_CMD_ENTRY_MODE, data->scan_mode);
if (err < 0) {
return err;
}
data->profile = type;
return 0;
}
static int ssd16xx_controller_init(const struct device *dev)
{
const struct ssd16xx_config *config = dev->config;
struct ssd16xx_data *data = dev->data;
enum display_orientation orientation;
int err;
LOG_DBG("");
data->blanking_on = false;
data->profile = SSD16XX_PROFILE_INVALID;
err = mipi_dbi_reset(config->mipi_dev, SSD16XX_RESET_DELAY);
if (err < 0) {
return err;
}
k_msleep(SSD16XX_RESET_DELAY);
err = ssd16xx_set_profile(dev, SSD16XX_PROFILE_FULL);
if (err < 0) {
return err;
}
err = ssd16xx_clear_cntlr_mem(dev, SSD16XX_CMD_WRITE_RAM);
if (err < 0) {
return err;
}
err = ssd16xx_clear_cntlr_mem(dev, SSD16XX_CMD_WRITE_RED_RAM);
if (err < 0) {
return err;
}
if (config->rotation == 0U) {
orientation = DISPLAY_ORIENTATION_NORMAL;
} else if (config->rotation == 90U) {
orientation = DISPLAY_ORIENTATION_ROTATED_90;
} else if (config->rotation == 180U) {
orientation = DISPLAY_ORIENTATION_ROTATED_180;
} else {
orientation = DISPLAY_ORIENTATION_ROTATED_270;
}
err = ssd16xx_set_orientation(dev, orientation);
if (err < 0) {
return err;
}
err = ssd16xx_update_display(dev);
if (err < 0) {
return err;
}
return 0;
}
static int ssd16xx_init(const struct device *dev)
{
const struct ssd16xx_config *config = dev->config;
struct ssd16xx_data *data = dev->data;
int err;
LOG_DBG("");
if (!device_is_ready(config->mipi_dev)) {
LOG_ERR("MIPI Device not ready");
return -ENODEV;
}
data->read_supported =
(config->dbi_config.config.operation & SPI_HALF_DUPLEX) != 0;
if (!gpio_is_ready_dt(&config->busy_gpio)) {
LOG_ERR("Busy GPIO device not ready");
return -ENODEV;
}
err = gpio_pin_configure_dt(&config->busy_gpio, GPIO_INPUT);
if (err < 0) {
LOG_ERR("Failed to configure busy GPIO");
return err;
}
if (config->width > config->quirks->max_width ||
config->height > config->quirks->max_height) {
LOG_ERR("Display size out of range.");
return -EINVAL;
}
return ssd16xx_controller_init(dev);
}
static const struct display_driver_api ssd16xx_driver_api = {
.blanking_on = ssd16xx_blanking_on,
.blanking_off = ssd16xx_blanking_off,
.write = ssd16xx_write,
.read = ssd16xx_read,
.get_capabilities = ssd16xx_get_capabilities,
.set_pixel_format = ssd16xx_set_pixel_format,
.set_orientation = ssd16xx_set_orientation,
};
#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1608)
static struct ssd16xx_quirks quirks_solomon_ssd1608 = {
.max_width = 320,
.max_height = 240,
.pp_width_bits = 16,
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
};
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1673)
static struct ssd16xx_quirks quirks_solomon_ssd1673 = {
.max_width = 250,
.max_height = 150,
.pp_width_bits = 8,
.pp_height_bits = 8,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
};
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1675a)
static struct ssd16xx_quirks quirks_solomon_ssd1675a = {
.max_width = 296,
.max_height = 160,
.pp_width_bits = 8,
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN1_CTRL2_TO_PATTERN,
.ctrl2_partial = SSD16XX_GEN1_CTRL2_TO_PATTERN,
};
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1680)
static const struct ssd16xx_quirks quirks_solomon_ssd1680 = {
.max_width = 296,
.max_height = 176,
.pp_width_bits = 8,
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN2_CTRL2_DISPLAY,
.ctrl2_partial = SSD16XX_GEN2_CTRL2_DISPLAY | SSD16XX_GEN2_CTRL2_MODE2,
};
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(solomon_ssd1681)
static struct ssd16xx_quirks quirks_solomon_ssd1681 = {
.max_width = 200,
.max_height = 200,
.pp_width_bits = 8,
.pp_height_bits = 16,
.ctrl2_full = SSD16XX_GEN2_CTRL2_DISPLAY,
.ctrl2_partial = SSD16XX_GEN2_CTRL2_DISPLAY | SSD16XX_GEN2_CTRL2_MODE2,
};
#endif
#define SOFTSTART_ASSIGN(n) \
.softstart = { \
.data = softstart_##n, \
.len = sizeof(softstart_##n), \
},
#define SSD16XX_MAKE_ARRAY_OPT(n, p) \
static uint8_t data_ ## n ## _ ## p[] = DT_PROP_OR(n, p, {})
#define SSD16XX_ASSIGN_ARRAY(n, p) \
{ \
.data = data_ ## n ## _ ## p, \
.len = sizeof(data_ ## n ## _ ## p), \
}
#define SSD16XX_PROFILE(n) \
SSD16XX_MAKE_ARRAY_OPT(n, lut); \
SSD16XX_MAKE_ARRAY_OPT(n, gdv); \
SSD16XX_MAKE_ARRAY_OPT(n, sdv); \
\
static const struct ssd16xx_profile ssd16xx_profile_ ## n = { \
.lut = SSD16XX_ASSIGN_ARRAY(n, lut), \
.gdv = SSD16XX_ASSIGN_ARRAY(n, gdv), \
.sdv = SSD16XX_ASSIGN_ARRAY(n, sdv), \
.vcom = DT_PROP_OR(n, vcom, 0), \
.override_vcom = DT_NODE_HAS_PROP(n, vcom), \
.bwf = DT_PROP_OR(n, border_waveform, 0), \
.override_bwf = DT_NODE_HAS_PROP(n, border_waveform), \
.dummy_line = DT_PROP_OR(n, dummy_line, 0), \
.override_dummy_line = DT_NODE_HAS_PROP(n, dummy_line), \
.gate_line_width = DT_PROP_OR(n, gate_line_width, 0), \
.override_gate_line_width = DT_NODE_HAS_PROP( \
n, gate_line_width), \
};
#define _SSD16XX_PROFILE_PTR(n) &ssd16xx_profile_ ## n
#define SSD16XX_PROFILE_PTR(n) \
COND_CODE_1(DT_NODE_EXISTS(n), \
(_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), \
.tssv = DT_PROP_OR(n, tssv, 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_ssd1680, SSD16XX_DEFINE,
&quirks_solomon_ssd1680);
DT_FOREACH_STATUS_OKAY_VARGS(solomon_ssd1681, SSD16XX_DEFINE,
&quirks_solomon_ssd1681);