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