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