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