| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * 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. |
| */ |
| //#define LOG_NDEBUG 0 |
| |
| #include "ExynosPrimaryDisplay.h" |
| |
| #include <fstream> |
| |
| #include "ExynosDevice.h" |
| #include "ExynosDisplayDrmInterface.h" |
| #include "ExynosDisplayDrmInterfaceModule.h" |
| #include "ExynosExternalDisplay.h" |
| #include "ExynosHWCDebug.h" |
| #include "ExynosHWCHelper.h" |
| |
| extern struct exynos_hwc_control exynosHWCControl; |
| |
| static const std::map<const DisplayType, const std::string> panelSysfsPath = |
| {{DisplayType::DISPLAY_PRIMARY, "/sys/devices/platform/exynos-drm/primary-panel/"}, |
| {DisplayType::DISPLAY_SECONDARY, "/sys/devices/platform/exynos-drm/secondary-panel/"}}; |
| |
| static std::string loadPanelGammaCalibration(const std::string &file) { |
| std::ifstream ifs(file); |
| |
| if (!ifs.is_open()) { |
| ALOGW("Unable to open gamma calibration '%s', error = %s", file.c_str(), strerror(errno)); |
| return {}; |
| } |
| |
| std::string raw_data, gamma; |
| char ch; |
| while (std::getline(ifs, raw_data, '\r')) { |
| gamma.append(raw_data); |
| gamma.append(1, ' '); |
| ifs.get(ch); |
| if (ch != '\n') { |
| gamma.append(1, ch); |
| } |
| } |
| ifs.close(); |
| |
| /* eliminate space character in the last byte */ |
| if (!gamma.empty()) { |
| gamma.pop_back(); |
| } |
| |
| return gamma; |
| } |
| |
| ExynosPrimaryDisplay::ExynosPrimaryDisplay(uint32_t __unused type, ExynosDevice *device) |
| : ExynosDisplay(HWC_DISPLAY_PRIMARY, device) |
| { |
| // TODO : Hard coded here |
| mNumMaxPriorityAllowed = 5; |
| |
| /* Initialization */ |
| mDisplayId = HWC_DISPLAY_PRIMARY; |
| mDisplayName = android::String8("PrimaryDisplay"); |
| |
| // Prepare multi resolution |
| // Will be exynosHWCControl.multiResoultion |
| mResolutionInfo.nNum = 1; |
| mResolutionInfo.nResolution[0].w = 1440; |
| mResolutionInfo.nResolution[0].h = 2960; |
| mResolutionInfo.nDSCYSliceSize[0] = 40; |
| mResolutionInfo.nDSCXSliceSize[0] = 1440 / 2; |
| mResolutionInfo.nPanelType[0] = PANEL_DSC; |
| mResolutionInfo.nResolution[1].w = 1080; |
| mResolutionInfo.nResolution[1].h = 2220; |
| mResolutionInfo.nDSCYSliceSize[1] = 30; |
| mResolutionInfo.nDSCXSliceSize[1] = 1080 / 2; |
| mResolutionInfo.nPanelType[1] = PANEL_DSC; |
| mResolutionInfo.nResolution[2].w = 720; |
| mResolutionInfo.nResolution[2].h = 1480; |
| mResolutionInfo.nDSCYSliceSize[2] = 74; |
| mResolutionInfo.nDSCXSliceSize[2] = 720; |
| mResolutionInfo.nPanelType[2] = PANEL_LEGACY; |
| |
| #if defined(MAX_BRIGHTNESS_NODE_BASE) && defined(BRIGHTNESS_NODE_BASE) |
| FILE *maxBrightnessFd = fopen(MAX_BRIGHTNESS_NODE_BASE, "r"); |
| ALOGI("Trying %s open for get max brightness", MAX_BRIGHTNESS_NODE_BASE); |
| |
| if (maxBrightnessFd != NULL) { |
| char val[MAX_BRIGHTNESS_LEN] = {0}; |
| size_t size = fread(val, 1, MAX_BRIGHTNESS_LEN, maxBrightnessFd); |
| if (size) { |
| mMaxBrightness = atoi(val); |
| ALOGI("Max brightness : %d", mMaxBrightness); |
| |
| mBrightnessFd = fopen(BRIGHTNESS_NODE_BASE, "w+"); |
| ALOGI("Trying %s open for brightness control", BRIGHTNESS_NODE_BASE); |
| |
| if (mBrightnessFd == NULL) |
| ALOGE("%s open failed! %s", BRIGHTNESS_NODE_BASE, strerror(errno)); |
| } else { |
| ALOGE("Max brightness read failed (size: %zu)", size); |
| if (ferror(maxBrightnessFd)) { |
| ALOGE("An error occurred"); |
| clearerr(maxBrightnessFd); |
| } |
| } |
| fclose(maxBrightnessFd); |
| } else { |
| ALOGE("Brightness node is not opened"); |
| } |
| #endif |
| } |
| |
| ExynosPrimaryDisplay::~ExynosPrimaryDisplay() |
| { |
| if (mBrightnessFd != NULL) { |
| fclose(mBrightnessFd); |
| mBrightnessFd = NULL; |
| } |
| } |
| |
| void ExynosPrimaryDisplay::setDDIScalerEnable(int width, int height) { |
| |
| if (exynosHWCControl.setDDIScaler == false) return; |
| |
| ALOGI("DDISCALER Info : setDDIScalerEnable(w=%d,h=%d)", width, height); |
| mNewScaledWidth = width; |
| mNewScaledHeight = height; |
| mXres = width; |
| mYres = height; |
| } |
| |
| int ExynosPrimaryDisplay::getDDIScalerMode(int width, int height) { |
| |
| if (exynosHWCControl.setDDIScaler == false) return 1; |
| |
| // Check if panel support support resolution or not. |
| for (uint32_t i=0; i < mResolutionInfo.nNum; i++) { |
| if (mResolutionInfo.nResolution[i].w * mResolutionInfo.nResolution[i].h == |
| static_cast<uint32_t>(width * height)) |
| return i + 1; |
| } |
| |
| return 1; // WQHD |
| } |
| |
| int32_t ExynosPrimaryDisplay::getActiveConfigInternal(hwc2_config_t *outConfig) { |
| if (outConfig && mPendActiveConfig != UINT_MAX) { |
| *outConfig = mPendActiveConfig; |
| return HWC2_ERROR_NONE; |
| } |
| return ExynosDisplay::getActiveConfigInternal(outConfig); |
| } |
| |
| int32_t ExynosPrimaryDisplay::setActiveConfigInternal(hwc2_config_t config) { |
| hwc2_config_t cur_config; |
| |
| getActiveConfigInternal(&cur_config); |
| if (cur_config == config) { |
| ALOGI("%s:: Same display config is set", __func__); |
| return HWC2_ERROR_NONE; |
| } |
| if (mPowerModeState != HWC2_POWER_MODE_ON) { |
| mPendActiveConfig = config; |
| return HWC2_ERROR_NONE; |
| } |
| return ExynosDisplay::setActiveConfigInternal(config); |
| } |
| |
| int32_t ExynosPrimaryDisplay::applyPendingConfig() { |
| hwc2_config_t config; |
| |
| if (mPendActiveConfig != UINT_MAX) { |
| config = mPendActiveConfig; |
| mPendActiveConfig = UINT_MAX; |
| } else { |
| getActiveConfigInternal(&config); |
| } |
| return ExynosDisplay::setActiveConfigInternal(config); |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerOn() { |
| ATRACE_CALL(); |
| |
| int ret = applyPendingConfig(); |
| |
| if (mPowerModeState == HWC2_POWER_MODE_OFF) { |
| // check the dynamic recomposition thread by following display |
| mDevice->checkDynamicRecompositionThread(); |
| if (ret) { |
| mDisplayInterface->setPowerMode(HWC2_POWER_MODE_ON); |
| } |
| setGeometryChanged(GEOMETRY_DISPLAY_POWER_ON); |
| } |
| |
| mPowerModeState = HWC2_POWER_MODE_ON; |
| |
| if (mFirstPowerOn) { |
| firstPowerOn(); |
| } |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerOff() { |
| ATRACE_CALL(); |
| |
| clearDisplay(); |
| |
| // check the dynamic recomposition thread by following display |
| mDevice->checkDynamicRecompositionThread(); |
| |
| mDisplayInterface->setPowerMode(HWC2_POWER_MODE_OFF); |
| mPowerModeState = HWC2_POWER_MODE_OFF; |
| |
| /* It should be called from validate() when the screen is on */ |
| mSkipFrame = true; |
| setGeometryChanged(GEOMETRY_DISPLAY_POWER_OFF); |
| if ((mRenderingState >= RENDERING_STATE_VALIDATED) && |
| (mRenderingState < RENDERING_STATE_PRESENTED)) |
| closeFencesForSkipFrame(RENDERING_STATE_VALIDATED); |
| mRenderingState = RENDERING_STATE_NONE; |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerDoze() { |
| ATRACE_CALL(); |
| |
| if (!mDisplayInterface->isDozeModeAvailable()) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| if (mDisplayInterface->setLowPowerMode()) { |
| ALOGI("Not support LP mode."); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| mPowerModeState = HWC2_POWER_MODE_DOZE; |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerMode(int32_t mode) { |
| Mutex::Autolock lock(mDisplayMutex); |
| |
| if (mode == static_cast<int32_t>(ext_hwc2_power_mode_t::PAUSE)) { |
| mode = HWC2_POWER_MODE_OFF; |
| mPauseDisplay = true; |
| } else if (mode == static_cast<int32_t>(ext_hwc2_power_mode_t::RESUME)) { |
| mode = HWC2_POWER_MODE_ON; |
| mPauseDisplay = false; |
| } |
| |
| if (mode == static_cast<int32_t>(mPowerModeState)) { |
| ALOGI("Skip power mode transition due to the same power state."); |
| return HWC2_ERROR_NONE; |
| } |
| |
| int fb_blank = (mode != HWC2_POWER_MODE_OFF) ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; |
| ALOGD("%s:: FBIOBLANK mode(%d), blank(%d)", __func__, mode, fb_blank); |
| |
| if (fb_blank == FB_BLANK_POWERDOWN) |
| mDREnable = false; |
| else |
| mDREnable = mDRDefault; |
| |
| switch (mode) { |
| case HWC2_POWER_MODE_DOZE_SUSPEND: |
| return HWC2_ERROR_UNSUPPORTED; |
| case HWC2_POWER_MODE_DOZE: |
| return setPowerDoze(); |
| case HWC2_POWER_MODE_OFF: |
| setPowerOff(); |
| break; |
| case HWC2_POWER_MODE_ON: |
| setPowerOn(); |
| break; |
| default: |
| return HWC2_ERROR_BAD_PARAMETER; |
| } |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| void ExynosPrimaryDisplay::firstPowerOn() { |
| SetCurrentPanelGammaSource(DisplayType::DISPLAY_PRIMARY, PanelGammaSource::GAMMA_CALIBRATION); |
| mFirstPowerOn = false; |
| } |
| |
| bool ExynosPrimaryDisplay::getHDRException(ExynosLayer* __unused layer) |
| { |
| return false; |
| } |
| |
| void ExynosPrimaryDisplay::initDisplayInterface(uint32_t interfaceType) |
| { |
| if (interfaceType == INTERFACE_TYPE_DRM) |
| mDisplayInterface = std::make_unique<ExynosPrimaryDisplayDrmInterfaceModule>((ExynosDisplay *)this); |
| else |
| LOG_ALWAYS_FATAL("%s::Unknown interface type(%d)", |
| __func__, interfaceType); |
| mDisplayInterface->init(this); |
| } |
| |
| std::string ExynosPrimaryDisplay::getPanelSysfsPath(const DisplayType &type) { |
| if ((type < DisplayType::DISPLAY_PRIMARY) || (type >= DisplayType::DISPLAY_MAX)) { |
| ALOGE("Invalid display panel type %d", type); |
| return {}; |
| } |
| |
| auto iter = panelSysfsPath.find(type); |
| if (iter == panelSysfsPath.end()) { |
| return {}; |
| } |
| |
| return iter->second; |
| } |
| |
| int32_t ExynosPrimaryDisplay::SetCurrentPanelGammaSource(const DisplayType type, |
| const PanelGammaSource &source) { |
| std::string &&panel_sysfs_path = getPanelSysfsPath(type); |
| if (panel_sysfs_path.empty()) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| std::ifstream ifs; |
| std::string &&path = panel_sysfs_path + "panel_name"; |
| ifs.open(path, std::ifstream::in); |
| if (!ifs.is_open()) { |
| ALOGW("Unable to access panel name path '%s' (%s)", path.c_str(), strerror(errno)); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| std::string panel_name; |
| std::getline(ifs, panel_name); |
| ifs.close(); |
| |
| path = panel_sysfs_path + "serial_number"; |
| ifs.open(path, std::ifstream::in); |
| if (!ifs.is_open()) { |
| ALOGW("Unable to access panel id path '%s' (%s)", path.c_str(), strerror(errno)); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| std::string panel_id; |
| std::getline(ifs, panel_id); |
| ifs.close(); |
| |
| std::string gamma_node = panel_sysfs_path + "gamma"; |
| if (access(gamma_node.c_str(), W_OK)) { |
| ALOGW("Unable to access panel gamma calibration node '%s' (%s)", gamma_node.c_str(), |
| strerror(errno)); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| std::string &&gamma_data = "default"; |
| if (source == PanelGammaSource::GAMMA_CALIBRATION) { |
| std::string gamma_cal_file(kDisplayCalFilePath); |
| gamma_cal_file.append(kPanelGammaCalFilePrefix) |
| .append(1, '_') |
| .append(panel_name) |
| .append(1, '_') |
| .append(panel_id) |
| .append(".cal"); |
| if (access(gamma_cal_file.c_str(), R_OK)) { |
| ALOGI("Fail to access `%s` (%s), try golden gamma calibration", gamma_cal_file.c_str(), |
| strerror(errno)); |
| gamma_cal_file = kDisplayCalFilePath; |
| gamma_cal_file.append(kPanelGammaCalFilePrefix) |
| .append(1, '_') |
| .append(panel_name) |
| .append(".cal"); |
| } |
| gamma_data = loadPanelGammaCalibration(gamma_cal_file); |
| } |
| |
| if (gamma_data.empty()) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| std::ofstream ofs(gamma_node); |
| if (!ofs.is_open()) { |
| ALOGW("Unable to open gamma node '%s', error = %s", gamma_node.c_str(), strerror(errno)); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| ofs.write(gamma_data.c_str(), gamma_data.size()); |
| ofs.close(); |
| |
| currentPanelGammaSource = source; |
| return HWC2_ERROR_NONE; |
| } |