[automerger skipped] Merge Android 14 am: 13c58a5ba6 -s ours am: 8731c09aee -s ours am: eacaaa2eff -s ours am: 56ddccbb1c -s ours

am skip reason: Merged-In I2450bb23241a33c6e94bc5d039e44bf782c07ef9 with SHA-1 4c45770465 is already in history

Original change: https://android-review.googlesource.com/c/platform/hardware/google/graphics/common/+/2776762

Change-Id: I49b0cba029f16686f23cef4930dd8b2f38ea98ee
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index 03af56d..f63f670 100644
--- a/.clang-format
+++ b/.clang-format
@@ -11,3 +11,7 @@
 IndentWidth: 4
 PenaltyBreakBeforeFirstCallParameter: 100000
 SpacesBeforeTrailingComments: 1
+IncludeBlocks: Preserve
+
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/gralloc-headers/Android.bp b/gralloc-headers/Android.bp
new file mode 100644
index 0000000..b87e472
--- /dev/null
+++ b/gralloc-headers/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "pixel-gralloc-headers",
+    // TODO(270442578): Change to vendor: true
+    vendor_available: true,
+    export_include_dirs: [
+        ".",
+    ],
+    shared_libs: [
+        "android.hardware.graphics.common-V4-ndk",
+        "libgralloctypes",
+    ],
+    visibility: [
+        "//visibility:public",
+    ],
+}
+
diff --git a/gralloc-headers/OWNERS b/gralloc-headers/OWNERS
new file mode 100644
index 0000000..0b8b903
--- /dev/null
+++ b/gralloc-headers/OWNERS
@@ -0,0 +1,3 @@
+jessehall@google.com
+layog@google.com
+spyffe@google.com
diff --git a/gralloc-headers/pixel-gralloc/format.h b/gralloc-headers/pixel-gralloc/format.h
new file mode 100644
index 0000000..4463067
--- /dev/null
+++ b/gralloc-headers/pixel-gralloc/format.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
+
+#include <cstdint>
+
+namespace pixel::graphics {
+
+using FrameworkFormat = aidl::android::hardware::graphics::common::PixelFormat;
+
+#define MapFormat(f) f = static_cast<uint32_t>(FrameworkFormat::f)
+
+enum class Format : uint32_t {
+    MapFormat(UNSPECIFIED),
+    MapFormat(RGBA_8888),
+    MapFormat(RGBX_8888),
+    MapFormat(RGB_888),
+    MapFormat(RGB_565),
+    MapFormat(BGRA_8888),
+    MapFormat(YCBCR_422_SP),
+    MapFormat(YCRCB_420_SP),
+    MapFormat(YCBCR_422_I),
+    MapFormat(RGBA_FP16),
+    MapFormat(RAW16),
+    MapFormat(BLOB),
+    MapFormat(IMPLEMENTATION_DEFINED),
+    MapFormat(YCBCR_420_888),
+    MapFormat(RAW_OPAQUE),
+    MapFormat(RAW10),
+    MapFormat(RAW12),
+    MapFormat(RGBA_1010102),
+    MapFormat(Y8),
+    MapFormat(Y16),
+    MapFormat(YV12),
+    MapFormat(DEPTH_16),
+    MapFormat(DEPTH_24),
+    MapFormat(DEPTH_24_STENCIL_8),
+    MapFormat(DEPTH_32F),
+    MapFormat(DEPTH_32F_STENCIL_8),
+    MapFormat(STENCIL_8),
+    MapFormat(YCBCR_P010),
+    MapFormat(HSV_888),
+    MapFormat(R_8),
+    MapFormat(R_16_UINT),
+    MapFormat(RG_1616_UINT),
+    MapFormat(RGBA_10101010),
+
+    // Pixel specific formats
+    GOOGLE_NV12 = 0x301,
+};
+
+#undef MapFormat
+
+} // namespace pixel::graphics
diff --git a/gralloc-headers/pixel-gralloc/metadata.h b/gralloc-headers/pixel-gralloc/metadata.h
new file mode 100644
index 0000000..7502f29
--- /dev/null
+++ b/gralloc-headers/pixel-gralloc/metadata.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/StandardMetadataType.h>
+#include <gralloctypes/Gralloc4.h>
+
+#include <cstdint>
+#include <limits>
+
+namespace pixel::graphics {
+
+constexpr const char* kGralloc4StandardMetadataTypeName = GRALLOC4_STANDARD_METADATA_TYPE;
+constexpr const char* kPixelMetadataTypeName = "android.hardware.graphics.common.PixelMetadataType";
+
+using StandardMetadataType = aidl::android::hardware::graphics::common::StandardMetadataType;
+
+#define MapMetadataType(f) f = static_cast<uint64_t>(StandardMetadataType::f)
+
+// This seemingly clashes with MetadataType in Mapper, but this enum represents just the "value"
+// member of that struct. MetadataType comprises of a metadata name and value. Name is just there to
+// identify what kind of metadata it is. So, for all StandardMetadataType, clients need to use
+// kGralloc4StandardMetadataType and for pixel specific metadata, clients should use
+// kPixelMetadataType.
+enum class MetadataType : int64_t {
+    MapMetadataType(INVALID),
+    MapMetadataType(BUFFER_ID),
+    MapMetadataType(NAME),
+    MapMetadataType(WIDTH),
+    MapMetadataType(HEIGHT),
+    MapMetadataType(LAYER_COUNT),
+    MapMetadataType(PIXEL_FORMAT_REQUESTED),
+    MapMetadataType(PIXEL_FORMAT_FOURCC),
+    MapMetadataType(PIXEL_FORMAT_MODIFIER),
+    MapMetadataType(USAGE),
+    MapMetadataType(ALLOCATION_SIZE),
+    MapMetadataType(PROTECTED_CONTENT),
+    MapMetadataType(COMPRESSION),
+    MapMetadataType(INTERLACED),
+    MapMetadataType(CHROMA_SITING),
+    MapMetadataType(PLANE_LAYOUTS),
+    MapMetadataType(CROP),
+    MapMetadataType(DATASPACE),
+    MapMetadataType(BLEND_MODE),
+    MapMetadataType(SMPTE2086),
+    MapMetadataType(CTA861_3),
+    MapMetadataType(SMPTE2094_40),
+    MapMetadataType(SMPTE2094_10),
+    MapMetadataType(STRIDE),
+
+    // Pixel specific metadata
+    // Make sure to use kPixelMetadataType as the name when using these metadata.
+
+    // TODO: These metadata queries returns a pointer inside metadata for now. Need to change that
+    // so we are returning proper data only.
+    VIDEO_HDR = std::numeric_limits<int64_t>::max() - (1 << 16),
+
+    // TODO(b/289448426#comment2): ROIINFO is probably not being used. Remove this after
+    // confirmation.
+    VIDEO_ROI,
+};
+
+#undef MapMetadataType
+
+} // namespace pixel::graphics
diff --git a/gralloc-headers/pixel-gralloc/usage.h b/gralloc-headers/pixel-gralloc/usage.h
new file mode 100644
index 0000000..323e3c2
--- /dev/null
+++ b/gralloc-headers/pixel-gralloc/usage.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+
+#include <cstdint>
+
+namespace pixel::graphics {
+
+using FrameworkUsage = aidl::android::hardware::graphics::common::BufferUsage;
+
+#define MapUsage(f) f = static_cast<uint32_t>(FrameworkUsage::f)
+
+enum Usage : uint64_t {
+    MapUsage(CPU_READ_MASK),
+    MapUsage(CPU_READ_NEVER),
+    MapUsage(CPU_READ_RARELY),
+    MapUsage(CPU_READ_OFTEN),
+    MapUsage(CPU_WRITE_MASK),
+    MapUsage(CPU_WRITE_NEVER),
+    MapUsage(CPU_WRITE_RARELY),
+    MapUsage(CPU_WRITE_OFTEN),
+    MapUsage(GPU_TEXTURE),
+    MapUsage(GPU_RENDER_TARGET),
+    MapUsage(COMPOSER_OVERLAY),
+    MapUsage(COMPOSER_CLIENT_TARGET),
+    MapUsage(PROTECTED),
+    MapUsage(COMPOSER_CURSOR),
+    MapUsage(VIDEO_ENCODER),
+    MapUsage(CAMERA_OUTPUT),
+    MapUsage(CAMERA_INPUT),
+    MapUsage(RENDERSCRIPT),
+    MapUsage(VIDEO_DECODER),
+    MapUsage(SENSOR_DIRECT_DATA),
+    MapUsage(GPU_DATA_BUFFER),
+    MapUsage(GPU_CUBE_MAP),
+    MapUsage(GPU_MIPMAP_COMPLETE),
+    MapUsage(HW_IMAGE_ENCODER),
+    MapUsage(FRONT_BUFFER),
+    MapUsage(VENDOR_MASK),
+    MapUsage(VENDOR_MASK_HI),
+
+    // Pixel specific usage
+    NO_COMPRESSION = 1ULL << 29,
+};
+
+#undef MapUsage
+
+} // namespace pixel::graphics
diff --git a/hwc3/Android.mk b/hwc3/Android.mk
index 28ed88d..6358a90 100644
--- a/hwc3/Android.mk
+++ b/hwc3/Android.mk
@@ -28,14 +28,15 @@
 
 LOCAL_CFLAGS += \
 	-DSOC_VERSION=$(soc_ver) \
-	-DLOG_TAG=\"hwc-3\"
+	-DLOG_TAG=\"hwc-3\" \
+	-Wthread-safety
 
 # hwc3 re-uses hwc2.2 ComposerResource and libexynosdisplay
 LOCAL_SHARED_LIBRARIES := android.hardware.graphics.composer3-V2-ndk \
 	android.hardware.graphics.composer@2.1-resources \
         android.hardware.graphics.composer@2.2-resources \
 	android.hardware.graphics.composer@2.4 \
-	com.google.hardware.pixel.display-V8-ndk \
+	com.google.hardware.pixel.display-V9-ndk \
 	libbase \
 	libbinder \
 	libbinder_ndk \
diff --git a/hwc3/Composer.h b/hwc3/Composer.h
index 9324c0b..d35c3de 100644
--- a/hwc3/Composer.h
+++ b/hwc3/Composer.h
@@ -43,7 +43,7 @@
 
     const std::unique_ptr<IComposerHal> mHal;
     std::mutex mClientMutex;
-    bool mClientAlive GUARDED_BY(mClientMutex) = false;
+    bool mClientAlive = false; // GUARDED_BY(mClientMutex)
     std::condition_variable mClientDestroyedCondition;
 };
 
diff --git a/hwc3/ComposerCommandEngine.cpp b/hwc3/ComposerCommandEngine.cpp
index 8b0f005..eac4df2 100644
--- a/hwc3/ComposerCommandEngine.cpp
+++ b/hwc3/ComposerCommandEngine.cpp
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+#include "ComposerCommandEngine.h"
+
+#include <hardware/hwcomposer2.h>
+
 #include <set>
 
-#include "ComposerCommandEngine.h"
 #include "Util.h"
 
 namespace aidl::android::hardware::graphics::composer3::impl {
@@ -145,7 +148,7 @@
                                   &requestedLayers, &requestMasks, &clientTargetProperty,
                                   &dimmingStage);
     mResources->setDisplayMustValidateState(display, false);
-    if (!err) {
+    if (err == HWC2_ERROR_NONE || err == HWC2_ERROR_HAS_CHANGES) {
         mWriter->setChangedCompositionTypes(display, changedLayers, compositionTypes);
         mWriter->setDisplayRequests(display, displayRequestMask, requestedLayers, requestMasks);
         static constexpr float kBrightness = 1.f;
@@ -233,17 +236,31 @@
         int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) {
     executeSetExpectedPresentTimeInternal(display, expectedPresentTime);
     // First try to Present as is.
-    auto err = mResources->mustValidateDisplay(display) ? IComposerClient::EX_NOT_VALIDATED
-                                                        : executePresentDisplay(display);
-    if (!err) {
+    auto presentErr = mResources->mustValidateDisplay(display) ? IComposerClient::EX_NOT_VALIDATED
+                                                               : executePresentDisplay(display);
+    if (!presentErr) {
         mWriter->setPresentOrValidateResult(display, PresentOrValidate::Result::Presented);
         return;
     }
 
     // Fallback to validate
-    err = executeValidateDisplayInternal(display);
-    if (!err) {
+    auto validateErr = executeValidateDisplayInternal(display);
+    if (validateErr != HWC2_ERROR_NONE && validateErr != HWC2_ERROR_HAS_CHANGES) return;
+
+    bool hasClientComp = false;
+    bool cannotPresentDirectly = (validateErr == HWC2_ERROR_HAS_CHANGES) ||
+            (mHal->getHasClientComposition(display, hasClientComp) == HWC2_ERROR_NONE &&
+             hasClientComp);
+    if (cannotPresentDirectly) {
         mWriter->setPresentOrValidateResult(display, PresentOrValidate::Result::Validated);
+        return;
+    }
+
+    // Try to call present again
+    executeAcceptDisplayChanges(display);
+    presentErr = executePresentDisplay(display);
+    if (!presentErr) {
+        mWriter->setPresentOrValidateResult(display, PresentOrValidate::Result::Presented);
     }
 }
 
diff --git a/hwc3/impl/HalImpl.cpp b/hwc3/impl/HalImpl.cpp
index 93cc6ad..d8fe206 100644
--- a/hwc3/impl/HalImpl.cpp
+++ b/hwc3/impl/HalImpl.cpp
@@ -691,6 +691,15 @@
     return halDisplay->setClientTarget(target, hwcFence, hwcDataspace);
 }
 
+int32_t HalImpl::getHasClientComposition(int64_t display, bool& outHasClientComp) {
+    ExynosDisplay* halDisplay;
+    RET_IF_ERR(getHalDisplay(display, halDisplay));
+
+    outHasClientComp = halDisplay->hasClientComposition();
+
+    return HWC2_ERROR_NONE;
+}
+
 int32_t HalImpl::setColorMode(int64_t display, ColorMode mode, RenderIntent intent) {
     ExynosDisplay* halDisplay;
     RET_IF_ERR(getHalDisplay(display, halDisplay));
@@ -1032,7 +1041,7 @@
         h2a::translate(hwcProperty, *outClientTargetProperty);
     } // else ignore this error
 
-    return HWC2_ERROR_NONE;
+    return err;
 }
 
 int HalImpl::setExpectedPresentTime(
diff --git a/hwc3/impl/HalImpl.h b/hwc3/impl/HalImpl.h
index 9cb8f1d..9822ff7 100644
--- a/hwc3/impl/HalImpl.h
+++ b/hwc3/impl/HalImpl.h
@@ -99,6 +99,7 @@
     int32_t setClientTarget(int64_t display, buffer_handle_t target,
                             const ndk::ScopedFileDescriptor& fence, common::Dataspace dataspace,
                             const std::vector<common::Rect>& damage) override;
+    int32_t getHasClientComposition(int64_t display, bool& outHasClientComp) override;
     int32_t setColorMode(int64_t display, ColorMode mode, RenderIntent intent) override;
     int32_t setColorTransform(int64_t display, const std::vector<float>& matrix) override;
     int32_t setContentType(int64_t display, ContentType contentType) override;
diff --git a/hwc3/include/IComposerHal.h b/hwc3/include/IComposerHal.h
index 9a9108f..93dda20 100644
--- a/hwc3/include/IComposerHal.h
+++ b/hwc3/include/IComposerHal.h
@@ -174,6 +174,7 @@
                                     const ndk::ScopedFileDescriptor& fence,
                                     common::Dataspace dataspace,
                                     const std::vector<common::Rect>& damage) = 0; // cmd
+    virtual int32_t getHasClientComposition(int64_t display, bool& outHasClientComp) = 0;
     virtual int32_t setColorMode(int64_t display, ColorMode mode, RenderIntent intent) = 0;
     virtual int32_t setColorTransform(int64_t display, const std::vector<float>& matrix) = 0; // cmd
     virtual int32_t setContentType(int64_t display, ContentType contentType) = 0;
diff --git a/include/displaycolor/displaycolor.h b/include/displaycolor/displaycolor.h
index 4161bee..8cef849 100644
--- a/include/displaycolor/displaycolor.h
+++ b/include/displaycolor/displaycolor.h
@@ -142,6 +142,7 @@
     virtual std::optional<uint32_t> NitsToDbv(BrightnessMode bm, float nits) const = 0;
     virtual std::optional<float> DbvToNits(BrightnessMode bm, uint32_t dbv) const = 0;
     virtual std::optional<float> NitsToBrightness(float nits) const = 0;
+    virtual std::optional<float> DbvToBrightness(uint32_t dbv) const = 0;
 };
 
 /**
@@ -320,6 +321,7 @@
                lhbm_on == rhs.lhbm_on &&
                dbv == rhs.dbv &&
                refresh_rate == rhs.refresh_rate &&
+               operation_rate == rhs.operation_rate &&
                hdr_layer_state == rhs.hdr_layer_state;
     }
     bool operator!=(const DisplayScene &rhs) const {
@@ -359,6 +361,9 @@
     /// refresh rate
     float refresh_rate = 60.0f;
 
+    /// operation rate to switch between hs/ns mode
+    uint32_t operation_rate;
+
     /// hdr layer state on screen
     HdrLayerState hdr_layer_state = HdrLayerState::kHdrNone;
 };
diff --git a/libacryl/Android.mk b/libacryl/Android.mk
index 2984d2d..98a51f2 100644
--- a/libacryl/Android.mk
+++ b/libacryl/Android.mk
@@ -17,6 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_CFLAGS += -DLOG_TAG=\"hwc-libacryl\"
+LOCAL_CFLAGS += -Wthread-safety
 #LOCAL_CFLAGS += -DLIBACRYL_DEBUG
 
 ifdef BOARD_LIBACRYL_DEFAULT_COMPOSITOR
diff --git a/libhwc2.1/Android.mk b/libhwc2.1/Android.mk
index 1e23ab7..5224595 100644
--- a/libhwc2.1/Android.mk
+++ b/libhwc2.1/Android.mk
@@ -44,6 +44,7 @@
 LOCAL_CFLAGS := -DHLOG_CODE=0
 LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
+LOCAL_CFLAGS += -Wthread-safety
 LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libdrm
 
 LOCAL_MODULE := libdrmresource
@@ -68,7 +69,7 @@
 	android.hardware.power-V2-ndk pixel-power-ext-V1-ndk
 
 LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V2-ndk \
-                          com.google.hardware.pixel.display-V8-ndk \
+                          com.google.hardware.pixel.display-V9-ndk \
                           libbinder_ndk \
                           libbase \
                           libpng \
@@ -112,6 +113,7 @@
 	libdevice/ExynosDisplay.cpp \
 	libdevice/ExynosDevice.cpp \
 	libdevice/ExynosLayer.cpp \
+	libdevice/HistogramDevice.cpp \
 	libmaindisplay/ExynosPrimaryDisplay.cpp \
 	libresource/ExynosMPP.cpp \
 	libresource/ExynosResourceManager.cpp \
@@ -138,6 +140,7 @@
 LOCAL_CFLAGS += -DLOG_TAG=\"hwc-display\"
 LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
+LOCAL_CFLAGS += -Wthread-safety
 
 LOCAL_MODULE := libexynosdisplay
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
@@ -162,7 +165,7 @@
 	android.hardware.graphics.mapper@2.0 \
 	android.hardware.graphics.composer3-V2-ndk
 
-LOCAL_SHARED_LIBRARIES += com.google.hardware.pixel.display-V8-ndk \
+LOCAL_SHARED_LIBRARIES += com.google.hardware.pixel.display-V9-ndk \
                           libbinder_ndk \
                           libbase
 
@@ -195,6 +198,7 @@
 LOCAL_CFLAGS := -DHLOG_CODE=0
 LOCAL_CFLAGS += -DLOG_TAG=\"hwc-service\"
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
+LOCAL_CFLAGS += -Wthread-safety
 
 LOCAL_SRC_FILES := \
 	libhwcService/IExynosHWC.cpp \
@@ -224,7 +228,7 @@
 	libui
 
 LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V2-ndk \
-                          com.google.hardware.pixel.display-V8-ndk \
+                          com.google.hardware.pixel.display-V9-ndk \
                           libbinder_ndk \
                           libbase
 
@@ -235,6 +239,7 @@
 LOCAL_CFLAGS := -DHLOG_CODE=0
 LOCAL_CFLAGS += -DLOG_TAG=\"hwc-2\"
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
+LOCAL_CFLAGS += -Wthread-safety
 
 ifeq ($(BOARD_USES_HWC_SERVICES),true)
 LOCAL_CFLAGS += -DUSES_HWC_SERVICES
diff --git a/libhwc2.1/ExynosHWCDebug.cpp b/libhwc2.1/ExynosHWCDebug.cpp
index 3473d50..d43f76d 100644
--- a/libhwc2.1/ExynosHWCDebug.cpp
+++ b/libhwc2.1/ExynosHWCDebug.cpp
@@ -39,37 +39,3 @@
     fileWriter.flush();
     return ret;
 }
-
-int32_t saveFenceTrace(ExynosDisplay *display) {
-    int32_t ret = NO_ERROR;
-    auto &fileWriter = display->mFenceFileWriter;
-
-    if (!fileWriter.chooseOpenedFile()) {
-        return -1;
-    }
-
-    ExynosDevice *device = display->mDevice;
-
-    String8 saveString;
-
-    struct timeval tv;
-    gettimeofday(&tv, NULL);
-    saveString.appendFormat("\n====== Fences at time:%s ======\n", getLocalTimeStr(tv).c_str());
-
-    if (device != NULL) {
-        for (const auto &[fd, info] : device->mFenceInfos) {
-            saveString.appendFormat("---- Fence FD : %d, Display(%d) ----\n", fd, info.displayId);
-            saveString.appendFormat("usage: %d, dupFrom: %d, pendingAllowed: %d, leaking: %d\n",
-                                    info.usage, info.dupFrom, info.pendingAllowed, info.leaking);
-
-            for (const auto &trace : info.traces) {
-                saveString.appendFormat("> dir: %d, type: %d, ip: %d, time:%s\n", trace.direction,
-                                        trace.type, trace.ip, getLocalTimeStr(trace.time).c_str());
-            }
-        }
-    }
-
-    fileWriter.write(saveString);
-    fileWriter.flush();
-    return ret;
-}
diff --git a/libhwc2.1/ExynosHWCDebug.h b/libhwc2.1/ExynosHWCDebug.h
index 2f2d0b3..bd62a2e 100644
--- a/libhwc2.1/ExynosHWCDebug.h
+++ b/libhwc2.1/ExynosHWCDebug.h
@@ -68,7 +68,18 @@
 }
 
 int32_t saveErrorLog(const android::String8 &errString, ExynosDisplay *display = NULL);
-int32_t saveFenceTrace(ExynosDisplay *display);
+
+#if defined(DISABLE_HWC_DEBUG)
+#define ALOGD_AND_ATRACE_NAME(debugFlag, fmt, ...)
+#else
+#define ALOGD_AND_ATRACE_NAME(debugFlag, fmt, ...)                           \
+    if (hwcCheckDebugMessages(debugFlag) || CC_UNLIKELY(ATRACE_ENABLED())) { \
+        String8 log;                                                         \
+        log.appendFormat((fmt), ##__VA_ARGS__);                              \
+        ALOGD("%s", log.c_str());                                            \
+        if (CC_UNLIKELY(ATRACE_ENABLED())) ATRACE_NAME(log.c_str());         \
+    }
+#endif
 
 #if defined(DISABLE_HWC_DEBUG)
 #define HDEBUGLOGD(...)
diff --git a/libhwc2.1/histogram_mediator.cpp b/libhwc2.1/histogram_mediator.cpp
index f23feec..b446d24 100644
--- a/libhwc2.1/histogram_mediator.cpp
+++ b/libhwc2.1/histogram_mediator.cpp
@@ -22,7 +22,6 @@
     mIDLHistogram = std::make_shared<HistogramReceiver>();
 
     moduleDisplayInterface->registerHistogramInfo(mIDLHistogram);
-    moduleDisplayInterface->getPanelResolution();
 }
 uint32_t histogram::HistogramMediator::getFrameCount() {
     ExynosDisplayDrmInterface *moduleDisplayInterface =
@@ -30,27 +29,8 @@
     return moduleDisplayInterface->getFrameCount();
 }
 
-bool histogram::HistogramMediator::isDisplayPowerOff() {
-    if (!mDisplay->mPowerModeState.has_value() ||
-        ((mDisplay->mPowerModeState.value() == HWC2_POWER_MODE_OFF) ||
-         (mDisplay->mPowerModeState.value() == HWC2_POWER_MODE_DOZE))) {
-        return true;
-    }
-    return false;
-}
-
-bool histogram::HistogramMediator::isSecureContentPresenting() {
-    Mutex::Autolock lock(mDisplay->mDRMutex);
-    for (uint32_t i = 0; i < mDisplay->mLayers.size(); i++) {
-        ExynosLayer *layer = mDisplay->mLayers[i];
-        if (layer != NULL && layer->isDrm()) { /* there is some DRM layer */
-            return true;
-        }
-    }
-    return false;
-}
 histogram::HistogramErrorCode histogram::HistogramMediator::requestHist() {
-    if (isSecureContentPresenting()) { /* there is some DRM layer */
+    if (mDisplay->isSecureContentPresenting()) {
         return histogram::HistogramErrorCode::DRM_PLAYING;
     }
     ExynosDisplayDrmInterface *moduleDisplayInterface =
@@ -110,7 +90,7 @@
     std::unique_lock<std::mutex> lk(mIDLHistogram->mDataCollectingMutex);
 
     mIDLHistogram->mHistData_cv.wait_for(lk, std::chrono::milliseconds(50), [this]() {
-        return (!isDisplayPowerOff() && !mIDLHistogram->mHistReq_pending);
+        return (!mDisplay->isPowerModeOff() && !mIDLHistogram->mHistReq_pending);
     });
     if (mIDLHistogram->mHistReq_pending == false) setSampleFrameCounter(getFrameCount());
     buf->assign(mIDLHistogram->mHistData, mIDLHistogram->mHistData + HISTOGRAM_BINS_SIZE);
@@ -123,12 +103,12 @@
     ExynosDisplayDrmInterface *moduleDisplayInterface =
             static_cast<ExynosDisplayDrmInterface *>(mDisplay->mDisplayInterface.get());
     roi_return.left = roi.left * moduleDisplayInterface->getActiveModeHDisplay() /
-            moduleDisplayInterface->panelHsize();
+            moduleDisplayInterface->getPanelFullResolutionHSize();
     roi_return.top = roi.top * moduleDisplayInterface->getActiveModeVDisplay() /
-            moduleDisplayInterface->panelVsize();
+            moduleDisplayInterface->getPanelFullResolutionVSize();
     roi_return.right = roi.right * moduleDisplayInterface->getActiveModeHDisplay() /
-            moduleDisplayInterface->panelHsize();
+            moduleDisplayInterface->getPanelFullResolutionHSize();
     roi_return.bottom = roi.bottom * moduleDisplayInterface->getActiveModeVDisplay() /
-            moduleDisplayInterface->panelVsize();
+            moduleDisplayInterface->getPanelFullResolutionVSize();
     return roi_return;
 }
diff --git a/libhwc2.1/histogram_mediator.h b/libhwc2.1/histogram_mediator.h
index 54d28c9..493524b 100644
--- a/libhwc2.1/histogram_mediator.h
+++ b/libhwc2.1/histogram_mediator.h
@@ -57,8 +57,6 @@
     HistogramMediator(ExynosDisplay *display);
     ~HistogramMediator() {}
 
-    bool isDisplayPowerOff();
-    bool isSecureContentPresenting();
     HistogramErrorCode requestHist();
     HistogramErrorCode cancelHistRequest();
     HistogramErrorCode collectRoiLuma(std::vector<char16_t> *buf);
diff --git a/libhwc2.1/libdevice/BrightnessController.cpp b/libhwc2.1/libdevice/BrightnessController.cpp
index 08912e0..2f9327e 100644
--- a/libhwc2.1/libdevice/BrightnessController.cpp
+++ b/libhwc2.1/libdevice/BrightnessController.cpp
@@ -64,6 +64,21 @@
     return brightness;
 }
 
