1 /**
2 * Copyright (c) 2023 Mr Beam Lasers GmbH.
3 * Copyright (c) 2023 Amrith Venkat Kesavamoorthi <amrith@mr-beam.org>
4 * Copyright (c) 2023 Martin Kiepfer <mrmarteng@teleschirm.org>
5 * SPDX-License-Identifier: Apache-2.0
6 */
7 #define DT_DRV_COMPAT galaxycore_gc9x01x
8
9 #include "display_gc9x01x.h"
10
11 #include <zephyr/dt-bindings/display/panel.h>
12 #include <zephyr/drivers/display.h>
13 #include <zephyr/drivers/mipi_dbi.h>
14 #include <zephyr/pm/device.h>
15 #include <zephyr/sys/util.h>
16 #include <zephyr/sys/byteorder.h>
17
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(display_gc9x01x, CONFIG_DISPLAY_LOG_LEVEL);
20
21 /* Maximum number of default init registers */
22 #define GC9X01X_NUM_DEFAULT_INIT_REGS 12U
23
24 /* Display data struct */
25 struct gc9x01x_data {
26 uint8_t bytes_per_pixel;
27 enum display_pixel_format pixel_format;
28 enum display_orientation orientation;
29 };
30
31 /* Configuration data struct.*/
32 struct gc9x01x_config {
33 const struct device *mipi_dev;
34 struct mipi_dbi_config dbi_config;
35 uint8_t pixel_format;
36 uint16_t orientation;
37 uint16_t x_resolution;
38 uint16_t y_resolution;
39 bool inversion;
40 const void *regs;
41 };
42
43 /* Initialization command data struct */
44 struct gc9x01x_default_init_regs {
45 uint8_t cmd;
46 uint8_t len;
47 uint8_t data[GC9X01X_NUM_DEFAULT_INIT_REGS];
48 };
49
50 /*
51 * Default initialization commands. There are a lot of undocumented commands
52 * within the manufacturer sample code, that are essential for proper operation of
53 * the display controller
54 */
55 static const struct gc9x01x_default_init_regs default_init_regs[] = {
56 {
57 .cmd = 0xEBU,
58 .len = 1U,
59 .data = {0x14U},
60 },
61 {
62 .cmd = 0x84U,
63 .len = 1U,
64 .data = {0x40U},
65 },
66 {
67 .cmd = 0x85U,
68 .len = 1U,
69 .data = {0xFFU},
70 },
71 {
72 .cmd = 0x86U,
73 .len = 1U,
74 .data = {0xFFU},
75 },
76 {
77 .cmd = 0x87U,
78 .len = 1U,
79 .data = {0xFFU},
80 },
81 {
82 .cmd = 0x88U,
83 .len = 1U,
84 .data = {0x0AU},
85 },
86 {
87 .cmd = 0x89U,
88 .len = 1U,
89 .data = {0x21U},
90 },
91 {
92 .cmd = 0x8AU,
93 .len = 1U,
94 .data = {0x00U},
95 },
96 {
97 .cmd = 0x8BU,
98 .len = 1U,
99 .data = {0x80U},
100 },
101 {
102 .cmd = 0x8CU,
103 .len = 1U,
104 .data = {0x01U},
105 },
106 {
107 .cmd = 0x8DU,
108 .len = 1U,
109 .data = {0x01U},
110 },
111 {
112 .cmd = 0x8EU,
113 .len = 1U,
114 .data = {0xFFU},
115 },
116 {
117 .cmd = 0x8FU,
118 .len = 1U,
119 .data = {0xFFU},
120 },
121 {
122 .cmd = 0xB6U,
123 .len = 2U,
124 .data = {0x00U, 0x20U},
125 },
126 {
127 .cmd = 0x90U,
128 .len = 4U,
129 .data = {0x08U, 0x08U, 0x08U, 0x08U},
130 },
131 {
132 .cmd = 0xBDU,
133 .len = 1U,
134 .data = {0x06U},
135 },
136 {
137 .cmd = 0xBCU,
138 .len = 1U,
139 .data = {0x00U},
140 },
141 {
142 .cmd = 0xFFU,
143 .len = 3U,
144 .data = {0x60U, 0x01U, 0x04U},
145 },
146 {
147 .cmd = 0xBEU,
148 .len = 1U,
149 .data = {0x11U},
150 },
151 {
152 .cmd = 0xE1U,
153 .len = 2U,
154 .data = {0x10U, 0x0EU},
155 },
156 {
157 .cmd = 0xDFU,
158 .len = 3U,
159 .data = {0x21U, 0x0CU, 0x02U},
160 },
161 {
162 .cmd = 0xEDU,
163 .len = 2U,
164 .data = {0x1BU, 0x0BU},
165 },
166 {
167 .cmd = 0xAEU,
168 .len = 1U,
169 .data = {0x77U},
170 },
171 {
172 .cmd = 0xCDU,
173 .len = 1U,
174 .data = {0x63U},
175 },
176 {
177 .cmd = 0x70U,
178 .len = 9U,
179 .data = {0x07U, 0x07U, 0x04U, 0x0EU, 0x0FU, 0x09U, 0x07U, 0x08U, 0x03U},
180 },
181 {
182 .cmd = 0x62U,
183 .len = 12U,
184 .data = {0x18U, 0x0DU, 0x71U, 0xEDU, 0x70U, 0x70U, 0x18U, 0x0FU, 0x71U, 0xEFU,
185 0x70U, 0x70U},
186 },
187 {
188 .cmd = 0x63U,
189 .len = 12U,
190 .data = {0x18U, 0x11U, 0x71U, 0xF1U, 0x70U, 0x70U, 0x18U, 0x13U, 0x71U, 0xF3U,
191 0x70U, 0x70U},
192 },
193 {
194 .cmd = 0x64U,
195 .len = 7U,
196 .data = {0x28U, 0x29U, 0xF1U, 0x01U, 0xF1U, 0x00U, 0x07U},
197 },
198 {
199 .cmd = 0x66U,
200 .len = 10U,
201 .data = {0x3CU, 0x00U, 0xCDU, 0x67U, 0x45U, 0x45U, 0x10U, 0x00U, 0x00U, 0x00U},
202 },
203 {
204 .cmd = 0x67U,
205 .len = 10U,
206 .data = {0x00U, 0x3CU, 0x00U, 0x00U, 0x00U, 0x01U, 0x54U, 0x10U, 0x32U, 0x98U},
207 },
208 {
209 .cmd = 0x74U,
210 .len = 7U,
211 .data = {0x10U, 0x85U, 0x80U, 0x00U, 0x00U, 0x4EU, 0x00U},
212 },
213 {
214 .cmd = 0x98U,
215 .len = 2U,
216 .data = {0x3EU, 0x07U},
217 },
218 };
219
gc9x01x_transmit(const struct device * dev,uint8_t cmd,const void * tx_data,size_t tx_len)220 static int gc9x01x_transmit(const struct device *dev, uint8_t cmd, const void *tx_data,
221 size_t tx_len)
222 {
223 const struct gc9x01x_config *config = dev->config;
224
225 return mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
226 cmd, tx_data, tx_len);
227 }
228
gc9x01x_regs_init(const struct device * dev)229 static int gc9x01x_regs_init(const struct device *dev)
230 {
231 const struct gc9x01x_config *config = dev->config;
232 const struct gc9x01x_regs *regs = config->regs;
233 int ret;
234
235 if (!device_is_ready(config->mipi_dev)) {
236 return -ENODEV;
237 }
238
239 /* Enable inter-command mode */
240 ret = gc9x01x_transmit(dev, GC9X01X_CMD_INREGEN1, NULL, 0);
241 if (ret < 0) {
242 return ret;
243 }
244 ret = gc9x01x_transmit(dev, GC9X01X_CMD_INREGEN2, NULL, 0);
245 if (ret < 0) {
246 return ret;
247 }
248
249 /* Apply default init sequence */
250 for (int i = 0; (i < ARRAY_SIZE(default_init_regs)) && (ret == 0); i++) {
251 ret = gc9x01x_transmit(dev, default_init_regs[i].cmd, default_init_regs[i].data,
252 default_init_regs[i].len);
253 if (ret < 0) {
254 return ret;
255 }
256 }
257
258 /* Apply generic configuration */
259 ret = gc9x01x_transmit(dev, GC9X01X_CMD_PWRCTRL2, regs->pwrctrl2, sizeof(regs->pwrctrl2));
260 if (ret < 0) {
261 return ret;
262 }
263 ret = gc9x01x_transmit(dev, GC9X01X_CMD_PWRCTRL3, regs->pwrctrl3, sizeof(regs->pwrctrl3));
264 if (ret < 0) {
265 return ret;
266 }
267 ret = gc9x01x_transmit(dev, GC9X01X_CMD_PWRCTRL4, regs->pwrctrl4, sizeof(regs->pwrctrl4));
268 if (ret < 0) {
269 return ret;
270 }
271 ret = gc9x01x_transmit(dev, GC9X01X_CMD_GAMMA1, regs->gamma1, sizeof(regs->gamma1));
272 if (ret < 0) {
273 return ret;
274 }
275 ret = gc9x01x_transmit(dev, GC9X01X_CMD_GAMMA2, regs->gamma2, sizeof(regs->gamma2));
276 if (ret < 0) {
277 return ret;
278 }
279 ret = gc9x01x_transmit(dev, GC9X01X_CMD_GAMMA3, regs->gamma3, sizeof(regs->gamma3));
280 if (ret < 0) {
281 return ret;
282 }
283 ret = gc9x01x_transmit(dev, GC9X01X_CMD_GAMMA4, regs->gamma4, sizeof(regs->gamma4));
284 if (ret < 0) {
285 return ret;
286 }
287 ret = gc9x01x_transmit(dev, GC9X01X_CMD_FRAMERATE, regs->framerate,
288 sizeof(regs->framerate));
289 if (ret < 0) {
290 return ret;
291 }
292
293 /* Enable Tearing line */
294 ret = gc9x01x_transmit(dev, GC9X01X_CMD_TEON, NULL, 0);
295 if (ret < 0) {
296 return ret;
297 }
298
299 return 0;
300 }
301
gc9x01x_exit_sleep(const struct device * dev)302 static int gc9x01x_exit_sleep(const struct device *dev)
303 {
304 int ret;
305
306 ret = gc9x01x_transmit(dev, GC9X01X_CMD_SLPOUT, NULL, 0);
307 if (ret < 0) {
308 return ret;
309 }
310
311 /*
312 * Exit sleepmode and enable display. 30ms on top of the sleepout time to account for
313 * any manufacturing defects.
314 * This is to allow time for the supply voltages and clock circuits stabilize
315 */
316 k_msleep(GC9X01X_SLEEP_IN_OUT_DURATION_MS + 30);
317
318 return 0;
319 }
320
321 #ifdef CONFIG_PM_DEVICE
gc9x01x_enter_sleep(const struct device * dev)322 static int gc9x01x_enter_sleep(const struct device *dev)
323 {
324 int ret;
325
326 ret = gc9x01x_transmit(dev, GC9X01X_CMD_SLPIN, NULL, 0);
327 if (ret < 0) {
328 return ret;
329 }
330
331 /*
332 * Exit sleepmode and enable display. 30ms on top of the sleepout time to account for
333 * any manufacturing defects.
334 */
335 k_msleep(GC9X01X_SLEEP_IN_OUT_DURATION_MS + 30);
336
337 return 0;
338 }
339 #endif
340
gc9x01x_hw_reset(const struct device * dev)341 static int gc9x01x_hw_reset(const struct device *dev)
342 {
343 const struct gc9x01x_config *config = dev->config;
344 int ret;
345
346 ret = mipi_dbi_reset(config->mipi_dev, 100);
347 if (ret < 0) {
348 return ret;
349 }
350 k_msleep(10);
351
352 return ret;
353 }
354
gc9x01x_display_blanking_off(const struct device * dev)355 static int gc9x01x_display_blanking_off(const struct device *dev)
356 {
357 LOG_DBG("Turning display blanking off");
358 return gc9x01x_transmit(dev, GC9X01X_CMD_DISPON, NULL, 0);
359 }
360
gc9x01x_display_blanking_on(const struct device * dev)361 static int gc9x01x_display_blanking_on(const struct device *dev)
362 {
363 LOG_DBG("Turning display blanking on");
364 return gc9x01x_transmit(dev, GC9X01X_CMD_DISPOFF, NULL, 0);
365 }
366
gc9x01x_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)367 static int gc9x01x_set_pixel_format(const struct device *dev,
368 const enum display_pixel_format pixel_format)
369 {
370 struct gc9x01x_data *data = dev->data;
371 int ret;
372 uint8_t tx_data;
373 uint8_t bytes_per_pixel;
374
375 if (pixel_format == PIXEL_FORMAT_RGB_565) {
376 bytes_per_pixel = 2U;
377 tx_data = GC9X01X_PIXFMT_VAL_MCU_16_BIT | GC9X01X_PIXFMT_VAL_RGB_16_BIT;
378 } else if (pixel_format == PIXEL_FORMAT_RGB_888) {
379 bytes_per_pixel = 3U;
380 tx_data = GC9X01X_PIXFMT_VAL_MCU_18_BIT | GC9X01X_PIXFMT_VAL_RGB_18_BIT;
381 } else {
382 LOG_ERR("Unsupported pixel format");
383 return -ENOTSUP;
384 }
385
386 ret = gc9x01x_transmit(dev, GC9X01X_CMD_PIXFMT, &tx_data, 1U);
387 if (ret < 0) {
388 return ret;
389 }
390
391 data->pixel_format = pixel_format;
392 data->bytes_per_pixel = bytes_per_pixel;
393
394 return 0;
395 }
396
gc9x01x_set_orientation(const struct device * dev,const enum display_orientation orientation)397 static int gc9x01x_set_orientation(const struct device *dev,
398 const enum display_orientation orientation)
399 {
400 struct gc9x01x_data *data = dev->data;
401 int ret;
402 uint8_t tx_data = GC9X01X_MADCTL_VAL_BGR;
403
404 if (orientation == DISPLAY_ORIENTATION_NORMAL) {
405 /* works 0° - default */
406 } else if (orientation == DISPLAY_ORIENTATION_ROTATED_90) {
407 /* works CW 90° */
408 tx_data |= GC9X01X_MADCTL_VAL_MV | GC9X01X_MADCTL_VAL_MY;
409 } else if (orientation == DISPLAY_ORIENTATION_ROTATED_180) {
410 /* works CW 180° */
411 tx_data |= GC9X01X_MADCTL_VAL_MY | GC9X01X_MADCTL_VAL_MX | GC9X01X_MADCTL_VAL_MH;
412 } else if (orientation == DISPLAY_ORIENTATION_ROTATED_270) {
413 /* works CW 270° */
414 tx_data |= GC9X01X_MADCTL_VAL_MV | GC9X01X_MADCTL_VAL_MX;
415 }
416
417 ret = gc9x01x_transmit(dev, GC9X01X_CMD_MADCTL, &tx_data, 1U);
418 if (ret < 0) {
419 return ret;
420 }
421
422 data->orientation = orientation;
423
424 return 0;
425 }
426
gc9x01x_configure(const struct device * dev)427 static int gc9x01x_configure(const struct device *dev)
428 {
429 const struct gc9x01x_config *config = dev->config;
430 int ret;
431
432 /* Set all the required registers. */
433 ret = gc9x01x_regs_init(dev);
434 if (ret < 0) {
435 return ret;
436 }
437
438 /* Pixel format */
439 ret = gc9x01x_set_pixel_format(dev, config->pixel_format);
440 if (ret < 0) {
441 return ret;
442 }
443
444 /* Orientation */
445 ret = gc9x01x_set_orientation(dev, config->orientation);
446 if (ret < 0) {
447 return ret;
448 }
449
450 /* Display inversion mode. */
451 if (config->inversion) {
452 ret = gc9x01x_transmit(dev, GC9X01X_CMD_INVON, NULL, 0);
453 if (ret < 0) {
454 return ret;
455 }
456 }
457
458 return 0;
459 }
460
gc9x01x_init(const struct device * dev)461 static int gc9x01x_init(const struct device *dev)
462 {
463 int ret;
464
465 gc9x01x_hw_reset(dev);
466
467 gc9x01x_display_blanking_on(dev);
468
469 ret = gc9x01x_configure(dev);
470 if (ret < 0) {
471 LOG_ERR("Could not configure display (%d)", ret);
472 return ret;
473 }
474
475 ret = gc9x01x_exit_sleep(dev);
476 if (ret < 0) {
477 LOG_ERR("Could not exit sleep mode (%d)", ret);
478 return ret;
479 }
480
481 return 0;
482 }
483
gc9x01x_set_mem_area(const struct device * dev,const uint16_t x,const uint16_t y,const uint16_t w,const uint16_t h)484 static int gc9x01x_set_mem_area(const struct device *dev, const uint16_t x, const uint16_t y,
485 const uint16_t w, const uint16_t h)
486 {
487 int ret;
488 uint16_t spi_data[2];
489
490 spi_data[0] = sys_cpu_to_be16(x);
491 spi_data[1] = sys_cpu_to_be16(x + w - 1U);
492 ret = gc9x01x_transmit(dev, GC9X01X_CMD_COLSET, &spi_data[0], 4U);
493 if (ret < 0) {
494 return ret;
495 }
496
497 spi_data[0] = sys_cpu_to_be16(y);
498 spi_data[1] = sys_cpu_to_be16(y + h - 1U);
499 ret = gc9x01x_transmit(dev, GC9X01X_CMD_ROWSET, &spi_data[0], 4U);
500 if (ret < 0) {
501 return ret;
502 }
503
504 return 0;
505 }
506
gc9x01x_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)507 static int gc9x01x_write(const struct device *dev, const uint16_t x, const uint16_t y,
508 const struct display_buffer_descriptor *desc, const void *buf)
509 {
510 const struct gc9x01x_config *config = dev->config;
511 struct gc9x01x_data *data = dev->data;
512 int ret;
513 const uint8_t *write_data_start = (const uint8_t *)buf;
514 struct display_buffer_descriptor mipi_desc;
515 uint16_t write_cnt;
516 uint16_t nbr_of_writes;
517 uint16_t write_h;
518
519 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width");
520 __ASSERT((desc->pitch * data->bytes_per_pixel * desc->height) <= desc->buf_size,
521 "Input buffer to small");
522
523 LOG_DBG("Writing %dx%d (w,h) @ %dx%d (x,y)", desc->width, desc->height, x, y);
524 ret = gc9x01x_set_mem_area(dev, x, y, desc->width, desc->height);
525 if (ret < 0) {
526 return ret;
527 }
528
529 if (desc->pitch > desc->width) {
530 write_h = 1U;
531 nbr_of_writes = desc->height;
532 mipi_desc.height = 1;
533 mipi_desc.buf_size = desc->pitch * data->bytes_per_pixel;
534 } else {
535 write_h = desc->height;
536 mipi_desc.height = desc->height;
537 mipi_desc.buf_size = desc->width * data->bytes_per_pixel * write_h;
538 nbr_of_writes = 1U;
539 }
540
541 mipi_desc.width = desc->width;
542 /* Per MIPI API, pitch must always match width */
543 mipi_desc.pitch = desc->width;
544
545 ret = gc9x01x_transmit(dev, GC9X01X_CMD_MEMWR, NULL, 0);
546 if (ret < 0) {
547 return ret;
548 }
549
550 for (write_cnt = 0U; write_cnt < nbr_of_writes; ++write_cnt) {
551 ret = mipi_dbi_write_display(config->mipi_dev,
552 &config->dbi_config,
553 write_data_start,
554 &mipi_desc,
555 data->pixel_format);
556 if (ret < 0) {
557 return ret;
558 }
559
560 write_data_start += desc->pitch * data->bytes_per_pixel;
561 }
562
563 return 0;
564 }
565
gc9x01x_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)566 static void gc9x01x_get_capabilities(const struct device *dev,
567 struct display_capabilities *capabilities)
568 {
569 struct gc9x01x_data *data = dev->data;
570 const struct gc9x01x_config *config = dev->config;
571
572 memset(capabilities, 0, sizeof(struct display_capabilities));
573
574 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_RGB_888;
575 capabilities->current_pixel_format = data->pixel_format;
576
577 if (data->orientation == DISPLAY_ORIENTATION_NORMAL ||
578 data->orientation == DISPLAY_ORIENTATION_ROTATED_180) {
579 capabilities->x_resolution = config->x_resolution;
580 capabilities->y_resolution = config->y_resolution;
581 } else {
582 capabilities->x_resolution = config->y_resolution;
583 capabilities->y_resolution = config->x_resolution;
584 }
585
586 capabilities->current_orientation = data->orientation;
587 }
588
589 #ifdef CONFIG_PM_DEVICE
gc9x01x_pm_action(const struct device * dev,enum pm_device_action action)590 static int gc9x01x_pm_action(const struct device *dev, enum pm_device_action action)
591 {
592 int ret;
593
594 switch (action) {
595 case PM_DEVICE_ACTION_RESUME:
596 ret = gc9x01x_exit_sleep(dev);
597 break;
598 case PM_DEVICE_ACTION_SUSPEND:
599 ret = gc9x01x_enter_sleep(dev);
600 break;
601 default:
602 ret = -ENOTSUP;
603 break;
604 }
605
606 return ret;
607 }
608 #endif /* CONFIG_PM_DEVICE */
609
610 /* Device driver API*/
611 static const struct display_driver_api gc9x01x_api = {
612 .blanking_on = gc9x01x_display_blanking_on,
613 .blanking_off = gc9x01x_display_blanking_off,
614 .write = gc9x01x_write,
615 .get_capabilities = gc9x01x_get_capabilities,
616 .set_pixel_format = gc9x01x_set_pixel_format,
617 .set_orientation = gc9x01x_set_orientation,
618 };
619
620 #define GC9X01X_INIT(inst) \
621 GC9X01X_REGS_INIT(inst); \
622 static const struct gc9x01x_config gc9x01x_config_##inst = { \
623 .mipi_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
624 .dbi_config = { \
625 .mode = MIPI_DBI_MODE_SPI_4WIRE, \
626 .config = MIPI_DBI_SPI_CONFIG_DT_INST(inst, \
627 SPI_OP_MODE_MASTER | \
628 SPI_WORD_SET(8), 0), \
629 }, \
630 .pixel_format = DT_INST_PROP(inst, pixel_format), \
631 .orientation = DT_INST_ENUM_IDX(inst, orientation), \
632 .x_resolution = DT_INST_PROP(inst, width), \
633 .y_resolution = DT_INST_PROP(inst, height), \
634 .inversion = DT_INST_PROP(inst, display_inversion), \
635 .regs = &gc9x01x_regs_##inst, \
636 }; \
637 static struct gc9x01x_data gc9x01x_data_##inst; \
638 PM_DEVICE_DT_INST_DEFINE(inst, gc9x01x_pm_action); \
639 DEVICE_DT_INST_DEFINE(inst, &gc9x01x_init, PM_DEVICE_DT_INST_GET(inst), \
640 &gc9x01x_data_##inst, &gc9x01x_config_##inst, POST_KERNEL, \
641 CONFIG_DISPLAY_INIT_PRIORITY, &gc9x01x_api);
642
643 DT_INST_FOREACH_STATUS_OKAY(GC9X01X_INIT)
644