1 /*
2  * Copyright (c) 2018 PHYTEC Messtechnik GmbH
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(ssd1306, 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/i2c.h>
16 #include <zephyr/drivers/spi.h>
17 #include <zephyr/kernel.h>
18 
19 #include "ssd1306_regs.h"
20 
21 #define SSD1306_CLOCK_DIV_RATIO		0x0
22 #define SSD1306_CLOCK_FREQUENCY		0x8
23 #define SSD1306_PANEL_VCOM_DESEL_LEVEL	0x20
24 #define SSD1306_PANEL_PUMP_VOLTAGE	SSD1306_SET_PUMP_VOLTAGE_90
25 
26 #define SSD1306_PANEL_VCOM_DESEL_LEVEL_SSD1309  0x34
27 
28 #ifndef SSD1306_ADDRESSING_MODE
29 #define SSD1306_ADDRESSING_MODE		(SSD1306_SET_MEM_ADDRESSING_HORIZONTAL)
30 #endif
31 
32 union ssd1306_bus {
33 	struct i2c_dt_spec i2c;
34 	struct spi_dt_spec spi;
35 };
36 
37 typedef bool (*ssd1306_bus_ready_fn)(const struct device *dev);
38 typedef int (*ssd1306_write_bus_fn)(const struct device *dev, uint8_t *buf, size_t len,
39 				    bool command);
40 typedef const char *(*ssd1306_bus_name_fn)(const struct device *dev);
41 
42 struct ssd1306_config {
43 	union ssd1306_bus bus;
44 	struct gpio_dt_spec data_cmd;
45 	struct gpio_dt_spec reset;
46 	struct gpio_dt_spec supply;
47 	ssd1306_bus_ready_fn bus_ready;
48 	ssd1306_write_bus_fn write_bus;
49 	ssd1306_bus_name_fn bus_name;
50 	uint16_t height;
51 	uint16_t width;
52 	uint8_t segment_offset;
53 	uint8_t page_offset;
54 	uint8_t display_offset;
55 	uint8_t multiplex_ratio;
56 	uint8_t prechargep;
57 	bool segment_remap;
58 	bool com_invdir;
59 	bool com_sequential;
60 	bool color_inversion;
61 	bool ssd1309_compatible;
62 	bool sh1106_compatible;
63 	int ready_time_ms;
64 	bool use_internal_iref;
65 };
66 
67 struct ssd1306_data {
68 	enum display_pixel_format pf;
69 };
70 
71 #if (DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(solomon_ssd1306fb, i2c) || \
72 	DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(solomon_ssd1309fb, i2c) || \
73 	DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(sinowealth_sh1106, i2c))
ssd1306_bus_ready_i2c(const struct device * dev)74 static bool ssd1306_bus_ready_i2c(const struct device *dev)
75 {
76 	const struct ssd1306_config *config = dev->config;
77 
78 	return i2c_is_ready_dt(&config->bus.i2c);
79 }
80 
ssd1306_write_bus_i2c(const struct device * dev,uint8_t * buf,size_t len,bool command)81 static int ssd1306_write_bus_i2c(const struct device *dev, uint8_t *buf, size_t len, bool command)
82 {
83 	const struct ssd1306_config *config = dev->config;
84 
85 	return i2c_burst_write_dt(&config->bus.i2c,
86 				  command ? SSD1306_CONTROL_ALL_BYTES_CMD :
87 				  SSD1306_CONTROL_ALL_BYTES_DATA,
88 				  buf, len);
89 }
90 
ssd1306_bus_name_i2c(const struct device * dev)91 static const char *ssd1306_bus_name_i2c(const struct device *dev)
92 {
93 	const struct ssd1306_config *config = dev->config;
94 
95 	return config->bus.i2c.bus->name;
96 }
97 #endif
98 
99 #if (DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(solomon_ssd1306fb, spi) || \
100 	DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(solomon_ssd1309fb, spi) || \
101 	DT_HAS_COMPAT_ON_BUS_STATUS_OKAY(sinowealth_sh1106, spi))
ssd1306_bus_ready_spi(const struct device * dev)102 static bool ssd1306_bus_ready_spi(const struct device *dev)
103 {
104 	const struct ssd1306_config *config = dev->config;
105 
106 	if (gpio_pin_configure_dt(&config->data_cmd, GPIO_OUTPUT_INACTIVE) < 0) {
107 		return false;
108 	}
109 
110 	return spi_is_ready_dt(&config->bus.spi);
111 }
112 
ssd1306_write_bus_spi(const struct device * dev,uint8_t * buf,size_t len,bool command)113 static int ssd1306_write_bus_spi(const struct device *dev, uint8_t *buf, size_t len, bool command)
114 {
115 	const struct ssd1306_config *config = dev->config;
116 	int ret;
117 
118 	gpio_pin_set_dt(&config->data_cmd, command ? 0 : 1);
119 	struct spi_buf tx_buf = {
120 		.buf = buf,
121 		.len = len
122 	};
123 
124 	struct spi_buf_set tx_bufs = {
125 		.buffers = &tx_buf,
126 		.count = 1
127 	};
128 
129 	ret = spi_write_dt(&config->bus.spi, &tx_bufs);
130 
131 	return ret;
132 }
133 
ssd1306_bus_name_spi(const struct device * dev)134 static const char *ssd1306_bus_name_spi(const struct device *dev)
135 {
136 	const struct ssd1306_config *config = dev->config;
137 
138 	return config->bus.spi.bus->name;
139 }
140 #endif
141 
ssd1306_bus_ready(const struct device * dev)142 static inline bool ssd1306_bus_ready(const struct device *dev)
143 {
144 	const struct ssd1306_config *config = dev->config;
145 
146 	return config->bus_ready(dev);
147 }
148 
ssd1306_write_bus(const struct device * dev,uint8_t * buf,size_t len,bool command)149 static inline int ssd1306_write_bus(const struct device *dev, uint8_t *buf, size_t len,
150 				    bool command)
151 {
152 	const struct ssd1306_config *config = dev->config;
153 
154 	return config->write_bus(dev, buf, len, command);
155 }
156 
ssd1306_set_panel_orientation(const struct device * dev)157 static inline int ssd1306_set_panel_orientation(const struct device *dev)
158 {
159 	const struct ssd1306_config *config = dev->config;
160 	uint8_t cmd_buf[] = {(config->segment_remap ? SSD1306_SET_SEGMENT_MAP_REMAPED
161 						    : SSD1306_SET_SEGMENT_MAP_NORMAL),
162 			     (config->com_invdir ? SSD1306_SET_COM_OUTPUT_SCAN_FLIPPED
163 						 : SSD1306_SET_COM_OUTPUT_SCAN_NORMAL)};
164 
165 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
166 }
167 
ssd1306_set_timing_setting(const struct device * dev)168 static inline int ssd1306_set_timing_setting(const struct device *dev)
169 {
170 	const struct ssd1306_config *config = dev->config;
171 	uint8_t cmd_buf[] = {SSD1306_SET_CLOCK_DIV_RATIO,
172 			     (SSD1306_CLOCK_FREQUENCY << 4) | SSD1306_CLOCK_DIV_RATIO,
173 			     SSD1306_SET_CHARGE_PERIOD,
174 			     config->prechargep,
175 			     SSD1306_SET_VCOM_DESELECT_LEVEL,
176 			     config->ssd1309_compatible ? SSD1306_PANEL_VCOM_DESEL_LEVEL_SSD1309 :
177 				SSD1306_PANEL_VCOM_DESEL_LEVEL};
178 
179 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
180 }
181 
ssd1306_set_hardware_config(const struct device * dev)182 static inline int ssd1306_set_hardware_config(const struct device *dev)
183 {
184 	const struct ssd1306_config *config = dev->config;
185 	uint8_t cmd_buf[] = {
186 		SSD1306_SET_START_LINE,
187 		SSD1306_SET_DISPLAY_OFFSET,
188 		config->display_offset,
189 		SSD1306_SET_PADS_HW_CONFIG,
190 		(config->com_sequential ? SSD1306_SET_PADS_HW_SEQUENTIAL
191 					: SSD1306_SET_PADS_HW_ALTERNATIVE),
192 		SSD1306_SET_MULTIPLEX_RATIO,
193 		config->multiplex_ratio,
194 	};
195 
196 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
197 }
198 
ssd1306_set_charge_pump(const struct device * dev)199 static inline int ssd1306_set_charge_pump(const struct device *dev)
200 {
201 	const struct ssd1306_config *config = dev->config;
202 	uint8_t cmd_buf[] = {
203 		(config->sh1106_compatible ? SH1106_SET_DCDC_MODE : SSD1306_SET_CHARGE_PUMP_ON),
204 		(config->sh1106_compatible ? SH1106_SET_DCDC_ENABLED
205 					   : SSD1306_SET_CHARGE_PUMP_ON_ENABLED),
206 		SSD1306_PANEL_PUMP_VOLTAGE,
207 	};
208 
209 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
210 }
211 
ssd1306_set_iref_mode(const struct device * dev)212 static inline int ssd1306_set_iref_mode(const struct device *dev)
213 {
214 	int ret = 0;
215 	const struct ssd1306_config *config = dev->config;
216 	uint8_t cmd_buf[] = {
217 		SSD1306_SET_IREF_MODE,
218 		SSD1306_SET_IREF_MODE_INTERNAL,
219 	};
220 
221 	if (config->use_internal_iref) {
222 		ret = ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
223 	}
224 
225 	return ret;
226 }
227 
ssd1306_resume(const struct device * dev)228 static int ssd1306_resume(const struct device *dev)
229 {
230 	const struct ssd1306_config *config = dev->config;
231 	uint8_t cmd_buf[] = {
232 		SSD1306_DISPLAY_ON,
233 	};
234 
235 	/* Turn on supply if pin connected */
236 	if (config->supply.port) {
237 		gpio_pin_set_dt(&config->supply, 1);
238 		k_sleep(K_MSEC(SSD1306_SUPPLY_DELAY));
239 	}
240 
241 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
242 }
243 
ssd1306_suspend(const struct device * dev)244 static int ssd1306_suspend(const struct device *dev)
245 {
246 	const struct ssd1306_config *config = dev->config;
247 	uint8_t cmd_buf[] = {
248 		SSD1306_DISPLAY_OFF,
249 	};
250 
251 	/* Turn off supply if pin connected */
252 	if (config->supply.port) {
253 		gpio_pin_set_dt(&config->supply, 0);
254 		k_sleep(K_MSEC(SSD1306_SUPPLY_DELAY));
255 	}
256 
257 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
258 }
259 
ssd1306_write_default(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf,const size_t buf_len)260 static int ssd1306_write_default(const struct device *dev, const uint16_t x, const uint16_t y,
261 				 const struct display_buffer_descriptor *desc, const void *buf,
262 				 const size_t buf_len)
263 {
264 	const struct ssd1306_config *config = dev->config;
265 	uint8_t x_off = config->segment_offset;
266 	uint8_t cmd_buf[] = {
267 		SSD1306_SET_MEM_ADDRESSING_MODE,
268 		SSD1306_ADDRESSING_MODE,
269 		SSD1306_SET_COLUMN_ADDRESS,
270 		x + x_off,
271 		(x + desc->width - 1) + x_off,
272 		SSD1306_SET_PAGE_ADDRESS,
273 		y/8,
274 		((y + desc->height)/8 - 1)
275 	};
276 
277 	if (ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true)) {
278 		LOG_ERR("Failed to write command");
279 		return -1;
280 	}
281 
282 	return ssd1306_write_bus(dev, (uint8_t *)buf, buf_len, false);
283 }
284 
ssd1306_write_sh1106(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf,const size_t buf_len)285 static int ssd1306_write_sh1106(const struct device *dev, const uint16_t x, const uint16_t y,
286 				const struct display_buffer_descriptor *desc, const void *buf,
287 				const size_t buf_len)
288 {
289 	const struct ssd1306_config *config = dev->config;
290 	uint8_t x_offset = x + config->segment_offset;
291 	uint8_t cmd_buf[] = {
292 		SSD1306_SET_LOWER_COL_ADDRESS |
293 			(x_offset & SSD1306_SET_LOWER_COL_ADDRESS_MASK),
294 		SSD1306_SET_HIGHER_COL_ADDRESS |
295 			((x_offset >> 4) & SSD1306_SET_LOWER_COL_ADDRESS_MASK),
296 		SSD1306_SET_PAGE_START_ADDRESS | (y / 8)
297 	};
298 	uint8_t *buf_ptr = (uint8_t *)buf;
299 
300 	for (uint8_t n = 0; n < desc->height / 8; n++) {
301 		cmd_buf[sizeof(cmd_buf) - 1] =
302 			SSD1306_SET_PAGE_START_ADDRESS | (n + (y / 8));
303 		LOG_HEXDUMP_DBG(cmd_buf, sizeof(cmd_buf), "cmd_buf");
304 
305 		if (ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true)) {
306 			return -1;
307 		}
308 
309 		if (ssd1306_write_bus(dev, buf_ptr, desc->width, false)) {
310 			return -1;
311 		}
312 
313 		buf_ptr = buf_ptr + desc->width;
314 		if (buf_ptr > ((uint8_t *)buf + buf_len)) {
315 			LOG_ERR("Exceeded buffer length");
316 			return -1;
317 		}
318 	}
319 
320 	return 0;
321 }
322 
ssd1306_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)323 static int ssd1306_write(const struct device *dev, const uint16_t x, const uint16_t y,
324 			 const struct display_buffer_descriptor *desc, const void *buf)
325 {
326 	const struct ssd1306_config *config = dev->config;
327 	size_t buf_len;
328 
329 	if (desc->pitch < desc->width) {
330 		LOG_ERR("Pitch is smaller than width");
331 		return -1;
332 	}
333 
334 	buf_len = MIN(desc->buf_size, desc->height * desc->width / 8);
335 	if (buf == NULL || buf_len == 0U) {
336 		LOG_ERR("Display buffer is not available");
337 		return -1;
338 	}
339 
340 	if (desc->pitch > desc->width) {
341 		LOG_ERR("Unsupported mode");
342 		return -1;
343 	}
344 
345 	if ((y & 0x7) != 0U) {
346 		LOG_ERR("Unsupported origin");
347 		return -1;
348 	}
349 
350 	LOG_DBG("x %u, y %u, pitch %u, width %u, height %u, buf_len %u", x, y, desc->pitch,
351 		desc->width, desc->height, buf_len);
352 
353 	if (config->sh1106_compatible) {
354 		return ssd1306_write_sh1106(dev, x, y, desc, buf, buf_len);
355 	}
356 
357 	return ssd1306_write_default(dev, x, y, desc, buf, buf_len);
358 }
359 
ssd1306_set_contrast(const struct device * dev,const uint8_t contrast)360 static int ssd1306_set_contrast(const struct device *dev, const uint8_t contrast)
361 {
362 	uint8_t cmd_buf[] = {
363 		SSD1306_SET_CONTRAST_CTRL,
364 		contrast,
365 	};
366 
367 	return ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true);
368 }
369 
ssd1306_get_capabilities(const struct device * dev,struct display_capabilities * caps)370 static void ssd1306_get_capabilities(const struct device *dev,
371 				     struct display_capabilities *caps)
372 {
373 	const struct ssd1306_config *config = dev->config;
374 	struct ssd1306_data *data = dev->data;
375 
376 	caps->x_resolution = config->width;
377 	caps->y_resolution = config->height;
378 	caps->supported_pixel_formats = PIXEL_FORMAT_MONO10 | PIXEL_FORMAT_MONO01;
379 	caps->current_pixel_format = data->pf;
380 	caps->screen_info = SCREEN_INFO_MONO_VTILED;
381 	caps->current_orientation = DISPLAY_ORIENTATION_NORMAL;
382 }
383 
ssd1306_set_pixel_format(const struct device * dev,const enum display_pixel_format pf)384 static int ssd1306_set_pixel_format(const struct device *dev,
385 				    const enum display_pixel_format pf)
386 {
387 	struct ssd1306_data *data = dev->data;
388 	uint8_t cmd;
389 	int ret;
390 
391 	if (pf == data->pf) {
392 		return 0;
393 	}
394 
395 	if (pf == PIXEL_FORMAT_MONO10) {
396 		cmd = SSD1306_SET_REVERSE_DISPLAY;
397 	} else if (pf == PIXEL_FORMAT_MONO01) {
398 		cmd = SSD1306_SET_NORMAL_DISPLAY;
399 	} else {
400 		LOG_WRN("Unsupported pixel format");
401 		return -ENOTSUP;
402 	}
403 
404 	ret = ssd1306_write_bus(dev, &cmd, 1, true);
405 	if (ret) {
406 		return ret;
407 	}
408 
409 	data->pf = pf;
410 
411 	return 0;
412 }
413 
ssd1306_init_device(const struct device * dev)414 static int ssd1306_init_device(const struct device *dev)
415 {
416 	const struct ssd1306_config *config = dev->config;
417 	struct ssd1306_data *data = dev->data;
418 
419 	uint8_t cmd_buf[] = {
420 		SSD1306_SET_ENTIRE_DISPLAY_OFF,
421 		(config->color_inversion ? SSD1306_SET_REVERSE_DISPLAY
422 					 : SSD1306_SET_NORMAL_DISPLAY),
423 	};
424 
425 	data->pf = config->color_inversion ? PIXEL_FORMAT_MONO10 : PIXEL_FORMAT_MONO01;
426 	/* Turn on supply if pin connected */
427 	if (config->supply.port) {
428 		gpio_pin_set_dt(&config->supply, 1);
429 		k_sleep(K_MSEC(SSD1306_SUPPLY_DELAY));
430 	}
431 
432 	/* Reset if pin connected */
433 	if (config->reset.port) {
434 		k_sleep(K_MSEC(SSD1306_RESET_DELAY));
435 		gpio_pin_set_dt(&config->reset, 1);
436 		k_sleep(K_MSEC(SSD1306_RESET_DELAY));
437 		gpio_pin_set_dt(&config->reset, 0);
438 	}
439 
440 	/* Turn display off */
441 	if (ssd1306_suspend(dev)) {
442 		return -EIO;
443 	}
444 
445 	if (ssd1306_set_timing_setting(dev)) {
446 		return -EIO;
447 	}
448 
449 	if (ssd1306_set_hardware_config(dev)) {
450 		return -EIO;
451 	}
452 
453 	if (ssd1306_set_panel_orientation(dev)) {
454 		return -EIO;
455 	}
456 
457 	if (!config->ssd1309_compatible) {
458 		if (ssd1306_set_charge_pump(dev)) {
459 			return -EIO;
460 		}
461 
462 		if (ssd1306_set_iref_mode(dev)) {
463 			return -EIO;
464 		}
465 	}
466 
467 	if (ssd1306_write_bus(dev, cmd_buf, sizeof(cmd_buf), true)) {
468 		return -EIO;
469 	}
470 
471 	if (ssd1306_set_contrast(dev, CONFIG_SSD1306_DEFAULT_CONTRAST)) {
472 		return -EIO;
473 	}
474 
475 	ssd1306_resume(dev);
476 
477 	return 0;
478 }
479 
ssd1306_init(const struct device * dev)480 static int ssd1306_init(const struct device *dev)
481 {
482 	const struct ssd1306_config *config = dev->config;
483 	int ret;
484 
485 	k_sleep(K_TIMEOUT_ABS_MS(config->ready_time_ms));
486 
487 	if (!ssd1306_bus_ready(dev)) {
488 		LOG_ERR("Bus device %s not ready!", config->bus_name(dev));
489 		return -EINVAL;
490 	}
491 
492 	if (config->supply.port) {
493 		ret = gpio_pin_configure_dt(&config->supply,
494 					    GPIO_OUTPUT_INACTIVE);
495 		if (ret < 0) {
496 			return ret;
497 		}
498 		if (!gpio_is_ready_dt(&config->supply)) {
499 			LOG_ERR("Supply GPIO device not ready");
500 			return -ENODEV;
501 		}
502 	}
503 
504 	if (config->reset.port) {
505 		ret = gpio_pin_configure_dt(&config->reset,
506 					    GPIO_OUTPUT_INACTIVE);
507 		if (ret < 0) {
508 			return ret;
509 		}
510 		if (!gpio_is_ready_dt(&config->reset)) {
511 			LOG_ERR("Reset GPIO device not ready");
512 			return -ENODEV;
513 		}
514 	}
515 
516 	if (ssd1306_init_device(dev)) {
517 		LOG_ERR("Failed to initialize device!");
518 		return -EIO;
519 	}
520 
521 	return 0;
522 }
523 
524 static DEVICE_API(display, ssd1306_driver_api) = {
525 	.blanking_on = ssd1306_suspend,
526 	.blanking_off = ssd1306_resume,
527 	.write = ssd1306_write,
528 	.set_contrast = ssd1306_set_contrast,
529 	.get_capabilities = ssd1306_get_capabilities,
530 	.set_pixel_format = ssd1306_set_pixel_format,
531 };
532 
533 #define SSD1306_CONFIG_SPI(node_id)                                                                \
534 	.bus = {.spi = SPI_DT_SPEC_GET(                                                            \
535 			node_id, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0)},     \
536 	.bus_ready = ssd1306_bus_ready_spi,                                                        \
537 	.write_bus = ssd1306_write_bus_spi,                                                        \
538 	.bus_name = ssd1306_bus_name_spi,                                                          \
539 	.data_cmd = GPIO_DT_SPEC_GET(node_id, data_cmd_gpios),
540 
541 #define SSD1306_CONFIG_I2C(node_id)                                                                \
542 	.bus = {.i2c = I2C_DT_SPEC_GET(node_id)},                                                  \
543 	.bus_ready = ssd1306_bus_ready_i2c,                                                        \
544 	.write_bus = ssd1306_write_bus_i2c,                                                        \
545 	.bus_name = ssd1306_bus_name_i2c,                                                          \
546 	.data_cmd = {0},
547 
548 #define SSD1306_DEFINE(node_id)                                                                    \
549 	static struct ssd1306_data data##node_id;                                                  \
550 	static const struct ssd1306_config config##node_id = {                                     \
551 		.reset = GPIO_DT_SPEC_GET_OR(node_id, reset_gpios, {0}),                           \
552 		.supply = GPIO_DT_SPEC_GET_OR(node_id, supply_gpios, {0}),                         \
553 		.height = DT_PROP(node_id, height),                                                \
554 		.width = DT_PROP(node_id, width),                                                  \
555 		.segment_offset = DT_PROP(node_id, segment_offset),                                \
556 		.page_offset = DT_PROP(node_id, page_offset),                                      \
557 		.display_offset = DT_PROP(node_id, display_offset),                                \
558 		.multiplex_ratio = DT_PROP(node_id, multiplex_ratio),                              \
559 		.segment_remap = DT_PROP(node_id, segment_remap),                                  \
560 		.com_invdir = DT_PROP(node_id, com_invdir),                                        \
561 		.com_sequential = DT_PROP(node_id, com_sequential),                                \
562 		.prechargep = DT_PROP(node_id, prechargep),                                        \
563 		.color_inversion = DT_PROP(node_id, inversion_on),                                 \
564 		.ssd1309_compatible = DT_NODE_HAS_COMPAT(node_id, solomon_ssd1309fb),              \
565 		.sh1106_compatible = DT_NODE_HAS_COMPAT(node_id, sinowealth_sh1106),               \
566 		.ready_time_ms = DT_PROP(node_id, ready_time_ms),                                  \
567 		.use_internal_iref = DT_PROP(node_id, use_internal_iref),                          \
568 		COND_CODE_1(DT_ON_BUS(node_id, spi), (SSD1306_CONFIG_SPI(node_id)),                \
569 			    (SSD1306_CONFIG_I2C(node_id)))                                         \
570 	};                                                                                         \
571                                                                                                    \
572 	DEVICE_DT_DEFINE(node_id, ssd1306_init, NULL, &data##node_id, &config##node_id,            \
573 			 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1306_driver_api);
574 
575 DT_FOREACH_STATUS_OKAY(solomon_ssd1306fb, SSD1306_DEFINE)
576 DT_FOREACH_STATUS_OKAY(solomon_ssd1309fb, SSD1306_DEFINE)
577 DT_FOREACH_STATUS_OKAY(sinowealth_sh1106, SSD1306_DEFINE)
578