xref: /openbmc/qemu/hw/display/omap_lcdc.c (revision 19f70347)
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 
20 #include "qemu/osdep.h"
21 #include "hw/irq.h"
22 #include "ui/console.h"
23 #include "hw/arm/omap.h"
24 #include "framebuffer.h"
25 #include "ui/pixel_ops.h"
26 
27 struct omap_lcd_panel_s {
28     MemoryRegion *sysmem;
29     MemoryRegion iomem;
30     MemoryRegionSection fbsection;
31     qemu_irq irq;
32     QemuConsole *con;
33 
34     int plm;
35     int tft;
36     int mono;
37     int enable;
38     int width;
39     int height;
40     int interrupts;
41     uint32_t timing[3];
42     uint32_t subpanel;
43     uint32_t ctrl;
44 
45     struct omap_dma_lcd_channel_s *dma;
46     uint16_t palette[256];
47     int palette_done;
48     int frame_done;
49     int invalidate;
50     int sync_error;
51 };
52 
53 static void omap_lcd_interrupts(struct omap_lcd_panel_s *s)
54 {
55     if (s->frame_done && (s->interrupts & 1)) {
56         qemu_irq_raise(s->irq);
57         return;
58     }
59 
60     if (s->palette_done && (s->interrupts & 2)) {
61         qemu_irq_raise(s->irq);
62         return;
63     }
64 
65     if (s->sync_error) {
66         qemu_irq_raise(s->irq);
67         return;
68     }
69 
70     qemu_irq_lower(s->irq);
71 }
72 
73 #define draw_line_func drawfn
74 
75 #define DEPTH 32
76 #include "omap_lcd_template.h"
77 
78 static void omap_update_display(void *opaque)
79 {
80     struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque;
81     DisplaySurface *surface = qemu_console_surface(omap_lcd->con);
82     draw_line_func draw_line;
83     int size, height, first, last;
84     int width, linesize, step, bpp, frame_offset;
85     hwaddr frame_base;
86 
87     if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable ||
88         !surface_bits_per_pixel(surface)) {
89         return;
90     }
91 
92     frame_offset = 0;
93     if (omap_lcd->plm != 2) {
94         cpu_physical_memory_read(
95                 omap_lcd->dma->phys_framebuffer[omap_lcd->dma->current_frame],
96                 omap_lcd->palette, 0x200);
97         switch (omap_lcd->palette[0] >> 12 & 7) {
98         case 3 ... 7:
99             frame_offset += 0x200;
100             break;
101         default:
102             frame_offset += 0x20;
103         }
104     }
105 
106     /* Colour depth */
107     switch ((omap_lcd->palette[0] >> 12) & 7) {
108     case 1:
109         draw_line = draw_line2_32;
110         bpp = 2;
111         break;
112 
113     case 2:
114         draw_line = draw_line4_32;
115         bpp = 4;
116         break;
117 
118     case 3:
119         draw_line = draw_line8_32;
120         bpp = 8;
121         break;
122 
123     case 4 ... 7:
124         if (!omap_lcd->tft)
125             draw_line = draw_line12_32;
126         else
127             draw_line = draw_line16_32;
128         bpp = 16;
129         break;
130 
131     default:
132         /* Unsupported at the moment.  */
133         return;
134     }
135 
136     /* Resolution */
137     width = omap_lcd->width;
138     if (width != surface_width(surface) ||
139         omap_lcd->height != surface_height(surface)) {
140         qemu_console_resize(omap_lcd->con,
141                             omap_lcd->width, omap_lcd->height);
142         surface = qemu_console_surface(omap_lcd->con);
143         omap_lcd->invalidate = 1;
144     }
145 
146     if (omap_lcd->dma->current_frame == 0)
147         size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top;
148     else
149         size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top;
150 
151     if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) {
152         omap_lcd->sync_error = 1;
153         omap_lcd_interrupts(omap_lcd);
154         omap_lcd->enable = 0;
155         return;
156     }
157 
158     /* Content */
159     frame_base = omap_lcd->dma->phys_framebuffer[
160             omap_lcd->dma->current_frame] + frame_offset;
161     omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame;
162     if (omap_lcd->dma->interrupts & 1)
163         qemu_irq_raise(omap_lcd->dma->irq);
164     if (omap_lcd->dma->dual)
165         omap_lcd->dma->current_frame ^= 1;
166 
167     if (!surface_bits_per_pixel(surface)) {
168         return;
169     }
170 
171     first = 0;
172     height = omap_lcd->height;
173     if (omap_lcd->subpanel & (1 << 31)) {
174         if (omap_lcd->subpanel & (1 << 29))
175             first = (omap_lcd->subpanel >> 16) & 0x3ff;
176         else
177             height = (omap_lcd->subpanel >> 16) & 0x3ff;
178         /* TODO: fill the rest of the panel with DPD */
179     }
180 
181     step = width * bpp >> 3;
182     linesize = surface_stride(surface);
183     if (omap_lcd->invalidate) {
184         framebuffer_update_memory_section(&omap_lcd->fbsection,
185                                           omap_lcd->sysmem, frame_base,
186                                           height, step);
187     }
188 
189     framebuffer_update_display(surface, &omap_lcd->fbsection,
190                                width, height,
191                                step, linesize, 0,
192                                omap_lcd->invalidate,
193                                draw_line, omap_lcd->palette,
194                                &first, &last);
195 
196     if (first >= 0) {
197         dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1);
198     }
199     omap_lcd->invalidate = 0;
200 }
201 
202 static void omap_invalidate_display(void *opaque) {
203     struct omap_lcd_panel_s *omap_lcd = opaque;
204     omap_lcd->invalidate = 1;
205 }
206 
207 static void omap_lcd_update(struct omap_lcd_panel_s *s) {
208     if (!s->enable) {
209         s->dma->current_frame = -1;
210         s->sync_error = 0;
211         if (s->plm != 1)
212             s->frame_done = 1;
213         omap_lcd_interrupts(s);
214         return;
215     }
216 
217     if (s->dma->current_frame == -1) {
218         s->frame_done = 0;
219         s->palette_done = 0;
220         s->dma->current_frame = 0;
221     }
222 
223     if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu,
224                             s->dma->src_f1_top) ||
225                     !s->dma->mpu->port[
226                     s->dma->src].addr_valid(s->dma->mpu,
227                             s->dma->src_f1_bottom) ||
228                     (s->dma->dual &&
229                      (!s->dma->mpu->port[
230                       s->dma->src].addr_valid(s->dma->mpu,
231                               s->dma->src_f2_top) ||
232                       !s->dma->mpu->port[
233                       s->dma->src].addr_valid(s->dma->mpu,
234                               s->dma->src_f2_bottom)))) {
235         s->dma->condition |= 1 << 2;
236         if (s->dma->interrupts & (1 << 1))
237             qemu_irq_raise(s->dma->irq);
238         s->enable = 0;
239         return;
240     }
241 
242     s->dma->phys_framebuffer[0] = s->dma->src_f1_top;
243     s->dma->phys_framebuffer[1] = s->dma->src_f2_top;
244 
245     if (s->plm != 2 && !s->palette_done) {
246         cpu_physical_memory_read(
247                             s->dma->phys_framebuffer[s->dma->current_frame],
248                             s->palette, 0x200);
249         s->palette_done = 1;
250         omap_lcd_interrupts(s);
251     }
252 }
253 
254 static uint64_t omap_lcdc_read(void *opaque, hwaddr addr,
255                                unsigned size)
256 {
257     struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
258 
259     switch (addr) {
260     case 0x00:	/* LCD_CONTROL */
261         return (s->tft << 23) | (s->plm << 20) |
262                 (s->tft << 7) | (s->interrupts << 3) |
263                 (s->mono << 1) | s->enable | s->ctrl | 0xfe000c34;
264 
265     case 0x04:	/* LCD_TIMING0 */
266         return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f;
267 
268     case 0x08:	/* LCD_TIMING1 */
269         return (s->timing[1] << 10) | (s->height - 1);
270 
271     case 0x0c:	/* LCD_TIMING2 */
272         return s->timing[2] | 0xfc000000;
273 
274     case 0x10:	/* LCD_STATUS */
275         return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done;
276 
277     case 0x14:	/* LCD_SUBPANEL */
278         return s->subpanel;
279 
280     default:
281         break;
282     }
283     OMAP_BAD_REG(addr);
284     return 0;
285 }
286 
287 static void omap_lcdc_write(void *opaque, hwaddr addr,
288                             uint64_t value, unsigned size)
289 {
290     struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
291 
292     switch (addr) {
293     case 0x00:	/* LCD_CONTROL */
294         s->plm = (value >> 20) & 3;
295         s->tft = (value >> 7) & 1;
296         s->interrupts = (value >> 3) & 3;
297         s->mono = (value >> 1) & 1;
298         s->ctrl = value & 0x01cff300;
299         if (s->enable != (value & 1)) {
300             s->enable = value & 1;
301             omap_lcd_update(s);
302         }
303         break;
304 
305     case 0x04:	/* LCD_TIMING0 */
306         s->timing[0] = value >> 10;
307         s->width = (value & 0x3ff) + 1;
308         break;
309 
310     case 0x08:	/* LCD_TIMING1 */
311         s->timing[1] = value >> 10;
312         s->height = (value & 0x3ff) + 1;
313         break;
314 
315     case 0x0c:	/* LCD_TIMING2 */
316         s->timing[2] = value;
317         break;
318 
319     case 0x10:	/* LCD_STATUS */
320         break;
321 
322     case 0x14:	/* LCD_SUBPANEL */
323         s->subpanel = value & 0xa1ffffff;
324         break;
325 
326     default:
327         OMAP_BAD_REG(addr);
328     }
329 }
330 
331 static const MemoryRegionOps omap_lcdc_ops = {
332     .read = omap_lcdc_read,
333     .write = omap_lcdc_write,
334     .endianness = DEVICE_NATIVE_ENDIAN,
335 };
336 
337 void omap_lcdc_reset(struct omap_lcd_panel_s *s)
338 {
339     s->dma->current_frame = -1;
340     s->plm = 0;
341     s->tft = 0;
342     s->mono = 0;
343     s->enable = 0;
344     s->width = 0;
345     s->height = 0;
346     s->interrupts = 0;
347     s->timing[0] = 0;
348     s->timing[1] = 0;
349     s->timing[2] = 0;
350     s->subpanel = 0;
351     s->palette_done = 0;
352     s->frame_done = 0;
353     s->sync_error = 0;
354     s->invalidate = 1;
355     s->subpanel = 0;
356     s->ctrl = 0;
357 }
358 
359 static const GraphicHwOps omap_ops = {
360     .invalidate  = omap_invalidate_display,
361     .gfx_update  = omap_update_display,
362 };
363 
364 struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem,
365                                         hwaddr base,
366                                         qemu_irq irq,
367                                         struct omap_dma_lcd_channel_s *dma,
368                                         omap_clk clk)
369 {
370     struct omap_lcd_panel_s *s = g_new0(struct omap_lcd_panel_s, 1);
371 
372     s->irq = irq;
373     s->dma = dma;
374     s->sysmem = sysmem;
375     omap_lcdc_reset(s);
376 
377     memory_region_init_io(&s->iomem, NULL, &omap_lcdc_ops, s, "omap.lcdc", 0x100);
378     memory_region_add_subregion(sysmem, base, &s->iomem);
379 
380     s->con = graphic_console_init(NULL, 0, &omap_ops, s);
381 
382     return s;
383 }
384