Skip to content

Commit ad3e941

Browse files
lucbeaufilsnashif
authored andcommitted
drivers: add SSD1327 display controller driver
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. Signed-off-by: Luc BEAUFILS <[email protected]>
1 parent 88c7a21 commit ad3e941

File tree

6 files changed

+475
-0
lines changed

6 files changed

+475
-0
lines changed

drivers/display/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ zephyr_library_sources_ifdef(CONFIG_LS0XX ls0xx.c)
1616
zephyr_library_sources_ifdef(CONFIG_MAX7219 display_max7219.c)
1717
zephyr_library_sources_ifdef(CONFIG_OTM8009A display_otm8009a.c)
1818
zephyr_library_sources_ifdef(CONFIG_SSD1306 ssd1306.c)
19+
zephyr_library_sources_ifdef(CONFIG_SSD1327 ssd1327.c)
1920
zephyr_library_sources_ifdef(CONFIG_SSD16XX ssd16xx.c)
2021
zephyr_library_sources_ifdef(CONFIG_ST7789V display_st7789v.c)
2122
zephyr_library_sources_ifdef(CONFIG_ST7735R display_st7735r.c)

drivers/display/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ source "drivers/display/Kconfig.nrf_led_matrix"
2626
source "drivers/display/Kconfig.ili9xxx"
2727
source "drivers/display/Kconfig.sdl"
2828
source "drivers/display/Kconfig.ssd1306"
29+
source "drivers/display/Kconfig.ssd1327"
2930
source "drivers/display/Kconfig.ssd16xx"
3031
source "drivers/display/Kconfig.st7735r"
3132
source "drivers/display/Kconfig.st7789v"

drivers/display/Kconfig.ssd1327

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SSD1327 display controller configuration options
2+
3+
# Copyright (c) 2024 Savoir-faire Linux
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
menuconfig SSD1327
7+
bool "SSD1327 display driver"
8+
default y
9+
depends on DT_HAS_SOLOMON_SSD1327FB_ENABLED
10+
select MIPI_DBI
11+
help
12+
Enable driver for SSD1327 display.
13+
14+
if SSD1327
15+
16+
config SSD1327_DEFAULT_CONTRAST
17+
int "SSD1327 default contrast"
18+
default 128
19+
range 0 255
20+
help
21+
SSD1327 default contrast.
22+
23+
endif # SSD1327

