blob: 113c1c358836db4ad8c9813549d86887a3bde20a [file] [log] [blame]
/*
* Copyright (c) 2010-2013, 2015, 2017 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.
*
* Authors: Chris Emmons
* Andreas Sandberg
*/
#include "dev/arm/hdlcd.hh"
#include "base/output.hh"
#include "base/trace.hh"
#include "base/vnc/vncinput.hh"
#include "debug/Checkpoint.hh"
#include "debug/HDLcd.hh"
#include "dev/arm/amba_device.hh"
#include "dev/arm/base_gic.hh"
#include "enums/ImageFormat.hh"
#include "mem/packet.hh"
#include "mem/packet_access.hh"
#include "params/HDLcd.hh"
#include "sim/system.hh"
using std::vector;
// initialize hdlcd registers
HDLcd::HDLcd(const HDLcdParams *p)
: AmbaDmaDevice(p, 0xFFFF),
// Parameters
vnc(p->vnc),
workaroundSwapRB(p->workaround_swap_rb),
workaroundDmaLineCount(p->workaround_dma_line_count),
addrRanges{RangeSize(pioAddr, pioSize)},
enableCapture(p->enable_capture),
pixelBufferSize(p->pixel_buffer_size),
virtRefreshRate(p->virt_refresh_rate),
// Registers
version(VERSION_RESETV),
int_rawstat(0), int_mask(0),
fb_base(0), fb_line_length(0), fb_line_count(0), fb_line_pitch(0),
bus_options(BUS_OPTIONS_RESETV),
v_sync(0), v_back_porch(0), v_data(0), v_front_porch(0),
h_sync(0), h_back_porch(0), h_data(0), h_front_porch(0),
polarities(0),
command(0),
pixel_format(0),
red_select(0), green_select(0), blue_select(0),
virtRefreshEvent([this]{ virtRefresh(); }, name()),
// Other
imgFormat(p->frame_format), pic(NULL), conv(PixelConverter::rgba8888_le),
pixelPump(*this, *p->pxl_clk, p->pixel_chunk)
{
if (vnc)
vnc->setFrameBuffer(&pixelPump.fb);
imgWriter = createImgWriter(imgFormat, &pixelPump.fb);
}
HDLcd::~HDLcd()
{
}
void
HDLcd::regStats()
{
AmbaDmaDevice::regStats();
using namespace Stats;
stats.underruns
.name(name() + ".underruns")
.desc("number of buffer underruns")
.flags(nozero)
;
}
void
HDLcd::serialize(CheckpointOut &cp) const
{
DPRINTF(Checkpoint, "Serializing ARM HDLCD\n");
SERIALIZE_SCALAR(int_rawstat);
SERIALIZE_SCALAR(int_mask);
SERIALIZE_SCALAR(fb_base);
SERIALIZE_SCALAR(fb_line_length);
SERIALIZE_SCALAR(fb_line_count);
SERIALIZE_SCALAR(fb_line_pitch);
SERIALIZE_SCALAR(bus_options);
SERIALIZE_SCALAR(v_sync);
SERIALIZE_SCALAR(v_back_porch);
SERIALIZE_SCALAR(v_data);
SERIALIZE_SCALAR(v_front_porch);
SERIALIZE_SCALAR(h_sync);
SERIALIZE_SCALAR(h_back_porch);
SERIALIZE_SCALAR(h_data);
SERIALIZE_SCALAR(h_front_porch);
SERIALIZE_SCALAR(polarities);
SERIALIZE_SCALAR(command);
SERIALIZE_SCALAR(pixel_format);
SERIALIZE_SCALAR(red_select);
SERIALIZE_SCALAR(green_select);
SERIALIZE_SCALAR(blue_select);
SERIALIZE_OBJ(pixelPump);
if (enabled())
dmaEngine->serializeSection(cp, "dmaEngine");
}
void
HDLcd::unserialize(CheckpointIn &cp)
{
DPRINTF(Checkpoint, "Unserializing ARM HDLCD\n");
UNSERIALIZE_SCALAR(int_rawstat);
UNSERIALIZE_SCALAR(int_mask);
UNSERIALIZE_SCALAR(fb_base);
UNSERIALIZE_SCALAR(fb_line_length);
UNSERIALIZE_SCALAR(fb_line_count);
UNSERIALIZE_SCALAR(fb_line_pitch);
UNSERIALIZE_SCALAR(bus_options);
UNSERIALIZE_SCALAR(v_sync);
UNSERIALIZE_SCALAR(v_back_porch);
UNSERIALIZE_SCALAR(v_data);
UNSERIALIZE_SCALAR(v_front_porch);
UNSERIALIZE_SCALAR(h_sync);
UNSERIALIZE_SCALAR(h_back_porch);
UNSERIALIZE_SCALAR(h_data);
UNSERIALIZE_SCALAR(h_front_porch);
UNSERIALIZE_SCALAR(polarities);
UNSERIALIZE_SCALAR(command);
UNSERIALIZE_SCALAR(pixel_format);
UNSERIALIZE_SCALAR(red_select);
UNSERIALIZE_SCALAR(green_select);
UNSERIALIZE_SCALAR(blue_select);
{
// Try to unserialize the pixel pump. It might not exist if
// we're unserializing an old checkpoint.
ScopedCheckpointSection sec(cp, "pixelPump");
if (cp.sectionExists(Serializable::currentSection()))
pixelPump.unserialize(cp);
}
if (enabled()) {
// Create the DMA engine and read its state from the
// checkpoint. We don't need to worry about the pixel pump as
// it is a proper SimObject.
createDmaEngine();
dmaEngine->unserializeSection(cp, "dmaEngine");
conv = pixelConverter();
}
}
void
HDLcd::drainResume()
{
AmbaDmaDevice::drainResume();
if (enabled()) {
if (sys->bypassCaches()) {
// We restart the HDLCD if we are in KVM mode. This
// ensures that we always use the fast refresh logic if we
// resume in KVM mode.
cmdDisable();
cmdEnable();
} else if (!pixelPump.active()) {
// We restored from an old checkpoint without a pixel
// pump, start an new refresh. This typically happens when
// restoring from old checkpoints.
cmdEnable();
}
}
// We restored from a checkpoint and need to update the VNC server
if (pixelPump.active() && vnc)
vnc->setDirty();
}
void
HDLcd::virtRefresh()
{
pixelPump.renderFrame();
schedule(virtRefreshEvent, (curTick() + virtRefreshRate));
}
// read registers and frame buffer
Tick
HDLcd::read(PacketPtr pkt)
{
assert(pkt->getAddr() >= pioAddr &&
pkt->getAddr() < pioAddr + pioSize);
const Addr daddr(pkt->getAddr() - pioAddr);
panic_if(pkt->getSize() != 4,
"Unhandled read size (address: 0x.4x, size: %u)",
daddr, pkt->getSize());
const uint32_t data(readReg(daddr));
DPRINTF(HDLcd, "read register 0x%04x: 0x%x\n", daddr, data);
pkt->setLE<uint32_t>(data);
pkt->makeAtomicResponse();
return pioDelay;
}
// write registers and frame buffer
Tick
HDLcd::write(PacketPtr pkt)
{
assert(pkt->getAddr() >= pioAddr &&
pkt->getAddr() < pioAddr + pioSize);
const Addr daddr(pkt->getAddr() - pioAddr);
panic_if(pkt->getSize() != 4,
"Unhandled read size (address: 0x.4x, size: %u)",
daddr, pkt->getSize());
const uint32_t data(pkt->getLE<uint32_t>());
DPRINTF(HDLcd, "write register 0x%04x: 0x%x\n", daddr, data);
writeReg(daddr, data);
pkt->makeAtomicResponse();
return pioDelay;
}
uint32_t
HDLcd::readReg(Addr offset)
{
switch (offset) {
case Version: return version;
case Int_RawStat: return int_rawstat;
case Int_Clear:
panic("HDLCD INT_CLEAR register is Write-Only\n");
case Int_Mask: return int_mask;
case Int_Status: return intStatus();
case Fb_Base: return fb_base;
case Fb_Line_Length: return fb_line_length;
case Fb_Line_Count: return fb_line_count;
case Fb_Line_Pitch: return fb_line_pitch;
case Bus_Options: return bus_options;
case V_Sync: return v_sync;
case V_Back_Porch: return v_back_porch;
case V_Data: return v_data;
case V_Front_Porch: return v_front_porch;
case H_Sync: return h_sync;
case H_Back_Porch: return h_back_porch;
case H_Data: return h_data;
case H_Front_Porch: return h_front_porch;
case Polarities: return polarities;
case Command: return command;
case Pixel_Format: return pixel_format;
case Red_Select: return red_select;
case Green_Select: return green_select;
case Blue_Select: return blue_select;
default:
panic("Tried to read HDLCD register that doesn't exist\n", offset);
}
}
void
HDLcd::writeReg(Addr offset, uint32_t value)
{
switch (offset) {
case Version:
panic("HDLCD VERSION register is read-Only\n");
case Int_RawStat:
intRaise(value);
return;
case Int_Clear:
intClear(value);
return;
case Int_Mask:
intMask(value);
return;
case Int_Status:
panic("HDLCD INT_STATUS register is read-Only\n");
break;
case Fb_Base:
fb_base = value;
return;
case Fb_Line_Length:
fb_line_length = value;
return;
case Fb_Line_Count:
fb_line_count = value;
return;
case Fb_Line_Pitch:
fb_line_pitch = value;
return;
case Bus_Options: {
const BusOptsReg old_bus_options(bus_options);
bus_options = value;
if (bus_options.max_outstanding != old_bus_options.max_outstanding) {
DPRINTF(HDLcd,
"Changing HDLcd outstanding DMA transactions: %d -> %d\n",
old_bus_options.max_outstanding,
bus_options.max_outstanding);
}
if (bus_options.burst_len != old_bus_options.burst_len) {
DPRINTF(HDLcd,
"Changing HDLcd DMA burst flags: 0x%x -> 0x%x\n",
old_bus_options.burst_len, bus_options.burst_len);
}
} return;
case V_Sync:
v_sync = value;
return;
case V_Back_Porch:
v_back_porch = value;
return;
case V_Data:
v_data = value;
return;
case V_Front_Porch:
v_front_porch = value;
return;
case H_Sync:
h_sync = value;
return;
case H_Back_Porch:
h_back_porch = value;
return;
case H_Data:
h_data = value;
return;
case H_Front_Porch:
h_front_porch = value;
return;
case Polarities:
polarities = value;
return;
case Command: {
const CommandReg new_command(value);
if (new_command.enable != command.enable) {
DPRINTF(HDLcd, "HDLCD switched %s\n",
new_command.enable ? "on" : "off");
if (new_command.enable) {
cmdEnable();
} else {
cmdDisable();
}
}
command = new_command;
} return;
case Pixel_Format:
pixel_format = value;
return;
case Red_Select:
red_select = value;
return;
case Green_Select:
green_select = value;
return;
case Blue_Select:
blue_select = value;
return;
default:
panic("Tried to write HDLCD register that doesn't exist\n", offset);
return;
}
}
PixelConverter
HDLcd::pixelConverter() const
{
ByteOrder byte_order(
pixel_format.big_endian ? BigEndianByteOrder : LittleEndianByteOrder);
/* Some Linux kernels have a broken driver that swaps the red and
* blue color select registers. */
if (!workaroundSwapRB) {
return PixelConverter(
pixel_format.bytes_per_pixel + 1,
red_select.offset, green_select.offset, blue_select.offset,
red_select.size, green_select.size, blue_select.size,
byte_order);
} else {
return PixelConverter(
pixel_format.bytes_per_pixel + 1,
blue_select.offset, green_select.offset, red_select.offset,
blue_select.size, green_select.size, red_select.size,
byte_order);
}
}
DisplayTimings
HDLcd::displayTimings() const
{
return DisplayTimings(
h_data.val + 1, v_data.val + 1,
h_back_porch.val + 1, h_sync.val + 1, h_front_porch.val + 1,
v_back_porch.val + 1, v_sync.val + 1, v_front_porch.val + 1);
}
void
HDLcd::createDmaEngine()
{
if (bus_options.max_outstanding == 0) {
warn("Maximum number of outstanding DMA transfers set to 0.");
return;
}
const uint32_t dma_burst_flags(bus_options.burst_len);
const uint32_t dma_burst_len(
dma_burst_flags ?
(1UL << (findMsbSet(dma_burst_flags) - 1)) :
MAX_BURST_LEN);
// Some drivers seem to set the DMA line count incorrectly. This
// could either be a driver bug or a specification bug. Unlike for
// timings, the specification does not require 1 to be added to
// the DMA engine's line count.
const uint32_t dma_lines(
fb_line_count + (workaroundDmaLineCount ? 1 : 0));
dmaEngine.reset(new DmaEngine(
*this, pixelBufferSize,
AXI_PORT_WIDTH * dma_burst_len,
bus_options.max_outstanding,
fb_line_length, fb_line_pitch, dma_lines));
}
void
HDLcd::cmdEnable()
{
createDmaEngine();
conv = pixelConverter();
// Update timing parameter before rendering frames
pixelPump.updateTimings(displayTimings());
if (sys->bypassCaches()) {
schedule(virtRefreshEvent, clockEdge());
} else {
pixelPump.start();
}
}
void
HDLcd::cmdDisable()
{
pixelPump.stop();
// Disable the virtual refresh event
if (virtRefreshEvent.scheduled()) {
assert(sys->bypassCaches());
deschedule(virtRefreshEvent);
}
dmaEngine->abortFrame();
}
bool
HDLcd::pxlNext(Pixel &p)
{
uint8_t pixel_data[MAX_PIXEL_SIZE];
assert(conv.length <= sizeof(pixel_data));
if (dmaEngine->tryGet(pixel_data, conv.length)) {
p = conv.toPixel(pixel_data);
return true;
} else {
return false;
}
}
void
HDLcd::pxlVSyncBegin()
{
DPRINTF(HDLcd, "Raising VSYNC interrupt.\n");
intRaise(INT_VSYNC);
}
void
HDLcd::pxlVSyncEnd()
{
DPRINTF(HDLcd, "End of VSYNC, starting DMA engine\n");
dmaEngine->startFrame(fb_base);
}
void
HDLcd::pxlUnderrun()
{
DPRINTF(HDLcd, "Buffer underrun, stopping DMA fill.\n");
++stats.underruns;
intRaise(INT_UNDERRUN);
dmaEngine->abortFrame();
}
void
HDLcd::pxlFrameDone()
{
DPRINTF(HDLcd, "Reached end of last visible line.\n");
if (dmaEngine->size()) {
warn("HDLCD %u bytes still in FIFO after frame: Ensure that DMA "
"and PixelPump configuration is consistent\n",
dmaEngine->size());
dmaEngine->dumpSettings();
pixelPump.dumpSettings();
}
if (vnc)
vnc->setDirty();
if (enableCapture) {
if (!pic) {
pic = simout.create(
csprintf("%s.framebuffer.%s",
sys->name(), imgWriter->getImgExtension()),
true);
}
assert(pic);
pic->stream()->seekp(0);
imgWriter->write(*pic->stream());
}
}
void
HDLcd::setInterrupts(uint32_t ints, uint32_t mask)
{
const bool old_ints(intStatus());
int_mask = mask;
int_rawstat = ints;
if (!old_ints && intStatus()) {
gic->sendInt(intNum);
} else if (old_ints && !intStatus()) {
gic->clearInt(intNum);
}
}
HDLcd::DmaEngine::DmaEngine(HDLcd &_parent, size_t size,
unsigned request_size, unsigned max_pending,
size_t line_size, ssize_t line_pitch, unsigned num_lines)
: DmaReadFifo(
_parent.dmaPort, size, request_size, max_pending,
Request::UNCACHEABLE),
parent(_parent),
lineSize(line_size), linePitch(line_pitch), numLines(num_lines),
nextLineAddr(0)
{
}
void
HDLcd::DmaEngine::serialize(CheckpointOut &cp) const
{
DmaReadFifo::serialize(cp);
SERIALIZE_SCALAR(nextLineAddr);
SERIALIZE_SCALAR(frameEnd);
}
void
HDLcd::DmaEngine::unserialize(CheckpointIn &cp)
{
DmaReadFifo::unserialize(cp);
UNSERIALIZE_SCALAR(nextLineAddr);
UNSERIALIZE_SCALAR(frameEnd);
}
void
HDLcd::DmaEngine::startFrame(Addr fb_base)
{
nextLineAddr = fb_base;
frameEnd = fb_base + numLines * linePitch;
startFill(nextLineAddr, lineSize);
}
void
HDLcd::DmaEngine::abortFrame()
{
nextLineAddr = frameEnd;
stopFill();
flush();
}
void
HDLcd::DmaEngine::dumpSettings()
{
inform("DMA line size: %u bytes", lineSize);
inform("DMA line pitch: %i bytes", linePitch);
inform("DMA num lines: %u", numLines);
}
void
HDLcd::DmaEngine::onEndOfBlock()
{
if (nextLineAddr == frameEnd)
// We're done with this frame. Ignore calls to this method
// until the next frame has been started.
return;
nextLineAddr += linePitch;
if (nextLineAddr != frameEnd)
startFill(nextLineAddr, lineSize);
}
void
HDLcd::DmaEngine::onIdle()
{
parent.intRaise(INT_DMA_END);
}
void
HDLcd::PixelPump::dumpSettings()
{
const DisplayTimings &t(timings());
inform("PixelPump width: %u", t.width);
inform("PixelPump height: %u", t.height);
inform("PixelPump horizontal back porch: %u", t.hBackPorch);
inform("PixelPump horizontal fron porch: %u", t.hFrontPorch);
inform("PixelPump horizontal fron porch: %u", t.hSync);
inform("PixelPump vertical back porch: %u", t.vBackPorch);
inform("PixelPump vertical fron porch: %u", t.vFrontPorch);
inform("PixelPump vertical fron porch: %u", t.vSync);
}
HDLcd *
HDLcdParams::create()
{
return new HDLcd(this);
}