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 };
49
50 struct st7789v_data {
51 uint16_t x_offset;
52 uint16_t y_offset;
53 };
54
55 #ifdef CONFIG_ST7789V_RGB888
56 #define ST7789V_PIXEL_SIZE 3u
57 #else
58 #define ST7789V_PIXEL_SIZE 2u
59 #endif
60
st7789v_set_lcd_margins(const struct device * dev,uint16_t x_offset,uint16_t y_offset)61 static void st7789v_set_lcd_margins(const struct device *dev,
62 uint16_t x_offset, uint16_t y_offset)
63 {
64 struct st7789v_data *data = dev->data;
65
66 data->x_offset = x_offset;
67 data->y_offset = y_offset;
68 }
69
st7789v_transmit(const struct device * dev,uint8_t cmd,uint8_t * tx_data,size_t tx_count)70 static void st7789v_transmit(const struct device *dev, uint8_t cmd,
71 uint8_t *tx_data, size_t tx_count)
72 {
73 const struct st7789v_config *config = dev->config;
74
75 mipi_dbi_command_write(config->mipi_dbi, &config->dbi_config, cmd,
76 tx_data, tx_count);
77 }
78
st7789v_exit_sleep(const struct device * dev)79 static void st7789v_exit_sleep(const struct device *dev)
80 {
81 st7789v_transmit(dev, ST7789V_CMD_SLEEP_OUT, NULL, 0);
82 k_sleep(K_MSEC(120));
83 }
84
st7789v_reset_display(const struct device * dev)85 static void st7789v_reset_display(const struct device *dev)
86 {
87 const struct st7789v_config *config = dev->config;
88 int ret;
89
90 LOG_DBG("Resetting display");
91
92 k_sleep(K_MSEC(1));
93 ret = mipi_dbi_reset(config->mipi_dbi, 6);
94 if (ret == -ENOTSUP) {
95 /* Send software reset command */
96 st7789v_transmit(dev, ST7789V_CMD_SW_RESET, NULL, 0);
97 k_sleep(K_MSEC(5));
98 } else {
99 k_sleep(K_MSEC(20));
100 }
101 }
102
st7789v_blanking_on(const struct device * dev)103 static int st7789v_blanking_on(const struct device *dev)
104 {
105 st7789v_transmit(dev, ST7789V_CMD_DISP_OFF, NULL, 0);
106 return 0;
107 }
108
st7789v_blanking_off(const struct device * dev)109 static int st7789v_blanking_off(const struct device *dev)
110 {
111 st7789v_transmit(dev, ST7789V_CMD_DISP_ON, NULL, 0);
112 return 0;
113 }
114
st7789v_set_mem_area(const struct device * dev,const uint16_t x,const uint16_t y,const uint16_t w,const uint16_t h)115 static void st7789v_set_mem_area(const struct device *dev, const uint16_t x,
116 const uint16_t y, const uint16_t w, const uint16_t h)
117 {
118 struct st7789v_data *data = dev->data;
119 uint16_t spi_data[2];
120
121 uint16_t ram_x = x + data->x_offset;
122 uint16_t ram_y = y + data->y_offset;
123
124 spi_data[0] = sys_cpu_to_be16(ram_x);
125 spi_data[1] = sys_cpu_to_be16(ram_x + w - 1);
126 st7789v_transmit(dev, ST7789V_CMD_CASET, (uint8_t *)&spi_data[0], 4);
127
128 spi_data[0] = sys_cpu_to_be16(ram_y);
129 spi_data[1] = sys_cpu_to_be16(ram_y + h - 1);
130 st7789v_transmit(dev, ST7789V_CMD_RASET, (uint8_t *)&spi_data[0], 4);
131 }
132
st7789v_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)133 static int st7789v_write(const struct device *dev,
134 const uint16_t x,
135 const uint16_t y,
136 const struct display_buffer_descriptor *desc,
137 const void *buf)
138 {
139 const struct st7789v_config *config = dev->config;
140 struct display_buffer_descriptor mipi_desc;
141 const uint8_t *write_data_start = (uint8_t *) buf;
142 uint16_t nbr_of_writes;
143 uint16_t write_h;
144 enum display_pixel_format pixfmt;
145
146 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width");
147 __ASSERT((desc->pitch * ST7789V_PIXEL_SIZE * desc->height) <= desc->buf_size,
148 "Input buffer too small");
149
150 LOG_DBG("Writing %dx%d (w,h) @ %dx%d (x,y)",
151 desc->width, desc->height, x, y);
152 st7789v_set_mem_area(dev, x, y, desc->width, desc->height);
153
154 if (desc->pitch > desc->width) {
155 write_h = 1U;
156 nbr_of_writes = desc->height;
157 mipi_desc.height = 1;
158 mipi_desc.buf_size = desc->pitch * ST7789V_PIXEL_SIZE;
159 } else {
160 write_h = desc->height;
161 nbr_of_writes = 1U;
162 mipi_desc.height = desc->height;
163 mipi_desc.buf_size = desc->width * write_h * ST7789V_PIXEL_SIZE;
164 }
165 if (IS_ENABLED(CONFIG_ST7789V_RGB565)) {
166 pixfmt = PIXEL_FORMAT_RGB_565;
167 } else if (IS_ENABLED(CONFIG_ST7789V_BGR565)) {
168 pixfmt = PIXEL_FORMAT_BGR_565;
169 } else {
170 pixfmt = PIXEL_FORMAT_RGB_888;
171 }
172
173 mipi_desc.width = desc->width;
174 /* Per MIPI API, pitch must always match width */
175 mipi_desc.pitch = desc->width;
176
177 /* Send RAMWR command */
178 st7789v_transmit(dev, ST7789V_CMD_RAMWR, NULL, 0);
179
180 for (uint16_t write_cnt = 0U; write_cnt < nbr_of_writes; ++write_cnt) {
181 mipi_dbi_write_display(config->mipi_dbi, &config->dbi_config,
182 write_data_start, &mipi_desc, pixfmt);
183
184 write_data_start += (desc->pitch * ST7789V_PIXEL_SIZE);
185 }
186
187 return 0;
188 }
189
st7789v_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)190 static void st7789v_get_capabilities(const struct device *dev,
191 struct display_capabilities *capabilities)
192 {
193 const struct st7789v_config *config = dev->config;
194
195 memset(capabilities, 0, sizeof(struct display_capabilities));
196 capabilities->x_resolution = config->width;
197 capabilities->y_resolution = config->height;
198
199 #ifdef CONFIG_ST7789V_RGB565
200 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565;
201 capabilities->current_pixel_format = PIXEL_FORMAT_RGB_565;
202 #elif CONFIG_ST7789V_BGR565
203 capabilities->supported_pixel_formats = PIXEL_FORMAT_BGR_565;
204 capabilities->current_pixel_format = PIXEL_FORMAT_BGR_565;
205 #else
206 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_888;
207 capabilities->current_pixel_format = PIXEL_FORMAT_RGB_888;
208 #endif
209 capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
210 }
211
st7789v_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)212 static int st7789v_set_pixel_format(const struct device *dev,
213 const enum display_pixel_format pixel_format)
214 {
215 #ifdef CONFIG_ST7789V_RGB565
216 if (pixel_format == PIXEL_FORMAT_RGB_565) {
217 #elif CONFIG_ST7789V_BGR565
218 if (pixel_format == PIXEL_FORMAT_BGR_565) {
219 #else
220 if (pixel_format == PIXEL_FORMAT_RGB_888) {
221 #endif
222 return 0;
223 }
224 LOG_ERR("Pixel format change not implemented");
225 return -ENOTSUP;
226 }
227
228 static int st7789v_set_orientation(const struct device *dev,
229 const enum display_orientation orientation)
230 {
231 if (orientation == DISPLAY_ORIENTATION_NORMAL) {
232 return 0;
233 }
234 LOG_ERR("Changing display orientation not implemented");
235 return -ENOTSUP;
236 }
237
238 static void st7789v_lcd_init(const struct device *dev)
239 {
240 struct st7789v_data *data = dev->data;
241 const struct st7789v_config *config = dev->config;
242 uint8_t tmp;
243
244 st7789v_set_lcd_margins(dev, data->x_offset,
245 data->y_offset);
246
247 st7789v_transmit(dev, ST7789V_CMD_CMD2EN,
248 (uint8_t *)config->cmd2en_param,
249 sizeof(config->cmd2en_param));
250
251 st7789v_transmit(dev, ST7789V_CMD_PORCTRL,
252 (uint8_t *)config->porch_param,
253 sizeof(config->porch_param));
254
255 /* Digital Gamma Enable, default disabled */
256 tmp = 0x00;
257 st7789v_transmit(dev, ST7789V_CMD_DGMEN, &tmp, 1);
258
259 /* Frame Rate Control in Normal Mode, default value */
260 tmp = 0x0f;
261 st7789v_transmit(dev, ST7789V_CMD_FRCTRL2, &tmp, 1);
262
263 tmp = config->gctrl;
264 st7789v_transmit(dev, ST7789V_CMD_GCTRL, &tmp, 1);
265
266 tmp = config->vcom;
267 st7789v_transmit(dev, ST7789V_CMD_VCOMS, &tmp, 1);
268
269 if (config->vdv_vrh_enable) {
270 tmp = 0x01;
271 st7789v_transmit(dev, ST7789V_CMD_VDVVRHEN, &tmp, 1);
272
273 tmp = config->vrh_value;
274 st7789v_transmit(dev, ST7789V_CMD_VRH, &tmp, 1);
275
276 tmp = config->vdv_value;
277 st7789v_transmit(dev, ST7789V_CMD_VDS, &tmp, 1);
278 }
279
280 st7789v_transmit(dev, ST7789V_CMD_PWCTRL1,
281 (uint8_t *)config->pwctrl1_param,
282 sizeof(config->pwctrl1_param));
283
284 /* Memory Data Access Control */
285 tmp = config->mdac;
286 st7789v_transmit(dev, ST7789V_CMD_MADCTL, &tmp, 1);
287
288 /* Interface Pixel Format */
289 tmp = config->colmod;
290 st7789v_transmit(dev, ST7789V_CMD_COLMOD, &tmp, 1);
291
292 tmp = config->lcm;
293 st7789v_transmit(dev, ST7789V_CMD_LCMCTRL, &tmp, 1);
294
295 tmp = config->gamma;
296 st7789v_transmit(dev, ST7789V_CMD_GAMSET, &tmp, 1);
297
298 if (config->inversion_on) {
299 st7789v_transmit(dev, ST7789V_CMD_INV_ON, NULL, 0);
300 } else {
301 st7789v_transmit(dev, ST7789V_CMD_INV_OFF, NULL, 0);
302 }
303
304 st7789v_transmit(dev, ST7789V_CMD_PVGAMCTRL,
305 (uint8_t *)config->pvgam_param,
306 sizeof(config->pvgam_param));
307
308 st7789v_transmit(dev, ST7789V_CMD_NVGAMCTRL,
309 (uint8_t *)config->nvgam_param,
310 sizeof(config->nvgam_param));
311
312 st7789v_transmit(dev, ST7789V_CMD_RAMCTRL,
313 (uint8_t *)config->ram_param,
314 sizeof(config->ram_param));
315
316 st7789v_transmit(dev, ST7789V_CMD_RGBCTRL,
317 (uint8_t *)config->rgb_param,
318 sizeof(config->rgb_param));
319 }
320
321 static int st7789v_init(const struct device *dev)
322 {
323 const struct st7789v_config *config = dev->config;
324
325 if (!device_is_ready(config->mipi_dbi)) {
326 LOG_ERR("MIPI DBI device not ready");
327 return -ENODEV;
328 }
329
330 st7789v_reset_display(dev);
331
332 st7789v_blanking_on(dev);
333
334 st7789v_lcd_init(dev);
335
336 st7789v_exit_sleep(dev);
337
338 return 0;
339 }
340
341 #ifdef CONFIG_PM_DEVICE
342 static int st7789v_pm_action(const struct device *dev,
343 enum pm_device_action action)
344 {
345 int ret = 0;
346
347 switch (action) {
348 case PM_DEVICE_ACTION_RESUME:
349 st7789v_exit_sleep(dev);
350 break;
351 case PM_DEVICE_ACTION_SUSPEND:
352 st7789v_transmit(dev, ST7789V_CMD_SLEEP_IN, NULL, 0);
353 break;
354 default:
355 ret = -ENOTSUP;
356 break;
357 }
358
359 return ret;
360 }
361 #endif /* CONFIG_PM_DEVICE */
362
363 static DEVICE_API(display, st7789v_api) = {
364 .blanking_on = st7789v_blanking_on,
365 .blanking_off = st7789v_blanking_off,
366 .write = st7789v_write,
367 .get_capabilities = st7789v_get_capabilities,
368 .set_pixel_format = st7789v_set_pixel_format,
369 .set_orientation = st7789v_set_orientation,
370 };
371
372 #define ST7789V_WORD_SIZE(inst) \
373 ((DT_INST_STRING_UPPER_TOKEN(inst, mipi_mode) == MIPI_DBI_MODE_SPI_4WIRE) ? \
374 SPI_WORD_SET(8) : SPI_WORD_SET(9))
375 #define ST7789V_INIT(inst) \
376 static const struct st7789v_config st7789v_config_ ## inst = { \
377 .mipi_dbi = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
378 .dbi_config = MIPI_DBI_CONFIG_DT_INST(inst, \
379 ST7789V_WORD_SIZE(inst) | \
380 SPI_OP_MODE_MASTER, 0), \
381 .vcom = DT_INST_PROP(inst, vcom), \
382 .gctrl = DT_INST_PROP(inst, gctrl), \
383 .vdv_vrh_enable = (DT_INST_NODE_HAS_PROP(inst, vrhs) \
384 && DT_INST_NODE_HAS_PROP(inst, vdvs)), \
385 .vrh_value = DT_INST_PROP_OR(inst, vrhs, 0), \
386 .vdv_value = DT_INST_PROP_OR(inst, vdvs, 0), \
387 .mdac = DT_INST_PROP(inst, mdac), \
388 .gamma = DT_INST_PROP(inst, gamma), \
389 .colmod = DT_INST_PROP(inst, colmod), \
390 .lcm = DT_INST_PROP(inst, lcm), \
391 .inversion_on = !DT_INST_PROP(inst, inversion_off), \
392 .porch_param = DT_INST_PROP(inst, porch_param), \
393 .cmd2en_param = DT_INST_PROP(inst, cmd2en_param), \
394 .pwctrl1_param = DT_INST_PROP(inst, pwctrl1_param), \
395 .pvgam_param = DT_INST_PROP(inst, pvgam_param), \
396 .nvgam_param = DT_INST_PROP(inst, nvgam_param), \
397 .ram_param = DT_INST_PROP(inst, ram_param), \
398 .rgb_param = DT_INST_PROP(inst, rgb_param), \
399 .width = DT_INST_PROP(inst, width), \
400 .height = DT_INST_PROP(inst, height), \
401 }; \
402 \
403 static struct st7789v_data st7789v_data_ ## inst = { \
404 .x_offset = DT_INST_PROP(inst, x_offset), \
405 .y_offset = DT_INST_PROP(inst, y_offset), \
406 }; \
407 \
408 PM_DEVICE_DT_INST_DEFINE(inst, st7789v_pm_action); \
409 \
410 DEVICE_DT_INST_DEFINE(inst, &st7789v_init, PM_DEVICE_DT_INST_GET(inst), \
411 &st7789v_data_ ## inst, &st7789v_config_ ## inst, \
412 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, \
413 &st7789v_api);
414
415 DT_INST_FOREACH_STATUS_OKAY(ST7789V_INIT)
416