/*
* Copyright (c) 2019, The Linux Foundation. All rights reserved.
*
* 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 Linux Foundation 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 "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* 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 <stdint.h>
#include <stdlib.h>
#include <drm.h>
#include <drm/sde_drm.h>
#include <drm_logger.h>
#include <errno.h>
#include <stdlib.h>
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include <iterator>

#include "drm_encoder.h"
#include "drm_utils.h"

namespace sde_drm {

using std::unique_ptr;
using std::map;
using std::pair;
using std::make_pair;
using std::set;
using std::distance;

#define __CLASS__ "DRMEncoderManager"

DRMEncoderManager::~DRMEncoderManager() {}

void DRMEncoderManager::Init(drmModeRes *resource) {
  for (int i = 0; i < resource->count_encoders; i++) {
    unique_ptr<DRMEncoder> encoder(new DRMEncoder(fd_));
    drmModeEncoder *libdrm_encoder = drmModeGetEncoder(fd_, resource->encoders[i]);
    if (!libdrm_encoder) {
      DRM_LOGE("Critical error: drmModeGetEncoder() failed for encoder %d.", resource->encoders[i]);
      continue;
    }
    encoder->InitAndParse(libdrm_encoder);
    encoder_pool_[resource->encoders[i]] = std::move(encoder);
  }

  // TODO(user): Remove call when driver reporting of encoders is consistent across all use cases
  InsertSecondaryDSI();
}

/*
 * This API is a workaround for maintaining appropriate HW port numbers for displays on platforms
 * that can dynamically change between single panel and secondary panel uses cases. It is required
 * that the port # for a pixel stream remains consistent, so userspace must account for the
 * possiblility of the secondary panel use case, even during single panel use case.

 * Driver rearchitecture for encoder creation is under discussion for future chipsets to avoid the
 * need of this function.
*/
void DRMEncoderManager::InsertSecondaryDSI() {
  uint32_t first_dsi_id = 0;
  bool second_dsi_found = false;
  bool first_dsi_found = false;

  for (auto encoder = encoder_pool_.begin(); encoder != encoder_pool_.end(); encoder++) {
    std::unique_ptr<DRMEncoder> &encoder_ptr = encoder->second;
    uint32_t encoder_type;
    encoder_ptr->GetType(&encoder_type);
    if (encoder_type == DRM_MODE_ENCODER_DSI) {
      if (!first_dsi_found) {
        encoder_ptr->GetId(&first_dsi_id);
        first_dsi_found = true;
      } else if (first_dsi_found) {
      // Secondary panel use case is active, below logic is skipped
      second_dsi_found = true;
      break;
      }
    }
  }

  if (first_dsi_found && !second_dsi_found) {
    // Single panel use case is active, inject new DSI encoder
    uint32_t enc_id = first_dsi_id + 1;
    unique_ptr<DRMEncoder> sec_dsi_enc(new DRMEncoder(fd_, enc_id, DRM_MODE_ENCODER_DSI));
    encoder_pool_[enc_id] = std::move(sec_dsi_enc);
    DRM_LOGI("Userspace has inserted secondary panel DSI encoder!");
  } else {
    DRM_LOGI("Userspace did not need to insert secondary panel DSI encoder, it is present.");
  }
}

void DRMEncoderManager::DumpByID(uint32_t id) {
  encoder_pool_.at(id)->Dump();
}

void DRMEncoderManager::DumpAll() {
  for (auto &encoder : encoder_pool_) {
    encoder.second->Dump();
  }
}

int DRMEncoderManager::GetEncoderInfo(uint32_t encoder_id, DRMEncoderInfo *info) {
  int ret = -ENODEV;
  auto iter = encoder_pool_.find(encoder_id);

  if (iter != encoder_pool_.end()) {
    encoder_pool_[encoder_id]->GetInfo(info);
    ret = 0;
  }
  return ret;
}

int DRMEncoderManager::GetEncoderList(std::vector<uint32_t> *encoder_ids) {
  if (!encoder_ids) {
    return -EINVAL;
  }
  encoder_ids->clear();
  for (auto &encoder : encoder_pool_) {
    encoder_ids->push_back(encoder.first);
  }
  return 0;
}

