927 lines
32 KiB
C++
927 lines
32 KiB
C++
/*
|
|
* Copyright (C) 2021 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 ATRACE_TAG (ATRACE_TAG_GRAPHICS | ATRACE_TAG_HAL)
|
|
|
|
#include <cutils/properties.h>
|
|
#include <poll.h>
|
|
|
|
#include "BrightnessController.h"
|
|
#include "ExynosHWCModule.h"
|
|
|
|
BrightnessController::BrightnessController(int32_t panelIndex, std::function<void(void)> refresh,
|
|
std::function<void(void)> updateDcLhbm)
|
|
: mPanelIndex(panelIndex),
|
|
mEnhanceHbmReq(false),
|
|
mLhbmReq(false),
|
|
mBrightnessFloatReq(-1),
|
|
mBrightnessLevel(0),
|
|
mGhbm(HbmMode::OFF),
|
|
mDimming(false),
|
|
mLhbm(false),
|
|
mSdrDim(false),
|
|
mPrevSdrDim(false),
|
|
mDimBrightnessReq(false),
|
|
mFrameRefresh(refresh),
|
|
mHdrLayerState(HdrLayerState::kHdrNone),
|
|
mUpdateDcLhbm(updateDcLhbm) {
|
|
initBrightnessSysfs();
|
|
initCabcSysfs();
|
|
}
|
|
|
|
BrightnessController::~BrightnessController() {
|
|
if (mDimmingLooper) {
|
|
mDimmingLooper->removeMessages(mDimmingHandler);
|
|
}
|
|
if (mDimmingThreadRunning) {
|
|
mDimmingLooper->sendMessage(mDimmingHandler, DimmingMsgHandler::MSG_QUIT);
|
|
mDimmingThread.join();
|
|
}
|
|
}
|
|
|
|
int BrightnessController::initDrm(const DrmDevice& drmDevice,
|
|
const DrmConnector& connector) {
|
|
initBrightnessTable(drmDevice, connector);
|
|
|
|
initDimmingUsage();
|
|
|
|
mLhbmSupported = connector.lhbm_on().id() != 0;
|
|
mGhbmSupported = connector.hbm_mode().id() != 0;
|
|
|
|
/* allow the first brightness to apply */
|
|
mBrightnessFloatReq.set_dirty();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void BrightnessController::initDimmingUsage() {
|
|
String8 propName;
|
|
propName.appendFormat(kDimmingUsagePropName, mPanelIndex);
|
|
|
|
mBrightnessDimmingUsage = static_cast<BrightnessDimmingUsage>(property_get_int32(propName, 0));
|
|
|
|
propName.clear();
|
|
propName.appendFormat(kDimmingHbmTimePropName, mPanelIndex);
|
|
mHbmDimmingTimeUs = property_get_int32(propName, kHbmDimmingTimeUs);
|
|
|
|
if (mBrightnessDimmingUsage == BrightnessDimmingUsage::NORMAL) {
|
|
mDimming.store(true);
|
|
}
|
|
|
|
if (mBrightnessDimmingUsage == BrightnessDimmingUsage::HBM) {
|
|
mDimmingHandler = new DimmingMsgHandler(this);
|
|
mDimmingThread = std::thread(&BrightnessController::dimmingThread, this);
|
|
}
|
|
}
|
|
|
|
void BrightnessController::initBrightnessSysfs() {
|
|
String8 nodeName;
|
|
nodeName.appendFormat(BRIGHTNESS_SYSFS_NODE, mPanelIndex);
|
|
mBrightnessOfs.open(nodeName.string(), std::ofstream::out);
|
|
if (mBrightnessOfs.fail()) {
|
|
ALOGE("%s %s fail to open", __func__, nodeName.string());
|
|
mBrightnessOfs.close();
|
|
return;
|
|
}
|
|
|
|
nodeName.clear();
|
|
nodeName.appendFormat(MAX_BRIGHTNESS_SYSFS_NODE, mPanelIndex);
|
|
|
|
std::ifstream ifsMaxBrightness(nodeName.string());
|
|
if (ifsMaxBrightness.fail()) {
|
|
ALOGE("%s fail to open %s", __func__, nodeName.string());
|
|
return;
|
|
}
|
|
|
|
ifsMaxBrightness >> mMaxBrightness;
|
|
ifsMaxBrightness.close();
|
|
}
|
|
|
|
void BrightnessController::initCabcSysfs() {
|
|
mCabcSupport = property_get_bool("vendor.display.cabc.supported", false);
|
|
if (!mCabcSupport) return;
|
|
|
|
String8 nodeName;
|
|
nodeName.appendFormat(kLocalCabcModeFileNode, mPanelIndex);
|
|
|
|
mCabcModeOfs.open(nodeName.string(), std::ofstream::out);
|
|
if (mCabcModeOfs.fail()) {
|
|
ALOGE("%s %s fail to open", __func__, nodeName.string());
|
|
mCabcModeOfs.close();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void BrightnessController::initBrightnessTable(const DrmDevice& drmDevice,
|
|
const DrmConnector& connector) {
|
|
if (connector.brightness_cap().id() == 0) {
|
|
ALOGD("the brightness_cap is not supported");
|
|
return;
|
|
}
|
|
|
|
const auto [ret, blobId] = connector.brightness_cap().value();
|
|
if (ret) {
|
|
ALOGE("Fail to get brightness_cap (ret = %d)", ret);
|
|
return;
|
|
}
|
|
|
|
if (blobId == 0) {
|
|
ALOGE("the brightness_cap is supported but blob is not valid");
|
|
return;
|
|
}
|
|
|
|
drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(drmDevice.fd(), blobId);
|
|
if (blob == nullptr) {
|
|
ALOGE("Fail to get brightness_cap blob");
|
|
return;
|
|
}
|
|
|
|
const struct brightness_capability *cap =
|
|
reinterpret_cast<struct brightness_capability *>(blob->data);
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)] = BrightnessTable(cap->normal);
|
|
mBrightnessTable[toUnderlying(BrightnessRange::HBM)] = BrightnessTable(cap->hbm);
|
|
|
|
parseHbmModeEnums(connector.hbm_mode());
|
|
|
|
// init to min before SF sets the brightness
|
|
mDisplayWhitePointNits = cap->normal.nits.min;
|
|
mPrevDisplayWhitePointNits = mDisplayWhitePointNits;
|
|
mBrightnessIntfSupported = true;
|
|
|
|
drmModeFreePropertyBlob(blob);
|
|
|
|
String8 nodeName;
|
|
nodeName.appendFormat(kDimBrightnessFileNode, mPanelIndex);
|
|
|
|
std::ifstream ifsDimBrightness(nodeName.string());
|
|
if (ifsDimBrightness.fail()) {
|
|
ALOGW("%s fail to open %s", __func__, nodeName.string());
|
|
} else {
|
|
ifsDimBrightness >> mDimBrightness;
|
|
ifsDimBrightness.close();
|
|
if (mDimBrightness >= mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBklStart)
|
|
mDimBrightness = 0;
|
|
}
|
|
mDbmSupported = !!mDimBrightness;
|
|
ALOGI("%s mDimBrightness=%d, mDbmSupported=%d", __func__, mDimBrightness, mDbmSupported);
|
|
}
|
|
|
|
int BrightnessController::processEnhancedHbm(bool on) {
|
|
if (!mGhbmSupported) {
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mEnhanceHbmReq.store(on);
|
|
if (mEnhanceHbmReq.is_dirty()) {
|
|
updateStates();
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void BrightnessController::processDimmingOff() {
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
if (mHbmDimming) {
|
|
mHbmDimming = false;
|
|
updateStates();
|
|
mFrameRefresh();
|
|
}
|
|
}
|
|
|
|
int BrightnessController::processDisplayBrightness(float brightness, const nsecs_t vsyncNs,
|
|
bool waitPresent) {
|
|
uint32_t level;
|
|
bool ghbm;
|
|
|
|
if (brightness < -1.0f || brightness > 1.0f) {
|
|
return HWC2_ERROR_BAD_PARAMETER;
|
|
}
|
|
|
|
ATRACE_CALL();
|
|
if (!mBrightnessIntfSupported) {
|
|
level = brightness < 0 ? 0 : static_cast<uint32_t>(brightness * mMaxBrightness + 0.5f);
|
|
return applyBrightnessViaSysfs(level);
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
/* apply the first brightness */
|
|
if (mBrightnessFloatReq.is_dirty()) mBrightnessLevel.set_dirty();
|
|
|
|
mBrightnessFloatReq.store(brightness);
|
|
if (!mBrightnessFloatReq.is_dirty()) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// check if it will go drm path for below cases.
|
|
// case 1: hbm state will change
|
|
// case 2: for hwc3, brightness command could apply at next present if possible
|
|
if (queryBrightness(brightness, &ghbm, &level) == NO_ERROR) {
|
|
// ghbm on/off always go drm path
|
|
// check if this will cause a hbm transition
|
|
if (mGhbmSupported && (mGhbm.get() != HbmMode::OFF) != ghbm) {
|
|
// this brightness change will go drm path
|
|
updateStates();
|
|
mFrameRefresh(); // force next frame to update brightness
|
|
return NO_ERROR;
|
|
}
|
|
// there will be a Present to apply this brightness change
|
|
if (waitPresent) {
|
|
// this brightness change will go drm path
|
|
updateStates();
|
|
return NO_ERROR;
|
|
}
|
|
} else {
|
|
level = brightness < 0 ? 0 : static_cast<uint32_t>(brightness * mMaxBrightness + 0.5f);
|
|
}
|
|
// go sysfs path
|
|
}
|
|
|
|
// Sysfs path is faster than drm path. If there is an unchecked drm path change, the sysfs
|
|
// path should check the sysfs content.
|
|
if (mUncheckedGbhmRequest) {
|
|
ATRACE_NAME("check_ghbm_mode");
|
|
checkSysfsStatus(kGlobalHbmModeFileNode,
|
|
{std::to_string(toUnderlying(mPendingGhbmStatus.load()))}, vsyncNs * 5);
|
|
mUncheckedGbhmRequest = false;
|
|
}
|
|
|
|
if (mUncheckedLhbmRequest) {
|
|
ATRACE_NAME("check_lhbm_mode");
|
|
checkSysfsStatus(kLocalHbmModeFileNode, {std::to_string(mPendingLhbmStatus)}, vsyncNs * 5);
|
|
mUncheckedLhbmRequest = false;
|
|
}
|
|
|
|
return applyBrightnessViaSysfs(level);
|
|
}
|
|
|
|
// In HWC3, brightness change could be applied via drm commit or sysfs path.
|
|
// If a brightness change command does not come with a frame update, this
|
|
// function wil be called to apply the brghtness change via sysfs path.
|
|
int BrightnessController::applyPendingChangeViaSysfs(const nsecs_t vsyncNs) {
|
|
ATRACE_CALL();
|
|
uint32_t level;
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
|
|
if (!mBrightnessLevel.is_dirty()) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// there will be a drm commit to apply this brightness change if a GHBM change is pending.
|
|
if (mGhbm.is_dirty()) {
|
|
ALOGI("%s standalone brightness change will be handled by next frame update for GHBM",
|
|
__func__);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// there will be a drm commit to apply this brightness change if a LHBM change is pending.
|
|
if (mLhbm.is_dirty()) {
|
|
ALOGI("%s standalone brightness change will be handled by next frame update for LHBM",
|
|
__func__);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
level = mBrightnessLevel.get();
|
|
}
|
|
|
|
if (mUncheckedBlRequest) {
|
|
ATRACE_NAME("check_bl_value");
|
|
checkSysfsStatus(BRIGHTNESS_SYSFS_NODE, {std::to_string(mPendingBl)}, vsyncNs * 5);
|
|
mUncheckedBlRequest = false;
|
|
}
|
|
|
|
return applyBrightnessViaSysfs(level);
|
|
}
|
|
|
|
int BrightnessController::processLocalHbm(bool on) {
|
|
if (!mLhbmSupported) {
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mLhbmReq.store(on);
|
|
if (mLhbmReq.is_dirty()) {
|
|
updateStates();
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void BrightnessController::updateFrameStates(HdrLayerState hdrState, bool sdrDim) {
|
|
mHdrLayerState.store(hdrState);
|
|
if (!mGhbmSupported) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mPrevSdrDim.store(mSdrDim.get());
|
|
mSdrDim.store(sdrDim);
|
|
if (mSdrDim.is_dirty() || mPrevSdrDim.is_dirty()) {
|
|
updateStates();
|
|
}
|
|
}
|
|
|
|
int BrightnessController::processInstantHbm(bool on) {
|
|
if (!mGhbmSupported) {
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mInstantHbmReq.store(on);
|
|
if (mInstantHbmReq.is_dirty()) {
|
|
updateStates();
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int BrightnessController::processDimBrightness(bool on) {
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mDimBrightnessReq.store(on);
|
|
if (mDimBrightnessReq.is_dirty()) {
|
|
updateStates();
|
|
ALOGI("%s request = %d", __func__, mDimBrightnessReq.get());
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
float BrightnessController::getSdrDimRatioForInstantHbm() {
|
|
if (!mBrightnessIntfSupported || !mGhbmSupported) {
|
|
return 1.0f;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
if (!mInstantHbmReq.get()) {
|
|
return 1.0f;
|
|
}
|
|
|
|
float sdr = 0;
|
|
if (queryBrightness(mBrightnessFloatReq.get(), nullptr, nullptr, &sdr) != NO_ERROR) {
|
|
return 1.0f;
|
|
}
|
|
|
|
float peak = mBrightnessTable[toUnderlying(BrightnessRange::MAX) - 1].mNitsEnd;
|
|
if (sdr == 0 || peak == 0) {
|
|
ALOGW("%s error luminance value sdr %f peak %f", __func__, sdr, peak);
|
|
return 1.0f;
|
|
}
|
|
|
|
float ratio = sdr / peak;
|
|
if (ratio < kGhbmMinDimRatio) {
|
|
ALOGW("%s sdr dim ratio %f too small", __func__, ratio);
|
|
ratio = kGhbmMinDimRatio;
|
|
}
|
|
|
|
return ratio;
|
|
}
|
|
|
|
void BrightnessController::onClearDisplay(bool needModeClear) {
|
|
resetLhbmState();
|
|
mInstantHbmReq.reset(false);
|
|
|
|
if (mBrightnessLevel.is_dirty()) applyBrightnessViaSysfs(mBrightnessLevel.get());
|
|
|
|
if (!needModeClear) return;
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mEnhanceHbmReq.reset(false);
|
|
mBrightnessFloatReq.reset(-1);
|
|
|
|
mBrightnessLevel.reset(0);
|
|
mDisplayWhitePointNits = 0;
|
|
mPrevDisplayWhitePointNits = 0;
|
|
mGhbm.reset(HbmMode::OFF);
|
|
mDimming.reset(false);
|
|
mHbmDimming = false;
|
|
if (mBrightnessDimmingUsage == BrightnessDimmingUsage::NORMAL) {
|
|
mDimming.store(true);
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> lock1(mCabcModeMutex);
|
|
mCabcMode.reset(CabcMode::OFF);
|
|
}
|
|
|
|
int BrightnessController::prepareFrameCommit(ExynosDisplay& display,
|
|
const DrmConnector& connector,
|
|
ExynosDisplayDrmInterface::DrmModeAtomicReq& drmReq,
|
|
const bool mixedComposition,
|
|
bool& ghbmSync, bool& lhbmSync, bool& blSync) {
|
|
int ret;
|
|
|
|
ghbmSync = false;
|
|
lhbmSync = false;
|
|
blSync = false;
|
|
|
|
ATRACE_CALL();
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
|
|
bool sync = false;
|
|
if (mixedComposition && mPrevDisplayWhitePointNits > 0 && mDisplayWhitePointNits > 0) {
|
|
float diff = std::abs(mPrevDisplayWhitePointNits - mDisplayWhitePointNits);
|
|
float min = std::min(mPrevDisplayWhitePointNits, mDisplayWhitePointNits);
|
|
if (diff / min > kBrightnessSyncThreshold) {
|
|
sync = true;
|
|
ALOGD("%s: enable brightness sync for change from %f to %f", __func__,
|
|
mPrevDisplayWhitePointNits, mDisplayWhitePointNits);
|
|
}
|
|
}
|
|
|
|
if (mDimming.is_dirty()) {
|
|
if ((ret = drmReq.atomicAddProperty(connector.id(), connector.dimming_on(),
|
|
mDimming.get())) < 0) {
|
|
ALOGE("%s: Fail to set dimming_on property", __func__);
|
|
}
|
|
mDimming.clear_dirty();
|
|
}
|
|
|
|
if (mLhbm.is_dirty() && mLhbmSupported) {
|
|
if ((ret = drmReq.atomicAddProperty(connector.id(), connector.lhbm_on(),
|
|
mLhbm.get())) < 0) {
|
|
ALOGE("%s: Fail to set lhbm_on property", __func__);
|
|
} else {
|
|
lhbmSync = true;
|
|
}
|
|
|
|
auto dbv = mBrightnessLevel.get();
|
|
auto old_dbv = dbv;
|
|
if (mLhbm.get()) {
|
|
mUpdateDcLhbm();
|
|
uint32_t dbv_adj = 0;
|
|
if (display.getColorAdjustedDbv(dbv_adj)) {
|
|
ALOGW("failed to get adjusted dbv");
|
|
} else if (dbv_adj != dbv && dbv_adj != 0) {
|
|
dbv_adj = std::clamp(dbv_adj,
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBklStart,
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBklEnd);
|
|
|
|
ALOGI("lhbm: adjust dbv from %d to %d", dbv, dbv_adj);
|
|
dbv = dbv_adj;
|
|
mLhbmBrightnessAdj = (dbv != old_dbv);
|
|
}
|
|
}
|
|
|
|
if (mLhbmBrightnessAdj) {
|
|
// case 1: lhbm on and dbv is changed, use the new dbv
|
|
// case 2: lhbm off and dbv was changed at lhbm on, use current dbv
|
|
if ((ret = drmReq.atomicAddProperty(connector.id(),
|
|
connector.brightness_level(), dbv)) < 0) {
|
|
ALOGE("%s: Fail to set brightness_level property", __func__);
|
|
} else {
|
|
blSync = true;
|
|
mUncheckedBlRequest = true;
|
|
mPendingBl = dbv;
|
|
}
|
|
}
|
|
|
|
// mLhbmBrightnessAdj will last from LHBM on to off
|
|
if (!mLhbm.get() && mLhbmBrightnessAdj) {
|
|
mLhbmBrightnessAdj = false;
|
|
}
|
|
|
|
mLhbm.clear_dirty();
|
|
}
|
|
|
|
if (mBrightnessLevel.is_dirty()) {
|
|
// skip if lhbm has updated bl
|
|
if (!blSync) {
|
|
if ((ret = drmReq.atomicAddProperty(connector.id(),
|
|
connector.brightness_level(),
|
|
mBrightnessLevel.get())) < 0) {
|
|
ALOGE("%s: Fail to set brightness_level property", __func__);
|
|
} else {
|
|
mUncheckedBlRequest = true;
|
|
mPendingBl = mBrightnessLevel.get();
|
|
blSync = sync;
|
|
}
|
|
}
|
|
mBrightnessLevel.clear_dirty();
|
|
mPrevDisplayWhitePointNits = mDisplayWhitePointNits;
|
|
}
|
|
|
|
if (mGhbm.is_dirty() && mGhbmSupported) {
|
|
HbmMode hbmMode = mGhbm.get();
|
|
auto [hbmEnum, ret] = DrmEnumParser::halToDrmEnum(static_cast<int32_t>(hbmMode),
|
|
mHbmModeEnums);
|
|
if (ret < 0) {
|
|
ALOGE("Fail to convert hbm mode(%d)", hbmMode);
|
|
return ret;
|
|
}
|
|
|
|
if ((ret = drmReq.atomicAddProperty(connector.id(), connector.hbm_mode(),
|
|
hbmEnum)) < 0) {
|
|
ALOGE("%s: Fail to set hbm_mode property", __func__);
|
|
} else {
|
|
ghbmSync = sync;
|
|
}
|
|
mGhbm.clear_dirty();
|
|
}
|
|
|
|
mHdrLayerState.clear_dirty();
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void BrightnessController::DimmingMsgHandler::handleMessage(const ::android::Message& message) {
|
|
ALOGI("%s %d", __func__, message.what);
|
|
|
|
switch (message.what) {
|
|
case MSG_DIMMING_OFF:
|
|
mBrightnessController->processDimmingOff();
|
|
break;
|
|
|
|
case MSG_QUIT:
|
|
mBrightnessController->mDimmingThreadRunning = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void BrightnessController::dimmingThread() {
|
|
mDimmingLooper = new Looper(false);
|
|
Looper::setForThread(mDimmingLooper);
|
|
mDimmingThreadRunning = true;
|
|
while (mDimmingThreadRunning.load(std::memory_order_relaxed)) {
|
|
mDimmingLooper->pollOnce(-1);
|
|
}
|
|
}
|
|
|
|
// Process all requests to update states for next commit
|
|
int BrightnessController::updateStates() {
|
|
bool ghbm;
|
|
uint32_t level;
|
|
float brightness = mInstantHbmReq.get() ? 1.0f : mBrightnessFloatReq.get();
|
|
if (queryBrightness(brightness, &ghbm, &level, &mDisplayWhitePointNits)) {
|
|
ALOGW("%s failed to convert brightness %f", __func__, mBrightnessFloatReq.get());
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
mBrightnessLevel.store(level);
|
|
mLhbm.store(mLhbmReq.get());
|
|
|
|
// turn off irc for sun light visibility
|
|
bool irc = !mEnhanceHbmReq.get();
|
|
if (ghbm) {
|
|
mGhbm.store(irc ? HbmMode::ON_IRC_ON : HbmMode::ON_IRC_OFF);
|
|
} else {
|
|
mGhbm.store(HbmMode::OFF);
|
|
}
|
|
|
|
if (mLhbm.is_dirty()) {
|
|
// Next sysfs path should verify this change has been applied.
|
|
mUncheckedLhbmRequest = true;
|
|
mPendingLhbmStatus = mLhbm.get();
|
|
}
|
|
if (mGhbm.is_dirty()) {
|
|
// Next sysfs path should verify this change has been applied.
|
|
mUncheckedGbhmRequest = true;
|
|
mPendingGhbmStatus = mGhbm.get();
|
|
}
|
|
|
|
// no dimming for instant hbm
|
|
// no dimming if current or previous frame is mixed composition
|
|
// - frame N-1: no HDR, HBM off, no sdr dim
|
|
// - frame N: HDR visible HBM on, sdr dim is enabled
|
|
// - frame N+1, HDR gone, HBM off, no sdr dim.
|
|
// We don't need panel dimming for HBM on at frame N and HBM off at frame N+1
|
|
bool dimming = !mInstantHbmReq.get() && !mSdrDim.get() && !mPrevSdrDim.get();
|
|
switch (mBrightnessDimmingUsage) {
|
|
case BrightnessDimmingUsage::HBM:
|
|
// turn on dimming at HBM on/off
|
|
// turn off dimming after mHbmDimmingTimeUs or there is an instant hbm on/off
|
|
if (mGhbm.is_dirty() && dimming) {
|
|
mHbmDimming = true;
|
|
if (mDimmingLooper) {
|
|
mDimmingLooper->removeMessages(mDimmingHandler,
|
|
DimmingMsgHandler::MSG_DIMMING_OFF);
|
|
mDimmingLooper->sendMessageDelayed(us2ns(mHbmDimmingTimeUs), mDimmingHandler,
|
|
DimmingMsgHandler::MSG_DIMMING_OFF);
|
|
}
|
|
}
|
|
|
|
dimming = dimming && (mHbmDimming);
|
|
break;
|
|
|
|
case BrightnessDimmingUsage::NONE:
|
|
dimming = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
mDimming.store(dimming);
|
|
|
|
mEnhanceHbmReq.clear_dirty();
|
|
mLhbmReq.clear_dirty();
|
|
mBrightnessFloatReq.clear_dirty();
|
|
mInstantHbmReq.clear_dirty();
|
|
mSdrDim.clear_dirty();
|
|
mPrevSdrDim.clear_dirty();
|
|
mDimBrightnessReq.clear_dirty();
|
|
|
|
if (mBrightnessLevel.is_dirty() || mDimming.is_dirty() || mGhbm.is_dirty() ||
|
|
mLhbm.is_dirty()) {
|
|
printBrightnessStates("drm");
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int BrightnessController::queryBrightness(float brightness, bool *ghbm, uint32_t *level,
|
|
float *nits) {
|
|
if (!mBrightnessIntfSupported) {
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
if (mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBklStart == 0 &&
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBklEnd == 0 &&
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBriStart == 0 &&
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mBriEnd == 0 &&
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mNitsStart == 0 &&
|
|
mBrightnessTable[toUnderlying(BrightnessRange::NORMAL)].mNitsEnd == 0) {
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
if (brightness < 0) {
|
|
// screen off
|
|
if (ghbm) {
|
|
*ghbm = false;
|
|
}
|
|
if (level) {
|
|
*level = 0;
|
|
}
|
|
if (nits) {
|
|
*nits = 0;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < toUnderlying(BrightnessRange::MAX); ++i) {
|
|
if (brightness <= mBrightnessTable[i].mBriEnd) {
|
|
if (ghbm) {
|
|
*ghbm = (i == toUnderlying(BrightnessRange::HBM));
|
|
}
|
|
|
|
if (level || nits) {
|
|
auto fSpan = mBrightnessTable[i].mBriEnd - mBrightnessTable[i].mBriStart;
|
|
auto norm = fSpan == 0 ? 1 : (brightness - mBrightnessTable[i].mBriStart) / fSpan;
|
|
|
|
if (level) {
|
|
auto iSpan = mBrightnessTable[i].mBklEnd - mBrightnessTable[i].mBklStart;
|
|
auto bl = norm * iSpan + mBrightnessTable[i].mBklStart;
|
|
*level = static_cast<uint32_t>(bl + 0.5);
|
|
}
|
|
|
|
if (nits) {
|
|
auto nSpan = mBrightnessTable[i].mNitsEnd - mBrightnessTable[i].mNitsStart;
|
|
*nits = norm * nSpan + mBrightnessTable[i].mNitsStart;
|
|
}
|
|
}
|
|
if ((i == toUnderlying(BrightnessRange::NORMAL)) && mDbmSupported &&
|
|
(mDimBrightnessReq.get() == true) && (*level == mBrightnessTable[i].mBklStart)) {
|
|
*level = mDimBrightness;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
// Return immediately if it's already in the status. Otherwise poll the status
|
|
int BrightnessController::checkSysfsStatus(const char* file,
|
|
const std::vector<std::string>& expectedValue,
|
|
const nsecs_t timeoutNs) {
|
|
ATRACE_CALL();
|
|
|
|
if (expectedValue.size() == 0) return false;
|
|
|
|
char buf[16];
|
|
String8 nodeName;
|
|
nodeName.appendFormat(file, mPanelIndex);
|
|
UniqueFd fd = open(nodeName.string(), O_RDONLY);
|
|
|
|
int size = read(fd.get(), buf, sizeof(buf));
|
|
if (size <= 0) {
|
|
ALOGE("%s failed to read from %s", __func__, kLocalHbmModeFileNode);
|
|
return false;
|
|
}
|
|
|
|
// '- 1' to remove trailing '\n'
|
|
std::string val = std::string(buf, size - 1);
|
|
if (std::find(expectedValue.begin(), expectedValue.end(), val) != expectedValue.end()) {
|
|
return true;
|
|
} else if (timeoutNs == 0) {
|
|
return false;
|
|
}
|
|
|
|
struct pollfd pfd;
|
|
int ret = EINVAL;
|
|
|
|
auto startTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
pfd.fd = fd.get();
|
|
pfd.events = POLLPRI;
|
|
while (true) {
|
|
auto currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
// int64_t for nsecs_t
|
|
auto remainTimeNs = timeoutNs - (currentTime - startTime);
|
|
if (remainTimeNs <= 0) {
|
|
remainTimeNs = ms2ns(1);
|
|
}
|
|
int pollRet = poll(&pfd, 1, ns2ms(remainTimeNs));
|
|
if (pollRet == 0) {
|
|
ALOGW("%s poll timeout", __func__);
|
|
// time out
|
|
ret = ETIMEDOUT;
|
|
break;
|
|
} else if (pollRet > 0) {
|
|
if (!(pfd.revents & POLLPRI)) {
|
|
continue;
|
|
}
|
|
|
|
lseek(fd.get(), 0, SEEK_SET);
|
|
size = read(fd.get(), buf, sizeof(buf));
|
|
if (size > 0) {
|
|
val = std::string(buf, size - 1);
|
|
if (std::find(expectedValue.begin(), expectedValue.end(), val) !=
|
|
expectedValue.end()) {
|
|
ret = 0;
|
|
} else {
|
|
std::string values;
|
|
for (auto& s : expectedValue) {
|
|
values += s + std::string(" ");
|
|
}
|
|
if (values.size() > 0) {
|
|
values.resize(values.size() - 1);
|
|
}
|
|
ALOGE("%s read %s expected %s after notified", __func__, val.c_str(),
|
|
values.c_str());
|
|
ret = EINVAL;
|
|
}
|
|
} else {
|
|
ret = EIO;
|
|
ALOGE("%s failed to read after notified %d", __func__, errno);
|
|
}
|
|
break;
|
|
} else {
|
|
if (errno == EAGAIN || errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
ALOGE("%s poll failed %d", __func__, errno);
|
|
ret = errno;
|
|
break;
|
|
}
|
|
};
|
|
|
|
return ret == NO_ERROR;
|
|
}
|
|
|
|
void BrightnessController::resetLhbmState() {
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mLhbmReq.reset(false);
|
|
mLhbm.reset(false);
|
|
mLhbmBrightnessAdj = false;
|
|
}
|
|
|
|
void BrightnessController::setOutdoorVisibility(LbeState state) {
|
|
std::lock_guard<std::recursive_mutex> lock(mCabcModeMutex);
|
|
mOutdoorVisibility = (state != LbeState::OFF);
|
|
}
|
|
|
|
int BrightnessController::updateCabcMode() {
|
|
if (!mCabcSupport || mCabcModeOfs.fail()) return HWC2_ERROR_UNSUPPORTED;
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(mCabcModeMutex);
|
|
CabcMode mode;
|
|
if (mOutdoorVisibility)
|
|
mode = CabcMode::OFF;
|
|
else
|
|
mode = isHdrLayerOn() ? CabcMode::CABC_MOVIE_MODE : CabcMode::CABC_UI_MODE;
|
|
mCabcMode.store(mode);
|
|
|
|
if (mCabcMode.is_dirty()) {
|
|
applyCabcModeViaSysfs(static_cast<uint8_t>(mode));
|
|
ALOGD("%s, isHdrLayerOn: %d, mOutdoorVisibility: %d.", __func__, isHdrLayerOn(),
|
|
mOutdoorVisibility);
|
|
mCabcMode.clear_dirty();
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
int BrightnessController::applyBrightnessViaSysfs(uint32_t level) {
|
|
if (mBrightnessOfs.is_open()) {
|
|
ATRACE_NAME("write_bl_sysfs");
|
|
mBrightnessOfs.seekp(std::ios_base::beg);
|
|
mBrightnessOfs << std::to_string(level);
|
|
mBrightnessOfs.flush();
|
|
if (mBrightnessOfs.fail()) {
|
|
ALOGE("%s fail to write brightness %d", __func__, level);
|
|
mBrightnessOfs.clear();
|
|
return HWC2_ERROR_NO_RESOURCES;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
mBrightnessLevel.reset(level);
|
|
mPrevDisplayWhitePointNits = mDisplayWhitePointNits;
|
|
printBrightnessStates("sysfs");
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return HWC2_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
int BrightnessController::applyCabcModeViaSysfs(uint8_t mode) {
|
|
if (!mCabcModeOfs.is_open()) return HWC2_ERROR_UNSUPPORTED;
|
|
|
|
ATRACE_NAME("write_cabc_mode_sysfs");
|
|
mCabcModeOfs.seekp(std::ios_base::beg);
|
|
mCabcModeOfs << std::to_string(mode);
|
|
mCabcModeOfs.flush();
|
|
if (mCabcModeOfs.fail()) {
|
|
ALOGE("%s fail to write CabcMode %d", __func__, mode);
|
|
mCabcModeOfs.clear();
|
|
return HWC2_ERROR_NO_RESOURCES;
|
|
}
|
|
ALOGI("%s Cabc_Mode=%d", __func__, mode);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// brightness is normalized to current display brightness
|
|
bool BrightnessController::validateLayerBrightness(float brightness) {
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
if (!std::isfinite(brightness)) {
|
|
ALOGW("%s layer brightness %f is not a valid floating value", __func__, brightness);
|
|
return false;
|
|
}
|
|
|
|
if (brightness > 1.f || brightness < 0.f) {
|
|
ALOGW("%s Brightness is out of [0, 1] range: %f", __func__, brightness);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BrightnessController::parseHbmModeEnums(const DrmProperty& property) {
|
|
const std::vector<std::pair<uint32_t, const char *>> modeEnums = {
|
|
{static_cast<uint32_t>(HbmMode::OFF), "Off"},
|
|
{static_cast<uint32_t>(HbmMode::ON_IRC_ON), "On IRC On"},
|
|
{static_cast<uint32_t>(HbmMode::ON_IRC_OFF), "On IRC Off"},
|
|
};
|
|
|
|
DrmEnumParser::parseEnums(property, modeEnums, mHbmModeEnums);
|
|
for (auto &e : mHbmModeEnums) {
|
|
ALOGD("hbm mode [hal: %d, drm: %" PRId64 ", %s]", e.first, e.second,
|
|
modeEnums[e.first].second);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* WARNING: This print is parsed by Battery Historian. Consult with the Battery
|
|
* Historian team before modifying (b/239640926).
|
|
*/
|
|
void BrightnessController::printBrightnessStates(const char* path) {
|
|
ALOGI("path=%s, id=%d, level=%d, DimmingOn=%d, Hbm=%d, LhbmOn=%d", path ?: "unknown",
|
|
mPanelIndex, mBrightnessLevel.get(), mDimming.get(), mGhbm.get(), mLhbm.get());
|
|
}
|
|
|
|
void BrightnessController::dump(String8& result) {
|
|
std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
|
|
|
|
result.appendFormat("BrightnessController:\n");
|
|
result.appendFormat("\tsysfs support %d, max %d, valid brightness table %d, "
|
|
"lhbm supported %d, ghbm supported %d\n", mBrightnessOfs.is_open(),
|
|
mMaxBrightness, mBrightnessIntfSupported, mLhbmSupported, mGhbmSupported);
|
|
result.appendFormat("\trequests: enhance hbm %d, lhbm %d, "
|
|
"brightness %f, instant hbm %d, DimBrightness %d\n",
|
|
mEnhanceHbmReq.get(), mLhbmReq.get(), mBrightnessFloatReq.get(),
|
|
mInstantHbmReq.get(), mDimBrightnessReq.get());
|
|
result.appendFormat("\tstates: brighntess level %d, ghbm %d, dimming %d, lhbm %d",
|
|
mBrightnessLevel.get(), mGhbm.get(), mDimming.get(), mLhbm.get());
|
|
result.appendFormat("\thdr layer state %d, unchecked lhbm request %d(%d), "
|
|
"unchecked ghbm request %d(%d)\n",
|
|
mHdrLayerState.get(), mUncheckedLhbmRequest.load(),
|
|
mPendingLhbmStatus.load(), mUncheckedGbhmRequest.load(),
|
|
mPendingGhbmStatus.load());
|
|
result.appendFormat("\tdimming usage %d, hbm dimming %d, time us %d\n", mBrightnessDimmingUsage,
|
|
mHbmDimming, mHbmDimmingTimeUs);
|
|
result.appendFormat("\twhite point nits current %f, previous %f\n", mDisplayWhitePointNits,
|
|
mPrevDisplayWhitePointNits);
|
|
result.appendFormat("\tcabc supported %d, cabcMode %d\n", mCabcModeOfs.is_open(),
|
|
mCabcMode.get());
|
|
|
|
result.appendFormat("\n");
|
|
}
|