master
c 454 lines 12.2 KB
Raw
1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * i.MX6UL LCDIF controller
5 *
6 * Copyright (c) 2026 Yucai Liu <1486344514@qq.com>
7 */
8
9 #include "qemu/osdep.h"
10 #include "hw/display/imx6ul_lcdif.h"
11 #include "hw/core/irq.h"
12 #include "hw/core/registerfields.h"
13 #include "hw/display/framebuffer.h"
14 #include "migration/vmstate.h"
15 #include "system/address-spaces.h"
16 #include "qemu/module.h"
17 #include "qemu/units.h"
18 #include "ui/pixel_ops.h"
19
20 #define LCDIF_MMIO_SIZE (16 * KiB)
21 #define LCDIF_RESET_CTRL1 0x000f0000
22
23 REG32(CTRL, 0x00)
24 FIELD(CTRL, RUN, 0, 1)
25 FIELD(CTRL, WORD_LENGTH, 8, 2)
26 REG32(CTRL1, 0x10)
27 FIELD(CTRL1, CUR_FRAME_DONE_IRQ, 9, 1)
28 FIELD(CTRL1, CUR_FRAME_DONE_IRQ_EN, 13, 1)
29 FIELD(CTRL1, BYTE_PACKING_FORMAT, 16, 4)
30 REG32(V4_TRANSFER_COUNT, 0x30)
31 FIELD(V4_TRANSFER_COUNT, H_COUNT, 0, 16)
32 FIELD(V4_TRANSFER_COUNT, V_COUNT, 16, 16)
33 REG32(V4_CUR_BUF, 0x40)
34 REG32(V4_NEXT_BUF, 0x50)
35 REG32(AS_NEXT_BUF, 0x230)
36
37 #define REG_SET 0x4
38 #define REG_CLR 0x8
39 #define REG_TOG 0xc
40
41 #define CTRL_WORD_LENGTH_16 0
42 #define CTRL_WORD_LENGTH_24 3
43
44 #define FRAME_PERIOD_NS (16 * 1000 * 1000ULL)
45
46 enum IMX6ULLCDIFReg {
47 IMX6UL_LCDIF_REG_CTRL = A_CTRL >> 4,
48 IMX6UL_LCDIF_REG_CTRL1 = A_CTRL1 >> 4,
49 IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT = A_V4_TRANSFER_COUNT >> 4,
50 IMX6UL_LCDIF_REG_V4_CUR_BUF = A_V4_CUR_BUF >> 4,
51 IMX6UL_LCDIF_REG_V4_NEXT_BUF = A_V4_NEXT_BUF >> 4,
52 IMX6UL_LCDIF_REG_AS_NEXT_BUF = A_AS_NEXT_BUF >> 4,
53 };
54
55 static inline bool imx6ul_lcdif_reg_exists(hwaddr reg)
56 {
57 return (reg >> 4) < IMX6UL_LCDIF_REGS_NUM;
58 }
59
60 static inline bool imx6ul_lcdif_reg_has_setclr(hwaddr reg)
61 {
62 switch (reg) {
63 case A_CTRL:
64 case A_CTRL1:
65 return true;
66 default:
67 return false;
68 }
69 }
70
71 static inline bool imx6ul_lcdif_is_running(IMX6ULLCDIFState *s)
72 {
73 uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL];
74
75 return FIELD_EX32(ctrl, CTRL, RUN);
76 }
77
78 static inline bool imx6ul_lcdif_frame_done_pending(IMX6ULLCDIFState *s)
79 {
80 uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];
81
82 return FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ);
83 }
84
85 static void imx6ul_lcdif_schedule_frame(IMX6ULLCDIFState *s)
86 {
87 int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
88
89 timer_mod(s->frame_timer, now + FRAME_PERIOD_NS);
90 }
91
92 static void imx6ul_lcdif_maybe_schedule_frame(IMX6ULLCDIFState *s)
93 {
94 if (imx6ul_lcdif_is_running(s) && !imx6ul_lcdif_frame_done_pending(s)) {
95 imx6ul_lcdif_schedule_frame(s);
96 } else {
97 timer_del(s->frame_timer);
98 }
99 }
100
101 static void imx6ul_lcdif_update_irq(IMX6ULLCDIFState *s)
102 {
103 uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];
104 bool level = FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ_EN) &&
105 FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ);
106
107 qemu_set_irq(s->irq, level);
108 }
109
110 static void imx6ul_lcdif_frame_done(IMX6ULLCDIFState *s)
111 {
112 uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1];
113
114 ctrl1 = FIELD_DP32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ, 1);
115 s->regs[IMX6UL_LCDIF_REG_CTRL1] = ctrl1;
116 imx6ul_lcdif_update_irq(s);
117 }
118
119 static void imx6ul_lcdif_draw_line_rgb565(void *opaque, uint8_t *dst,
120 const uint8_t *src, int width,
121 int dststep)
122 {
123 uint32_t *dst32 = (uint32_t *)dst;
124 int i;
125
126 for (i = 0; i < width; i++) {
127 uint16_t pixel = lduw_le_p(src);
128 uint8_t r = ((pixel >> 11) & 0x1f) << 3;
129 uint8_t g = ((pixel >> 5) & 0x3f) << 2;
130 uint8_t b = (pixel & 0x1f) << 3;
131
132 *dst32++ = rgb_to_pixel32(r, g, b);
133 src += 2;
134 }
135 }
136
137 static void imx6ul_lcdif_draw_line_xrgb8888(void *opaque, uint8_t *dst,
138 const uint8_t *src, int width,
139 int dststep)
140 {
141 uint32_t *dst32 = (uint32_t *)dst;
142 int i;
143
144 for (i = 0; i < width; i++) {
145 uint32_t pixel = ldl_le_p(src);
146 uint8_t r = (pixel >> 16) & 0xff;
147 uint8_t g = (pixel >> 8) & 0xff;
148 uint8_t b = pixel & 0xff;
149
150 *dst32++ = rgb_to_pixel32(r, g, b);
151 src += 4;
152 }
153 }
154
155 static bool imx6ul_lcdif_update_display(void *opaque)
156 {
157 IMX6ULLCDIFState *s = opaque;
158 DisplaySurface *surface = qemu_console_surface(s->con);
159 uint32_t transfer_count = s->regs[IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT];
160 uint32_t width = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, H_COUNT);
161 uint32_t height = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, V_COUNT);
162 uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL];
163 uint32_t frame_base = s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF];
164 drawfn fn;
165 int first = 0;
166 int last = 0;
167 int src_width;
168
169 if (!imx6ul_lcdif_is_running(s) || width == 0 || height == 0) {
170 return true;
171 }
172
173 switch (FIELD_EX32(ctrl, CTRL, WORD_LENGTH)) {
174 case CTRL_WORD_LENGTH_16:
175 s->src_bpp = 2;
176 fn = imx6ul_lcdif_draw_line_rgb565;
177 break;
178 case CTRL_WORD_LENGTH_24:
179 s->src_bpp = 4;
180 fn = imx6ul_lcdif_draw_line_xrgb8888;
181 break;
182 default:
183 return true;
184 }
185
186 if (surface_width(surface) != width || surface_height(surface) != height) {
187 qemu_console_resize(s->con, width, height);
188 surface = qemu_console_surface(s->con);
189 s->invalidate = true;
190 }
191
192 src_width = width * s->src_bpp;
193 if (s->invalidate || s->fb_base != frame_base ||
194 s->src_width != src_width || s->rows != height) {
195 framebuffer_update_memory_section(&s->fbsection, get_system_memory(),
196 frame_base, height, src_width);
197 s->fb_base = frame_base;
198 s->src_width = src_width;
199 s->rows = height;
200 }
201
202 framebuffer_update_display(surface, &s->fbsection, width, height,
203 src_width, surface_stride(surface), 0,
204 s->invalidate, fn, s, &first, &last);
205 if (first >= 0) {
206 qemu_console_update(s->con, 0, first, width, last - first + 1);
207 }
208
209 s->invalidate = false;
210 return true;
211 }
212
213 static void imx6ul_lcdif_invalidate_display(void *opaque)
214 {
215 IMX6ULLCDIFState *s = opaque;
216
217 s->invalidate = true;
218 }
219
220 static const GraphicHwOps imx6ul_lcdif_graphic_ops = {
221 .invalidate = imx6ul_lcdif_invalidate_display,
222 .gfx_update = imx6ul_lcdif_update_display,
223 };
224
225 static void imx6ul_lcdif_frame_timer_cb(void *opaque)
226 {
227 IMX6ULLCDIFState *s = opaque;
228
229 if (!imx6ul_lcdif_is_running(s) || imx6ul_lcdif_frame_done_pending(s)) {
230 return;
231 }
232
233 imx6ul_lcdif_frame_done(s);
234 }
235
236 static uint64_t imx6ul_lcdif_read(void *opaque, hwaddr offset, unsigned size)
237 {
238 IMX6ULLCDIFState *s = opaque;
239 hwaddr reg = offset & ~0xf;
240 uint32_t idx;
241
242 assert(size == 4);
243 assert(!(offset & 0x3));
244 assert(offset < LCDIF_MMIO_SIZE);
245
246 idx = reg >> 4;
247 if (idx >= ARRAY_SIZE(s->regs)) {
248 return 0;
249 }
250
251 return s->regs[idx];
252 }
253
254 static void imx6ul_lcdif_write(void *opaque, hwaddr offset,
255 uint64_t value, unsigned size)
256 {
257 IMX6ULLCDIFState *s = opaque;
258 hwaddr reg = offset & ~0xf;
259 uint32_t idx;
260 uint32_t oldv;
261
262 assert(size == 4);
263 assert(!(offset & 0x3));
264 assert(offset < LCDIF_MMIO_SIZE);
265
266 if (!imx6ul_lcdif_reg_exists(reg)) {
267 return;
268 }
269
270 idx = reg >> 4;
271 oldv = s->regs[idx];
272
273 switch (offset & 0xf) {
274 case 0:
275 s->regs[idx] = (uint32_t)value;
276 break;
277 case REG_SET:
278 if (!imx6ul_lcdif_reg_has_setclr(reg)) {
279 return;
280 }
281 s->regs[idx] = oldv | (uint32_t)value;
282 break;
283 case REG_CLR:
284 if (!imx6ul_lcdif_reg_has_setclr(reg)) {
285 return;
286 }
287 s->regs[idx] = oldv & ~(uint32_t)value;
288 break;
289 case REG_TOG:
290 if (!imx6ul_lcdif_reg_has_setclr(reg)) {
291 return;
292 }
293 s->regs[idx] = oldv ^ (uint32_t)value;
294 break;
295 default:
296 g_assert_not_reached();
297 }
298
299 switch (reg) {
300 case A_CTRL:
301 if (!FIELD_EX32(oldv, CTRL, RUN) &&
302 FIELD_EX32(s->regs[idx], CTRL, RUN)) {
303 s->invalidate = true;
304 qemu_console_hw_invalidate(s->con);
305 imx6ul_lcdif_maybe_schedule_frame(s);
306 break;
307 }
308 if (FIELD_EX32(oldv, CTRL, RUN) &&
309 !FIELD_EX32(s->regs[idx], CTRL, RUN)) {
310 timer_del(s->frame_timer);
311 }
312 break;
313 case A_CTRL1:
314 if (FIELD_EX32(oldv, CTRL1, CUR_FRAME_DONE_IRQ) &&
315 !FIELD_EX32(s->regs[idx], CTRL1, CUR_FRAME_DONE_IRQ)) {
316 imx6ul_lcdif_maybe_schedule_frame(s);
317 }
318 break;
319 case A_V4_TRANSFER_COUNT:
320 s->invalidate = true;
321 qemu_console_hw_invalidate(s->con);
322 break;
323 case A_V4_CUR_BUF:
324 s->invalidate = true;
325 qemu_console_hw_invalidate(s->con);
326 break;
327 case A_V4_NEXT_BUF:
328 s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF] = s->regs[idx];
329 imx6ul_lcdif_frame_done(s);
330 s->invalidate = true;
331 qemu_console_hw_invalidate(s->con);
332 imx6ul_lcdif_maybe_schedule_frame(s);
333 return;
334 case A_AS_NEXT_BUF:
335 imx6ul_lcdif_frame_done(s);
336 imx6ul_lcdif_maybe_schedule_frame(s);
337 return;
338 default:
339 break;
340 }
341
342 imx6ul_lcdif_update_irq(s);
343 }
344
345 static const MemoryRegionOps imx6ul_lcdif_ops = {
346 .read = imx6ul_lcdif_read,
347 .write = imx6ul_lcdif_write,
348 .endianness = DEVICE_LITTLE_ENDIAN,
349 .valid = {
350 .min_access_size = 4,
351 .max_access_size = 4,
352 .unaligned = false,
353 },
354 };
355
356 static void imx6ul_lcdif_reset(DeviceState *dev)
357 {
358 IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);
359
360 memset(s->regs, 0, sizeof(s->regs));
361 s->regs[IMX6UL_LCDIF_REG_CTRL1] = LCDIF_RESET_CTRL1;
362 s->fb_base = 0;
363 s->src_width = 0;
364 s->rows = 0;
365 s->src_bpp = 0;
366 s->invalidate = true;
367 timer_del(s->frame_timer);
368 imx6ul_lcdif_update_irq(s);
369 }
370
371 static int imx6ul_lcdif_post_load(void *opaque, int version_id)
372 {
373 IMX6ULLCDIFState *s = opaque;
374
375 s->fb_base = 0;
376 s->src_width = 0;
377 s->rows = 0;
378 s->src_bpp = 0;
379 s->invalidate = true;
380
381 imx6ul_lcdif_update_irq(s);
382 if (imx6ul_lcdif_is_running(s) &&
383 !imx6ul_lcdif_frame_done_pending(s) &&
384 !timer_pending(s->frame_timer)) {
385 imx6ul_lcdif_schedule_frame(s);
386 }
387
388 return 0;
389 }
390
391 static const VMStateDescription vmstate_imx6ul_lcdif = {
392 .name = TYPE_IMX6UL_LCDIF,
393 .version_id = 1,
394 .minimum_version_id = 1,
395 .post_load = imx6ul_lcdif_post_load,
396 .fields = (const VMStateField[]) {
397 VMSTATE_UINT32_ARRAY(regs, IMX6ULLCDIFState, IMX6UL_LCDIF_REGS_NUM),
398 VMSTATE_TIMER_PTR(frame_timer, IMX6ULLCDIFState),
399 VMSTATE_END_OF_LIST()
400 },
401 };
402
403 static void imx6ul_lcdif_realize(DeviceState *dev, Error **errp)
404 {
405 IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);
406
407 s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
408 imx6ul_lcdif_frame_timer_cb, s);
409 s->invalidate = true;
410 memory_region_init_io(&s->iomem, OBJECT(dev), &imx6ul_lcdif_ops, s,
411 TYPE_IMX6UL_LCDIF, LCDIF_MMIO_SIZE);
412 sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
413 sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
414 s->con = qemu_graphic_console_create(dev, 0, &imx6ul_lcdif_graphic_ops, s);
415 }
416
417 static void imx6ul_lcdif_unrealize(DeviceState *dev)
418 {
419 IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev);
420
421 timer_del(s->frame_timer);
422 timer_free(s->frame_timer);
423 s->frame_timer = NULL;
424
425 if (s->con) {
426 qemu_graphic_console_close(s->con);
427 s->con = NULL;
428 }
429 }
430
431 static void imx6ul_lcdif_class_init(ObjectClass *klass, const void *data)
432 {
433 DeviceClass *dc = DEVICE_CLASS(klass);
434
435 dc->realize = imx6ul_lcdif_realize;
436 dc->unrealize = imx6ul_lcdif_unrealize;
437 dc->vmsd = &vmstate_imx6ul_lcdif;
438 device_class_set_legacy_reset(dc, imx6ul_lcdif_reset);
439 dc->desc = "i.MX6UL LCDIF";
440 }
441
442 static const TypeInfo imx6ul_lcdif_info = {
443 .name = TYPE_IMX6UL_LCDIF,
444 .parent = TYPE_SYS_BUS_DEVICE,
445 .instance_size = sizeof(IMX6ULLCDIFState),
446 .class_init = imx6ul_lcdif_class_init,
447 };
448
449 static void imx6ul_lcdif_register_types(void)
450 {
451 type_register_static(&imx6ul_lcdif_info);
452 }
453
454 type_init(imx6ul_lcdif_register_types)