blob: a8c6644b0cc0462706381350050084a2f142227f [file] [log] [blame]
/*
* Copyright (c) 2025 MASSDRIVER EI (massdriver.space)
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(st730x, 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>
/* Registers */
#define ST730X_SLEEP_IN 0x10
#define ST730X_SLEEP_OUT 0x11
#define ST730X_SET_NORMAL_DISPLAY 0x20
#define ST730X_SET_REVERSE_DISPLAY 0x21
#define ST730X_DISPLAY_OFF 0x28
#define ST730X_DISPLAY_ON 0x29
#define ST730X_SET_COLUMN_ADDR 0x2A
#define ST730X_SET_ROW_ADDR 0x2B
#define ST730X_WRITE 0x2C
#define ST730X_READ 0x2E
#define ST730X_TEARING_OUT 0x35
#define ST730X_TEARING_OUT_VBLANK 0x00
#define ST730X_TEARING_OUT_VHBLANK 0x01
#define ST730X_MADCTL 0x36
#define ST730X_HPM 0x38
#define ST730X_LPM 0x39
#define ST730X_DTFORM 0x3A
#define ST730X_DTFORM_4W_24B 0x10
#define ST730X_DTFORM_3W_24B 0x11
#define ST730X_GATESET 0xB0
#define ST730X_FIRSTGATE 0xB1
#define ST730X_FRAMERATE 0xB2
#define ST730X_HPM_GATE_WAVEFORM 0xB3
#define ST730X_LPM_GATE_WAVEFORM 0xB4
#define ST730X_SOURCE_EQ_EN 0xB7
#define ST730X_SOURCE_EQ_EN_ENABLE 0x13
#define ST730X_SOURCE_EQ_EN_DISABLE 0x03
#define ST730X_PNLSET 0xB8
#define ST730X_GAMAMS 0xB9
#define ST730X_CLEAR_RAM 0xBB
#define ST730X_CLEAR_RAM_ENABLE 0xCF
#define ST730X_CLEAR_RAM_DISABLE 0x4F
#define ST730X_GAMAMS_MONO 0x20
#define ST730X_GAMAMS_4GS 0x00
#define ST730X_GATE_VOLTAGE 0xC0
#define ST730X_VSH 0xC1
#define ST730X_VSL 0xC2
#define ST730X_VSHN 0xC4
#define ST730X_VSLN 0xC5
#define ST730X_VSHLSEL 0xC9
#define ST730X_AUTOPWRDOWN 0xD0
#define ST730X_AUTOPWRDOWN_ON 0xFF
#define ST730X_AUTOPWRDOWN_OFF 0x7F
#define ST730X_BOOSTER_EN 0xD1
#define ST730X_BOOSTER_EN_ENABLE 0x01
#define ST730X_BOOSTER_EN_DISABLE 0x00
#define ST730X_NVM_LOAD 0xD6
#define ST730X_OSC_SETTINGS 0xD8
#define ST730X_OSC_SETTINGS_BYTE2 0xE9
#define ST730X_HPM_GATE_WAVEFORM_LEN 10
#define ST730X_LPM_GATE_WAVEFORM_LEN 8
/* Pixels per byte and pixels per x and y address */
#define ST730X_PPB 8
#define ST730X_PPXA 12
#define ST730X_PPYA 2
#define ST730X_RESET_DELAY 100
#define ST730X_SLEEP_DELAY 100
#ifdef CONFIG_ST730X_POWERMODE_LOW
#define ST730X_POWER_MODE ST730X_LPM
#else
#define ST730X_POWER_MODE ST730X_HPM
#endif
/* ST730x controllers have an evil data format for b&w
* where the pixels are ordered at each address as such:
* p1 p3 p5 p7
* p2 p4 p6 p8
*/
struct st730x_specific {
uint8_t column_offset;
};
struct st730x_config {
const struct device *mipi_dev;
const struct mipi_dbi_config dbi_config;
const struct st730x_specific *specifics;
uint16_t height;
uint16_t width;
uint16_t start_line;
uint16_t start_column;
uint8_t nvm_load[2];
uint8_t gate_voltages[2];
uint8_t vsh[4];
uint8_t vsl[4];
uint8_t vshn[4];
uint8_t vsln[4];
uint8_t osc_settings;
uint8_t framerate;
uint8_t multiplex_ratio;
uint8_t source_voltage;
uint8_t remap_value;
uint8_t panel_settings;
uint8_t hpm_gate_waveform[ST730X_HPM_GATE_WAVEFORM_LEN];
uint8_t lpm_gate_waveform[ST730X_LPM_GATE_WAVEFORM_LEN];
bool color_inversion;
uint8_t *conversion_buf;
size_t conversion_buf_size;
};
static int st730x_resume(const struct device *dev)
{
const struct st730x_config *config = dev->config;
int err;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_SLEEP_OUT,
NULL, 0);
if (err < 0) {
return err;
}
k_msleep(ST730X_SLEEP_DELAY);
/* Also enable display */
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_DISPLAY_ON,
NULL, 0);
if (err < 0) {
return err;
}
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
}
static int st730x_suspend(const struct device *dev)
{
const struct st730x_config *config = dev->config;
int err;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_SLEEP_IN,
NULL, 0);
if (err < 0) {
return err;
}
k_msleep(ST730X_SLEEP_DELAY);
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
}
static inline int st730x_set_hardware_config(const struct device *dev)
{
const struct st730x_config *config = dev->config;
int err;
uint8_t tmp[2];
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_NVM_LOAD, config->nvm_load, 2);
if (err < 0) {
return err;
}
tmp[0] = ST730X_BOOSTER_EN_ENABLE;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_BOOSTER_EN, tmp,
1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_GATE_VOLTAGE, config->gate_voltages, 2);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_VSH, config->vsh, 4);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_VSL, config->vsl, 4);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_VSHN, config->vshn, 4);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_VSLN, config->vsln, 4);
if (err < 0) {
return err;
}
tmp[0] = config->osc_settings;
tmp[1] = ST730X_OSC_SETTINGS_BYTE2;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_OSC_SETTINGS, tmp, 2);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_FRAMERATE, &config->framerate, 1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_HPM_GATE_WAVEFORM, config->hpm_gate_waveform,
ST730X_HPM_GATE_WAVEFORM_LEN);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_LPM_GATE_WAVEFORM, config->lpm_gate_waveform,
ST730X_LPM_GATE_WAVEFORM_LEN);
if (err < 0) {
return err;
}
tmp[0] = ST730X_SOURCE_EQ_EN_ENABLE;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_SOURCE_EQ_EN, tmp, 1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_GATESET, &config->multiplex_ratio, 1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_VSHLSEL, &config->source_voltage, 1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_MADCTL, &config->remap_value, 1);
if (err < 0) {
return err;
}
tmp[0] = ST730X_DTFORM_3W_24B;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_DTFORM, tmp, 1);
if (err < 0) {
return err;
}
tmp[0] = ST730X_GAMAMS_MONO;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_GAMAMS, tmp, 1);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_PNLSET, &config->panel_settings, 1);
if (err < 0) {
return err;
}
tmp[0] = ST730X_TEARING_OUT_VBLANK;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_TEARING_OUT, tmp, 1);
if (err < 0) {
return err;
}
tmp[0] = ST730X_AUTOPWRDOWN_ON;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_AUTOPWRDOWN, tmp, 1);
if (err < 0) {
return err;
}
tmp[0] = (config->start_line & 0x100) >> 8;
tmp[1] = config->start_line & 0xFF;
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_FIRSTGATE, tmp, 2);
if (err < 0) {
return err;
}
return mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
ST730X_POWER_MODE, NULL, 0);
}
/* Convert what the conversion buffer can hold to the st730x format */
static int st730x_convert(const struct device *dev, const uint8_t *buf, uint32_t offset,
const struct display_buffer_descriptor *desc)
{
const struct st730x_config *config = dev->config;
uint32_t vertical_offset = desc->width / ST730X_PPB;
uint32_t i = 0;
uint32_t ipos, ipos_zeroed;
uint32_t max_lines = (config->conversion_buf_size / (desc->width / ST730X_PPB)) & ~0x1;
if (max_lines < ST730X_PPYA) {
LOG_ERR("Buffer too small, cannot convert");
return -EINVAL;
}
for (; (offset + i) < desc->height && i < max_lines; i += ST730X_PPYA) {
ipos = (offset + i) * vertical_offset;
ipos_zeroed = i * vertical_offset / ST730X_PPYA;
for (uint32_t j = 0; j < desc->width / ST730X_PPB ; j++) {
config->conversion_buf[(ipos_zeroed + j) * 2 + 1] =
(buf[ipos + j + vertical_offset] & 0x80) >> 7
| (buf[ipos + j] & 0x80) >> 6
| (buf[ipos + j + vertical_offset] & 0x40) >> 4
| (buf[ipos + j] & 0x40) >> 3
| (buf[ipos + j + vertical_offset] & 0x20) >> 1
| (buf[ipos + j] & 0x20)
| (buf[ipos + j + vertical_offset] & 0x10) << 2
| (buf[ipos + j] & 0x10) << 3;
config->conversion_buf[(ipos_zeroed + j) * 2] =
(buf[ipos + j + vertical_offset] & 0x8) >> 3
| (buf[ipos + j] & 0x8) >> 2
| (buf[ipos + j + vertical_offset] & 0x4)
| (buf[ipos + j] & 0x4) << 1
| (buf[ipos + j + vertical_offset] & 0x2) << 3
| (buf[ipos + j] & 0x2) << 4
| (buf[ipos + j + vertical_offset] & 0x1) << 6
| (buf[ipos + j] & 0x1) << 7;
}
}
return i;
}
static int st730x_write(const struct device *dev, const uint16_t x, const uint16_t y,
const struct display_buffer_descriptor *desc, const void *buf)
{
const struct st730x_config *config = dev->config;
int err;
size_t buf_len;
uint32_t processed = 0;
int i;
struct display_buffer_descriptor mipi_desc = *desc;
uint8_t x_start = config->specifics->column_offset + (config->start_column + x)
/ ST730X_PPXA;
uint8_t x_end = config->specifics->column_offset
+ (config->start_column + x + desc->width) / ST730X_PPXA - 1;
uint8_t x_position[] = {x_start, x_end};
uint8_t y_position[] = {y / ST730X_PPYA, (y + desc->height) / ST730X_PPYA - 1};
if (desc->pitch != desc->width) {
LOG_ERR("Pitch is not width");
return -EINVAL;
}
buf_len = MIN(desc->buf_size, desc->height * desc->width / ST730X_PPB);
if (buf == NULL || buf_len == 0U) {
LOG_ERR("Display buffer is not available");
return -EINVAL;
}
if (x % ST730X_PPXA || desc->width % ST730X_PPXA) {
LOG_ERR("X coordinate and size must be aligned by 12 pixels");
return -EINVAL;
}
if (y % ST730X_PPYA || desc->height % ST730X_PPYA) {
LOG_ERR("Y coordinate and size must be aligned by 2 pixels");
return -EINVAL;
}
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 = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_SET_COLUMN_ADDR,
x_position, 2);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_SET_ROW_ADDR,
y_position, 2);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, ST730X_WRITE, NULL, 0);
if (err < 0) {
return err;
}
while (desc->height > processed) {
i = st730x_convert(dev, buf, processed, desc);
if (i < 0) {
return i;
}
mipi_desc.buf_size = i * desc->width / ST730X_PPB;
mipi_desc.width = desc->width;
mipi_desc.height = i;
err = mipi_dbi_write_display(config->mipi_dev, &config->dbi_config,
config->conversion_buf, &mipi_desc,
PIXEL_FORMAT_MONO01);
if (err < 0) {
return err;
}
processed += i;
}
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
}
static void st730x_get_capabilities(const struct device *dev, struct display_capabilities *caps)
{
const struct st730x_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_MONO01;
caps->current_pixel_format = PIXEL_FORMAT_MONO01;
caps->screen_info = 0;
}
static int st730x_set_pixel_format(const struct device *dev, const enum display_pixel_format pf)
{
if (pf == PIXEL_FORMAT_MONO01) {
return 0;
}
LOG_ERR("Unsupported pixel format");
return -ENOTSUP;
}
static int st730x_init_device(const struct device *dev)
{
const struct st730x_config *config = dev->config;
int err;
err = st730x_suspend(dev);
if (err < 0) {
return err;
}
err = st730x_set_hardware_config(dev);
if (err < 0) {
return err;
}
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
config->color_inversion ? ST730X_SET_REVERSE_DISPLAY
: ST730X_SET_NORMAL_DISPLAY,
NULL, 0);
if (err < 0) {
return err;
}
err = st730x_resume(dev);
if (err < 0) {
return err;
}
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
}
static int st730x_init(const struct device *dev)
{
const struct st730x_config *config = dev->config;
int err;
LOG_DBG("Initializing device");
if (!device_is_ready(config->mipi_dev)) {
LOG_ERR("MIPI Device not ready!");
return -ENODEV;
}
err = mipi_dbi_reset(config->mipi_dev, ST730X_RESET_DELAY);
if (err < 0) {
LOG_ERR("Failed to reset device!");
return err;
}
err = st730x_init_device(dev);
if (err < 0) {
LOG_ERR("Failed to initialize device! %d", err);
return err;
}
return 0;
}
static DEVICE_API(display, st730x_driver_api) = {
.blanking_on = st730x_suspend,
.blanking_off = st730x_resume,
.write = st730x_write,
.get_capabilities = st730x_get_capabilities,
.set_pixel_format = st730x_set_pixel_format,
};
#if DT_HAS_COMPAT_STATUS_OKAY(sitronix_st7305)
static const struct st730x_specific st7305_specifics = {
.column_offset = 19,
};
#endif
#if DT_HAS_COMPAT_STATUS_OKAY(sitronix_st7306)
static const struct st730x_specific st7306_specifics = {
.column_offset = 0,
};
#endif
#define ST730X_WORD_SIZE(inst) \
((DT_STRING_UPPER_TOKEN(inst, mipi_mode) == MIPI_DBI_MODE_SPI_4WIRE) ? SPI_WORD_SET(8) \
: SPI_WORD_SET(9))
#define ST730X_CONV_BUFFER_SIZE(node_id) \
ROUND_UP(DT_PROP(node_id, width) * CONFIG_ST730X_CONV_BUFFER_LINE_CNT, \
DT_PROP(node_id, width))
#define ST730X_DEFINE_MIPI(node_id, specifics_ptr) \
static uint8_t conversion_buf##node_id[ST730X_CONV_BUFFER_SIZE(node_id)]; \
static const struct st730x_config config##node_id = { \
.mipi_dev = DEVICE_DT_GET(DT_PARENT(node_id)), \
.dbi_config = MIPI_DBI_CONFIG_DT( \
node_id, ST730X_WORD_SIZE(node_id) | SPI_OP_MODE_MASTER, 0), \
.height = DT_PROP(node_id, height), \
.width = DT_PROP(node_id, width), \
.start_line = DT_PROP(node_id, start_line), \
.start_column = DT_PROP(node_id, start_column), \
.nvm_load = DT_PROP(node_id, nvm_load), \
.gate_voltages = DT_PROP(node_id, gate_voltages), \
.vsh = DT_PROP(node_id, vsh), \
.vsl = DT_PROP(node_id, vsl), \
.vshn = DT_PROP(node_id, vshn), \
.vsln = DT_PROP(node_id, vsln), \
.osc_settings = DT_PROP(node_id, osc_settings), \
.framerate = DT_PROP(node_id, framerate), \
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
.source_voltage = DT_PROP(node_id, source_voltage), \
.remap_value = DT_PROP(node_id, remap_value), \
.panel_settings = DT_PROP(node_id, panel_settings), \
.hpm_gate_waveform = DT_PROP(node_id, hpm_gate_waveform), \
.lpm_gate_waveform = DT_PROP(node_id, lpm_gate_waveform), \
.color_inversion = DT_PROP(node_id, inversion_on), \
.specifics = specifics_ptr, \
.conversion_buf = conversion_buf##node_id, \
.conversion_buf_size = sizeof(conversion_buf##node_id), \
}; \
DEVICE_DT_DEFINE(node_id, st730x_init, NULL, NULL, &config##node_id, \
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &st730x_driver_api);
DT_FOREACH_STATUS_OKAY_VARGS(sitronix_st7305, ST730X_DEFINE_MIPI, &st7305_specifics)
DT_FOREACH_STATUS_OKAY_VARGS(sitronix_st7306, ST730X_DEFINE_MIPI, &st7306_specifics)