1 /*
2 * Copyright (c) 2017 Jan Van Winkel <jan.van_winkel@dxplore.eu>
3 * Copyright (c) 2019 Nordic Semiconductor ASA
4 * Copyright (c) 2019 Marc Reilly
5 * Copyright (c) 2019 PHYTEC Messtechnik GmbH
6 * Copyright (c) 2020 Endian Technologies AB
7 * Copyright (c) 2022 Basalte bv
8 *
9 * SPDX-License-Identifier: Apache-2.0
10 */
11
12 #define DT_DRV_COMPAT sitronix_st7789v
13
14 #include "display_st7789v.h"
15
16 #include <zephyr/device.h>
17 #include <zephyr/drivers/mipi_dbi.h>
18 #include <zephyr/pm/device.h>
19 #include <zephyr/sys/byteorder.h>
20 #include <zephyr/drivers/display.h>
21
22 #define LOG_LEVEL CONFIG_DISPLAY_LOG_LEVEL
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(display_st7789v);
25
26 struct st7789v_config {
27 const struct device *mipi_dbi;
28 const struct mipi_dbi_config dbi_config;
29 uint8_t vcom;
30 uint8_t gctrl;
31 bool vdv_vrh_enable;
32 uint8_t vrh_value;
33 uint8_t vdv_value;
34 uint8_t mdac;
35 uint8_t gamma;
36 uint8_t colmod;
37 uint8_t lcm;
38 bool inversion_on;
39 uint8_t porch_param[5];
40 uint8_t cmd2en_param[4];
41 uint8_t pwctrl1_param[2];
42 uint8_t pvgam_param[14];
43 uint8_t nvgam_param[14];
44 uint8_t ram_param[2];
45 uint8_t rgb_param[3];
46 uint16_t height;
47 uint16_t width;
48 uint8_t ready_time_ms;
49 };
50
51 struct st7789v_data {
52 uint16_t x_offset;
53 uint16_t y_offset;
54 };
55
56 #ifdef CONFIG_ST7789V_RGB888
57 #define ST7789V_PIXEL_SIZE 3u
58 #else
59 #define ST7789V_PIXEL_SIZE 2u
60 #endif
61
st7789v_set_lcd_margins(const struct device * dev,uint16_t x_offset,uint16_t y_offset)62 static void st7789v_set_lcd_margins(const struct device *dev,
63 uint16_t x_offset, uint16_t y_offset)
64 {
65 struct st7789v_data *data = dev->data;
66
67 data->x_offset = x_offset;
68 data->y_offset = y_offset;
69 }
70
st7789v_transmit(const struct device * dev,uint8_t cmd,uint8_t * tx_data,size_t tx_count)71 static int st7789v_transmit(const struct device *dev, uint8_t cmd,
72 uint8_t *tx_data, size_t tx_count)
73 {
74 const struct st7789v_config *config = dev->config;
75
76 return mipi_dbi_command_write(config->mipi_dbi, &config->dbi_config,
77 cmd, tx_data, tx_count);
78 }
79
st7789v_exit_sleep(const struct device * dev)80 static int st7789v_exit_sleep(const struct device *dev)
81 {
82 int ret;
83
84 ret = st7789v_transmit(dev, ST7789V_CMD_SLEEP_OUT, NULL, 0);
85 if (ret < 0) {
86 return ret;
87 }
88
89 k_sleep(K_MSEC(120));
90 return ret;
91 }
92
st7789v_reset_display(const struct device * dev)93 static int st7789v_reset_display(const struct device *dev)
94 {
95 const struct st7789v_config *config = dev->config;
96 int ret;
97
98 LOG_DBG("Resetting display");
99
100 k_sleep(K_MSEC(1));
101 ret = mipi_dbi_reset(config->mipi_dbi, 6);
102 if (ret == -ENOTSUP) {
103 /* Send software reset command */
104 ret = st7789v_transmit(dev, ST7789V_CMD_SW_RESET, NULL, 0);
105 if (ret < 0) {
106 return ret;
107 }
108 k_sleep(K_MSEC(5));
109 } else {
110 k_sleep(K_MSEC(20));
111 }
112
113 return ret;
114 }
115
st7789v_blanking_on(const struct device * dev)116 static int st7789v_blanking_on(const struct device *dev)
117 {
118 return st7789v_transmit(dev, ST7789V_CMD_DISP_OFF, NULL, 0);
119 }
120
st7789v_blanking_off(const struct device * dev)121 static int st7789v_blanking_off(const struct device *dev)
122 {
123 return st7789v_transmit(dev, ST7789V_CMD_DISP_ON, NULL, 0);
124 }
125
st7789v_set_mem_area(const struct device * dev,const uint16_t x,const uint16_t y,const uint16_t w,const uint16_t h)126 static int st7789v_set_mem_area(const struct device *dev, const uint16_t x,
127 const uint16_t y, const uint16_t w, const uint16_t h)
128 {
129 struct st7789v_data *data = dev->data;
130 uint16_t spi_data[2];
131
132 uint16_t ram_x = x + data->x_offset;
133 uint16_t ram_y = y + data->y_offset;
134
135 int ret;
136
137 spi_data[0] = sys_cpu_to_be16(ram_x);
138 spi_data[1] = sys_cpu_to_be16(ram_x + w - 1);
139 ret = st7789v_transmit(dev, ST7789V_CMD_CASET, (uint8_t *)&spi_data[0], 4);
140 if (ret < 0) {
141 return ret;
142 }
143
144 spi_data[0] = sys_cpu_to_be16(ram_y);
145 spi_data[1] = sys_cpu_to_be16(ram_y + h - 1);
146 return st7789v_transmit(dev, ST7789V_CMD_RASET, (uint8_t *)&spi_data[0], 4);
147 }
148
st7789v_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)149 static int st7789v_write(const struct device *dev,
150 const uint16_t x,
151 const uint16_t y,
152 const struct display_buffer_descriptor *desc,
153 const void *buf)
154 {
155 const struct st7789v_config *config = dev->config;
156 struct display_buffer_descriptor mipi_desc;
157 const uint8_t *write_data_start = (uint8_t *) buf;
158 uint16_t nbr_of_writes;
159 uint16_t write_h;
160 enum display_pixel_format pixfmt;
161 int ret;
162
163 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width");
164 __ASSERT((desc->pitch * ST7789V_PIXEL_SIZE * desc->height) <= desc->buf_size,
165 "Input buffer too small");
166
167 LOG_DBG("Writing %dx%d (w,h) @ %dx%d (x,y)",
168 desc->width, desc->height, x, y);
169 ret = st7789v_set_mem_area(dev, x, y, desc->width, desc->height);
170 if (ret < 0) {
171 return ret;
172 }
173
174 if (desc->pitch > desc->width) {
175 write_h = 1U;
176 nbr_of_writes = desc->height;
177 mipi_desc.height = 1;
178 mipi_desc.buf_size = desc->pitch * ST7789V_PIXEL_SIZE;
179 } else {
180 write_h = desc->height;
181 nbr_of_writes = 1U;
182 mipi_desc.height = desc->height;
183 mipi_desc.buf_size = desc->width * write_h * ST7789V_PIXEL_SIZE;
184 }
185 if (IS_ENABLED(CONFIG_ST7789V_RGB565)) {
186 pixfmt = PIXEL_FORMAT_RGB_565;
187 } else if (IS_ENABLED(CONFIG_ST7789V_BGR565)) {
188 pixfmt = PIXEL_FORMAT_BGR_565;
189 } else {
190 pixfmt = PIXEL_FORMAT_RGB_888;
191 }
192
193 mipi_desc.width = desc->width;
194 /* Per MIPI API, pitch must always match width */
195 mipi_desc.pitch = desc->width;
196
197 /* Send RAMWR command */
198 ret = st7789v_transmit(dev, ST7789V_CMD_RAMWR, NULL, 0);
199 if (ret < 0) {
200 return ret;
201 }
202
203 for (uint16_t write_cnt = 0U; write_cnt < nbr_of_writes; ++write_cnt) {
204 ret = mipi_dbi_write_display(config->mipi_dbi, &config->dbi_config,
205 write_data_start, &mipi_desc, pixfmt);
206 if (ret < 0) {
207 return ret;
208 }
209
210 write_data_start += (desc->pitch * ST7789V_PIXEL_SIZE);
211 }
212
213 return ret;
214 }
215
st7789v_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)216 static void st7789v_get_capabilities(const struct device *dev,
217 struct display_capabilities *capabilities)
218 {
219 const struct st7789v_config *config = dev->config;
220
221 memset(capabilities, 0, sizeof(struct display_capabilities));
222 capabilities->x_resolution = config->width;
223 capabilities->y_resolution = config->height;
224
225 #ifdef CONFIG_ST7789V_RGB565
226 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565;
227 capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565;
228 #elif CONFIG_ST7789V_BGR565
229 capabilities->supported_pixel_formats = PIXEL_FORMAT_BGR_565;
230 capabilities->current_pixel_format = PIXEL_FORMAT_BGR_565;
231 #else
232 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_888;
233 capabilities->current_pixel_format = PIXEL_FORMAT_RGB_888;
234 #endif
235 capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
236 }
237
st7789v_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)238 static int st7789v_set_pixel_format(const struct device *dev,
239 const enum display_pixel_format pixel_format)
240 {
241 #ifdef CONFIG_ST7789V_RGB565
242 if (pixel_format == PIXEL_FORMAT_RGB_565) {
243 #elif CONFIG_ST7789V_BGR565
244 if (pixel_format == PIXEL_FORMAT_BGR_565) {
245 #else
246 if (pixel_format == PIXEL_FORMAT_RGB_888) {
247 #endif
248 return 0;
249 }
250 LOG_ERR("Pixel format change not implemented");
251 return -ENOTSUP;
252 }
253
254 static int st7789v_set_orientation(const struct device *dev,
255 const enum display_orientation orientation)
256 {
257 if (orientation == DISPLAY_ORIENTATION_NORMAL) {
258 return 0;
259 }
260 LOG_ERR("Changing display orientation not implemented");
261 return -ENOTSUP;
262 }
263
264 static int st7789v_lcd_init(const struct device *dev)
265 {
266 struct st7789v_data *data = dev->data;
267 const struct st7789v_config *config = dev->config;
268 uint8_t tmp;
269 int ret;
270
271 st7789v_set_lcd_margins(dev, data->x_offset,
272 data->y_offset);
273
274 ret = st7789v_transmit(dev, ST7789V_CMD_CMD2EN,
275 (uint8_t *)config->cmd2en_param,
276 sizeof(config->cmd2en_param));
277 if (ret < 0) {
278 return ret;
279 }
280
281 ret = st7789v_transmit(dev, ST7789V_CMD_PORCTRL,
282 (uint8_t *)config->porch_param,
283 sizeof(config->porch_param));
284 if (ret < 0) {
285 return ret;
286 }
287
288 /* Digital Gamma Enable, default disabled */
289 tmp = 0x00;
290 ret = st7789v_transmit(dev, ST7789V_CMD_DGMEN, &tmp, 1);
291 if (ret < 0) {
292 return ret;
293 }
294
295 /* Frame Rate Control in Normal Mode, default value */
296 tmp = 0x0f;
297 ret = st7789v_transmit(dev, ST7789V_CMD_FRCTRL2, &tmp, 1);
298 if (ret < 0) {
299 return ret;
300 }
301
302 tmp = config->gctrl;
303 ret = st7789v_transmit(dev, ST7789V_CMD_GCTRL, &tmp, 1);
304 if (ret < 0) {
305 return ret;
306 }
307
308 tmp = config->vcom;
309 ret = st7789v_transmit(dev, ST7789V_CMD_VCOMS, &tmp, 1);
310 if (ret < 0) {
311 return ret;
312 }
313
314 if (config->vdv_vrh_enable) {
315 tmp = 0x01;
316 ret = st7789v_transmit(dev, ST7789V_CMD_VDVVRHEN, &tmp, 1);
317 if (ret < 0) {
318 return ret;
319 }
320
321 tmp = config->vrh_value;
322 ret = st7789v_transmit(dev, ST7789V_CMD_VRH, &tmp, 1);
323 if (ret < 0) {
324 return ret;
325 }
326
327 tmp = config->vdv_value;
328 ret = st7789v_transmit(dev, ST7789V_CMD_VDS, &tmp, 1);
329 if (ret < 0) {
330 return ret;
331 }
332 }
333
334 ret = st7789v_transmit(dev, ST7789V_CMD_PWCTRL1,
335 (uint8_t *)config->pwctrl1_param,
336 sizeof(config->pwctrl1_param));
337 if (ret < 0) {
338 return ret;
339 }
340
341 /* Memory Data Access Control */
342 tmp = config->mdac;
343 ret = st7789v_transmit(dev, ST7789V_CMD_MADCTL, &tmp, 1);
344 if (ret < 0) {
345 return ret;
346 }
347
348 /* Interface Pixel Format */
349 tmp = config->colmod;
350 ret = st7789v_transmit(dev, ST7789V_CMD_COLMOD, &tmp, 1);
351 if (ret < 0) {
352 return ret;
353 }
354
355 tmp = config->lcm;
356 ret = st7789v_transmit(dev, ST7789V_CMD_LCMCTRL, &tmp, 1);
357 if (ret < 0) {
358 return ret;
359 }
360
361 tmp = config->gamma;
362 ret = st7789v_transmit(dev, ST7789V_CMD_GAMSET, &tmp, 1);
363 if (ret < 0) {
364 return ret;
365 }
366
367 if (config->inversion_on) {
368 ret = st7789v_transmit(dev, ST7789V_CMD_INV_ON, NULL, 0);
369 } else {
370 ret = st7789v_transmit(dev, ST7789V_CMD_INV_OFF, NULL, 0);
371 }
372 if (ret < 0) {
373 return ret;
374 }
375
376 ret = st7789v_transmit(dev, ST7789V_CMD_PVGAMCTRL,
377 (uint8_t *)config->pvgam_param,
378 sizeof(config->pvgam_param));
379 if (ret < 0) {
380 return ret;
381 }
382
383 ret = st7789v_transmit(dev, ST7789V_CMD_NVGAMCTRL,
384 (uint8_t *)config->nvgam_param,
385 sizeof(config->nvgam_param));
386 if (ret < 0) {
387 return ret;
388 }
389
390 ret = st7789v_transmit(dev, ST7789V_CMD_RAMCTRL,
391 (uint8_t *)config->ram_param,
392 sizeof(config->ram_param));
393 if (ret < 0) {
394 return ret;
395 }
396
397 ret = st7789v_transmit(dev, ST7789V_CMD_RGBCTRL,
398 (uint8_t *)config->rgb_param,
399 sizeof(config->rgb_param));
400 return ret;
401 }
402
403 static int st7789v_init(const struct device *dev)
404 {
405 const struct st7789v_config *config = dev->config;
406 int ret;
407
408 if (!device_is_ready(config->mipi_dbi)) {
409 LOG_ERR("MIPI DBI device not ready");
410 return -ENODEV;
411 }
412
413 k_sleep(K_TIMEOUT_ABS_MS(config->ready_time_ms));
414
415 ret = st7789v_reset_display(dev);
416 if (ret < 0) {
417 LOG_ERR("Failed to reset display (%d)", ret);
418 return ret;
419 }
420
421 ret = st7789v_blanking_on(dev);
422 if (ret < 0) {
423 LOG_ERR("Failed to turn blanking on (%d)", ret);
424 return ret;
425 }
426
427 ret = st7789v_lcd_init(dev);
428 if (ret < 0) {
429 LOG_ERR("Failed to init display (%d)", ret);
430 return ret;
431 }
432
433 ret = st7789v_exit_sleep(dev);
434 if (ret < 0) {
435 LOG_ERR("Failed to exit the sleep mode (%d)", ret);
436 return ret;
437 }
438
439 return ret;
440 }
441
442 #ifdef CONFIG_PM_DEVICE
443 static int st7789v_pm_action(const struct device *dev,
444 enum pm_device_action action)
445 {
446 int ret;
447
448 switch (action) {
449 case PM_DEVICE_ACTION_RESUME:
450 ret = st7789v_exit_sleep(dev);
451 break;
452 case PM_DEVICE_ACTION_SUSPEND:
453 ret = st7789v_transmit(dev, ST7789V_CMD_SLEEP_IN, NULL, 0);
454 break;
455 default:
456 ret = -ENOTSUP;
457 break;
458 }
459
460 return ret;
461 }
462 #endif /* CONFIG_PM_DEVICE */
463
464 static DEVICE_API(display, st7789v_api) = {
465 .blanking_on = st7789v_blanking_on,
466 .blanking_off = st7789v_blanking_off,
467 .write = st7789v_write,
468 .get_capabilities = st7789v_get_capabilities,
469 .set_pixel_format = st7789v_set_pixel_format,
470 .set_orientation = st7789v_set_orientation,
471 };
472
473 #define ST7789V_WORD_SIZE(inst) \
474 ((DT_INST_STRING_UPPER_TOKEN(inst, mipi_mode) == MIPI_DBI_MODE_SPI_4WIRE) ? \
475 SPI_WORD_SET(8) : SPI_WORD_SET(9))
476 #define ST7789V_INIT(inst) \
477 static const struct st7789v_config st7789v_config_ ## inst = { \
478 .mipi_dbi = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
479 .dbi_config = MIPI_DBI_CONFIG_DT_INST(inst, \
480 ST7789V_WORD_SIZE(inst) | \
481 SPI_OP_MODE_MASTER, 0), \
482 .vcom = DT_INST_PROP(inst, vcom), \
483 .gctrl = DT_INST_PROP(inst, gctrl), \
484 .vdv_vrh_enable = (DT_INST_NODE_HAS_PROP(inst, vrhs) \
485 && DT_INST_NODE_HAS_PROP(inst, vdvs)), \
486 .vrh_value = DT_INST_PROP_OR(inst, vrhs, 0), \
487 .vdv_value = DT_INST_PROP_OR(inst, vdvs, 0), \
488 .mdac = DT_INST_PROP(inst, mdac), \
489 .gamma = DT_INST_PROP(inst, gamma), \
490 .colmod = DT_INST_PROP(inst, colmod), \
491 .lcm = DT_INST_PROP(inst, lcm), \
492 .inversion_on = !DT_INST_PROP(inst, inversion_off), \
493 .porch_param = DT_INST_PROP(inst, porch_param), \
494 .cmd2en_param = DT_INST_PROP(inst, cmd2en_param), \
495 .pwctrl1_param = DT_INST_PROP(inst, pwctrl1_param), \
496 .pvgam_param = DT_INST_PROP(inst, pvgam_param), \
497 .nvgam_param = DT_INST_PROP(inst, nvgam_param), \
498 .ram_param = DT_INST_PROP(inst, ram_param), \
499 .rgb_param = DT_INST_PROP(inst, rgb_param), \
500 .width = DT_INST_PROP(inst, width), \
501 .height = DT_INST_PROP(inst, height), \
502 .ready_time_ms = DT_INST_PROP(inst, ready_time_ms), \
503 }; \
504 \
505 static struct st7789v_data st7789v_data_ ## inst = { \
506 .x_offset = DT_INST_PROP(inst, x_offset), \
507 .y_offset = DT_INST_PROP(inst, y_offset), \
508 }; \
509 \
510 PM_DEVICE_DT_INST_DEFINE(inst, st7789v_pm_action); \
511 \
512 DEVICE_DT_INST_DEFINE(inst, &st7789v_init, PM_DEVICE_DT_INST_GET(inst), \
513 &st7789v_data_ ## inst, &st7789v_config_ ## inst, \
514 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, \
515 &st7789v_api);
516
517 DT_INST_FOREACH_STATUS_OKAY(ST7789V_INIT)
518