/*
 * Copyright (c) 2014-2016 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: Andreas Sandberg
 */

#include "dev/arm/gpu_nomali.hh"

#include "debug/NoMali.hh"
#include "dev/arm/base_gic.hh"
#include "dev/arm/realview.hh"
#include "enums/MemoryMode.hh"
#include "mem/packet_access.hh"
#include "nomali/lib/mali_midg_regmap.h"
#include "params/CustomNoMaliGpu.hh"
#include "params/NoMaliGpu.hh"

static const std::map<Enums::NoMaliGpuType, nomali_gpu_type_t> gpuTypeMap{
    { Enums::T60x, NOMALI_GPU_T60X },
    { Enums::T62x, NOMALI_GPU_T62X },
    { Enums::T760, NOMALI_GPU_T760 },
};

NoMaliGpu::NoMaliGpu(const NoMaliGpuParams *p)
    : PioDevice(p),
      pioAddr(p->pio_addr),
      platform(p->platform),
      interruptMap{
          { NOMALI_INT_GPU, p->int_gpu },
          { NOMALI_INT_JOB, p->int_job },
          { NOMALI_INT_MMU, p->int_mmu },
      }
{
    if (nomali_api_version() != NOMALI_API_VERSION)
        panic("NoMali library API mismatch!\n");

    /* Setup the GPU configuration based on our param struct */
    nomali_config_t cfg;
    memset(&cfg, 0, sizeof(cfg));

    const auto it_gpu(gpuTypeMap.find(p->gpu_type));
    if (it_gpu == gpuTypeMap.end()) {
        fatal("Unrecognized GPU type: %s (%i)\n",
              Enums::NoMaliGpuTypeStrings[p->gpu_type], p->gpu_type);
    }
    cfg.type = it_gpu->second;

    cfg.ver_maj = p->ver_maj;
    cfg.ver_min = p->ver_min;
    cfg.ver_status = p->ver_status;

    panicOnErr(
        nomali_create(&nomali, &cfg),
        "Failed to instantiate NoMali");


    /* Setup an interrupt callback */
    nomali_callback_t cbk_int;
    cbk_int.type = NOMALI_CALLBACK_INT;
    cbk_int.usr = (void *)this;
    cbk_int.func.interrupt = NoMaliGpu::_interrupt;
    setCallback(cbk_int);

    /* Setup a reset callback */
    nomali_callback_t cbk_rst;
    cbk_rst.type = NOMALI_CALLBACK_RESET;
    cbk_rst.usr = (void *)this;
    cbk_rst.func.reset = NoMaliGpu::_reset;
    setCallback(cbk_rst);

    panicOnErr(
        nomali_get_info(nomali, &nomaliInfo),
        "Failed to get NoMali information struct");
}

NoMaliGpu::~NoMaliGpu()
{
    nomali_destroy(nomali);
}


void
NoMaliGpu::init()
{
    PioDevice::init();

    /* Reset the GPU here since the reset callback won't have been
     * installed when the GPU was reset at instantiation time.
     */
    reset();
}

void
NoMaliGpu::serialize(CheckpointOut &cp) const
{
    std::vector<uint32_t> regs(nomaliInfo.reg_size >> 2);

    for (int i = 0; i < nomaliInfo.reg_size; i += 4)
        regs[i >> 2] = readRegRaw(i);

    SERIALIZE_CONTAINER(regs);
}

void
NoMaliGpu::unserialize(CheckpointIn &cp)
{
    std::vector<uint32_t> regs(nomaliInfo.reg_size >> 2);

    UNSERIALIZE_CONTAINER(regs);

    for (int i = 0; i < nomaliInfo.reg_size; i += 4)
        writeRegRaw(i, regs[i >> 2]);
}

Tick
NoMaliGpu::read(PacketPtr pkt)
{
    assert(pkt->getAddr() >= pioAddr);
    const Addr addr(pkt->getAddr() - pioAddr);
    const unsigned size(pkt->getSize());

    if (addr + size >= nomaliInfo.reg_size)
        panic("GPU register '0x%x' out of range!\n", addr);

    if (size != 4)
        panic("Unexpected GPU register read size: %i\n", size);
    else if (addr & 0x3)
        panic("Unaligned GPU read: %i\n", size);

    pkt->set<uint32_t>(readReg(addr));
    pkt->makeResponse();

    return 0;
}

Tick
NoMaliGpu::write(PacketPtr pkt)
{
    assert(pkt->getAddr() >= pioAddr);
    const Addr addr(pkt->getAddr() - pioAddr);
    const unsigned size(pkt->getSize());

    if (addr + size >= nomaliInfo.reg_size)
        panic("GPU register '0x%x' out of range!\n", addr);

    if (size != 4)
        panic("Unexpected GPU register write size: %i\n", size);
    else if (addr & 0x3)
        panic("Unaligned GPU write: %i\n", size);

    writeReg(addr, pkt->get<uint32_t>());
    pkt->makeAtomicResponse();

    return 0;
}

AddrRangeList
NoMaliGpu::getAddrRanges() const
{
    return AddrRangeList({ RangeSize(pioAddr, nomaliInfo.reg_size) });
}

void
NoMaliGpu::reset()
{
    DPRINTF(NoMali, "reset()\n");

    panicOnErr(
        nomali_reset(nomali),
        "Failed to reset GPU");
}

uint32_t
NoMaliGpu::readReg(nomali_addr_t reg)
{
    uint32_t value;

    panicOnErr(
        nomali_reg_read(nomali, &value, reg),
        "GPU register read failed");

    DPRINTF(NoMali, "readReg(0x%x): 0x%x\n",
            reg, value);

    return value;
}


