/*
 * Copyright (c) 2014-2016 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 "libnomali/nomali.h"

#include <cstring>

#include "mali_t6xx.hh"
#include "mali_t7xx.hh"

#define EXPORT __attribute__ ((visibility ("default")))

static const char *errstrs[] = {
    "No error",
    "Unknown error",
    "Memory allocation failed",
    "Invalid model handle",
    "Invalid parameter",
};

static_assert(sizeof(errstrs) / sizeof(*errstrs) == NOMALI_E_NUM_ERRORS,
              "NoMali API error descriptions out of sync!");

class NoMaliApi
{
  public:
    NoMaliApi();
    ~NoMaliApi();

    void setGpu(NoMali::GPU *gpu) { _gpu = gpu; }

  public:
    nomali_error_t setCallback(const nomali_callback_t *callback);

    nomali_error_t getInfo(nomali_info_t *info);

    nomali_error_t reset();
    nomali_error_t regRead(uint32_t *value, nomali_addr_t addr);
    nomali_error_t regWrite(nomali_addr_t addr, uint32_t value);
    nomali_error_t regReadRaw(uint32_t *value, nomali_addr_t addr);
    nomali_error_t regWriteRaw(nomali_addr_t addr, uint32_t value);
    nomali_error_t intState(int *state, nomali_int_t intno) const;

  public:
    void callbackInt(nomali_int_t intno, int set);
    void callbackReset();

  private:
    nomali_callback_t callbacks[NOMALI_CALLBACK_NUM_CALLBACKS];

    NoMali::GPU *_gpu;
};

template<class BaseGpu>
class NoMaliApiGpu
    : public BaseGpu
{
  public:
    template<typename... Args>
    NoMaliApiGpu(NoMaliApi &_api, Args &&... args)
        : BaseGpu(std::forward<Args>(args)...),
          api(_api)
    {
        reset();
    }

    void reset() override {
        BaseGpu::reset();
        api.callbackReset();
    }

  public:
    void intJob(int set) override { api.callbackInt(NOMALI_INT_JOB, set); }
    void intMMU(int set) override { api.callbackInt(NOMALI_INT_MMU, set); }
    void intGPU(int set) override { api.callbackInt(NOMALI_INT_GPU, set); }

  private:
    NoMaliApi &api;
};


NoMaliApi::NoMaliApi()
    : _gpu(nullptr)
{
    memset(callbacks, 0, sizeof(callbacks));
}


NoMaliApi::~NoMaliApi()
{
}

nomali_error_t
NoMaliApi::setCallback(const nomali_callback_t *callback)
{
    if (!callback ||
        callback->type >= NOMALI_CALLBACK_NUM_CALLBACKS)
        return NOMALI_E_INVALID;

    callbacks[callback->type] = *callback;

    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::getInfo(nomali_info_t *info)
{
    if (!info)
        return NOMALI_E_INVALID;

    info->reg_size = 0x4000;

    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::reset()
{
    _gpu->reset();
    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::regRead(uint32_t *value, nomali_addr_t addr)
{
    if (!value)
        return NOMALI_E_INVALID;

    *value = _gpu->readReg(NoMali::RegAddr(addr));

    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::regWrite(nomali_addr_t addr, uint32_t value)
{
    _gpu->writeReg(NoMali::RegAddr(addr), value);

    return NOMALI_E_OK;
}


nomali_error_t
NoMaliApi::regReadRaw(uint32_t *value, nomali_addr_t addr)
{
    if (!value)
        return NOMALI_E_INVALID;

    *value = _gpu->readRegRaw(NoMali::RegAddr(addr));

    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::regWriteRaw(nomali_addr_t addr, uint32_t value)
{
    _gpu->writeRegRaw(NoMali::RegAddr(addr), value);

    return NOMALI_E_OK;
}

nomali_error_t
NoMaliApi::intState(int *state, nomali_int_t intno) const
{
    if (!state)
        return NOMALI_E_INVALID;

    switch (intno) {
      case NOMALI_INT_GPU:
        *state = _gpu->intGPUAsserted();
        break;

      case NOMALI_INT_JOB:
        *state = _gpu->intJobAsserted();
        break;

      case NOMALI_INT_MMU:
        *state = _gpu->intMMUAsserted();
        break;

      default:
        return NOMALI_E_INVALID;
    }

    return NOMALI_E_OK;
}


void
NoMaliApi::callbackInt(nomali_int_t intno, int set)
{
    const nomali_callback_t &c(callbacks[NOMALI_CALLBACK_INT]);

    if (c.func.interrupt)
        c.func.interrupt(static_cast<nomali_handle_t>(this), c.usr, intno, set);
}

void
NoMaliApi::callbackReset()
{
    const nomali_callback_t &c(callbacks[NOMALI_CALLBACK_RESET]);

    if (c.func.reset)
        c.func.reset(static_cast<nomali_handle_t>(this), c.usr);
}



static NoMaliApi *
get_gpu(nomali_handle_t h)
{
    return h ? static_cast<NoMaliApi *>(h) : nullptr;
}


extern "C" EXPORT nomali_api_version_t
nomali_api_version()
{
    return NOMALI_API_VERSION;
}

extern "C" EXPORT nomali_error_t
nomali_create(nomali_handle_t *h, const nomali_config_t *cfg)
{
    if (h && cfg) {
        NoMaliApi *api(new NoMaliApi());
        *h = api;
        if (!h)
            return NOMALI_E_MEMORY;

        NoMali::GPU *gpu;
        switch (cfg->type) {
          case NOMALI_GPU_T60X:
            gpu = new NoMaliApiGpu<NoMali::MaliT60x>(
                *api,
                cfg->ver_maj, cfg->ver_min, cfg->ver_status);
            break;

          case NOMALI_GPU_T62X:
            gpu = new NoMaliApiGpu<NoMali::MaliT62x>(
                *api,
                cfg->ver_maj, cfg->ver_min, cfg->ver_status);
            break;


          case NOMALI_GPU_T76X:
            gpu = new NoMaliApiGpu<NoMali::MaliT76x>(
                *api,
                cfg->ver_maj, cfg->ver_min, cfg->ver_status);
            break;

          default:
            delete api;
            return NOMALI_E_INVALID;
        };

        if (!gpu) {
            delete api;
            return NOMALI_E_MEMORY;
        }

        api->setGpu(gpu);

        return NOMALI_E_OK;
    } else {
        return NOMALI_E_INVALID;
    }
}

extern "C" EXPORT nomali_error_t
nomali_destroy(nomali_handle_t h)
{
    NoMaliApi *gpu(get_gpu(h));

    if (gpu) {
        delete gpu;
        return NOMALI_E_OK;
    } else {
        return NOMALI_E_HANDLE;
    }
}

extern "C" EXPORT const char *
nomali_errstr(nomali_error_t error)
{
    if (error < NOMALI_E_NUM_ERRORS)
        return errstrs[error];
    else
        return "Invalid error number";
}

extern "C" EXPORT nomali_error_t
nomali_set_callback(nomali_handle_t h,
                    const nomali_callback_t *callback)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->setCallback(callback) : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_get_info(nomali_handle_t h, nomali_info_t *info)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->getInfo(info) : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_reset(nomali_handle_t h)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->reset() : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_reg_read(nomali_handle_t h, uint32_t *value,
                nomali_addr_t addr)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->regRead(value, addr) : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_reg_write(nomali_handle_t h,
                 nomali_addr_t addr, uint32_t value)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->regWrite(addr, value) : NOMALI_E_HANDLE;
}


extern "C" EXPORT nomali_error_t
nomali_reg_read_raw(nomali_handle_t h, uint32_t *value,
                    nomali_addr_t addr)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->regReadRaw(value, addr) : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_reg_write_raw(nomali_handle_t h,
                     nomali_addr_t addr, uint32_t value)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->regWriteRaw(addr, value) : NOMALI_E_HANDLE;
}

extern "C" EXPORT nomali_error_t
nomali_int_state(nomali_handle_t h, int *state,
                 nomali_int_t intno)
{
    NoMaliApi *gpu(get_gpu(h));
    return gpu ? gpu->intState(state, intno) : NOMALI_E_HANDLE;
}