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