1 /*
2  * Copyright 2023 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT nxp_dcnano_lcdif
8 
9 #include <zephyr/drivers/display.h>
10 #include <zephyr/drivers/gpio.h>
11 #include <zephyr/kernel.h>
12 #include <zephyr/logging/log.h>
13 #include <zephyr/irq.h>
14 
15 #include <fsl_lcdif.h>
16 #ifdef CONFIG_HAS_MCUX_CACHE
17 #include <fsl_cache.h>
18 #endif
19 
20 
21 LOG_MODULE_REGISTER(display_mcux_dcnano_lcdif, CONFIG_DISPLAY_LOG_LEVEL);
22 
23 struct mcux_dcnano_lcdif_config {
24 	LCDIF_Type *base;
25 	void (*irq_config_func)(const struct device *dev);
26 	const struct gpio_dt_spec backlight_gpio;
27 	lcdif_dpi_config_t dpi_config;
28 	/* Pointer to start of first framebuffer */
29 	uint8_t *fb_ptr;
30 	/* Number of bytes used for each framebuffer */
31 	uint32_t fb_bytes;
32 };
33 
34 struct mcux_dcnano_lcdif_data {
35 	/* Pointer to active framebuffer */
36 	const uint8_t *active_fb;
37 	uint8_t *fb[CONFIG_MCUX_DCNANO_LCDIF_FB_NUM];
38 	lcdif_fb_config_t fb_config;
39 	uint8_t pixel_bytes;
40 	struct k_sem sem;
41 	/* Tracks index of next active driver framebuffer */
42 	uint8_t next_idx;
43 };
44 
mcux_dcnano_lcdif_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)45 static int mcux_dcnano_lcdif_write(const struct device *dev, const uint16_t x,
46 			     const uint16_t y,
47 			     const struct display_buffer_descriptor *desc,
48 			     const void *buf)
49 {
50 	const struct mcux_dcnano_lcdif_config *config = dev->config;
51 	struct mcux_dcnano_lcdif_data *data = dev->data;
52 	uint32_t h_idx;
53 	const uint8_t *src;
54 	uint8_t *dst;
55 
56 	__ASSERT((data->pixel_bytes * desc->pitch * desc->height) <=
57 		desc->buf_size, "Input buffer too small");
58 
59 	LOG_DBG("W=%d, H=%d @%d,%d", desc->width, desc->height, x, y);
60 
61 	if ((x == 0) && (y == 0) &&
62 		(desc->width == config->dpi_config.panelWidth) &&
63 		(desc->height == config->dpi_config.panelHeight) &&
64 		(desc->pitch == desc->width)) {
65 		/* We can use the display buffer directly, without copying */
66 		LOG_DBG("Setting FB from %p->%p",
67 			(void *)data->active_fb, (void *)buf);
68 		data->active_fb = buf;
69 	} else {
70 		/* We must use partial framebuffer copy */
71 		if (CONFIG_MCUX_DCNANO_LCDIF_FB_NUM == 0)  {
72 			LOG_ERR("Partial display refresh requires driver framebuffers");
73 			return -ENOTSUP;
74 		} else if (data->active_fb != data->fb[data->next_idx]) {
75 			/*
76 			 * Copy the entirety of the current framebuffer to new
77 			 * buffer, since we are changing the active buffer address
78 			 */
79 			src = data->active_fb;
80 			dst = data->fb[data->next_idx];
81 			memcpy(dst, src, config->fb_bytes);
82 		}
83 		/* Write the display update to the active framebuffer */
84 		src = buf;
85 		dst = data->fb[data->next_idx];
86 		dst += data->pixel_bytes * (y * config->dpi_config.panelWidth + x);
87 
88 		for (h_idx = 0; h_idx < desc->height; h_idx++) {
89 			memcpy(dst, src, data->pixel_bytes * desc->width);
90 			src += data->pixel_bytes * desc->pitch;
91 			dst += data->pixel_bytes * config->dpi_config.panelWidth;
92 		}
93 		LOG_DBG("Setting FB from %p->%p", (void *) data->active_fb,
94 			(void *) data->fb[data->next_idx]);
95 		/* Set new active framebuffer */
96 		data->active_fb = data->fb[data->next_idx];
97 	}
98 
99 #if defined(CONFIG_HAS_MCUX_CACHE) && defined(CONFIG_MCUX_DCNANO_LCDIF_MAINTAIN_CACHE)
100 	CACHE64_CleanCacheByRange((uint32_t) data->active_fb,
101 					config->fb_bytes);
102 #endif
103 
104 	k_sem_reset(&data->sem);
105 
106 	/* Set new framebuffer */
107 	LCDIF_SetFrameBufferStride(config->base, 0,
108 		config->dpi_config.panelWidth * data->pixel_bytes);
109 	LCDIF_SetFrameBufferAddr(config->base, 0,
110 		(uint32_t)data->active_fb);
111 	LCDIF_SetFrameBufferConfig(config->base, 0, &data->fb_config);
112 
113 #if CONFIG_MCUX_DCNANO_LCDIF_FB_NUM != 0
114 	/* Update index of active framebuffer */
115 	data->next_idx = (data->next_idx + 1) % CONFIG_MCUX_DCNANO_LCDIF_FB_NUM;
116 #endif
117 	/* Wait for frame to complete */
118 	k_sem_take(&data->sem, K_FOREVER);
119 
120 	return 0;
121 }
122 
mcux_dcnano_lcdif_get_capabilities(const struct device * dev,struct display_capabilities * capabilities)123 static void mcux_dcnano_lcdif_get_capabilities(const struct device *dev,
124 		struct display_capabilities *capabilities)
125 {
126 	const struct mcux_dcnano_lcdif_config *config = dev->config;
127 	struct mcux_dcnano_lcdif_data *data = dev->data;
128 
129 	capabilities->y_resolution = config->dpi_config.panelHeight;
130 	capabilities->x_resolution = config->dpi_config.panelWidth;
131 	capabilities->supported_pixel_formats =
132 		(PIXEL_FORMAT_BGR_565 | PIXEL_FORMAT_ARGB_8888);
133 	capabilities->current_orientation = DISPLAY_ORIENTATION_NORMAL;
134 	switch (data->fb_config.format) {
135 	case kLCDIF_PixelFormatRGB565:
136 		/* Zephyr stores RGB565 as big endian, and LCDIF
137 		 * expects little endian. Use BGR565 format to resolve
138 		 * this.
139 		 */
140 		capabilities->current_pixel_format = PIXEL_FORMAT_BGR_565;
141 		break;
142 	case kLCDIF_PixelFormatXRGB8888:
143 		capabilities->current_pixel_format = PIXEL_FORMAT_ARGB_8888;
144 		break;
145 	default:
146 		/* Other LCDIF formats don't have a Zephyr enum yet */
147 		return;
148 	}
149 }
150 
mcux_dcnano_lcdif_get_framebuffer(const struct device * dev)151 static void *mcux_dcnano_lcdif_get_framebuffer(const struct device *dev)
152 {
153 	struct mcux_dcnano_lcdif_data *data = dev->data;
154 
155 	return (void *)data->active_fb;
156 }
157 
mcux_dcnano_lcdif_display_blanking_off(const struct device * dev)158 static int mcux_dcnano_lcdif_display_blanking_off(const struct device *dev)
159 {
160 	const struct mcux_dcnano_lcdif_config *config = dev->config;
161 
162 	return gpio_pin_set_dt(&config->backlight_gpio, 1);
163 }
164 
mcux_dcnano_lcdif_display_blanking_on(const struct device * dev)165 static int mcux_dcnano_lcdif_display_blanking_on(const struct device *dev)
166 {
167 	const struct mcux_dcnano_lcdif_config *config = dev->config;
168 
169 	return gpio_pin_set_dt(&config->backlight_gpio, 0);
170 }
171 
mcux_dcnano_lcdif_set_pixel_format(const struct device * dev,const enum display_pixel_format pixel_format)172 static int mcux_dcnano_lcdif_set_pixel_format(const struct device *dev,
173 					const enum display_pixel_format
174 					pixel_format)
175 {
176 	struct mcux_dcnano_lcdif_data *data = dev->data;
177 
178 	switch (pixel_format) {
179 	case PIXEL_FORMAT_BGR_565:
180 		/* Zephyr stores RGB565 as big endian, and LCDIF
181 		 * expects little endian. Use BGR565 format to resolve
182 		 * this.
183 		 */
184 		data->fb_config.format = kLCDIF_PixelFormatRGB565;
185 		data->pixel_bytes = 2;
186 		break;
187 	case PIXEL_FORMAT_ARGB_8888:
188 		data->fb_config.format = kLCDIF_PixelFormatXRGB8888;
189 		data->pixel_bytes = 4;
190 		break;
191 	default:
192 		return -ENOTSUP;
193 	}
194 	return 0;
195 }
196 
mcux_dcnano_lcdif_isr(const struct device * dev)197 static void mcux_dcnano_lcdif_isr(const struct device *dev)
198 {
199 	const struct mcux_dcnano_lcdif_config *config = dev->config;
200 	struct mcux_dcnano_lcdif_data *data = dev->data;
201 	uint32_t status;
202 
203 	status = LCDIF_GetAndClearInterruptPendingFlags(config->base);
204 
205 	if (0 != (status & kLCDIF_Display0FrameDoneInterrupt)) {
206 		k_sem_give(&data->sem);
207 	}
208 }
209 
mcux_dcnano_lcdif_init(const struct device * dev)210 static int mcux_dcnano_lcdif_init(const struct device *dev)
211 {
212 	const struct mcux_dcnano_lcdif_config *config = dev->config;
213 	struct mcux_dcnano_lcdif_data *data = dev->data;
214 	int ret;
215 
216 	ret = gpio_pin_configure_dt(&config->backlight_gpio, GPIO_OUTPUT_ACTIVE);
217 	if (ret) {
218 		return ret;
219 	}
220 
221 	/* Convert pixel format from devicetree to the format used by HAL */
222 	ret = mcux_dcnano_lcdif_set_pixel_format(dev, data->fb_config.format);
223 	if (ret) {
224 		return ret;
225 	}
226 
227 	LCDIF_Init(config->base);
228 
229 	LCDIF_DpiModeSetConfig(config->base, 0, &config->dpi_config);
230 
231 	LCDIF_EnableInterrupts(config->base, kLCDIF_Display0FrameDoneInterrupt);
232 	config->irq_config_func(dev);
233 
234 	for (int i = 0; i < CONFIG_MCUX_DCNANO_LCDIF_FB_NUM; i++) {
235 		/* Record pointers to each driver framebuffer */
236 		data->fb[i] = config->fb_ptr + (config->fb_bytes * i);
237 	}
238 	data->active_fb = config->fb_ptr;
239 
240 	k_sem_init(&data->sem, 1, 1);
241 
242 #ifdef CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_MEM
243 	/* Clear external memory, as it is uninitialized */
244 	memset(config->fb_ptr, 0, config->fb_bytes * CONFIG_MCUX_DCNANO_LCDIF_FB_NUM);
245 #endif
246 
247 	return 0;
248 }
249 
250 static DEVICE_API(display, mcux_dcnano_lcdif_api) = {
251 	.blanking_on = mcux_dcnano_lcdif_display_blanking_on,
252 	.blanking_off = mcux_dcnano_lcdif_display_blanking_off,
253 	.set_pixel_format = mcux_dcnano_lcdif_set_pixel_format,
254 	.write = mcux_dcnano_lcdif_write,
255 	.get_capabilities = mcux_dcnano_lcdif_get_capabilities,
256 	.get_framebuffer = mcux_dcnano_lcdif_get_framebuffer,
257 };
258 
259 #define MCUX_DCNANO_LCDIF_PIXEL_BYTES(n)                                                           \
260 	(DISPLAY_BITS_PER_PIXEL(DT_INST_PROP(n, pixel_format)) / BITS_PER_BYTE)
261 #define MCUX_DCNANO_LCDIF_FB_SIZE(n) DT_INST_PROP(n, width) *			\
262 	DT_INST_PROP(n, height) * MCUX_DCNANO_LCDIF_PIXEL_BYTES(n)
263 
264 /* When using external framebuffer mem, we should not allocate framebuffers
265  * in SRAM. Instead, we use external framebuffer address and size
266  * from devicetree.
267  */
268 #ifdef CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_MEM
269 #define MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n)
270 #define MCUX_DCNANO_LCDIF_FRAMEBUFFER(n)					\
271 	(uint8_t *)CONFIG_MCUX_DCNANO_LCDIF_EXTERNAL_FB_ADDR
272 #else
273 #define MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n) uint8_t __aligned(LCDIF_FB_ALIGN)	\
274 		mcux_dcnano_lcdif_frame_buffer_##n[DT_INST_PROP(n, width) *	\
275 					 DT_INST_PROP(n, height) *		\
276 					 MCUX_DCNANO_LCDIF_PIXEL_BYTES(n) *	\
277 					 CONFIG_MCUX_DCNANO_LCDIF_FB_NUM]
278 #define MCUX_DCNANO_LCDIF_FRAMEBUFFER(n) mcux_dcnano_lcdif_frame_buffer_##n
279 #endif
280 
281 #define MCUX_DCNANO_LCDIF_DEVICE_INIT(n)					\
282 	static void mcux_dcnano_lcdif_config_func_##n(const struct device *dev)	\
283 	{									\
284 		IRQ_CONNECT(DT_INST_IRQN(n),					\
285 			   DT_INST_IRQ(n, priority),				\
286 			   mcux_dcnano_lcdif_isr,				\
287 			   DEVICE_DT_INST_GET(n),				\
288 			   0);							\
289 		irq_enable(DT_INST_IRQN(n));					\
290 	}									\
291 	MCUX_DCNANO_LCDIF_FRAMEBUFFER_DECL(n);					\
292 	struct mcux_dcnano_lcdif_data mcux_dcnano_lcdif_data_##n = {		\
293 		.fb_config = {							\
294 			.enable = true,						\
295 			.enableGamma = false,					\
296 			.format = DT_INST_PROP(n, pixel_format),		\
297 		},								\
298 		.next_idx = 0,							\
299 		.pixel_bytes = MCUX_DCNANO_LCDIF_PIXEL_BYTES(n),		\
300 	};									\
301 	struct mcux_dcnano_lcdif_config mcux_dcnano_lcdif_config_##n = {	\
302 		.base = (LCDIF_Type *) DT_INST_REG_ADDR(n),			\
303 		.irq_config_func = mcux_dcnano_lcdif_config_func_##n,		\
304 		.backlight_gpio = GPIO_DT_SPEC_INST_GET(n, backlight_gpios),	\
305 		.dpi_config = {							\
306 			.panelWidth = DT_INST_PROP(n, width),			\
307 			.panelHeight = DT_INST_PROP(n, height),			\
308 			.hsw = DT_PROP(DT_INST_CHILD(n, display_timings),	\
309 					hsync_len),				\
310 			.hfp = DT_PROP(DT_INST_CHILD(n, display_timings),	\
311 					hfront_porch),				\
312 			.hbp = DT_PROP(DT_INST_CHILD(n, display_timings),	\
313 					hback_porch),				\
314 			.vsw = DT_PROP(DT_INST_CHILD(n, display_timings),	\
315 					vsync_len),				\
316 			.vfp = DT_PROP(DT_INST_CHILD(n, display_timings),	\
317 					vfront_porch),				\
318 			.vbp = DT_PROP(DT_INST_CHILD(n, display_timings),	\
319 					vback_porch),				\
320 			.polarityFlags = (DT_PROP(DT_INST_CHILD(n,		\
321 					display_timings), de_active) ?		\
322 					kLCDIF_DataEnableActiveHigh :		\
323 					kLCDIF_DataEnableActiveLow) |		\
324 					(DT_PROP(DT_INST_CHILD(n,		\
325 					display_timings), pixelclk_active) ?	\
326 					kLCDIF_DriveDataOnRisingClkEdge :	\
327 					kLCDIF_DriveDataOnFallingClkEdge) |	\
328 					(DT_PROP(DT_INST_CHILD(n,		\
329 					display_timings), hsync_active) ?	\
330 					kLCDIF_HsyncActiveHigh :		\
331 					kLCDIF_HsyncActiveLow) |		\
332 					(DT_PROP(DT_INST_CHILD(n,		\
333 					display_timings), vsync_active) ?	\
334 					kLCDIF_VsyncActiveHigh :		\
335 					kLCDIF_VsyncActiveLow),			\
336 			.format = DT_INST_ENUM_IDX(n, data_bus_width),		\
337 		},								\
338 		.fb_ptr = MCUX_DCNANO_LCDIF_FRAMEBUFFER(n),			\
339 		.fb_bytes = MCUX_DCNANO_LCDIF_FB_SIZE(n),			\
340 	};									\
341 	DEVICE_DT_INST_DEFINE(n,						\
342 		&mcux_dcnano_lcdif_init,					\
343 		NULL,								\
344 		&mcux_dcnano_lcdif_data_##n,					\
345 		&mcux_dcnano_lcdif_config_##n,					\
346 		POST_KERNEL,							\
347 		CONFIG_DISPLAY_INIT_PRIORITY,					\
348 		&mcux_dcnano_lcdif_api);
349 
350 DT_INST_FOREACH_STATUS_OKAY(MCUX_DCNANO_LCDIF_DEVICE_INIT)
351