/*
* Copyright (c) 2015 - 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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <utils/debug.h>
#include <utils/sys.h>
#include <core/display_interface.h>
#include <linux/msm_mdp_ext.h>
#include <utils/rect.h>

#include <string>

#include "hw_primary.h"
#include "hw_color_manager.h"

#define __CLASS__ "HWPrimary"

#ifndef MDP_COMMIT_CWB_EN
#define MDP_COMMIT_CWB_EN 0x800
#endif

#ifndef MDP_COMMIT_CWB_DSPP
#define MDP_COMMIT_CWB_DSPP 0x1000
#endif

#ifndef MDP_COMMIT_AVR_EN
#define MDP_COMMIT_AVR_EN 0x08
#endif

#ifndef MDP_COMMIT_AVR_ONE_SHOT_MODE
#define MDP_COMMIT_AVR_ONE_SHOT_MODE 0x10
#endif

#ifndef MDP_COMMIT_PARTIAL_UPDATE_DUAL_ROI
#define MDP_COMMIT_PARTIAL_UPDATE_DUAL_ROI  0x20
#endif

namespace sdm {

using std::string;
using std::to_string;
using std::fstream;

HWPrimary::HWPrimary(BufferSyncHandler *buffer_sync_handler, HWInfoInterface *hw_info_intf)
  : HWDevice(buffer_sync_handler) {
  HWDevice::device_type_ = kDeviceBuiltIn;
  HWDevice::device_name_ = "BuiltIn Display Device";
  HWDevice::hw_info_intf_ = hw_info_intf;
}

DisplayError HWPrimary::Init() {
  DisplayError error = kErrorNone;

  error = HWDevice::Init();
  if (error != kErrorNone) {
    return error;
  }

  mdp_dest_scalar_data_.resize(hw_resource_.hw_dest_scalar_info.count);

  error = PopulateDisplayAttributes();
  if (error != kErrorNone) {
    return error;
  }

  UpdateMixerAttributes();

  // Need to enable HPD, but toggle at start when HDMI is external
  // This helps for framework reboot or adb shell stop/start
  EnableHotPlugDetection(0);
  EnableHotPlugDetection(1);
  InitializeConfigs();

  avr_prop_disabled_ = Debug::IsAVRDisabled();

  return error;
}

bool HWPrimary::GetCurrentModeFromSysfs(size_t *curr_x_pixels, size_t *curr_y_pixels) {
  bool ret = false;
  string mode_path = fb_path_ + string("0/mode");

  Sys::fstream fs(mode_path, fstream::in);
  if (!fs.is_open()) {
    return false;
  }

  string line;
  if (Sys::getline_(fs, line)) {
    // String is of form "U:1600x2560p-0". Documentation/fb/modedb.txt in
    // kernel has more info on the format.
    size_t xpos = line.find(':');
    size_t ypos = line.find('x');

    if (xpos == string::npos || ypos == string::npos) {
      DLOGI("Resolution switch not supported");
    } else {
      *curr_x_pixels = static_cast<size_t>(atoi(line.c_str() + xpos + 1));
      *curr_y_pixels = static_cast<size_t>(atoi(line.c_str() + ypos + 1));
      DLOGI("Current Config: %u x %u", *curr_x_pixels, *curr_y_pixels);
      ret = true;
    }
  }

  return ret;
}

void HWPrimary::InitializeConfigs() {
  size_t curr_x_pixels = 0;
  size_t curr_y_pixels = 0;

  if (!GetCurrentModeFromSysfs(&curr_x_pixels, &curr_y_pixels)) {
    return;
  }

  string modes_path = string(fb_path_) + string("0/modes");

  Sys::fstream fs(modes_path, fstream::in);
  if (!fs.is_open()) {
    DLOGI("Unable to process modes");
    return;
  }

  string line;
  while (Sys::getline_(fs, line)) {
    DisplayConfigVariableInfo config;
    // std::getline (unlike ::getline) removes \n while driver expects it in mode, so add back
    line += '\n';
    size_t xpos = line.find(':');
    size_t ypos = line.find('x');

    if (xpos == string::npos || ypos == string::npos) {
      continue;
    }

    config.x_pixels = UINT32(atoi(line.c_str() + xpos + 1));
    config.y_pixels = UINT32(atoi(line.c_str() + ypos + 1));
    DLOGI("Found mode %d x %d", config.x_pixels, config.y_pixels);
    display_configs_.push_back(config);
    display_config_strings_.push_back(string(line.c_str()));

    if (curr_x_pixels == config.x_pixels && curr_y_pixels == config.y_pixels) {
      active_config_index_ = UINT32(display_configs_.size() - 1);
      DLOGI("Active config index %u", active_config_index_);
    }
  }
}

DisplayError HWPrimary::GetNumDisplayAttributes(uint32_t *count) {
  *count = IsResolutionSwitchEnabled() ? UINT32(display_configs_.size()) : 1;
  return kErrorNone;
}

DisplayError HWPrimary::GetActiveConfig(uint32_t *active_config_index) {
  *active_config_index = active_config_index_;
  return kErrorNone;
}

DisplayError HWPrimary::GetDisplayAttributes(uint32_t index,
                                             HWDisplayAttributes *display_attributes) {
  if (!display_attributes) {
    return kErrorParameters;
  }

  if (IsResolutionSwitchEnabled() && index >= display_configs_.size()) {
    return kErrorParameters;
  }

  *display_attributes = display_attributes_;
  if (IsResolutionSwitchEnabled()) {
    // Overwrite only the parent portion of object
    display_attributes->x_pixels = display_configs_.at(index).x_pixels;
    display_attributes->y_pixels = display_configs_.at(index).y_pixels;
  }

  return kErrorNone;
}

DisplayError HWPrimary::PopulateDisplayAttributes() {
  DTRACE_SCOPED();

  // Variable screen info
  fb_var_screeninfo var_screeninfo = {};

  if (Sys::ioctl_(device_fd_, FBIOGET_VSCREENINFO, &var_screeninfo) < 0) {
    IOCTL_LOGE(FBIOGET_VSCREENINFO, device_type_);
    return kErrorHardware;
  }

  // Frame rate
  msmfb_metadata meta_data = {};
  meta_data.op = metadata_op_frame_rate;
  if (Sys::ioctl_(device_fd_, MSMFB_METADATA_GET, &meta_data) < 0) {
    IOCTL_LOGE(MSMFB_METADATA_GET, device_type_);
    return kErrorHardware;
  }

  // If driver doesn't return width/height information, default to 320 dpi
  if (INT(var_screeninfo.width) <= 0 || INT(var_screeninfo.height) <= 0) {
    var_screeninfo.width  = UINT32(((FLOAT(var_screeninfo.xres) * 25.4f)/320.0f) + 0.5f);
    var_screeninfo.height = UINT32(((FLOAT(var_screeninfo.yres) * 25.4f)/320.0f) + 0.5f);
    DLOGW("Driver doesn't report panel physical width and height - defaulting to 320dpi");
  }

  display_attributes_.x_pixels = var_screeninfo.xres;
  display_attributes_.y_pixels = var_screeninfo.yres;
  display_attributes_.v_front_porch = var_screeninfo.lower_margin;
  display_attributes_.v_back_porch = var_screeninfo.upper_margin;
  display_attributes_.v_pulse_width = var_screeninfo.vsync_len;
  uint32_t h_blanking = var_screeninfo.right_margin + var_screeninfo.left_margin +
      var_screeninfo.hsync_len;
  display_attributes_.h_total = var_screeninfo.xres + h_blanking;
  display_attributes_.x_dpi =
      (FLOAT(var_screeninfo.xres) * 25.4f) / FLOAT(var_screeninfo.width);
  display_attributes_.y_dpi =
      (FLOAT(var_screeninfo.yres) * 25.4f) / FLOAT(var_screeninfo.height);
  display_attributes_.fps = meta_data.data.panel_frame_rate;
  display_attributes_.vsync_period_ns = UINT32(1000000000L / display_attributes_.fps);
  display_attributes_.is_device_split = (hw_panel_info_.split_info.right_split ||
      (var_screeninfo.xres > hw_resource_.max_mixer_width));
  display_attributes_.h_total += (display_attributes_.is_device_split ||
    hw_panel_info_.ping_pong_split)? h_blanking : 0;

  return kErrorNone;
}

DisplayError HWPrimary::SetDisplayAttributes(uint32_t index) {
  DisplayError ret = kErrorNone;

  if (!IsResolutionSwitchEnabled()) {
    return kErrorNotSupported;
  }

  if (index >= display_configs_.size()) {
    return kErrorParameters;
  }

  string mode_path = string(fb_path_) + string("0/mode");
  int fd = Sys::open_(mode_path.c_str(), O_WRONLY);

  if (fd < 0) {
    DLOGE("Opening mode failed");
    return kErrorNotSupported;
  }

  ssize_t written = Sys::pwrite_(fd, display_config_strings_.at(index).c_str(),
                                 display_config_strings_.at(index).length(), 0);
  if (written > 0) {
    DLOGI("Successfully set config %u", index);
    PopulateHWPanelInfo();
    PopulateDisplayAttributes();
    UpdateMixerAttributes();
    active_config_index_ = index;
  } else {
    DLOGE("Writing config index %u failed with error: %s", index, strerror(errno));
    ret = kErrorParameters;
  }

  Sys::close_(fd);

  return ret;
}

DisplayError HWPrimary::SetRefreshRate(uint32_t refresh_rate) {
  char node_path[kMaxStringLength] = {0};

  if (hw_resource_.has_avr && !avr_prop_disabled_) {
    return kErrorNotSupported;
  }

  snprintf(node_path, sizeof(node_path), "%s%d/dynamic_fps", fb_path_, fb_node_index_);

  int fd = Sys::open_(node_path, O_WRONLY);
  if (fd < 0) {
    DLOGE("Failed to open %s with error %s", node_path, strerror(errno));
    return kErrorFileDescriptor;
  }

  char refresh_rate_string[kMaxStringLength];
  snprintf(refresh_rate_string, sizeof(refresh_rate_string), "%d", refresh_rate);
  DLOGI_IF(kTagDriverConfig, "Setting refresh rate = %d", refresh_rate);
  ssize_t len = Sys::pwrite_(fd, refresh_rate_string, strlen(refresh_rate_string), 0);
  if (len < 0) {
    DLOGE("Failed to write %d with error %s", refresh_rate, strerror(errno));
    Sys::close_(fd);
    return kErrorUndefined;
  }
  Sys::close_(fd);

  DisplayError error = PopulateDisplayAttributes();
  if (error != kErrorNone) {
    return error;
  }

  return kErrorNone;
}

DisplayError HWPrimary::GetConfigIndex(char *mode, uint32_t *index) {
  return HWDevice::GetConfigIndex(mode, index);
}

DisplayError HWPrimary::PowerOff(bool teardown) {
  if (Sys::ioctl_(device_fd_, FBIOBLANK, FB_BLANK_POWERDOWN) < 0) {
    IOCTL_LOGE(FB_BLANK_POWERDOWN, device_type_);
    return kErrorHardware;
  }

  auto_refresh_ = false;

  return kErrorNone;
}

DisplayError HWPrimary::Doze(const HWQosData &qos_data, int *release_fence) {
  if (Sys::ioctl_(device_fd_, FBIOBLANK, FB_BLANK_NORMAL) < 0) {
    IOCTL_LOGE(FB_BLANK_NORMAL, device_type_);
    return kErrorHardware;
  }

  return kErrorNone;
}

DisplayError HWPrimary::DozeSuspend(const HWQosData &qos_data, int *release_fence) {
  if (Sys::ioctl_(device_fd_, FBIOBLANK, FB_BLANK_VSYNC_SUSPEND) < 0) {
    IOCTL_LOGE(FB_BLANK_VSYNC_SUSPEND, device_type_);
    return kErrorHardware;
  }

  return kErrorNone;
}

DisplayError HWPrimary::Validate(HWLayers *hw_layers) {
  HWLayersInfo &hw_layer_info = hw_layers->info;
  LayerStack *stack = hw_layer_info.stack;

  HWDevice::ResetDisplayParams();

  mdp_layer_commit_v1 &mdp_commit = mdp_disp_commit_.commit_v1;

  LayerRect left_roi = hw_layer_info.left_frame_roi.at(0);
  LayerRect right_roi = hw_layer_info.right_frame_roi.at(0);

  mdp_commit.left_roi.x = UINT32(left_roi.left);
  mdp_commit.left_roi.y = UINT32(left_roi.top);
  mdp_commit.left_roi.w = UINT32(left_roi.right - left_roi.left);
  mdp_commit.left_roi.h = UINT32(left_roi.bottom - left_roi.top);

  // Update second roi information in right_roi
  if (hw_layer_info.left_frame_roi.size() == 2) {
    mdp_commit.flags |= MDP_COMMIT_PARTIAL_UPDATE_DUAL_ROI;
    right_roi = hw_layer_info.left_frame_roi.at(1);
  }

  // SDM treats ROI as one full coordinate system.
  // In case source split is disabled, However, Driver assumes Mixer to operate in
  // different co-ordinate system.
  if (IsValid(right_roi)) {
    mdp_commit.right_roi.x = UINT32(right_roi.left);
    if (!hw_resource_.is_src_split) {
      mdp_commit.right_roi.x = UINT32(right_roi.left) - mixer_attributes_.split_left;
    }
    mdp_commit.right_roi.y = UINT32(right_roi.top);
    mdp_commit.right_roi.w = UINT32(right_roi.right - right_roi.left);
    mdp_commit.right_roi.h = UINT32(right_roi.bottom - right_roi.top);
  }

  if (stack->output_buffer && hw_resource_.has_concurrent_writeback) {
    LayerBuffer *output_buffer = stack->output_buffer;
    mdp_out_layer_.writeback_ndx = hw_resource_.writeback_index;
    mdp_out_layer_.buffer.width = output_buffer->unaligned_width;
    mdp_out_layer_.buffer.height = output_buffer->unaligned_height;
    mdp_out_layer_.buffer.comp_ratio.denom = 1000;
    mdp_out_layer_.buffer.comp_ratio.numer = UINT32(hw_layers->output_compression * 1000);
    mdp_out_layer_.buffer.fence = -1;
#ifdef OUT_LAYER_COLOR_SPACE
    SetCSC(output_buffer->color_metadata, &mdp_out_layer_.color_space);
#endif
    SetFormat(output_buffer->format, &mdp_out_layer_.buffer.format);
    mdp_commit.flags |= MDP_COMMIT_CWB_EN;
    mdp_commit.flags |= (stack->flags.post_processed_output) ? MDP_COMMIT_CWB_DSPP : 0;
    DLOGI_IF(kTagDriverConfig, "****************** Conc WB Output buffer Info ******************");
    DLOGI_IF(kTagDriverConfig, "out_w %d, out_h %d, out_f %d, wb_id %d DSPP output %d",
             mdp_out_layer_.buffer.width, mdp_out_layer_.buffer.height,
             mdp_out_layer_.buffer.format, mdp_out_layer_.writeback_ndx,
             stack->flags.post_processed_output);
    DLOGI_IF(kTagDriverConfig, "****************************************************************");
  }

  if (hw_resource_.has_avr) {
    SetAVRFlags(hw_layers->hw_avr_info, &mdp_commit.flags);
  }

  return HWDevice::Validate(hw_layers);
}

DisplayError HWPrimary::Commit(HWLayers *hw_layers) {
  LayerBuffer *output_buffer = hw_layers->info.stack->output_buffer;

  if (hw_resource_.has_concurrent_writeback && output_buffer) {
    if (output_buffer->planes[0].fd >= 0) {
      mdp_out_layer_.buffer.planes[0].fd = output_buffer->planes[0].fd;
      mdp_out_layer_.buffer.planes[0].offset = output_buffer->planes[0].offset;
      SetStride(device_type_, output_buffer->format, output_buffer->planes[0].stride,
                &mdp_out_layer_.buffer.planes[0].stride);
      mdp_out_layer_.buffer.plane_count = 1;
      mdp_out_layer_.buffer.fence = -1;

      DLOGI_IF(kTagDriverConfig, "****************** Conc WB Output buffer Info ****************");
      DLOGI_IF(kTagDriverConfig, "out_fd %d, out_offset %d, out_stride %d",
               mdp_out_layer_.buffer.planes[0].fd, mdp_out_layer_.buffer.planes[0].offset,
               mdp_out_layer_.buffer.planes[0].stride);
      DLOGI_IF(kTagDriverConfig, "**************************************************************");
    } else {
      DLOGE("Invalid output buffer fd");
      return kErrorParameters;
    }
  }

  DisplayError ret = HWDevice::Commit(hw_layers);

  if (ret == kErrorNone && hw_resource_.has_concurrent_writeback && output_buffer) {
    output_buffer->release_fence_fd = mdp_out_layer_.buffer.fence;
  }

  return ret;
}

void HWPrimary::SetIdleTimeoutMs(uint32_t timeout_ms) {
  char node_path[kMaxStringLength] = {0};

  DLOGI_IF(kTagDriverConfig, "Setting idle timeout to = %d ms", timeout_ms);

  snprintf(node_path, sizeof(node_path), "%s%d/idle_time", fb_path_, fb_node_index_);

  // Open a sysfs node to send the timeout value to driver.
  int fd = Sys::open_(node_path, O_WRONLY);
  if (fd < 0) {
    DLOGE("Unable to open %s, node %s", node_path, strerror(errno));
    return;
  }

  char timeout_string[64];
  snprintf(timeout_string, sizeof(timeout_string), "%d", timeout_ms);

  // Notify driver about the timeout value
  ssize_t length = Sys::pwrite_(fd, timeout_string, strlen(timeout_string), 0);
  if (length <= 0) {
    DLOGE("Unable to write into %s, node %s", node_path, strerror(errno));
  }

  Sys::close_(fd);
}

DisplayError HWPrimary::SetVSyncState(bool enable) {
  DTRACE_SCOPED();
  return HWDevice::SetVSyncState(enable);
}

DisplayError HWPrimary::SetDisplayMode(const HWDisplayMode hw_display_mode) {
  uint32_t mode = kModeDefault;

  switch (hw_display_mode) {
  case kModeVideo:
    mode = kModeLPMVideo;
    break;
  case kModeCommand:
    mode = kModeLPMCommand;
    break;
  default:
    DLOGW("Failed to translate SDE display mode %d to a MSMFB_LPM_ENABLE mode",
          hw_display_mode);
    return kErrorParameters;
  }

  if (Sys::ioctl_(device_fd_, INT(MSMFB_LPM_ENABLE), &mode) < 0) {
    IOCTL_LOGE(MSMFB_LPM_ENABLE, device_type_);
    return kErrorHardware;
  }

  DLOGI("Triggering display mode change to %d on next commit.", hw_display_mode);
  synchronous_commit_ = true;

  return kErrorNone;
}

DisplayError HWPrimary::SetPanelBrightness(int level) {
  char buffer[kMaxSysfsCommandLength] = {0};

  DLOGV_IF(kTagDriverConfig, "Set brightness level to %d", level);
  if (brightness_fd_ < 0) {
    DLOGV_IF(kTagDriverConfig, "Unable to access brightness node = %s", kBrightnessNode);
    return kErrorFileDescriptor;
  }

  int32_t bytes = snprintf(buffer, kMaxSysfsCommandLength, "%d\n", level);
  if (bytes < 0) {
    DLOGV_IF(kTagDriverConfig, "Failed to copy new brightness level = %d", level);
    return kErrorUndefined;
  }

  ssize_t ret = Sys::pwrite_(brightness_fd_, buffer, static_cast<size_t>(bytes), 0);
  if (ret <= 0) {
    DLOGV_IF(kTagDriverConfig, "Failed to write to node = %s, error = %s ", kBrightnessNode,
             strerror(errno));
    return kErrorUndefined;
  }

  return kErrorNone;
}

DisplayError HWPrimary::GetPanelBrightness(int &level) const {
  char brightness[kMaxStringLength] = {0};

  if (brightness_fd_ < 0) {
    DLOGV_IF(kTagDriverConfig, "Unable to access brightness node = %s", kBrightnessNode);
    return kErrorFileDescriptor;
  }

  if (Sys::pread_(brightness_fd_, brightness, sizeof(brightness), 0) > 0) {
    level = atoi(brightness);
    DLOGV_IF(kTagDriverConfig, "Brightness level = %d", level);
  } else {
    DLOGW("Failed to read brightness level. error = %s", strerror(errno));
  }

  return kErrorNone;
}

void HWPrimary::GetHWPanelMaxBrightnessFromNode(HWPanelInfo *panel_info) {
  char brightness[kMaxStringLength] = {0};

  panel_info->panel_max_brightness = kDefaultMaxBrightness;
  if (max_brightness_fd_ < 0) {
    DLOGW("Unable to access max brightness node = %s", kMaxBrightnessNode);
    return;
  }

  if (Sys::pread_(max_brightness_fd_, brightness, sizeof(brightness), 0) > 0) {
    panel_info->panel_max_brightness = atoi(brightness);
    DLOGI("Max brightness level = %d", panel_info->panel_max_brightness);
  } else {
    DLOGW("Failed to read max brightness level. error = %s", strerror(errno));
  }
}

bool HWPrimary::IsSupportPanelBrightnessControl() {
  return (brightness_fd_ >= 0);
}

void HWPrimary::InitializePanelBrightnessFileDescriptor() {
  brightness_fd_ = Sys::open_(kBrightnessNode, O_RDWR);
  if (brightness_fd_ < 0) {
    DLOGW("Unable to open brightness node = %s, error = %s", kBrightnessNode, strerror(errno));
  }

  max_brightness_fd_ = Sys::open_(kMaxBrightnessNode, O_RDONLY);
  if (max_brightness_fd_ < 0) {
    DLOGW("Unable to open max brightness node = %s, error = %s", kMaxBrightnessNode,
          strerror(errno));
  }
}

DisplayError HWPrimary::SetAutoRefresh(bool enable) {
  const int kWriteLength = 2;
  char buffer[kWriteLength] = {'\0'};
  ssize_t bytes = snprintf(buffer, kWriteLength, "%d", enable);

  if (enable == auto_refresh_) {
    return kErrorNone;
  }

  if (HWDevice::SysFsWrite(kAutoRefreshNode, buffer, bytes) <= 0) {  // Returns bytes written
    return kErrorUndefined;
  }

  auto_refresh_ = enable;

  return kErrorNone;
}

DisplayError HWPrimary::GetPPFeaturesVersion(PPFeatureVersion *vers) {
  mdp_pp_feature_version version = {};

#ifdef PA_DITHER
  uint32_t feature_id_mapping[kMaxNumPPFeatures] = { PCC, IGC, GC, GC, PA,
                                                     DITHER, GAMUT, PA_DITHER };
#else
  uint32_t feature_id_mapping[kMaxNumPPFeatures] = { PCC, IGC, GC, GC, PA, DITHER, GAMUT };
#endif

  for (int i(0); i < kMaxNumPPFeatures; i++) {
    version.pp_feature = feature_id_mapping[i];

    if (Sys::ioctl_(device_fd_,  INT(MSMFB_MDP_PP_GET_FEATURE_VERSION), &version) < 0) {
      IOCTL_LOGE(MSMFB_MDP_PP_GET_FEATURE_VERSION, device_type_);
      return kErrorHardware;
    }
    vers->version[i] = version.version_info;
  }

  return kErrorNone;
}

// It was entered with PPFeaturesConfig::locker_ being hold.
DisplayError HWPrimary::SetPPFeatures(PPFeaturesConfig *feature_list) {
  msmfb_mdp_pp kernel_params = {};
  int ret = 0;
  PPFeatureInfo *feature = NULL;

  while (true) {
    ret = feature_list->RetrieveNextFeature(&feature);
    if (ret)
        break;

    if (feature) {
      DLOGV_IF(kTagDriverConfig, "feature_id = %d", feature->feature_id_);

      if ((feature->feature_id_ < kMaxNumPPFeatures)) {
        HWColorManager::SetFeature[feature->feature_id_](*feature, &kernel_params);
        if (Sys::ioctl_(device_fd_, INT(MSMFB_MDP_PP), &kernel_params) < 0) {
          IOCTL_LOGE(MSMFB_MDP_PP, device_type_);

          feature_list->Reset();
          return kErrorHardware;
        }
      }
    }
  }  // while(true)

  // Once all features were consumed, then destroy all feature instance from feature_list,
  // Then mark it as non-dirty of PPFeaturesConfig cache.
  feature_list->Reset();

  return kErrorNone;
}

DisplayError HWPrimary::SetMixerAttributes(const HWMixerAttributes &mixer_attributes) {
  if (IsResolutionSwitchEnabled()) {
    return kErrorNotSupported;
  }

  return HWDevice::SetMixerAttributes(mixer_attributes);
}

void HWPrimary::UpdateMixerAttributes() {
  mixer_attributes_.width = display_attributes_.x_pixels;
  mixer_attributes_.height = display_attributes_.y_pixels;
  mixer_attributes_.split_left = display_attributes_.is_device_split ?
      hw_panel_info_.split_info.left_split : mixer_attributes_.width;
}

void HWPrimary::SetAVRFlags(const HWAVRInfo &hw_avr_info, uint32_t *avr_flags) {
  // TODO(user): Add explicit cont. flag.
  if (hw_avr_info.mode == kOneShotMode) {
    *avr_flags |= MDP_COMMIT_AVR_ONE_SHOT_MODE | MDP_COMMIT_AVR_EN;
  }
}

}  // namespace sdm

