/*
* Copyright (c) 2015 - 2016, 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 <sys/ioctl.h>
#include <ctype.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <utils/debug.h>
#include <utils/sys.h>
#include <vector>
#include <map>
#include <utility>

#include "hw_hdmi.h"

#define __CLASS__ "HWHDMI"

namespace sdm {

static bool MapHDMIDisplayTiming(const msm_hdmi_mode_timing_info *mode,
                                 fb_var_screeninfo *info) {
  if (!mode || !info) {
    return false;
  }

  info->reserved[0] = 0;
  info->reserved[1] = 0;
  info->reserved[2] = 0;
  info->reserved[3] = (info->reserved[3] & 0xFFFF) | (mode->video_format << 16);
  info->xoffset = 0;
  info->yoffset = 0;
  info->xres = mode->active_h;
  info->yres = mode->active_v;
  info->pixclock = (mode->pixel_freq) * 1000;
  info->vmode = mode->interlaced ? FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED;
  info->right_margin = mode->front_porch_h;
  info->hsync_len = mode->pulse_width_h;
  info->left_margin = mode->back_porch_h;
  info->lower_margin = mode->front_porch_v;
  info->vsync_len = mode->pulse_width_v;
  info->upper_margin = mode->back_porch_v;

  info->grayscale = V4L2_PIX_FMT_RGB24;
  // If the mode supports YUV420 set grayscale to the FOURCC value for YUV420.
  std::bitset<32> pixel_formats = mode->pixel_formats;
  if (pixel_formats[1]) {
    info->grayscale = V4L2_PIX_FMT_NV12;
  }

  return true;
}

DisplayError HWHDMI::Create(HWInterface **intf, HWInfoInterface *hw_info_intf,
                            BufferSyncHandler *buffer_sync_handler) {
  DisplayError error = kErrorNone;
  HWHDMI *hw_fb_hdmi = NULL;

  hw_fb_hdmi = new HWHDMI(buffer_sync_handler, hw_info_intf);
  error = hw_fb_hdmi->Init();
  if (error != kErrorNone) {
    delete hw_fb_hdmi;
  } else {
    *intf = hw_fb_hdmi;
  }
  return error;
}

DisplayError HWHDMI::Destroy(HWInterface *intf) {
  HWHDMI *hw_fb_hdmi = static_cast<HWHDMI *>(intf);
  hw_fb_hdmi->Deinit();
  delete hw_fb_hdmi;

  return kErrorNone;
}

HWHDMI::HWHDMI(BufferSyncHandler *buffer_sync_handler,  HWInfoInterface *hw_info_intf)
  : HWDevice(buffer_sync_handler), hw_scan_info_(), active_config_index_(0) {
  HWDevice::device_type_ = kDeviceHDMI;
  HWDevice::device_name_ = "HDMI Display Device";
  HWDevice::hw_info_intf_ = hw_info_intf;
}

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

  SetSourceProductInformation("vendor_name", "ro.product.manufacturer");
  SetSourceProductInformation("product_description", "ro.product.name");

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

  mdp_dest_scalar_data_.resize(hw_resource_.hw_dest_scalar_info.count);

  error = ReadEDIDInfo();
  if (error != kErrorNone) {
    Deinit();
    return error;
  }

  if (!IsResolutionFilePresent()) {
    Deinit();
    return kErrorHardware;
  }

  error = ReadTimingInfo();
  if (error != kErrorNone) {
    Deinit();
    return error;
  }

  ReadScanInfo();

  s3d_mode_sdm_to_mdp_.insert(std::pair<HWS3DMode, msm_hdmi_s3d_mode>
                             (kS3DModeNone, HDMI_S3D_NONE));
  s3d_mode_sdm_to_mdp_.insert(std::pair<HWS3DMode, msm_hdmi_s3d_mode>
                             (kS3DModeLR, HDMI_S3D_SIDE_BY_SIDE));
  s3d_mode_sdm_to_mdp_.insert(std::pair<HWS3DMode, msm_hdmi_s3d_mode>
                             (kS3DModeRL, HDMI_S3D_SIDE_BY_SIDE));
  s3d_mode_sdm_to_mdp_.insert(std::pair<HWS3DMode, msm_hdmi_s3d_mode>
                             (kS3DModeTB, HDMI_S3D_TOP_AND_BOTTOM));
  s3d_mode_sdm_to_mdp_.insert(std::pair<HWS3DMode, msm_hdmi_s3d_mode>
                             (kS3DModeFP, HDMI_S3D_FRAME_PACKING));

  return error;
}

DisplayError HWHDMI::Deinit() {
  return HWDevice::Deinit();
}

DisplayError HWHDMI::GetNumDisplayAttributes(uint32_t *count) {
  *count = UINT32(hdmi_modes_.size());
  if (*count <= 0) {
    return kErrorHardware;
  }

  return kErrorNone;
}

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

DisplayError HWHDMI::ReadEDIDInfo() {
  ssize_t length = -1;
  char edid_str[kPageSize] = {'\0'};
  char edid_path[kMaxStringLength] = {'\0'};
  snprintf(edid_path, sizeof(edid_path), "%s%d/edid_modes", fb_path_, fb_node_index_);
  int edid_file = Sys::open_(edid_path, O_RDONLY);
  if (edid_file < 0) {
    DLOGE("EDID file open failed.");
    return kErrorHardware;
  }

  length = Sys::pread_(edid_file, edid_str, sizeof(edid_str)-1, 0);
  if (length <= 0) {
    DLOGE("%s: edid_modes file empty");
    return kErrorHardware;
  }
  Sys::close_(edid_file);

  DLOGI("EDID mode string: %s", edid_str);
  while (length > 1 && isspace(edid_str[length-1])) {
    --length;
  }
  edid_str[length] = '\0';

  if (length > 0) {
    // Get EDID modes from the EDID string
    char *ptr = edid_str;
    const uint32_t edid_count_max = 128;
    char *tokens[edid_count_max] = { NULL };
    uint32_t hdmi_mode_count = 0;

    ParseLine(ptr, tokens, edid_count_max, &hdmi_mode_count);

    supported_video_modes_.resize(hdmi_mode_count);

    hdmi_modes_.resize(hdmi_mode_count);
    for (uint32_t i = 0; i < hdmi_mode_count; i++) {
      hdmi_modes_[i] = UINT32(atoi(tokens[i]));
    }
  }

  return kErrorNone;
}

DisplayError HWHDMI::GetDisplayAttributes(uint32_t index,
                                          HWDisplayAttributes *display_attributes) {
  DTRACE_SCOPED();

  if (index >= hdmi_modes_.size()) {
    return kErrorNotSupported;
  }

  // Get the resolution info from the look up table
  msm_hdmi_mode_timing_info *timing_mode = &supported_video_modes_[0];
  for (uint32_t i = 0; i < hdmi_modes_.size(); i++) {
    msm_hdmi_mode_timing_info *cur = &supported_video_modes_[i];
    if (cur->video_format == hdmi_modes_[index]) {
      timing_mode = cur;
      break;
    }
  }
  display_attributes->x_pixels = timing_mode->active_h;
  display_attributes->y_pixels = timing_mode->active_v;
  display_attributes->v_front_porch = timing_mode->front_porch_v;
  display_attributes->v_back_porch = timing_mode->back_porch_v;
  display_attributes->v_pulse_width = timing_mode->pulse_width_v;
  uint32_t h_blanking = timing_mode->front_porch_h + timing_mode->back_porch_h +
      timing_mode->pulse_width_h;
  display_attributes->h_total = timing_mode->active_h + h_blanking;
  display_attributes->x_dpi = 0;
  display_attributes->y_dpi = 0;
  display_attributes->fps = timing_mode->refresh_rate / 1000;
  display_attributes->vsync_period_ns = UINT32(1000000000L / display_attributes->fps);
  display_attributes->is_device_split = false;
  if (display_attributes->x_pixels > hw_resource_.max_mixer_width) {
    display_attributes->is_device_split = true;
    display_attributes->h_total += h_blanking;
  }

  GetDisplayS3DSupport(index, display_attributes);
  std::bitset<32> pixel_formats = timing_mode->pixel_formats;

  display_attributes->is_yuv = pixel_formats[1];

  return kErrorNone;
}

DisplayError HWHDMI::SetDisplayAttributes(uint32_t index) {
  DTRACE_SCOPED();

  if (index > hdmi_modes_.size()) {
    return kErrorNotSupported;
  }

  // Variable screen info
  fb_var_screeninfo vscreeninfo = {};
  if (Sys::ioctl_(device_fd_, FBIOGET_VSCREENINFO, &vscreeninfo) < 0) {
    IOCTL_LOGE(FBIOGET_VSCREENINFO, device_type_);
    return kErrorHardware;
  }

  DLOGI("GetInfo<Mode=%d %dx%d (%d,%d,%d),(%d,%d,%d) %dMHz>", vscreeninfo.reserved[3],
        vscreeninfo.xres, vscreeninfo.yres, vscreeninfo.right_margin, vscreeninfo.hsync_len,
        vscreeninfo.left_margin, vscreeninfo.lower_margin, vscreeninfo.vsync_len,
        vscreeninfo.upper_margin, vscreeninfo.pixclock/1000000);

  msm_hdmi_mode_timing_info *timing_mode = &supported_video_modes_[0];
  for (uint32_t i = 0; i < hdmi_modes_.size(); i++) {
    msm_hdmi_mode_timing_info *cur = &supported_video_modes_[i];
    if (cur->video_format == hdmi_modes_[index]) {
      timing_mode = cur;
      break;
    }
  }

  if (MapHDMIDisplayTiming(timing_mode, &vscreeninfo) == false) {
    return kErrorParameters;
  }

  msmfb_metadata metadata = {};
  metadata.op = metadata_op_vic;
  metadata.data.video_info_code = timing_mode->video_format;
  if (Sys::ioctl_(device_fd_, MSMFB_METADATA_SET, &metadata) < 0) {
    IOCTL_LOGE(MSMFB_METADATA_SET, device_type_);
    return kErrorHardware;
  }

  DLOGI("SetInfo<Mode=%d %dx%d (%d,%d,%d),(%d,%d,%d) %dMHz>", vscreeninfo.reserved[3] & 0xFF00,
        vscreeninfo.xres, vscreeninfo.yres, vscreeninfo.right_margin, vscreeninfo.hsync_len,
        vscreeninfo.left_margin, vscreeninfo.lower_margin, vscreeninfo.vsync_len,
        vscreeninfo.upper_margin, vscreeninfo.pixclock/1000000);

  vscreeninfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_ALL | FB_ACTIVATE_FORCE;
  if (Sys::ioctl_(device_fd_, FBIOPUT_VSCREENINFO, &vscreeninfo) < 0) {
    IOCTL_LOGE(FBIOPUT_VSCREENINFO, device_type_);
    return kErrorHardware;
  }

  active_config_index_ = index;

  frame_rate_ = timing_mode->refresh_rate;

  // Get the display attributes for current active config index
  GetDisplayAttributes(active_config_index_, &display_attributes_);
  UpdateMixerAttributes();

  supported_s3d_modes_.clear();
  supported_s3d_modes_.push_back(kS3DModeNone);
  for (uint32_t mode = kS3DModeNone + 1; mode < kS3DModeMax; mode ++) {
    if (display_attributes_.s3d_config[(HWS3DMode)mode]) {
      supported_s3d_modes_.push_back((HWS3DMode)mode);
    }
  }

  SetS3DMode(kS3DModeNone);

  return kErrorNone;
}

DisplayError HWHDMI::GetConfigIndex(uint32_t mode, uint32_t *index) {
  // Check if the mode is valid and return corresponding index
  for (uint32_t i = 0; i < hdmi_modes_.size(); i++) {
    if (hdmi_modes_[i] == mode) {
      *index = i;
      DLOGI("Index = %d for config = %d", *index, mode);
      return kErrorNone;
    }
  }

  DLOGE("Config = %d not supported", mode);
  return kErrorNotSupported;
}

DisplayError HWHDMI::Validate(HWLayers *hw_layers) {
  HWDevice::ResetDisplayParams();
  return HWDevice::Validate(hw_layers);
}

DisplayError HWHDMI::GetHWScanInfo(HWScanInfo *scan_info) {
  if (!scan_info) {
    return kErrorParameters;
  }
  *scan_info = hw_scan_info_;
  return kErrorNone;
}

DisplayError HWHDMI::GetVideoFormat(uint32_t config_index, uint32_t *video_format) {
  if (config_index > hdmi_modes_.size()) {
    return kErrorNotSupported;
  }

  *video_format = hdmi_modes_[config_index];

  return kErrorNone;
}

DisplayError HWHDMI::GetMaxCEAFormat(uint32_t *max_cea_format) {
  *max_cea_format = HDMI_VFRMT_END;

  return kErrorNone;
}

DisplayError HWHDMI::OnMinHdcpEncryptionLevelChange(uint32_t min_enc_level) {
  DisplayError error = kErrorNone;
  int fd = -1;
  char data[kMaxStringLength] = {'\0'};

  snprintf(data, sizeof(data), "%s%d/hdcp2p2/min_level_change", fb_path_, fb_node_index_);

  fd = Sys::open_(data, O_WRONLY);
  if (fd < 0) {
    DLOGW("File '%s' could not be opened.", data);
    return kErrorHardware;
  }

  snprintf(data, sizeof(data), "%d", min_enc_level);

  ssize_t err = Sys::pwrite_(fd, data, strlen(data), 0);
  if (err <= 0) {
    DLOGE("Write failed, Error = %s", strerror(errno));
    error = kErrorHardware;
  }

  Sys::close_(fd);

  return error;
}

HWScanSupport HWHDMI::MapHWScanSupport(uint32_t value) {
  switch (value) {
  // TODO(user): Read the scan type from driver defined values instead of hardcoding
  case 0:
    return kScanNotSupported;
  case 1:
    return kScanAlwaysOverscanned;
  case 2:
    return kScanAlwaysUnderscanned;
  case 3:
    return kScanBoth;
  default:
    return kScanNotSupported;
    break;
  }
}

void HWHDMI::ReadScanInfo() {
  int scan_info_file = -1;
  ssize_t len = -1;
  char data[kPageSize] = {'\0'};

  snprintf(data, sizeof(data), "%s%d/scan_info", fb_path_, fb_node_index_);
  scan_info_file = Sys::open_(data, O_RDONLY);
  if (scan_info_file < 0) {
    DLOGW("File '%s' not found.", data);
    return;
  }

  memset(&data[0], 0, sizeof(data));
  len = Sys::pread_(scan_info_file, data, sizeof(data) - 1, 0);
  if (len <= 0) {
    Sys::close_(scan_info_file);
    DLOGW("File %s%d/scan_info is empty.", fb_path_, fb_node_index_);
    return;
  }
  data[len] = '\0';
  Sys::close_(scan_info_file);

  const uint32_t scan_info_max_count = 3;
  uint32_t scan_info_count = 0;
  char *tokens[scan_info_max_count] = { NULL };
  ParseLine(data, tokens, scan_info_max_count, &scan_info_count);
  if (scan_info_count != scan_info_max_count) {
    DLOGW("Failed to parse scan info string %s", data);
    return;
  }

  hw_scan_info_.pt_scan_support = MapHWScanSupport(UINT32(atoi(tokens[0])));
  hw_scan_info_.it_scan_support = MapHWScanSupport(UINT32(atoi(tokens[1])));
  hw_scan_info_.cea_scan_support = MapHWScanSupport(UINT32(atoi(tokens[2])));
  DLOGI("PT %d IT %d CEA %d", hw_scan_info_.pt_scan_support, hw_scan_info_.it_scan_support,
        hw_scan_info_.cea_scan_support);
}

int HWHDMI::OpenResolutionFile(int file_mode) {
  char file_path[kMaxStringLength];
  memset(file_path, 0, sizeof(file_path));
  snprintf(file_path , sizeof(file_path), "%s%d/res_info", fb_path_, fb_node_index_);

  int fd = Sys::open_(file_path, file_mode);

  if (fd < 0) {
    DLOGE("file '%s' not found : ret = %d err str: %s", file_path, fd, strerror(errno));
  }

  return fd;
}

// Method to request HDMI driver to write a new page of timing info into res_info node
void HWHDMI::RequestNewPage(uint32_t page_number) {
  char page_string[kPageSize];
  int fd = OpenResolutionFile(O_WRONLY);
  if (fd < 0) {
    return;
  }

  snprintf(page_string, sizeof(page_string), "%d", page_number);

  DLOGI_IF(kTagDriverConfig, "page=%s", page_string);

  ssize_t err = Sys::pwrite_(fd, page_string, sizeof(page_string), 0);
  if (err <= 0) {
    DLOGE("Write to res_info failed (%s)", strerror(errno));
  }

  Sys::close_(fd);
}

// Reads the contents of res_info node into a buffer if the file is not empty
bool HWHDMI::ReadResolutionFile(char *config_buffer) {
  ssize_t bytes_read = 0;
  int fd = OpenResolutionFile(O_RDONLY);
  if (fd >= 0) {
    bytes_read = Sys::pread_(fd, config_buffer, kPageSize, 0);
    Sys::close_(fd);
  }

  DLOGI_IF(kTagDriverConfig, "bytes_read = %d", bytes_read);

  return (bytes_read > 0);
}

// Populates the internal timing info structure with the timing info obtained
// from the HDMI driver
DisplayError HWHDMI::ReadTimingInfo() {
  uint32_t config_index = 0;
  uint32_t page_number = MSM_HDMI_INIT_RES_PAGE;
  uint32_t size = sizeof(msm_hdmi_mode_timing_info);

  while (true) {
    char config_buffer[kPageSize] = {0};
    msm_hdmi_mode_timing_info *info = reinterpret_cast<msm_hdmi_mode_timing_info *>(config_buffer);
    RequestNewPage(page_number);

    if (!ReadResolutionFile(config_buffer)) {
      break;
    }

    while (info->video_format && size < kPageSize && config_index < hdmi_modes_.size()) {
      supported_video_modes_[config_index] = *info;
      size += sizeof(msm_hdmi_mode_timing_info);

      DLOGI_IF(kTagDriverConfig, "Config=%d Mode %d: (%dx%d) @ %d, pixel formats %d",
               config_index,
               supported_video_modes_[config_index].video_format,
               supported_video_modes_[config_index].active_h,
               supported_video_modes_[config_index].active_v,
               supported_video_modes_[config_index].refresh_rate,
               supported_video_modes_[config_index].pixel_formats);

      info++;
      config_index++;
    }

    size = sizeof(msm_hdmi_mode_timing_info);
    // Request HDMI driver to populate res_info with more
    // timing information
    page_number++;
  }

  if (page_number == MSM_HDMI_INIT_RES_PAGE || config_index == 0) {
    DLOGE("No timing information found.");
    return kErrorHardware;
  }

  return kErrorNone;
}

bool HWHDMI::IsResolutionFilePresent() {
  bool is_file_present = false;
  int fd = OpenResolutionFile(O_RDONLY);
  if (fd >= 0) {
    is_file_present = true;
    Sys::close_(fd);
  }

  return is_file_present;
}

void HWHDMI::SetSourceProductInformation(const char *node, const char *name) {
  char property_value[kMaxStringLength];
  char sys_fs_path[kMaxStringLength];
  int hdmi_node_index = GetFBNodeIndex(kDeviceHDMI);
  if (hdmi_node_index < 0) {
    return;
  }

  ssize_t length = 0;
  bool prop_read_success = Debug::GetProperty(name, property_value);
  if (!prop_read_success) {
    return;
  }

  snprintf(sys_fs_path , sizeof(sys_fs_path), "%s%d/%s", fb_path_, hdmi_node_index, node);
  length = HWDevice::SysFsWrite(sys_fs_path, property_value,
                                static_cast<ssize_t>(strlen(property_value)));
  if (length <= 0) {
    DLOGW("Failed to write %s = %s", node, property_value);
  }
}

DisplayError HWHDMI::GetDisplayS3DSupport(uint32_t index,
                                          HWDisplayAttributes *attrib) {
  ssize_t length = -1;
  char edid_s3d_str[kPageSize] = {'\0'};
  char edid_s3d_path[kMaxStringLength] = {'\0'};
  snprintf(edid_s3d_path, sizeof(edid_s3d_path), "%s%d/edid_3d_modes", fb_path_, fb_node_index_);

  if (index > hdmi_modes_.size()) {
    return kErrorNotSupported;
  }

  attrib->s3d_config[kS3DModeNone] = 1;

  // Three level inception!
  // The string looks like 16=SSH,4=FP:TAB:SSH,5=FP:SSH,32=FP:TAB:SSH
  char *saveptr_l1, *saveptr_l2, *saveptr_l3;
  char *l1, *l2, *l3;

  int edid_s3d_node = Sys::open_(edid_s3d_path, O_RDONLY);
  if (edid_s3d_node < 0) {
    DLOGW("%s could not be opened : %s", edid_s3d_path, strerror(errno));
    return kErrorNotSupported;
  }

  length = Sys::pread_(edid_s3d_node, edid_s3d_str, sizeof(edid_s3d_str)-1, 0);
  if (length <= 0) {
    Sys::close_(edid_s3d_node);
    return kErrorNotSupported;
  }

  l1 = strtok_r(edid_s3d_str, ",", &saveptr_l1);
  while (l1 != NULL) {
    l2 = strtok_r(l1, "=", &saveptr_l2);
    if (l2 != NULL) {
      if (hdmi_modes_[index] == (uint32_t)atoi(l2)) {
          l3 = strtok_r(saveptr_l2, ":", &saveptr_l3);
          while (l3 != NULL) {
            if (strncmp("SSH", l3, strlen("SSH")) == 0) {
              attrib->s3d_config[kS3DModeLR] = 1;
              attrib->s3d_config[kS3DModeRL] = 1;
            } else if (strncmp("TAB", l3, strlen("TAB")) == 0) {
              attrib->s3d_config[kS3DModeTB] = 1;
            } else if (strncmp("FP", l3, strlen("FP")) == 0) {
              attrib->s3d_config[kS3DModeFP] = 1;
            }
            l3 = strtok_r(NULL, ":", &saveptr_l3);
          }
      }
    }
    l1 = strtok_r(NULL, ",", &saveptr_l1);
  }

  Sys::close_(edid_s3d_node);
  return kErrorNone;
}

bool HWHDMI::IsSupportedS3DMode(HWS3DMode s3d_mode) {
  for (uint32_t i = 0; i < supported_s3d_modes_.size(); i++) {
    if (supported_s3d_modes_[i] == s3d_mode) {
      return true;
    }
  }
  return false;
}

DisplayError HWHDMI::SetS3DMode(HWS3DMode s3d_mode) {
  if (!IsSupportedS3DMode(s3d_mode)) {
    DLOGW("S3D mode is not supported s3d_mode = %d", s3d_mode);
    return kErrorNotSupported;
  }

  std::map<HWS3DMode, msm_hdmi_s3d_mode>::iterator it = s3d_mode_sdm_to_mdp_.find(s3d_mode);
  if (it == s3d_mode_sdm_to_mdp_.end()) {
    return kErrorNotSupported;
  }
  msm_hdmi_s3d_mode s3d_mdp_mode = it->second;

  if (active_mdp_s3d_mode_ == s3d_mdp_mode) {
    // HDMI_S3D_SIDE_BY_SIDE is an mdp mapping for kS3DModeLR and kS3DModeRL s3d modes. So no need
    // to update the s3d_mode node. hw_panel_info needs to be updated to differentiate these two s3d
    // modes in strategy
    hw_panel_info_.s3d_mode = s3d_mode;
    return kErrorNone;
  }

  ssize_t length = -1;
  char s3d_mode_path[kMaxStringLength] = {'\0'};
  char s3d_mode_string[kMaxStringLength] = {'\0'};
  snprintf(s3d_mode_path, sizeof(s3d_mode_path), "%s%d/s3d_mode", fb_path_, fb_node_index_);

  int s3d_mode_node = Sys::open_(s3d_mode_path, O_RDWR);
  if (s3d_mode_node < 0) {
    DLOGW("%s could not be opened : %s", s3d_mode_path, strerror(errno));
    return kErrorNotSupported;
  }

  snprintf(s3d_mode_string, sizeof(s3d_mode_string), "%d", s3d_mdp_mode);
  length = Sys::pwrite_(s3d_mode_node, s3d_mode_string, sizeof(s3d_mode_string), 0);
  if (length <= 0) {
    DLOGW("Failed to write into s3d node: %s", strerror(errno));
    Sys::close_(s3d_mode_node);
    return kErrorNotSupported;
  }

  active_mdp_s3d_mode_ = s3d_mdp_mode;
  hw_panel_info_.s3d_mode = s3d_mode;
  Sys::close_(s3d_mode_node);

  DLOGI_IF(kTagDriverConfig, "s3d mode %d", hw_panel_info_.s3d_mode);
  return kErrorNone;
}

DisplayError HWHDMI::SetRefreshRate(uint32_t refresh_rate) {
  char mode_path[kMaxStringLength] = {0};
  char node_path[kMaxStringLength] = {0};
  uint32_t mode = kModeHFP;

  if (refresh_rate == frame_rate_) {
    return kErrorNone;
  }

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

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

  char dfps_mode[kMaxStringLength];
  snprintf(dfps_mode, sizeof(dfps_mode), "%d", mode);
  DLOGI_IF(kTagDriverConfig, "Setting dfps_mode  = %d", mode);
  ssize_t len = Sys::pwrite_(fd_mode, dfps_mode, strlen(dfps_mode), 0);
  if (len < 0) {
    DLOGE("Failed to enable dfps mode %d with error %s", mode, strerror(errno));
    Sys::close_(fd_mode);
    return kErrorUndefined;
  }
  Sys::close_(fd_mode);

  int fd_node = Sys::open_(node_path, O_WRONLY);
  if (fd_node < 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);
  len = Sys::pwrite_(fd_node, 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_node);
    return kErrorUndefined;
  }
  Sys::close_(fd_node);

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

  GetDisplayAttributes(active_config_index_, &display_attributes_);
  UpdateMixerAttributes();

  frame_rate_ = refresh_rate;

  return kErrorNone;
}

void HWHDMI::UpdateMixerAttributes() {
  mixer_attributes_.width = display_attributes_.x_pixels;
  mixer_attributes_.height = display_attributes_.y_pixels;
  mixer_attributes_.split_left = display_attributes_.is_device_split ?
      (display_attributes_.x_pixels / 2) : mixer_attributes_.width;
}

}  // namespace sdm

