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) 2020 Kim Bøndergaard <kim@fam-boendergaard.dk>
8 * Copyright 2024 NXP
9 *
10 * SPDX-License-Identifier: Apache-2.0
11 */
12
13 #define DT_DRV_COMPAT sitronix_st7735r
14
15 #include "display_st7735r.h"
16
17 #include <zephyr/device.h>
18 #include <zephyr/drivers/mipi_dbi.h>
19 #include <zephyr/drivers/gpio.h>
20 #include <zephyr/pm/device.h>
21 #include <zephyr/sys/byteorder.h>
22 #include <zephyr/drivers/display.h>
23
24 #include <zephyr/logging/log.h>
25 LOG_MODULE_REGISTER(display_st7735r, CONFIG_DISPLAY_LOG_LEVEL);
26
27 #define ST7735R_RESET_TIME 1
28 #define ST7735R_EXIT_SLEEP_TIME K_MSEC(120)
29
30 #define ST7735R_PIXEL_SIZE 2u
31
32 struct st7735r_config {
33 const struct device *mipi_dev;
34 const struct mipi_dbi_config dbi_config;
35 uint16_t height;
36 uint16_t width;
37 uint8_t madctl;
38 uint8_t colmod;
39 uint8_t caset[4];
40 uint8_t raset[4];
41 uint8_t vmctr1;
42 uint8_t invctr;
43 uint8_t pwctr1[3];
44 uint8_t pwctr2[1];
45 uint8_t pwctr3[2];
46 uint8_t pwctr4[2];
47 uint8_t pwctr5[2];
48 uint8_t frmctr1[3];
49 uint8_t frmctr2[3];
50 uint8_t frmctr3[6];
51 uint8_t gamctrp1[16];
52 uint8_t gamctrn1[16];
53 bool inversion_on;
54 bool rgb_is_inverted;
55 };
56
57 struct st7735r_data {
58 uint16_t x_offset;
59 uint16_t y_offset;
60 };
61
st7735r_set_lcd_margins(const struct device * dev,uint16_t x_offset,uint16_t y_offset)62 static void st7735r_set_lcd_margins(const struct device *dev,
63 uint16_t x_offset, uint16_t y_offset)
64 {
65 struct st7735r_data *data = dev->data;
66
67 data->x_offset = x_offset;
68 data->y_offset = y_offset;
69 }
70
st7735r_transmit_hold(const struct device * dev,uint8_t cmd,const uint8_t * tx_data,size_t tx_count)71 static int st7735r_transmit_hold(const struct device *dev, uint8_t cmd,
72 const uint8_t *tx_data, size_t tx_count)
73 {
74 const struct st7735r_config *config = dev->config;
75
76 return mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
77 cmd, tx_data, tx_count);
78 }
79
st7735r_transmit(const struct device * dev,uint8_t cmd,const uint8_t * tx_data,size_t tx_count)80 static int st7735r_transmit(const struct device *dev, uint8_t cmd,
81 const uint8_t *tx_data, size_t tx_count)
82 {
83 const struct st7735r_config *config = dev->config;
84 int ret;
85
86 ret = st7735r_transmit_hold(dev, cmd, tx_data, tx_count);
87 mipi_dbi_release(config->mipi_dev, &config->dbi_config);
88 return ret;
89 }
90
st7735r_exit_sleep(const struct device * dev)91 static int st7735r_exit_sleep(const struct device *dev)
92 {
93 int ret;
94
95 ret = st7735r_transmit(dev, ST7735R_CMD_SLEEP_OUT, NULL, 0);
96 if (ret < 0) {
97 return ret;
98 }
99
100 k_sleep(ST7735R_EXIT_SLEEP_TIME);
101
102 return 0;
103 }
104
st7735r_reset_display(const struct device * dev)105 static int st7735r_reset_display(const struct device *dev)
106 {
107 const struct st7735r_config *config = dev->config;
108 int ret;
109
110 LOG_DBG("Resetting display");
111 ret = mipi_dbi_reset(config->mipi_dev, ST7735R_RESET_TIME);
112 if (ret != 0) {
113 ret = st7735r_transmit(dev, ST7735R_CMD_SW_RESET, NULL, 0);
114 if (ret < 0) {
115 return ret;
116 }
117 }
118
119 k_sleep(ST7735R_EXIT_SLEEP_TIME);
120
121 return 0;
122 }
123
st7735r_blanking_on(const struct device * dev)124 static int st7735r_blanking_on(const struct device *dev)
125 {
126 return st7735r_transmit(dev, ST7735R_CMD_DISP_OFF, NULL, 0);
127 }
128
st7735r_blanking_off(const struct device * dev)129 static int st7735r_blanking_off(const struct device *dev)
130 {
131 return st7735r_transmit(dev, ST7735R_CMD_DISP_ON, NULL, 0);
132 }
133
st7735r_set_mem_area(const struct device * dev,const uint16_t x,const uint16_t y,const uint16_t w,const uint16_t h)134 static int st7735r_set_mem_area(const struct device *dev,
135 const uint16_t x, const uint16_t y,
136 const uint16_t w, const uint16_t h)
137 {
138 const struct st7735r_config *config = dev->config;
139 struct st7735r_data *data = dev->data;
140 uint16_t spi_data[2];
141
142 int ret;
143
144 /* ST7735S requires repeating COLMOD for each transfer */
145 ret = st7735r_transmit_hold(dev, ST7735R_CMD_COLMOD, &config->colmod, 1);
146 if (ret < 0) {
147 return ret;
148 }
149
150 uint16_t ram_x = x + data->x_offset;
151 uint16_t ram_y = y + data->y_offset;
152
153 spi_data[0] = sys_cpu_to_be16(ram_x);
154 spi_data[1] = sys_cpu_to_be16(ram_x + w - 1);
155 ret = st7735r_transmit_hold(dev, ST7735R_CMD_CASET, (uint8_t *)&spi_data[0], 4);
156 if (ret < 0) {
157 return ret;
158 }
159
160 spi_data[0] = sys_cpu_to_be16(ram_y);
161 spi_data[1] = sys_cpu_to_be16(ram_y + h - 1);
162 ret = st7735r_transmit_hold(dev, ST7735R_CMD_RASET, (uint8_t *)&spi_data[0], 4);
163 if (ret < 0) {
164 return ret;
165 }
166
167 /* NB: CS still held - data transfer coming next */
168 return 0;
169 }
170
st7735r_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)171 static int st7735r_write(const struct device *dev,
172 const uint16_t x,
173 const uint16_t y,
174 const struct display_buffer_descriptor *desc,
175 const void *buf)
176 {
177 const struct st7735r_config *config = dev->config;
178 const uint8_t *write_data_start = (uint8_t *) buf;
179 uint16_t write_cnt;
180 uint16_t nbr_of_writes;
181 uint16_t write_h;
182 int ret;
183 enum display_pixel_format fmt;
184 struct display_buffer_descriptor mipi_desc;
185
186 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width");
187 __ASSERT((desc->pitch * ST7735R_PIXEL_SIZE * desc->height)
188 <= desc->buf_size, "Input buffer too small");
189
190 LOG_DBG("Writing %dx%d (w,h) @ %dx%d (x,y)",
191 desc->width, desc->height, x, y);
192 ret = st7735r_set_mem_area(dev, x, y, desc->width, desc->height);
193 if (ret < 0) {
194 goto out;
195 }
196
197 if (desc->pitch > desc->width) {
198 write_h = 1U;
199 nbr_of_writes = desc->height;
200 mipi_desc.height = 1;
201 mipi_desc.buf_size = desc->pitch * ST7735R_PIXEL_SIZE;
202 } else {
203 write_h = desc->height;
204 nbr_of_writes = 1U;
205 mipi_desc.height = desc->height;
206 mipi_desc.buf_size = desc->width * ST7735R_PIXEL_SIZE * write_h;
207 }
208
209 mipi_desc.width = desc->width;
210 /* Per MIPI API, pitch must always match width */
211 mipi_desc.pitch = desc->width;
212
213
214 if (!(config->madctl & ST7735R_MADCTL_BGR) != !config->rgb_is_inverted) {
215 fmt = PIXEL_FORMAT_BGR_565;
216 } else {
217 fmt = PIXEL_FORMAT_RGB_565;
218 }
219
220 ret = st7735r_transmit_hold(dev, ST7735R_CMD_RAMWR,
221 (void *) write_data_start,
222 desc->width * ST7735R_PIXEL_SIZE * write_h);
223 if (ret < 0) {
224 goto out;
225 }
226
227 write_data_start += (desc->pitch * ST7735R_PIXEL_SIZE);
228 for (write_cnt = 1U; write_cnt < nbr_of_writes; ++write_cnt) {
229 ret = mipi_dbi_write_display(config->mipi_dev,
230 &config->dbi_config,
231 write_data_start,
232 &mipi_desc,
233 fmt);
234 if (ret < 0) {
235 goto out;
236 }
237
238 write_data_start += (desc->pitch * ST7735R_PIXEL_SIZE);
239 }
240
241 ret = 0;
242 out:
243 mipi_dbi_release(config->mipi_dev, &config->dbi_config);
244 return ret;
245 }
246
st7735r_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)247 static void st7735r_get_capabilities(const struct device *dev,
248 struct display_capabilities *capabilities)
249 {
250 const struct st7735r_config *config = dev->config;
251
252 memset(capabilities, 0, sizeof(struct display_capabilities));
253 capabilities->x_resolution = config->width;
254 capabilities->y_resolution = config->height;
255
256 /*
257 * Invert the pixel format if rgb_is_inverted is enabled.
258 * Report pixel format as the same format set in the MADCTL
259 * if disabling the rgb_is_inverted option.
260 * Or not so, reporting pixel format as RGB if MADCTL setting
261 * is BGR. And also vice versa.
262 * It is a workaround for supporting buggy modules that display RGB as BGR.
263 */
264 if (!(config->madctl & ST7735R_MADCTL_BGR) != !config->rgb_is_inverted) {
265 capabilities->supported_pixel_formats = PIXEL_FORMAT_BGR_565;
266 capabilities->current_pixel_format = PIXEL_FORMAT_BGR_565;
267 } else {
268 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565;
269 capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565;
270 }
271
272 capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
273 }
274
st7735r_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)275 static int st7735r_set_pixel_format(const struct device *dev,
276 const enum display_pixel_format pixel_format)
277 {
278 const struct st7735r_config *config = dev->config;
279
280 if ((pixel_format == PIXEL_FORMAT_RGB_565) &&
281 (~config->madctl & ST7735R_MADCTL_BGR)) {
282 return 0;
283 }
284
285 if ((pixel_format == PIXEL_FORMAT_BGR_565) &&
286 (config->madctl & ST7735R_MADCTL_BGR)) {
287 return 0;
288 }
289
290 LOG_ERR("Pixel format change not implemented");
291
292 return -ENOTSUP;
293 }
294
st7735r_set_orientation(const struct device * dev,const enum display_orientation orientation)295 static int st7735r_set_orientation(const struct device *dev,
296 const enum display_orientation orientation)
297 {
298 if (orientation == DISPLAY_ORIENTATION_NORMAL) {
299 return 0;
300 }
301
302 LOG_ERR("Changing display orientation not implemented");
303
304 return -ENOTSUP;
305 }
306
st7735r_lcd_init(const struct device * dev)307 static int st7735r_lcd_init(const struct device *dev)
308 {
309 const struct st7735r_config *config = dev->config;
310 struct st7735r_data *data = dev->data;
311 int ret;
312
313 st7735r_set_lcd_margins(dev, data->x_offset, data->y_offset);
314
315 ret = st7735r_transmit(dev, ST7735R_CMD_FRMCTR1, config->frmctr1,
316 sizeof(config->frmctr1));
317 if (ret < 0) {
318 return ret;
319 }
320
321 ret = st7735r_transmit(dev, ST7735R_CMD_FRMCTR2, config->frmctr2,
322 sizeof(config->frmctr2));
323 if (ret < 0) {
324 return ret;
325 }
326
327 ret = st7735r_transmit(dev, ST7735R_CMD_FRMCTR3, config->frmctr3,
328 sizeof(config->frmctr3));
329 if (ret < 0) {
330 return ret;
331 }
332
333 ret = st7735r_transmit(dev, ST7735R_CMD_INVCTR, &config->invctr, 1);
334 if (ret < 0) {
335 return ret;
336 }
337
338 ret = st7735r_transmit(dev, ST7735R_CMD_PWCTR1, config->pwctr1,
339 sizeof(config->pwctr1));
340 if (ret < 0) {
341 return ret;
342 }
343
344 ret = st7735r_transmit(dev, ST7735R_CMD_PWCTR2, config->pwctr2,
345 sizeof(config->pwctr2));
346 if (ret < 0) {
347 return ret;
348 }
349
350 ret = st7735r_transmit(dev, ST7735R_CMD_PWCTR3, config->pwctr3,
351 sizeof(config->pwctr3));
352 if (ret < 0) {
353 return ret;
354 }
355
356 ret = st7735r_transmit(dev, ST7735R_CMD_PWCTR4, config->pwctr4,
357 sizeof(config->pwctr4));
358 if (ret < 0) {
359 return ret;
360 }
361
362 ret = st7735r_transmit(dev, ST7735R_CMD_PWCTR5, config->pwctr5,
363 sizeof(config->pwctr5));
364 if (ret < 0) {
365 return ret;
366 }
367
368 ret = st7735r_transmit(dev, ST7735R_CMD_VMCTR1, &config->vmctr1, 1);
369 if (ret < 0) {
370 return ret;
371 }
372
373 if (config->inversion_on) {
374 ret = st7735r_transmit(dev, ST7735R_CMD_INV_ON, NULL, 0);
375 } else {
376 ret = st7735r_transmit(dev, ST7735R_CMD_INV_OFF, NULL, 0);
377 }
378 if (ret < 0) {
379 return ret;
380 }
381
382 ret = st7735r_transmit(dev, ST7735R_CMD_MADCTL, &config->madctl, 1);
383 if (ret < 0) {
384 return ret;
385 }
386
387 ret = st7735r_transmit(dev, ST7735R_CMD_COLMOD, &config->colmod, 1);
388 if (ret < 0) {
389 return ret;
390 }
391
392 ret = st7735r_transmit(dev, ST7735R_CMD_CASET, config->caset,
393 sizeof(config->caset));
394 if (ret < 0) {
395 return ret;
396 }
397
398 ret = st7735r_transmit(dev, ST7735R_CMD_RASET, config->raset,
399 sizeof(config->raset));
400 if (ret < 0) {
401 return ret;
402 }
403
404 ret = st7735r_transmit(dev, ST7735R_CMD_GAMCTRP1, config->gamctrp1,
405 sizeof(config->gamctrp1));
406 if (ret < 0) {
407 return ret;
408 }
409
410 ret = st7735r_transmit(dev, ST7735R_CMD_GAMCTRN1, config->gamctrn1,
411 sizeof(config->gamctrn1));
412 if (ret < 0) {
413 return ret;
414 }
415
416 ret = st7735r_transmit(dev, ST7735R_CMD_NORON, NULL, 0);
417 if (ret < 0) {
418 return ret;
419 }
420
421 ret = st7735r_transmit(dev, ST7735R_CMD_DISP_ON, NULL, 0);
422 if (ret < 0) {
423 return ret;
424 }
425
426 return 0;
427 }
428
st7735r_init(const struct device * dev)429 static int st7735r_init(const struct device *dev)
430 {
431 const struct st7735r_config *config = dev->config;
432 int ret;
433
434 if (!device_is_ready(config->mipi_dev)) {
435 LOG_ERR("MIPI bus %s not ready", config->mipi_dev->name);
436 return -ENODEV;
437 }
438
439 ret = st7735r_reset_display(dev);
440 if (ret < 0) {
441 LOG_ERR("Couldn't reset display");
442 return ret;
443 }
444
445 ret = st7735r_exit_sleep(dev);
446 if (ret < 0) {
447 LOG_ERR("Couldn't exit sleep");
448 return ret;
449 }
450
451 ret = st7735r_lcd_init(dev);
452 if (ret < 0) {
453 LOG_ERR("Couldn't init LCD");
454 return ret;
455 }
456
457 return 0;
458 }
459
460 #ifdef CONFIG_PM_DEVICE
st7735r_pm_action(const struct device * dev,enum pm_device_action action)461 static int st7735r_pm_action(const struct device *dev,
462 enum pm_device_action action)
463 {
464 int ret = 0;
465
466 switch (action) {
467 case PM_DEVICE_ACTION_RESUME:
468 ret = st7735r_exit_sleep(dev);
469 break;
470 case PM_DEVICE_ACTION_SUSPEND:
471 ret = st7735r_transmit(dev, ST7735R_CMD_SLEEP_IN, NULL, 0);
472 break;
473 default:
474 ret = -ENOTSUP;
475 break;
476 }
477
478 return ret;
479 }
480 #endif /* CONFIG_PM_DEVICE */
481
482 static DEVICE_API(display, st7735r_api) = {
483 .blanking_on = st7735r_blanking_on,
484 .blanking_off = st7735r_blanking_off,
485 .write = st7735r_write,
486 .get_capabilities = st7735r_get_capabilities,
487 .set_pixel_format = st7735r_set_pixel_format,
488 .set_orientation = st7735r_set_orientation,
489 };
490
491
492 #define ST7735R_INIT(inst) \
493 const static struct st7735r_config st7735r_config_ ## inst = { \
494 .mipi_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
495 .dbi_config = MIPI_DBI_CONFIG_DT_INST(inst, \
496 SPI_OP_MODE_MASTER | \
497 ((DT_INST_STRING_UPPER_TOKEN(inst, mipi_mode) == \
498 MIPI_DBI_MODE_SPI_4WIRE) ? SPI_WORD_SET(8) : \
499 SPI_WORD_SET(9)) | \
500 SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \
501 .width = DT_INST_PROP(inst, width), \
502 .height = DT_INST_PROP(inst, height), \
503 .madctl = DT_INST_PROP(inst, madctl), \
504 .colmod = DT_INST_PROP(inst, colmod), \
505 .caset = DT_INST_PROP(inst, caset), \
506 .raset = DT_INST_PROP(inst, raset), \
507 .vmctr1 = DT_INST_PROP(inst, vmctr1), \
508 .invctr = DT_INST_PROP(inst, invctr), \
509 .pwctr1 = DT_INST_PROP(inst, pwctr1), \
510 .pwctr2 = DT_INST_PROP(inst, pwctr2), \
511 .pwctr3 = DT_INST_PROP(inst, pwctr3), \
512 .pwctr4 = DT_INST_PROP(inst, pwctr4), \
513 .pwctr5 = DT_INST_PROP(inst, pwctr5), \
514 .frmctr1 = DT_INST_PROP(inst, frmctr1), \
515 .frmctr2 = DT_INST_PROP(inst, frmctr2), \
516 .frmctr3 = DT_INST_PROP(inst, frmctr3), \
517 .gamctrp1 = DT_INST_PROP(inst, gamctrp1), \
518 .gamctrn1 = DT_INST_PROP(inst, gamctrn1), \
519 .inversion_on = DT_INST_PROP(inst, inversion_on), \
520 .rgb_is_inverted = DT_INST_PROP(inst, rgb_is_inverted), \
521 }; \
522 \
523 static struct st7735r_data st7735r_data_ ## inst = { \
524 .x_offset = DT_INST_PROP(inst, x_offset), \
525 .y_offset = DT_INST_PROP(inst, y_offset), \
526 }; \
527 \
528 PM_DEVICE_DT_INST_DEFINE(inst, st7735r_pm_action); \
529 \
530 DEVICE_DT_INST_DEFINE(inst, st7735r_init, PM_DEVICE_DT_INST_GET(inst), \
531 &st7735r_data_ ## inst, &st7735r_config_ ## inst, \
532 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, \
533 &st7735r_api);
534
535 DT_INST_FOREACH_STATUS_OKAY(ST7735R_INIT)
536