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