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