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