void
NoMaliGpu::writeReg(nomali_addr_t reg, uint32_t value)
{
    DPRINTF(NoMali, "writeReg(0x%x, 0x%x)\n",
            reg, value);

    panicOnErr(
        nomali_reg_write(nomali, reg, value),
        "GPU register write failed");
}

uint32_t
NoMaliGpu::readRegRaw(nomali_addr_t reg) const
{
    uint32_t value;

    panicOnErr(
        nomali_reg_read_raw(nomali, &value, reg),
        "GPU raw register read failed");

    return value;
}


void
NoMaliGpu::writeRegRaw(nomali_addr_t reg, uint32_t value)
{
    panicOnErr(
        nomali_reg_write_raw(nomali, reg, value),
        "GPU raw register write failed");
}

bool
NoMaliGpu::intState(nomali_int_t intno)
{
    int state = 0;
    panicOnErr(
        nomali_int_state(nomali, &state, intno),
        "Failed to get interrupt state");

    return !!state;
}

void
NoMaliGpu::gpuPanic(nomali_error_t err, const char *msg)
{
    panic("%s: %s\n", msg, nomali_errstr(err));
}


void
NoMaliGpu::onInterrupt(nomali_int_t intno, bool set)
{
    const auto it_int(interruptMap.find(intno));
    if (it_int == interruptMap.end())
        panic("Unhandled interrupt from NoMali: %i\n", intno);

    DPRINTF(NoMali, "Interrupt %i->%i: %i\n",
            intno, it_int->second, set);

    assert(platform);
    assert(platform->gic);

    if (set)
        platform->gic->sendInt(it_int->second);
    else
        platform->gic->clearInt(it_int->second);
}

void
NoMaliGpu::onReset()
{
    DPRINTF(NoMali, "Reset\n");
}

void
NoMaliGpu::setCallback(const nomali_callback_t &callback)
{
    DPRINTF(NoMali, "Registering callback %i\n",
            callback.type);

    panicOnErr(
        nomali_set_callback(nomali, &callback),
        "Failed to register callback");
}

void
NoMaliGpu::_interrupt(nomali_handle_t h, void *usr,
                      nomali_int_t intno, int set)
{
    NoMaliGpu *_this(static_cast<NoMaliGpu *>(usr));

    _this->onInterrupt(intno, !!set);
}

void
NoMaliGpu::_reset(nomali_handle_t h, void *usr)
{
    NoMaliGpu *_this(static_cast<NoMaliGpu *>(usr));

    _this->onReset();
}


CustomNoMaliGpu::CustomNoMaliGpu(const CustomNoMaliGpuParams *p)
    : NoMaliGpu(p),
      idRegs{
        { GPU_CONTROL_REG(GPU_ID), p->gpu_id },
        { GPU_CONTROL_REG(L2_FEATURES), p->l2_features },
        { GPU_CONTROL_REG(TILER_FEATURES), p->tiler_features },
        { GPU_CONTROL_REG(MEM_FEATURES), p->mem_features },
        { GPU_CONTROL_REG(MMU_FEATURES), p->mmu_features },
        { GPU_CONTROL_REG(AS_PRESENT), p->as_present },
        { GPU_CONTROL_REG(JS_PRESENT), p->js_present },

        { GPU_CONTROL_REG(THREAD_MAX_THREADS), p->thread_max_threads },
        { GPU_CONTROL_REG(THREAD_MAX_WORKGROUP_SIZE),
          p->thread_max_workgroup_size },
        { GPU_CONTROL_REG(THREAD_MAX_BARRIER_SIZE),
          p->thread_max_barrier_size },
        { GPU_CONTROL_REG(THREAD_FEATURES), p->thread_features },

        { GPU_CONTROL_REG(SHADER_PRESENT_LO), bits(p->shader_present, 31, 0) },
        { GPU_CONTROL_REG(SHADER_PRESENT_HI), bits(p->shader_present, 63, 32) },
        { GPU_CONTROL_REG(TILER_PRESENT_LO), bits(p->tiler_present, 31, 0) },
        { GPU_CONTROL_REG(TILER_PRESENT_HI), bits(p->tiler_present, 63, 32) },
        { GPU_CONTROL_REG(L2_PRESENT_LO), bits(p->l2_present, 31, 0) },
        { GPU_CONTROL_REG(L2_PRESENT_HI), bits(p->l2_present, 63, 32) },
      }
{
    fatal_if(p->texture_features.size() > 3,
             "Too many texture feature registers specified (%i)\n",
             p->texture_features.size());

    fatal_if(p->js_features.size() > 16,
             "Too many job slot feature registers specified (%i)\n",
             p->js_features.size());

    for (int i = 0; i < p->texture_features.size(); i++)
        idRegs[TEXTURE_FEATURES_REG(i)] = p->texture_features[i];

    for (int i = 0; i < p->js_features.size(); i++)
        idRegs[JS_FEATURES_REG(i)] = p->js_features[i];
}

CustomNoMaliGpu::~CustomNoMaliGpu()
{
}

void
CustomNoMaliGpu::onReset()
{
    NoMaliGpu::onReset();

    for (const auto &reg : idRegs)
        writeRegRaw(reg.first, reg.second);
}



NoMaliGpu *
NoMaliGpuParams::create()
{
    return new NoMaliGpu(this);
}

CustomNoMaliGpu *
CustomNoMaliGpuParams::create()
{
    return new CustomNoMaliGpu(this);
}