+std::optional<float> BrightnessController::LinearBrightnessTable::DbvToBrightness(
+        uint32_t dbv) const {
+    BrightnessMode bm = getBrightnessModeForDbv(dbv);
+    if (bm == BrightnessMode::BM_INVALID) {
+        return std::nullopt;
+    }
+
+    std::optional<float> nits = DbvToNits(bm, dbv);
+    if (nits == std::nullopt) {
+        return std::nullopt;
+    }
+
+    return NitsToBrightness(nits.value());
+}
+
 std::optional<float> BrightnessController::LinearBrightnessTable::BrightnessToNits(
         float brightness, BrightnessMode& bm) const {
     bm = GetBrightnessMode(brightness);
@@ -439,6 +454,20 @@
     return processDisplayBrightness(brightness.value(), vsyncNs);
 }
 
+int BrightnessController::setBrightnessDbv(uint32_t dbv, const nsecs_t vsyncNs) {
+    ALOGI("%s set brightness to %u dbv", __func__, dbv);
+
+    std::optional<float> brightness =
+            mBrightnessTable ? mBrightnessTable->DbvToBrightness(dbv) : std::nullopt;
+
+    if (brightness == std::nullopt) {
+        ALOGI("%s could not find brightness for %d dbv", __func__, dbv);
+        return -EINVAL;
+    }
+
+    return processDisplayBrightness(brightness.value(), vsyncNs);
+}
+
 // 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.
@@ -528,6 +557,7 @@
     if (mColorRenderIntent.is_dirty()) {
         updateAclMode();
         ALOGI("%s Color Render Intent = %d", __func__, mColorRenderIntent.get());
+        mColorRenderIntent.clear_dirty();
     }
 }
 
diff --git a/libhwc2.1/libdevice/BrightnessController.h b/libhwc2.1/libdevice/BrightnessController.h
index 6733d46..63747f6 100644
--- a/libhwc2.1/libdevice/BrightnessController.h
+++ b/libhwc2.1/libdevice/BrightnessController.h
@@ -71,6 +71,7 @@
     int processDisplayBrightness(float bl, const nsecs_t vsyncNs, bool waitPresent = false);
     int ignoreBrightnessUpdateRequests(bool ignore);
     int setBrightnessNits(float nits, const nsecs_t vsyncNs);
+    int setBrightnessDbv(uint32_t dbv, const nsecs_t vsyncNs);
     int processLocalHbm(bool on);
     int processDimBrightness(bool on);
     int processOperationRate(int32_t hz);
@@ -264,6 +265,7 @@
         }
         std::optional<float> BrightnessToNits(float brightness, BrightnessMode& bm) const override;
         std::optional<float> NitsToBrightness(float nits) const override;
+        std::optional<float> DbvToBrightness(uint32_t dbv) const override;
         std::optional<uint32_t> NitsToDbv(BrightnessMode bm, float nits) const override;
         std::optional<float> DbvToNits(BrightnessMode bm, uint32_t dbv) const override;
 
@@ -289,6 +291,16 @@
             return BrightnessMode::BM_INVALID;
         }
 
+        BrightnessMode getBrightnessModeForDbv(uint32_t dbv) const {
+            for (const auto& [mode, range] : mBrightnessRanges) {
+                if (dbv >= range.dbv_min && dbv <= range.dbv_max) {
+                    return mode;
+                }
+            }
+            // return BM_INVALID if there is no matching range
+            return BrightnessMode::BM_INVALID;
+        }
+
     private:
         static void setBrightnessRangeFromAttribute(const struct brightness_attribute& attr,
                                                     displaycolor::DisplayBrightnessRange& range) {
@@ -359,14 +371,14 @@
     void initDimmingUsage();
     int applyBrightnessViaSysfs(uint32_t level);
     int applyCabcModeViaSysfs(uint8_t mode);
-    int updateStates() REQUIRES(mBrightnessMutex);
+    int updateStates(); // REQUIRES(mBrightnessMutex)
     void dimmingThread();
     void processDimmingOff();
     int updateAclMode();
 
     void parseHbmModeEnums(const DrmProperty& property);
 
-    void printBrightnessStates(const char* path)  REQUIRES(mBrightnessMutex);
+    void printBrightnessStates(const char* path); // REQUIRES(mBrightnessMutex)
 
     bool mLhbmSupported = false;
     bool mGhbmSupported = false;
@@ -382,19 +394,19 @@
     // brightness state
     std::recursive_mutex mBrightnessMutex;
     // requests
-    CtrlValue<bool> mEnhanceHbmReq GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mLhbmReq GUARDED_BY(mBrightnessMutex);
-    CtrlValue<float> mBrightnessFloatReq GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mInstantHbmReq GUARDED_BY(mBrightnessMutex);
+    CtrlValue<bool> mEnhanceHbmReq;       // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mLhbmReq;             // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<float> mBrightnessFloatReq; // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mInstantHbmReq;       // GUARDED_BY(mBrightnessMutex)
     // states to drm after updateStates call
-    CtrlValue<uint32_t> mBrightnessLevel GUARDED_BY(mBrightnessMutex);
-    CtrlValue<HbmMode> mGhbm GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mDimming GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mLhbm GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mSdrDim GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mPrevSdrDim GUARDED_BY(mBrightnessMutex);
-    CtrlValue<bool> mDimBrightnessReq GUARDED_BY(mBrightnessMutex);
-    CtrlValue<uint32_t> mOperationRate GUARDED_BY(mBrightnessMutex);
+    CtrlValue<uint32_t> mBrightnessLevel; // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<HbmMode> mGhbm;             // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mDimming;             // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mLhbm;                // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mSdrDim;              // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mPrevSdrDim;          // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<bool> mDimBrightnessReq;    // GUARDED_BY(mBrightnessMutex)
+    CtrlValue<uint32_t> mOperationRate;   // GUARDED_BY(mBrightnessMutex)
 
     // Indicating if the last LHBM on has changed the brightness level
     bool mLhbmBrightnessAdj = false;
@@ -419,7 +431,7 @@
 
     // these are dimming related
     BrightnessDimmingUsage mBrightnessDimmingUsage = BrightnessDimmingUsage::NORMAL;
-    bool mHbmDimming GUARDED_BY(mBrightnessMutex) = false;
+    bool mHbmDimming = false; // GUARDED_BY(mBrightnessMutex)
     int32_t mHbmDimmingTimeUs = 0;
     std::thread mDimmingThread;
     std::atomic<bool> mDimmingThreadRunning;
@@ -461,9 +473,9 @@
     static constexpr const char* kLocalCabcModeFileNode =
             "/sys/class/backlight/panel%d-backlight/cabc_mode";
     std::recursive_mutex mCabcModeMutex;
-    bool mOutdoorVisibility GUARDED_BY(mCabcModeMutex) = false;
+    bool mOutdoorVisibility = false; // GUARDED_BY(mCabcModeMutex)
     bool isHdrLayerOn() { return mHdrLayerState.get() == HdrLayerState::kHdrLarge; }
-    CtrlValue<CabcMode> mCabcMode GUARDED_BY(mCabcModeMutex);
+    CtrlValue<CabcMode> mCabcMode; // GUARDED_BY(mCabcModeMutex)
 };
 
 #endif // _BRIGHTNESS_CONTROLLER_H_
diff --git a/libhwc2.1/libdevice/ExynosDevice.cpp b/libhwc2.1/libdevice/ExynosDevice.cpp
index 7ff868a..0f2d202 100644
--- a/libhwc2.1/libdevice/ExynosDevice.cpp
+++ b/libhwc2.1/libdevice/ExynosDevice.cpp
@@ -884,7 +884,8 @@
          * All display's validateDisplay should be skipped or all display's validateDisplay
          * should not be skipped.
          */
