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