drivers/display/ssd1327.c

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
/*
2+
* Copyright (c) 2024 Savoir-faire Linux
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/logging/log.h>
8+
LOG_MODULE_REGISTER(ssd1327, CONFIG_DISPLAY_LOG_LEVEL);
9+
10+
#include <string.h>
11+
#include <zephyr/device.h>
12+
#include <zephyr/init.h>
13+
#include <zephyr/drivers/display.h>
14+
#include <zephyr/drivers/gpio.h>
15+
#include <zephyr/drivers/mipi_dbi.h>
16+
#include <zephyr/kernel.h>
17+
18+
#include "ssd1327_regs.h"
19+
20+
#define SSD1327_ENABLE_VDD 0x01
21+
#define SSD1327_ENABLE_SECOND_PRECHARGE 0x62
22+
#define SSD1327_VCOMH_VOLTAGE 0x0f
23+
#define SSD1327_PHASES_VALUE 0xf1
24+
#define SSD1327_DEFAULT_PRECHARGE_V 0x08
25+
#define SSD1327_UNLOCK_COMMAND 0x12
26+
27+
struct ssd1327_config {
28+
const struct device *mipi_dev;
29+
const struct mipi_dbi_config dbi_config;
30+
uint16_t height;
31+
uint16_t width;
32+
uint8_t oscillator_freq;
33+
uint8_t start_line;
34+
uint8_t display_offset;
35+
uint8_t multiplex_ratio;
36+
uint8_t prechargep;
37+
uint8_t remap_value;
38+
bool color_inversion;
39+
};
40+
41+
struct ssd1327_data {
42+
uint8_t contrast;
43+
uint8_t scan_mode;
44+
};
45+
46+
static inline int ssd1327_write_bus_cmd(const struct device *dev, const uint8_t cmd,
47+
const uint8_t *data, size_t len)
48+
{
49+
const struct ssd1327_config *config = dev->config;
50+
int err;
51+
52+
/* Values given after the memory register must be sent with pin D/C set to 0. */
53+
/* Data is sent as a command following the mipi_cbi api */
54+
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, cmd, NULL, 0);
55+
if (err) {
56+
return err;
57+
}
58+
for (size_t i = 0; i < len; i++) {
59+
err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
60+
data[i], NULL, 0);
61+
if (err) {
62+
return err;
63+
}
64+
}
65+
mipi_dbi_release(config->mipi_dev, &config->dbi_config);
66+
67+
return 0;
68+
}
69+
70+
static inline int ssd1327_set_timing_setting(const struct device *dev)
71+
{
72+
const struct ssd1327_config *config = dev->config;
73+
uint8_t buf;
74+
75+
buf = SSD1327_PHASES_VALUE;
76+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PHASE_LENGTH, &buf, 1)) {
77+
return -EIO;
78+
}
79+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_OSC_FREQ, &config->oscillator_freq, 1)) {
80+
return -EIO;
81+
}
82+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_PERIOD, &config->prechargep, 1)) {
83+
return -EIO;
84+
}
85+
if (ssd1327_write_bus_cmd(dev, SSD1327_LINEAR_LUT, NULL, 0)) {
86+
return -EIO;
87+
}
88+
buf = SSD1327_DEFAULT_PRECHARGE_V;
89+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_VOLTAGE, &buf, 1)) {
90+
return -EIO;
91+
}
92+
buf = SSD1327_VCOMH_VOLTAGE;
93+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_VCOMH, &buf, 1)) {
94+
return -EIO;
95+
}
96+
buf = SSD1327_ENABLE_SECOND_PRECHARGE;
97+
if (ssd1327_write_bus_cmd(dev, SSD1327_FUNCTION_SELECTION_B, &buf, 1)) {
98+
return -EIO;
99+
}
100+
buf = SSD1327_UNLOCK_COMMAND;
101+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COMMAND_LOCK, &buf, 1)) {
102+
return -EIO;
103+
}
104+
105+
return 0;
106+
}
107+
108+
static inline int ssd1327_set_hardware_config(const struct device *dev)
109+
{
110+
const struct ssd1327_config *config = dev->config;
111+
uint8_t buf;
112+
113+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_START_LINE, &config->start_line, 1)) {
114+
return -EIO;
115+
}
116+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_OFFSET, &config->display_offset, 1)) {
117+
return -EIO;
118+
}
119+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_NORMAL_DISPLAY, NULL, 0)) {
120+
return -EIO;
121+
}
122+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
123+
return -EIO;
124+
}
125+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_MULTIPLEX_RATIO, &config->multiplex_ratio, 1)) {
126+
return -EIO;
127+
}
128+
buf = SSD1327_ENABLE_VDD;
129+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_FUNCTION_A, &buf, 1)) {
130+
return -EIO;
131+
}
132+
133+
return 0;
134+
}
135+
136+
static int ssd1327_resume(const struct device *dev)
137+
{
138+
return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_ON, NULL, 0);
139+
}
140+
141+
static int ssd1327_suspend(const struct device *dev)
142+
{
143+
return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_OFF, NULL, 0);
144+
}
145+
146+
static int ssd1327_set_display(const struct device *dev)
147+
{
148+
const struct ssd1327_config *config = dev->config;
149+
uint8_t x_position[] = {
150+
0,
151+
config->width - 1
152+
};
153+
uint8_t y_position[] = {
154+
0,
155+
config->height - 1
156+
};
157+
158+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position))) {
159+
return -EIO;
160+
}
161+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position))) {
162+
return -EIO;
163+
}
164+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
165+
return -EIO;
166+
}
167+
168+
return 0;
169+
}
170+
171+
static int ssd1327_write(const struct device *dev, const uint16_t x, const uint16_t y,
172+
const struct display_buffer_descriptor *desc, const void *buf)
173+
{
174+
const struct ssd1327_config *config = dev->config;
175+
struct display_buffer_descriptor mipi_desc;
176+
int err;
177+
size_t buf_len;
178+
uint8_t x_position[] = { x, x + desc->width - 1 };
179+
uint8_t y_position[] = { y, y + desc->height - 1 };
180+
181+
if (desc->pitch < desc->width) {
182+
LOG_ERR("Pitch is smaller than width");
183+
return -1;
184+
}
185+
mipi_desc.pitch = desc->pitch;
186+
187+
/* Following the datasheet, in the GDDRAM, two segment are split in one register */
188+
buf_len = MIN(desc->buf_size, desc->height * desc->width / 2);
189+
if (buf == NULL || buf_len == 0U) {
190+
LOG_ERR("Display buffer is not available");
191+
return -1;
192+
}
193+
mipi_desc.buf_size = buf_len;
194+
195+
if (desc->pitch > desc->width) {
196+
LOG_ERR("Unsupported mode");
197+
return -1;
198+
}
199+
200+
if ((y & 0x7) != 0U) {
201+
LOG_ERR("Unsupported origin");
202+
return -1;
203+
}
204+
mipi_desc.height = desc->height;
205+
mipi_desc.width = desc->width;
206+
207+
LOG_DBG("x %u, y %u, pitch %u, width %u, height %u, buf_len %u", x, y, desc->pitch,
208+
desc->width, desc->height, buf_len);
209+
210+
err = ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position));
211+
if (err) {
212+
return err;
213+
}
214+
215+
err = ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position));
216+
if (err) {
217+
return err;
218+
}
219+
220+
err = mipi_dbi_write_display(config->mipi_dev, &config->dbi_config, buf, &mipi_desc,
221+
PIXEL_FORMAT_MONO10);
222+
if (err) {
223+
return err;
224+
}
225+
return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
226+
}
227+
228+
static int ssd1327_set_contrast(const struct device *dev, const uint8_t contrast)
229+
{
230+
return ssd1327_write_bus_cmd(dev, SSD1327_SET_CONTRAST_CTRL, &contrast, 1);
231+
}
232+
233+
static void ssd1327_get_capabilities(const struct device *dev,
234+
struct display_capabilities *caps)
235+
{
236+
const struct ssd1327_config *config = dev->config;
237+
238+
memset(caps, 0, sizeof(struct display_capabilities));
239+
caps->x_resolution = config->width;
240+
caps->y_resolution = config->height;
241+
caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
242+
caps->current_pixel_format = PIXEL_FORMAT_MONO10;
243+
caps->screen_info = SCREEN_INFO_MONO_VTILED;
244+
}
245+
246+
static int ssd1327_set_pixel_format(const struct device *dev,
247+
const enum display_pixel_format pf)
248+
{
249+
if (pf == PIXEL_FORMAT_MONO10) {
250+
return 0;
251+
}
252+
LOG_ERR("Unsupported pixel format");
253+
return -ENOTSUP;
254+
}
255+
256+
static int ssd1327_init_device(const struct device *dev)
257+
{
258+
const struct ssd1327_config *config = dev->config;
259+
uint8_t buf;
260+
261+
/* Turn display off */
262+
if (ssd1327_suspend(dev)) {
263+
return -EIO;
264+
}
265+
266+
if (ssd1327_set_display(dev)) {
267+
return -EIO;
268+
}
269+
270+
if (ssd1327_set_contrast(dev, CONFIG_SSD1327_DEFAULT_CONTRAST)) {
271+
return -EIO;
272+
}
273+
274+
if (ssd1327_set_hardware_config(dev)) {
275+
return -EIO;
276+
}
277+
278+
buf = (config->color_inversion ?
279+
SSD1327_SET_REVERSE_DISPLAY : SSD1327_SET_NORMAL_DISPLAY);
280+
if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ENTIRE_DISPLAY_OFF, &buf, 1)) {
281+
return -EIO;
282+
}
283+
284+
if (ssd1327_set_timing_setting(dev)) {
285+
return -EIO;
286+
}
287+
288+
if (ssd1327_resume(dev)) {
289+
return -EIO;
290+
}
291+
292+
return 0;
293+
}
294+
295+
static int ssd1327_init(const struct device *dev)
296+
{
297+
const struct ssd1327_config *config = dev->config;
298+
299+
LOG_DBG("Initializing device");
300+
301+
if (!device_is_ready(config->mipi_dev)) {
302+
LOG_ERR("MIPI Device not ready!");
303+
return -EINVAL;
304+
}
305+
306+
if (mipi_dbi_reset(config->mipi_dev, SSD1327_RESET_DELAY)) {
307+
LOG_ERR("Failed to reset device!");
308+
return -EIO;
309+
}
310+
k_msleep(SSD1327_RESET_DELAY);
311+
312+
if (ssd1327_init_device(dev)) {
313+
LOG_ERR("Failed to initialize device!");
314+
return -EIO;
315+
}
316+
317+
return 0;
318+
}
319+
320+
static struct display_driver_api ssd1327_driver_api = {
321+
.blanking_on = ssd1327_suspend,
322+
.blanking_off = ssd1327_resume,
323+
.write = ssd1327_write,
324+
.set_contrast = ssd1327_set_contrast,
325+
.get_capabilities = ssd1327_get_capabilities,
326+
.set_pixel_format = ssd1327_set_pixel_format,
327+
};
328+
329+
#define SSD1327_DEFINE(node_id) \
330+
static struct ssd1327_data data##node_id; \
331+
static const struct ssd1327_config config##node_id = { \
332+
.mipi_dev = DEVICE_DT_GET(DT_PARENT(node_id)), \
333+
.dbi_config = { .mode = MIPI_DBI_MODE_SPI_4WIRE, \
334+
.config = MIPI_DBI_SPI_CONFIG_DT(node_id, \
335+
SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | \
336+
SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \
337+
}, \
338+
.height = DT_PROP(node_id, height), \
339+
.width = DT_PROP(node_id, width), \
340+
.oscillator_freq = DT_PROP(node_id, oscillator_freq), \
341+
.display_offset = DT_PROP(node_id, display_offset), \
342+
.start_line = DT_PROP(node_id, start_line), \
343+
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
344+
.prechargep = DT_PROP(node_id, prechargep), \
345+
.remap_value = DT_PROP(node_id, remap_value), \
346+
.color_inversion = DT_PROP(node_id, inversion_on), \
347+
}; \
348+
\
349+
DEVICE_DT_DEFINE(node_id, ssd1327_init, NULL, &data##node_id, &config##node_id, \
350+
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1327_driver_api);
351+
352+
DT_FOREACH_STATUS_OKAY(solomon_ssd1327fb, SSD1327_DEFINE)

0 commit comments

Comments
 (0)