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