xref: /openbmc/qemu/hw/display/omap_lcdc.c (revision 47b43a1f)
1 /*
2  * OMAP LCD controller.
3  *
4  * Copyright (C) 2006-2007 Andrzej Zaborowski  <balrog@zabor.org>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19 #include "hw/hw.h"
20 #include "ui/console.h"
21 #include "hw/arm/omap.h"
22 #include "framebuffer.h"
23 #include "ui/pixel_ops.h"
24 
25 struct omap_lcd_panel_s {
26     MemoryRegion *sysmem;
27     MemoryRegion iomem;
28     qemu_irq irq;
29     QemuConsole *con;
30 
31     int plm;
32     int tft;
33     int mono;
34     int enable;
35     int width;
36     int height;
37     int interrupts;
38     uint32_t timing[3];
39     uint32_t subpanel;
40     uint32_t ctrl;
41 
42     struct omap_dma_lcd_channel_s *dma;
43     uint16_t palette[256];
44     int palette_done;
45     int frame_done;
46     int invalidate;
47     int sync_error;
48 };
49 
50 static void omap_lcd_interrupts(struct omap_lcd_panel_s *s)
51 {
52     if (s->frame_done && (s->interrupts & 1)) {
53         qemu_irq_raise(s->irq);
54         return;
55     }
56 
57     if (s->palette_done && (s->interrupts & 2)) {
58         qemu_irq_raise(s->irq);
59         return;
60     }
61 
62     if (s->sync_error) {
63         qemu_irq_raise(s->irq);
64         return;
65     }
66 
67     qemu_irq_lower(s->irq);
68 }
69 
70 #define draw_line_func drawfn
71 
72 #define DEPTH 8
73 #include "omap_lcd_template.h"
74 #define DEPTH 15
75 #include "omap_lcd_template.h"
76 #define DEPTH 16
77 #include "omap_lcd_template.h"
78 #define DEPTH 32
79 #include "omap_lcd_template.h"
80 
81 static draw_line_func draw_line_table2[33] = {
82     [0 ... 32]	= NULL,
83     [8]		= draw_line2_8,
84     [15]	= draw_line2_15,
85     [16]	= draw_line2_16,
86     [32]	= draw_line2_32,
87 }, draw_line_table4[33] = {
88     [0 ... 32]	= NULL,
89     [8]		= draw_line4_8,
90     [15]	= draw_line4_15,
91     [16]	= draw_line4_16,
92     [32]	= draw_line4_32,
93 }, draw_line_table8[33] = {
94     [0 ... 32]	= NULL,
95     [8]		= draw_line8_8,
96     [15]	= draw_line8_15,
97     [16]	= draw_line8_16,
98     [32]	= draw_line8_32,
99 }, draw_line_table12[33] = {
100     [0 ... 32]	= NULL,
101     [8]		= draw_line12_8,
102     [15]	= draw_line12_15,
103     [16]	= draw_line12_16,
104     [32]	= draw_line12_32,
105 }, draw_line_table16[33] = {
106     [0 ... 32]	= NULL,
107     [8]		= draw_line16_8,
108     [15]	= draw_line16_15,
109     [16]	= draw_line16_16,
110     [32]	= draw_line16_32,
111 };
112 
113 static void omap_update_display(void *opaque)
114 {
115     struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque;
116     DisplaySurface *surface = qemu_console_surface(omap_lcd->con);
117     draw_line_func draw_line;
118     int size, height, first, last;
119     int width, linesize, step, bpp, frame_offset;
120     hwaddr frame_base;
121 
122     if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable ||
123         !surface_bits_per_pixel(surface)) {
124         return;
125     }
126 
127     frame_offset = 0;
128     if (omap_lcd->plm != 2) {
129         cpu_physical_memory_read(omap_lcd->dma->phys_framebuffer[
130                                   omap_lcd->dma->current_frame],
131                                  (void *)omap_lcd->palette, 0x200);
132         switch (omap_lcd->palette[0] >> 12 & 7) {
133         case 3 ... 7:
134             frame_offset += 0x200;
135             break;
136         default:
137             frame_offset += 0x20;
138         }
139     }
140 
141     /* Colour depth */
142     switch ((omap_lcd->palette[0] >> 12) & 7) {
143     case 1:
144         draw_line = draw_line_table2[surface_bits_per_pixel(surface)];
145         bpp = 2;
146         break;
147 
148     case 2:
149         draw_line = draw_line_table4[surface_bits_per_pixel(surface)];
150         bpp = 4;
151         break;
152 
153     case 3:
154         draw_line = draw_line_table8[surface_bits_per_pixel(surface)];
155         bpp = 8;
156         break;
157 
158     case 4 ... 7:
159         if (!omap_lcd->tft)
160             draw_line = draw_line_table12[surface_bits_per_pixel(surface)];
161         else
162             draw_line = draw_line_table16[surface_bits_per_pixel(surface)];
163         bpp = 16;
164         break;
165 
166     default:
167         /* Unsupported at the moment.  */
168         return;
169     }
170 
171     /* Resolution */
172     width = omap_lcd->width;
173     if (width != surface_width(surface) ||
174         omap_lcd->height != surface_height(surface)) {
175         qemu_console_resize(omap_lcd->con,
176                             omap_lcd->width, omap_lcd->height);
177         surface = qemu_console_surface(omap_lcd->con);
178         omap_lcd->invalidate = 1;
179     }
180 
181     if (omap_lcd->dma->current_frame == 0)
182         size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top;
183     else
184         size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top;
185 
186     if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) {
187         omap_lcd->sync_error = 1;
188         omap_lcd_interrupts(omap_lcd);
189         omap_lcd->enable = 0;
190         return;
191     }
192 
193     /* Content */
194     frame_base = omap_lcd->dma->phys_framebuffer[
195             omap_lcd->dma->current_frame] + frame_offset;
196     omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame;
197     if (omap_lcd->dma->interrupts & 1)
198         qemu_irq_raise(omap_lcd->dma->irq);
199     if (omap_lcd->dma->dual)
200         omap_lcd->dma->current_frame ^= 1;
201 
202     if (!surface_bits_per_pixel(surface)) {
203         return;
204     }
205 
206     first = 0;
207     height = omap_lcd->height;
208     if (omap_lcd->subpanel & (1 << 31)) {
209         if (omap_lcd->subpanel & (1 << 29))
210             first = (omap_lcd->subpanel >> 16) & 0x3ff;
211         else
212             height = (omap_lcd->subpanel >> 16) & 0x3ff;
213         /* TODO: fill the rest of the panel with DPD */
214     }
215 
216     step = width * bpp >> 3;
217     linesize = surface_stride(surface);
218     framebuffer_update_display(surface, omap_lcd->sysmem,
219                                frame_base, width, height,
220                                step, linesize, 0,
221                                omap_lcd->invalidate,
222                                draw_line, omap_lcd->palette,
223                                &first, &last);
224     if (first >= 0) {
225         dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1);
226     }
227     omap_lcd->invalidate = 0;
228 }
229 
230 static void omap_ppm_save(const char *filename, uint8_t *data,
231                     int w, int h, int linesize, Error **errp)
232 {
233     FILE *f;
234     uint8_t *d, *d1;
235     unsigned int v;
236     int ret, y, x, bpp;
237 
238     f = fopen(filename, "wb");
239     if (!f) {
240         error_setg(errp, "failed to open file '%s': %s", filename,
241                    strerror(errno));
242         return;
243     }
244     ret = fprintf(f, "P6\n%d %d\n%d\n", w, h, 255);
245     if (ret < 0) {
246         goto write_err;
247     }
248     d1 = data;
249     bpp = linesize / w;
250     for (y = 0; y < h; y ++) {
251         d = d1;
252         for (x = 0; x < w; x ++) {
253             v = *(uint32_t *) d;
254             switch (bpp) {
255             case 2:
256                 ret = fputc((v >> 8) & 0xf8, f);
257                 if (ret == EOF) {
258                     goto write_err;
259                 }
260                 ret = fputc((v >> 3) & 0xfc, f);
261                 if (ret == EOF) {
262                     goto write_err;
263                 }
264                 ret = fputc((v << 3) & 0xf8, f);
265                 if (ret == EOF) {
266                     goto write_err;
267                 }
268                 break;
269             case 3:
270             case 4:
271             default:
272                 ret = fputc((v >> 16) & 0xff, f);
273                 if (ret == EOF) {
274                     goto write_err;
275                 }
276                 ret = fputc((v >> 8) & 0xff, f);
277                 if (ret == EOF) {
278                     goto write_err;
279                 }
280                 ret = fputc((v) & 0xff, f);
281                 if (ret == EOF) {
282                     goto write_err;
283                 }
284                 break;
285             }
286             d += bpp;
287         }
288         d1 += linesize;
289     }
290 out:
291     fclose(f);
292     return;
293 
294 write_err:
295     error_setg(errp, "failed to write to file '%s': %s", filename,
296                strerror(errno));
297     unlink(filename);
298     goto out;
299 }
300 
301 static void omap_screen_dump(void *opaque, const char *filename, bool cswitch,
302                              Error **errp)
303 {
304     struct omap_lcd_panel_s *omap_lcd = opaque;
305     DisplaySurface *surface = qemu_console_surface(omap_lcd->con);
306 
307     omap_update_display(opaque);
308     if (omap_lcd && surface_data(surface))
309         omap_ppm_save(filename, surface_data(surface),
310                     omap_lcd->width, omap_lcd->height,
311                     surface_stride(surface), errp);
312 }
313 
314 static void omap_invalidate_display(void *opaque) {
315     struct omap_lcd_panel_s *omap_lcd = opaque;
316     omap_lcd->invalidate = 1;
317 }
318 
319 static void omap_lcd_update(struct omap_lcd_panel_s *s) {
320     if (!s->enable) {
321         s->dma->current_frame = -1;
322         s->sync_error = 0;
323         if (s->plm != 1)
324             s->frame_done = 1;
325         omap_lcd_interrupts(s);
326         return;
327     }
328 
329     if (s->dma->current_frame == -1) {
330         s->frame_done = 0;
331         s->palette_done = 0;
332         s->dma->current_frame = 0;
333     }
334 
335     if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu,
336                             s->dma->src_f1_top) ||
337                     !s->dma->mpu->port[
338                     s->dma->src].addr_valid(s->dma->mpu,
339                             s->dma->src_f1_bottom) ||
340                     (s->dma->dual &&
341                      (!s->dma->mpu->port[
342                       s->dma->src].addr_valid(s->dma->mpu,
343                               s->dma->src_f2_top) ||
344                       !s->dma->mpu->port[
345                       s->dma->src].addr_valid(s->dma->mpu,
346                               s->dma->src_f2_bottom)))) {
347         s->dma->condition |= 1 << 2;
348         if (s->dma->interrupts & (1 << 1))
349             qemu_irq_raise(s->dma->irq);
350         s->enable = 0;
351         return;
352     }
353 
354     s->dma->phys_framebuffer[0] = s->dma->src_f1_top;
355     s->dma->phys_framebuffer[1] = s->dma->src_f2_top;
356 
357     if (s->plm != 2 && !s->palette_done) {
358         cpu_physical_memory_read(
359             s->dma->phys_framebuffer[s->dma->current_frame],
360             (void *)s->palette, 0x200);
361         s->palette_done = 1;
362         omap_lcd_interrupts(s);
363     }
364 }
365 
366 static uint64_t omap_lcdc_read(void *opaque, hwaddr addr,
367                                unsigned size)
368 {
369     struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
370 
371     switch (addr) {
372     case 0x00:	/* LCD_CONTROL */
373         return (s->tft << 23) | (s->plm << 20) |
374                 (s->tft << 7) | (s->interrupts << 3) |
375                 (s->mono << 1) | s->enable | s->ctrl | 0xfe000c34;
376 
377     case 0x04:	/* LCD_TIMING0 */
378         return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f;
379 
380     case 0x08:	/* LCD_TIMING1 */
381         return (s->timing[1] << 10) | (s->height - 1);
382 
383     case 0x0c:	/* LCD_TIMING2 */
384         return s->timing[2] | 0xfc000000;
385 
386     case 0x10:	/* LCD_STATUS */
387         return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done;
388 
389     case 0x14:	/* LCD_SUBPANEL */
390         return s->subpanel;
391 
392     default:
393         break;
394     }
395     OMAP_BAD_REG(addr);
396     return 0;
397 }
398 
399 static void omap_lcdc_write(void *opaque, hwaddr addr,
400                             uint64_t value, unsigned size)
401 {
402     struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
403 
404     switch (addr) {
405     case 0x00:	/* LCD_CONTROL */
406         s->plm = (value >> 20) & 3;
407         s->tft = (value >> 7) & 1;
408         s->interrupts = (value >> 3) & 3;
409         s->mono = (value >> 1) & 1;
410         s->ctrl = value & 0x01cff300;
411         if (s->enable != (value & 1)) {
412             s->enable = value & 1;
413             omap_lcd_update(s);
414         }
415         break;
416 
417     case 0x04:	/* LCD_TIMING0 */
418         s->timing[0] = value >> 10;
419         s->width = (value & 0x3ff) + 1;
420         break;
421 
422     case 0x08:	/* LCD_TIMING1 */
423         s->timing[1] = value >> 10;
424         s->height = (value & 0x3ff) + 1;
425         break;
426 
427     case 0x0c:	/* LCD_TIMING2 */
428         s->timing[2] = value;
429         break;
430 
431     case 0x10:	/* LCD_STATUS */
432         break;
433 
434     case 0x14:	/* LCD_SUBPANEL */
435         s->subpanel = value & 0xa1ffffff;
436         break;
437 
438     default:
439         OMAP_BAD_REG(addr);
440     }
441 }
442 
443 static const MemoryRegionOps omap_lcdc_ops = {
444     .read = omap_lcdc_read,
445     .write = omap_lcdc_write,
446     .endianness = DEVICE_NATIVE_ENDIAN,
447 };
448 
449 void omap_lcdc_reset(struct omap_lcd_panel_s *s)
450 {
451     s->dma->current_frame = -1;
452     s->plm = 0;
453     s->tft = 0;
454     s->mono = 0;
455     s->enable = 0;
456     s->width = 0;
457     s->height = 0;
458     s->interrupts = 0;
459     s->timing[0] = 0;
460     s->timing[1] = 0;
461     s->timing[2] = 0;
462     s->subpanel = 0;
463     s->palette_done = 0;
464     s->frame_done = 0;
465     s->sync_error = 0;
466     s->invalidate = 1;
467     s->subpanel = 0;
468     s->ctrl = 0;
469 }
470 
471 struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem,
472                                         hwaddr base,
473                                         qemu_irq irq,
474                                         struct omap_dma_lcd_channel_s *dma,
475                                         omap_clk clk)
476 {
477     struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *)
478             g_malloc0(sizeof(struct omap_lcd_panel_s));
479 
480     s->irq = irq;
481     s->dma = dma;
482     s->sysmem = sysmem;
483     omap_lcdc_reset(s);
484 
485     memory_region_init_io(&s->iomem, &omap_lcdc_ops, s, "omap.lcdc", 0x100);
486     memory_region_add_subregion(sysmem, base, &s->iomem);
487 
488     s->con = graphic_console_init(omap_update_display,
489                                   omap_invalidate_display,
490                                   omap_screen_dump, NULL, s);
491 
492     return s;
493 }
494