blob: be3ff5ed61f53670ad60711e15a02bb9c838318c [file] [log] [blame]
/*
* Copyright (c) 2010-2012, 2015 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "dev/arm/pl111.hh"
#include "base/output.hh"
#include "base/trace.hh"
#include "base/vnc/vncinput.hh"
#include "debug/PL111.hh"
#include "debug/Uart.hh"
#include "dev/arm/amba_device.hh"
#include "dev/arm/base_gic.hh"
#include "mem/packet.hh"
#include "mem/packet_access.hh"
#include "sim/system.hh"
// clang complains about std::set being overloaded with Packet::set if
// we open up the entire namespace std
using std::vector;
// initialize clcd registers
Pl111::Pl111(const Params &p)
: AmbaDmaDevice(p, 0x10000), lcdTiming0(0), lcdTiming1(0), lcdTiming2(0),
lcdTiming3(0), lcdUpbase(0), lcdLpbase(0), lcdControl(0), lcdImsc(0),
lcdRis(0), lcdMis(0),
clcdCrsrCtrl(0), clcdCrsrConfig(0), clcdCrsrPalette0(0),
clcdCrsrPalette1(0), clcdCrsrXY(0), clcdCrsrClip(0), clcdCrsrImsc(0),
clcdCrsrIcr(0), clcdCrsrRis(0), clcdCrsrMis(0),
pixelClock(p.pixel_clock),
converter(PixelConverter::rgba8888_le), fb(LcdMaxWidth, LcdMaxHeight),
vnc(p.vnc), bmp(&fb), pic(NULL),
width(LcdMaxWidth), height(LcdMaxHeight),
bytesPerPixel(4), startTime(0), startAddr(0), maxAddr(0), curAddr(0),
waterMark(0), dmaPendingNum(0),
readEvent([this]{ readFramebuffer(); }, name()),
fillFifoEvent([this]{ fillFifo(); }, name()),
dmaDoneEventAll(maxOutstandingDma, this),
dmaDoneEventFree(maxOutstandingDma),
intEvent([this]{ generateInterrupt(); }, name()),
enableCapture(p.enable_capture)
{
dmaBuffer = new uint8_t[buffer_size];
memset(lcdPalette, 0, sizeof(lcdPalette));
memset(cursorImage, 0, sizeof(cursorImage));
memset(dmaBuffer, 0, buffer_size);
for (int i = 0; i < maxOutstandingDma; ++i)
dmaDoneEventFree[i] = &dmaDoneEventAll[i];
if (vnc)
vnc->setFrameBuffer(&fb);
}
Pl111::~Pl111()
{
delete[] dmaBuffer;
}
// read registers and frame buffer
Tick
Pl111::read(PacketPtr pkt)
{
// use a temporary data since the LCD registers are read/written with
// different size operations
uint32_t data = 0;
assert(pkt->getAddr() >= pioAddr &&
pkt->getAddr() < pioAddr + pioSize);
Addr daddr = pkt->getAddr() - pioAddr;
DPRINTF(PL111, " read register %#x size=%d\n", daddr, pkt->getSize());
switch (daddr) {
case LcdTiming0:
data = lcdTiming0;
break;
case LcdTiming1:
data = lcdTiming1;
break;
case LcdTiming2:
data = lcdTiming2;
break;
case LcdTiming3:
data = lcdTiming3;
break;
case LcdUpBase:
data = lcdUpbase;
break;
case LcdLpBase:
data = lcdLpbase;
break;
case LcdControl:
data = lcdControl;
break;
case LcdImsc:
data = lcdImsc;
break;
case LcdRis:
data = lcdRis;
break;
case LcdMis:
data = lcdMis;
break;
case LcdIcr:
panic("LCD register at offset %#x is Write-Only\n", daddr);
break;
case LcdUpCurr:
data = curAddr;
break;
case LcdLpCurr:
data = curAddr;
break;
case ClcdCrsrCtrl:
data = clcdCrsrCtrl;
break;
case ClcdCrsrConfig:
data = clcdCrsrConfig;
break;
case ClcdCrsrPalette0:
data = clcdCrsrPalette0;
break;
case ClcdCrsrPalette1:
data = clcdCrsrPalette1;
break;
case ClcdCrsrXY:
data = clcdCrsrXY;
break;
case ClcdCrsrClip:
data = clcdCrsrClip;
break;
case ClcdCrsrImsc:
data = clcdCrsrImsc;
break;
case ClcdCrsrIcr:
panic("CLCD register at offset %#x is Write-Only\n", daddr);
break;
case ClcdCrsrRis:
data = clcdCrsrRis;
break;
case ClcdCrsrMis:
data = clcdCrsrMis;
break;
default:
if (readId(pkt, AMBA_ID, pioAddr)) {
// Hack for variable size accesses
data = pkt->getUintX(ByteOrder::little);
break;
} else if (daddr >= CrsrImage && daddr <= 0xBFC) {
// CURSOR IMAGE
int index;
index = (daddr - CrsrImage) >> 2;
data= cursorImage[index];
break;
} else if (daddr >= LcdPalette && daddr <= 0x3FC) {
// LCD Palette
int index;
index = (daddr - LcdPalette) >> 2;
data = lcdPalette[index];
break;
} else {
panic("Tried to read CLCD register at offset %#x that "
"doesn't exist\n", daddr);
break;
}
}
pkt->setUintX(data, ByteOrder::little);
pkt->makeAtomicResponse();
return pioDelay;
}
// write registers and frame buffer
Tick
Pl111::write(PacketPtr pkt)
{
// use a temporary data since the LCD registers are read/written with
// different size operations
//
const uint32_t data = pkt->getUintX(ByteOrder::little);
assert(pkt->getAddr() >= pioAddr &&
pkt->getAddr() < pioAddr + pioSize);
Addr daddr = pkt->getAddr() - pioAddr;
DPRINTF(PL111, " write register %#x value %#x size=%d\n", daddr,
pkt->getLE<uint8_t>(), pkt->getSize());
switch (daddr) {
case LcdTiming0:
lcdTiming0 = data;
// width = 16 * (PPL+1)
width = (lcdTiming0.ppl + 1) << 4;
break;
case LcdTiming1:
lcdTiming1 = data;
// height = LPP + 1
height = (lcdTiming1.lpp) + 1;
break;
case LcdTiming2:
lcdTiming2 = data;
break;
case LcdTiming3:
lcdTiming3 = data;
break;
case LcdUpBase:
lcdUpbase = data;
DPRINTF(PL111, "####### Upper panel base set to: %#x #######\n", lcdUpbase);
break;
case LcdLpBase:
warn_once("LCD dual screen mode not supported\n");
lcdLpbase = data;
DPRINTF(PL111, "###### Lower panel base set to: %#x #######\n", lcdLpbase);
break;
case LcdControl:
int old_lcdpwr;
old_lcdpwr = lcdControl.lcdpwr;
lcdControl = data;
DPRINTF(PL111, "LCD power is:%d\n", lcdControl.lcdpwr);
// LCD power enable
if (lcdControl.lcdpwr && !old_lcdpwr) {
updateVideoParams();
DPRINTF(PL111, " lcd size: height %d width %d\n", height, width);
waterMark = lcdControl.watermark ? 8 : 4;
startDma();
}
break;
case LcdImsc:
lcdImsc = data;
if (lcdImsc.vcomp)
panic("Interrupting on vcomp not supported\n");
lcdMis = lcdImsc & lcdRis;
if (!lcdMis)
interrupt->clear();
break;
case LcdRis:
panic("LCD register at offset %#x is Read-Only\n", daddr);
break;
case LcdMis:
panic("LCD register at offset %#x is Read-Only\n", daddr);
break;
case LcdIcr:
lcdRis = lcdRis & ~data;
lcdMis = lcdImsc & lcdRis;
if (!lcdMis)
interrupt->clear();
break;
case LcdUpCurr:
panic("LCD register at offset %#x is Read-Only\n", daddr);
break;
case LcdLpCurr:
panic("LCD register at offset %#x is Read-Only\n", daddr);
break;
case ClcdCrsrCtrl:
clcdCrsrCtrl = data;
break;
case ClcdCrsrConfig:
clcdCrsrConfig = data;
break;
case ClcdCrsrPalette0:
clcdCrsrPalette0 = data;
break;
case ClcdCrsrPalette1:
clcdCrsrPalette1 = data;
break;
case ClcdCrsrXY:
clcdCrsrXY = data;
break;
case ClcdCrsrClip:
clcdCrsrClip = data;
break;
case ClcdCrsrImsc:
clcdCrsrImsc = data;
break;
case ClcdCrsrIcr:
clcdCrsrIcr = data;
break;
case ClcdCrsrRis:
panic("CLCD register at offset %#x is Read-Only\n", daddr);
break;
case ClcdCrsrMis:
panic("CLCD register at offset %#x is Read-Only\n", daddr);
break;
default:
if (daddr >= CrsrImage && daddr <= 0xBFC) {
// CURSOR IMAGE
int index;
index = (daddr - CrsrImage) >> 2;
cursorImage[index] = data;
break;
} else if (daddr >= LcdPalette && daddr <= 0x3FC) {
// LCD Palette
int index;
index = (daddr - LcdPalette) >> 2;
lcdPalette[index] = data;
break;
} else {
panic("Tried to write PL111 register at offset %#x that "
"doesn't exist\n", daddr);
break;
}
}
pkt->makeAtomicResponse();
return pioDelay;
}
PixelConverter
Pl111::pixelConverter() const
{
unsigned rw, gw, bw;
unsigned offsets[3];
switch (lcdControl.lcdbpp) {
case bpp24:
rw = gw = bw = 8;
offsets[0] = 0;
offsets[1] = 8;
offsets[2] = 16;
break;
case bpp16m565:
rw = 5;
gw = 6;
bw = 5;
offsets[0] = 0;
offsets[1] = 5;
offsets[2] = 11;
break;
default:
panic("Unimplemented video mode\n");
}
if (lcdControl.bgr) {
return PixelConverter(
bytesPerPixel,
offsets[2], offsets[1], offsets[0],
rw, gw, bw,
ByteOrder::little);
} else {
return PixelConverter(
bytesPerPixel,
offsets[0], offsets[1], offsets[2],
rw, gw, bw,
ByteOrder::little);
}
}
void
Pl111::updateVideoParams()
{
if (lcdControl.lcdbpp == bpp24) {
bytesPerPixel = 4;
} else if (lcdControl.lcdbpp == bpp16m565) {
bytesPerPixel = 2;
}
fb.resize(width, height);
converter = pixelConverter();
// Workaround configuration bugs where multiple display
// controllers are attached to the same VNC server by reattaching
// enabled devices. This isn't ideal, but works as long as only
// one display controller is active at a time.
if (lcdControl.lcdpwr && vnc)
vnc->setFrameBuffer(&fb);
}
void
Pl111::startDma()
{
if (dmaPendingNum != 0 || readEvent.scheduled())
return;
readFramebuffer();
}
void
Pl111::readFramebuffer()
{
// initialization for dma read from frame buffer to dma buffer
uint32_t length = height * width;
if (startAddr != lcdUpbase)
startAddr = lcdUpbase;
// Updating base address, interrupt if we're supposed to
lcdRis.baseaddr = 1;
if (!intEvent.scheduled())
schedule(intEvent, clockEdge());
curAddr = 0;
startTime = curTick();
maxAddr = static_cast<Addr>(length * bytesPerPixel);
DPRINTF(PL111, " lcd frame buffer size of %d bytes \n", maxAddr);
fillFifo();
}
void
Pl111::fillFifo()
{
while ((dmaPendingNum < maxOutstandingDma) && (maxAddr >= curAddr + dmaSize )) {
// concurrent dma reads need different dma done events
// due to assertion in scheduling state
++dmaPendingNum;
assert(!dmaDoneEventFree.empty());
DmaDoneEvent *event(dmaDoneEventFree.back());
dmaDoneEventFree.pop_back();
assert(!event->scheduled());
// We use a uncachable request here because the requests from the CPU
// will be uncacheable as well. If we have uncacheable and cacheable
// requests in the memory system for the same address it won't be
// pleased
dmaPort.dmaAction(MemCmd::ReadReq, curAddr + startAddr, dmaSize,
event, curAddr + dmaBuffer,
0, Request::UNCACHEABLE);
curAddr += dmaSize;
}
}
void
Pl111::dmaDone()
{
DPRINTF(PL111, "DMA Done\n");
Tick maxFrameTime = lcdTiming2.cpl * height * pixelClock;
--dmaPendingNum;
if (maxAddr == curAddr && !dmaPendingNum) {
if ((curTick() - startTime) > maxFrameTime) {
warn("CLCD controller buffer underrun, took %d ticks when should"
" have taken %d\n", curTick() - startTime, maxFrameTime);
lcdRis.underflow = 1;
if (!intEvent.scheduled())
schedule(intEvent, clockEdge());
}
assert(!readEvent.scheduled());
fb.copyIn(dmaBuffer, converter);
if (vnc)
vnc->setDirty();
if (enableCapture) {
DPRINTF(PL111, "-- write out frame buffer into bmp\n");
if (!pic)
pic = simout.create(csprintf("%s.framebuffer.bmp", sys->name()),
true);
assert(pic);
pic->stream()->seekp(0);
bmp.write(*pic->stream());
}
// schedule the next read based on when the last frame started
// and the desired fps (i.e. maxFrameTime), we turn the
// argument into a relative number of cycles in the future
if (lcdControl.lcden)
schedule(readEvent, clockEdge(ticksToCycles(startTime -
curTick() +
maxFrameTime)));
}
if (dmaPendingNum > (maxOutstandingDma - waterMark))
return;
if (!fillFifoEvent.scheduled())
schedule(fillFifoEvent, clockEdge());
}
void
Pl111::serialize(CheckpointOut &cp) const
{
DPRINTF(PL111, "Serializing ARM PL111\n");
uint32_t lcdTiming0_serial = lcdTiming0;
SERIALIZE_SCALAR(lcdTiming0_serial);
uint32_t lcdTiming1_serial = lcdTiming1;
SERIALIZE_SCALAR(lcdTiming1_serial);
uint32_t lcdTiming2_serial = lcdTiming2;
SERIALIZE_SCALAR(lcdTiming2_serial);
uint32_t lcdTiming3_serial = lcdTiming3;
SERIALIZE_SCALAR(lcdTiming3_serial);
SERIALIZE_SCALAR(lcdUpbase);
SERIALIZE_SCALAR(lcdLpbase);
uint32_t lcdControl_serial = lcdControl;
SERIALIZE_SCALAR(lcdControl_serial);
uint8_t lcdImsc_serial = lcdImsc;
SERIALIZE_SCALAR(lcdImsc_serial);
uint8_t lcdRis_serial = lcdRis;
SERIALIZE_SCALAR(lcdRis_serial);
uint8_t lcdMis_serial = lcdMis;
SERIALIZE_SCALAR(lcdMis_serial);
SERIALIZE_ARRAY(lcdPalette, LcdPaletteSize);
SERIALIZE_ARRAY(cursorImage, CrsrImageSize);
SERIALIZE_SCALAR(clcdCrsrCtrl);
SERIALIZE_SCALAR(clcdCrsrConfig);
SERIALIZE_SCALAR(clcdCrsrPalette0);
SERIALIZE_SCALAR(clcdCrsrPalette1);
SERIALIZE_SCALAR(clcdCrsrXY);
SERIALIZE_SCALAR(clcdCrsrClip);
uint8_t clcdCrsrImsc_serial = clcdCrsrImsc;
SERIALIZE_SCALAR(clcdCrsrImsc_serial);
uint8_t clcdCrsrIcr_serial = clcdCrsrIcr;
SERIALIZE_SCALAR(clcdCrsrIcr_serial);
uint8_t clcdCrsrRis_serial = clcdCrsrRis;
SERIALIZE_SCALAR(clcdCrsrRis_serial);
uint8_t clcdCrsrMis_serial = clcdCrsrMis;
SERIALIZE_SCALAR(clcdCrsrMis_serial);
SERIALIZE_SCALAR(height);
SERIALIZE_SCALAR(width);
SERIALIZE_SCALAR(bytesPerPixel);
SERIALIZE_ARRAY(dmaBuffer, buffer_size);
SERIALIZE_SCALAR(startTime);
SERIALIZE_SCALAR(startAddr);
SERIALIZE_SCALAR(maxAddr);
SERIALIZE_SCALAR(curAddr);
SERIALIZE_SCALAR(waterMark);
SERIALIZE_SCALAR(dmaPendingNum);
Tick int_event_time = 0;
Tick read_event_time = 0;
Tick fill_fifo_event_time = 0;
if (readEvent.scheduled())
read_event_time = readEvent.when();
if (fillFifoEvent.scheduled())
fill_fifo_event_time = fillFifoEvent.when();
if (intEvent.scheduled())
int_event_time = intEvent.when();
SERIALIZE_SCALAR(read_event_time);
SERIALIZE_SCALAR(fill_fifo_event_time);
SERIALIZE_SCALAR(int_event_time);
vector<Tick> dma_done_event_tick;
dma_done_event_tick.resize(maxOutstandingDma);
for (int x = 0; x < maxOutstandingDma; x++) {
dma_done_event_tick[x] = dmaDoneEventAll[x].scheduled() ?
dmaDoneEventAll[x].when() : 0;
}
SERIALIZE_CONTAINER(dma_done_event_tick);
}
void
Pl111::unserialize(CheckpointIn &cp)
{
DPRINTF(PL111, "Unserializing ARM PL111\n");
uint32_t lcdTiming0_serial;
UNSERIALIZE_SCALAR(lcdTiming0_serial);
lcdTiming0 = lcdTiming0_serial;
uint32_t lcdTiming1_serial;
UNSERIALIZE_SCALAR(lcdTiming1_serial);
lcdTiming1 = lcdTiming1_serial;
uint32_t lcdTiming2_serial;
UNSERIALIZE_SCALAR(lcdTiming2_serial);
lcdTiming2 = lcdTiming2_serial;
uint32_t lcdTiming3_serial;
UNSERIALIZE_SCALAR(lcdTiming3_serial);
lcdTiming3 = lcdTiming3_serial;
UNSERIALIZE_SCALAR(lcdUpbase);
UNSERIALIZE_SCALAR(lcdLpbase);
uint32_t lcdControl_serial;
UNSERIALIZE_SCALAR(lcdControl_serial);
lcdControl = lcdControl_serial;
uint8_t lcdImsc_serial;
UNSERIALIZE_SCALAR(lcdImsc_serial);
lcdImsc = lcdImsc_serial;
uint8_t lcdRis_serial;
UNSERIALIZE_SCALAR(lcdRis_serial);
lcdRis = lcdRis_serial;
uint8_t lcdMis_serial;
UNSERIALIZE_SCALAR(lcdMis_serial);
lcdMis = lcdMis_serial;
UNSERIALIZE_ARRAY(lcdPalette, LcdPaletteSize);
UNSERIALIZE_ARRAY(cursorImage, CrsrImageSize);
UNSERIALIZE_SCALAR(clcdCrsrCtrl);
UNSERIALIZE_SCALAR(clcdCrsrConfig);
UNSERIALIZE_SCALAR(clcdCrsrPalette0);
UNSERIALIZE_SCALAR(clcdCrsrPalette1);
UNSERIALIZE_SCALAR(clcdCrsrXY);
UNSERIALIZE_SCALAR(clcdCrsrClip);
uint8_t clcdCrsrImsc_serial;
UNSERIALIZE_SCALAR(clcdCrsrImsc_serial);
clcdCrsrImsc = clcdCrsrImsc_serial;
uint8_t clcdCrsrIcr_serial;
UNSERIALIZE_SCALAR(clcdCrsrIcr_serial);
clcdCrsrIcr = clcdCrsrIcr_serial;
uint8_t clcdCrsrRis_serial;
UNSERIALIZE_SCALAR(clcdCrsrRis_serial);
clcdCrsrRis = clcdCrsrRis_serial;
uint8_t clcdCrsrMis_serial;
UNSERIALIZE_SCALAR(clcdCrsrMis_serial);
clcdCrsrMis = clcdCrsrMis_serial;
UNSERIALIZE_SCALAR(height);
UNSERIALIZE_SCALAR(width);
UNSERIALIZE_SCALAR(bytesPerPixel);
UNSERIALIZE_ARRAY(dmaBuffer, buffer_size);
UNSERIALIZE_SCALAR(startTime);
UNSERIALIZE_SCALAR(startAddr);
UNSERIALIZE_SCALAR(maxAddr);
UNSERIALIZE_SCALAR(curAddr);
UNSERIALIZE_SCALAR(waterMark);
UNSERIALIZE_SCALAR(dmaPendingNum);
Tick int_event_time = 0;
Tick read_event_time = 0;
Tick fill_fifo_event_time = 0;
UNSERIALIZE_SCALAR(read_event_time);
UNSERIALIZE_SCALAR(fill_fifo_event_time);
UNSERIALIZE_SCALAR(int_event_time);
if (int_event_time)
schedule(intEvent, int_event_time);
if (read_event_time)
schedule(readEvent, read_event_time);
if (fill_fifo_event_time)
schedule(fillFifoEvent, fill_fifo_event_time);
vector<Tick> dma_done_event_tick;
dma_done_event_tick.resize(maxOutstandingDma);
UNSERIALIZE_CONTAINER(dma_done_event_tick);
dmaDoneEventFree.clear();
for (int x = 0; x < maxOutstandingDma; x++) {
if (dma_done_event_tick[x])
schedule(dmaDoneEventAll[x], dma_done_event_tick[x]);
else
dmaDoneEventFree.push_back(&dmaDoneEventAll[x]);
}
assert(maxOutstandingDma - dmaDoneEventFree.size() == dmaPendingNum);
if (lcdControl.lcdpwr) {
updateVideoParams();
fb.copyIn(dmaBuffer, converter);
if (vnc)
vnc->setDirty();
}
}
void
Pl111::generateInterrupt()
{
DPRINTF(PL111, "Generate Interrupt: lcdImsc=0x%x lcdRis=0x%x lcdMis=0x%x\n",
(uint32_t)lcdImsc, (uint32_t)lcdRis, (uint32_t)lcdMis);
lcdMis = lcdImsc & lcdRis;
if (lcdMis.underflow || lcdMis.baseaddr || lcdMis.vcomp || lcdMis.ahbmaster) {
interrupt->raise();
DPRINTF(PL111, " -- Generated\n");
}
}
AddrRangeList
Pl111::getAddrRanges() const
{
AddrRangeList ranges;
ranges.push_back(RangeSize(pioAddr, pioSize));
return ranges;
}