1 /*
2  * Copyright (c) 2024 Renesas Electronics Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ilitek_ili9806e_dsi
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/display.h>
11 #include <zephyr/drivers/mipi_dsi.h>
12 #include <zephyr/drivers/gpio.h>
13 #include <zephyr/sys/byteorder.h>
14 #include <zephyr/logging/log.h>
15 #include "display_ili9806e_dsi.h"
16 LOG_MODULE_REGISTER(display_ili9806e_dsi, CONFIG_DISPLAY_LOG_LEVEL);
17 
18 #define ILITEK_ILI9806E_COLMOD_RGB565 0x50
19 #define ILITEK_ILI9806E_COLMOD_RGB888 0x70
20 struct ili9806e_config {
21 	const struct device *mipi_dsi;
22 	const struct gpio_dt_spec reset;
23 	const struct gpio_dt_spec backlight;
24 	enum display_pixel_format pixel_format;
25 	uint8_t data_lanes;
26 	uint16_t width;
27 	uint16_t height;
28 	uint8_t channel;
29 };
30 
31 struct ili9806e_init_cmd {
32 	uint8_t reg;
33 	uint8_t cmd_len;
34 	uint8_t cmd[5];
35 } __packed;
36 
37 static const struct ili9806e_init_cmd init_cmds[] = {
38 	/* Change to Page 1 CMD */
39 	{.reg = 0xff, .cmd_len = 5, .cmd = {0xFF, 0x98, 0x06, 0x04, 0x01}},
40 	/* Output SDA */
41 	{.reg = 0x08, .cmd_len = 1, .cmd = {0x10}},
42 	/* DE = 1 Active */
43 	{.reg = 0x21, .cmd_len = 1, .cmd = {0x01}},
44 	/* Resolution setting 480 X 800 */
45 	{.reg = 0x30, .cmd_len = 1, .cmd = {0x01}},
46 	/* Inversion setting */
47 	{.reg = 0x31, .cmd_len = 1, .cmd = {0x00}},
48 	/* BT 15 */
49 	{.reg = 0x40, .cmd_len = 1, .cmd = {0x14}},
50 	/* avdd +5.2v,avee-5.2v */
51 	{.reg = 0x41, .cmd_len = 1, .cmd = {0x33}},
52 	/* VGL=DDVDL+VCL-VCIP,VGH=2DDVDH-DDVDL */
53 	{.reg = 0x42, .cmd_len = 1, .cmd = {0x02}},
54 	/* Set VGH clamp level */
55 	{.reg = 0x43, .cmd_len = 1, .cmd = {0x09}},
56 	/* Set VGL clamp level */
57 	{.reg = 0x44, .cmd_len = 1, .cmd = {0x06}},
58 	/* Set VREG1 */
59 	{.reg = 0x50, .cmd_len = 1, .cmd = {0x70}},
60 	/* Set VREG2 */
61 	{.reg = 0x51, .cmd_len = 1, .cmd = {0x70}},
62 	/* Flicker MSB */
63 	{.reg = 0x52, .cmd_len = 1, .cmd = {0x00}},
64 	/* Flicker LSB */
65 	{.reg = 0x53, .cmd_len = 1, .cmd = {0x48}},
66 	/* Timing Adjust */
67 	{.reg = 0x60, .cmd_len = 1, .cmd = {0x07}},
68 	{.reg = 0x61, .cmd_len = 1, .cmd = {0x00}},
69 	{.reg = 0x62, .cmd_len = 1, .cmd = {0x08}},
70 	{.reg = 0x63, .cmd_len = 1, .cmd = {0x00}},
71 	/* Positive Gamma Control 1 */
72 	{.reg = 0xa0, .cmd_len = 1, .cmd = {0x00}},
73 	/* Positive Gamma Control 2 */
74 	{.reg = 0xa1, .cmd_len = 1, .cmd = {0x03}},
75 	/* Positive Gamma Control 3 */
76 	{.reg = 0xa2, .cmd_len = 1, .cmd = {0x09}},
77 	/* Positive Gamma Control 4 */
78 	{.reg = 0xa3, .cmd_len = 1, .cmd = {0x0d}},
79 	/* Positive Gamma Control 5 */
80 	{.reg = 0xa4, .cmd_len = 1, .cmd = {0x06}},
81 	/* Positive Gamma Control 6 */
82 	{.reg = 0xa5, .cmd_len = 1, .cmd = {0x16}},
83 	/* Positive Gamma Control 7 */
84 	{.reg = 0xa6, .cmd_len = 1, .cmd = {0x09}},
85 	/* Positive Gamma Control 8 */
86 	{.reg = 0xa7, .cmd_len = 1, .cmd = {0x08}},
87 	/* Positive Gamma Control 9 */
88 	{.reg = 0xa8, .cmd_len = 1, .cmd = {0x03}},
89 	/* Positive Gamma Control 10 */
90 	{.reg = 0xa9, .cmd_len = 1, .cmd = {0x07}},
91 	/* Positive Gamma Control 11 */
92 	{.reg = 0xaa, .cmd_len = 1, .cmd = {0x06}},
93 	/* Positive Gamma Control 12 */
94 	{.reg = 0xab, .cmd_len = 1, .cmd = {0x05}},
95 	/* Positive Gamma Control 13 */
96 	{.reg = 0xac, .cmd_len = 1, .cmd = {0x0d}},
97 	/* Positive Gamma Control 14 */
98 	{.reg = 0xad, .cmd_len = 1, .cmd = {0x2c}},
99 	/* Positive Gamma Control 15 */
100 	{.reg = 0xae, .cmd_len = 1, .cmd = {0x26}},
101 	/* Positive Gamma Control 16 */
102 	{.reg = 0xaf, .cmd_len = 1, .cmd = {0x00}},
103 	/* Negative Gamma Correction 1 */
104 	{.reg = 0xc0, .cmd_len = 1, .cmd = {0x00}},
105 	/* Negative Gamma Correction 2 */
106 	{.reg = 0xc1, .cmd_len = 1, .cmd = {0x04}},
107 	/* Negative Gamma Correction 3 */
108 	{.reg = 0xc2, .cmd_len = 1, .cmd = {0x0b}},
109 	/* Negative Gamma Correction 4 */
110 	{.reg = 0xc3, .cmd_len = 1, .cmd = {0x0f}},
111 	/* Negative Gamma Correction 5 */
112 	{.reg = 0xc4, .cmd_len = 1, .cmd = {0x09}},
113 	/* Negative Gamma Correction 6 */
114 	{.reg = 0xc5, .cmd_len = 1, .cmd = {0x18}},
115 	/* Negative Gamma Correction 7 */
116 	{.reg = 0xc6, .cmd_len = 1, .cmd = {0x07}},
117 	/* Negative Gamma Correction 8 */
118 	{.reg = 0xc7, .cmd_len = 1, .cmd = {0x08}},
119 	/* Negative Gamma Correction 9 */
120 	{.reg = 0xc8, .cmd_len = 1, .cmd = {0x05}},
121 	/* Negative Gamma Correction 10 */
122 	{.reg = 0xc9, .cmd_len = 1, .cmd = {0x09}},
123 	/* Negative Gamma Correction 11 */
124 	{.reg = 0xca, .cmd_len = 1, .cmd = {0x07}},
125 	/* Negative Gamma Correction 12 */
126 	{.reg = 0xcb, .cmd_len = 1, .cmd = {0x05}},
127 	/* Negative Gamma Correction 13 */
128 	{.reg = 0xcc, .cmd_len = 1, .cmd = {0x0c}},
129 	/* Negative Gamma Correction 14 */
130 	{.reg = 0xcd, .cmd_len = 1, .cmd = {0x2d}},
131 	/* Negative Gamma Correction 15 */
132 	{.reg = 0xce, .cmd_len = 1, .cmd = {0x28}},
133 	/* Negative Gamma Correction 16 */
134 	{.reg = 0xcf, .cmd_len = 1, .cmd = {0x00}},
135 
136 	/* Change to Page 6 CMD for GIP timing */
137 	{.reg = 0xff, .cmd_len = 5, .cmd = {0xFF, 0x98, 0x06, 0x04, 0x06}},
138 	/* GIP Control 1 */
139 	{.reg = 0x00, .cmd_len = 1, .cmd = {0x21}},
140 	{.reg = 0x01, .cmd_len = 1, .cmd = {0x09}},
141 	{.reg = 0x02, .cmd_len = 1, .cmd = {0x00}},
142 	{.reg = 0x03, .cmd_len = 1, .cmd = {0x00}},
143 	{.reg = 0x04, .cmd_len = 1, .cmd = {0x01}},
144 	{.reg = 0x05, .cmd_len = 1, .cmd = {0x01}},
145 	{.reg = 0x06, .cmd_len = 1, .cmd = {0x80}},
146 	{.reg = 0x07, .cmd_len = 1, .cmd = {0x05}},
147 	{.reg = 0x08, .cmd_len = 1, .cmd = {0x02}},
148 	{.reg = 0x09, .cmd_len = 1, .cmd = {0x80}},
149 	{.reg = 0x0a, .cmd_len = 1, .cmd = {0x00}},
150 	{.reg = 0x0b, .cmd_len = 1, .cmd = {0x00}},
151 	{.reg = 0x0c, .cmd_len = 1, .cmd = {0x0a}},
152 	{.reg = 0x0d, .cmd_len = 1, .cmd = {0x0a}},
153 	{.reg = 0x0e, .cmd_len = 1, .cmd = {0x00}},
154 	{.reg = 0x0f, .cmd_len = 1, .cmd = {0x00}},
155 	{.reg = 0x10, .cmd_len = 1, .cmd = {0xe0}},
156 	{.reg = 0x11, .cmd_len = 1, .cmd = {0xe4}},
157 	{.reg = 0x12, .cmd_len = 1, .cmd = {0x04}},
158 	{.reg = 0x13, .cmd_len = 1, .cmd = {0x00}},
159 	{.reg = 0x14, .cmd_len = 1, .cmd = {0x00}},
160 	{.reg = 0x15, .cmd_len = 1, .cmd = {0xc0}},
161 	{.reg = 0x16, .cmd_len = 1, .cmd = {0x08}},
162 	{.reg = 0x17, .cmd_len = 1, .cmd = {0x00}},
163 	{.reg = 0x18, .cmd_len = 1, .cmd = {0x00}},
164 	{.reg = 0x19, .cmd_len = 1, .cmd = {0x00}},
165 	{.reg = 0x1a, .cmd_len = 1, .cmd = {0x00}},
166 	{.reg = 0x1b, .cmd_len = 1, .cmd = {0x00}},
167 	{.reg = 0x1c, .cmd_len = 1, .cmd = {0x00}},
168 	{.reg = 0x1d, .cmd_len = 1, .cmd = {0x00}},
169 	/* GIP Control 2 */
170 	{.reg = 0x20, .cmd_len = 1, .cmd = {0x01}},
171 	{.reg = 0x21, .cmd_len = 1, .cmd = {0x23}},
172 	{.reg = 0x22, .cmd_len = 1, .cmd = {0x45}},
173 	{.reg = 0x23, .cmd_len = 1, .cmd = {0x67}},
174 	{.reg = 0x24, .cmd_len = 1, .cmd = {0x01}},
175 	{.reg = 0x25, .cmd_len = 1, .cmd = {0x23}},
176 	{.reg = 0x26, .cmd_len = 1, .cmd = {0x45}},
177 	{.reg = 0x27, .cmd_len = 1, .cmd = {0x67}},
178 	/* GIP Control 3 */
179 	{.reg = 0x30, .cmd_len = 1, .cmd = {0x01}},
180 	{.reg = 0x31, .cmd_len = 1, .cmd = {0x11}},
181 	{.reg = 0x32, .cmd_len = 1, .cmd = {0x00}},
182 	{.reg = 0x33, .cmd_len = 1, .cmd = {0xee}},
183 	{.reg = 0x34, .cmd_len = 1, .cmd = {0xff}},
184 	{.reg = 0x35, .cmd_len = 1, .cmd = {0xcb}},
185 	{.reg = 0x36, .cmd_len = 1, .cmd = {0xda}},
186 	{.reg = 0x37, .cmd_len = 1, .cmd = {0xad}},
187 	{.reg = 0x38, .cmd_len = 1, .cmd = {0xbc}},
188 	{.reg = 0x39, .cmd_len = 1, .cmd = {0x76}},
189 	{.reg = 0x3a, .cmd_len = 1, .cmd = {0x67}},
190 	{.reg = 0x3b, .cmd_len = 1, .cmd = {0x22}},
191 	{.reg = 0x3c, .cmd_len = 1, .cmd = {0x22}},
192 	{.reg = 0x3d, .cmd_len = 1, .cmd = {0x22}},
193 	{.reg = 0x3e, .cmd_len = 1, .cmd = {0x22}},
194 	{.reg = 0x3f, .cmd_len = 1, .cmd = {0x22}},
195 	{.reg = 0x40, .cmd_len = 1, .cmd = {0x22}},
196 	/* GOUT VGLO Control */
197 	{.reg = 0x53, .cmd_len = 1, .cmd = {0x10}},
198 	{.reg = 0x54, .cmd_len = 1, .cmd = {0x10}},
199 	/* Change to Page 7 CMD for Normal command */
200 	{.reg = 0xff, .cmd_len = 5, .cmd = {0xff, 0x98, 0x06, 0x04, 0x07}},
201 	/* VREG1/2OUT ENABLE */
202 	{.reg = 0x18, .cmd_len = 1, .cmd = {0x1d}},
203 	{.reg = 0x26, .cmd_len = 1, .cmd = {0xb2}},
204 	{.reg = 0x02, .cmd_len = 1, .cmd = {0x77}},
205 	{.reg = 0xe1, .cmd_len = 1, .cmd = {0x79}},
206 	{.reg = 0x17, .cmd_len = 1, .cmd = {0x22}},
207 	/* Change to Page 0 CMD for Normal command */
208 	{.reg = 0xff, .cmd_len = 5, .cmd = {0xff, 0x98, 0x06, 0x04, 0x00}}};
209 
ili9806e_write_reg(const struct device * dev,uint8_t reg,const uint8_t * buf,size_t len)210 static int ili9806e_write_reg(const struct device *dev, uint8_t reg, const uint8_t *buf, size_t len)
211 {
212 	int ret;
213 	const struct ili9806e_config *cfg = dev->config;
214 
215 	ret = mipi_dsi_dcs_write(cfg->mipi_dsi, cfg->channel, reg, buf, len);
216 	if (ret < 0) {
217 		LOG_ERR("Failed writing reg: 0x%x result: (%d)", reg, ret);
218 		return ret;
219 	}
220 
221 	return 0;
222 }
223 
ili9806e_write_reg_val(const struct device * dev,uint8_t reg,uint8_t value)224 static int ili9806e_write_reg_val(const struct device *dev, uint8_t reg, uint8_t value)
225 {
226 	return ili9806e_write_reg(dev, reg, &value, 1);
227 }
228 
ili9806e_write_sequence(const struct device * dev,const struct ili9806e_init_cmd * cmd,uint8_t nr_cmds)229 static int ili9806e_write_sequence(const struct device *dev, const struct ili9806e_init_cmd *cmd,
230 				   uint8_t nr_cmds)
231 {
232 	int ret = 0;
233 
234 	/* Loop through all commands as long as writes are successful */
235 	for (int i = 0; i < nr_cmds && ret == 0; i++) {
236 		ret = ili9806e_write_reg(dev, cmd->reg, cmd->cmd, cmd->cmd_len);
237 		if (ret < 0) {
238 			LOG_ERR("Failed writing sequence: 0x%x result: (%d)", cmd->reg, ret);
239 			return ret;
240 		}
241 		cmd++;
242 	}
243 
244 	return ret;
245 }
246 
ili9806e_config(const struct device * dev)247 static int ili9806e_config(const struct device *dev)
248 {
249 	const struct ili9806e_config *cfg = dev->config;
250 	int ret;
251 
252 	ret = ili9806e_write_sequence(dev, init_cmds, ARRAY_SIZE(init_cmds));
253 	if (ret < 0) {
254 		return ret;
255 	}
256 	/* Add a delay, otherwise MADCTL not taken */
257 	k_msleep(120);
258 
259 	/* Exit sleep mode */
260 	ret = ili9806e_write_reg(dev, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
261 	if (ret < 0) {
262 		return ret;
263 	}
264 
265 	/* Wait for sleep out exit */
266 	k_msleep(5);
267 
268 	/* Set color mode */
269 	ret = ili9806e_write_reg_val(dev, MIPI_DCS_SET_PIXEL_FORMAT,
270 				     cfg->pixel_format == PIXEL_FORMAT_RGB_565
271 					     ? ILITEK_ILI9806E_COLMOD_RGB565
272 					     : ILITEK_ILI9806E_COLMOD_RGB888);
273 	if (ret < 0) {
274 		return ret;
275 	}
276 
277 	/* Turn on display */
278 	ret = ili9806e_write_reg(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
279 	return ret;
280 }
281 
ili9806e_blanking_on(const struct device * dev)282 static int ili9806e_blanking_on(const struct device *dev)
283 {
284 	const struct ili9806e_config *cfg = dev->config;
285 	int ret;
286 
287 	if (cfg->backlight.port != NULL) {
288 		ret = gpio_pin_set_dt(&cfg->backlight, 0);
289 		if (ret) {
290 			LOG_ERR("Disable backlight failed! (%d)", ret);
291 			return ret;
292 		}
293 	}
294 
295 	return ili9806e_write_reg(dev, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
296 }
297 
ili9806e_blanking_off(const struct device * dev)298 static int ili9806e_blanking_off(const struct device *dev)
299 {
300 	const struct ili9806e_config *cfg = dev->config;
301 	int ret;
302 
303 	if (cfg->backlight.port != NULL) {
304 		ret = gpio_pin_set_dt(&cfg->backlight, 1);
305 		if (ret) {
306 			LOG_ERR("Enable backlight failed! (%d)", ret);
307 			return ret;
308 		}
309 	}
310 
311 	return ili9806e_write_reg(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
312 }
313 
ili9806e_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)314 static void ili9806e_get_capabilities(const struct device *dev,
315 				      struct display_capabilities *capabilities)
316 {
317 	const struct ili9806e_config *cfg = dev->config;
318 
319 	memset(capabilities, 0, sizeof(struct display_capabilities));
320 	capabilities->x_resolution = cfg->width;
321 	capabilities->y_resolution = cfg->height;
322 	capabilities->supported_pixel_formats = cfg->pixel_format;
323 	capabilities->current_pixel_format = cfg->pixel_format;
324 }
325 
ili9806e_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)326 static int ili9806e_pixel_format(const struct device *dev,
327 				 const enum display_pixel_format pixel_format)
328 {
329 	const struct ili9806e_config *config = dev->config;
330 
331 	LOG_WRN("Pixel format change not implemented");
332 	if (pixel_format == config->pixel_format) {
333 		return 0;
334 	}
335 
336 	return -ENOTSUP;
337 }
338 
339 static DEVICE_API(display, ili9806e_api) = {
340 	.blanking_on = ili9806e_blanking_on,
341 	.blanking_off = ili9806e_blanking_off,
342 	.set_pixel_format = ili9806e_pixel_format,
343 	.get_capabilities = ili9806e_get_capabilities,
344 };
345 
ili9806e_init(const struct device * dev)346 static int ili9806e_init(const struct device *dev)
347 {
348 	const struct ili9806e_config *cfg = dev->config;
349 	struct mipi_dsi_device mdev;
350 	int ret;
351 
352 	if (cfg->reset.port) {
353 		if (!gpio_is_ready_dt(&cfg->reset)) {
354 			LOG_ERR("Reset GPIO device is not ready!");
355 			return -ENODEV;
356 		}
357 		k_sleep(K_MSEC(1));
358 
359 		ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_INACTIVE);
360 		if (ret < 0) {
361 			LOG_ERR("Reset display failed! (%d)", ret);
362 			return ret;
363 		}
364 
365 		ret = gpio_pin_set_dt(&cfg->reset, 0);
366 		if (ret < 0) {
367 			LOG_ERR("Reset display failed! (%d)", ret);
368 			return ret;
369 		}
370 		k_sleep(K_MSEC(1));
371 
372 		ret = gpio_pin_set_dt(&cfg->reset, 1);
373 		if (ret < 0) {
374 			LOG_ERR("Enable display failed! (%d)", ret);
375 			return ret;
376 		}
377 		k_sleep(K_MSEC(50));
378 	}
379 
380 	/* attach to MIPI-DSI host */
381 	if (cfg->pixel_format == PIXEL_FORMAT_RGB_565) {
382 		mdev.pixfmt = MIPI_DSI_PIXFMT_RGB565;
383 	} else {
384 		mdev.pixfmt = MIPI_DSI_PIXFMT_RGB888;
385 	}
386 	mdev.data_lanes = cfg->data_lanes;
387 	mdev.mode_flags = MIPI_DSI_MODE_VIDEO;
388 	mdev.timings.hactive = cfg->width;
389 	mdev.timings.hbp = ILITEK_ILI9806E_HBP;
390 	mdev.timings.hfp = ILITEK_ILI9806E_HFP;
391 	mdev.timings.hsync = ILITEK_ILI9806E_HSYNC;
392 	mdev.timings.vactive = cfg->height;
393 	mdev.timings.vbp = ILITEK_ILI9806E_VBP;
394 	mdev.timings.vfp = ILITEK_ILI9806E_VFP;
395 	mdev.timings.vsync = ILITEK_ILI9806E_VSYNC;
396 
397 	ret = mipi_dsi_attach(cfg->mipi_dsi, cfg->channel, &mdev);
398 	if (ret < 0) {
399 		LOG_ERR("Could not attach to MIPI-DSI host");
400 		return ret;
401 	}
402 
403 	if (cfg->backlight.port != NULL) {
404 		ret = gpio_pin_configure_dt(&cfg->backlight, GPIO_OUTPUT_ACTIVE);
405 		if (ret < 0) {
406 			LOG_ERR("Could not configure backlight GPIO (%d)", ret);
407 			return ret;
408 		}
409 	}
410 
411 	ret = ili9806e_config(dev);
412 	if (ret) {
413 		LOG_ERR("DSI init sequence failed! (%d)", ret);
414 		return ret;
415 	}
416 
417 	return 0;
418 }
419 
420 #define ILITEK_ILI9806E_DEFINE(n)                                                                  \
421 	static const struct ili9806e_config ili9806e_config_##n = {                                \
422 		.mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(n)),                                         \
423 		.reset = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}),                            \
424 		.backlight = GPIO_DT_SPEC_INST_GET_OR(n, bl_gpios, {0}),                           \
425 		.data_lanes = DT_INST_PROP_BY_IDX(n, data_lanes, 0),                               \
426 		.width = DT_INST_PROP(n, width),                                                   \
427 		.height = DT_INST_PROP(n, height),                                                 \
428 		.channel = DT_INST_REG_ADDR(n),                                                    \
429 		.pixel_format = DT_INST_PROP(n, pixel_format),                                     \
430 	};                                                                                         \
431 	DEVICE_DT_INST_DEFINE(n, &ili9806e_init, NULL, NULL, &ili9806e_config_##n, POST_KERNEL,    \
432 			      CONFIG_DISPLAY_ILI9806E_DSI_INIT_PRIORITY, &ili9806e_api);
433 
434 DT_INST_FOREACH_STATUS_OKAY(ILITEK_ILI9806E_DEFINE)
435