| /* |
| * 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 |
| |
| #define ATRACE_TAG (ATRACE_TAG_GRAPHICS | ATRACE_TAG_HAL) |
| |
| #include "ExynosPrimaryDisplay.h" |
| |
| #include <linux/fb.h> |
| #include <poll.h> |
| |
| #include <chrono> |
| #include <fstream> |
| |
| #include "BrightnessController.h" |
| #include "ExynosDevice.h" |
| #include "ExynosDisplayDrmInterface.h" |
| #include "ExynosDisplayDrmInterfaceModule.h" |
| #include "ExynosExternalDisplay.h" |
| #include "ExynosHWCDebug.h" |
| #include "ExynosHWCHelper.h" |
| #include "ExynosLayer.h" |
| |
| extern struct exynos_hwc_control exynosHWCControl; |
| |
| using namespace SOC_VERSION; |
| constexpr auto nsecsPerSec = std::chrono::nanoseconds(1s).count(); |
| constexpr auto nsecsPerMs = std::chrono::nanoseconds(1ms).count(); |
| |
| 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 constexpr const char *PROPERTY_BOOT_MODE = "persist.vendor.display.primary.boot_config"; |
| |
| 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 index, ExynosDevice *device) |
| : ExynosDisplay(index, device), |
| mMinIdleRefreshRate(0), |
| mRefreshRateDelayNanos(0), |
| mLastRefreshRateAppliedNanos(0), |
| mAppliedActiveConfig(0), |
| mDisplayIdleTimerEnabled(false), |
| mDisplayNeedHandleIdleExit(false) { |
| // TODO : Hard coded here |
| mNumMaxPriorityAllowed = 5; |
| |
| /* Initialization */ |
| mType = HWC_DISPLAY_PRIMARY; |
| mIndex = index; |
| mDisplayId = getDisplayId(mType, mIndex); |
| mFramesToReachLhbmPeakBrightness = |
| property_get_int32("vendor.primarydisplay.lhbm.frames_to_reach_peak_brightness", 3); |
| |
| // 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; |
| |
| mEarlyWakeupDispFd = fopen(EARLY_WAKUP_NODE_0_BASE, "w"); |
| if (mEarlyWakeupDispFd == nullptr) |
| ALOGE("open %s failed! %s", EARLY_WAKUP_NODE_0_BASE, strerror(errno)); |
| mBrightnessController = std::make_unique<BrightnessController>( |
| mIndex, [this]() { mDevice->onRefresh(); }, |
| [this]() { updatePresentColorConversionInfo(); }); |
| } |
| |
| ExynosPrimaryDisplay::~ExynosPrimaryDisplay() |
| { |
| if (mEarlyWakeupDispFd) { |
| fclose(mEarlyWakeupDispFd); |
| mEarlyWakeupDispFd = nullptr; |
| } |
| |
| if (mDisplayNeedHandleIdleExitOfs.is_open()) { |
| mDisplayNeedHandleIdleExitOfs.close(); |
| } |
| } |
| |
| 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::doDisplayConfigInternal(hwc2_config_t config) { |
| if (mPowerModeState != HWC2_POWER_MODE_ON) { |
| mPendActiveConfig = config; |
| mConfigRequestState = hwc_request_state_t::SET_CONFIG_STATE_NONE; |
| DISPLAY_LOGI("%s:: Pending desired Config: %d", __func__, config); |
| return NO_ERROR; |
| } |
| return ExynosDisplay::doDisplayConfigInternal(config); |
| } |
| |
| 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, bool force) { |
| 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, force); |
| } |
| |
| int32_t ExynosPrimaryDisplay::applyPendingConfig() { |
| hwc2_config_t config; |
| |
| if (mPendActiveConfig != UINT_MAX) { |
| config = mPendActiveConfig; |
| mPendActiveConfig = UINT_MAX; |
| } else { |
| getActiveConfigInternal(&config); |
| } |
| |
| return ExynosDisplay::setActiveConfigInternal(config, true); |
| } |
| |
| int32_t ExynosPrimaryDisplay::setBootDisplayConfig(int32_t config) { |
| auto hwcConfig = static_cast<hwc2_config_t>(config); |
| |
| const auto &it = mDisplayConfigs.find(hwcConfig); |
| if (it == mDisplayConfigs.end()) { |
| DISPLAY_LOGE("%s: invalid config %d", __func__, config); |
| return HWC2_ERROR_BAD_CONFIG; |
| } |
| |
| const auto &mode = it->second; |
| if (mode.vsyncPeriod == 0) |
| return HWC2_ERROR_BAD_CONFIG; |
| |
| int refreshRate = round(nsecsPerSec / mode.vsyncPeriod * 0.1f) * 10; |
| char modeStr[PROPERTY_VALUE_MAX]; |
| int ret = snprintf(modeStr, sizeof(modeStr), "%dx%d@%d", |
| mode.width, mode.height, refreshRate); |
| if (ret <= 0) |
| return HWC2_ERROR_BAD_CONFIG; |
| |
| ALOGD("%s: mode=%s (%d) vsyncPeriod=%d", __func__, modeStr, config, |
| mode.vsyncPeriod); |
| ret = property_set(PROPERTY_BOOT_MODE, modeStr); |
| |
| return !ret ? HWC2_ERROR_NONE : HWC2_ERROR_BAD_CONFIG; |
| } |
| |
| int32_t ExynosPrimaryDisplay::clearBootDisplayConfig() { |
| auto ret = property_set(PROPERTY_BOOT_MODE, nullptr); |
| |
| ALOGD("%s: clearing boot mode", __func__); |
| return !ret ? HWC2_ERROR_NONE : HWC2_ERROR_BAD_CONFIG; |
| } |
| |
| int32_t ExynosPrimaryDisplay::getPreferredDisplayConfigInternal(int32_t *outConfig) { |
| char modeStr[PROPERTY_VALUE_MAX]; |
| auto ret = property_get(PROPERTY_BOOT_MODE, modeStr, ""); |
| |
| if (ret <= 0) { |
| return mDisplayInterface->getDefaultModeId(outConfig); |
| } |
| |
| int width, height; |
| int fps = 0; |
| |
| ret = sscanf(modeStr, "%dx%d@%d", &width, &height, &fps); |
| if ((ret < 3) || !fps) { |
| ALOGD("%s: unable to find boot config for mode: %s", __func__, modeStr); |
| return HWC2_ERROR_BAD_CONFIG; |
| } |
| |
| const auto vsyncPeriod = nsecsPerSec / fps; |
| |
| for (auto const& [config, mode] : mDisplayConfigs) { |
| long delta = abs(vsyncPeriod - mode.vsyncPeriod); |
| if ((width == mode.width) && (height == mode.height) && |
| (delta < nsecsPerMs)) { |
| ALOGD("%s: found preferred display config for mode: %s=%d", |
| __func__, modeStr, config); |
| *outConfig = config; |
| return HWC2_ERROR_NONE; |
| } |
| } |
| return HWC2_ERROR_BAD_CONFIG; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerOn() { |
| ATRACE_CALL(); |
| updateAppliedActiveConfig(0, 0); |
| 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(true); |
| |
| // 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; |
| |
| // in the case user turns off screen when LHBM is on |
| // TODO: b/236433238 considering a lock for mLhbmOn state |
| mLhbmOn = false; |
| return HWC2_ERROR_NONE; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setPowerDoze(hwc2_power_mode_t mode) { |
| ATRACE_CALL(); |
| |
| if (!mDisplayInterface->isDozeModeAvailable()) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| if ((mPowerModeState == HWC2_POWER_MODE_OFF) || (mPowerModeState == HWC2_POWER_MODE_ON)) { |
| if (mDisplayInterface->setLowPowerMode()) { |
| ALOGI("Not support LP mode."); |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| } |
| |
| mPowerModeState = mode; |
| |
| ExynosDisplay::updateRefreshRateHint(); |
| |
| 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: |
| case HWC2_POWER_MODE_DOZE: |
| return setPowerDoze(static_cast<hwc2_power_mode_t>(mode)); |
| case HWC2_POWER_MODE_OFF: |
| setPowerOff(); |
| break; |
| case HWC2_POWER_MODE_ON: |
| setPowerOn(); |
| break; |
| default: |
| return HWC2_ERROR_BAD_PARAMETER; |
| } |
| |
| ExynosDisplay::updateRefreshRateHint(); |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| void ExynosPrimaryDisplay::firstPowerOn() { |
| SetCurrentPanelGammaSource(DisplayType::DISPLAY_PRIMARY, PanelGammaSource::GAMMA_CALIBRATION); |
| mFirstPowerOn = false; |
| getDisplayIdleTimerEnabled(mDisplayIdleTimerEnabled); |
| initDisplayHandleIdleExit(); |
| } |
| |
| 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); |
| |
| mDpuData.init(mMaxWindowNum, mDevice->getSpecialPlaneNum(mDisplayId)); |
| mLastDpuData.init(mMaxWindowNum, mDevice->getSpecialPlaneNum(mDisplayId)); |
| ALOGI("window configs size(%zu) rcd configs zie(%zu)", mDpuData.configs.size(), |
| mDpuData.rcdConfigs.size()); |
| } |
| |
| 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; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setLhbmState(bool enabled) { |
| // NOTE: mLhbmOn could be set to false at any time by setPowerOff in another |
| // thread. Make sure no side effect if that happens. Or add lock if we have |
| // to when new code is added. |
| ATRACE_CALL(); |
| if (enabled) { |
| ATRACE_NAME("wait for peak refresh rate"); |
| for (int32_t i = 0; i <= kLhbmWaitForPeakRefreshRate; i++) { |
| if (!isCurrentPeakRefreshRate()) { |
| if (i == kLhbmWaitForPeakRefreshRate) { |
| ALOGW("setLhbmState(on) wait for peak refresh rate timeout !"); |
| return TIMED_OUT; |
| } |
| usleep(mVsyncPeriod / 1000 + 1); |
| } else { |
| ALOGI_IF(i, "waited %d vsync to reach peak refresh rate", i); |
| break; |
| } |
| } |
| } |
| |
| if (enabled) { |
| setLHBMRefreshRateThrottle(kLhbmRefreshRateThrottleMs); |
| } |
| |
| requestLhbm(enabled); |
| constexpr uint32_t kSysfsCheckTimeoutMs = 500; |
| ALOGI("setLhbmState =%d", enabled); |
| bool succeed = mBrightnessController->checkSysfsStatus( |
| BrightnessController::kLocalHbmModeFileNode, |
| std::to_string(enabled ? 1 : 0), |
| ms2ns(kSysfsCheckTimeoutMs)); |
| if (!succeed) { |
| ALOGE("failed to update lhbm mode"); |
| if (enabled) { |
| setLHBMRefreshRateThrottle(0); |
| } |
| return -ENODEV; |
| } |
| |
| if (!enabled) { |
| setLHBMRefreshRateThrottle(0); |
| } |
| |
| { |
| // lhbm takes effect at next vblank |
| ATRACE_NAME("lhbm_wait_apply"); |
| if (mDisplayInterface->waitVBlank()) { |
| ALOGE("%s failed to wait vblank", __func__); |
| return -ENODEV; |
| } |
| } |
| |
| if (enabled) { |
| for (int32_t i = mFramesToReachLhbmPeakBrightness; i > 0; i--) { |
| ATRACE_NAME("lhbm_wait_peak_brightness"); |
| mDevice->onRefresh(); |
| if (mDisplayInterface->waitVBlank()) { |
| ALOGE("%s failed to wait vblank, %d", __func__, i); |
| return -ENODEV; |
| } |
| } |
| } |
| |
| mLhbmOn = enabled; |
| if (mPowerModeState == HWC2_POWER_MODE_OFF && mLhbmOn) { |
| mLhbmOn = false; |
| ALOGE("%s power off during request lhbm on", __func__); |
| return -EINVAL; |
| } |
| return NO_ERROR; |
| } |
| |
| bool ExynosPrimaryDisplay::getLhbmState() { |
| return mLhbmOn; |
| } |
| |
| void ExynosPrimaryDisplay::setLHBMRefreshRateThrottle(const uint32_t delayMs) { |
| ATRACE_CALL(); |
| |
| if (delayMs) { |
| // make new throttle take effect |
| mLastRefreshRateAppliedNanos = systemTime(SYSTEM_TIME_MONOTONIC); |
| } |
| |
| setRefreshRateThrottleNanos(std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::milliseconds(delayMs)) |
| .count(), |
| DispIdleTimerRequester::LHBM); |
| } |
| |
| void ExynosPrimaryDisplay::setEarlyWakeupDisplay() { |
| if (mEarlyWakeupDispFd) { |
| writeFileNode(mEarlyWakeupDispFd, 1); |
| } |
| } |
| |
| void ExynosPrimaryDisplay::setExpectedPresentTime(uint64_t timestamp) { |
| mExpectedPresentTime.store(timestamp); |
| } |
| |
| uint64_t ExynosPrimaryDisplay::getPendingExpectedPresentTime() { |
| if (mExpectedPresentTime.is_dirty()) { |
| return mExpectedPresentTime.get(); |
| } |
| |
| return 0; |
| } |
| |
| void ExynosPrimaryDisplay::applyExpectedPresentTime() { |
| mExpectedPresentTime.clear_dirty(); |
| } |
| |
| int32_t ExynosPrimaryDisplay::setDisplayIdleTimer(const int32_t timeoutMs) { |
| bool support = false; |
| if (getDisplayIdleTimerSupport(support) || support == false) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| if (timeoutMs < 0) { |
| return HWC2_ERROR_BAD_PARAMETER; |
| } |
| |
| if (timeoutMs > 0) { |
| setRefreshRateThrottleNanos(std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::milliseconds(timeoutMs)) |
| .count(), |
| DispIdleTimerRequester::SF); |
| } |
| |
| bool enabled = (timeoutMs > 0); |
| if (enabled != mDisplayIdleTimerEnabled) { |
| if (setDisplayIdleTimerEnabled(enabled) == NO_ERROR) { |
| mDisplayIdleTimerEnabled = enabled; |
| } |
| } |
| |
| return HWC2_ERROR_NONE; |
| } |
| |
| int32_t ExynosPrimaryDisplay::getDisplayIdleTimerEnabled(bool &enabled) { |
| bool support = false; |
| if (getDisplayIdleTimerSupport(support) || support == false) { |
| return HWC2_ERROR_UNSUPPORTED; |
| } |
| |
| const std::string path = getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "panel_idle"; |
| std::ifstream ifs(path); |
| if (!ifs.is_open()) { |
| ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno)); |
| return errno; |
| } else { |
| std::string panel_idle; |
| std::getline(ifs, panel_idle); |
| ifs.close(); |
| enabled = (panel_idle == "1"); |
| ALOGI("%s() get panel_idle(%d) from the sysfs node", __func__, enabled); |
| } |
| return NO_ERROR; |
| } |
| |
| int32_t ExynosPrimaryDisplay::setDisplayIdleTimerEnabled(const bool enabled) { |
| const std::string path = getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "panel_idle"; |
| std::ofstream ofs(path); |
| if (!ofs.is_open()) { |
| ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno)); |
| return errno; |
| } else { |
| ofs << enabled; |
| ofs.close(); |
| ALOGI("%s() writes panel_idle(%d) to the sysfs node", __func__, enabled); |
| } |
| return NO_ERROR; |
| } |
| |
| void ExynosPrimaryDisplay::initDisplayHandleIdleExit() { |
| if (bool support; getDisplayIdleTimerSupport(support) || support == false) { |
| return; |
| } |
| |
| const std::string path = |
| getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "panel_need_handle_idle_exit"; |
| mDisplayNeedHandleIdleExitOfs.open(path, std::ofstream::out); |
| if (!mDisplayNeedHandleIdleExitOfs.is_open()) { |
| ALOGI("%s() '%s' doesn't exist(%s)", __func__, path.c_str(), strerror(errno)); |
| } |
| |
| setDisplayNeedHandleIdleExit(false, true); |
| } |
| |
| void ExynosPrimaryDisplay::setDisplayNeedHandleIdleExit(const bool needed, const bool force) { |
| if (!mDisplayNeedHandleIdleExitOfs.is_open()) { |
| return; |
| } |
| |
| if (needed == mDisplayNeedHandleIdleExit && !force) { |
| return; |
| } |
| |
| mDisplayNeedHandleIdleExitOfs << needed; |
| if (mDisplayNeedHandleIdleExitOfs.fail()) { |
| ALOGW("%s() failed to write panel_need_handle_idle_exit(%d) to sysfs node %s", __func__, |
| needed, strerror(errno)); |
| return; |
| } |
| |
| mDisplayNeedHandleIdleExitOfs.flush(); |
| if (mDisplayNeedHandleIdleExitOfs.fail()) { |
| ALOGW("%s() failed to flush panel_need_handle_idle_exit(%d) to sysfs node %s", __func__, |
| needed, strerror(errno)); |
| return; |
| } |
| |
| ALOGI("%s() writes panel_need_handle_idle_exit(%d) to sysfs node", __func__, needed); |
| mDisplayNeedHandleIdleExit = needed; |
| } |
| |
| void ExynosPrimaryDisplay::handleDisplayIdleEnter(const uint32_t idleTeRefreshRate) { |
| Mutex::Autolock lock(mDisplayMutex); |
| uint32_t btsRefreshRate = getBtsRefreshRate(); |
| if (idleTeRefreshRate <= btsRefreshRate) { |
| return; |
| } |
| |
| bool needed = false; |
| for (size_t i = 0; i < mLayers.size(); i++) { |
| if (mLayers[i]->mOtfMPP && mLayers[i]->mM2mMPP == nullptr && |
| !mLayers[i]->checkDownscaleCap(idleTeRefreshRate)) { |
| needed = true; |
| break; |
| } |
| } |
| |
| setDisplayNeedHandleIdleExit(needed, false); |
| } |
| |
| int ExynosPrimaryDisplay::setMinIdleRefreshRate(const int fps) { |
| mMinIdleRefreshRate = fps; |
| |
| const std::string path = getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "min_vrefresh"; |
| std::ofstream ofs(path); |
| if (!ofs.is_open()) { |
| ALOGW("Unable to open node '%s', error = %s", path.c_str(), strerror(errno)); |
| return errno; |
| } else { |
| ofs << mMinIdleRefreshRate; |
| ofs.close(); |
| ALOGI("ExynosPrimaryDisplay::%s() writes min_vrefresh(%d) to the sysfs node", __func__, |
| fps); |
| } |
| return NO_ERROR; |
| } |
| |
| int ExynosPrimaryDisplay::setRefreshRateThrottleNanos(const int64_t delayNanos, |
| const DispIdleTimerRequester requester) { |
| ALOGI("%s() requester(%u) set delay to %" PRId64 "ns", __func__, toUnderlying(requester), |
| delayNanos); |
| if (delayNanos < 0) { |
| ALOGW("%s() set invalid delay(%" PRId64 ")", __func__, delayNanos); |
| return BAD_VALUE; |
| } |
| |
| std::lock_guard<std::mutex> lock(mIdleRefreshRateThrottleMutex); |
| |
| int64_t maxDelayNanos = 0; |
| mDisplayIdleTimerNanos[toUnderlying(requester)] = delayNanos; |
| for (uint32_t i = 0; i < toUnderlying(DispIdleTimerRequester::MAX); i++) { |
| if (mDisplayIdleTimerNanos[i] > maxDelayNanos) { |
| maxDelayNanos = mDisplayIdleTimerNanos[i]; |
| } |
| } |
| |
| if (mRefreshRateDelayNanos == maxDelayNanos) { |
| return NO_ERROR; |
| } |
| |
| mRefreshRateDelayNanos = maxDelayNanos; |
| |
| const int32_t refreshRateDelayMs = std::chrono::duration_cast<std::chrono::milliseconds>( |
| std::chrono::nanoseconds(mRefreshRateDelayNanos)) |
| .count(); |
| const std::string path = getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "idle_delay_ms"; |
| std::ofstream ofs(path); |
| if (!ofs.is_open()) { |
| ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno)); |
| return errno; |
| } else { |
| ofs << refreshRateDelayMs; |
| ALOGI("%s() writes idle_delay_ms(%d) to the sysfs node (0x%x)", __func__, |
| refreshRateDelayMs, ofs.rdstate()); |
| ofs.close(); |
| } |
| |
| return NO_ERROR; |
| } |
| |
| void ExynosPrimaryDisplay::dump(String8 &result) { |
| ExynosDisplay::dump(result); |
| result.appendFormat("Display idle timer: %s\n", |
| (mDisplayIdleTimerEnabled) ? "enabled" : "disabled"); |
| result.appendFormat("Min idle refresh rate: %d\n", mMinIdleRefreshRate); |
| result.appendFormat("Refresh rate delay: %" PRId64 " ns\n", mRefreshRateDelayNanos); |
| for (uint32_t i = 0; i < toUnderlying(DispIdleTimerRequester::MAX); i++) { |
| result.appendFormat("\t[%u] set to %" PRId64 " ns\n", i, mDisplayIdleTimerNanos[i]); |
| } |
| result.appendFormat("\n"); |
| } |
| |
| void ExynosPrimaryDisplay::calculateTimeline( |
| hwc2_config_t config, hwc_vsync_period_change_constraints_t *vsyncPeriodChangeConstraints, |
| hwc_vsync_period_change_timeline_t *outTimeline) { |
| int64_t desiredUpdateTime = vsyncPeriodChangeConstraints->desiredTimeNanos; |
| const int64_t origDesiredUpdateTime = desiredUpdateTime; |
| const int64_t threshold = mRefreshRateDelayNanos; |
| int64_t lastUpdateDelta = 0; |
| int64_t actualChangeTime = 0; |
| bool isDelayed = false; |
| |
| /* actualChangeTime includes transient duration */ |
| mDisplayInterface->getVsyncAppliedTime(config, &actualChangeTime); |
| |
| outTimeline->refreshRequired = true; |
| |
| /* when refresh rate is from high to low */ |
| if (threshold != 0 && mLastRefreshRateAppliedNanos != 0 && |
| mDisplayConfigs[mActiveConfig].vsyncPeriod < mDisplayConfigs[config].vsyncPeriod) { |
| lastUpdateDelta = desiredUpdateTime - mLastRefreshRateAppliedNanos; |
| if (lastUpdateDelta < threshold) { |
| /* in this case, the active config change needs to be delayed */ |
| isDelayed = true; |
| desiredUpdateTime += threshold - lastUpdateDelta; |
| } |
| } |
| mVsyncPeriodChangeConstraints.desiredTimeNanos = desiredUpdateTime; |
| |
| getConfigAppliedTime(mVsyncPeriodChangeConstraints.desiredTimeNanos, actualChangeTime, |
| outTimeline->newVsyncAppliedTimeNanos, outTimeline->refreshTimeNanos); |
| |
| if (isDelayed) { |
| DISPLAY_LOGD(eDebugDisplayConfig, |
| "requested config : %d(%d)->%d(%d) is delayed! " |
| "delta %" PRId64 ", delay %" PRId64 ", threshold %" PRId64 ", " |
| "desired %" PRId64 "->%" PRId64 ", newVsyncAppliedTimeNanos : %" PRId64 |
| ", refreshTimeNanos:%" PRId64, |
| mActiveConfig, mDisplayConfigs[mActiveConfig].vsyncPeriod, config, |
| mDisplayConfigs[config].vsyncPeriod, lastUpdateDelta, |
| threshold - lastUpdateDelta, threshold, origDesiredUpdateTime, |
| mVsyncPeriodChangeConstraints.desiredTimeNanos, |
| outTimeline->newVsyncAppliedTimeNanos, outTimeline->refreshTimeNanos); |
| } else { |
| DISPLAY_LOGD(eDebugDisplayConfig, |
| "requested config : %d(%d)->%d(%d), " |
| "lastUpdateDelta %" PRId64 ", threshold %" PRId64 ", " |
| "desired %" PRId64 ", newVsyncAppliedTimeNanos : %" PRId64 "", |
| mActiveConfig, mDisplayConfigs[mActiveConfig].vsyncPeriod, config, |
| mDisplayConfigs[config].vsyncPeriod, lastUpdateDelta, threshold, |
| mVsyncPeriodChangeConstraints.desiredTimeNanos, |
| outTimeline->newVsyncAppliedTimeNanos); |
| } |
| } |
| |
| void ExynosPrimaryDisplay::updateAppliedActiveConfig(const hwc2_config_t newConfig, |
| const int64_t ts) { |
| if (mAppliedActiveConfig == 0 || |
| getDisplayVsyncPeriodFromConfig(mAppliedActiveConfig) != |
| getDisplayVsyncPeriodFromConfig(newConfig)) { |
| DISPLAY_LOGD(eDebugDisplayConfig, |
| "%s mAppliedActiveConfig(%d->%d), mLastRefreshRateAppliedNanos(%" PRIu64 |
| " -> %" PRIu64 ")", |
| __func__, mAppliedActiveConfig, newConfig, mLastRefreshRateAppliedNanos, ts); |
| mLastRefreshRateAppliedNanos = ts; |
| } |
| |
| mAppliedActiveConfig = newConfig; |
| } |
| |
| void ExynosPrimaryDisplay::checkBtsReassignResource(const uint32_t vsyncPeriod, |
| const uint32_t btsVsyncPeriod) { |
| ATRACE_CALL(); |
| uint32_t refreshRate = static_cast<uint32_t>(round(nsecsPerSec / vsyncPeriod * 0.1f) * 10); |
| |
| if (vsyncPeriod < btsVsyncPeriod) { |
| for (size_t i = 0; i < mLayers.size(); i++) { |
| if (mLayers[i]->mOtfMPP && mLayers[i]->mM2mMPP == nullptr && |
| !mLayers[i]->checkDownscaleCap(refreshRate)) { |
| mLayers[i]->setGeometryChanged(GEOMETRY_DEVICE_CONFIG_CHANGED); |
| break; |
| } |
| } |
| } else if (vsyncPeriod > btsVsyncPeriod) { |
| for (size_t i = 0; i < mLayers.size(); i++) { |
| if (mLayers[i]->mOtfMPP && mLayers[i]->mM2mMPP) { |
| float srcWidth = mLayers[i]->mSourceCrop.right - mLayers[i]->mSourceCrop.left; |
| float srcHeight = mLayers[i]->mSourceCrop.bottom - mLayers[i]->mSourceCrop.top; |
| float resolution = srcWidth * srcHeight * refreshRate / 1000; |
| float ratioVertical = static_cast<float>(mLayers[i]->mDisplayFrame.bottom - |
| mLayers[i]->mDisplayFrame.top) / |
| mYres; |
| |
| if (mLayers[i]->mOtfMPP->checkDownscaleCap(resolution, ratioVertical)) { |
| mLayers[i]->setGeometryChanged(GEOMETRY_DEVICE_CONFIG_CHANGED); |
| break; |
| } |
| } |
| } |
| } |
| } |