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