1 /*
2 * Copyright (c) 2024 Erik Andersson <erian747@gmail.com>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 *
6 * Based on the NT35510 driver provided by STMicroelectronics at
7 * https://github.com/STMicroelectronics/stm32-nt35510/blob/main/nt35510.c
8 */
9
10 #define DT_DRV_COMPAT frida_nt35510
11
12 #include <errno.h>
13 #include <string.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/drivers/display.h>
16 #include <zephyr/drivers/mipi_dsi.h>
17 #include <zephyr/drivers/gpio.h>
18 #include <zephyr/sys/byteorder.h>
19 #include <zephyr/logging/log.h>
20 #include "display_nt35510.h"
21
22 LOG_MODULE_REGISTER(nt35510, CONFIG_DISPLAY_LOG_LEVEL);
23
24 /**
25 * @brief Possible values of COLMOD parameter corresponding to used pixel formats
26 */
27 #define NT35510_COLMOD_RGB565 0x55
28 #define NT35510_COLMOD_RGB888 0x77
29
30 /**
31 * @brief NT35510_480X800 Timing parameters for Portrait orientation mode
32 */
33 #define NT35510_480X800_HSYNC ((uint16_t)2) /* Horizontal synchronization */
34 #define NT35510_480X800_HBP ((uint16_t)34) /* Horizontal back porch */
35 #define NT35510_480X800_HFP ((uint16_t)34) /* Horizontal front porch */
36
37 #define NT35510_480X800_VSYNC ((uint16_t)120) /* Vertical synchronization */
38 #define NT35510_480X800_VBP ((uint16_t)150) /* Vertical back porch */
39 #define NT35510_480X800_VFP ((uint16_t)150) /* Vertical front porch */
40
41 /**
42 * @brief NT35510_800X480 Timing parameters for Landscape orientation mode
43 * Same values as for Portrait mode in fact.
44 */
45 #define NT35510_800X480_HSYNC NT35510_480X800_VSYNC /* Horizontal synchronization */
46 #define NT35510_800X480_HBP NT35510_480X800_VBP /* Horizontal back porch */
47 #define NT35510_800X480_HFP NT35510_480X800_VFP /* Horizontal front porch */
48 #define NT35510_800X480_VSYNC NT35510_480X800_HSYNC /* Vertical synchronization */
49 #define NT35510_800X480_VBP NT35510_480X800_HBP /* Vertical back porch */
50 #define NT35510_800X480_VFP NT35510_480X800_HFP /* Vertical front porch */
51
52 struct nt35510_config {
53 const struct device *mipi_dsi;
54 const struct gpio_dt_spec reset;
55 const struct gpio_dt_spec backlight;
56 uint8_t data_lanes;
57 uint16_t width;
58 uint16_t height;
59 uint8_t channel;
60 uint16_t rotation;
61 };
62
63 struct nt35510_data {
64 enum display_pixel_format pixel_format;
65 enum display_orientation orientation;
66 uint16_t xres;
67 uint16_t yres;
68 };
69
70 struct nt35510_init_cmd {
71 uint8_t reg;
72 uint8_t cmd_len;
73 uint8_t cmd[5];
74 } __packed;
75
76 static const struct nt35510_init_cmd init_cmds[] = {
77 /* LV2: Page 1 enable */
78 {.reg = 0xf0, .cmd_len = 5, .cmd = {0x55, 0xaa, 0x52, 0x08, 0x01}},
79 /* AVDD: 5.2V */
80 {.reg = 0xb0, .cmd_len = 3, .cmd = {0x03, 0x03, 0x03}},
81 /* AVDD: Ratio */
82 {.reg = 0xb6, .cmd_len = 3, .cmd = {0x46, 0x46, 0x46}},
83 /* AVEE: -5.2V */
84 {.reg = 0xb1, .cmd_len = 3, .cmd = {0x03, 0x03, 0x03}},
85 /* AVEE: Ratio */
86 {.reg = 0xb7, .cmd_len = 3, .cmd = {0x36, 0x36, 0x36}},
87 /* VCL: -2.5V */
88 {.reg = 0xb2, .cmd_len = 3, .cmd = {0x00, 0x00, 0x02}},
89 /* VCL: Ratio */
90 {.reg = 0xb8, .cmd_len = 3, .cmd = {0x26, 0x26, 0x26}},
91 /* VGH: 15V (Free Pump) */
92 {.reg = 0xbf, .cmd_len = 1, .cmd = {0x01}},
93 /* Frida LCD MFR specific */
94 {.reg = 0xb3, .cmd_len = 3, .cmd = {0x09, 0x09, 0x09}},
95 /* VGH: Ratio */
96 {.reg = 0xb9, .cmd_len = 3, .cmd = {0x36, 0x36, 0x36}},
97 /* VGL_REG: -10V */
98 {.reg = 0xb5, .cmd_len = 3, .cmd = {0x08, 0x08, 0x08}},
99 /* VGLX: Ratio */
100 {.reg = 0xba, .cmd_len = 3, .cmd = {0x26, 0x26, 0x26}},
101 /* VGMP/VGSP: 4.5V/0V */
102 {.reg = 0xbc, .cmd_len = 3, .cmd = {0x00, 0x80, 0x00}},
103 /* VGMN/VGSN:-4.5V/0V */
104 {.reg = 0xbd, .cmd_len = 3, .cmd = {0x00, 0x80, 0x00}},
105 /* VCOM: -1.325V */
106 {.reg = 0xbe, .cmd_len = 2, .cmd = {0x00, 0x50}},
107
108 /* LV2: Page 0 enable */
109 {.reg = 0xf0, .cmd_len = 5, .cmd = {0x55, 0xaa, 0x52, 0x08, 0x00}},
110 /* Display optional control */
111 {.reg = 0xb1, .cmd_len = 2, .cmd = {0xfc, 0x00}},
112 /* Set source output data hold time */
113 {.reg = 0xb6, .cmd_len = 1, .cmd = {0x03}},
114 /* Display resolution control */
115 {.reg = 0xb5, .cmd_len = 1, .cmd = {0x51}},
116 /* Gate EQ control */
117 {.reg = 0xb7, .cmd_len = 2, .cmd = {0x00, 0x00}},
118 /* Src EQ control(Mode2) */
119 {.reg = 0xb8, .cmd_len = 4, .cmd = {0x01, 0x02, 0x02, 0x02}},
120 /* Frida LCD MFR specific */
121 {.reg = 0xbc, .cmd_len = 3, .cmd = {0x00, 0x00, 0x00}},
122 /* Frida LCD MFR specific */
123 {.reg = 0xcc, .cmd_len = 3, .cmd = {0x03, 0x00, 0x00}},
124 /* Frida LCD MFR specific */
125 {.reg = 0xba, .cmd_len = 1, .cmd = {0x01}}};
126
127 static const struct nt35510_init_cmd portrait_cmds[] = {
128 {.reg = NT35510_CMD_MADCTL, .cmd_len = 1, .cmd = {0x00}},
129 {.reg = NT35510_CMD_CASET, .cmd_len = 4, .cmd = {0x00, 0x00, 0x01, 0xdf}},
130 {.reg = NT35510_CMD_RASET, .cmd_len = 4, .cmd = {0x00, 0x00, 0x03, 0x1f}}};
131
132 static const struct nt35510_init_cmd landscape_cmds[] = {
133 {.reg = NT35510_CMD_MADCTL, .cmd_len = 1, .cmd = {0x60}},
134 {.reg = NT35510_CMD_CASET, .cmd_len = 4, .cmd = {0x00, 0x00, 0x03, 0x1f}},
135 {.reg = NT35510_CMD_RASET, .cmd_len = 4, .cmd = {0x00, 0x00, 0x01, 0xdf}}};
136
137 static const struct nt35510_init_cmd turn_on_cmds[] = {
138 /* Content Adaptive Backlight Control section start */
139 {.reg = NT35510_CMD_WRDISBV, .cmd_len = 1, .cmd = {0x7f}},
140 /* Brightness Control Block, Display Dimming & BackLight on */
141 {.reg = NT35510_CMD_WRCTRLD, .cmd_len = 1, .cmd = {0x2c}},
142 /* Image Content based Adaptive Brightness [Still Picture] */
143 {.reg = NT35510_CMD_WRCABC, .cmd_len = 1, .cmd = {0x02}},
144 /* Brightness, use maximum as default */
145 {.reg = NT35510_CMD_WRCABCMB, .cmd_len = 1, .cmd = {0xff}},
146 /* Turn on display */
147 {.reg = MIPI_DCS_SET_DISPLAY_ON, .cmd_len = 0, .cmd = {}},
148 /* Send Command GRAM memory write (no parameters)
149 * this initiates frame write via other DSI commands sent by
150 * DSI host from LTDC incoming pixels in video mode
151 */
152 {.reg = NT35510_CMD_RAMWR, .cmd_len = 0, .cmd = {}},
153 };
154
155 /* Write data buffer to LCD register */
nt35510_write_reg(const struct device * dev,uint8_t reg,const uint8_t * buf,size_t len)156 static int nt35510_write_reg(const struct device *dev, uint8_t reg, const uint8_t *buf, size_t len)
157 {
158 int ret;
159 const struct nt35510_config *cfg = dev->config;
160
161 ret = mipi_dsi_dcs_write(cfg->mipi_dsi, cfg->channel, reg, buf, len);
162 if (ret < 0) {
163 LOG_ERR("Failed writing reg: 0x%x result: (%d)", reg, ret);
164 return ret;
165 }
166 return 0;
167 }
168
169 /* Write an 8-bit value to a register */
nt35510_write_reg_val(const struct device * dev,uint8_t reg,uint8_t value)170 static int nt35510_write_reg_val(const struct device *dev, uint8_t reg, uint8_t value)
171 {
172 return nt35510_write_reg(dev, reg, &value, 1);
173 }
174
175 /* Write a list of commands to registers */
nt35510_write_sequence(const struct device * dev,const struct nt35510_init_cmd * cmd,uint8_t nr_cmds)176 static int nt35510_write_sequence(const struct device *dev, const struct nt35510_init_cmd *cmd,
177 uint8_t nr_cmds)
178 {
179 int ret = 0;
180
181 /* Loop trough all commands as long as writes are successful*/
182 for (int i = 0; i < nr_cmds && ret == 0; i++) {
183 ret = nt35510_write_reg(dev, cmd->reg, cmd->cmd, cmd->cmd_len);
184 cmd++;
185 }
186 return ret;
187 }
188
189 /* Initialization, configuration turn on sequence */
nt35510_config(const struct device * dev)190 static int nt35510_config(const struct device *dev)
191 {
192 struct nt35510_data *data = dev->data;
193 int ret;
194
195 ret = nt35510_write_sequence(dev, init_cmds, ARRAY_SIZE(init_cmds));
196 if (ret < 0) {
197 return ret;
198 }
199 /* Add a delay, otherwise MADCTL not taken */
200 k_msleep(200);
201
202 /* Configure orientation */
203 if (data->orientation == DISPLAY_ORIENTATION_NORMAL) {
204 ret = nt35510_write_sequence(dev, portrait_cmds, ARRAY_SIZE(portrait_cmds));
205 } else {
206 ret = nt35510_write_sequence(dev, landscape_cmds, ARRAY_SIZE(landscape_cmds));
207 }
208 if (ret < 0) {
209 return ret;
210 }
211 /* Exit sleep mode */
212 ret = nt35510_write_reg(dev, NT35510_CMD_SLPOUT, NULL, 0);
213 if (ret < 0) {
214 return ret;
215 }
216
217 /* Wait for sleep out exit */
218 k_msleep(20);
219
220 /* Set color mode */
221 ret = nt35510_write_reg_val(dev, NT35510_CMD_COLMOD,
222 data->pixel_format == PIXEL_FORMAT_RGB_565
223 ? NT35510_COLMOD_RGB565
224 : NT35510_COLMOD_RGB888);
225 if (ret < 0) {
226 return ret;
227 }
228
229 /* Adjust brightness and turn on display */
230 ret = nt35510_write_sequence(dev, turn_on_cmds, ARRAY_SIZE(turn_on_cmds));
231 return ret;
232 }
233
nt35510_blanking_on(const struct device * dev)234 static int nt35510_blanking_on(const struct device *dev)
235 {
236 const struct nt35510_config *cfg = dev->config;
237 int ret;
238
239 if (cfg->backlight.port != NULL) {
240 ret = gpio_pin_set_dt(&cfg->backlight, 0);
241 if (ret) {
242 LOG_ERR("Disable backlight failed! (%d)", ret);
243 return ret;
244 }
245 }
246 return nt35510_write_reg(dev, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
247 }
248
nt35510_blanking_off(const struct device * dev)249 static int nt35510_blanking_off(const struct device *dev)
250 {
251 const struct nt35510_config *cfg = dev->config;
252 int ret;
253
254 if (cfg->backlight.port != NULL) {
255 ret = gpio_pin_set_dt(&cfg->backlight, 1);
256 if (ret) {
257 LOG_ERR("Enable backlight failed! (%d)", ret);
258 return ret;
259 }
260 }
261 return nt35510_write_reg(dev, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
262 }
263
nt35510_set_brightness(const struct device * dev,const uint8_t brightness)264 static int nt35510_set_brightness(const struct device *dev, const uint8_t brightness)
265 {
266 return nt35510_write_reg(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, &brightness, 1);
267 }
268
nt35510_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)269 static void nt35510_get_capabilities(const struct device *dev,
270 struct display_capabilities *capabilities)
271 {
272 const struct nt35510_config *cfg = dev->config;
273 struct nt35510_data *data = dev->data;
274
275 memset(capabilities, 0, sizeof(struct display_capabilities));
276 capabilities->x_resolution = cfg->width;
277 capabilities->y_resolution = cfg->height;
278 capabilities->supported_pixel_formats = PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_RGB_888;
279 capabilities->current_pixel_format = data->pixel_format;
280 capabilities->current_orientation = data->orientation;
281 }
282
nt35510_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)283 static int nt35510_set_pixel_format(const struct device *dev,
284 const enum display_pixel_format pixel_format)
285 {
286 struct nt35510_data *data = dev->data;
287
288 if (pixel_format == PIXEL_FORMAT_RGB_565 || pixel_format == PIXEL_FORMAT_RGB_888) {
289 data->pixel_format = pixel_format;
290 return 0;
291 }
292 LOG_ERR("Pixel format not supported");
293 return -ENOTSUP;
294 }
295
nt35510_check_id(const struct device * dev)296 static int nt35510_check_id(const struct device *dev)
297 {
298 const struct nt35510_config *cfg = dev->config;
299 uint8_t id = 0;
300 int ret;
301
302 ret = mipi_dsi_dcs_read(cfg->mipi_dsi, cfg->channel, NT35510_CMD_RDID2, &id, 1);
303 if (ret != sizeof(id)) {
304 LOG_ERR("Failed reading ID (%d)", ret);
305 return -EIO;
306 }
307
308 if (id != NT35510_ID) {
309 LOG_ERR("ID 0x%x, expected: 0x%x)", id, NT35510_ID);
310 return -EINVAL;
311 }
312 return 0;
313 }
314
nt35510_init(const struct device * dev)315 static int nt35510_init(const struct device *dev)
316 {
317 const struct nt35510_config *cfg = dev->config;
318 struct nt35510_data *data = dev->data;
319 struct mipi_dsi_device mdev;
320 int ret;
321
322 if (cfg->reset.port) {
323 if (!gpio_is_ready_dt(&cfg->reset)) {
324 LOG_ERR("Reset GPIO device is not ready!");
325 return -ENODEV;
326 }
327 ret = gpio_pin_configure_dt(&cfg->reset, GPIO_OUTPUT_INACTIVE);
328 if (ret < 0) {
329 LOG_ERR("Reset display failed! (%d)", ret);
330 return ret;
331 }
332 k_msleep(20);
333 ret = gpio_pin_set_dt(&cfg->reset, 1);
334 if (ret < 0) {
335 LOG_ERR("Enable display failed! (%d)", ret);
336 return ret;
337 }
338 k_msleep(200);
339 }
340
341 /* Store x/y resolution & rotation */
342 if (cfg->rotation == 0) {
343 data->xres = cfg->width;
344 data->yres = cfg->height;
345 data->orientation = DISPLAY_ORIENTATION_NORMAL;
346 } else if (cfg->rotation == 90) {
347 data->xres = cfg->height;
348 data->yres = cfg->width;
349 data->orientation = DISPLAY_ORIENTATION_ROTATED_90;
350 } else if (cfg->rotation == 180) {
351 data->xres = cfg->width;
352 data->yres = cfg->height;
353 data->orientation = DISPLAY_ORIENTATION_ROTATED_180;
354 } else if (cfg->rotation == 270) {
355 data->xres = cfg->height;
356 data->yres = cfg->width;
357 data->orientation = DISPLAY_ORIENTATION_ROTATED_270;
358 }
359
360 /* Attach to MIPI-DSI host */
361 mdev.data_lanes = cfg->data_lanes;
362 mdev.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | MIPI_DSI_MODE_LPM;
363
364 if (data->pixel_format == PIXEL_FORMAT_RGB_565) {
365 mdev.pixfmt = MIPI_DSI_PIXFMT_RGB565;
366 } else {
367 mdev.pixfmt = MIPI_DSI_PIXFMT_RGB888;
368 }
369
370 mdev.timings.hactive = data->xres;
371 mdev.timings.hbp = NT35510_480X800_HBP;
372 mdev.timings.hfp = NT35510_480X800_HFP;
373 mdev.timings.hsync = NT35510_480X800_HSYNC;
374 mdev.timings.vactive = data->yres;
375 mdev.timings.vbp = NT35510_480X800_VBP;
376 mdev.timings.vfp = NT35510_480X800_VFP;
377 mdev.timings.vsync = NT35510_480X800_VSYNC;
378
379 ret = mipi_dsi_attach(cfg->mipi_dsi, cfg->channel, &mdev);
380 if (ret < 0) {
381 LOG_ERR("MIPI-DSI attach failed! (%d)", ret);
382 return ret;
383 }
384
385 ret = nt35510_check_id(dev);
386 if (ret) {
387 LOG_ERR("Panel ID check failed! (%d)", ret);
388 return ret;
389 }
390
391 ret = gpio_pin_configure_dt(&cfg->backlight, GPIO_OUTPUT_ACTIVE);
392 if (ret < 0) {
393 LOG_ERR("Backlight pin init fail (%d)", ret);
394 return ret;
395 }
396
397 ret = nt35510_config(dev);
398 if (ret) {
399 LOG_ERR("DSI init sequence failed! (%d)", ret);
400 return ret;
401 }
402
403 ret = nt35510_blanking_off(dev);
404 if (ret) {
405 LOG_ERR("Display blanking off failed! (%d)", ret);
406 return ret;
407 }
408
409 LOG_INF("Init complete(%d)", ret);
410 return 0;
411 }
412
413 static DEVICE_API(display, nt35510_api) = {
414 .blanking_on = nt35510_blanking_on,
415 .blanking_off = nt35510_blanking_off,
416 .set_brightness = nt35510_set_brightness,
417 .get_capabilities = nt35510_get_capabilities,
418 .set_pixel_format = nt35510_set_pixel_format,
419 };
420
421 #define NT35510_DEFINE(n) \
422 static const struct nt35510_config nt35510_config_##n = { \
423 .mipi_dsi = DEVICE_DT_GET(DT_INST_BUS(n)), \
424 .reset = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), \
425 .backlight = GPIO_DT_SPEC_INST_GET_OR(n, bl_gpios, {0}), \
426 .data_lanes = DT_INST_PROP_BY_IDX(n, data_lanes, 0), \
427 .width = DT_INST_PROP(n, width), \
428 .height = DT_INST_PROP(n, height), \
429 .channel = DT_INST_REG_ADDR(n), \
430 .rotation = DT_INST_PROP(n, rotation), \
431 }; \
432 \
433 static struct nt35510_data nt35510_data_##n = { \
434 .pixel_format = DT_INST_PROP(n, pixel_format), \
435 }; \
436 DEVICE_DT_INST_DEFINE(n, &nt35510_init, NULL, &nt35510_data_##n, &nt35510_config_##n, \
437 POST_KERNEL, CONFIG_DISPLAY_NT35510_INIT_PRIORITY, &nt35510_api);
438
439 DT_INST_FOREACH_STATUS_OKAY(NT35510_DEFINE)
440