1 /*
2 * Copyright 2019-24, NXP
3 * Copyright (c) 2022, Basalte bv
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT nxp_imx_elcdif
9
10 #include <zephyr/drivers/display.h>
11 #include <zephyr/drivers/pinctrl.h>
12 #include <zephyr/drivers/gpio.h>
13 #include <zephyr/kernel.h>
14 #include <fsl_elcdif.h>
15
16 #ifdef CONFIG_HAS_MCUX_CACHE
17 #include <fsl_cache.h>
18 #endif
19
20 #ifdef CONFIG_MCUX_ELCDIF_PXP
21 #include <zephyr/drivers/dma.h>
22 #include <zephyr/drivers/dma/dma_mcux_pxp.h>
23 #endif
24
25 #include <zephyr/logging/log.h>
26 #include <zephyr/irq.h>
27
28 LOG_MODULE_REGISTER(display_mcux_elcdif, CONFIG_DISPLAY_LOG_LEVEL);
29
30 /* Define the heap size. 512 bytes of padding are included for kernel heap structures */
31 K_HEAP_DEFINE(display_heap, CONFIG_MCUX_ELCDIF_FB_NUM * CONFIG_MCUX_ELCDIF_FB_SIZE + 512);
32
33 static const uint32_t supported_fmts = PIXEL_FORMAT_BGR_565 | PIXEL_FORMAT_ARGB_8888;
34
35 struct mcux_elcdif_config {
36 LCDIF_Type *base;
37 void (*irq_config_func)(const struct device *dev);
38 elcdif_rgb_mode_config_t rgb_mode;
39 const struct pinctrl_dev_config *pincfg;
40 const struct gpio_dt_spec backlight_gpio;
41 const struct device *pxp;
42 };
43
44 struct mcux_elcdif_data {
45 /* Pointer to active framebuffer */
46 const uint8_t *active_fb;
47 /* Pointers to driver allocated framebuffers */
48 uint8_t *fb[CONFIG_MCUX_ELCDIF_FB_NUM];
49 enum display_pixel_format pixel_format;
50 size_t pixel_bytes;
51 size_t fb_bytes;
52 elcdif_rgb_mode_config_t rgb_mode;
53 struct k_sem sem;
54 /* Tracks index of next active driver framebuffer */
55 uint8_t next_idx;
56 #ifdef CONFIG_MCUX_ELCDIF_PXP
57 /* Given to when PXP completes operation */
58 struct k_sem pxp_done;
59 #endif
60 };
61
62 #ifdef CONFIG_MCUX_ELCDIF_PXP
mcux_elcdif_pxp_callback(const struct device * dma_dev,void * user_data,uint32_t channel,int ret)63 static void mcux_elcdif_pxp_callback(const struct device *dma_dev, void *user_data,
64 uint32_t channel, int ret)
65 {
66 struct mcux_elcdif_data *data = user_data;
67
68 k_sem_give(&data->pxp_done);
69 }
70 #endif /* CONFIG_MCUX_ELCDIF_PXP */
71
mcux_elcdif_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)72 static int mcux_elcdif_write(const struct device *dev, const uint16_t x, const uint16_t y,
73 const struct display_buffer_descriptor *desc, const void *buf)
74 {
75 const struct mcux_elcdif_config *config = dev->config;
76 struct mcux_elcdif_data *dev_data = dev->data;
77 int h_idx;
78 const uint8_t *src;
79 uint8_t *dst;
80 int ret = 0;
81 bool full_fb = false;
82
83 __ASSERT((dev_data->pixel_bytes * desc->pitch * desc->height) <= desc->buf_size,
84 "Input buffer too small");
85
86 LOG_DBG("W=%d, H=%d, @%d,%d", desc->width, desc->height, x, y);
87
88 if ((x == 0) && (y == 0) && (desc->width == config->rgb_mode.panelWidth) &&
89 (desc->height == config->rgb_mode.panelHeight) && (desc->pitch == desc->width)) {
90 /* We can use the display buffer directly, no need to copy it */
91 LOG_DBG("Setting FB from %p->%p", (void *)dev_data->active_fb, (void *)buf);
92 dev_data->active_fb = buf;
93 full_fb = true;
94 } else if ((x == 0) && (y == 0) && (desc->width == config->rgb_mode.panelHeight) &&
95 (desc->height == config->rgb_mode.panelWidth) && (desc->pitch == desc->width) &&
96 IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP)) {
97 /* With the PXP, we can rotate this display buffer to align
98 * with output dimensions
99 */
100 LOG_DBG("Setting FB from %p->%p", (void *)dev_data->active_fb, (void *)buf);
101 dev_data->active_fb = buf;
102 full_fb = true;
103 } else {
104 /* We must use partial framebuffer copy */
105 if (CONFIG_MCUX_ELCDIF_FB_NUM == 0) {
106 LOG_ERR("Partial display refresh requires driver framebuffers");
107 return -ENOTSUP;
108 } else if (dev_data->active_fb != dev_data->fb[dev_data->next_idx]) {
109 /*
110 * We must copy the entire current framebuffer to new
111 * buffer, since we wil change the active buffer
112 * address
113 */
114 src = dev_data->active_fb;
115 dst = dev_data->fb[dev_data->next_idx];
116 memcpy(dst, src, dev_data->fb_bytes);
117 }
118 /* Now, write the display update into active framebuffer */
119 src = buf;
120 dst = dev_data->fb[dev_data->next_idx];
121 dst += dev_data->pixel_bytes * (y * config->rgb_mode.panelWidth + x);
122
123 for (h_idx = 0; h_idx < desc->height; h_idx++) {
124 memcpy(dst, src, dev_data->pixel_bytes * desc->width);
125 src += dev_data->pixel_bytes * desc->pitch;
126 dst += dev_data->pixel_bytes * config->rgb_mode.panelWidth;
127 }
128
129 LOG_DBG("Setting FB from %p->%p", (void *)dev_data->active_fb,
130 (void *)dev_data->fb[dev_data->next_idx]);
131 /* Set new active framebuffer */
132 dev_data->active_fb = dev_data->fb[dev_data->next_idx];
133 }
134
135 #ifdef CONFIG_HAS_MCUX_CACHE
136 DCACHE_CleanByRange((uint32_t)dev_data->active_fb, dev_data->fb_bytes);
137 #endif
138
139 #ifdef CONFIG_MCUX_ELCDIF_PXP
140 if (full_fb) {
141 /* Configure PXP using DMA API, and rotate/flip frame */
142 struct dma_config pxp_dma = {0};
143 struct dma_block_config pxp_block = {0};
144
145 /* Source buffer is input to display_write, we will
146 * place modified output into a driver framebuffer.
147 */
148 dev_data->active_fb = dev_data->fb[dev_data->next_idx];
149 pxp_block.source_address = (uint32_t)buf;
150 pxp_block.dest_address = (uint32_t)dev_data->active_fb;
151 pxp_block.block_size = desc->buf_size;
152
153 /* DMA slot sets pixel format and rotation angle */
154 if (dev_data->pixel_format == PIXEL_FORMAT_BGR_565) {
155 pxp_dma.dma_slot = DMA_MCUX_PXP_FMT(DMA_MCUX_PXP_FMT_RGB565);
156 } else if (dev_data->pixel_format == PIXEL_FORMAT_RGB_888) {
157 pxp_dma.dma_slot = DMA_MCUX_PXP_FMT(DMA_MCUX_PXP_FMT_RGB888);
158 } else if (dev_data->pixel_format == PIXEL_FORMAT_ARGB_8888) {
159 pxp_dma.dma_slot = DMA_MCUX_PXP_FMT(DMA_MCUX_PXP_FMT_ARGB8888);
160 } else {
161 /* Cannot rotate */
162 return -ENOTSUP;
163 }
164 if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_ROTATE_90)) {
165 pxp_dma.dma_slot |= DMA_MCUX_PXP_CMD(DMA_MCUX_PXP_CMD_ROTATE_90);
166 } else if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_ROTATE_180)) {
167 pxp_dma.dma_slot |= DMA_MCUX_PXP_CMD(DMA_MCUX_PXP_CMD_ROTATE_180);
168 } else if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_ROTATE_270)) {
169 pxp_dma.dma_slot |= DMA_MCUX_PXP_CMD(DMA_MCUX_PXP_CMD_ROTATE_270);
170 } else {
171 pxp_dma.dma_slot |= DMA_MCUX_PXP_CMD(DMA_MCUX_PXP_CMD_ROTATE_0);
172 }
173
174 /* DMA linked_channel sets the flip direction */
175 if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_FLIP_HORIZONTAL)) {
176 pxp_dma.linked_channel |= DMA_MCUX_PXP_FLIP(DMA_MCUX_PXP_FLIP_HORIZONTAL);
177 } else if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_FLIP_VERTICAL)) {
178 pxp_dma.linked_channel |= DMA_MCUX_PXP_FLIP(DMA_MCUX_PXP_FLIP_VERTICAL);
179 } else if (IS_ENABLED(CONFIG_MCUX_ELCDIF_PXP_FLIP_BOTH)) {
180 pxp_dma.linked_channel |= DMA_MCUX_PXP_FLIP(DMA_MCUX_PXP_FLIP_BOTH);
181 } else {
182 pxp_dma.linked_channel |= DMA_MCUX_PXP_FLIP(DMA_MCUX_PXP_FLIP_DISABLE);
183 }
184
185 pxp_dma.channel_direction = MEMORY_TO_MEMORY;
186 pxp_dma.source_data_size = desc->width * dev_data->pixel_bytes;
187 pxp_dma.dest_data_size = config->rgb_mode.panelWidth * dev_data->pixel_bytes;
188 /* Burst lengths are heights of source/dest buffer in pixels */
189 pxp_dma.source_burst_length = desc->height;
190 pxp_dma.dest_burst_length = config->rgb_mode.panelHeight;
191 pxp_dma.head_block = &pxp_block;
192 pxp_dma.dma_callback = mcux_elcdif_pxp_callback;
193 pxp_dma.user_data = dev_data;
194
195 ret = dma_config(config->pxp, 0, &pxp_dma);
196 if (ret < 0) {
197 return ret;
198 }
199 ret = dma_start(config->pxp, 0);
200 if (ret < 0) {
201 return ret;
202 }
203 k_sem_take(&dev_data->pxp_done, K_FOREVER);
204 } else {
205 LOG_WRN("PXP rotation/flip will not work correctly unless a full sized "
206 "framebuffer is provided");
207 }
208 #endif /* CONFIG_MCUX_ELCDIF_PXP */
209
210 /* Queue next framebuffer */
211 ELCDIF_SetNextBufferAddr(config->base, (uint32_t)dev_data->active_fb);
212
213 #if CONFIG_MCUX_ELCDIF_FB_NUM != 0
214 /* Update index of active framebuffer */
215 dev_data->next_idx = (dev_data->next_idx + 1) % CONFIG_MCUX_ELCDIF_FB_NUM;
216 #endif
217 /* Enable frame buffer completion interrupt */
218 ELCDIF_EnableInterrupts(config->base, kELCDIF_CurFrameDoneInterruptEnable);
219 /* Wait for frame send to complete */
220 k_sem_take(&dev_data->sem, K_FOREVER);
221 return ret;
222 }
223
mcux_elcdif_display_blanking_off(const struct device * dev)224 static int mcux_elcdif_display_blanking_off(const struct device *dev)
225 {
226 const struct mcux_elcdif_config *config = dev->config;
227
228 return gpio_pin_set_dt(&config->backlight_gpio, 1);
229 }
230
mcux_elcdif_display_blanking_on(const struct device * dev)231 static int mcux_elcdif_display_blanking_on(const struct device *dev)
232 {
233 const struct mcux_elcdif_config *config = dev->config;
234
235 return gpio_pin_set_dt(&config->backlight_gpio, 0);
236 }
237
mcux_elcdif_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)238 static int mcux_elcdif_set_pixel_format(const struct device *dev,
239 const enum display_pixel_format pixel_format)
240 {
241 struct mcux_elcdif_data *dev_data = dev->data;
242 const struct mcux_elcdif_config *config = dev->config;
243
244 if (!(pixel_format & supported_fmts)) {
245 LOG_ERR("Unsupported pixel format");
246 return -ENOTSUP;
247 }
248
249 dev_data->pixel_format = pixel_format;
250 dev_data->pixel_bytes = DISPLAY_BITS_PER_PIXEL(pixel_format) / BITS_PER_BYTE;
251 dev_data->fb_bytes =
252 config->rgb_mode.panelWidth * config->rgb_mode.panelHeight * dev_data->pixel_bytes;
253
254 for (int i = 0; i < CONFIG_MCUX_ELCDIF_FB_NUM; i++) {
255 k_heap_free(&display_heap, dev_data->fb[i]);
256 dev_data->fb[i] =
257 k_heap_aligned_alloc(&display_heap, 64, dev_data->fb_bytes, K_FOREVER);
258 if (dev_data->fb[i] == NULL) {
259 LOG_ERR("Could not allocate memory for framebuffers");
260 return -ENOMEM;
261 }
262 memset(dev_data->fb[i], 0, dev_data->fb_bytes);
263 }
264
265 dev_data->rgb_mode = config->rgb_mode;
266 if (pixel_format == PIXEL_FORMAT_BGR_565) {
267 dev_data->rgb_mode.pixelFormat = kELCDIF_PixelFormatRGB565;
268 } else if (pixel_format == PIXEL_FORMAT_RGB_888) {
269 dev_data->rgb_mode.pixelFormat = kELCDIF_PixelFormatRGB888;
270 } else if (pixel_format == PIXEL_FORMAT_ARGB_8888) {
271 dev_data->rgb_mode.pixelFormat = kELCDIF_PixelFormatXRGB8888;
272 }
273
274 ELCDIF_RgbModeSetPixelFormat(config->base, dev_data->rgb_mode.pixelFormat);
275
276 return 0;
277 }
278
mcux_elcdif_set_orientation(const struct device * dev,const enum display_orientation orientation)279 static int mcux_elcdif_set_orientation(const struct device *dev,
280 const enum display_orientation orientation)
281 {
282 if (orientation == DISPLAY_ORIENTATION_NORMAL) {
283 return 0;
284 }
285 LOG_ERR("Changing display orientation not implemented");
286 return -ENOTSUP;
287 }
288
mcux_elcdif_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)289 static void mcux_elcdif_get_capabilities(const struct device *dev,
290 struct display_capabilities *capabilities)
291 {
292 const struct mcux_elcdif_config *config = dev->config;
293
294 memset(capabilities, 0, sizeof(struct display_capabilities));
295 capabilities->x_resolution = config->rgb_mode.panelWidth;
296 capabilities->y_resolution = config->rgb_mode.panelHeight;
297 capabilities->supported_pixel_formats = supported_fmts;
298 capabilities->current_pixel_format = ((struct mcux_elcdif_data *)dev->data)->pixel_format;
299 capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
300 }
301
mcux_elcdif_isr(const struct device * dev)302 static void mcux_elcdif_isr(const struct device *dev)
303 {
304 const struct mcux_elcdif_config *config = dev->config;
305 struct mcux_elcdif_data *dev_data = dev->data;
306 uint32_t status;
307
308 status = ELCDIF_GetInterruptStatus(config->base);
309 ELCDIF_ClearInterruptStatus(config->base, status);
310 if (config->base->CUR_BUF == ((uint32_t)dev_data->active_fb)) {
311 /* Disable frame completion interrupt, post to
312 * sem to notify that frame send is complete.
313 */
314 ELCDIF_DisableInterrupts(config->base, kELCDIF_CurFrameDoneInterruptEnable);
315 k_sem_give(&dev_data->sem);
316 }
317 }
318
mcux_elcdif_init(const struct device * dev)319 static int mcux_elcdif_init(const struct device *dev)
320 {
321 const struct mcux_elcdif_config *config = dev->config;
322 struct mcux_elcdif_data *dev_data = dev->data;
323 int err;
324
325 err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
326 if (err) {
327 return err;
328 }
329
330 err = gpio_pin_configure_dt(&config->backlight_gpio, GPIO_OUTPUT_ACTIVE);
331 if (err) {
332 return err;
333 }
334
335 k_sem_init(&dev_data->sem, 0, 1);
336 #ifdef CONFIG_MCUX_ELCDIF_PXP
337 k_sem_init(&dev_data->pxp_done, 0, 1);
338 if (!device_is_ready(config->pxp)) {
339 LOG_ERR("PXP device is not ready");
340 return -ENODEV;
341 }
342 #endif
343
344 config->irq_config_func(dev);
345
346 /* Set default pixel format obtained from device tree */
347 mcux_elcdif_set_pixel_format(dev, dev_data->pixel_format);
348
349 dev_data->active_fb = dev_data->fb[0];
350
351 ELCDIF_RgbModeInit(config->base, &dev_data->rgb_mode);
352 ELCDIF_RgbModeStart(config->base);
353
354 return 0;
355 }
356
357 static DEVICE_API(display, mcux_elcdif_api) = {
358 .blanking_on = mcux_elcdif_display_blanking_on,
359 .blanking_off = mcux_elcdif_display_blanking_off,
360 .write = mcux_elcdif_write,
361 .get_capabilities = mcux_elcdif_get_capabilities,
362 .set_pixel_format = mcux_elcdif_set_pixel_format,
363 .set_orientation = mcux_elcdif_set_orientation,
364 };
365
366 #define MCUX_ELCDIF_DEVICE_INIT(id) \
367 PINCTRL_DT_INST_DEFINE(id); \
368 static void mcux_elcdif_config_func_##id(const struct device *dev); \
369 static const struct mcux_elcdif_config mcux_elcdif_config_##id = { \
370 .base = (LCDIF_Type *)DT_INST_REG_ADDR(id), \
371 .irq_config_func = mcux_elcdif_config_func_##id, \
372 .rgb_mode = \
373 { \
374 .panelWidth = DT_INST_PROP(id, width), \
375 .panelHeight = DT_INST_PROP(id, height), \
376 .hsw = DT_PROP(DT_INST_CHILD(id, display_timings), hsync_len), \
377 .hfp = DT_PROP(DT_INST_CHILD(id, display_timings), hfront_porch), \
378 .hbp = DT_PROP(DT_INST_CHILD(id, display_timings), hback_porch), \
379 .vsw = DT_PROP(DT_INST_CHILD(id, display_timings), vsync_len), \
380 .vfp = DT_PROP(DT_INST_CHILD(id, display_timings), vfront_porch), \
381 .vbp = DT_PROP(DT_INST_CHILD(id, display_timings), vback_porch), \
382 .polarityFlags = \
383 (DT_PROP(DT_INST_CHILD(id, display_timings), hsync_active) \
384 ? kELCDIF_HsyncActiveHigh \
385 : kELCDIF_HsyncActiveLow) | \
386 (DT_PROP(DT_INST_CHILD(id, display_timings), vsync_active) \
387 ? kELCDIF_VsyncActiveHigh \
388 : kELCDIF_VsyncActiveLow) | \
389 (DT_PROP(DT_INST_CHILD(id, display_timings), de_active) \
390 ? kELCDIF_DataEnableActiveHigh \
391 : kELCDIF_DataEnableActiveLow) | \
392 (DT_PROP(DT_INST_CHILD(id, display_timings), \
393 pixelclk_active) \
394 ? kELCDIF_DriveDataOnRisingClkEdge \
395 : kELCDIF_DriveDataOnFallingClkEdge), \
396 .dataBus = LCDIF_CTRL_LCD_DATABUS_WIDTH( \
397 DT_INST_ENUM_IDX(id, data_bus_width)), \
398 }, \
399 .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id), \
400 .backlight_gpio = GPIO_DT_SPEC_INST_GET(id, backlight_gpios), \
401 IF_ENABLED(CONFIG_MCUX_ELCDIF_PXP, \
402 (.pxp = DEVICE_DT_GET(DT_INST_PHANDLE(id, nxp_pxp)),))}; \
403 static struct mcux_elcdif_data mcux_elcdif_data_##id = { \
404 .next_idx = 0, \
405 .pixel_format = DT_INST_PROP(id, pixel_format), \
406 }; \
407 DEVICE_DT_INST_DEFINE(id, &mcux_elcdif_init, NULL, &mcux_elcdif_data_##id, \
408 &mcux_elcdif_config_##id, POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, \
409 &mcux_elcdif_api); \
410 static void mcux_elcdif_config_func_##id(const struct device *dev) \
411 { \
412 IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), mcux_elcdif_isr, \
413 DEVICE_DT_INST_GET(id), 0); \
414 irq_enable(DT_INST_IRQN(id)); \
415 }
416
417 DT_INST_FOREACH_STATUS_OKAY(MCUX_ELCDIF_DEVICE_INIT)
418