qemu/hw/display/omap_lcdc.c
Paolo Bonzini c1076c3e13 framebuffer: set DIRTY_MEMORY_VGA on RAM that is used for the framebuffer
The MemoryRegionSection contains enough information to access the
RAM region underlying the framebuffer, and can be cached inside the
display device.

By doing this, the new framebuffer_update_memory_section function can
enable dirty memory logging on the relevant RAM region.  The function
must be called whenever the stride or base of the framebuffer changes;
a simple way to cover these cases is to call it on every full frame
invalidation, which is a rare case.

framebuffer_update_display now works entirely on a MemoryRegionSection,
without going through cpu_physical_memory_map/unmap.

Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2015-07-24 13:57:45 +02:00

421 lines
12 KiB
C

/*
* OMAP LCD controller.
*
* Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "hw/hw.h"
#include "ui/console.h"
#include "hw/arm/omap.h"
#include "framebuffer.h"
#include "ui/pixel_ops.h"
struct omap_lcd_panel_s {
MemoryRegion *sysmem;
MemoryRegion iomem;
MemoryRegionSection fbsection;
qemu_irq irq;
QemuConsole *con;
int plm;
int tft;
int mono;
int enable;
int width;
int height;
int interrupts;
uint32_t timing[3];
uint32_t subpanel;
uint32_t ctrl;
struct omap_dma_lcd_channel_s *dma;
uint16_t palette[256];
int palette_done;
int frame_done;
int invalidate;
int sync_error;
};
static void omap_lcd_interrupts(struct omap_lcd_panel_s *s)
{
if (s->frame_done && (s->interrupts & 1)) {
qemu_irq_raise(s->irq);
return;
}
if (s->palette_done && (s->interrupts & 2)) {
qemu_irq_raise(s->irq);
return;
}
if (s->sync_error) {
qemu_irq_raise(s->irq);
return;
}
qemu_irq_lower(s->irq);
}
#define draw_line_func drawfn
#define DEPTH 8
#include "omap_lcd_template.h"
#define DEPTH 15
#include "omap_lcd_template.h"
#define DEPTH 16
#include "omap_lcd_template.h"
#define DEPTH 32
#include "omap_lcd_template.h"
static draw_line_func draw_line_table2[33] = {
[0 ... 32] = NULL,
[8] = draw_line2_8,
[15] = draw_line2_15,
[16] = draw_line2_16,
[32] = draw_line2_32,
}, draw_line_table4[33] = {
[0 ... 32] = NULL,
[8] = draw_line4_8,
[15] = draw_line4_15,
[16] = draw_line4_16,
[32] = draw_line4_32,
}, draw_line_table8[33] = {
[0 ... 32] = NULL,
[8] = draw_line8_8,
[15] = draw_line8_15,
[16] = draw_line8_16,
[32] = draw_line8_32,
}, draw_line_table12[33] = {
[0 ... 32] = NULL,
[8] = draw_line12_8,
[15] = draw_line12_15,
[16] = draw_line12_16,
[32] = draw_line12_32,
}, draw_line_table16[33] = {
[0 ... 32] = NULL,
[8] = draw_line16_8,
[15] = draw_line16_15,
[16] = draw_line16_16,
[32] = draw_line16_32,
};
static void omap_update_display(void *opaque)
{
struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque;
DisplaySurface *surface = qemu_console_surface(omap_lcd->con);
draw_line_func draw_line;
int size, height, first, last;
int width, linesize, step, bpp, frame_offset;
hwaddr frame_base;
if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable ||
!surface_bits_per_pixel(surface)) {
return;
}
frame_offset = 0;
if (omap_lcd->plm != 2) {
cpu_physical_memory_read(omap_lcd->dma->phys_framebuffer[
omap_lcd->dma->current_frame],
(void *)omap_lcd->palette, 0x200);
switch (omap_lcd->palette[0] >> 12 & 7) {
case 3 ... 7:
frame_offset += 0x200;
break;
default:
frame_offset += 0x20;
}
}
/* Colour depth */
switch ((omap_lcd->palette[0] >> 12) & 7) {
case 1:
draw_line = draw_line_table2[surface_bits_per_pixel(surface)];
bpp = 2;
break;
case 2:
draw_line = draw_line_table4[surface_bits_per_pixel(surface)];
bpp = 4;
break;
case 3:
draw_line = draw_line_table8[surface_bits_per_pixel(surface)];
bpp = 8;
break;
case 4 ... 7:
if (!omap_lcd->tft)
draw_line = draw_line_table12[surface_bits_per_pixel(surface)];
else
draw_line = draw_line_table16[surface_bits_per_pixel(surface)];
bpp = 16;
break;
default:
/* Unsupported at the moment. */
return;
}
/* Resolution */
width = omap_lcd->width;
if (width != surface_width(surface) ||
omap_lcd->height != surface_height(surface)) {
qemu_console_resize(omap_lcd->con,
omap_lcd->width, omap_lcd->height);
surface = qemu_console_surface(omap_lcd->con);
omap_lcd->invalidate = 1;
}
if (omap_lcd->dma->current_frame == 0)
size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top;
else
size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top;
if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) {
omap_lcd->sync_error = 1;
omap_lcd_interrupts(omap_lcd);
omap_lcd->enable = 0;
return;
}
/* Content */
frame_base = omap_lcd->dma->phys_framebuffer[
omap_lcd->dma->current_frame] + frame_offset;
omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame;
if (omap_lcd->dma->interrupts & 1)
qemu_irq_raise(omap_lcd->dma->irq);
if (omap_lcd->dma->dual)
omap_lcd->dma->current_frame ^= 1;
if (!surface_bits_per_pixel(surface)) {
return;
}
first = 0;
height = omap_lcd->height;
if (omap_lcd->subpanel & (1 << 31)) {
if (omap_lcd->subpanel & (1 << 29))
first = (omap_lcd->subpanel >> 16) & 0x3ff;
else
height = (omap_lcd->subpanel >> 16) & 0x3ff;
/* TODO: fill the rest of the panel with DPD */
}
step = width * bpp >> 3;
linesize = surface_stride(surface);
if (omap_lcd->invalidate) {
framebuffer_update_memory_section(&omap_lcd->fbsection,
omap_lcd->sysmem, frame_base,
height, step);
}
framebuffer_update_display(surface, &omap_lcd->fbsection,
width, height,
step, linesize, 0,
omap_lcd->invalidate,
draw_line, omap_lcd->palette,
&first, &last);
if (first >= 0) {
dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1);
}
omap_lcd->invalidate = 0;
}
static void omap_invalidate_display(void *opaque) {
struct omap_lcd_panel_s *omap_lcd = opaque;
omap_lcd->invalidate = 1;
}
static void omap_lcd_update(struct omap_lcd_panel_s *s) {
if (!s->enable) {
s->dma->current_frame = -1;
s->sync_error = 0;
if (s->plm != 1)
s->frame_done = 1;
omap_lcd_interrupts(s);
return;
}
if (s->dma->current_frame == -1) {
s->frame_done = 0;
s->palette_done = 0;
s->dma->current_frame = 0;
}
if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu,
s->dma->src_f1_top) ||
!s->dma->mpu->port[
s->dma->src].addr_valid(s->dma->mpu,
s->dma->src_f1_bottom) ||
(s->dma->dual &&
(!s->dma->mpu->port[
s->dma->src].addr_valid(s->dma->mpu,
s->dma->src_f2_top) ||
!s->dma->mpu->port[
s->dma->src].addr_valid(s->dma->mpu,
s->dma->src_f2_bottom)))) {
s->dma->condition |= 1 << 2;
if (s->dma->interrupts & (1 << 1))
qemu_irq_raise(s->dma->irq);
s->enable = 0;
return;
}
s->dma->phys_framebuffer[0] = s->dma->src_f1_top;
s->dma->phys_framebuffer[1] = s->dma->src_f2_top;
if (s->plm != 2 && !s->palette_done) {
cpu_physical_memory_read(
s->dma->phys_framebuffer[s->dma->current_frame],
(void *)s->palette, 0x200);
s->palette_done = 1;
omap_lcd_interrupts(s);
}
}
static uint64_t omap_lcdc_read(void *opaque, hwaddr addr,
unsigned size)
{
struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
switch (addr) {
case 0x00: /* LCD_CONTROL */
return (s->tft << 23) | (s->plm << 20) |
(s->tft << 7) | (s->interrupts << 3) |
(s->mono << 1) | s->enable | s->ctrl | 0xfe000c34;
case 0x04: /* LCD_TIMING0 */
return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f;
case 0x08: /* LCD_TIMING1 */
return (s->timing[1] << 10) | (s->height - 1);
case 0x0c: /* LCD_TIMING2 */
return s->timing[2] | 0xfc000000;
case 0x10: /* LCD_STATUS */
return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done;
case 0x14: /* LCD_SUBPANEL */
return s->subpanel;
default:
break;
}
OMAP_BAD_REG(addr);
return 0;
}
static void omap_lcdc_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
switch (addr) {
case 0x00: /* LCD_CONTROL */
s->plm = (value >> 20) & 3;
s->tft = (value >> 7) & 1;
s->interrupts = (value >> 3) & 3;
s->mono = (value >> 1) & 1;
s->ctrl = value & 0x01cff300;
if (s->enable != (value & 1)) {
s->enable = value & 1;
omap_lcd_update(s);
}
break;
case 0x04: /* LCD_TIMING0 */
s->timing[0] = value >> 10;
s->width = (value & 0x3ff) + 1;
break;
case 0x08: /* LCD_TIMING1 */
s->timing[1] = value >> 10;
s->height = (value & 0x3ff) + 1;
break;
case 0x0c: /* LCD_TIMING2 */
s->timing[2] = value;
break;
case 0x10: /* LCD_STATUS */
break;
case 0x14: /* LCD_SUBPANEL */
s->subpanel = value & 0xa1ffffff;
break;
default:
OMAP_BAD_REG(addr);
}
}
static const MemoryRegionOps omap_lcdc_ops = {
.read = omap_lcdc_read,
.write = omap_lcdc_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
void omap_lcdc_reset(struct omap_lcd_panel_s *s)
{
s->dma->current_frame = -1;
s->plm = 0;
s->tft = 0;
s->mono = 0;
s->enable = 0;
s->width = 0;
s->height = 0;
s->interrupts = 0;
s->timing[0] = 0;
s->timing[1] = 0;
s->timing[2] = 0;
s->subpanel = 0;
s->palette_done = 0;
s->frame_done = 0;
s->sync_error = 0;
s->invalidate = 1;
s->subpanel = 0;
s->ctrl = 0;
}
static const GraphicHwOps omap_ops = {
.invalidate = omap_invalidate_display,
.gfx_update = omap_update_display,
};
struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem,
hwaddr base,
qemu_irq irq,
struct omap_dma_lcd_channel_s *dma,
omap_clk clk)
{
struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *)
g_malloc0(sizeof(struct omap_lcd_panel_s));
s->irq = irq;
s->dma = dma;
s->sysmem = sysmem;
omap_lcdc_reset(s);
memory_region_init_io(&s->iomem, NULL, &omap_lcdc_ops, s, "omap.lcdc", 0x100);
memory_region_add_subregion(sysmem, base, &s->iomem);
s->con = graphic_console_init(NULL, 0, &omap_ops, s);
return s;
}