/*
 * Copyright (c) 2014-2015 ARM Limited
 * All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Authors: Andreas Sandberg
 */

#include "gpucontrol.hh"
#include "gpu.hh"
#include "regutils.hh"

namespace NoMali {

typedef void (GPUControl::*GpuCmdHandler)(uint32_t);

const std::vector<GpuCmdHandler> GPUControl::cmds {
    &GPUControl::cmdNop,                     // GPU_COMMAND_NOP
    &GPUControl::cmdSoftReset,               // GPU_COMMAND_SOFT_RESET
    &GPUControl::cmdHardReset,               // GPU_COMMAND_HARD_RESET
    &GPUControl::cmdPerfCntClear,            // GPU_COMMAND_PRFCNT_CLEAR
    &GPUControl::cmdPerfCntSample,           // GPU_COMMAND_PRFCNT_SAMPLE
    &GPUControl::cmdCycleCountStart,         // GPU_COMMAND_CYCLE_COUNT_START
    &GPUControl::cmdCycleCountStop,          // GPU_COMMAND_COUNT_STOP
    &GPUControl::cmdCleanCaches,             // GPU_COMMAND_CLEAN_CACHES
    &GPUControl::cmdCleanInvCaches,          // GPU_COMMAND_CLEAN_INV_CACHES
};

GPUControl::GPUControl(GPU &_gpu)
    : GPUBlockInt(_gpu,
                  RegAddr(GPU_IRQ_RAWSTAT),
                  RegAddr(GPU_IRQ_CLEAR),
                  RegAddr(GPU_IRQ_MASK),
                  RegAddr(GPU_IRQ_STATUS))
{
}

GPUControl::~GPUControl()
{
}

void
GPUControl::reset()
{
    GPUBlock::reset();
}

void
GPUControl::writeReg(RegAddr addr, uint32_t value)
{
    switch (addr.value) {
      case GPU_IRQ_RAWSTAT:
      case GPU_IRQ_CLEAR:
      case GPU_IRQ_MASK:
      case GPU_IRQ_STATUS:
        GPUBlockInt::writeReg(addr, value);
        break;

      case GPU_COMMAND:
        gpuCommand(value);
        break;

      case SHADER_PWRON_LO:
      case SHADER_PWRON_HI:
      case TILER_PWRON_LO:
      case TILER_PWRON_HI:
      case L2_PWRON_LO:
      case L2_PWRON_HI:
      case L3_PWRON_LO:
      case L3_PWRON_HI: {
          const RegAddr ready_reg(SHADER_READY_LO +
                                  (addr.value - SHADER_PWRON_LO));
          const RegAddr present_reg(SHADER_PRESENT_LO +
                                    (addr.value - SHADER_PWRON_LO));

          regs[ready_reg] |= value & regs[present_reg];
          raiseInterrupt(POWER_CHANGED_SINGLE | POWER_CHANGED_ALL);
      } break;

      case SHADER_PWROFF_LO:
      case SHADER_PWROFF_HI:
      case TILER_PWROFF_LO:
      case TILER_PWROFF_HI:
      case L2_PWROFF_LO:
      case L2_PWROFF_HI:
      case L3_PWROFF_LO:
      case L3_PWROFF_HI: {
          const RegAddr ready_reg(SHADER_READY_LO +
                                  (addr.value - SHADER_PWROFF_LO));

          regs[ready_reg] &= ~value;
          raiseInterrupt(POWER_CHANGED_SINGLE | POWER_CHANGED_ALL);
      } break;

      default:
        // Ignore writes by default
        break;
    };
}

void
GPUControl::onInterrupt(int set)
{
    gpu.intGPU(set);
}

void
GPUControl::gpuCommand(uint32_t cmd)
{
    if (cmd < cmds.size())
        (this->*cmds[cmd])(cmd);
}

void
GPUControl::cmdNop(uint32_t cmd)
{
}

void
GPUControl::cmdHardReset(uint32_t cmd)
{
    gpu.reset();
    raiseInterrupt(RESET_COMPLETED);
}

void
GPUControl::cmdSoftReset(uint32_t cmd)
{
    gpu.reset();
    raiseInterrupt(RESET_COMPLETED);
}

void
GPUControl::cmdPerfCntClear(uint32_t cmd)
{
}

void
GPUControl::cmdPerfCntSample(uint32_t cmd)
{
    raiseInterrupt(PRFCNT_SAMPLE_COMPLETED);
}

void
GPUControl::cmdCycleCountStart(uint32_t cmd)
{
}

void
GPUControl::cmdCycleCountStop(uint32_t cmd)
{
}

void
GPUControl::cmdCleanCaches(uint32_t cmd)
{
    raiseInterrupt(CLEAN_CACHES_COMPLETED);
}

void
GPUControl::cmdCleanInvCaches(uint32_t cmd)
{
    raiseInterrupt(CLEAN_CACHES_COMPLETED);
}

}