-        if (mDisplays[i]->mPlugState) {
+        if (mDisplays[i]->mPlugState && mDisplays[i]->mPowerModeState.has_value() &&
+            mDisplays[i]->mPowerModeState.value() != HWC2_POWER_MODE_OFF) {
             /*
              * presentDisplay is called without validateDisplay.
              * Call functions that should be called in validateDiplay
@@ -893,9 +894,11 @@
             mDisplays[i]->checkLayerFps();
 
             if ((ret = mDisplays[i]->canSkipValidate()) != NO_ERROR) {
-                HDEBUGLOGD(eDebugSkipValidate, "Display[%d] can't skip validate (%d), renderingState(%d), geometryChanged(0x%" PRIx64 ")",
-                        mDisplays[i]->mDisplayId, ret,
-                        mDisplays[i]->mRenderingState, mGeometryChanged);
+                ALOGD_AND_ATRACE_NAME(eDebugSkipValidate,
+                                      "Display[%d] can't skip validate (%d), renderingState(%d), "
+                                      "geometryChanged(0x%" PRIx64 ")",
+                                      mDisplays[i]->mDisplayId, ret, mDisplays[i]->mRenderingState,
+                                      mGeometryChanged);
                 return false;
             } else {
                 HDEBUGLOGD(eDebugSkipValidate, "Display[%d] can skip validate (%d), renderingState(%d), geometryChanged(0x%" PRIx64 ")",
@@ -908,27 +911,7 @@
 }
 
 bool ExynosDevice::validateFences(ExynosDisplay *display) {
-    std::scoped_lock lock(display->mDevice->mFenceMutex);
-
-    if (!validateFencePerFrame(display)) {
-        ALOGE("You should doubt fence leak!");
-        saveFenceTrace(display);
-        return false;
-    }
-
-    if (fenceWarn(display, MAX_FENCE_THRESHOLD)) {
-        printLeakFds(display);
-        saveFenceTrace(display);
-        return false;
-    }
-
-    if (exynosHWCControl.doFenceFileDump) {
-        ALOGD("Fence file dump !");
-        saveFenceTrace(display);
-        exynosHWCControl.doFenceFileDump = false;
-    }
-
-    return true;
+    return mFenceTracker.validateFences(display);
 }
 
 void ExynosDevice::compareVsyncPeriod() {
diff --git a/libhwc2.1/libdevice/ExynosDevice.h b/libhwc2.1/libdevice/ExynosDevice.h
index 69c0839..bcfb112 100644
--- a/libhwc2.1/libdevice/ExynosDevice.h
+++ b/libhwc2.1/libdevice/ExynosDevice.h
@@ -207,9 +207,7 @@
 
         uint32_t mDisplayMode;
 
-        // Variable for fence tracer
-        std::map<int, HwcFenceInfo> mFenceInfos GUARDED_BY(mFenceMutex);
-        std::mutex mFenceMutex;
+        FenceTracker mFenceTracker;
 
         /**
          * This will be initialized with differnt class
diff --git a/libhwc2.1/libdevice/ExynosDisplay.cpp b/libhwc2.1/libdevice/ExynosDisplay.cpp
index fc15a14..a69c4b9 100644
--- a/libhwc2.1/libdevice/ExynosDisplay.cpp
+++ b/libhwc2.1/libdevice/ExynosDisplay.cpp
@@ -38,6 +38,7 @@
 #include "BrightnessController.h"
 #include "ExynosExternalDisplay.h"
 #include "ExynosLayer.h"
+#include "HistogramController.h"
 #include "VendorGraphicBuffer.h"
 #include "exynos_format.h"
 #include "utils/Timers.h"
@@ -924,8 +925,7 @@
     mCompressionInfo.type = compressionType;
 }
 
-void ExynosCompositionInfo::dump(String8& result)
-{
+void ExynosCompositionInfo::dump(String8& result) const {
     result.appendFormat("CompositionInfo (%d)\n", mType);
     result.appendFormat("mHasCompositionLayer(%d)\n", mHasCompositionLayer);
     if (mHasCompositionLayer) {
@@ -1498,10 +1498,6 @@
     }
 }
 
-bool ExynosDisplay::isFrameUpdate() {
-    return mGeometryChanged > 0 || mBufferUpdates > 0;
-}
-
 int ExynosDisplay::handleStaticLayers(ExynosCompositionInfo& compositionInfo)
 {
     if (compositionInfo.mType != COMPOSITION_CLIENT)
@@ -2453,8 +2449,8 @@
     result.appendFormat("Device mGeometryChanged(%" PRIx64 "), mGeometryChanged(%" PRIx64 "), mRenderingState(%d)\n",
             mDevice->mGeometryChanged, mGeometryChanged, mRenderingState);
     result.appendFormat("=======================  dump composition infos  ================================\n");
-    ExynosCompositionInfo clientCompInfo = mClientCompositionInfo;
-    ExynosCompositionInfo exynosCompInfo = mExynosCompositionInfo;
+    const ExynosCompositionInfo& clientCompInfo = mClientCompositionInfo;
+    const ExynosCompositionInfo& exynosCompInfo = mExynosCompositionInfo;
     clientCompInfo.dump(result);
     exynosCompInfo.dump(result);
     ALOGD("%s", result.c_str());
@@ -3518,8 +3514,6 @@
             DISPLAY_LOGD(eDebugSkipValidate, "validate is skipped");
         }
 
-        updateBrightnessState();
-
         if (updateColorConversionInfo() != NO_ERROR) {
             ALOGE("%s:: updateColorConversionInfo() fail, ret(%d)",
                     __func__, ret);
@@ -3553,6 +3547,7 @@
 
     if ((mLayers.size() == 0) &&
         (mType != HWC_DISPLAY_VIRTUAL)) {
+        ALOGI("%s:: layer size is 0", __func__);
         clearDisplay();
         *outRetireFence = -1;
         mLastRetireFence = fence_close(mLastRetireFence, this,
@@ -3563,6 +3558,7 @@
     }
 
     if (!checkFrameValidation()) {
+        ALOGW("%s: checkFrameValidation fail", __func__);
         clearDisplay();
         *outRetireFence = -1;
         mLastRetireFence = fence_close(mLastRetireFence, this,
@@ -3633,7 +3629,7 @@
         mPowerHalHint.signalIdle();
     }
 
-    if (isFrameUpdate()) {
+    if (needUpdateRRIndicator()) {
         updateRefreshRateIndicator();
     }
 
@@ -4062,6 +4058,23 @@
     return HWC2_ERROR_UNSUPPORTED;
 }
 
+int32_t ExynosDisplay::setBrightnessDbv(const uint32_t dbv) {
+    if (mBrightnessController) {
+        int32_t ret = mBrightnessController->setBrightnessDbv(dbv, mVsyncPeriod);
+
+        if (ret == NO_ERROR) {
+            setMinIdleRefreshRate(0, VrrThrottleRequester::BRIGHTNESS);
+            if (mOperationRateManager) {
+                mOperationRateManager->onBrightness(mBrightnessController->getBrightnessLevel());
+            }
+        }
+
+        return ret;
+    }
+
+    return HWC2_ERROR_UNSUPPORTED;
+}
+
 int32_t ExynosDisplay::getDisplayConnectionType(uint32_t* outType)
 {
     if (mType == HWC_DISPLAY_PRIMARY)
@@ -4180,8 +4193,8 @@
             mXres, mYres, mVsyncPeriod, mXdpi, mYdpi);
 
     if (mConfigRequestState == hwc_request_state_t::SET_CONFIG_STATE_REQUESTED) {
-        DISPLAY_LOGI("%s, previous request config is processing (mDesiredConfig: %d)", __func__,
-                     mDesiredConfig);
+        DISPLAY_LOGI("%s, previous request config is processing (desird %d, new request %d)",
+                     __func__, mDesiredConfig, config);
     }
     /* Config would be requested on present time */
     mConfigRequestState = hwc_request_state_t::SET_CONFIG_STATE_PENDING;
@@ -4413,7 +4426,7 @@
             current, actualChangeTime,
             mVsyncPeriodChangeConstraints.desiredTimeNanos);
     if (actualChangeTime >= mVsyncPeriodChangeConstraints.desiredTimeNanos) {
-        DISPLAY_LOGD(eDebugDisplayConfig, "Request setActiveConfig");
+        DISPLAY_LOGD(eDebugDisplayConfig, "Request setActiveConfig %d", mDesiredConfig);
         needSetActiveConfig = true;
         DISPLAY_ATRACE_INT("Pending ActiveConfig", 0);
         DISPLAY_ATRACE_INT64("TimeToChangeConfig", 0);
@@ -4606,8 +4619,6 @@
         mDisplayInterface->setForcePanic();
     }
 
-    updateBrightnessState();
-
     if ((ret = skipStaticLayers(mClientCompositionInfo)) != NO_ERROR) {
         validateError = true;
         HWC_LOGE(this, "%s:: skipStaticLayers() fail, display(%d), ret(%d)", __func__, mDisplayId, ret);
@@ -4779,24 +4790,30 @@
 
     result.appendFormat("PanelGammaSource (%d)\n\n", GetCurrentPanelGammaSource());
 
-    if (mLayers.size()) {
-        result.appendFormat("============================== dump layers ===========================================\n");
-        for (uint32_t i = 0; i < mLayers.size(); i++) {
-            ExynosLayer *layer = mLayers[i];
-            layer->dump(result);
+    {
+        Mutex::Autolock lock(mDRMutex);
+        if (mLayers.size()) {
+            result.appendFormat("============================== dump layers ===========================================\n");
+            for (uint32_t i = 0; i < mLayers.size(); i++) {
+                ExynosLayer *layer = mLayers[i];
+                layer->dump(result);
+            }
         }
-    }
-    if (mIgnoreLayers.size()) {
-        result.appendFormat("\n============================== dump ignore layers ===========================================\n");
-        for (uint32_t i = 0; i < mIgnoreLayers.size(); i++) {
-            ExynosLayer *layer = mIgnoreLayers[i];
-            layer->dump(result);
+        if (mIgnoreLayers.size()) {
+            result.appendFormat("\n============================== dump ignore layers ===========================================\n");
+            for (uint32_t i = 0; i < mIgnoreLayers.size(); i++) {
+                ExynosLayer *layer = mIgnoreLayers[i];
+                layer->dump(result);
+            }
         }
     }
     result.appendFormat("\n");
     if (mBrightnessController) {
         mBrightnessController->dump(result);
     }
+    if (mHistogramController) {
+        mHistogramController->dump(result);
+    }
 }
 
 void ExynosDisplay::dumpConfig(String8 &result, const exynos_win_config_data &c)
@@ -4852,14 +4869,11 @@
 
 int32_t ExynosDisplay::setCompositionTargetExynosImage(uint32_t targetType, exynos_image *src_img, exynos_image *dst_img)
 {
-    std::optional<ExynosCompositionInfo> compositionInfo;
-
-    if (targetType == COMPOSITION_CLIENT)
-        compositionInfo = mClientCompositionInfo;
-    else if (targetType == COMPOSITION_EXYNOS)
-        compositionInfo = mExynosCompositionInfo;
-
-    if (!compositionInfo) return -EINVAL;
+    if (targetType != COMPOSITION_CLIENT && targetType != COMPOSITION_EXYNOS) {
+        return -EINVAL;
+    }
+    const ExynosCompositionInfo& compositionInfo =
+            (targetType == COMPOSITION_CLIENT) ? mClientCompositionInfo : mExynosCompositionInfo;
 
     src_img->fullWidth = mXres;
     src_img->fullHeight = mYres;
@@ -4870,10 +4884,10 @@
     src_img->w = mXres;
     src_img->h = mYres;
 
-    if (compositionInfo->mTargetBuffer != NULL) {
-        src_img->bufferHandle = compositionInfo->mTargetBuffer;
+    if (compositionInfo.mTargetBuffer != NULL) {
+        src_img->bufferHandle = compositionInfo.mTargetBuffer;
 
-        VendorGraphicBufferMeta gmeta(compositionInfo->mTargetBuffer);
+        VendorGraphicBufferMeta gmeta(compositionInfo.mTargetBuffer);
         src_img->format = gmeta.format;
         src_img->usageFlags = gmeta.producer_usage;
     } else {
@@ -4882,21 +4896,21 @@
         src_img->usageFlags = 0;
     }
     src_img->layerFlags = 0x0;
-    src_img->acquireFenceFd = compositionInfo->mAcquireFence;
+    src_img->acquireFenceFd = compositionInfo.mAcquireFence;
     src_img->releaseFenceFd = -1;
-    src_img->dataSpace = compositionInfo->mDataSpace;
+    src_img->dataSpace = compositionInfo.mDataSpace;
     src_img->blending = HWC2_BLEND_MODE_PREMULTIPLIED;
     src_img->transform = 0;
-    src_img->compressionInfo = compositionInfo->mCompressionInfo;
+    src_img->compressionInfo = compositionInfo.mCompressionInfo;
     src_img->planeAlpha = 1;
     src_img->zOrder = 0;
     if ((targetType == COMPOSITION_CLIENT) && (mType == HWC_DISPLAY_VIRTUAL)) {
-        if (compositionInfo->mLastIndex < mExynosCompositionInfo.mLastIndex)
+        if (compositionInfo.mLastIndex < mExynosCompositionInfo.mLastIndex)
             src_img->zOrder = 0;
         else
             src_img->zOrder = 1000;
     }
-    src_img->needPreblending = compositionInfo->mNeedPreblending;
+    src_img->needPreblending = compositionInfo.mNeedPreblending;
 
     dst_img->fullWidth = mXres;
     dst_img->fullHeight = mYres;
@@ -4919,7 +4933,7 @@
         dst_img->dataSpace = colorModeToDataspace(mColorMode);
     dst_img->blending = HWC2_BLEND_MODE_NONE;
     dst_img->transform = 0;
-    dst_img->compressionInfo = compositionInfo->mCompressionInfo;
+    dst_img->compressionInfo = compositionInfo.mCompressionInfo;
     dst_img->planeAlpha = 1;
     dst_img->zOrder = src_img->zOrder;
 
@@ -5093,6 +5107,10 @@
     return ret;
 }
 
+bool ExynosDisplay::hasClientComposition() {
+    return mClientCompositionInfo.mHasCompositionLayer;
+}
+
 int32_t ExynosDisplay::addExynosCompositionLayer(uint32_t layerIndex, float totalUsedCapa) {
     bool invalidFlag = false;
     int32_t changeFlag = NO_ERROR;
@@ -5337,6 +5355,24 @@
     return changeFlag;
 }
 
+bool ExynosDisplay::isPowerModeOff() const {
+    ATRACE_CALL();
+    Mutex::Autolock lock(mDisplayMutex);
+    return mPowerModeState.has_value() && mPowerModeState.value() == HWC2_POWER_MODE_OFF;
+}
+
+bool ExynosDisplay::isSecureContentPresenting() const {
+    ATRACE_CALL();
+    Mutex::Autolock lock(mDRMutex);
+    for (uint32_t i = 0; i < mLayers.size(); i++) {
+        ExynosLayer *layer = mLayers[i];
+        if (layer != NULL && layer->isDrm()) { /* there is some DRM layer */
+            return true;
+        }
+    }
+    return false;
+}
+
 bool ExynosDisplay::windowUpdateExceptions()
 {
 
@@ -5950,6 +5986,8 @@
 
     for (size_t i = 0; i < mLayers.size(); i++) {
         if (mLayers[i]->mIsHdrLayer) {
+            // TODO(longling): Below code block for RGB HDR is obsolete also
+            // need some fix (mExynosCompositionType is invalid at this time)
             if (mLayers[i]->isLayerFormatRgb()) {
                 auto meta = mLayers[i]->getMetaParcel();
                 if ((meta != nullptr) && (meta->eType & VIDEO_INFO_TYPE_HDR_STATIC) &&
@@ -6260,7 +6298,11 @@
 }
 
 ExynosDisplay::RefreshRateIndicatorHandler::RefreshRateIndicatorHandler(ExynosDisplay *display)
-      : mDisplay(display), mLastRefreshRate(0), mLastCallbackTime(0) {}
+      : mDisplay(display),
+        mLastRefreshRate(0),
+        mLastCallbackTime(0),
+        mIgnoringLastUpdate(false),
+        mCanIgnoreIncreaseUpdate(false) {}
 
 int32_t ExynosDisplay::RefreshRateIndicatorHandler::init() {
     auto path = String8::format(kRefreshRateStatePathFormat, mDisplay->mIndex);
@@ -6277,11 +6319,12 @@
 void ExynosDisplay::RefreshRateIndicatorHandler::updateRefreshRateLocked(int refreshRate) {
     ATRACE_CALL();
     ATRACE_INT("Refresh rate indicator event", refreshRate);
-    auto lastUpdate = mDisplay->getLastLayerUpdateTime();
     // Ignore refresh rate increase that is caused by refresh rate indicator update but there's
     // no update for the other layers
-    if (refreshRate > mLastRefreshRate && mLastRefreshRate > 0 && lastUpdate < mLastCallbackTime) {
+    if (mCanIgnoreIncreaseUpdate && refreshRate > mLastRefreshRate && mLastRefreshRate > 0 &&
+        mDisplay->getLastLayerUpdateTime() < mLastCallbackTime) {
         mIgnoringLastUpdate = true;
+        mCanIgnoreIncreaseUpdate = false;
         return;
     }
     mIgnoringLastUpdate = false;
@@ -6292,6 +6335,7 @@
     mLastCallbackTime = systemTime(CLOCK_MONOTONIC);
     ATRACE_INT("Refresh rate indicator callback", mLastRefreshRate);
     mDisplay->mDevice->onRefreshRateChangedDebug(mDisplay->mDisplayId, s2ns(1) / mLastRefreshRate);
+    mCanIgnoreIncreaseUpdate = true;
 }
 
 void ExynosDisplay::RefreshRateIndicatorHandler::handleSysfsEvent() {
@@ -6379,6 +6423,11 @@
     mRefreshRateIndicatorHandler->handleSysfsEvent();
 }
 
+bool ExynosDisplay::needUpdateRRIndicator() {
+    uint64_t exclude = GEOMETRY_LAYER_TYPE_CHANGED;
+    return (mGeometryChanged & ~exclude) > 0 || mBufferUpdates > 0;
+}
+
 uint32_t ExynosDisplay::getPeakRefreshRate() {
     float opRate = mBrightnessController->getOperationRate();
     return static_cast<uint32_t>(std::round(opRate ?: mPeakRefreshRate));
diff --git a/libhwc2.1/libdevice/ExynosDisplay.h b/libhwc2.1/libdevice/ExynosDisplay.h
index faf055a..51286ad 100644
--- a/libhwc2.1/libdevice/ExynosDisplay.h
+++ b/libhwc2.1/libdevice/ExynosDisplay.h
@@ -57,6 +57,7 @@
 class ExynosDevice;
 class ExynosMPP;
 class ExynosMPPSource;
+class HistogramController;
 
 namespace aidl {
 namespace google {
@@ -331,7 +332,7 @@
         void setTargetBuffer(ExynosDisplay *display, buffer_handle_t handle,
                 int32_t acquireFence, android_dataspace dataspace);
         void setCompressionType(uint32_t compressionType);
-        void dump(String8& result);
+        void dump(String8& result) const;
         String8 getTypeStr();
 };
 
@@ -418,7 +419,7 @@
         const String8 mDisplayName;
         const String8 mDisplayTraceName;
         HwcMountOrientation mMountOrientation = HwcMountOrientation::ROT_0;
-        Mutex mDisplayMutex;
+        mutable Mutex mDisplayMutex;
 
         /** State variables */
         bool mPlugState;
@@ -486,7 +487,7 @@
         dynamic_recomp_mode mDynamicReCompMode;
         bool mDREnable;
         bool mDRDefault;
-        Mutex mDRMutex;
+        mutable Mutex mDRMutex;
 
         nsecs_t  mLastFpsTime;
         uint64_t mFrameCount;
@@ -540,6 +541,9 @@
 
         std::unique_ptr<BrightnessController> mBrightnessController;
 
+        /* For histogram */
+        std::unique_ptr<HistogramController> mHistogramController;
+
         /* For debugging */
         hwc_display_contents_1_t *mHWC1LayerList;
 
@@ -576,8 +580,12 @@
         int32_t initializeValidateInfos();
         int32_t addClientCompositionLayer(uint32_t layerIndex);
         int32_t removeClientCompositionLayer(uint32_t layerIndex);
+        bool hasClientComposition();
         int32_t addExynosCompositionLayer(uint32_t layerIndex, float totalUsedCapa);
 
+        bool isPowerModeOff() const;
+        bool isSecureContentPresenting() const;
+
         /**
          * Dynamic AFBC Control solution : To get the prepared information is applied to current or not.
          */
@@ -1202,7 +1210,6 @@
         void setHWCControl(uint32_t ctrl, int32_t val);
         void setGeometryChanged(uint64_t changedBit);
         void clearGeometryChanged();
-        bool isFrameUpdate();
 
         virtual void setDDIScalerEnable(int width, int height);
         virtual int getDDIScalerMode(int width, int height);
@@ -1267,6 +1274,9 @@
         /* set brightness to specific nits value */
         virtual int32_t setBrightnessNits(const float nits);
 
+        /* set brightness by dbv value */
+        virtual int32_t setBrightnessDbv(const uint32_t dbv);
+
     protected:
         virtual bool getHDRException(ExynosLayer *layer);
         virtual int32_t getActiveConfigInternal(hwc2_config_t* outConfig);
@@ -1640,6 +1650,7 @@
             int mLastRefreshRate GUARDED_BY(mMutex);
             nsecs_t mLastCallbackTime GUARDED_BY(mMutex);
             std::atomic_bool mIgnoringLastUpdate = false;
+            bool mCanIgnoreIncreaseUpdate GUARDED_BY(mMutex) = false;
             UniqueFd mFd;
             std::mutex mMutex;
 
@@ -1651,6 +1662,7 @@
         int32_t setRefreshRateChangedCallbackDebugEnabled(bool enabled);
         void updateRefreshRateIndicator();
         nsecs_t getLastLayerUpdateTime();
+        bool needUpdateRRIndicator();
         virtual void checkPreblendingRequirement(){};
 
         void resetColorMappingInfoForClientComp();
diff --git a/libhwc2.1/libdevice/HistogramDevice.cpp b/libhwc2.1/libdevice/HistogramDevice.cpp
new file mode 100644
index 0000000..f31961f
--- /dev/null
+++ b/libhwc2.1/libdevice/HistogramDevice.cpp
@@ -0,0 +1,899 @@
+/*
+ * Copyright (C) 2023 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 "HistogramDevice.h"
+
+#include <drm/samsung_drm.h>
+
+#include <sstream>
+#include <string>
+
+#include "ExynosDisplayDrmInterface.h"
+#include "ExynosHWCHelper.h"
+#include "android-base/macros.h"
+
+/**
+ * histogramOnBinderDied
+ *
+ * The binderdied callback function which is registered in registerHistogram and would trigger
+ * unregisterHistogram to cleanup the channel.
+ *
+ * @cookie pointer to the TokenInfo of the binder object.
+ */
+static void histogramOnBinderDied(void *cookie) {
+    ATRACE_CALL();
+    HistogramDevice::HistogramErrorCode histogramErrorCode;
+    HistogramDevice::TokenInfo *tokenInfo = (HistogramDevice::TokenInfo *)cookie;
+    ALOGI("%s: histogram channel #%u: client with token(%p) is died", __func__,
+          tokenInfo->channelId, tokenInfo->token.get());
+
+    /* release the histogram channel */
+    tokenInfo->histogramDevice->unregisterHistogram(tokenInfo->token, &histogramErrorCode);
+
+    if (histogramErrorCode != HistogramDevice::HistogramErrorCode::NONE) {
+        ALOGE("%s: histogram channel #%u: failed to unregisterHistogram: %s", __func__,
+              tokenInfo->channelId,
+              aidl::com::google::hardware::pixel::display::toString(histogramErrorCode).c_str());
+    }
+}
+
+HistogramDevice::ChannelInfo::ChannelInfo()
+      : status(ChannelStatus_t::DISABLED),
+        token(nullptr),
+        pid(-1),
+        requestedRoi(),
+        workingConfig(),
+        threshold(0),
+        histDataCollecting(false) {}
+
+HistogramDevice::ChannelInfo::ChannelInfo(const ChannelInfo &other) {
+    std::scoped_lock lock(other.channelInfoMutex);
+    status = other.status;
+    token = other.token;
+    pid = other.pid;
+    requestedRoi = other.requestedRoi;
+    workingConfig = other.workingConfig;
+    threshold = other.threshold;
+    histDataCollecting = other.histDataCollecting;
+}
+
+HistogramDevice::HistogramDevice(ExynosDisplay *display, uint8_t channelCount,
+                                 std::vector<uint8_t> reservedChannels) {
+    mDisplay = display;
+
+    // TODO: b/295786065 - Get available channels from crtc property.
+    initChannels(channelCount, reservedChannels);
+
+    /* Create the death recipient which will be deleted in the destructor */
+    mDeathRecipient = AIBinder_DeathRecipient_new(histogramOnBinderDied);
+}
+
+HistogramDevice::~HistogramDevice() {
+    if (mDeathRecipient) {
+        AIBinder_DeathRecipient_delete(mDeathRecipient);
+    }
+}
+
+void HistogramDevice::initDrm(const DrmCrtc &crtc) {
+    // TODO: b/295786065 - Get available channels from crtc property.
+
+    // TODO: b/295786065 - Check if the multi channel property is supported.
+    initHistogramCapability(crtc.histogram_channel_property(0).id() != 0);
+}
+
+ndk::ScopedAStatus HistogramDevice::getHistogramCapability(
+        HistogramCapability *histogramCapability) const {
+    ATRACE_CALL();
+
+    if (!histogramCapability) {
+        ALOGE("%s: binder error, histogramCapability is nullptr", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    *histogramCapability = mHistogramCapability;
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus HistogramDevice::registerHistogram(const ndk::SpAIBinder &token,
+                                                      const HistogramConfig &histogramConfig,
+                                                      HistogramErrorCode *histogramErrorCode) {
+    ATRACE_CALL();
+
+    if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
+        ALOGE("%s: histogram interface is not supported", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    return configHistogram(token, histogramConfig, histogramErrorCode, false);
+}
+
+ndk::ScopedAStatus HistogramDevice::queryHistogram(const ndk::SpAIBinder &token,
+                                                   std::vector<char16_t> *histogramBuffer,
+                                                   HistogramErrorCode *histogramErrorCode) {
+    ATRACE_CALL();
+
+    if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
+        ALOGE("%s: histogram interface is not supported", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    /* No need to validate the argument (token), if the token is not correct it cannot be converted
+     * to the channel id later. */
+
+    /* validate the argument (histogramBuffer) */
+    if (!histogramBuffer) {
+        ALOGE("%s: binder error, histogramBuffer is nullptr", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    /* validate the argument (histogramErrorCode) */
+    if (!histogramErrorCode) {
+        ALOGE("%s: binder error, histogramErrorCode is nullptr", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    /* default histogramErrorCode: no error */
+    *histogramErrorCode = HistogramErrorCode::NONE;
+
+    if (mDisplay->isPowerModeOff()) {
+        ALOGW("%s: DISPLAY_POWEROFF, histogram is not available when display is off", __func__);
+        *histogramErrorCode = HistogramErrorCode::DISPLAY_POWEROFF;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    if (mDisplay->isSecureContentPresenting()) {
+        ALOGV("%s: DRM_PLAYING, histogram is not available when secure content is presenting",
+              __func__);
+        *histogramErrorCode = HistogramErrorCode::DRM_PLAYING;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    uint8_t channelId;
+
+    /* Hold the mAllocatorMutex for a short time just to convert the token to channel id. Prevent
+     * holding the mAllocatorMutex when waiting for the histogram data back which may takes several
+     * milliseconds */
+    {
+        ATRACE_NAME("getChannelId");
+        std::scoped_lock lock(mAllocatorMutex);
+        if ((*histogramErrorCode = getChannelIdByTokenLocked(token, channelId)) !=
+            HistogramErrorCode::NONE) {
+            return ndk::ScopedAStatus::ok();
+        }
+    }
+
+    getHistogramData(channelId, histogramBuffer, histogramErrorCode);
+
+    /* Clear the histogramBuffer when error occurs */
+    if (*histogramErrorCode != HistogramErrorCode::NONE) {
+        histogramBuffer->assign(HISTOGRAM_BIN_COUNT, 0);
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus HistogramDevice::reconfigHistogram(const ndk::SpAIBinder &token,
+                                                      const HistogramConfig &histogramConfig,
+                                                      HistogramErrorCode *histogramErrorCode) {
+    ATRACE_CALL();
+
+    if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
+        ALOGE("%s: histogram interface is not supported", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    return configHistogram(token, histogramConfig, histogramErrorCode, true);
+}
+
+ndk::ScopedAStatus HistogramDevice::unregisterHistogram(const ndk::SpAIBinder &token,
+                                                        HistogramErrorCode *histogramErrorCode) {
+    ATRACE_CALL();
+
+    if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
+        ALOGE("%s: histogram interface is not supported", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    }
+
+    /* No need to validate the argument (token), if the token is not correct it cannot be converted
+     * to the channel id later. */
+
+    /* validate the argument (histogramErrorCode) */
+    if (!histogramErrorCode) {
+        ALOGE("%s: binder error, histogramErrorCode is nullptr", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    /* default histogramErrorCode: no error */
+    *histogramErrorCode = HistogramErrorCode::NONE;
+
+    ATRACE_BEGIN("getChannelId");
+    uint8_t channelId;
+    std::scoped_lock lock(mAllocatorMutex);
+
+    if ((*histogramErrorCode = getChannelIdByTokenLocked(token, channelId)) !=
+        HistogramErrorCode::NONE) {
+        ATRACE_END();
+        return ndk::ScopedAStatus::ok();
+    }
+    ATRACE_END();
+
+    releaseChannelLocked(channelId);
+
+    /*
+     * If AIBinder is alive, the unregisterHistogram is triggered from the histogram client, and we
+     * need to unlink the binder object from death notification.
+     * If AIBinder is already dead, the unregisterHistogram is triggered from binderdied callback,
+     * no need to unlink here.
+     */
+    if (LIKELY(AIBinder_isAlive(token.get()))) {
+        binder_status_t status;
+        if ((status = AIBinder_unlinkToDeath(token.get(), mDeathRecipient,
+                                             &mTokenInfoMap[token.get()]))) {
+            /* Not return error due to the AIBinder_unlinkToDeath */
+            ALOGE("%s: histogram channel #%u: AIBinder_linkToDeath error %d", __func__, channelId,
+                  status);
+        }
+    }
+
+    /* Delete the corresponding TokenInfo after the binder object is already unlinked. */
+    mTokenInfoMap.erase(token.get());
+
+    return ndk::ScopedAStatus::ok();
+}
+
+void HistogramDevice::handleDrmEvent(void *event) {
+    int ret = NO_ERROR;
+    uint8_t channelId;
+    char16_t *buffer;
+
+    if ((ret = parseDrmEvent(event, channelId, buffer))) {
+        ALOGE("%s: failed to parseDrmEvent, ret %d", __func__, ret);
+        return;
+    }
+
+    ATRACE_NAME(String8::format("handleHistogramDrmEvent #%u", channelId).c_str());
+    if (channelId >= mChannels.size()) {
+        ALOGE("%s: histogram channel #%u: invalid channelId", __func__, channelId);
+        return;
+    }
+
+    ChannelInfo &channel = mChannels[channelId];
+    std::unique_lock<std::mutex> lock(channel.histDataCollectingMutex);
+
+    /* Check if the histogram channel is collecting the histogram data */
+    if (channel.histDataCollecting == true) {
+        std::memcpy(channel.histData, buffer, HISTOGRAM_BIN_COUNT * sizeof(char16_t));
+        channel.histDataCollecting = false;
+    } else {
+        ALOGW("%s: histogram channel #%u: ignore the histogram channel event", __func__, channelId);
+    }
+
+    channel.histDataCollecting_cv.notify_all();
+}
+
+void HistogramDevice::prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq) {
+    ATRACE_NAME("HistogramAtomicCommit");
+
+    /* Loop through every channel and call prepareChannelCommit */
+    for (uint8_t channelId = 0; channelId < mChannels.size(); ++channelId) {
+        int channelRet = prepareChannelCommit(drmReq, channelId);
+
+        /* Every channel is independent, no early return when the channel commit fails. */
+        if (channelRet) {
+            ALOGE("%s: histogram channel #%u: failed to prepare atomic commit: %d", __func__,
+                  channelId, channelRet);
+        }
+    }
+}
+
+void HistogramDevice::postAtomicCommit() {
+    /* Atomic commit is success, loop through every channel and update the channel status */
+    for (uint8_t channelId = 0; channelId < mChannels.size(); ++channelId) {
+        ChannelInfo &channel = mChannels[channelId];
+        std::scoped_lock lock(channel.channelInfoMutex);
+
+        switch (channel.status) {
+            case ChannelStatus_t::CONFIG_BLOB_ADDED:
+                channel.status = ChannelStatus_t::CONFIG_COMMITTED;
+                break;
+            case ChannelStatus_t::DISABLE_BLOB_ADDED:
+                channel.status = ChannelStatus_t::DISABLED;
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+void HistogramDevice::dump(String8 &result) const {
+    /* Do not dump the Histogram Device if it is not supported. */
+    if (!mHistogramCapability.supportMultiChannel) {
+        return;
+    }
+
+    /* print the histogram capability */
+    dumpHistogramCapability(result);
+
+    result.appendFormat("\n");
+
+    /* print the histogram channel info*/
+    result.appendFormat("Histogram channel info:\n");
+    for (uint8_t channelId = 0; channelId < mChannels.size(); ++channelId) {
+        // TODO: b/294489887 - Use buildForMiniDump can eliminate the redundant rows.
+        TableBuilder tb;
+        const ChannelInfo &channel = mChannels[channelId];
+        std::scoped_lock lock(channel.channelInfoMutex);
+        tb.add("ID", (int)channelId);
+        tb.add("status", toString(channel.status));
+        tb.add("token", channel.token.get());
+        tb.add("pid", channel.pid);
+        tb.add("requestedRoi", toString(channel.requestedRoi));
+        tb.add("workingRoi", toString(channel.workingConfig.roi));
+        tb.add("threshold", channel.threshold);
+        tb.add("weightRGB", toString(channel.workingConfig.weights));
+        tb.add("samplePos",
+               aidl::com::google::hardware::pixel::display::toString(
+                       channel.workingConfig.samplePos));
+        result.append(tb.build().c_str());
+    }
+
+    result.appendFormat("\n");
+}
+
+void HistogramDevice::initChannels(uint8_t channelCount,
+                                   const std::vector<uint8_t> &reservedChannels) {
+    mChannels.resize(channelCount);
+    ALOGI("%s: init histogram with %d channels", __func__, channelCount);
+
+    for (const uint8_t reservedChannelId : reservedChannels) {
+        if (reservedChannelId < mChannels.size()) {
+            std::scoped_lock channelLock(mChannels[reservedChannelId].channelInfoMutex);
+            mChannels[reservedChannelId].status = ChannelStatus_t::RESERVED;
+        }
+    }
+
+    std::scoped_lock lock(mAllocatorMutex);
+    for (uint8_t channelId = 0; channelId < channelCount; ++channelId) {
+        std::scoped_lock channelLock(mChannels[channelId].channelInfoMutex);
+
+        if (mChannels[channelId].status == ChannelStatus_t::RESERVED) {
+            ALOGI("%s: histogram channel #%u: reserved for driver", __func__, (int)channelId);
+            continue;
+        }
+
+        mFreeChannels.push(channelId);
+    }
+}
+
+void HistogramDevice::initHistogramCapability(bool supportMultiChannel) {
+    ExynosDisplayDrmInterface *moduleDisplayInterface =
+            static_cast<ExynosDisplayDrmInterface *>(mDisplay->mDisplayInterface.get());
+
+    if (!moduleDisplayInterface) {
+        ALOGE("%s: failed to get ExynosDisplayDrmInterface (nullptr), cannot get panel full "
+              "resolution",
+              __func__);
+        mHistogramCapability.fullResolutionWidth = 0;
+        mHistogramCapability.fullResolutionHeight = 0;
+    } else {
+        mHistogramCapability.fullResolutionWidth =
+                moduleDisplayInterface->getPanelFullResolutionHSize();
+        mHistogramCapability.fullResolutionHeight =
+                moduleDisplayInterface->getPanelFullResolutionVSize();
+    }
+    ALOGI("%s: full screen roi: (0,0)x(%dx%d)", __func__, mHistogramCapability.fullResolutionWidth,
+          mHistogramCapability.fullResolutionHeight);
+
+    mHistogramCapability.channelCount = mChannels.size();
+    mHistogramCapability.supportMultiChannel = supportMultiChannel;
+
+    initSupportSamplePosList();
+}
+
+void HistogramDevice::initSupportSamplePosList() {
+    mHistogramCapability.supportSamplePosList.push_back(HistogramSamplePos::PRE_POSTPROC);
+    mHistogramCapability.supportSamplePosList.push_back(HistogramSamplePos::POST_POSTPROC);
+}
+
+ndk::ScopedAStatus HistogramDevice::configHistogram(const ndk::SpAIBinder &token,
+                                                    const HistogramConfig &histogramConfig,
+                                                    HistogramErrorCode *histogramErrorCode,
+                                                    bool isReconfig) {
+    /* validate the argument (histogramErrorCode) */
+    if (!histogramErrorCode) {
+        ALOGE("%s: binder error, histogramErrorCode is nullptr", __func__);
+        return ndk::ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
+    }
+
+    /* default histogramErrorCode: no error */
+    *histogramErrorCode = HistogramErrorCode::NONE;
+
+    /* validate the argument (token) */
+    if (token.get() == nullptr) {
+        ALOGE("%s: BAD_TOKEN, token is nullptr", __func__);
+        *histogramErrorCode = HistogramErrorCode::BAD_TOKEN;
+        return ndk::ScopedAStatus::ok();
+    }
+
+    /* validate the argument (histogramConfig) */
+    if ((*histogramErrorCode = validateHistogramConfig(histogramConfig)) !=
+        HistogramErrorCode::NONE) {
+        return ndk::ScopedAStatus::ok();
+    }
+
+    /* threshold is calculated based on the roi coordinates rather than configured by the client */
+    int threshold = calculateThreshold(histogramConfig.roi);
+
+    {
+        ATRACE_BEGIN("getOrAcquireChannelId");
+        uint8_t channelId;
+        std::scoped_lock lock(mAllocatorMutex);
+
+        /* isReconfig is false: registerHistogram, need to allcoate the histogram channel
+         * isReconfig is true: reconfigHistogram, already registered, only need to get channel id
+         */
+        if (!isReconfig) {
+            if ((*histogramErrorCode = acquireChannelLocked(token, channelId)) !=
+                HistogramErrorCode::NONE) {
+                ATRACE_END();
+                return ndk::ScopedAStatus::ok();
+            }
+        } else {
+            if ((*histogramErrorCode = getChannelIdByTokenLocked(token, channelId)) !=
+                HistogramErrorCode::NONE) {
+                ATRACE_END();
+                return ndk::ScopedAStatus::ok();
+            }
+        }
+        ATRACE_END();
+
+        /* store the histogram information, and mark the channel ready for atomic commit by setting
+         * the status to CONFIG_PENDING */
+        fillupChannelInfo(channelId, token, histogramConfig, threshold);
+
+        if (!isReconfig) {
+            /* link the binder object (token) to the death recipient. When the binder object is
+             * destructed, the callback function histogramOnBinderDied can release the histogram
+             * resources automatically. */
+            binder_status_t status;
+            if ((status = AIBinder_linkToDeath(token.get(), mDeathRecipient,
+                                               &mTokenInfoMap[token.get()]))) {
+                /* Not return error due to the AIBinder_linkToDeath because histogram function can
+                 * still work */
+                ALOGE("%s: histogram channel #%u: AIBinder_linkToDeath error %d", __func__,
+                      channelId, status);
+            }
+        }
+    }
+
+    if (!mDisplay->isPowerModeOff()) {
+        mDisplay->mDevice->onRefresh(mDisplay->mDisplayId);
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+void HistogramDevice::getHistogramData(uint8_t channelId, std::vector<char16_t> *histogramBuffer,
+                                       HistogramErrorCode *histogramErrorCode) {
+    ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
+    int32_t ret;
+    ExynosDisplayDrmInterface *moduleDisplayInterface =
+            static_cast<ExynosDisplayDrmInterface *>(mDisplay->mDisplayInterface.get());
+    if (!moduleDisplayInterface) {
+        *histogramErrorCode = HistogramErrorCode::BAD_HIST_DATA;
+        ALOGE("%s: histogram channel #%u: BAD_HIST_DATA, moduleDisplayInterface is nullptr",
+              __func__, channelId);
+        return;
+    }
+
+    ChannelInfo &channel = mChannels[channelId];
+
+    std::unique_lock<std::mutex> lock(channel.histDataCollectingMutex);
+
+    /* Check if the previous queryHistogram is finished */
+    if (channel.histDataCollecting) {
+        *histogramErrorCode = HistogramErrorCode::BAD_HIST_DATA;
+        ALOGE("%s: histogram channel #%u: BAD_HIST_DATA, previous %s not finished", __func__,
+              channelId, __func__);
+        return;
+    }
+
+    /* Send the ioctl request (histogram_channel_request_ioctl) which allocate the drm event and
+     * send back the drm event with data when available. */
+    if ((ret = moduleDisplayInterface->sendHistogramChannelIoctl(HistogramChannelIoctl_t::REQUEST,
+                                                                 channelId)) != NO_ERROR) {
+        *histogramErrorCode = HistogramErrorCode::BAD_HIST_DATA;
+        ALOGE("%s: histogram channel #%u: BAD_HIST_DATA, sendHistogramChannelIoctl (REQUEST) "
+              "error "
+              "(%d)",
+              __func__, channelId, ret);
+        return;
+    }
+    channel.histDataCollecting = true;
+
+    {
+        ATRACE_NAME(String8::format("waitDrmEvent #%u", channelId).c_str());
+        /* Wait until the condition variable is notified or timeout. */
+        channel.histDataCollecting_cv.wait_for(lock, std::chrono::milliseconds(50),
+                                               [this, &channel]() {
+                                                   return (!mDisplay->isPowerModeOff() &&
+                                                           !channel.histDataCollecting);
+                                               });
+    }
+
+    /* If the histDataCollecting is not cleared, check the reason and clear the histogramBuffer.
+     */
+    if (channel.histDataCollecting) {
+        if (mDisplay->isPowerModeOff()) {
+            *histogramErrorCode = HistogramErrorCode::DISPLAY_POWEROFF;
+            ALOGW("%s: histogram channel #%u: DISPLAY_POWEROFF, histogram is not available "
+                  "when "
+                  "display is off",
+                  __func__, channelId);
+        } else {
+            *histogramErrorCode = HistogramErrorCode::BAD_HIST_DATA;
+            ALOGE("%s: histogram channel #%u: BAD_HIST_DATA, no histogram channel event is "
+                  "handled",
+                  __func__, channelId);
+        }
+
+        /* Cancel the histogram data request */
+        ALOGI("%s: histogram channel #%u: cancel histogram data request", __func__, channelId);
+        if ((ret = moduleDisplayInterface
+                           ->sendHistogramChannelIoctl(HistogramChannelIoctl_t::CANCEL,
+                                                       channelId)) != NO_ERROR) {
+            ALOGE("%s: histogram channel #%u: sendHistogramChannelIoctl (CANCEL) error (%d)",
+                  __func__, channelId, ret);
+        }
+
+        channel.histDataCollecting = false;
+        return;
+    }
+
+    if (mDisplay->isSecureContentPresenting()) {
+        ALOGV("%s: histogram channel #%u: DRM_PLAYING, histogram is not available when secure "
+              "content is presenting",
+              __func__, channelId);
+        *histogramErrorCode = HistogramErrorCode::DRM_PLAYING;
+        return;
+    }
+
+    /* Copy the histogram data from histogram info to histogramBuffer */
+    histogramBuffer->assign(channel.histData, channel.histData + HISTOGRAM_BIN_COUNT);
+}
+
+int HistogramDevice::parseDrmEvent(void *event, uint8_t &channelId, char16_t *&buffer) const {
+    channelId = 0;
+    buffer = nullptr;
+    return INVALID_OPERATION;
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::acquireChannelLocked(
+        const ndk::SpAIBinder &token, uint8_t &channelId) {
+    ATRACE_CALL();
+    if (mFreeChannels.size() == 0) {
+        ALOGE("%s: NO_CHANNEL_AVAILABLE, there is no histogram channel available", __func__);
+        return HistogramErrorCode::NO_CHANNEL_AVAILABLE;
+    }
+
+    if (mTokenInfoMap.find(token.get()) != mTokenInfoMap.end()) {
+        ALOGE("%s: BAD_TOKEN, token (%p) is already registered", __func__, token.get());
+        return HistogramErrorCode::BAD_TOKEN;
+    }
+
+    /* Acquire free channel id from the free list */
+    channelId = mFreeChannels.front();
+    mFreeChannels.pop();
+    mTokenInfoMap[token.get()] = {.channelId = channelId, .histogramDevice = this, .token = token};
+
+    return HistogramErrorCode::NONE;
+}
+
+void HistogramDevice::releaseChannelLocked(uint8_t channelId) {
+    /* Add the channel id back to the free list and cleanup the channel info with status set to
+     * DISABLE_PENDING */
+    mFreeChannels.push(channelId);
+    cleanupChannelInfo(channelId);
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::getChannelIdByTokenLocked(
+        const ndk::SpAIBinder &token, uint8_t &channelId) {
+    if (mTokenInfoMap.find(token.get()) == mTokenInfoMap.end()) {
+        ALOGE("%s: BAD_TOKEN, token (%p) is not registered", __func__, token.get());
+        return HistogramErrorCode::BAD_TOKEN;
+    }
+
+    channelId = mTokenInfoMap[token.get()].channelId;
+
+    return HistogramErrorCode::NONE;
+}
+
+void HistogramDevice::cleanupChannelInfo(uint8_t channelId) {
+    ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
+    ChannelInfo &channel = mChannels[channelId];
+    std::scoped_lock lock(channel.channelInfoMutex);
+    channel.status = ChannelStatus_t::DISABLE_PENDING;
+    channel.token = nullptr;
+    channel.pid = -1;
+    channel.requestedRoi = {.left = 0, .top = 0, .right = 0, .bottom = 0};
+    channel.workingConfig = {.roi.left = 0,
+                             .roi.top = 0,
+                             .roi.right = 0,
+                             .roi.bottom = 0,
+                             .weights.weightR = 0,
+                             .weights.weightG = 0,
+                             .weights.weightB = 0,
+                             .samplePos = HistogramSamplePos::POST_POSTPROC};
+    channel.threshold = 0;
+}
+
+void HistogramDevice::fillupChannelInfo(uint8_t channelId, const ndk::SpAIBinder &token,
+                                        const HistogramConfig &histogramConfig, int threshold) {
+    ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
+    ChannelInfo &channel = mChannels[channelId];
+    std::scoped_lock lock(channel.channelInfoMutex);
+    channel.status = ChannelStatus_t::CONFIG_PENDING;
+    channel.token = token;
+    channel.pid = AIBinder_getCallingPid();
+    channel.requestedRoi = histogramConfig.roi;
+    channel.workingConfig = histogramConfig;
+    channel.workingConfig.roi = {};
+    channel.threshold = threshold;
+}
+
+int HistogramDevice::prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq,
+                                          uint8_t channelId) {
+    int ret = NO_ERROR;
+    ExynosDisplayDrmInterface *moduleDisplayInterface =
+            static_cast<ExynosDisplayDrmInterface *>(mDisplay->mDisplayInterface.get());
+    if (!moduleDisplayInterface) {
+        HWC_LOGE(mDisplay, "%s: failed to get ExynosDisplayDrmInterface (nullptr)", __func__);
+        return -EINVAL;
+    }
+
+    ChannelInfo &channel = mChannels[channelId];
+    std::scoped_lock lock(channel.channelInfoMutex);
+
+    if (channel.status == ChannelStatus_t::CONFIG_COMMITTED ||
+        channel.status == ChannelStatus_t::CONFIG_PENDING) {
+        HistogramRoiRect workingRoi;
+
+        /* calculate the roi based on the current active resolution */
+        ret = convertRoiLocked(moduleDisplayInterface, channel.requestedRoi, workingRoi);
+        if (ret == -EAGAIN) {
+            /* postpone the histogram config to next atomic commit */
+            ALOGD("%s: histogram channel #%u: set status (CONFIG_PENDING)", __func__, channelId);
+            channel.status = ChannelStatus_t::CONFIG_PENDING;
+            return NO_ERROR;
+        } else if (ret) {
+            ALOGE("%s: histogram channel #%u: failed to convert to workingRoi, ret: %d", __func__,
+                  channelId, ret);
+            channel.status = ChannelStatus_t::CONFIG_ERROR;
+            return ret;
+        }
+
+        /* If the channel status is CONFIG_COMMITTED, also check if the working roi needs to be
+         * updated due to resolution changed. */
+        if (channel.status == ChannelStatus_t::CONFIG_COMMITTED) {
+            if (LIKELY(channel.workingConfig.roi == workingRoi)) {
+                return NO_ERROR;
+            } else {
+                ALOGI("%s: histogram channel #%u: detect resolution changed, update roi setting",
+                      __func__, channelId);
+            }
+        }
+
+        /* Adopt the roi calculated from convertRoiLocked */
+        channel.workingConfig.roi = workingRoi;
+
+        /* Create histogram drm config struct (platform dependent) */
+        std::shared_ptr<void> blobData;
+        size_t blobLength = 0;
+        if ((ret = createHistogramDrmConfigLocked(channel, blobData, blobLength))) {
+            ALOGE("%s: histogram channel #%u: failed to createHistogramDrmConfig, ret: %d",
+                  __func__, channelId, ret);
+            channel.status = ChannelStatus_t::CONFIG_ERROR;
+            return ret;
+        }
+
+        /* Add histogram blob to atomic commit */
+        ret = moduleDisplayInterface->setDisplayHistogramChannelSetting(drmReq, channelId,
+                                                                        blobData.get(), blobLength);
+        if (ret == NO_ERROR) {
+            channel.status = ChannelStatus_t::CONFIG_BLOB_ADDED;
+        } else {
+            ALOGE("%s: histogram channel #%u: failed to setDisplayHistogramChannelSetting, ret: %d",
+                  __func__, channelId, ret);
+            channel.status = ChannelStatus_t::CONFIG_ERROR;
+            return ret;
+        }
+    } else if (channel.status == ChannelStatus_t::DISABLE_PENDING) {
+        ret = moduleDisplayInterface->clearDisplayHistogramChannelSetting(drmReq, channelId);
+        if (ret == NO_ERROR) {
+            channel.status = ChannelStatus_t::DISABLE_BLOB_ADDED;
+        } else {
+            ALOGE("%s: histogram channel #%u: failed to clearDisplayHistogramChannelSetting, ret: "
+                  "%d",
+                  __func__, channelId, ret);
+            channel.status = ChannelStatus_t::DISABLE_ERROR;
+        }
+    }
+
+    return ret;
+}
+
+int HistogramDevice::createHistogramDrmConfigLocked(const ChannelInfo &channel,
+                                                    std::shared_ptr<void> &configPtr,
+                                                    size_t &length) const {
+    /* Default implementation doesn't know the histogram channel config struct in the kernel.
+     * Cannot allocate and initialize the channel config. */
+    configPtr = nullptr;
+    length = 0;
+    return INVALID_OPERATION;
+}
+
+int HistogramDevice::convertRoiLocked(ExynosDisplayDrmInterface *moduleDisplayInterface,
+                                      const HistogramRoiRect &requestedRoi,
+                                      HistogramRoiRect &workingRoi) const {
+    int32_t activeH = moduleDisplayInterface->getActiveModeHDisplay();
+    int32_t activeV = moduleDisplayInterface->getActiveModeVDisplay();
+    int32_t panelH = mHistogramCapability.fullResolutionWidth;
+    int32_t panelV = mHistogramCapability.fullResolutionHeight;
+
+    ALOGV("%s: active: (%dx%d), panel: (%dx%d)", __func__, activeH, activeV, panelH, panelV);
+
+    if (panelH < activeH || activeH < 0 || panelV < activeV || activeV < 0) {
+        ALOGE("%s: failed to convert roi, active: (%dx%d), panel: (%dx%d)", __func__, activeH,
+              activeV, panelH, panelV);
+        return -EINVAL;
+    } else if (activeH == 0 || activeV == 0) {
+        /* mActiveModeState is not initialized, postpone histogram config to next atomic commit */
+        ALOGW("%s: mActiveModeState is not initialized, active: (%dx%d), postpone histogram config "
+              "to next atomic commit",
+              __func__, activeH, activeV);
+        return -EAGAIN;
+    }
+
+    /* Linear transform from full resolution to active resolution */
+    workingRoi.left = requestedRoi.left * activeH / panelH;
+    workingRoi.top = requestedRoi.top * activeV / panelV;
+    workingRoi.right = requestedRoi.right * activeH / panelH;
+    workingRoi.bottom = requestedRoi.bottom * activeV / panelV;
+
+    ALOGV("%s: working roi: %s", __func__, toString(workingRoi).c_str());
+
+    return NO_ERROR;
+}
+
+void HistogramDevice::dumpHistogramCapability(String8 &result) const {
+    /* Append the histogram capability info to the dump string */
+    result.appendFormat("Histogram capability:\n");
+    result.appendFormat("\tsupportMultiChannel: %s\n",
+                        mHistogramCapability.supportMultiChannel ? "true" : "false");
+    result.appendFormat("\tchannelCount: %d\n", mHistogramCapability.channelCount);
+    result.appendFormat("\tfullscreen roi: (0,0)x(%dx%d)\n",
+                        mHistogramCapability.fullResolutionWidth,
+                        mHistogramCapability.fullResolutionHeight);
+    result.appendFormat("\tsupportSamplePosList:");
+    for (HistogramSamplePos samplePos : mHistogramCapability.supportSamplePosList) {
+        result.appendFormat(" %s",
+                            aidl::com::google::hardware::pixel::display::toString(samplePos)
+                                    .c_str());
+    }
+    result.appendFormat("\n");
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramConfig(
+        const HistogramConfig &histogramConfig) const {
+    HistogramErrorCode ret;
+
+    if ((ret = validateHistogramRoi(histogramConfig.roi)) != HistogramErrorCode::NONE ||
+        (ret = validateHistogramWeights(histogramConfig.weights)) != HistogramErrorCode::NONE ||
+        (ret = validateHistogramSamplePos(histogramConfig.samplePos)) != HistogramErrorCode::NONE) {
+        return ret;
+    }
+
+    return HistogramErrorCode::NONE;
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramRoi(
+        const HistogramRoiRect &roi) const {
+    if ((roi.left < 0) || (roi.top < 0) || (roi.right - roi.left <= 0) ||
+        (roi.bottom - roi.top <= 0) || (roi.right > mHistogramCapability.fullResolutionWidth) ||
+        (roi.bottom > mHistogramCapability.fullResolutionHeight)) {
+        ALOGE("%s: BAD_ROI, roi: %s, full screen roi: (0,0)x(%dx%d)", __func__,
+              toString(roi).c_str(), mHistogramCapability.fullResolutionWidth,
+              mHistogramCapability.fullResolutionHeight);
+        return HistogramErrorCode::BAD_ROI;
+    }
+
+    return HistogramErrorCode::NONE;
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramWeights(
+        const HistogramWeights &weights) const {
+    if ((weights.weightR + weights.weightG + weights.weightB) != WEIGHT_SUM) {
+        ALOGE("%s: BAD_WEIGHT, weights(%d,%d,%d)\n", __func__, weights.weightR, weights.weightG,
+              weights.weightB);
+        return HistogramErrorCode::BAD_WEIGHT;
+    }
+
+    return HistogramErrorCode::NONE;
+}
+
+HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramSamplePos(
+        const HistogramSamplePos &samplePos) const {
+    for (HistogramSamplePos mSamplePos : mHistogramCapability.supportSamplePosList) {
+        if (samplePos == mSamplePos) {
+            return HistogramErrorCode::NONE;
+        }
+    }
+
+    ALOGE("%s: BAD_POSITION, samplePos is %d", __func__, (int)samplePos);
+    return HistogramErrorCode::BAD_POSITION;
+}
+
+int HistogramDevice::calculateThreshold(const HistogramRoiRect &roi) {
+    // TODO: b/294491895 - Check if the threshold plus one really need it
+    int threshold = ((roi.bottom - roi.top) * (roi.right - roi.left)) >> 16;
+    return threshold + 1;
+}
+
+std::string HistogramDevice::toString(const ChannelStatus_t &status) {
+    switch (status) {
+        case ChannelStatus_t::RESERVED:
+            return "RESERVED";
+        case ChannelStatus_t::DISABLED:
+            return "DISABLED";
+        case ChannelStatus_t::CONFIG_PENDING:
+            return "CONFIG_PENDING";
+        case ChannelStatus_t::CONFIG_BLOB_ADDED:
+            return "CONFIG_BLOB_ADDED";
+        case ChannelStatus_t::CONFIG_COMMITTED:
+            return "CONFIG_COMMITTED";
+        case ChannelStatus_t::CONFIG_ERROR:
+            return "CONFIG_ERROR";
+        case ChannelStatus_t::DISABLE_PENDING:
+            return "DISABLE_PENDING";
+        case ChannelStatus_t::DISABLE_BLOB_ADDED:
+            return "DISABLE_BLOB_ADDED";
+        case ChannelStatus_t::DISABLE_ERROR:
+            return "DISABLE_ERROR";
+    }
+
+    return "UNDEFINED";
+}
+
+std::string HistogramDevice::toString(const HistogramRoiRect &roi) {
+    std::ostringstream os;
+    os << "(" << roi.left << "," << roi.top << ")";
+    os << "x";
+    os << "(" << roi.right << "," << roi.bottom << ")";
+    return os.str();
+}
+
+std::string HistogramDevice::toString(const HistogramWeights &weights) {
+    std::ostringstream os;
+    os << "(";
+    os << (int)weights.weightR << ",";
+    os << (int)weights.weightG << ",";
+    os << (int)weights.weightB;
+    os << ")";
+    return os.str();
+}
diff --git a/libhwc2.1/libdevice/HistogramDevice.h b/libhwc2.1/libdevice/HistogramDevice.h
new file mode 100644
index 0000000..ee74cae
--- /dev/null
+++ b/libhwc2.1/libdevice/HistogramDevice.h
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/Rect.h>
+#include <aidl/com/google/hardware/pixel/display/HistogramCapability.h>
+#include <aidl/com/google/hardware/pixel/display/HistogramConfig.h>
+#include <aidl/com/google/hardware/pixel/display/HistogramErrorCode.h>
+#include <aidl/com/google/hardware/pixel/display/HistogramSamplePos.h>
+#include <aidl/com/google/hardware/pixel/display/Weight.h>
+#include <android-base/thread_annotations.h>
+#include <drm/samsung_drm.h>
+#include <utils/String8.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+#include <unordered_map>
+
+#include "ExynosDisplay.h"
+#include "ExynosDisplayDrmInterface.h"
+#include "drmcrtc.h"
+
+using namespace android;
+
+class HistogramDevice {
+public:
+    using HistogramCapability = aidl::com::google::hardware::pixel::display::HistogramCapability;
+    using HistogramConfig = aidl::com::google::hardware::pixel::display::HistogramConfig;
+    using HistogramErrorCode = aidl::com::google::hardware::pixel::display::HistogramErrorCode;
+    using HistogramRoiRect = aidl::android::hardware::graphics::common::Rect;
+    using HistogramSamplePos = aidl::com::google::hardware::pixel::display::HistogramSamplePos;
+    using HistogramWeights = aidl::com::google::hardware::pixel::display::Weight;
+    using HistogramChannelIoctl_t = ExynosDisplayDrmInterface::HistogramChannelIoctl_t;
+
+    /* Histogram weight constraint: weightR + weightG + weightB = WEIGHT_SUM */
+    static constexpr size_t WEIGHT_SUM = 1024;
+
+    /* Histogram channel status */
+    enum class ChannelStatus_t : uint32_t {
+        /* occupied by the driver for specific usage such as LHBM */
+        RESERVED = 0,
+
+        /* channel is off */
+        DISABLED,
+
+        /* channel config is ready and requires to be added into an atomic commit */
+        CONFIG_PENDING,
+
+        /* channel config (blob) is added to an atomic commit but not committed yet */
+        CONFIG_BLOB_ADDED,
+
+        /* channel config is committed to drm driver successfully */
+        CONFIG_COMMITTED,
+
+        /* channel config has error */
+        CONFIG_ERROR,
+
+        /* channel is released and requires an atomic commit to cleanup completely */
+        DISABLE_PENDING,
+
+        /* channel is released and the cleanup blob is added but not committed yet */
+        DISABLE_BLOB_ADDED,
+
+        /* channel disable has error */
+        DISABLE_ERROR,
+    };
+
+    struct ChannelInfo {
+        /* protect the channel info fields */
+        mutable std::mutex channelInfoMutex;
+
+        /* protect histDataCollecting variable */
+        mutable std::mutex histDataCollectingMutex;
+
+        /* track the channel status */
+        ChannelStatus_t status GUARDED_BY(channelInfoMutex);
+
+        /* token passed in by the histogram client */
+        ndk::SpAIBinder token GUARDED_BY(channelInfoMutex);
+
+        /* histogram client process id */
+        pid_t pid GUARDED_BY(channelInfoMutex);
+
+        /* requested roi from the client by registerHistogram or reconfigHistogram */
+        HistogramRoiRect requestedRoi GUARDED_BY(channelInfoMutex);
+
+        /* histogram config that would be applied to hardware, the requestedRoi may be different
+         * from the roi described in workingConfig due to RRS (Runtime Resolution Switch) */
+        HistogramConfig workingConfig GUARDED_BY(channelInfoMutex);
+
+        /* histogram threshold that would be applied to the hardware which is used to prevent the
+         * histogram data (16 bits) overflow */
+        int threshold GUARDED_BY(channelInfoMutex);
+
+        /* histogram data would be stored as part of the channel info */
+        uint16_t histData[HISTOGRAM_BIN_COUNT];
+        bool histDataCollecting; // GUARDED_BY(histDataCollectingMutex);
+        std::condition_variable histDataCollecting_cv;
+
+        ChannelInfo();
+        ChannelInfo(const ChannelInfo &other);
+    };
+
+    /* TokenInfo is not only used to stored the corresponding channel id but also passed to the
+     * binderdied callback */
+    struct TokenInfo {
+        /* corresponding channel id of the token */
+        uint8_t channelId;
+
+        /* pointer to the HistogramDevice, binderdied callback would use this pointer to cleanup the
+         * channel in HistogramDevice by the member function unregisterHistogram */
+        HistogramDevice *histogramDevice;
+
+        /* binderdied callback would call unregisterHistogram with this token */
+        ndk::SpAIBinder token;
+    };
+
+    /**
+     * HistogramDevice
+     *
+     * Construct the HistogramDevice to mange histogram channel.
+     *
+     * @display display pointer which would be stored in mDisplay.
+     * @channelCount number of the histogram channels in the system.
+     * @reservedChannels a list of channel id that are reserved by the driver.
+     */
+    explicit HistogramDevice(ExynosDisplay *display, uint8_t channelCount,
+                             std::vector<uint8_t> reservedChannels);
+
+    /**
+     * ~HistogramDevice
+     *
+     * Destruct the HistogramDevice.
+     */
+    virtual ~HistogramDevice();
+
+    /**
+     * initDrm
+     *
+     * Get histogram info from crtc property and initialize the mHistogramCapability.
+     *     1. The available histogram channel bitmask.
+     *     2. Determine kernel support multi channel property or not.
+     *
+     * @crtc drm crtc object which would contain histogram related information.
+     */
+    void initDrm(const DrmCrtc &crtc);
+
+    /**
+     * getHistogramCapability
+     *
+     * Return the histogram capability for the system.
+     *
+     * @histogramCapability: describe the histogram capability for the system.
+     * @return ok() when the interface is supported and arguments are valid, else otherwise.
+     */
+    ndk::ScopedAStatus getHistogramCapability(HistogramCapability *histogramCapability) const;
+
+    /**
+     * registerHistogram
+     *
+     * Register the histogram sampling config, and allocate a histogram channel if available.
+     * If the display is not turned on, just store the histogram config. Otherwise, trigger the
+     * onRefresh call to force the config take effect, and then the DPU hardware will continuously
+     * sample the histogram data.
+     *
+     * @token binder object created by the client whose lifetime should be equal to the client. When
+     * the binder object is destructed, the unregisterHistogram would be called automatically. Token
+     * serves as the handle in every histogram operation.
+     * @histogramConfig histogram config from the client.
+     * @histogramErrorCode NONE when no error, or else otherwise. Client should retry when failed.
+     * @return ok() when the interface is supported, or EX_UNSUPPORTED_OPERATION when the interface
+     * is not supported yet.
+     */
+    ndk::ScopedAStatus registerHistogram(const ndk::SpAIBinder &token,
+                                         const HistogramConfig &histogramConfig,
+                                         HistogramErrorCode *histogramErrorCode);
+
+    /**
+     * queryHistogram
+     *
+     * Query the histogram data from the corresponding channel of the token.
+     *
+     * @token is the handle registered via registerHistogram which would be used to identify the
+     * channel.
+     * @histogramBuffer 256 * 16 bits buffer to store the luma counts return by the histogram
+     * hardware.
+     * @histogramErrorCode NONE when no error, or else otherwise. Client should examine this
+     * errorcode.
+     * @return ok() when the interface is supported, or EX_UNSUPPORTED_OPERATION when the interface
+     * is not supported yet.
+     */
+    ndk::ScopedAStatus queryHistogram(const ndk::SpAIBinder &token,
+                                      std::vector<char16_t> *histogramBuffer,
+                                      HistogramErrorCode *histogramErrorCode);
+
+    /**
+     * reconfigHistogram
+     *
+     * Change the histogram config for the corresponding channel of the token.
+     *
+     * @token is the handle registered via registerHistogram which would be used to identify the
+     * channel.
+     * @histogramConfig histogram config from the client.
+     * @histogramErrorCode NONE when no error, or else otherwise. Client should examine this
+     * errorcode.
+     * @return ok() when the interface is supported, or EX_UNSUPPORTED_OPERATION when the interface
+     * is not supported yet.
+     */
+    ndk::ScopedAStatus reconfigHistogram(const ndk::SpAIBinder &token,
+                                         const HistogramConfig &histogramConfig,
+                                         HistogramErrorCode *histogramErrorCode);
+
+    /**
+     * unregisterHistogram
+     *
+     * Release the corresponding channel of the token and add the channel id to free channel list.
+     *
+     * @token is the handle registered via registerHistogram which would be used to identify the
+     * channel.
+     * @histogramErrorCode NONE when no error, or else otherwise. Client should examine this
+     * errorcode.
+     * @return ok() when the interface is supported, or EX_UNSUPPORTED_OPERATION when the interface
+     * is not supported yet.
+     */
+    ndk::ScopedAStatus unregisterHistogram(const ndk::SpAIBinder &token,
+                                           HistogramErrorCode *histogramErrorCode);
+
+    /**
+     * handleDrmEvent
+     *
+     * Handle the histogram channel drm event (EXYNOS_DRM_HISTOGRAM_CHANNEL_EVENT) and copy the
+     * histogram data from event struct to channel info.
+     *
+     * @event histogram channel drm event pointer (struct exynos_drm_histogram_channel_event *)
+     */
+    void handleDrmEvent(void *event);
+
+    /**
+     * prepareAtomicCommit
+     *
+     * Prepare the histogram atomic commit for each channel (see prepareChannelCommit).
+     *
+     * @drmReq drm atomic request object
+     */
+    void prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq);
+
+    /**
+     * postAtomicCommit
+     *
+     * After the atomic commit is done, update the channel status as below.
+     * Channel_Status:
+     *     CONFIG_BLOB_ADDED  -> CONFIG_COMMITTED
+     *     DISABLE_BLOB_ADDED -> DISABLED
+     */
+    void postAtomicCommit();
+
+    /**
+     * dump
+     *
+     * Dump every histogram channel information.
+     *
+     * @result histogram channel dump information would be appended to this string
+     */
+    void dump(String8 &result) const;
+
+protected:
+    HistogramCapability mHistogramCapability;
+
+private:
+    mutable std::mutex mAllocatorMutex;
+    std::queue<uint8_t> mFreeChannels GUARDED_BY(mAllocatorMutex); // free channel list
+    std::unordered_map<AIBinder *, TokenInfo> mTokenInfoMap GUARDED_BY(mAllocatorMutex);
+    std::vector<ChannelInfo> mChannels;
+    ExynosDisplay *mDisplay = nullptr;
+
+    /* Death recipient for the binderdied callback, would be deleted in the destructor */
+    AIBinder_DeathRecipient *mDeathRecipient = nullptr;
+
+    /**
+     * initChannels
+     *
+     * Allocate channelCount channels and initialize the channel status for every channel.
+     *
+     * @channelCount number of channels in the system including the reserved channels.
+     * @reservedChannels a list of channel id that are reserved by the driver.
+     */
+    void initChannels(uint8_t channelCount, const std::vector<uint8_t> &reservedChannels);
+
+    /**
+     * initHistogramCapability
+     *
+     * Initialize the histogramCapability which would be queried by the client (see
+     * getHistogramCapability).
+     *
+     * @supportMultiChannel true if the kernel support multi channel property, false otherwise.
+     */
+    void initHistogramCapability(bool supportMultiChannel);
+
+    /**
+     * initSupportSamplePosList
+     *
+     * Initialize the supported sample position list.
+     */
+    virtual void initSupportSamplePosList();
+
+    /**
+     * configHistogram
+     *
+     * Implementation of the registerHistogram and reconfigHistogram.
+     *
+     * @token binder object created by the client.
+     * @histogramConfig histogram config requested by the client.
+     * @histogramErrorCode::NONE when success, or else otherwise.
+     * @isReconfig is true if it is not the register request, only need to change the config.
+     * @return ok() when the interface is supported, or else otherwise.
+     */
+    ndk::ScopedAStatus configHistogram(const ndk::SpAIBinder &token,
+                                       const HistogramConfig &histogramConfig,
+                                       HistogramErrorCode *histogramErrorCode, bool isReconfig);
+
+    /**
+     * getHistogramData
+     *
+     * Get the histogram data by sending ioctl request which will allocate the drm event for
+     * histogram, and wait on the condition variable histDataCollecting_cv until the drm event is
+     * handled or timeout. Copy the histogram data from channel info to histogramBuffer.
+     *
+     * @channelId histogram channel id.
+     * @histogramBuffer AIDL created buffer which will be sent back to the client.
+     * @histogramErrorCode::NONE when success, or else otherwise.
+     */
+    void getHistogramData(uint8_t channelId, std::vector<char16_t> *histogramBuffer,
+                          HistogramErrorCode *histogramErrorCode);
+
+    /**
+     * parseDrmEvent
+     *
+     * Parse the histogram drm event (struct may vary between different platform), need to be
+     * overrided in the derived HistogramController class if needed. This function should get the
+     * histogram channel id and the histogram buffer address from the event struct.
+     *
+     * @event histogram drm event struct.
+     * @channelId stores the extracted channel id from the event.
+     * @buffer stores the extracted buffer address from the event.
+     * @return NO_ERROR on success, else otherwise.
+     */
+    virtual int parseDrmEvent(void *event, uint8_t &channelId, char16_t *&buffer) const;
+
+    /**
+     * acquireChannelLocked
+     *
+     * Acquire an available channel from the mFreeChannels, and record the token to channel id
+     * mapping info. Should be called with mAllocatorMutex held.
+     *
+     * @token binder object created by the client.
+     * @channelId store the acquired channel id.
+     * @return HistogramErrorCode::NONE when success, or else otherwise.
+     */
+    HistogramErrorCode acquireChannelLocked(const ndk::SpAIBinder &token, uint8_t &channelId)
+            REQUIRES(mAllocatorMutex);
+
+    /**
+     * releaseChannelLocked
+     *
+     * Find the corresponding channel id of the token and release the channel. Add the channel id to
+     * the mFreeChannels and cleanup the channel. Should be called with mAllocatorMutex held.
+     *
+     * @channelId the channel id to be cleanup.
+     */
+    void releaseChannelLocked(uint8_t channelId) REQUIRES(mAllocatorMutex);
+
+    /**
+     * getChannelIdByTokenLocked
+     *
+     * Convert the token to the channel id. Should be called with mAllocatorMutex held.
+     *
+     * @token binder object created by the client.
+     * @return HistogramErrorCode::NONE when success, or else otherwise.
+     */
+    HistogramErrorCode getChannelIdByTokenLocked(const ndk::SpAIBinder &token, uint8_t &channelId)
+            REQUIRES(mAllocatorMutex);
+
+    /**
+     * cleanupChannelInfo
+     *
+     * Cleanup the channel info and set status to DISABLE_PENDING which means need to wait
+     * for the atomic commit to release the kernel and hardware channel resources.
+     *
+     * @channelId the channel id to be cleanup.
+     */
+    void cleanupChannelInfo(uint8_t channelId);
+
+    /**
+     * fillupChannelInfo
+     *
+     * Fillup the channel info with the histogramConfig from the client, and set status to
+     * CONFIG_PENDING which means need to wait for the atomic commit to configure the
+     * channel.
+     *
+     * @channelId the channel id to be configured.
+     * @token binder object created by the client.
+     * @histogramConfig histogram config requested by the client.
+     * @threshold histogram threshold calculated from the roi.
+     */
+    void fillupChannelInfo(uint8_t channelId, const ndk::SpAIBinder &token,
+                           const HistogramConfig &histogramConfig, int threshold);
+
+    /**
+     * prepareChannelCommit
+     *
+     * For the histogram channel needed to be configured, prepare the histogram channel config into
+     * the struct histogram_channel_config which will be used to creating the drm blob in
+     * setDisplayHistogramChannelSetting.
+     * ChannelStatus_t:
+     *     CONFIG_PENDING -> CONFIG_BLOB_ADDED
+     *     CONFIG_DONE (detect roi needs update due to resolution change) -> CONFIG_BLOB_ADDED
+     *
+     * For the histogram channel needed to be disabled, call clearDisplayHistogramChannelSetting to
+     * disable.
+     * ChannelStatus_t:
+     *     DISABLE_PENDING -> DISABLE_BLOB_ADDED
+     *
+     * @drmReq drm atomic request object
+     * @channelId histogram channel id
+     * @return NO_ERROR on success, else otherwise
+     */
+    int prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq,
+                             uint8_t channelId);
+
+    /**
+     * createHistogramDrmConfigLocked
+     *
+     * Allocate and initialize the histogram config for the drm driver. Composer would trigger
+     * setDisplayHistogramChannelSetting and create the property blob with this config. The
+     * allcoated config should be deleted via deleteHistogramDrmConfig after the property blob
+     * is created. This function should be called with channelInfoMutex hold.
+     *
+     * @channel histogram channel.
+     * @configPtr shared pointer to the allocated histogram config struct.
+     * @length size of the histogram config.
+     * @return NO_ERROR on success, else otherwise
+     */
+    virtual int createHistogramDrmConfigLocked(const ChannelInfo &channel,
+                                               std::shared_ptr<void> &configPtr,
+                                               size_t &length) const
+            REQUIRES(channel.channelInfoMutex);
+
+    /**
+     * convertRoiLocked
+     *
+     * Linear transform the requested roi (based on panel full resolution) into the working roi
+     * (active resolution).
+     *
+     * @moduleDisplayInterface the displayInterface which contains the full resolution info
+     * @requestedRoi requested roi
+     * @workingRoi converted roi from the requested roi
+     * @return NO_ERROR on success, else otherwise
+     */
+    int convertRoiLocked(ExynosDisplayDrmInterface *moduleDisplayInterface,
+                         const HistogramRoiRect &requestedRoi, HistogramRoiRect &workingRoi) const;
+
+    void dumpHistogramCapability(String8 &result) const;
+
+    HistogramErrorCode validateHistogramConfig(const HistogramConfig &histogramConfig) const;
+    HistogramErrorCode validateHistogramRoi(const HistogramRoiRect &roi) const;
+    HistogramErrorCode validateHistogramWeights(const HistogramWeights &weights) const;
+    HistogramErrorCode validateHistogramSamplePos(const HistogramSamplePos &samplePos) const;
+
+    static int calculateThreshold(const HistogramRoiRect &roi);
+    static std::string toString(const ChannelStatus_t &status);
+    static std::string toString(const HistogramRoiRect &roi);
+    static std::string toString(const HistogramWeights &weights);
+};
diff --git a/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.cpp b/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.cpp
index beaf94c..fbf5fc4 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.cpp
+++ b/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.cpp
@@ -14,15 +14,18 @@
  * limitations under the License.
  */
 
-#include <drm/drm_mode.h>
 #include "ExynosDeviceDrmInterface.h"
-#include "ExynosDisplayDrmInterface.h"
-#include "ExynosHWCDebug.h"
+
+#include <drm/drm_mode.h>
+#include <drm/samsung_drm.h>
+#include <hardware/hwcomposer_defs.h>
+
 #include "ExynosDevice.h"
 #include "ExynosDisplay.h"
+#include "ExynosDisplayDrmInterface.h"
 #include "ExynosExternalDisplayModule.h"
-#include <hardware/hwcomposer_defs.h>
-#include <drm/samsung_drm.h>
+#include "ExynosHWCDebug.h"
+#include "HistogramController.h"
 
 void set_hwc_dpp_size_range(hwc_dpp_size_range &hwc_dpp_range, dpp_size_range &dpp_range) {
     hwc_dpp_range.min = dpp_range.min;
@@ -72,6 +75,8 @@
             static_cast<DrmEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->UnRegisterHistogramHandler(
             static_cast<DrmHistogramEventHandler *>(&mExynosDrmEventHandler));
+    mDrmDevice->event_listener()->UnRegisterHistogramChannelHandler(
+            static_cast<DrmHistogramChannelEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->UnRegisterTUIHandler(
             static_cast<DrmTUIEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->UnRegisterPanelIdleHandler(
@@ -92,6 +97,8 @@
             static_cast<DrmEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->RegisterHistogramHandler(
             static_cast<DrmHistogramEventHandler *>(&mExynosDrmEventHandler));
+    mDrmDevice->event_listener()->RegisterHistogramChannelHandler(
+            static_cast<DrmHistogramChannelEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->RegisterTUIHandler(
             static_cast<DrmTUIEventHandler *>(&mExynosDrmEventHandler));
     mDrmDevice->event_listener()->RegisterPanelIdleHandler(
@@ -240,6 +247,32 @@
     }
 }
 
+#if defined(EXYNOS_DRM_HISTOGRAM_CHANNEL_EVENT)
+void ExynosDeviceDrmInterface::ExynosDrmEventHandler::handleHistogramChannelEvent(void *event) {
+    struct exynos_drm_histogram_channel_event *histogram_channel_event =
+            (struct exynos_drm_histogram_channel_event *)event;
+
+    for (auto display : mExynosDevice->mDisplays) {
+        ExynosDisplayDrmInterface *displayInterface =
+                static_cast<ExynosDisplayDrmInterface *>(display->mDisplayInterface.get());
+        if (histogram_channel_event->crtc_id == displayInterface->getCrtcId()) {
+            if (display->mHistogramController) {
+                display->mHistogramController->handleDrmEvent(histogram_channel_event);
+            } else {
+                ALOGE("%s: no valid mHistogramController for crtc_id (%u)", __func__,
+                      histogram_channel_event->crtc_id);
+            }
+
+            return;
+        }
+    }
+
+    ALOGE("%s: no display with crtc_id (%u)", __func__, histogram_channel_event->crtc_id);
+}
+#else
+void ExynosDeviceDrmInterface::ExynosDrmEventHandler::handleHistogramChannelEvent(void *event) {}
+#endif
+
 void ExynosDeviceDrmInterface::ExynosDrmEventHandler::handleTUIEvent() {
     if (mDrmDevice->event_listener()->IsDrmInTUI()) {
         /* Received TUI Enter event */
diff --git a/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.h b/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.h
index 647407e..4465783 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.h
+++ b/libhwc2.1/libdisplayinterface/ExynosDeviceDrmInterface.h
@@ -39,11 +39,13 @@
     protected:
         class ExynosDrmEventHandler : public DrmEventHandler,
                                       public DrmHistogramEventHandler,
+                                      public DrmHistogramChannelEventHandler,
                                       public DrmTUIEventHandler,
                                       public DrmPanelIdleEventHandler {
         public:
             void handleEvent(uint64_t timestamp_us) override;
             void handleHistogramEvent(uint32_t crtc_id, void *bin) override;
+            void handleHistogramChannelEvent(void *event) override;
             void handleTUIEvent() override;
             void handleIdleEnterEvent(char const *event) override;
             void init(ExynosDevice *exynosDevice, DrmDevice *drmDevice);
diff --git a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
index 4d265b5..e8a4001 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
@@ -32,6 +32,7 @@
 #include "ExynosHWCHelper.h"
 #include "ExynosLayer.h"
 #include "ExynosPrimaryDisplay.h"
+#include "HistogramController.h"
 
 using namespace std::chrono_literals;
 using namespace SOC_VERSION;
@@ -761,6 +762,12 @@
 
     chosePreferredConfig();
 
+    // After chosePreferredConfig, the mDrmConnector->modes array is initialized, get the panel full
+    // resolution information here.
+    if (mExynosDisplay->mType == HWC_DISPLAY_PRIMARY) {
+        retrievePanelFullResolution();
+    }
+
     parseColorModeEnums(mDrmCrtc->color_mode_property());
     parseMipiSyncEnums(mDrmConnector->mipi_sync());
     updateMountOrientation();
@@ -772,6 +779,10 @@
         ALOGW("%s failed to init brightness controller", __func__);
     }
 
+    if (mExynosDisplay->mHistogramController) {
+        mExynosDisplay->mHistogramController->initDrm(*mDrmCrtc);
+    }
+
     return NO_ERROR;
 }
 
@@ -1232,8 +1243,9 @@
         return HWC2_ERROR_BAD_CONFIG;
     }
 
-    if ((mActiveModeState.blob_id != 0) &&
-        (mActiveModeState.mode.id() == config)) {
+    if (mDesiredModeState.needsModeSet()) {
+        ALOGI("Previous mode change %d request is not applied", mDesiredModeState.mode.id());
+    } else if ((mActiveModeState.blob_id != 0) && (mActiveModeState.mode.id() == config)) {
         ALOGD("%s:: same mode %d", __func__, config);
         /* trigger resetConfigRequestStateLocked() */
         mVsyncCallback.setDesiredVsyncPeriod(nsecsPerSec / mActiveModeState.mode.v_refresh());
@@ -1241,10 +1253,6 @@
         return HWC2_ERROR_NONE;
     }
 
-    if (mDesiredModeState.needsModeSet()) {
-        ALOGD("Previous mode change request is not applied");
-    }
-
     int32_t ret = HWC2_ERROR_NONE;
     DrmModeAtomicReq drmReq(this);
     uint32_t modeBlob = 0;
@@ -1368,18 +1376,6 @@
     return 0;
 }
 
-int32_t ExynosDisplayDrmInterface::getPanelResolution() {
-    std::lock_guard<std::recursive_mutex> lock(mDrmConnector->modesLock());
-
-    for (auto it = mDrmConnector->modes().begin(); it != mDrmConnector->modes().end(); it++) {
-        if (it->h_display() * it->v_display() > mPanelResolutionHsize * mPanelResolutionVsize) {
-            mPanelResolutionHsize = it->h_display();
-            mPanelResolutionVsize = it->v_display();
-        }
-    }
-    return 0;
-}
-
 int32_t ExynosDisplayDrmInterface::createModeBlob(const DrmMode &mode,
         uint32_t &modeBlob)
 {
@@ -1820,9 +1816,8 @@
     bool hasSecureFrameBuffer = false;
     bool hasM2mSecureLayerBuffer = false;
 
-    if (mExynosDisplay->isFrameUpdate()) {
-        mFrameCounter++;
-    }
+    mFrameCounter++;
+
     funcReturnCallback retCallback([&]() {
         if ((ret == NO_ERROR) && !drmReq.getError()) {
             mFBManager.flip(hasSecureFrameBuffer, hasM2mSecureLayerBuffer);
@@ -2016,9 +2011,14 @@
         flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
 
     /* For Histogram */
+    // TODO: b/300026478 - Skip setDisplayHistogramSetting when multi channel is enabled
     if (dqeEnable && (ret = setDisplayHistogramSetting(drmReq)) != 0) {
         HWC_LOGE(mExynosDisplay, "Failed to set display histogram setting (%d)", ret);
-        return ret;
+    }
+
+    /* For multichannel histogram */
+    if (dqeEnable && mExynosDisplay->mHistogramController) {
+        mExynosDisplay->mHistogramController->prepareAtomicCommit(drmReq);
     }
 
     if (mDrmConnector->mipi_sync().id() && (mipi_sync_type != 0)) {
@@ -2039,7 +2039,22 @@
         /* TODO: don't pass expected present time before we can provide accurate time that desire
          * refresh rate take effect (b/202346402)
          */
-        if (!mVsyncCallback.getDesiredVsyncPeriod()) {
+        bool ignoreExpectedPresentTime = false;
+        if (mVsyncCallback.getDesiredVsyncPeriod()) {
+            ignoreExpectedPresentTime = true;
+
+            /* limit the condition to avoid unexpected early present */
+            auto desiredVsyncPeriod = mVsyncCallback.getDesiredVsyncPeriod();
+            auto currentVsyncPeriod = mExynosDisplay->mVsyncPeriod;
+            constexpr auto nsecsPerMs = std::chrono::nanoseconds(1ms).count();
+            if (currentVsyncPeriod > desiredVsyncPeriod &&
+                (((currentVsyncPeriod % desiredVsyncPeriod) < nsecsPerMs) ||
+                 (desiredVsyncPeriod - (currentVsyncPeriod % desiredVsyncPeriod)) < nsecsPerMs)) {
+                ignoreExpectedPresentTime = false;
+            }
+        }
+
+        if (!ignoreExpectedPresentTime) {
             if ((ret = drmReq.atomicAddProperty(mDrmCrtc->id(),
                                                 mDrmCrtc->expected_present_time_property(),
                                                 expectedPresentTime)) < 0) {
@@ -2082,6 +2097,11 @@
         mDrmVSyncWorker.VSyncControl(true);
     }
 
+    /* For multichannel histogram */
+    if (dqeEnable && mExynosDisplay->mHistogramController) {
+        mExynosDisplay->mHistogramController->postAtomicCommit();
+    }
+
     return NO_ERROR;
 }
 
@@ -2673,3 +2693,81 @@
 
     return (mDrmConnector->state() == DRM_MODE_CONNECTED);
 }
+
+void ExynosDisplayDrmInterface::retrievePanelFullResolution() {
+    std::lock_guard<std::recursive_mutex> lock(mDrmConnector->modesLock());
+
+    // The largest resolution in the modes of mDrmConnector is the panel full resolution.
+    for (auto it = mDrmConnector->modes().begin(); it != mDrmConnector->modes().end(); it++) {
+        if (it->h_display() * it->v_display() >
+            mPanelFullResolutionHSize * mPanelFullResolutionVSize) {
+            mPanelFullResolutionHSize = it->h_display();
+            mPanelFullResolutionVSize = it->v_display();
+        }
+    }
+
+    if (mPanelFullResolutionHSize <= 0 || mPanelFullResolutionVSize <= 0) {
+        ALOGE("%s: failed to get panel full resolution", __func__);
+    } else {
+        ALOGI("%s: panel full resolution: (%dx%d)", __func__, mPanelFullResolutionHSize,
+              mPanelFullResolutionVSize);
+    }
+}
+
+int32_t ExynosDisplayDrmInterface::setDisplayHistogramChannelSetting(
+        ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq, uint8_t channelId, void *blobData,
+        size_t blobLength) {
+    int ret = NO_ERROR;
+    uint32_t blobId = 0;
+
+    ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
+
+    const DrmProperty &prop = mDrmCrtc->histogram_channel_property(channelId);
+    if (!prop.id()) {
+        ALOGE("Unsupported multi-channel histrogram for channel:%d", channelId);
+        return -ENOTSUP;
+    }
+
+    ret = mDrmDevice->CreatePropertyBlob(blobData, blobLength, &blobId);
+    if (ret) {
+        HWC_LOGE(mExynosDisplay, "Failed to create histogram channel(%d) blob %d", channelId, ret);
+        return ret;
+    }
+
+    if ((ret = drmReq.atomicAddProperty(mDrmCrtc->id(), prop, blobId)) < 0) {
+        HWC_LOGE(mExynosDisplay, "%s: Failed to add property", __func__);
+        return ret;
+    }
+
+    // TODO: b/295794044 - Clear the old histogram channel blob
+
+    return ret;
+}
+
+int32_t ExynosDisplayDrmInterface::clearDisplayHistogramChannelSetting(
+        ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq, uint8_t channelId) {
+    int ret = NO_ERROR;
+
+    ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
+
+    const DrmProperty &prop = mDrmCrtc->histogram_channel_property(channelId);
+    if (!prop.id()) {
+        ALOGE("Unsupported multi-channel histrogram for channel:%d", channelId);
+        return -ENOTSUP;
+    }
+
+    if ((ret = drmReq.atomicAddProperty(mDrmCrtc->id(), prop, 0)) < 0) {
+        HWC_LOGE(mExynosDisplay, "%s: Failed to add property", __func__);
+        return ret;
+    }
+
+    // TODO: b/295794044 - Clear the old histogram channel blob
+
+    return ret;
+}
+
+int32_t ExynosDisplayDrmInterface::sendHistogramChannelIoctl(HistogramChannelIoctl_t control,
+                                                             uint8_t channelId) const {
+    ALOGE("%s: kernel doesn't support multi channel histogram ioctl", __func__);
+    return INVALID_OPERATION;
+}
diff --git a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
index ef9338b..d47e45a 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
@@ -328,12 +328,35 @@
 
         virtual int32_t waitVBlank();
         float getDesiredRefreshRate() { return mDesiredModeState.mode.v_refresh(); }
+        int32_t getOperationRate() {
+            if (mExynosDisplay->mOperationRateManager) {
+                    return mExynosDisplay->mOperationRateManager->getTargetOperationRate();
+            }
+            return 0;
+        }
 
         /* For Histogram */
         virtual int32_t setDisplayHistogramSetting(
                 ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq) {
             return NO_ERROR;
         }
+
+        /* For Histogram Multi Channel support */
+        int32_t setDisplayHistogramChannelSetting(
+                ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq, uint8_t channelId,
+                void *blobData, size_t blobLength);
+        int32_t clearDisplayHistogramChannelSetting(
+                ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq, uint8_t channelId);
+        enum class HistogramChannelIoctl_t {
+            /* send the histogram data request by calling histogram_channel_request_ioctl */
+            REQUEST = 0,
+
+            /* cancel the histogram data request by calling histogram_channel_cancel_ioctl */
+            CANCEL,
+        };
+        virtual int32_t sendHistogramChannelIoctl(HistogramChannelIoctl_t control,
+                                                  uint8_t channelId) const;
+
         int32_t getFrameCount() { return mFrameCounter; }
         virtual void registerHistogramInfo(const std::shared_ptr<IDLHistogram> &info) { return; }
         virtual int32_t setHistogramControl(hidl_histogram_control_t enabled) { return NO_ERROR; }
@@ -341,9 +364,8 @@
         int32_t getActiveModeHDisplay() { return mActiveModeState.mode.h_display(); }
         int32_t getActiveModeVDisplay() { return mActiveModeState.mode.v_display(); }
         uint32_t getActiveModeId() { return mActiveModeState.mode.id(); }
-        int32_t panelHsize() { return mPanelResolutionHsize; }
-        int32_t panelVsize() { return mPanelResolutionVsize; }
-        int32_t getPanelResolution();
+        int32_t getPanelFullResolutionHSize() { return mPanelFullResolutionHSize; }
+        int32_t getPanelFullResolutionVSize() { return mPanelFullResolutionVSize; }
         uint32_t getCrtcId() { return mDrmCrtc->id(); }
         int32_t triggerClearDisplayPlanes();
 
@@ -520,8 +542,19 @@
         DrmMode mDozeDrmMode;
         uint32_t mMaxWindowNum = 0;
         int32_t mFrameCounter = 0;
-        int32_t mPanelResolutionHsize = 0;
-        int32_t mPanelResolutionVsize = 0;
+        int32_t mPanelFullResolutionHSize = 0;
+        int32_t mPanelFullResolutionVSize = 0;
+
+        /**
+         * retrievePanelFullResolution
+         *
+         * Retrieve the panel full resolution by looking into the modes of the mDrmConnector
+         * and store the full resolution info in mPanelFullResolutionHSize (x component) and
+         * mPanelFullResolutionVSize (y component).
+         *
+         * Note: this function will be called only once in initDrmDevice()
+         */
+        void retrievePanelFullResolution();
 
     public:
         virtual bool readHotplugStatus();
diff --git a/libhwc2.1/libdrmresource/drm/drmcrtc.cpp b/libhwc2.1/libdrmresource/drm/drmcrtc.cpp
index 55cee3c..2d7d1d2 100644
--- a/libhwc2.1/libdrmresource/drm/drmcrtc.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmcrtc.cpp
@@ -87,6 +87,8 @@
     ALOGI("Failed to get color mode property");
   if (drm_->GetCrtcProperty(*this, "expected_present_time", &expected_present_time_property_))
       ALOGI("Failed to get expected_present_time property");
+  if (drm_->GetCrtcProperty(*this, "rcd_plane_id", &rcd_plane_id_property_))
+      ALOGI("Failed to get &rcd_plane_id property");
 
   /* Histogram Properties */
   if (drm_->GetCrtcProperty(*this, "histogram_roi", &histogram_roi_property_))
@@ -97,8 +99,6 @@
       ALOGI("Failed to get &histogram_threshold property");
   if (drm_->GetCrtcProperty(*this, "histogram_pos", &histogram_position_property_))
       ALOGI("Failed to get &histogram_position property");
-  if (drm_->GetCrtcProperty(*this, "rcd_plane_id", &rcd_plane_id_property_))
-      ALOGI("Failed to get &rcd_plane_id property");
 
   properties_.push_back(&active_property_);
   properties_.push_back(&mode_property_);
@@ -121,6 +121,7 @@
   properties_.push_back(&dqe_enabled_property_);
   properties_.push_back(&color_mode_property_);
   properties_.push_back(&expected_present_time_property_);
+  properties_.push_back(&rcd_plane_id_property_);
 
   /* Histogram Properties */
   properties_.push_back(&histogram_roi_property_);
@@ -128,8 +129,20 @@
   properties_.push_back(&histogram_threshold_property_);
   properties_.push_back(&histogram_position_property_);
 
-  properties_.push_back(&rcd_plane_id_property_);
+  /* Histogram Properties :: multichannel */
+  // TODO: b/295786065 - Get available channels from crtc property.
+  for (int i = 0; i < histogram_channels_max; i++) {
+      char pname[64];
 
+      sprintf(pname, "histogram_%d", i);
+      if (!drm_->GetCrtcProperty(*this, pname, &histogram_channel_property_[i])) {
+        ALOGD("histogram_channel #%d property found", i);
+        properties_.push_back(&histogram_channel_property_[i]);
+      } else {
+        ALOGD("histogram_channel #%d property not found, break", i);
+        break;
+      }
+  }
   return 0;
 }
 
@@ -270,4 +283,13 @@
     return rcd_plane_id_property_;
 }
 
+/* Histogram Properties */
+const DrmProperty &DrmCrtc::histogram_channel_property(uint8_t channelId) const {
+    if (histogram_channels_max <= channelId) {
+        ALOGE("Invalid histogram channel number %u\n", channelId);
+        return histogram_channel_property_[0];
+    }
+    return histogram_channel_property_[channelId];
+}
+
 }  // namespace android
diff --git a/libhwc2.1/libdrmresource/drm/drmeventlistener.cpp b/libhwc2.1/libdrmresource/drm/drmeventlistener.cpp
index 0eeb917..bab23fe 100644
--- a/libhwc2.1/libdrmresource/drm/drmeventlistener.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmeventlistener.cpp
@@ -122,6 +122,25 @@
     if (handler == histogram_handler_.get()) histogram_handler_ = NULL;
 }
 
+void DrmEventListener::RegisterHistogramChannelHandler(DrmHistogramChannelEventHandler *handler) {
+    assert(!histogram_channel_handler_);
+
+    if (handler) {
+        histogram_channel_handler_.reset(handler);
+    } else {
+        ALOGE("%s: failed to register, handler is nullptr", __func__);
+    }
+}
+
+void DrmEventListener::UnRegisterHistogramChannelHandler(DrmHistogramChannelEventHandler *handler) {
+    if (handler == histogram_channel_handler_.get()) {
+        histogram_channel_handler_ = NULL;
+    } else {
+        ALOGE("%s: failed to unregister, handler(%p), histogram_channel_handler(%p)", __func__,
+              handler, histogram_channel_handler_.get());
+    }
+}
+
 void DrmEventListener::RegisterTUIHandler(DrmTUIEventHandler *handler) {
   if (tui_handler_) {
     ALOGE("TUI handler was already registered");
@@ -278,6 +297,15 @@
                                                              (void *)&(histo->bins));
                 }
                 break;
+#if defined(EXYNOS_DRM_HISTOGRAM_CHANNEL_EVENT)
+            case EXYNOS_DRM_HISTOGRAM_CHANNEL_EVENT:
+                if (histogram_channel_handler_) {
+                    histogram_channel_handler_->handleHistogramChannelEvent((void *)e);
+                } else {
+                    ALOGE("%s: no valid histogram channel event handler", __func__);
+                }
+                break;
+#endif
             case DRM_EVENT_FLIP_COMPLETE:
                 vblank = (struct drm_event_vblank *)e;
                 user_data = (void *)(unsigned long)(vblank->user_data);
diff --git a/libhwc2.1/libdrmresource/include/drmcrtc.h b/libhwc2.1/libdrmresource/include/drmcrtc.h
index 6bb73fd..7148e46 100644
--- a/libhwc2.1/libdrmresource/include/drmcrtc.h
+++ b/libhwc2.1/libdrmresource/include/drmcrtc.h
@@ -17,12 +17,14 @@
 #ifndef ANDROID_DRM_CRTC_H_
 #define ANDROID_DRM_CRTC_H_
 
-#include "drmmode.h"
-#include "drmproperty.h"
-
 #include <stdint.h>
 #include <xf86drmMode.h>
 
+#include <array>
+
+#include "drmmode.h"
+#include "drmproperty.h"
+
 namespace android {
 
 class DrmDevice;
@@ -65,6 +67,7 @@
   const DrmProperty &dqe_enabled_property() const;
   const DrmProperty &color_mode_property() const;
   const DrmProperty &expected_present_time_property() const;
+  const DrmProperty &rcd_plane_id_property() const;
 
   /* Histogram Properties */
   const DrmProperty &histogram_roi_property() const;
@@ -72,7 +75,8 @@
   const DrmProperty &histogram_threshold_property() const;
   const DrmProperty &histogram_position_property() const;
 
-  const DrmProperty &rcd_plane_id_property() const;
+  /* Histogram Properties:: multi channel */
+  const DrmProperty &histogram_channel_property(uint8_t channelId) const;
 
   const std::vector<DrmProperty *> &properties() const {
       return properties_;
@@ -108,6 +112,7 @@
   DrmProperty dqe_enabled_property_;
   DrmProperty color_mode_property_;
   DrmProperty expected_present_time_property_;
+  DrmProperty rcd_plane_id_property_;
 
   /* Histogram Properties */
   DrmProperty histogram_roi_property_;
@@ -115,10 +120,15 @@
   DrmProperty histogram_threshold_property_;
   DrmProperty histogram_position_property_;
 
-  DrmProperty rcd_plane_id_property_;
+  /* Histogram Properties :: multichannel support */
+  // TODO: b/295786065 - Get available channels from crtc property.
+  static const uint8_t histogram_channels_max = 4;
+  std::array<DrmProperty, histogram_channels_max> histogram_channel_property_;
 
+  /* all properties */
   std::vector<DrmProperty *> properties_;
 };
+
 }  // namespace android
 
 #endif  // ANDROID_DRM_CRTC_H_
diff --git a/libhwc2.1/libdrmresource/include/drmdevice.h b/libhwc2.1/libdrmresource/include/drmdevice.h
index 1f733bc..a24329b 100644
--- a/libhwc2.1/libdrmresource/include/drmdevice.h
+++ b/libhwc2.1/libdrmresource/include/drmdevice.h
@@ -84,6 +84,9 @@
   void RegisterHistogramHandler(DrmHistogramEventHandler *handler) {
       event_listener_.RegisterHistogramHandler(handler);
   }
+  void RegisterHistogramChannelHandler(DrmHistogramChannelEventHandler *handler) {
+      event_listener_.RegisterHistogramChannelHandler(handler);
+  }
 
   int CallVendorIoctl(unsigned long request, void *arg);
 
diff --git a/libhwc2.1/libdrmresource/include/drmeventlistener.h b/libhwc2.1/libdrmresource/include/drmeventlistener.h
index e85ca0a..c26a458 100644
--- a/libhwc2.1/libdrmresource/include/drmeventlistener.h
+++ b/libhwc2.1/libdrmresource/include/drmeventlistener.h
@@ -46,6 +46,14 @@
     virtual void handleHistogramEvent(uint32_t crtc_id, void *) = 0;
 };
 
+class DrmHistogramChannelEventHandler {
+   public:
+    DrmHistogramChannelEventHandler() {}
+    virtual ~DrmHistogramChannelEventHandler() {}
+
+    virtual void handleHistogramChannelEvent(void *) = 0;
+};
+
 class DrmTUIEventHandler {
  public:
   DrmTUIEventHandler() {
@@ -87,6 +95,8 @@
   void UnRegisterHotplugHandler(DrmEventHandler *handler);
   void RegisterHistogramHandler(DrmHistogramEventHandler *handler);
   void UnRegisterHistogramHandler(DrmHistogramEventHandler *handler);
+  void RegisterHistogramChannelHandler(DrmHistogramChannelEventHandler *handler);
+  void UnRegisterHistogramChannelHandler(DrmHistogramChannelEventHandler *handler);
   void RegisterTUIHandler(DrmTUIEventHandler *handler);
   void UnRegisterTUIHandler(DrmTUIEventHandler *handler);
   void RegisterPanelIdleHandler(DrmPanelIdleEventHandler *handler);
@@ -115,6 +125,7 @@
   DrmDevice *drm_;
   std::unique_ptr<DrmEventHandler> hotplug_handler_;
   std::unique_ptr<DrmHistogramEventHandler> histogram_handler_;
+  std::unique_ptr<DrmHistogramChannelEventHandler> histogram_channel_handler_;
   std::unique_ptr<DrmTUIEventHandler> tui_handler_;
   std::unique_ptr<DrmPanelIdleEventHandler> panel_idle_handler_;
   std::mutex mutex_;
diff --git a/libhwc2.1/libdrmresource/include/worker.h b/libhwc2.1/libdrmresource/include/worker.h
index 153cbce..eeae4c1 100644
--- a/libhwc2.1/libdrmresource/include/worker.h
+++ b/libhwc2.1/libdrmresource/include/worker.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_WORKER_H_
 #define ANDROID_WORKER_H_
 
+#include <android-base/thread_annotations.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <string>
@@ -29,10 +30,10 @@
 
 class Worker {
  public:
-  void Lock() {
+  void Lock() ACQUIRE(mutex_) {
     mutex_.lock();
   }
-  void Unlock() {
+  void Unlock() RELEASE(mutex_) {
     mutex_.unlock();
   }
 
diff --git a/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp b/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
index 48d7f49..84a6971 100644
--- a/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
+++ b/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
@@ -35,8 +35,6 @@
       : ExynosDisplay(HWC_DISPLAY_EXTERNAL, index, device, displayName) {
     DISPLAY_LOGD(eDebugExternalDisplay, "");
 
-    mDisplayControl.cursorSupport = true;
-
     mEnabled = false;
     mBlanked = false;
 
diff --git a/libhwc2.1/libhwcService/ExynosHWCService.cpp b/libhwc2.1/libhwcService/ExynosHWCService.cpp
index 79f414d..4ae010a 100644
--- a/libhwc2.1/libhwcService/ExynosHWCService.cpp
+++ b/libhwc2.1/libhwcService/ExynosHWCService.cpp
@@ -431,6 +431,18 @@
     return -EINVAL;
 }
 
+int32_t ExynosHWCService::setDisplayBrightnessDbv(int32_t display_id, uint32_t dbv) {
+    auto display = mHWCCtx->device->getDisplay(display_id);
+
+    if (display != nullptr) {
+        return display->setBrightnessDbv(dbv);
+    } else {
+        ALOGE("ExynosHWCService::%s() invalid display id: %d\n", __func__, display_id);
+    }
+
+    return -EINVAL;
+}
+
 int32_t ExynosHWCService::setDisplayLhbm(int32_t display_id, uint32_t on) {
     if (on > 1) return -EINVAL;
 
diff --git a/libhwc2.1/libhwcService/ExynosHWCService.h b/libhwc2.1/libhwcService/ExynosHWCService.h
index 460d12e..b443ca4 100644
--- a/libhwc2.1/libhwcService/ExynosHWCService.h
+++ b/libhwc2.1/libhwcService/ExynosHWCService.h
@@ -72,6 +72,7 @@
     virtual int32_t setDisplayBrightness(int32_t display_id, float brightness);
     virtual int32_t ignoreDisplayBrightnessUpdateRequests(int32_t displayId, bool ignore);
     virtual int32_t setDisplayBrightnessNits(const int32_t display_id, const float nits);
+    virtual int32_t setDisplayBrightnessDbv(const int32_t display_id, const uint32_t dbv);
     virtual int32_t setDisplayLhbm(int32_t display_id, uint32_t on);
 
     virtual int32_t setMinIdleRefreshRate(uint32_t display_id, int32_t fps);
diff --git a/libhwc2.1/libhwcService/IExynosHWC.cpp b/libhwc2.1/libhwcService/IExynosHWC.cpp
index bf08d85..d09a3f6 100644
--- a/libhwc2.1/libhwcService/IExynosHWC.cpp
+++ b/libhwc2.1/libhwcService/IExynosHWC.cpp
@@ -68,6 +68,7 @@
     TRIGGER_REFRESH_RATE_INDICATOR_UPDATE = 1011,
     IGNORE_DISPLAY_BRIGHTNESS_UPDATE_REQUESTS = 1012,
     SET_DISPLAY_BRIGHTNESS_NITS = 1013,
+    SET_DISPLAY_BRIGHTNESS_DBV = 1014,
 };
 
 class BpExynosHWCService : public BpInterface<IExynosHWCService> {
@@ -422,6 +423,18 @@
         return result;
     }
 
+    virtual int32_t setDisplayBrightnessDbv(int32_t displayId, uint32_t dbv) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IExynosHWCService::getInterfaceDescriptor());
+        data.writeInt32(displayId);
+        data.writeUint32(dbv);
+        int result = remote()->transact(SET_DISPLAY_BRIGHTNESS_DBV, data, &reply);
+        if (result) {
+            ALOGE("SET_DISPLAY_BRIGHTNESS_DBV transact error(%d)", result);
+        }
+        return result;
+    }
+
     virtual int32_t setDisplayLhbm(int32_t display_id, uint32_t on) {
         Parcel data, reply;
         data.writeInterfaceToken(IExynosHWCService::getInterfaceDescriptor());
@@ -795,6 +808,15 @@
             return NO_ERROR;
         } break;
 
+        case SET_DISPLAY_BRIGHTNESS_DBV: {
+            CHECK_INTERFACE(IExynosHWCService, data, reply);
+            int32_t displayId = data.readInt32();
+            uint32_t dbv = data.readUint32();
+            int32_t error = setDisplayBrightnessDbv(displayId, dbv);
+            reply->writeInt32(error);
+            return NO_ERROR;
+        } break;
+
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libhwc2.1/libhwcService/IExynosHWC.h b/libhwc2.1/libhwcService/IExynosHWC.h
index 71a4677..a454c4a 100644
--- a/libhwc2.1/libhwcService/IExynosHWC.h
+++ b/libhwc2.1/libhwcService/IExynosHWC.h
@@ -70,6 +70,7 @@
     virtual int32_t setDisplayBrightness(int32_t display_id, float brightness) = 0;
     virtual int32_t ignoreDisplayBrightnessUpdateRequests(int32_t displayId, bool ignore) = 0;
     virtual int32_t setDisplayBrightnessNits(int32_t displayId, float nits) = 0;
+    virtual int32_t setDisplayBrightnessDbv(int32_t displayId, uint32_t dbv) = 0;
     virtual int32_t setDisplayLhbm(int32_t display_id, uint32_t on) = 0;
     virtual int32_t setMinIdleRefreshRate(uint32_t display_id, int32_t refresh_rate) = 0;
     virtual int32_t setRefreshRateThrottle(uint32_t display_id, int32_t throttle) = 0;
diff --git a/libhwc2.1/libhwchelper/ExynosHWCHelper.cpp b/libhwc2.1/libhwchelper/ExynosHWCHelper.cpp
index d8befce..56b58c4 100644
--- a/libhwc2.1/libhwchelper/ExynosHWCHelper.cpp
+++ b/libhwc2.1/libhwchelper/ExynosHWCHelper.cpp
@@ -918,14 +918,22 @@
                            ((tv.tv_sec * 1000) + (tv.tv_usec / 1000)));
 }
 
-void setFenceInfo(uint32_t fd, ExynosDisplay* display, HwcFdebugFenceType type, HwcFdebugIpType ip,
-                  HwcFenceDirection direction, bool pendingAllowed, int32_t dupFrom) {
+void setFenceInfo(uint32_t fd, const ExynosDisplay *display, HwcFdebugFenceType type,
+                  HwcFdebugIpType ip, HwcFenceDirection direction, bool pendingAllowed,
+                  int32_t dupFrom) {
     if (!fence_valid(fd) || display == NULL) return;
 
     ExynosDevice* device = display->mDevice;
+    device->mFenceTracker.updateFenceInfo(fd, display, type, ip, direction, pendingAllowed,
+                                          dupFrom);
+}
 
-    std::scoped_lock lock(device->mFenceMutex);
-    HwcFenceInfo& info = device->mFenceInfos[fd];
+void FenceTracker::updateFenceInfo(uint32_t fd, const ExynosDisplay *display,
+                                   HwcFdebugFenceType type, HwcFdebugIpType ip,
+                                   HwcFenceDirection direction, bool pendingAllowed,
+                                   int32_t dupFrom) {
+    std::scoped_lock lock(mFenceMutex);
+    HwcFenceInfo &info = mFenceInfos[fd];
     info.displayId = display->mDisplayId;
 
     if (info.leaking) {
@@ -955,11 +963,11 @@
     }
 
     if (info.usage == 0) {
-        device->mFenceInfos.erase(fd);
+        mFenceInfos.erase(fd);
         return;
     } else if (info.usage < 0) {
         ALOGE("%s : Invalid negative usage (%d) for Fence FD:%d", __func__, info.usage, fd);
-        printLastFenceInfo(fd, display);
+        printLastFenceInfoLocked(fd);
     }
 
     HwcFenceTrace trace = {.direction = direction, .type = type, .ip = ip, .time = {0, 0}};
@@ -975,45 +983,39 @@
     info.pendingAllowed = pendingAllowed;
 }
 
-void printLastFenceInfo(uint32_t fd, ExynosDisplay* display) {
+void FenceTracker::printLastFenceInfoLocked(uint32_t fd) {
     if (!fence_valid(fd)) return;
 
-    ExynosDevice* device = display->mDevice;
-
-    auto it = device->mFenceInfos.find(fd);
-    if (it == device->mFenceInfos.end()) return;
-    HwcFenceInfo& info = it->second;
+    auto it = mFenceInfos.find(fd);
+    if (it == mFenceInfos.end()) return;
+    HwcFenceInfo &info = it->second;
     FT_LOGD("---- Fence FD : %d, Display(%d) ----", fd, info.displayId);
     FT_LOGD("usage: %d, dupFrom: %d, pendingAllowed: %d, leaking: %d", info.usage, info.dupFrom,
             info.pendingAllowed, info.leaking);
 
-    for (const auto& trace : info.traces) {
+    for (const auto &trace : info.traces) {
         FT_LOGD("> dir: %d, type: %d, ip: %d, time:%s", trace.direction, trace.type, trace.ip,
                 getLocalTimeStr(trace.time).c_str());
     }
 }
 
-void dumpFenceInfo(ExynosDisplay* display, int32_t count) {
-    ExynosDevice* device = display->mDevice;
-
+void FenceTracker::dumpFenceInfoLocked(int32_t count) {
     FT_LOGD("Dump fence (up to %d fences) ++", count);
-    for (const auto& [fd, info] : device->mFenceInfos) {
+    for (const auto &[fd, info] : mFenceInfos) {
         if (info.pendingAllowed) continue;
         if (count-- <= 0) break;
-        printLastFenceInfo(fd, display);
+        printLastFenceInfoLocked(fd);
     }
     FT_LOGD("Dump fence --");
 }
 
-void printLeakFds(ExynosDisplay* display) {
-    ExynosDevice* device = display->mDevice;
-
-    auto reportLeakFds = [&fenceInfos = device->mFenceInfos](int sign) {
+void FenceTracker::printLeakFdsLocked() {
+    auto reportLeakFdsLocked = [&fenceInfos = mFenceInfos](int sign) REQUIRES(mFenceMutex) {
         String8 errString;
         errString.appendFormat("Leak Fds (%d) :\n", sign);
 
         int cnt = 0;
-        for (const auto& [fd, info] : fenceInfos) {
+        for (const auto &[fd, info] : fenceInfos) {
             if (!info.leaking) continue;
             if (info.usage * sign > 0) {
                 errString.appendFormat("%d,", fd);
@@ -1026,52 +1028,48 @@
         FT_LOGW("%s", errString.c_str());
     };
 
-    reportLeakFds(+1);
-    reportLeakFds(-1);
+    reportLeakFdsLocked(+1);
+    reportLeakFdsLocked(-1);
 }
 
-void dumpNCheckLeak(ExynosDisplay* display, int32_t __unused depth) {
-    ExynosDevice* device = display->mDevice;
-
+void FenceTracker::dumpNCheckLeakLocked() {
     FT_LOGD("Dump leaking fence ++");
-    for (auto& [fd, info] : device->mFenceInfos) {
+    for (auto &[fd, info] : mFenceInfos) {
         if (!info.pendingAllowed) {
-            // leak is occured in this frame first
+            // leak is occurred in this frame first
             if (!info.leaking) {
                 info.leaking = true;
-                printLastFenceInfo(fd, display);
+                printLastFenceInfoLocked(fd);
             }
         }
     }
 
     int priv = exynosHWCControl.fenceTracer;
     exynosHWCControl.fenceTracer = 3;
-    printLeakFds(display);
+    printLeakFdsLocked();
     exynosHWCControl.fenceTracer = priv;
 
     FT_LOGD("Dump leaking fence --");
 }
 
-bool fenceWarn(ExynosDisplay* display, uint32_t threshold) {
-    ExynosDevice* device = display->mDevice;
-    uint32_t cnt = device->mFenceInfos.size();
+bool FenceTracker::fenceWarnLocked(uint32_t threshold) {
+    uint32_t cnt = mFenceInfos.size();
 
     if (cnt > threshold) {
         ALOGE("Fence leak! -- the number of fences(%d) exceeds threshold(%d)", cnt, threshold);
         int priv = exynosHWCControl.fenceTracer;
         exynosHWCControl.fenceTracer = 3;
-        dumpFenceInfo(display, 10);
+        dumpFenceInfoLocked(10);
         exynosHWCControl.fenceTracer = priv;
     }
 
     return (cnt > threshold);
 }
 
-bool validateFencePerFrame(ExynosDisplay* display) {
-    ExynosDevice* device = display->mDevice;
+bool FenceTracker::validateFencePerFrameLocked(const ExynosDisplay *display) {
     bool ret = true;
 
-    for (const auto& [fd, info] : device->mFenceInfos) {
+    for (const auto &[fd, info] : mFenceInfos) {
         if (info.displayId != display->mDisplayId) continue;
         if ((!info.pendingAllowed) && (!info.leaking)) {
             ret = false;
@@ -1082,13 +1080,67 @@
     if (!ret) {
         int priv = exynosHWCControl.fenceTracer;
         exynosHWCControl.fenceTracer = 3;
-        dumpNCheckLeak(display, 0);
+        dumpNCheckLeakLocked();
         exynosHWCControl.fenceTracer = priv;
     }
 
     return ret;
 }
 
+bool FenceTracker::validateFences(ExynosDisplay *display) {
+    std::scoped_lock lock(mFenceMutex);
+
+    if (!validateFencePerFrameLocked(display)) {
+        ALOGE("You should doubt fence leak!");
+        saveFenceTraceLocked(display);
+        return false;
+    }
+
+    if (fenceWarnLocked(MAX_FENCE_THRESHOLD)) {
+        printLeakFdsLocked();
+        saveFenceTraceLocked(display);
+        return false;
+    }
+
+    if (exynosHWCControl.doFenceFileDump) {
+        ALOGD("Fence file dump !");
+        saveFenceTraceLocked(display);
+        exynosHWCControl.doFenceFileDump = false;
+    }
+
+    return true;
+}
+
+int32_t FenceTracker::saveFenceTraceLocked(ExynosDisplay *display) {
+    int32_t ret = NO_ERROR;
+    auto &fileWriter = display->mFenceFileWriter;
+
+    if (!fileWriter.chooseOpenedFile()) {
+        return -1;
+    }
+
+    String8 saveString;
+
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    saveString.appendFormat("\n====== Fences at time:%s ======\n", getLocalTimeStr(tv).c_str());
+
+    for (const auto &[fd, info] : mFenceInfos) {
+        saveString.appendFormat("---- Fence FD : %d, Display(%d) ----\n", fd, info.displayId);
+        saveString.appendFormat("usage: %d, dupFrom: %d, pendingAllowed: %d, leaking: %d\n",
+                                info.usage, info.dupFrom, info.pendingAllowed, info.leaking);
+
+        for (const auto &trace : info.traces) {
+            saveString.appendFormat("> dir: %d, type: %d, ip: %d, time:%s\n", trace.direction,
+                                    trace.type, trace.ip, getLocalTimeStr(trace.time).c_str());
+        }
+    }
+
+    fileWriter.write(saveString);
+    fileWriter.flush();
+    return ret;
+}
+
 String8 getMPPStr(int typeId) {
     if (typeId < MPP_DPP_NUM){
         int cnt = sizeof(available_otf_mpp_units)/sizeof(exynos_mpp_t);
diff --git a/libhwc2.1/libhwchelper/ExynosHWCHelper.h b/libhwc2.1/libhwchelper/ExynosHWCHelper.h
index 1efde16..ed9964a 100644
--- a/libhwc2.1/libhwchelper/ExynosHWCHelper.h
+++ b/libhwc2.1/libhwchelper/ExynosHWCHelper.h
@@ -162,6 +162,8 @@
         1, 1, 32, RGB | BIT10 | COMP_TYPE_NONE | COMP_TYPE_AFBC, true, String8("RGBA_1010102"), 0},
     {HAL_PIXEL_FORMAT_EXYNOS_ARGB_8888, DECON_PIXEL_FORMAT_MAX, DRM_FORMAT_ARGB8888,
         1, 1, 32, RGB | BIT8 | COMP_TYPE_NONE | COMP_TYPE_AFBC, true, String8("EXYNOS_ARGB_8888"), 0},
+    {HAL_PIXEL_FORMAT_RGBA_FP16, DECON_PIXEL_FORMAT_MAX, DRM_FORMAT_ARGB16161616F,
+        1, 1, 64, RGB | BIT16 | COMP_TYPE_NONE | COMP_TYPE_AFBC, true, String8("RGBA_FP16"), 0},
 
     /* YUV 420 */
     {HAL_PIXEL_FORMAT_EXYNOS_YCbCr_420_P_M, DECON_PIXEL_FORMAT_YUV420M, DRM_FORMAT_UNDEFINED,
@@ -538,23 +540,41 @@
 };
 
 class funcReturnCallback {
-    public:
-        funcReturnCallback(const std::function<void(void)> cb) : mCb(cb) {}
-        ~funcReturnCallback() { mCb(); }
-    private:
-        const std::function<void(void)> mCb;
+public:
+    funcReturnCallback(const std::function<void(void)> cb) : mCb(cb) {}
+    ~funcReturnCallback() { mCb(); }
+
+private:
+    const std::function<void(void)> mCb;
 };
 
 String8 getLocalTimeStr(struct timeval tv);
 
 void setFenceName(int fenceFd, HwcFenceType fenceType);
-void setFenceInfo(uint32_t fd, ExynosDisplay *display, HwcFdebugFenceType type, HwcFdebugIpType ip,
-                  HwcFenceDirection direction, bool pendingAllowed = false, int32_t dupFrom = -1);
-void printLastFenceInfo(uint32_t fd, ExynosDisplay *display);
-void dumpFenceInfo(ExynosDisplay *display, int32_t count);
-bool fenceWarn(ExynosDisplay *display, uint32_t threshold);
-void printLeakFds(ExynosDisplay *display);
-bool validateFencePerFrame(ExynosDisplay *display);
+void setFenceInfo(uint32_t fd, const ExynosDisplay *display, HwcFdebugFenceType type,
+                  HwcFdebugIpType ip, HwcFenceDirection direction, bool pendingAllowed = false,
+                  int32_t dupFrom = -1);
+
+class FenceTracker {
+public:
+    void updateFenceInfo(uint32_t fd, const ExynosDisplay *display, HwcFdebugFenceType type,
+                         HwcFdebugIpType ip, HwcFenceDirection direction,
+                         bool pendingAllowed = false, int32_t dupFrom = -1);
+    bool validateFences(ExynosDisplay *display);
+
+private:
+    void printLastFenceInfoLocked(uint32_t fd) REQUIRES(mFenceMutex);
+    void dumpFenceInfoLocked(int32_t count) REQUIRES(mFenceMutex);
+    void printLeakFdsLocked() REQUIRES(mFenceMutex);
+    void dumpNCheckLeakLocked() REQUIRES(mFenceMutex);
+    bool fenceWarnLocked(uint32_t threshold) REQUIRES(mFenceMutex);
+    bool validateFencePerFrameLocked(const ExynosDisplay *display) REQUIRES(mFenceMutex);
+    int32_t saveFenceTraceLocked(ExynosDisplay *display) REQUIRES(mFenceMutex);
+
+    std::map<int, HwcFenceInfo> mFenceInfos GUARDED_BY(mFenceMutex);
+    mutable std::mutex mFenceMutex;
+};
+
 android_dataspace colorModeToDataspace(android_color_mode_t mode);
 bool hasPPC(uint32_t physicalType, uint32_t formatIndex, uint32_t rotIndex);
 
diff --git a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
index a46e086..ec1e8ba 100644
--- a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
+++ b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
@@ -33,6 +33,7 @@
 #include "ExynosHWCDebug.h"
 #include "ExynosHWCHelper.h"
 #include "ExynosLayer.h"
+#include "HistogramController.h"
 
 extern struct exynos_hwc_control exynosHWCControl;
 
@@ -155,6 +156,7 @@
     mBrightnessController = std::make_unique<BrightnessController>(
             mIndex, [this]() { mDevice->onRefresh(mDisplayId); },
             [this]() { updatePresentColorConversionInfo(); });
+    mHistogramController = std::make_unique<HistogramController>(this);
 
     mDisplayControl.multiThreadedPresent = true;
 }
diff --git a/libhwc2.1/libresource/ExynosMPP.cpp b/libhwc2.1/libresource/ExynosMPP.cpp
index 778aa1a..406996a 100644
--- a/libhwc2.1/libresource/ExynosMPP.cpp
+++ b/libhwc2.1/libresource/ExynosMPP.cpp
@@ -125,14 +125,12 @@
     mMidImg.releaseFenceFd = -1;
 }
 
-void ExynosMPPSource::setExynosImage(exynos_image src_img, exynos_image dst_img)
-{
+void ExynosMPPSource::setExynosImage(const exynos_image& src_img, const exynos_image& dst_img) {
     mSrcImg = src_img;
     mDstImg = dst_img;
 }
 
-void ExynosMPPSource::setExynosMidImage(exynos_image mid_img)
-{
+void ExynosMPPSource::setExynosMidImage(const exynos_image& mid_img) {
     mMidImg = mid_img;
 }
 
diff --git a/libhwc2.1/libresource/ExynosMPP.h b/libhwc2.1/libresource/ExynosMPP.h
index 0877841..7ea3fbe 100644
--- a/libhwc2.1/libresource/ExynosMPP.h
+++ b/libhwc2.1/libresource/ExynosMPP.h
@@ -451,8 +451,8 @@
         ExynosMPPSource();
         ExynosMPPSource(uint32_t sourceType, void *source);
         ~ExynosMPPSource(){};
-        void setExynosImage(exynos_image src_img, exynos_image dst_img);
-        void setExynosMidImage(exynos_image mid_img);
+        void setExynosImage(const exynos_image& src_img, const exynos_image& dst_img);
+        void setExynosMidImage(const exynos_image& mid_img);
 
         uint32_t mSourceType;
         void *mSource;
diff --git a/libhwc2.1/libresource/ExynosResourceManager.cpp b/libhwc2.1/libresource/ExynosResourceManager.cpp
index d92d32a..47a1fb2 100644
--- a/libhwc2.1/libresource/ExynosResourceManager.cpp
+++ b/libhwc2.1/libresource/ExynosResourceManager.cpp
@@ -808,9 +808,11 @@
     return ret;
 }
 
-int32_t ExynosResourceManager::changeLayerFromClientToDevice(ExynosDisplay *display, ExynosLayer *layer,
-        uint32_t layer_index, exynos_image m2m_out_img, ExynosMPP *m2mMPP, ExynosMPP *otfMPP)
-{
+int32_t ExynosResourceManager::changeLayerFromClientToDevice(ExynosDisplay* display,
+                                                             ExynosLayer* layer,
+                                                             uint32_t layer_index,
+                                                             const exynos_image& m2m_out_img,
+                                                             ExynosMPP* m2mMPP, ExynosMPP* otfMPP) {
     int ret = NO_ERROR;
     if ((ret = display->removeClientCompositionLayer(layer_index)) != NO_ERROR) {
         ALOGD("removeClientCompositionLayer return error(%d)", ret);
diff --git a/libhwc2.1/libresource/ExynosResourceManager.h b/libhwc2.1/libresource/ExynosResourceManager.h
index e996554..c8f308f 100644
--- a/libhwc2.1/libresource/ExynosResourceManager.h
+++ b/libhwc2.1/libresource/ExynosResourceManager.h
@@ -187,8 +187,9 @@
                           struct exynos_image &dst, ExynosMPPSource *mppSrc);
 
     private:
-        int32_t changeLayerFromClientToDevice(ExynosDisplay *display, ExynosLayer *layer,
-                uint32_t layer_index, exynos_image m2m_out_img, ExynosMPP *m2mMPP, ExynosMPP *otfMPP);
+        int32_t changeLayerFromClientToDevice(ExynosDisplay* display, ExynosLayer* layer,
+                                              uint32_t layer_index, const exynos_image& m2m_out_img,
+                                              ExynosMPP* m2mMPP, ExynosMPP* otfMPP);
         void dump(const restriction_classification_t, String8 &result) const;
 
         sp<DstBufMgrThread> mDstBufMgrThread;
diff --git a/libhwc2.1/pixel-display-default.xml b/libhwc2.1/pixel-display-default.xml
index 7f2e4ba..50835eb 100644
--- a/libhwc2.1/pixel-display-default.xml
+++ b/libhwc2.1/pixel-display-default.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>com.google.hardware.pixel.display</name>
-        <version>8</version>
+        <version>9</version>
         <fqname>IDisplay/default</fqname>
     </hal>
 </manifest>
diff --git a/libhwc2.1/pixel-display-secondary.xml b/libhwc2.1/pixel-display-secondary.xml
index ff313be..3cf2883 100644
--- a/libhwc2.1/pixel-display-secondary.xml
+++ b/libhwc2.1/pixel-display-secondary.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>com.google.hardware.pixel.display</name>
-        <version>8</version>
+        <version>9</version>
         <fqname>IDisplay/secondary</fqname>
     </hal>
 </manifest>
diff --git a/libhwc2.1/pixel-display.cpp b/libhwc2.1/pixel-display.cpp
index d2eb986..c958346 100644
--- a/libhwc2.1/pixel-display.cpp
+++ b/libhwc2.1/pixel-display.cpp
@@ -25,6 +25,7 @@
 
 #include "ExynosDisplay.h"
 #include "ExynosPrimaryDisplay.h"
+#include "HistogramController.h"
 
 extern int32_t load_png_image(const char *filepath, buffer_handle_t buffer);
 
@@ -205,7 +206,7 @@
     histogram::HistogramMediator::HistogramConfig pendingConfig(roi, weight, pos);
 
     {
-        std::unique_lock<std::mutex> lk(mMediator.mConfigMutex);
+        std::scoped_lock lock(mMediator.mConfigMutex);
         isConfigChanged = mMediator.mConfig != pendingConfig;
 
         if (isConfigChanged &&
@@ -251,11 +252,11 @@
         *_aidl_return = HistogramErrorCode::BAD_HIST_DATA;
         return ndk::ScopedAStatus::ok();
     }
-    if (mMediator.isDisplayPowerOff() == true) {
+    if (mDisplay->isPowerModeOff() == true) {
         *_aidl_return = HistogramErrorCode::DISPLAY_POWEROFF; // panel is off
         return ndk::ScopedAStatus::ok();
     }
-    if (mMediator.isSecureContentPresenting() == true) {
+    if (mDisplay->isSecureContentPresenting() == true) {
         *_aidl_return = HistogramErrorCode::DRM_PLAYING; // panel is playing DRM content
         return ndk::ScopedAStatus::ok();
     }
@@ -284,7 +285,7 @@
     }
     RoiRect roiCaled = mMediator.calRoi(roi); // fit roi coordinates to RRS
     runMediator(roiCaled, weight, pos, histogrambuffer);
-    if (mMediator.isSecureContentPresenting() == true) {
+    if (mDisplay->isSecureContentPresenting() == true) {
         /* clear data to avoid leakage */
         std::fill(histogrambuffer->begin(), histogrambuffer->end(), 0);
         histogrambuffer->clear();
@@ -320,6 +321,50 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Display::getHistogramCapability(HistogramCapability *_aidl_return) {
+    if (mDisplay && mDisplay->mHistogramController) {
+        return mDisplay->mHistogramController->getHistogramCapability(_aidl_return);
+    }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Display::registerHistogram(const ndk::SpAIBinder &token,
+                                              const HistogramConfig &histogramConfig,
+                                              HistogramErrorCode *_aidl_return) {
+    if (mDisplay && mDisplay->mHistogramController) {
+        return mDisplay->mHistogramController->registerHistogram(token, histogramConfig,
+                                                                 _aidl_return);
+    }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Display::queryHistogram(const ndk::SpAIBinder &token,
+                                           std::vector<char16_t> *histogramBuffer,
+                                           HistogramErrorCode *_aidl_return) {
+    if (mDisplay && mDisplay->mHistogramController) {
+        return mDisplay->mHistogramController->queryHistogram(token, histogramBuffer, _aidl_return);
+    }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Display::reconfigHistogram(const ndk::SpAIBinder &token,
+                                              const HistogramConfig &histogramConfig,
+                                              HistogramErrorCode *_aidl_return) {
+    if (mDisplay && mDisplay->mHistogramController) {
+        return mDisplay->mHistogramController->reconfigHistogram(token, histogramConfig,
+                                                                 _aidl_return);
+    }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Display::unregisterHistogram(const ndk::SpAIBinder &token,
+                                                HistogramErrorCode *_aidl_return) {
+    if (mDisplay && mDisplay->mHistogramController) {
+        return mDisplay->mHistogramController->unregisterHistogram(token, _aidl_return);
+    }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
 } // namespace display
 } // namespace pixel
 } // namespace hardware
diff --git a/libhwc2.1/pixel-display.h b/libhwc2.1/pixel-display.h
index df437af..63aa037 100644
--- a/libhwc2.1/pixel-display.h
+++ b/libhwc2.1/pixel-display.h
@@ -64,6 +64,18 @@
     ndk::ScopedAStatus setPeakRefreshRate(int rate) override;
     ndk::ScopedAStatus setLowPowerMode(bool enabled) override;
     ndk::ScopedAStatus isOperationRateSupported(bool *_aidl_return) override;
+    ndk::ScopedAStatus getHistogramCapability(HistogramCapability *_aidl_return) override;
+    ndk::ScopedAStatus registerHistogram(const ndk::SpAIBinder &token,
+                                         const HistogramConfig &histogramConfig,
+                                         HistogramErrorCode *_aidl_return) override;
+    ndk::ScopedAStatus queryHistogram(const ndk::SpAIBinder &token,
+                                      std::vector<char16_t> *histogramBuffer,
+                                      HistogramErrorCode *_aidl_return) override;
+    ndk::ScopedAStatus reconfigHistogram(const ndk::SpAIBinder &token,
+                                         const HistogramConfig &histogramConfig,
+                                         HistogramErrorCode *_aidl_return) override;
+    ndk::ScopedAStatus unregisterHistogram(const ndk::SpAIBinder &token,
+                                           HistogramErrorCode *_aidl_return) override;
 
 private:
     bool runMediator(const RoiRect &roi, const Weight &weight, const HistogramPos &pos,