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