1 /*
2 * Copyright 2023, NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT sitronix_st7796s
8
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/display.h>
11 #include <zephyr/sys/byteorder.h>
12 #include <zephyr/drivers/mipi_dbi.h>
13
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(display_st7796s, CONFIG_DISPLAY_LOG_LEVEL);
16
17 #include "display_st7796s.h"
18
19 /* Magic numbers used to lock/unlock command settings */
20 #define ST7796S_UNLOCK_1 0xC3
21 #define ST7796S_UNLOCK_2 0x96
22
23 #define ST7796S_LOCK_1 0x3C
24 #define ST7796S_LOCK_2 0x69
25
26 #define ST7796S_PIXEL_SIZE 2 /* Only 16 bit color mode supported with this driver */
27
28 struct st7796s_config {
29 const struct device *mipi_dbi;
30 const struct mipi_dbi_config dbi_config;
31 uint16_t width;
32 uint16_t height;
33 bool inverted; /* Display color inversion */
34 /* Display configuration parameters */
35 uint8_t dic; /* Display inversion control */
36 uint8_t frmctl1[2]; /* Frame rate control, normal mode */
37 uint8_t frmctl2[2]; /* Frame rate control, idle mode */
38 uint8_t frmctl3[2]; /* Frame rate control, partial mode */
39 uint8_t bpc[4]; /* Blanking porch control */
40 uint8_t dfc[4]; /* Display function control */
41 uint8_t pwr1[2]; /* Power control 1 */
42 uint8_t pwr2; /* Power control 2 */
43 uint8_t pwr3; /* Power control 3 */
44 uint8_t vcmpctl; /* VCOM control */
45 uint8_t doca[8]; /* Display output ctrl */
46 uint8_t pgc[14]; /* Positive gamma control */
47 uint8_t ngc[14]; /* Negative gamma control */
48 uint8_t madctl; /* Memory data access control */
49 uint8_t te_mode; /* Tearing enable mode */
50 uint32_t te_delay; /* Tearing enable delay */
51 bool rgb_is_inverted;
52 };
53
st7796s_send_cmd(const struct device * dev,uint8_t cmd,const uint8_t * data,size_t len)54 static int st7796s_send_cmd(const struct device *dev,
55 uint8_t cmd, const uint8_t *data, size_t len)
56 {
57 const struct st7796s_config *config = dev->config;
58
59 return mipi_dbi_command_write(config->mipi_dbi, &config->dbi_config,
60 cmd, data, len);
61 }
62
st7796s_set_cursor(const struct device * dev,const uint16_t x,const uint16_t y,const uint16_t width,const uint16_t height)63 static int st7796s_set_cursor(const struct device *dev,
64 const uint16_t x, const uint16_t y,
65 const uint16_t width, const uint16_t height)
66 {
67 uint16_t addr_data[2];
68 int ret;
69
70 /* Column address */
71 addr_data[0] = sys_cpu_to_be16(x);
72 addr_data[1] = sys_cpu_to_be16(x + width - 1);
73
74 ret = st7796s_send_cmd(dev, ST7796S_CMD_CASET,
75 (uint8_t *)addr_data, sizeof(addr_data));
76 if (ret < 0) {
77 return ret;
78 }
79
80 /* Row address */
81 addr_data[0] = sys_cpu_to_be16(y);
82 addr_data[1] = sys_cpu_to_be16(y + height - 1);
83 ret = st7796s_send_cmd(dev, ST7796S_CMD_RASET,
84 (uint8_t *)addr_data, sizeof(addr_data));
85 return ret;
86 }
87
st7796s_blanking_on(const struct device * dev)88 static int st7796s_blanking_on(const struct device *dev)
89 {
90 return st7796s_send_cmd(dev, ST7796S_CMD_DISPOFF, NULL, 0);
91 }
92
st7796s_blanking_off(const struct device * dev)93 static int st7796s_blanking_off(const struct device *dev)
94 {
95 return st7796s_send_cmd(dev, ST7796S_CMD_DISPON, NULL, 0);
96 }
97
st7796s_get_pixelfmt(const struct device * dev)98 static int st7796s_get_pixelfmt(const struct device *dev)
99 {
100 const struct st7796s_config *config = dev->config;
101
102 /*
103 * Invert the pixel format for 8-bit 8080 Parallel Interface.
104 *
105 * Zephyr uses big endian byte order when the pixel format has
106 * multiple bytes.
107 *
108 * For RGB565, Red is placed in byte 1 and Blue in byte 0.
109 * For BGR565, Red is placed in byte 0 and Blue in byte 1.
110 *
111 * This is not an issue when using a 16-bit interface.
112 * For RGB565, this would map to Red being in D[11:15] and
113 * Blue in D[0:4] and vice versa for BGR565.
114 *
115 * However this is an issue when using a 8-bit interface.
116 * For RGB565, Blue is placed in byte 0 as mentioned earlier.
117 * However the controller expects Red to be in D[3:7] of byte 0.
118 *
119 * Hence we report pixel format as RGB when MADCTL setting is BGR
120 * and vice versa.
121 */
122 if (config->dbi_config.mode == MIPI_DBI_MODE_8080_BUS_8_BIT) {
123 /*
124 * Similar to the handling for other interface modes,
125 * invert the reported pixel format if "rgb_is_inverted"
126 * is enabled
127 */
128 if (((bool)(config->madctl & ST7796S_MADCTL_BGR)) !=
129 config->rgb_is_inverted) {
130 return PIXEL_FORMAT_RGB_565;
131 } else {
132 return PIXEL_FORMAT_BGR_565;
133 }
134 }
135
136 /*
137 * Invert the pixel format if rgb_is_inverted is enabled.
138 * Report pixel format as the same format set in the MADCTL
139 * if rgb_is_inverted is disabled.
140 * Report pixel format as RGB if MADCTL setting is BGR and vice versa
141 * if rgb_is_inverted is enabled.
142 * It is a workaround for supporting buggy modules that display RGB as BGR.
143 */
144 if (((bool)(config->madctl & ST7796S_MADCTL_BGR)) !=
145 config->rgb_is_inverted) {
146 return PIXEL_FORMAT_BGR_565;
147 } else {
148 return PIXEL_FORMAT_RGB_565;
149 }
150 }
151
st7796s_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)152 static int st7796s_write(const struct device *dev,
153 const uint16_t x,
154 const uint16_t y,
155 const struct display_buffer_descriptor *desc,
156 const void *buf)
157 {
158 const struct st7796s_config *config = dev->config;
159 int ret;
160 struct display_buffer_descriptor mipi_desc = {0};
161 enum display_pixel_format pixfmt;
162
163 ret = st7796s_set_cursor(dev, x, y, desc->width, desc->height);
164 if (ret < 0) {
165 return ret;
166 }
167
168 mipi_desc.buf_size = desc->width * desc->height * ST7796S_PIXEL_SIZE;
169 mipi_desc.frame_incomplete = desc->frame_incomplete;
170
171 ret = mipi_dbi_command_write(config->mipi_dbi,
172 &config->dbi_config, ST7796S_CMD_RAMWR,
173 NULL, 0);
174 if (ret < 0) {
175 return ret;
176 }
177
178 pixfmt = st7796s_get_pixelfmt(dev);
179
180 return mipi_dbi_write_display(config->mipi_dbi,
181 &config->dbi_config, buf,
182 &mipi_desc, pixfmt);
183 }
184
st7796s_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)185 static void st7796s_get_capabilities(const struct device *dev,
186 struct display_capabilities *capabilities)
187 {
188 const struct st7796s_config *config = dev->config;
189
190 memset(capabilities, 0, sizeof(struct display_capabilities));
191
192 capabilities->current_pixel_format = st7796s_get_pixelfmt(dev);
193
194 capabilities->x_resolution = config->width;
195 capabilities->y_resolution = config->height;
196 capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
197 }
198
st7796s_lcd_config(const struct device * dev)199 static int st7796s_lcd_config(const struct device *dev)
200 {
201 const struct st7796s_config *config = dev->config;
202 int ret;
203 uint8_t param;
204
205 /* Unlock display configuration */
206 param = ST7796S_UNLOCK_1;
207 ret = st7796s_send_cmd(dev, ST7796S_CMD_CSCON, ¶m, sizeof(param));
208 if (ret < 0) {
209 return ret;
210 }
211
212 param = ST7796S_UNLOCK_2;
213 ret = st7796s_send_cmd(dev, ST7796S_CMD_CSCON, ¶m, sizeof(param));
214 if (ret < 0) {
215 return ret;
216 }
217
218 ret = st7796s_send_cmd(dev, ST7796S_CMD_DIC, &config->dic, sizeof(config->dic));
219 if (ret < 0) {
220 return ret;
221 }
222
223 ret = st7796s_send_cmd(dev, ST7796S_CMD_FRMCTR1, config->frmctl1,
224 sizeof(config->frmctl1));
225 if (ret < 0) {
226 return ret;
227 }
228
229 ret = st7796s_send_cmd(dev, ST7796S_CMD_FRMCTR2, config->frmctl2,
230 sizeof(config->frmctl2));
231 if (ret < 0) {
232 return ret;
233 }
234
235 ret = st7796s_send_cmd(dev, ST7796S_CMD_FRMCTR3, config->frmctl3,
236 sizeof(config->frmctl3));
237 if (ret < 0) {
238 return ret;
239 }
240
241 ret = st7796s_send_cmd(dev, ST7796S_CMD_BPC, config->bpc, sizeof(config->bpc));
242 if (ret < 0) {
243 return ret;
244 }
245
246 ret = st7796s_send_cmd(dev, ST7796S_CMD_DFC, config->dfc, sizeof(config->dfc));
247 if (ret < 0) {
248 return ret;
249 }
250
251 ret = st7796s_send_cmd(dev, ST7796S_CMD_PWR1, config->pwr1, sizeof(config->pwr1));
252 if (ret < 0) {
253 return ret;
254 }
255
256 ret = st7796s_send_cmd(dev, ST7796S_CMD_PWR2, &config->pwr2, sizeof(config->pwr2));
257 if (ret < 0) {
258 return ret;
259 }
260
261 ret = st7796s_send_cmd(dev, ST7796S_CMD_PWR3, &config->pwr3, sizeof(config->pwr3));
262 if (ret < 0) {
263 return ret;
264 }
265
266 ret = st7796s_send_cmd(dev, ST7796S_CMD_VCMPCTL, &config->vcmpctl,
267 sizeof(config->vcmpctl));
268 if (ret < 0) {
269 return ret;
270 }
271
272 ret = st7796s_send_cmd(dev, ST7796S_CMD_DOCA, config->doca,
273 sizeof(config->doca));
274 if (ret < 0) {
275 return ret;
276 }
277
278 ret = st7796s_send_cmd(dev, ST7796S_CMD_PGC, config->pgc, sizeof(config->pgc));
279 if (ret < 0) {
280 return ret;
281 }
282
283 ret = st7796s_send_cmd(dev, ST7796S_CMD_NGC, config->ngc, sizeof(config->ngc));
284 if (ret < 0) {
285 return ret;
286 }
287
288 /* Attempt to enable TE signal */
289 ret = mipi_dbi_configure_te(config->mipi_dbi, config->te_mode,
290 config->te_delay);
291 if (ret == 0) {
292 /* TE was enabled- send TEON, and enable vblank only */
293 param = 0x0; /* Set TMEM bit to 0 */
294 ret = st7796s_send_cmd(dev, ST7796S_CMD_TEON, ¶m, sizeof(param));
295 if (ret < 0) {
296 return ret;
297 }
298 }
299
300 /* Lock display configuration */
301 param = ST7796S_LOCK_1;
302 ret = st7796s_send_cmd(dev, ST7796S_CMD_CSCON, ¶m, sizeof(param));
303 if (ret < 0) {
304 return ret;
305 }
306
307 param = ST7796S_LOCK_2;
308 return st7796s_send_cmd(dev, ST7796S_CMD_CSCON, ¶m, sizeof(param));
309 }
310
st7796s_init(const struct device * dev)311 static int st7796s_init(const struct device *dev)
312 {
313 const struct st7796s_config *config = dev->config;
314 int ret;
315 uint8_t param;
316
317 /* Since VDDI comes up before reset pin is low, we must reset display
318 * state. Pulse for 100 MS, per datasheet
319 */
320 ret = mipi_dbi_reset(config->mipi_dbi, 100);
321 if (ret < 0) {
322 return ret;
323 }
324 /* Delay an additional 100ms after reset */
325 k_msleep(100);
326
327 /* Configure controller parameters */
328 ret = st7796s_lcd_config(dev);
329 if (ret < 0) {
330 LOG_ERR("Could not set LCD configuration (%d)", ret);
331 return ret;
332 }
333
334 if (config->inverted) {
335 ret = st7796s_send_cmd(dev, ST7796S_CMD_INVON, NULL, 0);
336 } else {
337 ret = st7796s_send_cmd(dev, ST7796S_CMD_INVOFF, NULL, 0);
338 }
339 if (ret < 0) {
340 return ret;
341 }
342
343 param = ST7796S_CONTROL_16BIT;
344 ret = st7796s_send_cmd(dev, ST7796S_CMD_COLMOD, ¶m, sizeof(param));
345 if (ret < 0) {
346 return ret;
347 }
348
349 param = config->madctl;
350 ret = st7796s_send_cmd(dev, ST7796S_CMD_MADCTL, ¶m, sizeof(param));
351 if (ret < 0) {
352 return ret;
353 }
354
355 /* Exit sleep */
356 st7796s_send_cmd(dev, ST7796S_CMD_SLPOUT, NULL, 0);
357 /* Delay 5ms after sleep out command, per datasheet */
358 k_msleep(5);
359 /* Turn on display */
360 st7796s_send_cmd(dev, ST7796S_CMD_DISPON, NULL, 0);
361
362 return 0;
363 }
364
365 static DEVICE_API(display, st7796s_api) = {
366 .blanking_on = st7796s_blanking_on,
367 .blanking_off = st7796s_blanking_off,
368 .write = st7796s_write,
369 .get_capabilities = st7796s_get_capabilities,
370 };
371
372
373 #define ST7796S_INIT(n) \
374 static const struct st7796s_config st7796s_config_##n = { \
375 .mipi_dbi = DEVICE_DT_GET(DT_INST_PARENT(n)), \
376 .dbi_config = { \
377 .config = MIPI_DBI_SPI_CONFIG_DT( \
378 DT_DRV_INST(n), \
379 SPI_OP_MODE_MASTER | \
380 SPI_WORD_SET(8), \
381 0), \
382 .mode = DT_INST_STRING_UPPER_TOKEN_OR(n, mipi_mode, \
383 MIPI_DBI_MODE_SPI_4WIRE), \
384 }, \
385 .width = DT_INST_PROP(n, width), \
386 .height = DT_INST_PROP(n, height), \
387 .inverted = DT_INST_PROP(n, color_invert), \
388 .dic = DT_INST_ENUM_IDX(n, invert_mode), \
389 .frmctl1 = DT_INST_PROP(n, frmctl1), \
390 .frmctl2 = DT_INST_PROP(n, frmctl2), \
391 .frmctl3 = DT_INST_PROP(n, frmctl3), \
392 .bpc = DT_INST_PROP(n, bpc), \
393 .dfc = DT_INST_PROP(n, dfc), \
394 .pwr1 = DT_INST_PROP(n, pwr1), \
395 .pwr2 = DT_INST_PROP(n, pwr2), \
396 .pwr3 = DT_INST_PROP(n, pwr3), \
397 .vcmpctl = DT_INST_PROP(n, vcmpctl), \
398 .doca = DT_INST_PROP(n, doca), \
399 .pgc = DT_INST_PROP(n, pgc), \
400 .ngc = DT_INST_PROP(n, ngc), \
401 .madctl = DT_INST_PROP(n, madctl), \
402 .rgb_is_inverted = DT_INST_PROP(n, rgb_is_inverted), \
403 .te_mode = MIPI_DBI_TE_MODE_DT_INST(n, te_mode), \
404 .te_delay = DT_INST_PROP(n, te_delay), \
405 }; \
406 \
407 DEVICE_DT_INST_DEFINE(n, st7796s_init, \
408 NULL, \
409 NULL, \
410 &st7796s_config_##n, \
411 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, \
412 &st7796s_api);
413
414 DT_INST_FOREACH_STATUS_OKAY(ST7796S_INIT)
415