int DRMEncoderManager::Reserve(const std::set<uint32_t> &possible_encoders, DRMDisplayToken *token) {
  int ret = -ENODEV;
  for (auto encoder = encoder_pool_.begin(); encoder != encoder_pool_.end(); encoder++) {
    const uint32_t &encoder_id = encoder->first;
    if ((encoder->second)->GetStatus() == DRMStatus::FREE) {
      // If encoder is found in the set, the encoder is a candidate for selection and the encoder
      // type for display's connector and encoder-this-iteration are already matched implicitly
      if (possible_encoders.find(encoder_id) != possible_encoders.end()) {
        encoder->second->Lock();
        token->encoder_id = encoder_id;
        int encoder_index = distance(encoder_pool_.begin(), encoder) + 1;  // 1-indexed port
        // Port id format.
        // Bit 7   --> Display type 0: Pluggable 1: BuiltIn X:Virtual.
        // Bit 6   --> Pluggable: 0 for TMDS encoder, 1 for DPMST encoder.
        //             Builtin Or Virtual: X
        // Bit 5-0 --> Encoder index.
        uint32_t encoder_type;
        encoder->second->GetType(&encoder_type);
        token->hw_port = GetDisplayTypeCode(encoder_type) | encoder_index;
        ret = 0;
        break;
      }
    }
  }
  return ret;
}

int DRMEncoderManager::GetDisplayTypeCode(uint32_t encoder_type) {
  int disp_info = 0x2;
  switch (encoder_type) {
    case DRM_MODE_ENCODER_TMDS:
      disp_info = 0x0;
      break;
    case DRM_MODE_ENCODER_DPMST:
      disp_info = 0x1;
      break;
    default:
      break;
  }

  return (disp_info << 6);
}

void DRMEncoderManager::Free(DRMDisplayToken *token) {
  auto iter = encoder_pool_.find(token->encoder_id);
  if (iter != encoder_pool_.end()) {
    iter->second->Unlock();
  } else {
    DRM_LOGW("Failed! encoder_id %u not found! Cleaning up token anyway...", token->encoder_id);
  }
  token->encoder_id = 0;
  token->hw_port = 0;
}

int DRMEncoderManager::GetPossibleCrtcIndices(uint32_t encoder_id,
                                  std::set<uint32_t> *possible_crtc_indices) {
  return encoder_pool_[encoder_id]->GetPossibleCrtcIndices(possible_crtc_indices);
}

// ==============================================================================================//

#undef __CLASS__
#define __CLASS__ "DRMEncoder"

DRMEncoder::~DRMEncoder() {
  if (drm_encoder_) {
    drmModeFreeEncoder(drm_encoder_);
  }
}

void DRMEncoder::GetInfo(DRMEncoderInfo *info) {
  *info = encoder_info_;
}

void DRMEncoder::Lock() {
  status_ = DRMStatus::BUSY;
}

void DRMEncoder::Unlock() {
  status_ = DRMStatus::FREE;
}

void DRMEncoder::InitAndParse(drmModeEncoder *encoder) {
  drm_encoder_ = encoder;
  encoder_info_.type = drm_encoder_->encoder_type;
}

int DRMEncoder::GetPossibleCrtcIndices(std::set<uint32_t> *possible_crtc_indices) {
  if (!possible_crtc_indices) {
    return -EINVAL;
  }

  (*possible_crtc_indices).clear();
  std::bitset<32> possible_crtcs = drm_encoder_->possible_crtcs;
  for (uint32_t i = 0; i < possible_crtcs.size(); i++) {
    if (possible_crtcs[i]) {
      (*possible_crtc_indices).insert(i);
    }
  }

  return 0;
}

void DRMEncoder::Dump() {
  if (drm_encoder_) {
    DRM_LOGI("[driver-reported] id: %u encoder_type: %u crtc id: %u possible_crtcs: %u"
             "possible_clones: %u fd = %d",
             drm_encoder_->encoder_id, drm_encoder_->encoder_type, drm_encoder_->crtc_id,
             drm_encoder_->possible_crtcs, drm_encoder_->possible_clones, fd_);
  } else {
    DRM_LOGI("[userspace-only] id: %u encoder_type: %u fd = %d ", fake_id_, fake_type_, fd_);
  }
}

}  // namespace sde_drm
