[automerger skipped] libhwc2.1: Fix incorrect DRM_FORMAT for RGBA_FP16 am: 3ec426448d am: 61965f4d1c -s ours

am skip reason: Merged-In I2be3c58e9ec3d80e031c3ad24a5592df637a2346 with SHA-1 8f0a2bf300 is already in history

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

Change-Id: Idc66dd05b1d4068d07dd4541cdf66f7063ecbee8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/gralloc-headers/Android.bp b/gralloc-headers/Android.bp
index b87e472..c596301 100644
--- a/gralloc-headers/Android.bp
+++ b/gralloc-headers/Android.bp
@@ -6,15 +6,23 @@
     name: "pixel-gralloc-headers",
     // TODO(270442578): Change to vendor: true
     vendor_available: true,
+    defaults: [
+        "android.hardware.graphics.common-ndk_shared",
+    ],
     export_include_dirs: [
         ".",
     ],
     shared_libs: [
-        "android.hardware.graphics.common-V4-ndk",
+        "android.hardware.graphics.mapper@4.0",
         "libgralloctypes",
     ],
     visibility: [
         "//visibility:public",
     ],
-}
+    // should be platform available since this header is used in lib_aion_buffer which is platform-available
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 
+}
diff --git a/gralloc-headers/pixel-gralloc/format.h b/gralloc-headers/pixel-gralloc/format.h
index 4463067..2edd3ad 100644
--- a/gralloc-headers/pixel-gralloc/format.h
+++ b/gralloc-headers/pixel-gralloc/format.h
@@ -47,6 +47,7 @@
 
     // Pixel specific formats
     GOOGLE_NV12 = 0x301,
+    GOOGLE_R8 = 0x303,
 };
 
 #undef MapFormat
diff --git a/gralloc-headers/pixel-gralloc/mapper.h b/gralloc-headers/pixel-gralloc/mapper.h
new file mode 100644
index 0000000..14990a9
--- /dev/null
+++ b/gralloc-headers/pixel-gralloc/mapper.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <android/hardware/graphics/mapper/4.0/IMapper.h>
+#include <log/log.h>
+
+#include "metadata.h"
+#include "utils.h"
+
+namespace pixel::graphics::mapper {
+
+namespace {
+
+using ::aidl::android::hardware::graphics::common::PlaneLayout;
+using android::hardware::graphics::mapper::V4_0::Error;
+using android::hardware::graphics::mapper::V4_0::IMapper;
+using HidlPixelFormat = ::android::hardware::graphics::common::V1_2::PixelFormat;
+using namespace ::pixel::graphics;
+
+android::sp<IMapper> get_mapper() {
+    static android::sp<IMapper> mapper = []() {
+        auto mapper = IMapper::getService();
+        if (!mapper) {
+            ALOGE("Failed to get mapper service");
+        }
+        return mapper;
+    }();
+
+    return mapper;
+}
+
+} // namespace
+
+template <MetadataType T>
+struct always_false : std::false_type {};
+
+template <MetadataType T>
+struct ReturnType {
+    static_assert(always_false<T>::value, "Unspecialized ReturnType is not supported");
+    using type = void;
+};
+
+template <MetadataType T>
+static std::optional<typename ReturnType<T>::type> get(buffer_handle_t /*handle*/) {
+    static_assert(always_false<T>::value, "Unspecialized get is not supported");
+    return {};
+}
+
+// TODO: Add support for stable-c mapper
+#define GET(metadata, return_type)                                                                \
+    template <>                                                                                   \
+    struct ReturnType<MetadataType::metadata> {                                                   \
+        using type = return_type;                                                                 \
+    };                                                                                            \
+                                                                                                  \
+    template <>                                                                                   \
+    std::optional<typename ReturnType<MetadataType::metadata>::type> get<MetadataType::metadata>( \
+            buffer_handle_t handle) {                                                             \
+        auto mapper = get_mapper();                                                               \
+        IMapper::MetadataType type = {                                                            \
+                .name = kPixelMetadataTypeName,                                                   \
+                .value = static_cast<int64_t>(MetadataType::metadata),                            \
+        };                                                                                        \
+                                                                                                  \
+        android::hardware::hidl_vec<uint8_t> vec;                                                 \
+        Error error;                                                                              \
+        auto ret = mapper->get(const_cast<native_handle_t*>(handle), type,                        \
+                               [&](const auto& tmpError,                                          \
+                                   const android::hardware::hidl_vec<uint8_t>& tmpVec) {          \
+                                   error = tmpError;                                              \
+                                   vec = tmpVec;                                                  \
+                               });                                                                \
+        if (!ret.isOk()) {                                                                        \
+            return {};                                                                            \
+        }                                                                                         \
+                                                                                                  \
+        return utils::decode<return_type>(vec);                                                   \
+    }
+
+GET(PLANE_DMA_BUFS, std::vector<int>);
+GET(VIDEO_HDR, void*);
+GET(VIDEO_ROI, void*);
+
+#undef GET
+
+} // namespace pixel::graphics::mapper
diff --git a/gralloc-headers/pixel-gralloc/metadata.h b/gralloc-headers/pixel-gralloc/metadata.h
index 7502f29..adb44c0 100644
--- a/gralloc-headers/pixel-gralloc/metadata.h
+++ b/gralloc-headers/pixel-gralloc/metadata.h
@@ -51,11 +51,18 @@
 
     // TODO: These metadata queries returns a pointer inside metadata for now. Need to change that
     // so we are returning proper data only.
+    // Returns: void*
     VIDEO_HDR = std::numeric_limits<int64_t>::max() - (1 << 16),
 
     // TODO(b/289448426#comment2): ROIINFO is probably not being used. Remove this after
     // confirmation.
+    // Returns: void*
     VIDEO_ROI,
+
+    // This metadata just refers to the same fd contained in buffer handle and not a clone.
+    // So the client should not attempt to close these fds.
+    // Returns: std::vector<int>
+    PLANE_DMA_BUFS,
 };
 
 #undef MapMetadataType
diff --git a/gralloc-headers/pixel-gralloc/usage.h b/gralloc-headers/pixel-gralloc/usage.h
index 323e3c2..54a1053 100644
--- a/gralloc-headers/pixel-gralloc/usage.h
+++ b/gralloc-headers/pixel-gralloc/usage.h
@@ -39,8 +39,18 @@
     MapUsage(VENDOR_MASK),
     MapUsage(VENDOR_MASK_HI),
 
-    // Pixel specific usage
+    // Pixel specific usages
+
+    // Used for AION to allocate PLACEHOLDER buffers
+    PLACEHOLDER_BUFFER = 1ULL << 28,
+
     NO_COMPRESSION = 1ULL << 29,
+
+    // Used for the camera ISP image heap of the dual PD buffer.
+    TPU_INPUT = 1ULL << 62,
+
+    // Used to select specific heap for faceauth raw images used for evaluation
+    FACEAUTH_RAW_EVAL = 1ULL << 63,
 };
 
 #undef MapUsage
diff --git a/gralloc-headers/pixel-gralloc/utils.h b/gralloc-headers/pixel-gralloc/utils.h
new file mode 100644
index 0000000..993ca16
--- /dev/null
+++ b/gralloc-headers/pixel-gralloc/utils.h
@@ -0,0 +1,88 @@
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <optional>
+#include <type_traits>
+#include <vector>
+
+namespace {
+
+// Trivial type
+template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
+std::vector<uint8_t> encode_helper(const T& val) {
+    auto begin = reinterpret_cast<const uint8_t*>(&val);
+    auto end = begin + sizeof(val);
+    return {begin, end};
+}
+
+// Container type
+template <typename Container,
+          std::enable_if_t<!std::is_trivially_copyable_v<Container>, bool> = true>
+std::vector<uint8_t> encode_helper(const Container& val) {
+    // Check comment in decode_helper below
+    static_assert(std::is_trivially_copyable_v<typename Container::value_type>,
+                  "Can encode only a containers of trivial types currently");
+
+    constexpr auto member_size = sizeof(typename Container::value_type);
+    auto n_bytes = member_size * val.size();
+
+    std::vector<uint8_t> out(n_bytes);
+    std::memcpy(out.data(), val.data(), n_bytes);
+
+    return out;
+}
+
+// Trivial type
+template <typename T, std::enable_if_t<std::is_trivially_copyable_v<T>, bool> = true>
+std::optional<T> decode_helper(const std::vector<uint8_t>& bytes) {
+    T t;
+
+    if (sizeof(t) != bytes.size()) {
+        return {};
+    }
+
+    std::memcpy(&t, bytes.data(), bytes.size());
+    return t;
+}
+
+// Container type
+template <typename Container,
+          std::enable_if_t<!std::is_trivially_copyable_v<Container>, bool> = true>
+std::optional<Container> decode_helper(const std::vector<uint8_t>& bytes) {
+    Container t;
+    size_t member_size = sizeof(typename Container::value_type);
+
+    // NOTE: This can only reconstruct container of trivial types, not a
+    // container of non-trivial types. We can either use a standard serializer
+    // (like protobuf) or roll one of our own simple ones (like prepending size
+    // of the object), but have to be careful about securing such a serializer.
+    // But, do we even need that? I do not see any metadata which is either not
+    // trivial or a container of trivial type.
+    size_t to_copy = bytes.size();
+    if (to_copy % member_size != 0) {
+        return {};
+    }
+
+    size_t members = to_copy / member_size;
+    t.resize(members);
+    std::memcpy(t.data(), bytes.data(), to_copy);
+    return t;
+}
+
+} // namespace
+
+namespace pixel::graphics::utils {
+
+// TODO: Setup a fuzzer for encode/decode
+template <typename T>
+std::vector<uint8_t> encode(const T& val) {
+    return encode_helper(val);
+}
+
+template <typename T>
+std::optional<T> decode(const std::vector<uint8_t>& bytes) {
+    return decode_helper<T>(bytes);
+}
+
+} // namespace pixel::graphics::utils
diff --git a/hwc3/Android.mk b/hwc3/Android.mk
index 6358a90..38f9595 100644
--- a/hwc3/Android.mk
+++ b/hwc3/Android.mk
@@ -32,11 +32,11 @@
 	-Wthread-safety
 
 # hwc3 re-uses hwc2.2 ComposerResource and libexynosdisplay
-LOCAL_SHARED_LIBRARIES := android.hardware.graphics.composer3-V2-ndk \
+LOCAL_SHARED_LIBRARIES := android.hardware.graphics.composer3-V3-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-V9-ndk \
+	com.google.hardware.pixel.display-V10-ndk \
 	libbase \
 	libbinder \
 	libbinder_ndk \
@@ -86,6 +86,20 @@
 LOCAL_HEADER_LIBRARIES += libbinder_headers
 endif
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 LOCAL_VINTF_FRAGMENTS = hwc3-default.xml
 LOCAL_INIT_RC := hwc3-pixel.rc
 
diff --git a/hwc3/Composer.cpp b/hwc3/Composer.cpp
index 4a57d96..40cadc8 100644
--- a/hwc3/Composer.cpp
+++ b/hwc3/Composer.cpp
@@ -25,6 +25,17 @@
 
 namespace aidl::android::hardware::graphics::composer3::impl {
 
+Composer::Composer() {
+    int32_t composerInterfaceVersion = 1;
+    const auto status = getInterfaceVersion(&composerInterfaceVersion);
+    if (!status.isOk()) {
+        ALOGE("Get interface version from Composer constructor failed %s",
+              status.getDescription().c_str());
+    }
+    mHal = IComposerHal::create(composerInterfaceVersion);
+    CHECK(mHal != nullptr);
+}
+
 ndk::ScopedAStatus Composer::createClient(std::shared_ptr<IComposerClient>* outClient) {
     DEBUG_FUNC();
     std::unique_lock<std::mutex> lock(mClientMutex);
diff --git a/hwc3/Composer.h b/hwc3/Composer.h
index d35c3de..5ea93a1 100644
--- a/hwc3/Composer.h
+++ b/hwc3/Composer.h
@@ -26,11 +26,11 @@
 
 class Composer : public BnComposer {
 public:
-    Composer(std::unique_ptr<IComposerHal> hal) : mHal(std::move(hal)) {}
+    Composer();
 
     binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
 
-    // compser3 api
+    // composer3 api
     ndk::ScopedAStatus createClient(std::shared_ptr<IComposerClient>* client) override;
     ndk::ScopedAStatus getCapabilities(std::vector<Capability>* caps) override;
 
@@ -41,7 +41,7 @@
     bool waitForClientDestroyedLocked(std::unique_lock<std::mutex>& lock);
     void onClientDestroyed();
 
-    const std::unique_ptr<IComposerHal> mHal;
+    std::unique_ptr<IComposerHal> mHal;
     std::mutex mClientMutex;
     bool mClientAlive = false; // GUARDED_BY(mClientMutex)
     std::condition_variable mClientDestroyedCondition;
diff --git a/hwc3/ComposerClient.cpp b/hwc3/ComposerClient.cpp
index c34b13a..104bac6 100644
--- a/hwc3/ComposerClient.cpp
+++ b/hwc3/ComposerClient.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/logging.h>
 #include <android/binder_ibinder_platform.h>
+#include <hardware/hwcomposer2.h>
 
 #include "Util.h"
 
@@ -76,6 +77,21 @@
     return TO_BINDER_STATUS(err);
 }
 
+ndk::ScopedAStatus ComposerClient::getDisplayConfigurations(
+        int64_t display, int32_t maxFrameIntervalNs, std::vector<DisplayConfiguration>* configs) {
+    DEBUG_DISPLAY_FUNC(display);
+    auto err = mHal->getDisplayConfigurations(display, maxFrameIntervalNs, configs);
+    return TO_BINDER_STATUS(err);
+}
+
+ndk::ScopedAStatus ComposerClient::notifyExpectedPresent(
+        int64_t display, const ClockMonotonicTimestamp& expectedPresentTime,
+        int32_t frameIntervalNs) {
+    DEBUG_DISPLAY_FUNC(display);
+    auto err = mHal->notifyExpectedPresent(display, expectedPresentTime, frameIntervalNs);
+    return TO_BINDER_STATUS(err);
+}
+
 ndk::ScopedAStatus ComposerClient::destroyLayer(int64_t display, int64_t layer) {
     DEBUG_DISPLAY_FUNC(display);
     auto err = mHal->destroyLayer(display, layer);
@@ -171,6 +187,16 @@
         caps->push_back(DisplayCapability::DISPLAY_IDLE_TIMER);
     }
 
+    err = mHal->getDisplayMultiThreadedPresentSupport(display, support);
+    if (err != ::android::OK) {
+        LOG(ERROR) << "failed to getDisplayMultiThreadedPresentSupport: " << err;
+        return TO_BINDER_STATUS(err);
+    }
+
+    if (support) {
+        caps->push_back(DisplayCapability::MULTI_THREADED_PRESENT);
+    }
+
     return TO_BINDER_STATUS(err);
 }
 
diff --git a/hwc3/ComposerClient.h b/hwc3/ComposerClient.h
index 7a76fa4..11e0a52 100644
--- a/hwc3/ComposerClient.h
+++ b/hwc3/ComposerClient.h
@@ -134,6 +134,12 @@
     ndk::ScopedAStatus setIdleTimerEnabled(int64_t display, int32_t timeout) override;
     ndk::ScopedAStatus setRefreshRateChangedCallbackDebugEnabled(int64_t /* display */,
                                                                  bool /* enabled */) override;
+    ndk::ScopedAStatus getDisplayConfigurations(
+            int64_t display, int32_t maxFrameIntervalNs,
+            std::vector<DisplayConfiguration>* configs) override;
+    ndk::ScopedAStatus notifyExpectedPresent(int64_t display,
+                                             const ClockMonotonicTimestamp& expectedPresentTime,
+                                             int32_t frameIntervalNs) override;
 
 protected:
     ::ndk::SpAIBinder createBinder() override;
diff --git a/hwc3/ComposerCommandEngine.cpp b/hwc3/ComposerCommandEngine.cpp
index eac4df2..32ad027 100644
--- a/hwc3/ComposerCommandEngine.cpp
+++ b/hwc3/ComposerCommandEngine.cpp
@@ -51,11 +51,11 @@
         }                                                                    \
     } while (0)
 
-#define DISPATCH_DISPLAY_BOOL_COMMAND_AND_DATA(displayCmd, field, data, funcName) \
-    do {                                                                          \
-        if (displayCmd.field) {                                                   \
-            execute##funcName(displayCmd.display, displayCmd.data);               \
-        }                                                                         \
+#define DISPATCH_DISPLAY_COMMAND_AND_TWO_DATA(displayCmd, field, data_1, data_2, funcName) \
+    do {                                                                                   \
+        if (displayCmd.field) {                                                            \
+            execute##funcName(displayCmd.display, displayCmd.data_1, displayCmd.data_2);   \
+        }                                                                                  \
     } while (0)
 
 int32_t ComposerCommandEngine::init() {
@@ -104,12 +104,12 @@
     DISPATCH_DISPLAY_COMMAND(command, colorTransformMatrix, SetColorTransform);
     DISPATCH_DISPLAY_COMMAND(command, clientTarget, SetClientTarget);
     DISPATCH_DISPLAY_COMMAND(command, virtualDisplayOutputBuffer, SetOutputBuffer);
-    DISPATCH_DISPLAY_BOOL_COMMAND_AND_DATA(command, validateDisplay, expectedPresentTime,
-                                           ValidateDisplay);
+    DISPATCH_DISPLAY_COMMAND_AND_TWO_DATA(command, validateDisplay, expectedPresentTime,
+                                          frameIntervalNs, ValidateDisplay);
     DISPATCH_DISPLAY_BOOL_COMMAND(command, acceptDisplayChanges, AcceptDisplayChanges);
     DISPATCH_DISPLAY_BOOL_COMMAND(command, presentDisplay, PresentDisplay);
-    DISPATCH_DISPLAY_BOOL_COMMAND_AND_DATA(command, presentOrValidateDisplay, expectedPresentTime,
-                                           PresentOrValidateDisplay);
+    DISPATCH_DISPLAY_COMMAND_AND_TWO_DATA(command, presentOrValidateDisplay, expectedPresentTime,
+                                          frameIntervalNs, PresentOrValidateDisplay);
 }
 
 void ComposerCommandEngine::dispatchLayerCommand(int64_t display, const LayerCommand& command) {
@@ -213,13 +213,15 @@
 }
 
 void ComposerCommandEngine::executeSetExpectedPresentTimeInternal(
-        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) {
-    mHal->setExpectedPresentTime(display, expectedPresentTime);
+        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+        int frameIntervalNs) {
+    mHal->setExpectedPresentTime(display, expectedPresentTime, frameIntervalNs);
 }
 
 void ComposerCommandEngine::executeValidateDisplay(
-        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) {
-    executeSetExpectedPresentTimeInternal(display, expectedPresentTime);
+        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+        int frameIntervalNs) {
+    executeSetExpectedPresentTimeInternal(display, expectedPresentTime, frameIntervalNs);
     executeValidateDisplayInternal(display);
 }
 
@@ -233,8 +235,9 @@
 }
 
 void ComposerCommandEngine::executePresentOrValidateDisplay(
-        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) {
-    executeSetExpectedPresentTimeInternal(display, expectedPresentTime);
+        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+        int frameIntervalNs) {
+    executeSetExpectedPresentTimeInternal(display, expectedPresentTime, frameIntervalNs);
     // First try to Present as is.
     auto presentErr = mResources->mustValidateDisplay(display) ? IComposerClient::EX_NOT_VALIDATED
                                                                : executePresentDisplay(display);
diff --git a/hwc3/ComposerCommandEngine.h b/hwc3/ComposerCommandEngine.h
index 872c7e5..1ec110e 100644
--- a/hwc3/ComposerCommandEngine.h
+++ b/hwc3/ComposerCommandEngine.h
@@ -52,9 +52,11 @@
       void executeSetDisplayBrightness(uint64_t display, const DisplayBrightness& command);
       void executeSetOutputBuffer(uint64_t display, const Buffer& buffer);
       void executeValidateDisplay(int64_t display,
-                                  const std::optional<ClockMonotonicTimestamp> expectedPresentTime);
+                                  const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+                                  int frameIntervalNs);
       void executePresentOrValidateDisplay(
-              int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime);
+              int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+              int frameIntervalNs);
       void executeAcceptDisplayChanges(int64_t display);
       int executePresentDisplay(int64_t display);
 
@@ -94,7 +96,8 @@
 
       int32_t executeValidateDisplayInternal(int64_t display);
       void executeSetExpectedPresentTimeInternal(
-              int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime);
+              int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+              int frameIntervalNs);
 
       IComposerHal* mHal;
       IResourceManager* mResources;
diff --git a/hwc3/hwc3-default.xml b/hwc3/hwc3-default.xml
index fd9e638..911f7f8 100644
--- a/hwc3/hwc3-default.xml
+++ b/hwc3/hwc3-default.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.graphics.composer3</name>
-        <version>2</version>
+        <version>3</version>
         <interface>
             <name>IComposer</name>
             <instance>default</instance>
diff --git a/hwc3/impl/HalImpl.cpp b/hwc3/impl/HalImpl.cpp
index d8fe206..37e7d9f 100644
--- a/hwc3/impl/HalImpl.cpp
+++ b/hwc3/impl/HalImpl.cpp
@@ -30,14 +30,20 @@
 
 using namespace SOC_VERSION;
 
+namespace {
+
+static constexpr int32_t kMinComposerInterfaceVersionForVrrApi = 3;
+
+};
+
 namespace aidl::android::hardware::graphics::composer3::impl {
 
-std::unique_ptr<IComposerHal> IComposerHal::create() {
-    auto device = std::make_unique<ExynosDeviceModule>();
+std::unique_ptr<IComposerHal> IComposerHal::create(int32_t composerInterfaceVersion) {
+    bool vrrApiSupported = composerInterfaceVersion >= kMinComposerInterfaceVersionForVrrApi;
+    auto device = std::make_unique<ExynosDeviceModule>(vrrApiSupported);
     if (!device) {
         return nullptr;
     }
-
     return std::make_unique<HalImpl>(std::move(device));
 }
 
@@ -107,9 +113,11 @@
 
     h2a::translate(hwcDisplay, display);
     h2a::translate(hwcVsyncPeriodNanos, vsyncPeriodNanos);
+    // TODO (b/314527560) Update refreshPeriodNanos for VRR display
     hal->getEventCallback()->onRefreshRateChangedDebug(RefreshRateChangedDebugData{
             .display = display,
             .vsyncPeriodNanos = vsyncPeriodNanos,
+            .refreshPeriodNanos = vsyncPeriodNanos,
     });
 }
 
@@ -373,6 +381,67 @@
     return HWC2_ERROR_NONE;
 }
 
+int32_t HalImpl::getDisplayConfigurations(int64_t display, int32_t,
+                                          std::vector<DisplayConfiguration>* outConfigs) {
+    ExynosDisplay* halDisplay;
+    RET_IF_ERR(getHalDisplay(display, halDisplay));
+
+    std::vector<int32_t> configIds;
+    RET_IF_ERR(getDisplayConfigs(display, &configIds));
+
+    for (const auto configId : configIds) {
+        DisplayConfiguration config;
+        config.configId = configId;
+        // Get required display attributes
+        RET_IF_ERR(getDisplayAttribute(display, configId, DisplayAttribute::WIDTH, &config.width));
+        RET_IF_ERR(
+                getDisplayAttribute(display, configId, DisplayAttribute::HEIGHT, &config.height));
+        RET_IF_ERR(getDisplayAttribute(display, configId, DisplayAttribute::VSYNC_PERIOD,
+                                       &config.vsyncPeriod));
+        RET_IF_ERR(getDisplayAttribute(display, configId, DisplayAttribute::CONFIG_GROUP,
+                                       &config.configGroup));
+        // Get optional display attributes
+        int32_t dpiX, dpiY;
+        auto statusDpiX = getDisplayAttribute(display, configId, DisplayAttribute::DPI_X, &dpiX);
+        auto statusDpiY = getDisplayAttribute(display, configId, DisplayAttribute::DPI_Y, &dpiY);
+        // TODO(b/294120341): getDisplayAttribute for DPI should return dots per inch
+        if (statusDpiX == HWC2_ERROR_NONE && statusDpiY == HWC2_ERROR_NONE) {
+            config.dpi = {dpiX / 1000.0f, dpiY / 1000.0f};
+        }
+        // Determine whether there is a need to configure VRR.
+        hwc2_config_t hwcConfigId;
+        a2h::translate(configId, hwcConfigId);
+        std::optional<VrrConfig_t> vrrConfig = halDisplay->getVrrConfigs(hwcConfigId);
+        if (vrrConfig.has_value()) {
+            // TODO(b/290843234): complete the remaining values within vrrConfig.
+            VrrConfig hwc3VrrConfig;
+            VrrConfig::NotifyExpectedPresentConfig notifyExpectedPresentConfig;
+            hwc3VrrConfig.minFrameIntervalNs = vrrConfig->minFrameIntervalNs;
+            notifyExpectedPresentConfig.notifyExpectedPresentHeadsUpNs =
+                    vrrConfig->notifyExpectedPresentConfig.HeadsUpNs;
+            notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs =
+                    vrrConfig->notifyExpectedPresentConfig.TimeoutNs;
+            hwc3VrrConfig.notifyExpectedPresentConfig =
+                    std::make_optional(notifyExpectedPresentConfig);
+            config.vrrConfig = std::make_optional(hwc3VrrConfig);
+        }
+        outConfigs->push_back(config);
+    }
+
+    return HWC2_ERROR_NONE;
+}
+
+int32_t HalImpl::notifyExpectedPresent(int64_t display,
+                                       const ClockMonotonicTimestamp& expectedPresentTime,
+                                       int32_t frameIntervalNs) {
+    ExynosDisplay* halDisplay;
+    RET_IF_ERR(getHalDisplay(display, halDisplay));
+
+    RET_IF_ERR(
+            halDisplay->notifyExpectedPresent(expectedPresentTime.timestampNanos, frameIntervalNs));
+    return HWC2_ERROR_NONE;
+}
+
 int32_t HalImpl::getDisplayConnectionType(int64_t display, DisplayConnectionType* outType) {
     ExynosDisplay* halDisplay;
     RET_IF_ERR(getHalDisplay(display, halDisplay));
@@ -1045,7 +1114,8 @@
 }
 
 int HalImpl::setExpectedPresentTime(
-        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) {
+        int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+        int frameIntervalNs) {
     ExynosDisplay* halDisplay;
     RET_IF_ERR(getHalDisplay(display, halDisplay));
 
@@ -1055,7 +1125,7 @@
         ALOGW("HalImpl: set expected present time multiple times in one frame");
     }
 
-    halDisplay->setExpectedPresentTime(expectedPresentTime->timestampNanos);
+    halDisplay->setExpectedPresentTime(expectedPresentTime->timestampNanos, frameIntervalNs);
 
     return HWC2_ERROR_NONE;
 }
diff --git a/hwc3/impl/HalImpl.h b/hwc3/impl/HalImpl.h
index 9822ff7..6a0108a 100644
--- a/hwc3/impl/HalImpl.h
+++ b/hwc3/impl/HalImpl.h
@@ -60,6 +60,11 @@
     int32_t getDisplayBrightnessSupport(int64_t display, bool& outSupport) override;
     int32_t getDisplayCapabilities(int64_t display, std::vector<DisplayCapability>* caps) override;
     int32_t getDisplayConfigs(int64_t display, std::vector<int32_t>* configs) override;
+    int32_t getDisplayConfigurations(int64_t display, int32_t maxFrameIntervalNs,
+                                     std::vector<DisplayConfiguration>* outConfigs) override;
+    int32_t notifyExpectedPresent(int64_t display,
+                                  const ClockMonotonicTimestamp& expectedPresentTime,
+                                  int32_t frameIntervalNs) override;
     int32_t getDisplayConnectionType(int64_t display, DisplayConnectionType* outType) override;
     int32_t getDisplayIdentificationData(int64_t display, DisplayIdentification* id) override;
     int32_t getDisplayName(int64_t display, std::string* outName) override;
@@ -154,9 +159,9 @@
                             std::vector<int32_t>* outRequestMasks,
                             ClientTargetProperty* outClientTargetProperty,
                             DimmingStage* outDimmingStage) override;
-    int32_t setExpectedPresentTime(
-            int64_t display,
-            const std::optional<ClockMonotonicTimestamp> expectedPresentTime) override;
+    int32_t setExpectedPresentTime(int64_t display,
+                                   const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+                                   int frameIntervalNs) override;
 
     EventCallback* getEventCallback() { return mEventCallback; }
     int32_t setRefreshRateChangedCallbackDebugEnabled(int64_t display, bool enabled) override;
diff --git a/hwc3/include/IComposerHal.h b/hwc3/include/IComposerHal.h
index 93dda20..2387973 100644
--- a/hwc3/include/IComposerHal.h
+++ b/hwc3/include/IComposerHal.h
@@ -49,6 +49,7 @@
 #include <aidl/android/hardware/graphics/composer3/DisplayBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCommand.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayConnectionType.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayContentSample.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayContentSamplingAttributes.h>
@@ -83,6 +84,7 @@
 // avoid naming conflict
 using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
 using AidlNativeHandle = aidl::android::hardware::common::NativeHandle;
+using DisplayConfiguration = aidl::android::hardware::graphics::composer3::DisplayConfiguration;
 
 namespace aidl::android::hardware::graphics::composer3::impl {
 
@@ -90,25 +92,25 @@
 // IComposerClient interface.
 class IComposerHal {
  public:
-    static std::unique_ptr<IComposerHal> create();
-    virtual ~IComposerHal() = default;
+     static std::unique_ptr<IComposerHal> create(int32_t composerInterfaceVersion);
+     virtual ~IComposerHal() = default;
 
-    virtual void getCapabilities(std::vector<Capability>* caps) = 0;
-    virtual void dumpDebugInfo(std::string* output) = 0;
-    virtual bool hasCapability(Capability cap) = 0;
+     virtual void getCapabilities(std::vector<Capability>* caps) = 0;
+     virtual void dumpDebugInfo(std::string* output) = 0;
+     virtual bool hasCapability(Capability cap) = 0;
 
-    class EventCallback {
-      public:
-        virtual ~EventCallback() = default;
-        virtual void onHotplug(int64_t display, bool connected) = 0;
-        virtual void onRefresh(int64_t display) = 0;
-        virtual void onVsync(int64_t display, int64_t timestamp, int32_t vsyncPeriodNanos) = 0;
-        virtual void onVsyncPeriodTimingChanged(int64_t display,
-                                                const VsyncPeriodChangeTimeline& timeline) = 0;
-        virtual void onVsyncIdle(int64_t display) = 0;
-        virtual void onSeamlessPossible(int64_t display) = 0;
-        virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) = 0;
-    };
+     class EventCallback {
+     public:
+         virtual ~EventCallback() = default;
+         virtual void onHotplug(int64_t display, bool connected) = 0;
+         virtual void onRefresh(int64_t display) = 0;
+         virtual void onVsync(int64_t display, int64_t timestamp, int32_t vsyncPeriodNanos) = 0;
+         virtual void onVsyncPeriodTimingChanged(int64_t display,
+                                                 const VsyncPeriodChangeTimeline& timeline) = 0;
+         virtual void onVsyncIdle(int64_t display) = 0;
+         virtual void onSeamlessPossible(int64_t display) = 0;
+         virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) = 0;
+     };
     virtual void registerEventCallback(EventCallback* callback) = 0;
     virtual void unregisterEventCallback() = 0;
 
@@ -132,6 +134,11 @@
     virtual int32_t getDisplayCapabilities(int64_t display,
                                            std::vector<DisplayCapability>* caps) = 0;
     virtual int32_t getDisplayConfigs(int64_t display, std::vector<int32_t>* configs) = 0;
+    virtual int32_t getDisplayConfigurations(int64_t display, int32_t maxFrameIntervalNs,
+                                             std::vector<DisplayConfiguration>* configs) = 0;
+    virtual int32_t notifyExpectedPresent(int64_t display,
+                                          const ClockMonotonicTimestamp& expectedPresentTime,
+                                          int32_t frameIntervalNs) = 0;
     virtual int32_t getDisplayConnectionType(int64_t display, DisplayConnectionType* outType) = 0;
     virtual int32_t getDisplayIdentificationData(int64_t display, DisplayIdentification *id) = 0;
     virtual int32_t getDisplayName(int64_t display, std::string* outName) = 0;
@@ -226,7 +233,8 @@
                                     ClientTargetProperty* outClientTargetProperty,
                                     DimmingStage* outDimmingStage) = 0;
     virtual int32_t setExpectedPresentTime(
-            int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime) = 0;
+            int64_t display, const std::optional<ClockMonotonicTimestamp> expectedPresentTime,
+            int frameIntervalNs) = 0;
     virtual int32_t setIdleTimerEnabled(int64_t display, int32_t timeout) = 0;
     virtual int32_t getRCDLayerSupport(int64_t display, bool& outSupport) = 0;
     virtual int32_t setLayerBlockingRegion(
diff --git a/hwc3/service.cpp b/hwc3/service.cpp
index 9269470..4b5fb1a 100644
--- a/hwc3/service.cpp
+++ b/hwc3/service.cpp
@@ -40,10 +40,7 @@
         LOG(ERROR) << "Couldn't set SCHED_FIFO: " << errno;
     }
 
-    std::unique_ptr<IComposerHal> halImpl = IComposerHal::create();
-    CHECK(halImpl != nullptr);
-
-    std::shared_ptr<Composer> composer = ndk::SharedRefBase::make<Composer>(std::move(halImpl));
+    std::shared_ptr<Composer> composer = ndk::SharedRefBase::make<Composer>();
     CHECK(composer != nullptr);
 
     const std::string instance = std::string() + Composer::descriptor + "/default";
diff --git a/include/displaycolor/displaycolor.h b/include/displaycolor/displaycolor.h
index 8cef849..002ee8e 100644
--- a/include/displaycolor/displaycolor.h
+++ b/include/displaycolor/displaycolor.h
@@ -86,8 +86,10 @@
     DISPLAY_PRIMARY = 0,
     /// builtin secondary display
     DISPLAY_SECONDARY = 1,
+    /// external display
+    DISPLAY_EXTERNAL = 2,
     /// number of display
-    DISPLAY_MAX = 2,
+    DISPLAY_MAX = 3,
 };
 
 enum BrightnessMode {
@@ -149,6 +151,7 @@
  * @brief This structure holds data imported from HWC.
  */
 struct DisplayInfo {
+    DisplayType display_type;
     std::string panel_name;
     std::string panel_serial;
 
@@ -362,7 +365,7 @@
     float refresh_rate = 60.0f;
 
     /// operation rate to switch between hs/ns mode
-    uint32_t operation_rate;
+    uint32_t operation_rate = 120;
 
     /// hdr layer state on screen
     HdrLayerState hdr_layer_state = HdrLayerState::kHdrNone;
@@ -483,7 +486,9 @@
      * @param table Return brightness table if successful, nullptr if the table is not valid.
      * @return OK if successful, error otherwise.
      */
-    virtual int GetBrightnessTable(DisplayType display, const IBrightnessTable *&table) const = 0;
+    virtual int GetBrightnessTable(DisplayType display,
+                                   std::unique_ptr<const IBrightnessTable> &table) const = 0;
+
 };
 
 extern "C" {
diff --git a/libacryl/Android.mk b/libacryl/Android.mk
index 98a51f2..43471e3 100644
--- a/libacryl/Android.mk
+++ b/libacryl/Android.mk
@@ -42,6 +42,20 @@
     LOCAL_CFLAGS += -DLIBACRYL_G2D_HDR_PLUGIN
 endif
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 LOCAL_HEADER_LIBRARIES += google_libacryl_hdrplugin_headers
 LOCAL_HEADER_LIBRARIES += google_hal_headers
 LOCAL_HEADER_LIBRARIES += libgralloc_headers
diff --git a/libhwc2.1/Android.mk b/libhwc2.1/Android.mk
index 5224595..746bb7a 100644
--- a/libhwc2.1/Android.mk
+++ b/libhwc2.1/Android.mk
@@ -47,6 +47,20 @@
 LOCAL_CFLAGS += -Wthread-safety
 LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libdrm
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 LOCAL_MODULE := libdrmresource
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
 LOCAL_LICENSE_CONDITIONS := notice
@@ -68,8 +82,8 @@
 	libvendorgraphicbuffer libbinder_ndk \
 	android.hardware.power-V2-ndk pixel-power-ext-V1-ndk
 
-LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V2-ndk \
-                          com.google.hardware.pixel.display-V9-ndk \
+LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V3-ndk \
+                          com.google.hardware.pixel.display-V10-ndk \
                           libbinder_ndk \
                           libbase \
                           libpng \
@@ -105,6 +119,7 @@
 	$(TOP)/hardware/google/graphics/common/libhwc2.1/libhwcService \
 	$(TOP)/hardware/google/graphics/common/libhwc2.1/libdisplayinterface \
 	$(TOP)/hardware/google/graphics/common/libhwc2.1/libdrmresource/include \
+	$(TOP)/hardware/google/graphics/common/libhwc2.1/libvrr \
         $(TOP)/hardware/google/graphics/$(soc_ver)
 LOCAL_SRC_FILES := \
 	libhwchelper/ExynosHWCHelper.cpp \
@@ -123,6 +138,7 @@
 	libdisplayinterface/ExynosDisplayInterface.cpp \
 	libdisplayinterface/ExynosDeviceDrmInterface.cpp \
 	libdisplayinterface/ExynosDisplayDrmInterface.cpp \
+	libvrr/VariableRefreshRateController.cpp \
 	pixel-display.cpp \
 	histogram_mediator.cpp
 
@@ -142,6 +158,20 @@
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
 LOCAL_CFLAGS += -Wthread-safety
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 LOCAL_MODULE := libexynosdisplay
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
 LOCAL_LICENSE_CONDITIONS := notice
@@ -163,9 +193,9 @@
 	android.hardware.graphics.composer@2.4 \
 	android.hardware.graphics.allocator@2.0 \
 	android.hardware.graphics.mapper@2.0 \
-	android.hardware.graphics.composer3-V2-ndk
+	android.hardware.graphics.composer3-V3-ndk
 
-LOCAL_SHARED_LIBRARIES += com.google.hardware.pixel.display-V9-ndk \
+LOCAL_SHARED_LIBRARIES += com.google.hardware.pixel.display-V10-ndk \
                           libbinder_ndk \
                           libbase
 
@@ -200,6 +230,20 @@
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
 LOCAL_CFLAGS += -Wthread-safety
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 LOCAL_SRC_FILES := \
 	libhwcService/IExynosHWC.cpp \
 	libhwcService/ExynosHWCService.cpp
@@ -227,8 +271,8 @@
 	android.hardware.graphics.mapper@2.0 \
 	libui
 
-LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V2-ndk \
-                          com.google.hardware.pixel.display-V9-ndk \
+LOCAL_SHARED_LIBRARIES += android.hardware.graphics.composer3-V3-ndk \
+                          com.google.hardware.pixel.display-V10-ndk \
                           libbinder_ndk \
                           libbase
 
@@ -241,6 +285,20 @@
 LOCAL_CFLAGS += -DSOC_VERSION=$(soc_ver)
 LOCAL_CFLAGS += -Wthread-safety
 
+ifeq ($(CLANG_COVERAGE),true)
+# enable code coverage (these flags are copied from build/soong/cc/coverage.go)
+LOCAL_CFLAGS += -fprofile-instr-generate -fcoverage-mapping
+LOCAL_CFLAGS += -Wno-frame-larger-than=
+LOCAL_WHOLE_STATIC_LIBRARIES += libprofile-clang-extras_ndk
+LOCAL_LDFLAGS += -fprofile-instr-generate
+LOCAL_LDFLAGS += -Wl,--wrap,open
+
+ifeq ($(CLANG_COVERAGE_CONTINUOUS_MODE),true)
+LOCAL_CFLAGS += -mllvm -runtime-counter-relocation
+LOCAL_LDFLAGS += -Wl,-mllvm=-runtime-counter-relocation
+endif
+endif
+
 ifeq ($(BOARD_USES_HWC_SERVICES),true)
 LOCAL_CFLAGS += -DUSES_HWC_SERVICES
 LOCAL_SHARED_LIBRARIES += libExynosHWCService
diff --git a/libhwc2.1/ExynosHWC.cpp b/libhwc2.1/ExynosHWC.cpp
index f83a735..cca498f 100644
--- a/libhwc2.1/ExynosHWC.cpp
+++ b/libhwc2.1/ExynosHWC.cpp
@@ -1340,7 +1340,8 @@
     dev = (struct exynos_hwc2_device_t *)malloc(sizeof(*dev));
     memset(dev, 0, sizeof(*dev));
 
-    dev->device = new ExynosDeviceModule;
+    // The legacy HIDL does not provide compatibility for the Vrr API as defined by AIDL.
+    dev->device = new ExynosDeviceModule(false);
     g_exynosDevice = dev->device;
 
     dev->base.common.tag = HARDWARE_DEVICE_TAG;
diff --git a/libhwc2.1/ExynosHWCDebug.cpp b/libhwc2.1/ExynosHWCDebug.cpp
index d43f76d..6f2b806 100644
--- a/libhwc2.1/ExynosHWCDebug.cpp
+++ b/libhwc2.1/ExynosHWCDebug.cpp
@@ -18,7 +18,7 @@
 #include <sync/sync.h>
 #include "exynos_sync.h"
 
-int32_t saveErrorLog(const String8 &errString, ExynosDisplay *display) {
+int32_t saveErrorLog(const String8& errString, const ExynosDisplay* display) {
     if (display == nullptr) return -1;
     int32_t ret = NO_ERROR;
 
diff --git a/libhwc2.1/ExynosHWCDebug.h b/libhwc2.1/ExynosHWCDebug.h
index bd62a2e..50826f9 100644
--- a/libhwc2.1/ExynosHWCDebug.h
+++ b/libhwc2.1/ExynosHWCDebug.h
@@ -67,7 +67,7 @@
         return fence;
 }
 
-int32_t saveErrorLog(const android::String8 &errString, ExynosDisplay *display = NULL);
+int32_t saveErrorLog(const android::String8& errString, const ExynosDisplay* display = NULL);
 
 #if defined(DISABLE_HWC_DEBUG)
 #define ALOGD_AND_ATRACE_NAME(debugFlag, fmt, ...)
diff --git a/libhwc2.1/libdevice/BrightnessController.cpp b/libhwc2.1/libdevice/BrightnessController.cpp
index cc36c94..d8055ed 100644
--- a/libhwc2.1/libdevice/BrightnessController.cpp
+++ b/libhwc2.1/libdevice/BrightnessController.cpp
@@ -156,10 +156,10 @@
     }
 }
 
-void BrightnessController::updateBrightnessTable(const IBrightnessTable* table) {
+void BrightnessController::updateBrightnessTable(std::unique_ptr<const IBrightnessTable>& table) {
     if (table && table->GetBrightnessRange(BrightnessMode::BM_NOMINAL)) {
         ALOGI("%s: apply brightness table from libdisplaycolor", __func__);
-        mBrightnessTable = table;
+        mBrightnessTable = std::move(table);
     } else {
         ALOGW("%s: table is not valid!", __func__);
     }
@@ -304,7 +304,7 @@
             reinterpret_cast<struct brightness_capability *>(blob->data);
     mKernelBrightnessTable.Init(cap);
     if (mKernelBrightnessTable.IsValid()) {
-        mBrightnessTable = &mKernelBrightnessTable;
+        mBrightnessTable = std::make_unique<LinearBrightnessTable>(mKernelBrightnessTable);
     }
 
     parseHbmModeEnums(connector.hbm_mode());
@@ -343,6 +343,14 @@
         mAclMode.store(mAclModeDefault);
     }
 
+    if (applyAclViaSysfs() == HWC2_ERROR_NO_RESOURCES)
+        ALOGW("%s try to apply acl_mode when brightness changed", __func__);
+
+    return NO_ERROR;
+}
+
+int BrightnessController::applyAclViaSysfs() {
+    if (!mAclModeOfs.is_open()) return NO_ERROR;
     if (!mAclMode.is_dirty()) return NO_ERROR;
 
     mAclModeOfs.seekp(std::ios_base::beg);
@@ -376,6 +384,11 @@
     }
 
     ATRACE_CALL();
+
+    /* update ACL */
+    if (applyAclViaSysfs() == HWC2_ERROR_NO_RESOURCES)
+        ALOGE("%s failed to apply acl_mode", __func__);
+
     if (!mBrightnessIntfSupported) {
         level = brightness < 0 ? 0 : static_cast<uint32_t>(brightness * mMaxBrightness + 0.5f);
         return applyBrightnessViaSysfs(level);
diff --git a/libhwc2.1/libdevice/BrightnessController.h b/libhwc2.1/libdevice/BrightnessController.h
index 63747f6..de2f72e 100644
--- a/libhwc2.1/libdevice/BrightnessController.h
+++ b/libhwc2.1/libdevice/BrightnessController.h
@@ -77,6 +77,7 @@
     int processOperationRate(int32_t hz);
     bool isDbmSupported() { return mDbmSupported; }
     int applyPendingChangeViaSysfs(const nsecs_t vsyncNs);
+    int applyAclViaSysfs();
     bool validateLayerBrightness(float brightness);
 
     /**
@@ -155,6 +156,11 @@
         return mOperationRate.get();
     }
 
+    bool isOperationRatePending() {
+        std::lock_guard<std::recursive_mutex> lock(mBrightnessMutex);
+        return mOperationRate.is_dirty();
+    }
+
     bool isSupported() {
         // valid mMaxBrightness means both brightness and max_brightness sysfs exist
         return mMaxBrightness > 0;
@@ -181,7 +187,7 @@
         return nodeName.c_str();
     }
 
-    void updateBrightnessTable(const IBrightnessTable* table);
+    void updateBrightnessTable(std::unique_ptr<const IBrightnessTable>& table);
     const BrightnessRangeMap& getBrightnessRanges() const {
         return mKernelBrightnessTable.GetBrightnessRangeMap();
     }
@@ -386,7 +392,7 @@
     bool mBrightnessIntfSupported = false;
     LinearBrightnessTable mKernelBrightnessTable;
     // External object from libdisplaycolor
-    const IBrightnessTable* mBrightnessTable = nullptr;
+    std::unique_ptr<const IBrightnessTable> mBrightnessTable;
 
     int32_t mPanelIndex;
     DrmEnumParser::MapHal2DrmEnum mHbmModeEnums;
diff --git a/libhwc2.1/libdevice/ExynosDevice.cpp b/libhwc2.1/libdevice/ExynosDevice.cpp
index 0f2d202..e432d34 100644
--- a/libhwc2.1/libdevice/ExynosDevice.cpp
+++ b/libhwc2.1/libdevice/ExynosDevice.cpp
@@ -68,16 +68,16 @@
         return INTERFACE_TYPE_FB;
 }
 
-ExynosDevice::ExynosDevice()
-    : mGeometryChanged(0),
-    mVsyncFd(-1),
-    mExtVsyncFd(-1),
-    mVsyncDisplayId(getDisplayId(HWC_DISPLAY_PRIMARY, 0)),
-    mTimestamp(0),
-    mDisplayMode(0),
-    mInterfaceType(INTERFACE_TYPE_FB),
-    mIsInTUI(false)
-{
+ExynosDevice::ExynosDevice(bool vrrApiSupported)
+      : mGeometryChanged(0),
+        mVsyncFd(-1),
+        mExtVsyncFd(-1),
+        mVsyncDisplayId(getDisplayId(HWC_DISPLAY_PRIMARY, 0)),
+        mTimestamp(0),
+        mDisplayMode(0),
+        mInterfaceType(INTERFACE_TYPE_FB),
+        mIsInTUI(false),
+        mVrrApiSupported(vrrApiSupported) {
     exynosHWCControl.forceGpu = false;
     exynosHWCControl.windowUpdate = true;
     exynosHWCControl.forcePanic = false;
@@ -150,8 +150,7 @@
         mDisplayMap.insert(std::make_pair(exynos_display->mDisplayId, exynos_display));
 
 #ifndef FORCE_DISABLE_DR
-        if (exynos_display->mDREnable)
-            exynosHWCControl.useDynamicRecomp = true;
+        if (exynos_display->mDRDefault) exynosHWCControl.useDynamicRecomp = true;
 #endif
     }
 
@@ -222,8 +221,7 @@
     for (uint32_t i = 0; i < mDisplays.size();) {
         ExynosDisplay* display = mDisplays[i];
         display->initDisplayInterface(interfaceType);
-        if (mDeviceInterface->initDisplayInterface(
-                    display->mDisplayInterface) != NO_ERROR) {
+        if (mDeviceInterface->initDisplayInterface(display->mDisplayInterface) != NO_ERROR) {
             ALOGD("Remove display[%d], Failed to initialize display interface", i);
             mDisplays.removeAt(i);
             mDisplayMap.erase(display->mDisplayId);
@@ -312,6 +310,7 @@
             if (mDisplays[i]->mDREnable)
                 return;
         }
+        ALOGI("Destroying dynamic recomposition thread");
         mDRLoopStatus = false;
         mDRWakeUpCondition.notify_one();
         mDRThread.join();
@@ -321,6 +320,7 @@
 void ExynosDevice::dynamicRecompositionThreadCreate()
 {
     if (exynosHWCControl.useDynamicRecomp == true) {
+        ALOGI("Creating dynamic recomposition thread");
         mDRLoopStatus = true;
         mDRThread = std::thread(&dynamicRecompositionThreadLoop, this);
     }
@@ -520,11 +520,31 @@
     return isCallbackRegisteredLocked(descriptor);
 }
 
-void ExynosDevice::onHotPlug(uint32_t displayId, bool status) {
+void ExynosDevice::onHotPlug(uint32_t displayId, bool status, int hotplugErrorCode) {
     Mutex::Autolock lock(mDeviceCallbackMutex);
 
     if (!isCallbackRegisteredLocked(HWC2_CALLBACK_HOTPLUG)) return;
 
+    if (hotplugErrorCode) {
+        // We need to pass the error code to SurfaceFlinger, but we cannot modify the HWC
+        // HAL interface, so for now we'll send the hotplug error via a onVsync callback with
+        // a negative time value indicating the hotplug error.
+        if (isCallbackRegisteredLocked(HWC2_CALLBACK_VSYNC_2_4)) {
+            ALOGD("%s: hotplugErrorCode=%d sending to SF via onVsync_2_4", __func__,
+                  hotplugErrorCode);
+            hwc2_callback_data_t vsyncCallbackData =
+                    mCallbackInfos[HWC2_CALLBACK_VSYNC_2_4].callbackData;
+            HWC2_PFN_VSYNC_2_4 vsyncCallbackFunc = reinterpret_cast<HWC2_PFN_VSYNC_2_4>(
+                    mCallbackInfos[HWC2_CALLBACK_VSYNC_2_4].funcPointer);
+            vsyncCallbackFunc(vsyncCallbackData, displayId, -hotplugErrorCode, ~0);
+            return;
+        } else {
+            ALOGW("%s: onVsync_2_4 is not registered, ignoring hotplugErrorCode=%d", __func__,
+                  hotplugErrorCode);
+            return;
+        }
+    }
+
     hwc2_callback_data_t callbackData = mCallbackInfos[HWC2_CALLBACK_HOTPLUG].callbackData;
     HWC2_PFN_HOTPLUG callbackFunc =
             reinterpret_cast<HWC2_PFN_HOTPLUG>(mCallbackInfos[HWC2_CALLBACK_HOTPLUG].funcPointer);
diff --git a/libhwc2.1/libdevice/ExynosDevice.h b/libhwc2.1/libdevice/ExynosDevice.h
index bcfb112..9bdbea7 100644
--- a/libhwc2.1/libdevice/ExynosDevice.h
+++ b/libhwc2.1/libdevice/ExynosDevice.h
@@ -217,7 +217,7 @@
         std::unique_ptr<ExynosDeviceInterface> mDeviceInterface;
 
         // Con/Destructors
-        ExynosDevice();
+        ExynosDevice(bool vrrApiSupported);
         virtual ~ExynosDevice();
 
         bool isFirstValidate();
@@ -280,7 +280,7 @@
         int32_t registerCallback (
                 int32_t descriptor, hwc2_callback_data_t callbackData, hwc2_function_pointer_t point);
         bool isCallbackAvailable(int32_t descriptor);
-        void onHotPlug(uint32_t displayId, bool status);
+        void onHotPlug(uint32_t displayId, bool status, int hotplugErrorCode);
         void onRefresh(uint32_t displayId);
         void onRefreshDisplays();
 
@@ -346,6 +346,8 @@
 
         void onRefreshRateChangedDebug(hwc2_display_t displayId, uint32_t vsyncPeriod);
 
+        bool isVrrApiSupported() const { return mVrrApiSupported; };
+
     protected:
         void initDeviceInterface(uint32_t interfaceType);
     protected:
@@ -365,6 +367,7 @@
     private:
         bool mIsInTUI;
         bool mDisplayOffAsync;
+        bool mVrrApiSupported = false;
 
     public:
         void handleHotplug();
diff --git a/libhwc2.1/libdevice/ExynosDisplay.cpp b/libhwc2.1/libdevice/ExynosDisplay.cpp
index 3688207..2f77bb9 100644
--- a/libhwc2.1/libdevice/ExynosDisplay.cpp
+++ b/libhwc2.1/libdevice/ExynosDisplay.cpp
@@ -63,7 +63,7 @@
 constexpr int64_t nsecsIdleHintTimeout = std::chrono::nanoseconds(100ms).count();
 
 ExynosDisplay::PowerHalHintWorker::PowerHalHintWorker(uint32_t displayId,
-                                                      const String8 &displayTraceName)
+                                                      const String8& displayTraceName)
       : Worker("DisplayHints", HAL_PRIORITY_URGENT_DISPLAY),
         mNeedUpdateRefreshRateHint(false),
         mLastRefreshRateHint(0),
@@ -74,7 +74,7 @@
         mIdleHintIsSupported(false),
         mDisplayTraceName(displayTraceName),
         mPowerModeState(HWC2_POWER_MODE_OFF),
-        mVsyncPeriod(16666666),
+        mRefreshRate(kDefaultRefreshRateFrequency),
         mConnectRetryCount(0),
         mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)),
         mPowerHalExtAidl(nullptr),
@@ -196,7 +196,7 @@
     return NO_ERROR;
 }
 
-int32_t ExynosDisplay::PowerHalHintWorker::checkRefreshRateHintSupport(int refreshRate) {
+int32_t ExynosDisplay::PowerHalHintWorker::checkRefreshRateHintSupport(const int32_t refreshRate) {
     int32_t ret = NO_ERROR;
 
     if (!isPowerHalExist()) {
@@ -223,7 +223,8 @@
     return ret;
 }
 
-int32_t ExynosDisplay::PowerHalHintWorker::sendRefreshRateHint(int refreshRate, bool enabled) {
+int32_t ExynosDisplay::PowerHalHintWorker::sendRefreshRateHint(const int32_t refreshRate,
+                                                               bool enabled) {
     std::string hintStr = mRefreshRateHintPrefixStr + std::to_string(refreshRate) + "FPS";
     int32_t ret = sendPowerHalExtHint(hintStr, enabled);
     if (ret == -ENOTCONN) {
@@ -234,11 +235,10 @@
 }
 
 int32_t ExynosDisplay::PowerHalHintWorker::updateRefreshRateHintInternal(
-        hwc2_power_mode_t powerMode, uint32_t vsyncPeriod) {
+        const hwc2_power_mode_t powerMode, const int32_t refreshRate) {
     int32_t ret = NO_ERROR;
 
     /* TODO: add refresh rate buckets, tracked in b/181100731 */
-    int refreshRate = round(nsecsPerSec / vsyncPeriod * 0.1f) * 10;
     // skip sending unnecessary hint if it's still the same.
     if (mLastRefreshRateHint == refreshRate && powerMode == HWC2_POWER_MODE_ON) {
         return NO_ERROR;
@@ -340,7 +340,8 @@
     return out;
 }
 
-int32_t ExynosDisplay::PowerHalHintWorker::updateIdleHint(int64_t deadlineTime, bool forceUpdate) {
+int32_t ExynosDisplay::PowerHalHintWorker::updateIdleHint(const int64_t deadlineTime,
+                                                          const bool forceUpdate) {
     int32_t ret = checkIdleHintSupport();
     if (ret != NO_ERROR) {
         return ret;
@@ -461,7 +462,7 @@
     }
 
     mActualWorkDuration = reportedDurationNs;
-    WorkDuration duration = {.durationNanos = reportedDurationNs, .timeStampNanos = systemTime()};
+    WorkDuration duration = {.timeStampNanos = systemTime(), .durationNanos = reportedDurationNs};
 
     if (sTraceHintSessionData) {
         DISPLAY_ATRACE_INT64("Measured duration", actualDurationNanos);
@@ -509,17 +510,17 @@
 }
 
 void ExynosDisplay::PowerHalHintWorker::signalRefreshRate(hwc2_power_mode_t powerMode,
-                                                          uint32_t vsyncPeriod) {
+                                                          int32_t refreshRate) {
     Lock();
     mPowerModeState = powerMode;
-    mVsyncPeriod = vsyncPeriod;
+    mRefreshRate = refreshRate;
     mNeedUpdateRefreshRateHint = true;
     Unlock();
 
     Signal();
 }
 
-void ExynosDisplay::PowerHalHintWorker::signalIdle() {
+void ExynosDisplay::PowerHalHintWorker::signalNonIdle() {
     ATRACE_CALL();
 
     Lock();
@@ -579,7 +580,6 @@
     bool needUpdateRefreshRateHint = mNeedUpdateRefreshRateHint;
     int64_t deadlineTime = mIdleHintDeadlineTime;
     hwc2_power_mode_t powerMode = mPowerModeState;
-    uint32_t vsyncPeriod = mVsyncPeriod;
 
     /*
      * Clear the flags here instead of clearing them after calling the hint
@@ -602,7 +602,7 @@
     updateIdleHint(deadlineTime, forceUpdateIdleHint);
 
     if (needUpdateRefreshRateHint) {
-        int32_t rc = updateRefreshRateHintInternal(powerMode, vsyncPeriod);
+        int32_t rc = updateRefreshRateHintInternal(powerMode, mRefreshRate);
         if (rc != NO_ERROR && rc != -EOPNOTSUPP) {
             Lock();
             if (mPowerModeState == HWC2_POWER_MODE_ON) {
@@ -980,8 +980,8 @@
     }
 }
 
-ExynosDisplay::ExynosDisplay(uint32_t type, uint32_t index, ExynosDevice *device,
-                             const std::string &displayName)
+ExynosDisplay::ExynosDisplay(uint32_t type, uint32_t index, ExynosDevice* device,
+                             const std::string& displayName)
       : mDisplayId(getDisplayId(type, index)),
         mType(type),
         mIndex(index),
@@ -990,8 +990,9 @@
         mYres(2960),
         mXdpi(25400),
         mYdpi(25400),
-        mVsyncPeriod(16666666),
-        mBtsVsyncPeriod(16666666),
+        mVsyncPeriod(kDefaultVsyncPeriodNanoSecond),
+        mBtsFrameScanoutPeriod(kDefaultVsyncPeriodNanoSecond),
+        mBtsPendingOperationRatePeriod(0),
         mDevice(device),
         mDisplayName(displayName.c_str()),
         mDisplayTraceName(String8::format("%s(%d)", displayName.c_str(), mDisplayId)),
@@ -1403,15 +1404,21 @@
     }
 
     unsigned int incomingPixels = 0;
+    hwc_rect_t dispRect = {INT_MAX, INT_MAX, 0, 0};
     for (size_t i = 0; i < mLayers.size(); i++) {
-        auto w = WIDTH(mLayers[i]->mPreprocessedInfo.displayFrame);
-        auto h = HEIGHT(mLayers[i]->mPreprocessedInfo.displayFrame);
+        auto& r = mLayers[i]->mPreprocessedInfo.displayFrame;
+        if (r.top < dispRect.top) dispRect.top = r.top;
+        if (r.left < dispRect.left) dispRect.left = r.left;
+        if (r.bottom > dispRect.bottom) dispRect.bottom = r.bottom;
+        if (r.right > dispRect.right) dispRect.right = r.right;
+        auto w = WIDTH(r);
+        auto h = HEIGHT(r);
         incomingPixels += w * h;
     }
 
     /* Mode Switch is not required if total pixels are not more than the threshold */
-    unsigned int lcdSize = mXres * mYres;
-    if (incomingPixels <= lcdSize) {
+    unsigned int mergedDisplayFrameSize = WIDTH(dispRect) * HEIGHT(dispRect);
+    if (incomingPixels <= mergedDisplayFrameSize) {
         auto ret = switchDynamicReCompMode(CLIENT_2_DEVICE);
         if (ret) {
             mUpdateCallCnt = 0;
@@ -1719,18 +1726,26 @@
     return NO_ERROR;
 }
 
-bool ExynosDisplay::skipSignalIdle(void) {
+bool ExynosDisplay::shouldSignalNonIdle(void) {
+    // Some cases such that we can skip calling mPowerHalHint.signalNonIdle():
+    // 1. Updating source crop or buffer for video layer
+    // 2. Updating refresh rate indicator layer
+    uint64_t exclude = GEOMETRY_LAYER_SOURCECROP_CHANGED;
+    if ((mGeometryChanged & ~exclude) != 0) {
+        return true;
+    }
     for (size_t i = 0; i < mLayers.size(); i++) {
         // Frame update for refresh rate overlay indicator layer can be ignored
         if (mLayers[i]->mRequestedCompositionType == HWC2_COMPOSITION_REFRESH_RATE_INDICATOR)
             continue;
         // Frame update for video layer can be ignored
         if (mLayers[i]->isLayerFormatYuv()) continue;
-        if (mLayers[i]->mLastLayerBuffer != mLayers[i]->mLayerBuffer) {
-            return false;
+        if (mLayers[i]->mLastLayerBuffer != mLayers[i]->mLayerBuffer ||
+            mLayers[i]->mGeometryChanged != 0) {
+            return true;
         }
     }
-    return true;
+    return false;
 }
 
 /**
@@ -1823,8 +1838,8 @@
             return -EINVAL;
         }
 
-        if ((ret = mExynosCompositionInfo.mM2mMPP->doPostProcessing(mExynosCompositionInfo.mSrcImg,
-                mExynosCompositionInfo.mDstImg)) != NO_ERROR) {
+        if ((ret = mExynosCompositionInfo.mM2mMPP->doPostProcessing(
+                     mExynosCompositionInfo.mDstImg)) != NO_ERROR) {
             DISPLAY_LOGE("exynosComposition doPostProcessing fail ret(%d)", ret);
             return ret;
         }
@@ -3489,6 +3504,8 @@
         return ret;
     }
 
+    tryUpdateBtsFromOperationRate(true);
+
     if (mRenderingState != RENDERING_STATE_ACCEPTED_CHANGE) {
         /*
          * presentDisplay() can be called before validateDisplay()
@@ -3596,11 +3613,10 @@
             if ((mDisplayControl.earlyStartMPP == false) &&
                 (mLayers[i]->mExynosCompositionType == HWC2_COMPOSITION_DEVICE) &&
                 (mLayers[i]->mM2mMPP != NULL)) {
-                ExynosMPP *m2mMpp = mLayers[i]->mM2mMPP;
-                srcImg = mLayers[i]->mSrcImg;
+                ExynosMPP* m2mMpp = mLayers[i]->mM2mMPP;
                 midImg = mLayers[i]->mMidImg;
                 m2mMpp->requestHWStateChange(MPP_HW_STATE_RUNNING);
-                if ((ret = m2mMpp->doPostProcessing(srcImg, midImg)) != NO_ERROR) {
+                if ((ret = m2mMpp->doPostProcessing(midImg)) != NO_ERROR) {
                     HWC_LOGE(this, "%s:: doPostProcessing() failed, layer(%zu), ret(%d)",
                             __func__, i, ret);
                     errString.appendFormat("%s:: doPostProcessing() failed, layer(%zu), ret(%d)\n",
@@ -3625,12 +3641,12 @@
         goto err;
     }
 
-    if (mGeometryChanged != 0 || !skipSignalIdle()) {
-        mPowerHalHint.signalIdle();
+    if (shouldSignalNonIdle()) {
+        mPowerHalHint.signalNonIdle();
     }
 
-    if (needUpdateRRIndicator()) {
-        updateRefreshRateIndicator();
+    if (mRefreshRateIndicatorHandler && needUpdateRRIndicator()) {
+        mRefreshRateIndicatorHandler->checkOnPresentDisplay();
     }
 
     handleWindowUpdate();
@@ -3733,6 +3749,8 @@
 
     mPriorFrameMixedComposition = mixedComposition;
 
+    tryUpdateBtsFromOperationRate(false);
+
     return ret;
 err:
     printDebugInfos(errString);
@@ -4022,7 +4040,7 @@
         ret = mBrightnessController->processDisplayBrightness(brightness, mVsyncPeriod,
                                                               waitPresent);
         if (ret == NO_ERROR) {
-            setMinIdleRefreshRate(0, VrrThrottleRequester::BRIGHTNESS);
+            setMinIdleRefreshRate(0, RrThrottleRequester::BRIGHTNESS);
             if (mOperationRateManager) {
                 mOperationRateManager->onBrightness(mBrightnessController->getBrightnessLevel());
                 handleTargetOperationRate();
@@ -4047,7 +4065,7 @@
         int32_t ret = mBrightnessController->setBrightnessNits(nits, mVsyncPeriod);
 
         if (ret == NO_ERROR) {
-            setMinIdleRefreshRate(0, VrrThrottleRequester::BRIGHTNESS);
+            setMinIdleRefreshRate(0, RrThrottleRequester::BRIGHTNESS);
             if (mOperationRateManager)
                 mOperationRateManager->onBrightness(mBrightnessController->getBrightnessLevel());
         }
@@ -4063,7 +4081,7 @@
         int32_t ret = mBrightnessController->setBrightnessDbv(dbv, mVsyncPeriod);
 
         if (ret == NO_ERROR) {
-            setMinIdleRefreshRate(0, VrrThrottleRequester::BRIGHTNESS);
+            setMinIdleRefreshRate(0, RrThrottleRequester::BRIGHTNESS);
             if (mOperationRateManager) {
                 mOperationRateManager->onBrightness(mBrightnessController->getBrightnessLevel());
             }
@@ -4207,13 +4225,15 @@
     /* mActiveConfig should be changed immediately for internal status */
     mActiveConfig = config;
     mVsyncAppliedTimeLine = *outTimeline;
-    uint32_t vsync_period = getDisplayVsyncPeriodFromConfig(config);
-    updateBtsVsyncPeriod(vsync_period);
+    updateBtsFrameScanoutPeriod(getDisplayFrameScanoutPeriodFromConfig(config));
 
     bool earlyWakeupNeeded = checkRrCompensationEnabled();
     if (earlyWakeupNeeded) {
         setEarlyWakeupDisplay();
     }
+    if (mRefreshRateIndicatorHandler) {
+        mRefreshRateIndicatorHandler->checkOnSetActiveConfig(mDisplayConfigs[config].refreshRate);
+    }
 
     return HWC2_ERROR_NONE;
 }
@@ -4304,25 +4324,88 @@
     if (updateVsync) {
         resetConfigRequestStateLocked(config);
     }
+    if (mRefreshRateIndicatorHandler) {
+        mRefreshRateIndicatorHandler->checkOnSetActiveConfig(mDisplayConfigs[config].refreshRate);
+    }
 
     return NO_ERROR;
 }
 
-void ExynosDisplay::updateBtsVsyncPeriod(uint32_t vsyncPeriod, bool configApplied) {
-    if (configApplied || vsyncPeriod < mBtsVsyncPeriod) {
-        checkBtsReassignResource(vsyncPeriod, mBtsVsyncPeriod);
-        mBtsVsyncPeriod = vsyncPeriod;
+void ExynosDisplay::updateBtsFrameScanoutPeriod(int32_t frameScanoutPeriod, bool configApplied) {
+    if (mBtsFrameScanoutPeriod == frameScanoutPeriod) {
+        return;
+    }
+
+    if (configApplied || frameScanoutPeriod < mBtsFrameScanoutPeriod) {
+        checkBtsReassignResource(frameScanoutPeriod, mBtsFrameScanoutPeriod);
+        mBtsFrameScanoutPeriod = frameScanoutPeriod;
+        ATRACE_INT("btsFrameScanoutPeriod", mBtsFrameScanoutPeriod);
     }
 }
 
+void ExynosDisplay::tryUpdateBtsFromOperationRate(bool beforeValidateDisplay) {
+    if (mOperationRateManager == nullptr || mBrightnessController == nullptr ||
+        mActiveConfig == UINT_MAX) {
+        return;
+    }
+
+    if (!mDisplayConfigs[mActiveConfig].isOperationRateToBts) {
+        return;
+    }
+
+    if (beforeValidateDisplay && mBrightnessController->isOperationRatePending()) {
+        uint32_t opRate = mBrightnessController->getOperationRate();
+        if (opRate) {
+            int32_t operationRatePeriod = nsecsPerSec / opRate;
+            if (operationRatePeriod < mBtsFrameScanoutPeriod) {
+                updateBtsFrameScanoutPeriod(opRate);
+                mBtsPendingOperationRatePeriod = 0;
+            } else if (operationRatePeriod != mBtsFrameScanoutPeriod) {
+                mBtsPendingOperationRatePeriod = operationRatePeriod;
+            }
+        }
+    }
+
+    if (!beforeValidateDisplay && mBtsPendingOperationRatePeriod &&
+        !mBrightnessController->isOperationRatePending()) {
+        /* Do not update during rr transition, it will be updated after setting config done */
+        if (mConfigRequestState != hwc_request_state_t::SET_CONFIG_STATE_REQUESTED) {
+            updateBtsFrameScanoutPeriod(mBtsPendingOperationRatePeriod, true);
+        }
+        mBtsPendingOperationRatePeriod = 0;
+    }
+}
+
+inline int32_t ExynosDisplay::getDisplayFrameScanoutPeriodFromConfig(hwc2_config_t config) {
+    int32_t frameScanoutPeriodNs;
+    std::optional<VrrConfig_t> vrrConfig = getVrrConfigs(config);
+    if (vrrConfig.has_value()) {
+        frameScanoutPeriodNs = vrrConfig->minFrameIntervalNs;
+    } else {
+        getDisplayAttribute(config, HWC2_ATTRIBUTE_VSYNC_PERIOD, &frameScanoutPeriodNs);
+        if (mOperationRateManager && mBrightnessController &&
+            mDisplayConfigs[config].isOperationRateToBts) {
+            uint32_t opRate = mBrightnessController->getOperationRate();
+            if (opRate) {
+                uint32_t opPeriodNs = nsecsPerSec / opRate;
+                frameScanoutPeriodNs =
+                        (frameScanoutPeriodNs <= opPeriodNs) ? frameScanoutPeriodNs : opPeriodNs;
+            }
+        }
+    }
+
+    assert(frameScanoutPeriodNs > 0);
+    return frameScanoutPeriodNs;
+}
+
 uint32_t ExynosDisplay::getBtsRefreshRate() const {
-    return static_cast<uint32_t>(round(nsecsPerSec / mBtsVsyncPeriod * 0.1f) * 10);
+    return static_cast<uint32_t>(round(nsecsPerSec / mBtsFrameScanoutPeriod * 0.1f) * 10);
 }
 
 void ExynosDisplay::updateRefreshRateHint() {
-    if (mVsyncPeriod) {
+    if (mRefreshRate) {
         mPowerHalHint.signalRefreshRate(mPowerModeState.value_or(HWC2_POWER_MODE_OFF),
-                                        mVsyncPeriod);
+                                        mRefreshRate);
     }
 }
 
@@ -4330,8 +4413,11 @@
 int32_t ExynosDisplay::resetConfigRequestStateLocked(hwc2_config_t config) {
     ATRACE_CALL();
 
+    assert(isBadConfig(config) == false);
+
+    mRefreshRate = mDisplayConfigs[config].refreshRate;
     mVsyncPeriod = getDisplayVsyncPeriodFromConfig(config);
-    updateBtsVsyncPeriod(mVsyncPeriod, true);
+    updateBtsFrameScanoutPeriod(getDisplayFrameScanoutPeriodFromConfig(config), true);
     DISPLAY_LOGD(eDebugDisplayConfig, "Update mVsyncPeriod %d by config(%d)", mVsyncPeriod, config);
 
     updateRefreshRateHint();
@@ -4598,15 +4684,14 @@
 
     for (size_t i = 0; i < mLayers.size(); i++) mLayers[i]->setSrcAcquireFence();
 
+    tryUpdateBtsFromOperationRate(true);
     doPreProcessing();
     checkLayerFps();
-    if (exynosHWCControl.useDynamicRecomp == true && mDREnable)
+    if (exynosHWCControl.useDynamicRecomp == true && mDREnable) {
         checkDynamicReCompMode();
-
-    if (exynosHWCControl.useDynamicRecomp == true &&
-        mDevice->isDynamicRecompositionThreadAlive() == false &&
-        mDevice->mDRLoopStatus == false) {
-        mDevice->dynamicRecompositionThreadCreate();
+        if (mDevice->isDynamicRecompositionThreadAlive() == false &&
+            mDevice->mDRLoopStatus == false)
+            mDevice->dynamicRecompositionThreadCreate();
     }
 
     if ((ret = mResourceManager->assignResource(this)) != NO_ERROR) {
@@ -4722,11 +4807,10 @@
             mLayers[i]->setSrcExynosImage(&srcImg);
             mLayers[i]->setDstExynosImage(&dstImg);
             mLayers[i]->setExynosImage(srcImg, dstImg);
-            ExynosMPP *m2mMpp = mLayers[i]->mM2mMPP;
-            srcImg = mLayers[i]->mSrcImg;
+            ExynosMPP* m2mMpp = mLayers[i]->mM2mMPP;
             midImg = mLayers[i]->mMidImg;
             m2mMpp->requestHWStateChange(MPP_HW_STATE_RUNNING);
-            if ((ret = m2mMpp->doPostProcessing(srcImg, midImg)) != NO_ERROR) {
+            if ((ret = m2mMpp->doPostProcessing(midImg)) != NO_ERROR) {
                 DISPLAY_LOGE("%s:: doPostProcessing() failed, layer(%zu), ret(%d)",
                         __func__, i, ret);
                 errString.appendFormat("%s:: doPostProcessing() failed, layer(%zu), ret(%d)\n",
@@ -5805,6 +5889,13 @@
     return HWC2_ERROR_NONE;
 }
 
+std::optional<VrrConfig_t> ExynosDisplay::getVrrConfigs(hwc2_config_t config) {
+    if (isBadConfig(config)) {
+        return std::nullopt;
+    }
+    return mDisplayConfigs[config].vrrConfig;
+}
+
 // Support DDI scalser
 void ExynosDisplay::setDDIScalerEnable(int __unused width, int __unused height) {
 }
@@ -6035,7 +6126,7 @@
 
 int32_t ExynosDisplay::flushDisplayBrightnessChange() {
     if (mBrightnessController) {
-        setMinIdleRefreshRate(0, VrrThrottleRequester::BRIGHTNESS);
+        setMinIdleRefreshRate(0, RrThrottleRequester::BRIGHTNESS);
         if (mOperationRateManager) {
             mOperationRateManager->onBrightness(mBrightnessController->getBrightnessLevel());
             handleTargetOperationRate();
@@ -6174,21 +6265,22 @@
 int ExynosDisplay::lookupDisplayConfigs(const int32_t &width,
                                         const int32_t &height,
                                         const int32_t &fps,
+                                        const int32_t &vsyncRate,
                                         int32_t *outConfig) {
-    if (!fps)
+    if (!fps || !vsyncRate)
         return HWC2_ERROR_BAD_CONFIG;
 
     constexpr auto nsecsPerSec = std::chrono::nanoseconds(1s).count();
     constexpr auto nsecsPerMs = std::chrono::nanoseconds(1ms).count();
 
-    const auto vsyncPeriod = nsecsPerSec / fps;
+    const auto vsyncPeriod = nsecsPerSec / vsyncRate;
 
     for (auto const& [config, mode] : mDisplayConfigs) {
         long delta = abs(vsyncPeriod - mode.vsyncPeriod);
         if ((width == 0 || width == mode.width) && (height == 0 || height == mode.height) &&
-            (delta < nsecsPerMs)) {
-            ALOGD("%s: found display config for mode: %dx%d@%d=%d",
-                 __func__, width, height, fps, config);
+            (delta < nsecsPerMs) && (fps == mode.refreshRate)) {
+            ALOGD("%s: found display config for mode: %dx%d@%d:%d config=%d",
+                 __func__, width, height, fps, vsyncRate, config);
             *outConfig = config;
             return HWC2_ERROR_NONE;
         }
@@ -6281,10 +6373,13 @@
 
     hpdStatus = mDisplayInterface->readHotplugStatus();
 
-    DISPLAY_LOGI("[%s] mDisplayId(%d), mIndex(%d), HPD Status(previous :%d, current : %d)",
-                                       __func__, mDisplayId, mIndex, mHpdStatus, hpdStatus);
+    int hotplugErrorCode = mDisplayInterface->readHotplugErrorCode();
 
-    return (mHpdStatus != hpdStatus);
+    DISPLAY_LOGI("[%s] mDisplayId(%d), mIndex(%d), HPD Status(previous :%d, current : %d), "
+                 "hotplugErrorCode=%d",
+                 __func__, mDisplayId, mIndex, mHpdStatus, hpdStatus, hotplugErrorCode);
+
+    return (mHpdStatus != hpdStatus) || (hotplugErrorCode != 0);
 }
 
 void ExynosDisplay::handleHotplugEvent(bool hpdStatus) {
@@ -6292,19 +6387,21 @@
 }
 
 void ExynosDisplay::hotplug() {
-    mDevice->onHotPlug(mDisplayId, mHpdStatus);
-    ALOGI("HPD callback(%s, mDisplayId %d) was called",
-                            mHpdStatus ? "connection" : "disconnection", mDisplayId);
+    int hotplugErrorCode = mDisplayInterface->readHotplugErrorCode();
+    mDisplayInterface->resetHotplugErrorCode();
+    mDevice->onHotPlug(mDisplayId, mHpdStatus, hotplugErrorCode);
+    ALOGI("HPD callback(%s, mDisplayId %d, hotplugErrorCode=%d) was called",
+          mHpdStatus ? "connection" : "disconnection", mDisplayId, hotplugErrorCode);
 }
 
-ExynosDisplay::RefreshRateIndicatorHandler::RefreshRateIndicatorHandler(ExynosDisplay *display)
+ExynosDisplay::SysfsBasedRRIHandler::SysfsBasedRRIHandler(ExynosDisplay* display)
       : mDisplay(display),
         mLastRefreshRate(0),
         mLastCallbackTime(0),
         mIgnoringLastUpdate(false),
         mCanIgnoreIncreaseUpdate(false) {}
 
-int32_t ExynosDisplay::RefreshRateIndicatorHandler::init() {
+int32_t ExynosDisplay::SysfsBasedRRIHandler::init() {
     auto path = String8::format(kRefreshRateStatePathFormat, mDisplay->mIndex);
     mFd.Set(open(path.c_str(), O_RDONLY));
     if (mFd.get() < 0) {
@@ -6312,11 +6409,21 @@
               strerror(errno));
         return -errno;
     }
-
+    auto ret = mDisplay->mDevice->mDeviceInterface->registerSysfsEventHandler(shared_from_this());
+    if (ret != NO_ERROR) {
+        ALOGE("%s: Failed to register sysfs event handler: %d", __func__, ret);
+        return ret;
+    }
+    // Call the callback immediately
+    handleSysfsEvent();
     return NO_ERROR;
 }
 
-void ExynosDisplay::RefreshRateIndicatorHandler::updateRefreshRateLocked(int refreshRate) {
+int32_t ExynosDisplay::SysfsBasedRRIHandler::disable() {
+    return mDisplay->mDevice->mDeviceInterface->unregisterSysfsEventHandler(getFd());
+}
+
+void ExynosDisplay::SysfsBasedRRIHandler::updateRefreshRateLocked(int refreshRate) {
     ATRACE_CALL();
     ATRACE_INT("Refresh rate indicator event", refreshRate);
     // Ignore refresh rate increase that is caused by refresh rate indicator update but there's
@@ -6338,7 +6445,7 @@
     mCanIgnoreIncreaseUpdate = true;
 }
 
-void ExynosDisplay::RefreshRateIndicatorHandler::handleSysfsEvent() {
+void ExynosDisplay::SysfsBasedRRIHandler::handleSysfsEvent() {
     ATRACE_CALL();
     std::scoped_lock lock(mMutex);
 
@@ -6362,7 +6469,7 @@
     updateRefreshRateLocked(refreshRate);
 }
 
-void ExynosDisplay::RefreshRateIndicatorHandler::updateRefreshRate(int refreshRate) {
+void ExynosDisplay::SysfsBasedRRIHandler::updateRefreshRate(int refreshRate) {
     std::scoped_lock lock(mMutex);
     updateRefreshRateLocked(refreshRate);
 }
@@ -6375,7 +6482,11 @@
     }
     int32_t ret = NO_ERROR;
     if (enabled) {
-        mRefreshRateIndicatorHandler = std::make_shared<RefreshRateIndicatorHandler>(this);
+        if (mType == HWC_DISPLAY_PRIMARY) {
+            mRefreshRateIndicatorHandler = std::make_shared<SysfsBasedRRIHandler>(this);
+        } else {
+            mRefreshRateIndicatorHandler = std::make_shared<ActiveConfigBasedRRIHandler>(this);
+        }
         if (!mRefreshRateIndicatorHandler) {
             ALOGE("%s: Failed to create refresh rate debug handler", __func__);
             return -ENOMEM;
@@ -6386,17 +6497,8 @@
             mRefreshRateIndicatorHandler.reset();
             return ret;
         }
-        ret = mDevice->mDeviceInterface->registerSysfsEventHandler(mRefreshRateIndicatorHandler);
-        if (ret != NO_ERROR) {
-            ALOGE("%s: Failed to register sysfs event handler: %d", __func__, ret);
-            mRefreshRateIndicatorHandler.reset();
-            return ret;
-        }
-        // Call the callback immediately
-        mRefreshRateIndicatorHandler->handleSysfsEvent();
     } else {
-        ret = mDevice->mDeviceInterface->unregisterSysfsEventHandler(
-                mRefreshRateIndicatorHandler->getFd());
+        ret = mRefreshRateIndicatorHandler->disable();
         mRefreshRateIndicatorHandler.reset();
     }
     return ret;
@@ -6414,13 +6516,34 @@
     return time;
 }
 
-void ExynosDisplay::updateRefreshRateIndicator() {
+void ExynosDisplay::SysfsBasedRRIHandler::checkOnPresentDisplay() {
     // Update refresh rate indicator if the last update event is ignored to make sure that
     // the refresh rate caused by the current frame update will be applied immediately since
     // we may not receive the sysfs event if the refresh rate is the same as the last ignored one.
-    if (!mRefreshRateIndicatorHandler || !mRefreshRateIndicatorHandler->isIgnoringLastUpdate())
+    if (!mIgnoringLastUpdate) {
         return;
-    mRefreshRateIndicatorHandler->handleSysfsEvent();
+    }
+    handleSysfsEvent();
+}
+
+ExynosDisplay::ActiveConfigBasedRRIHandler::ActiveConfigBasedRRIHandler(ExynosDisplay* display)
+      : mDisplay(display), mLastRefreshRate(0) {}
+
+int32_t ExynosDisplay::ActiveConfigBasedRRIHandler::init() {
+    updateRefreshRate(mDisplay->mRefreshRate);
+    return NO_ERROR;
+}
+
+void ExynosDisplay::ActiveConfigBasedRRIHandler::updateRefreshRate(int refreshRate) {
+    if (mLastRefreshRate == refreshRate) {
+        return;
+    }
+    mLastRefreshRate = refreshRate;
+    mDisplay->mDevice->onRefreshRateChangedDebug(mDisplay->mDisplayId, s2ns(1) / refreshRate);
+}
+
+void ExynosDisplay::ActiveConfigBasedRRIHandler::checkOnSetActiveConfig(int refreshRate) {
+    updateRefreshRate(refreshRate);
 }
 
 bool ExynosDisplay::needUpdateRRIndicator() {
@@ -6429,21 +6552,20 @@
 }
 
 uint32_t ExynosDisplay::getPeakRefreshRate() {
-    float opRate = mBrightnessController->getOperationRate();
-    return static_cast<uint32_t>(std::round(opRate ?: mPeakRefreshRate));
+    uint32_t opRate = mBrightnessController->getOperationRate();
+    return opRate ?: mPeakRefreshRate;
 }
 
 VsyncPeriodNanos ExynosDisplay::getVsyncPeriod(const int32_t config) {
-    const auto &it = mDisplayConfigs.find(config);
+    const auto& it = mDisplayConfigs.find(config);
     if (it == mDisplayConfigs.end()) return 0;
-    return mDisplayConfigs[config].vsyncPeriod;
+    return it->second.vsyncPeriod;
 }
 
 uint32_t ExynosDisplay::getRefreshRate(const int32_t config) {
-    VsyncPeriodNanos period = getVsyncPeriod(config);
-    if (!period) return 0;
-    constexpr float nsecsPerSec = std::chrono::nanoseconds(1s).count();
-    return round(nsecsPerSec / period * 0.1f) * 10;
+    const auto& it = mDisplayConfigs.find(config);
+    if (it == mDisplayConfigs.end()) return 0;
+    return it->second.refreshRate;
 }
 
 uint32_t ExynosDisplay::getConfigId(const int32_t refreshRate, const int32_t width,
@@ -6494,3 +6616,18 @@
     }
     mClientCompositionInfo.mPrevHasCompositionLayer = mClientCompositionInfo.mHasCompositionLayer;
 }
+
+displaycolor::DisplayType ExynosDisplay::getDcDisplayType() const {
+    switch (mType) {
+        case HWC_DISPLAY_PRIMARY:
+            return mIndex == 0 ? displaycolor::DisplayType::DISPLAY_PRIMARY
+                               : displaycolor::DisplayType::DISPLAY_SECONDARY;
+        case HWC_DISPLAY_EXTERNAL:
+            return displaycolor::DisplayType::DISPLAY_EXTERNAL;
+        case HWC_DISPLAY_VIRTUAL:
+        default:
+            DISPLAY_LOGE("%s: Unsupported display type(%d)", __func__, mType);
+            assert(false);
+            return displaycolor::DisplayType::DISPLAY_PRIMARY;
+    }
+}
diff --git a/libhwc2.1/libdevice/ExynosDisplay.h b/libhwc2.1/libdevice/ExynosDisplay.h
index 51286ad..b329f11 100644
--- a/libhwc2.1/libdevice/ExynosDisplay.h
+++ b/libhwc2.1/libdevice/ExynosDisplay.h
@@ -149,7 +149,7 @@
     SET_CONFIG_STATE_REQUESTED,
 };
 
-enum class VrrThrottleRequester : uint32_t {
+enum class RrThrottleRequester : uint32_t {
     PIXEL_DISP = 0,
     TEST,
     LHBM,
@@ -159,7 +159,7 @@
 
 enum class DispIdleTimerRequester : uint32_t {
     SF = 0,
-    VRR_THROTTLE,
+    RR_THROTTLE,
     MAX,
 };
 
@@ -350,6 +350,28 @@
     int      nPanelType[3];
 };
 
+typedef struct FrameIntervalPowerHint {
+    int frameIntervalNs = 0;
+    int averageRefreshPeriodNs = 0;
+} FrameIntervalPowerHint_t;
+
+typedef struct NotifyExpectedPresentConfig {
+    int HeadsUpNs = 0;
+    int TimeoutNs = 0;
+} NotifyExpectedPresentConfig_t;
+
+typedef struct VrrConfig {
+    int minFrameIntervalNs = 0;
+    std::vector<FrameIntervalPowerHint_t> frameIntervalPowerHint;
+    NotifyExpectedPresentConfig_t notifyExpectedPresentConfig;
+} VrrConfig_t;
+
+typedef struct VrrSettings {
+    bool enabled;
+    NotifyExpectedPresentConfig_t notifyExpectedPresentConfig;
+    std::function<void(int)> configChangeCallback;
+} VrrSettings_t;
+
 typedef struct displayConfigs {
     // HWC2_ATTRIBUTE_VSYNC_PERIOD
     VsyncPeriodNanos vsyncPeriod;
@@ -363,6 +385,12 @@
     uint32_t Ydpi;
     // HWC2_ATTRIBUTE_CONFIG_GROUP
     uint32_t groupId;
+
+    std::optional<VrrConfig_t> vrrConfig;
+
+    /* internal use */
+    bool isOperationRateToBts;
+    int32_t refreshRate;
 } displayConfigs_t;
 
 struct DisplayControl {
@@ -403,10 +431,9 @@
         uint32_t mXdpi;
         uint32_t mYdpi;
         uint32_t mVsyncPeriod;
-        uint32_t mBtsVsyncPeriod;
-
-        int                     mPanelType;
-        int                     mPsrMode;
+        int32_t mRefreshRate;
+        int32_t mBtsFrameScanoutPeriod;
+        int32_t mBtsPendingOperationRatePeriod;
 
         /* Constructor */
         ExynosDisplay(uint32_t type, uint32_t index, ExynosDevice* device,
@@ -1145,6 +1172,15 @@
          */
         int32_t getMountOrientation(HwcMountOrientation *orientation);
 
+        /*
+         * HWC3
+         *
+         * Retrieve the vrrConfig for the corresponding display configuration.
+         * If the configuration doesn't exist, return a nullptr.
+         *
+         */
+        std::optional<VrrConfig_t> getVrrConfigs(hwc2_config_t config);
+
         /* setActiveConfig MISCs */
         bool isBadConfig(hwc2_config_t config);
         bool needNotChangeConfig(hwc2_config_t config);
@@ -1160,10 +1196,11 @@
         int32_t getConfigAppliedTime(const uint64_t desiredTime,
                 const uint64_t actualChangeTime,
                 int64_t &appliedTime, int64_t &refreshTime);
-        void updateBtsVsyncPeriod(uint32_t vsyncPeriod, bool configApplied = false);
+        void updateBtsFrameScanoutPeriod(int32_t frameScanoutPeriod, bool configApplied = false);
+        void tryUpdateBtsFromOperationRate(bool beforeValidateDisplay);
         uint32_t getBtsRefreshRate() const;
-        virtual void checkBtsReassignResource(const uint32_t __unused vsyncPeriod,
-                                              const uint32_t __unused btsVsyncPeriod) {}
+        virtual void checkBtsReassignResource(const int32_t __unused vsyncPeriod,
+                                              const int32_t __unused btsVsyncPeriod) {}
 
         /* TODO : TBD */
         int32_t setCursorPositionAsync(uint32_t x_pos, uint32_t y_pos);
@@ -1241,8 +1278,10 @@
         virtual int32_t setLhbmState(bool __unused enabled) { return NO_ERROR; }
         virtual bool getLhbmState() { return false; };
         virtual void setEarlyWakeupDisplay() {}
-        virtual void setExpectedPresentTime(uint64_t __unused timestamp) {}
+        virtual void setExpectedPresentTime(uint64_t __unused timestamp,
+                                            int __unused frameIntervalNs) {}
         virtual uint64_t getPendingExpectedPresentTime() { return 0; }
+        virtual int getPendingFrameInterval() { return 0; }
         virtual void applyExpectedPresentTime() {}
         virtual int32_t getDisplayIdleTimerSupport(bool& outSupport);
         virtual int32_t getDisplayMultiThreadedPresentSupport(bool& outSupport);
@@ -1277,6 +1316,17 @@
         /* set brightness by dbv value */
         virtual int32_t setBrightnessDbv(const uint32_t dbv);
 
+        virtual std::string getPanelSysfsPath() const { return std::string(); }
+
+        virtual void onVsync(int64_t __unused timestamp) { return; };
+
+        displaycolor::DisplayType getDcDisplayType() const;
+
+        virtual int32_t notifyExpectedPresent(int64_t __unused timestamp,
+                                              int32_t __unused frameIntervalNs) {
+            return HWC2_ERROR_UNSUPPORTED;
+        };
+
     protected:
         virtual bool getHDRException(ExynosLayer *layer);
         virtual int32_t getActiveConfigInternal(hwc2_config_t* outConfig);
@@ -1295,11 +1345,11 @@
         void requestLhbm(bool on);
 
         virtual int setMinIdleRefreshRate(const int __unused fps,
-                                          const VrrThrottleRequester __unused requester) {
+                                          const RrThrottleRequester __unused requester) {
             return NO_ERROR;
         }
         virtual int setRefreshRateThrottleNanos(const int64_t __unused delayNanos,
-                                                const VrrThrottleRequester __unused requester) {
+                                                const RrThrottleRequester __unused requester) {
             return NO_ERROR;
         }
 
@@ -1324,12 +1374,13 @@
         int lookupDisplayConfigs(const int32_t& width,
                                  const int32_t& height,
                                  const int32_t& fps,
+                                 const int32_t& vsyncRate,
                                  int32_t* outConfig);
 
     private:
         bool skipStaticLayerChanged(ExynosCompositionInfo& compositionInfo);
 
-        bool skipSignalIdle();
+        bool shouldSignalNonIdle();
 
         /// minimum possible dim rate in the case hbm peak is 1000 nits and norml
         // display brightness is 2 nits
@@ -1354,8 +1405,8 @@
             virtual ~PowerHalHintWorker();
             int Init();
 
-            void signalRefreshRate(hwc2_power_mode_t powerMode, uint32_t vsyncPeriod);
-            void signalIdle();
+            void signalRefreshRate(hwc2_power_mode_t powerMode, int32_t refreshRate);
+            void signalNonIdle();
             void signalActualWorkDuration(nsecs_t actualDurationNanos);
             void signalTargetWorkDuration(nsecs_t targetDurationNanos);
 
@@ -1380,14 +1431,14 @@
             int32_t checkPowerHalExtHintSupport(const std::string& mode);
             int32_t sendPowerHalExtHint(const std::string& mode, bool enabled);
 
-            int32_t checkRefreshRateHintSupport(int refreshRate);
-            int32_t updateRefreshRateHintInternal(hwc2_power_mode_t powerMode,
-                                                  uint32_t vsyncPeriod);
-            int32_t sendRefreshRateHint(int refreshRate, bool enabled);
+            int32_t checkRefreshRateHintSupport(const int32_t refreshRate);
+            int32_t updateRefreshRateHintInternal(const hwc2_power_mode_t powerMode,
+                                                  const int32_t refreshRate);
+            int32_t sendRefreshRateHint(const int32_t refreshRate, bool enabled);
             void forceUpdateHints();
 
             int32_t checkIdleHintSupport();
-            int32_t updateIdleHint(int64_t deadlineTime, bool forceUpdate);
+            int32_t updateIdleHint(const int64_t deadlineTime, const bool forceUpdate);
             bool needUpdateIdleHintLocked(int64_t& timeout) REQUIRES(mutex_);
 
             // for adpf cpu hints
@@ -1411,7 +1462,7 @@
             int mLastRefreshRateHint;
 
             // support list of refresh rate hints
-            std::map<int, bool> mRefreshRateHintSupportMap;
+            std::map<int32_t, bool> mRefreshRateHintSupportMap;
 
             bool mIdleHintIsEnabled;
             bool mForceUpdateIdleHint;
@@ -1428,7 +1479,7 @@
             std::string mRefreshRateHintPrefixStr;
 
             hwc2_power_mode_t mPowerModeState;
-            uint32_t mVsyncPeriod;
+            int32_t mRefreshRate;
 
             uint32_t mConnectRetryCount;
             bool isPowerHalExist() { return mConnectRetryCount < 10; }
@@ -1548,7 +1599,7 @@
             assert(vsync_period > 0);
             return static_cast<uint32_t>(vsync_period);
         }
-
+        inline int32_t getDisplayFrameScanoutPeriodFromConfig(hwc2_config_t config);
         virtual void calculateTimeline(
                 hwc2_config_t config,
                 hwc_vsync_period_change_constraints_t* vsyncPeriodChangeConstraints,
@@ -1604,7 +1655,7 @@
             int32_t mLastFileIndex;
             FILE* mFile;
         };
-        RotatingLogFileWriter mErrLogFileWriter;
+        mutable RotatingLogFileWriter mErrLogFileWriter;
         RotatingLogFileWriter mDebugDumpFileWriter;
         RotatingLogFileWriter mFenceFileWriter;
 
@@ -1619,7 +1670,7 @@
             virtual int32_t onConfig(hwc2_config_t __unused cfg) { return 0; }
             virtual int32_t onBrightness(uint32_t __unused dbv) { return 0; }
             virtual int32_t onPowerMode(int32_t __unused mode) { return 0; }
-            virtual int32_t getTargetOperationRate() { return 0; }
+            virtual int32_t getTargetOperationRate() const { return 0; }
         };
 
     public:
@@ -1634,14 +1685,30 @@
         virtual void handleHotplugEvent(bool hpdStatus);
         virtual void hotplug();
 
-        class RefreshRateIndicatorHandler : public DrmSysfsEventHandler {
+        class RefreshRateIndicator {
         public:
-            RefreshRateIndicatorHandler(ExynosDisplay* display);
-            int32_t init();
-            virtual void handleSysfsEvent() override;
-            virtual int getFd() override { return mFd.get(); };
-            bool isIgnoringLastUpdate() { return mIgnoringLastUpdate; }
-            void updateRefreshRate(int refreshRate);
+            virtual ~RefreshRateIndicator() = default;
+            virtual int32_t init() { return NO_ERROR; }
+            virtual int32_t disable() { return NO_ERROR; }
+            virtual void updateRefreshRate(int __unused refreshRate) {}
+            virtual void checkOnPresentDisplay() {}
+            virtual void checkOnSetActiveConfig(int __unused refreshRate) {}
+        };
+
+        class SysfsBasedRRIHandler : public RefreshRateIndicator,
+                                     public DrmSysfsEventHandler,
+                                     public std::enable_shared_from_this<SysfsBasedRRIHandler> {
+        public:
+            SysfsBasedRRIHandler(ExynosDisplay* display);
+            virtual ~SysfsBasedRRIHandler() = default;
+
+            int32_t init() override;
+            int32_t disable() override;
+            void updateRefreshRate(int refreshRate) override;
+            void checkOnPresentDisplay() override;
+
+            void handleSysfsEvent() override;
+            int getFd() override { return mFd.get(); }
 
         private:
             void updateRefreshRateLocked(int refreshRate) REQUIRES(mMutex);
@@ -1658,9 +1725,24 @@
                     "/sys/class/backlight/panel%d-backlight/state";
         };
 
-        std::shared_ptr<RefreshRateIndicatorHandler> mRefreshRateIndicatorHandler;
+        class ActiveConfigBasedRRIHandler : public RefreshRateIndicator {
+        public:
+            ActiveConfigBasedRRIHandler(ExynosDisplay* display);
+            virtual ~ActiveConfigBasedRRIHandler() = default;
+
+            int32_t init() override;
+            void updateRefreshRate(int refreshRate) override;
+            void checkOnSetActiveConfig(int refreshRate) override;
+
+        private:
+            void updateVsyncPeriod(int vsyncPeriod);
+
+            ExynosDisplay* mDisplay;
+            int mLastRefreshRate;
+        };
+
+        std::shared_ptr<RefreshRateIndicator> mRefreshRateIndicatorHandler;
         int32_t setRefreshRateChangedCallbackDebugEnabled(bool enabled);
-        void updateRefreshRateIndicator();
         nsecs_t getLastLayerUpdateTime();
         bool needUpdateRRIndicator();
         virtual void checkPreblendingRequirement(){};
diff --git a/libhwc2.1/libdevice/ExynosLayer.cpp b/libhwc2.1/libdevice/ExynosLayer.cpp
index 5d5a614..14aaf5f 100644
--- a/libhwc2.1/libdevice/ExynosLayer.cpp
+++ b/libhwc2.1/libdevice/ExynosLayer.cpp
@@ -606,7 +606,6 @@
 }
 
 int32_t ExynosLayer::setLayerSourceCrop(hwc_frect_t crop) {
-
     if ((crop.left != mSourceCrop.left) ||
         (crop.top != mSourceCrop.top) ||
         (crop.right != mSourceCrop.right) ||
@@ -1029,6 +1028,7 @@
 {
     int format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
     int32_t fd, fd1, fd2;
+    uint64_t unique_id;
     if (mLayerBuffer != NULL)
     {
         VendorGraphicBufferMeta gmeta(mLayerBuffer);
@@ -1036,11 +1036,13 @@
         fd = gmeta.fd;
         fd1 = gmeta.fd1;
         fd2 = gmeta.fd2;
+        unique_id = gmeta.unique_id;
     } else {
         format = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
         fd = -1;
         fd1 = -1;
         fd2 = -1;
+        unique_id = 0;
     }
 
     {
@@ -1083,6 +1085,7 @@
                           .add("exynosType", mExynosCompositionType)
                           .add("validateType", mValidateCompositionType)
                           .add("overlayInfo", mOverlayInfo, true)
+                          .add("GrallocBufferId", unique_id)
                           .build()
                           .c_str());
 
diff --git a/libhwc2.1/libdevice/HistogramDevice.cpp b/libhwc2.1/libdevice/HistogramDevice.cpp
index f31961f..2ac3507 100644
--- a/libhwc2.1/libdevice/HistogramDevice.cpp
+++ b/libhwc2.1/libdevice/HistogramDevice.cpp
@@ -35,10 +35,10 @@
  *
  * @cookie pointer to the TokenInfo of the binder object.
  */
-static void histogramOnBinderDied(void *cookie) {
+static void histogramOnBinderDied(void* cookie) {
     ATRACE_CALL();
     HistogramDevice::HistogramErrorCode histogramErrorCode;
-    HistogramDevice::TokenInfo *tokenInfo = (HistogramDevice::TokenInfo *)cookie;
+    HistogramDevice::TokenInfo* tokenInfo = (HistogramDevice::TokenInfo*)cookie;
     ALOGI("%s: histogram channel #%u: client with token(%p) is died", __func__,
           tokenInfo->channelId, tokenInfo->token.get());
 
@@ -56,23 +56,25 @@
       : status(ChannelStatus_t::DISABLED),
         token(nullptr),
         pid(-1),
-        requestedRoi(),
+        requestedRoi(DISABLED_ROI),
+        requestedBlockingRoi(DISABLED_ROI),
         workingConfig(),
         threshold(0),
         histDataCollecting(false) {}
 
-HistogramDevice::ChannelInfo::ChannelInfo(const ChannelInfo &other) {
+HistogramDevice::ChannelInfo::ChannelInfo(const ChannelInfo& other) {
     std::scoped_lock lock(other.channelInfoMutex);
     status = other.status;
     token = other.token;
     pid = other.pid;
     requestedRoi = other.requestedRoi;
+    requestedBlockingRoi = other.requestedBlockingRoi;
     workingConfig = other.workingConfig;
     threshold = other.threshold;
     histDataCollecting = other.histDataCollecting;
 }
 
-HistogramDevice::HistogramDevice(ExynosDisplay *display, uint8_t channelCount,
+HistogramDevice::HistogramDevice(ExynosDisplay* display, uint8_t channelCount,
                                  std::vector<uint8_t> reservedChannels) {
     mDisplay = display;
 
@@ -89,15 +91,20 @@
     }
 }
 
-void HistogramDevice::initDrm(const DrmCrtc &crtc) {
+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);
+
+    /* print the histogram capability */
+    String8 logString;
+    dumpHistogramCapability(logString);
+    ALOGI("%s", logString.c_str());
 }
 
 ndk::ScopedAStatus HistogramDevice::getHistogramCapability(
-        HistogramCapability *histogramCapability) const {
+        HistogramCapability* histogramCapability) const {
     ATRACE_CALL();
 
     if (!histogramCapability) {
@@ -110,9 +117,9 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus HistogramDevice::registerHistogram(const ndk::SpAIBinder &token,
-                                                      const HistogramConfig &histogramConfig,
-                                                      HistogramErrorCode *histogramErrorCode) {
+ndk::ScopedAStatus HistogramDevice::registerHistogram(const ndk::SpAIBinder& token,
+                                                      const HistogramConfig& histogramConfig,
+                                                      HistogramErrorCode* histogramErrorCode) {
     ATRACE_CALL();
 
     if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
@@ -123,9 +130,9 @@
     return configHistogram(token, histogramConfig, histogramErrorCode, false);
 }
 
-ndk::ScopedAStatus HistogramDevice::queryHistogram(const ndk::SpAIBinder &token,
-                                                   std::vector<char16_t> *histogramBuffer,
-                                                   HistogramErrorCode *histogramErrorCode) {
+ndk::ScopedAStatus HistogramDevice::queryHistogram(const ndk::SpAIBinder& token,
+                                                   std::vector<char16_t>* histogramBuffer,
+                                                   HistogramErrorCode* histogramErrorCode) {
     ATRACE_CALL();
 
     if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
@@ -188,9 +195,9 @@
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus HistogramDevice::reconfigHistogram(const ndk::SpAIBinder &token,
-                                                      const HistogramConfig &histogramConfig,
-                                                      HistogramErrorCode *histogramErrorCode) {
+ndk::ScopedAStatus HistogramDevice::reconfigHistogram(const ndk::SpAIBinder& token,
+                                                      const HistogramConfig& histogramConfig,
+                                                      HistogramErrorCode* histogramErrorCode) {
     ATRACE_CALL();
 
     if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
@@ -201,8 +208,8 @@
     return configHistogram(token, histogramConfig, histogramErrorCode, true);
 }
 
-ndk::ScopedAStatus HistogramDevice::unregisterHistogram(const ndk::SpAIBinder &token,
-                                                        HistogramErrorCode *histogramErrorCode) {
+ndk::ScopedAStatus HistogramDevice::unregisterHistogram(const ndk::SpAIBinder& token,
+                                                        HistogramErrorCode* histogramErrorCode) {
     ATRACE_CALL();
 
     if (UNLIKELY(!mHistogramCapability.supportMultiChannel)) {
@@ -257,10 +264,10 @@
     return ndk::ScopedAStatus::ok();
 }
 
-void HistogramDevice::handleDrmEvent(void *event) {
+void HistogramDevice::handleDrmEvent(void* event) {
     int ret = NO_ERROR;
     uint8_t channelId;
-    char16_t *buffer;
+    char16_t* buffer;
 
     if ((ret = parseDrmEvent(event, channelId, buffer))) {
         ALOGE("%s: failed to parseDrmEvent, ret %d", __func__, ret);
@@ -273,7 +280,7 @@
         return;
     }
 
-    ChannelInfo &channel = mChannels[channelId];
+    ChannelInfo& channel = mChannels[channelId];
     std::unique_lock<std::mutex> lock(channel.histDataCollectingMutex);
 
     /* Check if the histogram channel is collecting the histogram data */
@@ -287,12 +294,28 @@
     channel.histDataCollecting_cv.notify_all();
 }
 
-void HistogramDevice::prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq) {
+void HistogramDevice::prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq& drmReq) {
     ATRACE_NAME("HistogramAtomicCommit");
 
+    ExynosDisplayDrmInterface* moduleDisplayInterface =
+            static_cast<ExynosDisplayDrmInterface*>(mDisplay->mDisplayInterface.get());
+    if (!moduleDisplayInterface) {
+        HWC_LOGE(mDisplay, "%s: failed to get ExynosDisplayDrmInterface (nullptr)", __func__);
+        return;
+    }
+
+    /* Get the current active region and check if the resolution is changed. */
+    int32_t currDisplayActiveH = moduleDisplayInterface->getActiveModeHDisplay();
+    int32_t currDisplayActiveV = moduleDisplayInterface->getActiveModeVDisplay();
+    bool isResolutionChanged =
+            (mDisplayActiveH != currDisplayActiveH) || (mDisplayActiveV != currDisplayActiveV);
+    mDisplayActiveH = currDisplayActiveH;
+    mDisplayActiveV = currDisplayActiveV;
+
     /* Loop through every channel and call prepareChannelCommit */
     for (uint8_t channelId = 0; channelId < mChannels.size(); ++channelId) {
-        int channelRet = prepareChannelCommit(drmReq, channelId);
+        int channelRet = prepareChannelCommit(drmReq, channelId, moduleDisplayInterface,
+                                              isResolutionChanged);
 
         /* Every channel is independent, no early return when the channel commit fails. */
         if (channelRet) {
@@ -305,7 +328,7 @@
 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];
+        ChannelInfo& channel = mChannels[channelId];
         std::scoped_lock lock(channel.channelInfoMutex);
 
         switch (channel.status) {
@@ -321,7 +344,7 @@
     }
 }
 
-void HistogramDevice::dump(String8 &result) const {
+void HistogramDevice::dump(String8& result) const {
     /* Do not dump the Histogram Device if it is not supported. */
     if (!mHistogramCapability.supportMultiChannel) {
         return;
@@ -337,7 +360,7 @@
     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];
+        const ChannelInfo& channel = mChannels[channelId];
         std::scoped_lock lock(channel.channelInfoMutex);
         tb.add("ID", (int)channelId);
         tb.add("status", toString(channel.status));
@@ -345,6 +368,9 @@
         tb.add("pid", channel.pid);
         tb.add("requestedRoi", toString(channel.requestedRoi));
         tb.add("workingRoi", toString(channel.workingConfig.roi));
+        tb.add("requestedBlockRoi", toString(channel.requestedBlockingRoi));
+        tb.add("workingBlockRoi",
+               toString(channel.workingConfig.blockingRoi.value_or(DISABLED_ROI)));
         tb.add("threshold", channel.threshold);
         tb.add("weightRGB", toString(channel.workingConfig.weights));
         tb.add("samplePos",
@@ -357,7 +383,7 @@
 }
 
 void HistogramDevice::initChannels(uint8_t channelCount,
-                                   const std::vector<uint8_t> &reservedChannels) {
+                                   const std::vector<uint8_t>& reservedChannels) {
     mChannels.resize(channelCount);
     ALOGI("%s: init histogram with %d channels", __func__, channelCount);
 
@@ -382,8 +408,8 @@
 }
 
 void HistogramDevice::initHistogramCapability(bool supportMultiChannel) {
-    ExynosDisplayDrmInterface *moduleDisplayInterface =
-            static_cast<ExynosDisplayDrmInterface *>(mDisplay->mDisplayInterface.get());
+    ExynosDisplayDrmInterface* moduleDisplayInterface =
+            static_cast<ExynosDisplayDrmInterface*>(mDisplay->mDisplayInterface.get());
 
     if (!moduleDisplayInterface) {
         ALOGE("%s: failed to get ExynosDisplayDrmInterface (nullptr), cannot get panel full "
@@ -397,23 +423,18 @@
         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);
+    mHistogramCapability.supportBlockingRoi = false;
+
+    initPlatformHistogramCapability();
 }
 
-ndk::ScopedAStatus HistogramDevice::configHistogram(const ndk::SpAIBinder &token,
-                                                    const HistogramConfig &histogramConfig,
-                                                    HistogramErrorCode *histogramErrorCode,
+ndk::ScopedAStatus HistogramDevice::configHistogram(const ndk::SpAIBinder& token,
+                                                    const HistogramConfig& histogramConfig,
+                                                    HistogramErrorCode* histogramErrorCode,
                                                     bool isReconfig) {
     /* validate the argument (histogramErrorCode) */
     if (!histogramErrorCode) {
@@ -437,9 +458,6 @@
         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;
@@ -465,7 +483,7 @@
 
         /* store the histogram information, and mark the channel ready for atomic commit by setting
          * the status to CONFIG_PENDING */
-        fillupChannelInfo(channelId, token, histogramConfig, threshold);
+        fillupChannelInfo(channelId, token, histogramConfig);
 
         if (!isReconfig) {
             /* link the binder object (token) to the death recipient. When the binder object is
@@ -489,12 +507,12 @@
     return ndk::ScopedAStatus::ok();
 }
 
-void HistogramDevice::getHistogramData(uint8_t channelId, std::vector<char16_t> *histogramBuffer,
-                                       HistogramErrorCode *histogramErrorCode) {
+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());
+    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",
@@ -502,7 +520,7 @@
         return;
     }
 
-    ChannelInfo &channel = mChannels[channelId];
+    ChannelInfo& channel = mChannels[channelId];
 
     std::unique_lock<std::mutex> lock(channel.histDataCollectingMutex);
 
@@ -578,14 +596,14 @@
     histogramBuffer->assign(channel.histData, channel.histData + HISTOGRAM_BIN_COUNT);
 }
 
-int HistogramDevice::parseDrmEvent(void *event, uint8_t &channelId, char16_t *&buffer) const {
+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) {
+        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__);
@@ -613,7 +631,7 @@
 }
 
 HistogramDevice::HistogramErrorCode HistogramDevice::getChannelIdByTokenLocked(
-        const ndk::SpAIBinder &token, uint8_t &channelId) {
+        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;
@@ -626,72 +644,64 @@
 
 void HistogramDevice::cleanupChannelInfo(uint8_t channelId) {
     ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
-    ChannelInfo &channel = mChannels[channelId];
+    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,
+    channel.requestedRoi = DISABLED_ROI;
+    channel.requestedBlockingRoi = DISABLED_ROI;
+    channel.workingConfig = {.roi = DISABLED_ROI,
                              .weights.weightR = 0,
                              .weights.weightG = 0,
                              .weights.weightB = 0,
-                             .samplePos = HistogramSamplePos::POST_POSTPROC};
+                             .samplePos = HistogramSamplePos::POST_POSTPROC,
+                             .blockingRoi = DISABLED_ROI};
     channel.threshold = 0;
 }
 
-void HistogramDevice::fillupChannelInfo(uint8_t channelId, const ndk::SpAIBinder &token,
-                                        const HistogramConfig &histogramConfig, int threshold) {
+void HistogramDevice::fillupChannelInfo(uint8_t channelId, const ndk::SpAIBinder& token,
+                                        const HistogramConfig& histogramConfig) {
     ATRACE_NAME(String8::format("%s #%u", __func__, channelId).c_str());
-    ChannelInfo &channel = mChannels[channelId];
+    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.requestedBlockingRoi = histogramConfig.blockingRoi.value_or(DISABLED_ROI);
     channel.workingConfig = histogramConfig;
-    channel.workingConfig.roi = {};
-    channel.threshold = threshold;
+    channel.workingConfig.roi = DISABLED_ROI;
+    channel.workingConfig.blockingRoi = DISABLED_ROI;
 }
 
-int HistogramDevice::prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq,
-                                          uint8_t channelId) {
+int HistogramDevice::prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq& drmReq,
+                                          uint8_t channelId,
+                                          ExynosDisplayDrmInterface* moduleDisplayInterface,
+                                          bool isResolutionChanged) {
     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];
+    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) {
+        if (mDisplayActiveH == 0 || mDisplayActiveV == 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__, mDisplayActiveH, mDisplayActiveV);
             /* 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
+        /* If the channel status is CONFIG_COMMITTED, 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)) {
+            if (LIKELY(isResolutionChanged == false)) {
                 return NO_ERROR;
             } else {
                 ALOGI("%s: histogram channel #%u: detect resolution changed, update roi setting",
@@ -699,8 +709,30 @@
             }
         }
 
-        /* Adopt the roi calculated from convertRoiLocked */
-        channel.workingConfig.roi = workingRoi;
+        HistogramRoiRect convertedRoi;
+
+        /* calculate the roi based on the current active resolution */
+        ret = convertRoiLocked(moduleDisplayInterface, channel.requestedRoi, convertedRoi);
+        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;
+        }
+        channel.workingConfig.roi = convertedRoi;
+
+        /* calculate the blocking roi based on the current active resolution */
+        ret = convertRoiLocked(moduleDisplayInterface, channel.requestedBlockingRoi, convertedRoi);
+        if (ret) {
+            ALOGE("%s: histogram channel #%u: failed to convert to workingBlockRoi, ret: %d",
+                  __func__, channelId, ret);
+            channel.status = ChannelStatus_t::CONFIG_ERROR;
+            return ret;
+        }
+        channel.workingConfig.blockingRoi = convertedRoi;
+
+        /* threshold is calculated based on the roi coordinates rather than configured by client */
+        channel.threshold = calculateThreshold(channel.workingConfig.roi);
 
         /* Create histogram drm config struct (platform dependent) */
         std::shared_ptr<void> blobData;
@@ -738,9 +770,9 @@
     return ret;
 }
 
-int HistogramDevice::createHistogramDrmConfigLocked(const ChannelInfo &channel,
-                                                    std::shared_ptr<void> &configPtr,
-                                                    size_t &length) const {
+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;
@@ -748,44 +780,40 @@
     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;
+int HistogramDevice::convertRoiLocked(ExynosDisplayDrmInterface* moduleDisplayInterface,
+                                      const HistogramRoiRect& requestedRoi,
+                                      HistogramRoiRect& convertedRoi) const {
+    const int32_t& panelH = mHistogramCapability.fullResolutionWidth;
+    const int32_t& panelV = mHistogramCapability.fullResolutionHeight;
 
-    ALOGV("%s: active: (%dx%d), panel: (%dx%d)", __func__, activeH, activeV, panelH, panelV);
+    ALOGV("%s: active: (%dx%d), panel: (%dx%d)", __func__, mDisplayActiveH, mDisplayActiveV, 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);
+    if (panelH < mDisplayActiveH || mDisplayActiveH < 0 || panelV < mDisplayActiveV ||
+        mDisplayActiveV < 0) {
+        ALOGE("%s: failed to convert roi, active: (%dx%d), panel: (%dx%d)", __func__,
+              mDisplayActiveH, mDisplayActiveV, 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;
+    convertedRoi.left = requestedRoi.left * mDisplayActiveH / panelH;
+    convertedRoi.top = requestedRoi.top * mDisplayActiveV / panelV;
+    convertedRoi.right = requestedRoi.right * mDisplayActiveH / panelH;
+    convertedRoi.bottom = requestedRoi.bottom * mDisplayActiveV / panelV;
 
-    ALOGV("%s: working roi: %s", __func__, toString(workingRoi).c_str());
+    ALOGV("%s: working roi: %s", __func__, toString(convertedRoi).c_str());
 
     return NO_ERROR;
 }
 
-void HistogramDevice::dumpHistogramCapability(String8 &result) const {
+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("\tsupportBlockingRoi: %s\n",
+                        mHistogramCapability.supportBlockingRoi ? "true" : "false");
     result.appendFormat("\tchannelCount: %d\n", mHistogramCapability.channelCount);
     result.appendFormat("\tfullscreen roi: (0,0)x(%dx%d)\n",
                         mHistogramCapability.fullResolutionWidth,
@@ -800,12 +828,14 @@
 }
 
 HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramConfig(
-        const HistogramConfig &histogramConfig) const {
+        const HistogramConfig& histogramConfig) const {
     HistogramErrorCode ret;
 
-    if ((ret = validateHistogramRoi(histogramConfig.roi)) != HistogramErrorCode::NONE ||
+    if ((ret = validateHistogramRoi(histogramConfig.roi, "")) != HistogramErrorCode::NONE ||
         (ret = validateHistogramWeights(histogramConfig.weights)) != HistogramErrorCode::NONE ||
-        (ret = validateHistogramSamplePos(histogramConfig.samplePos)) != HistogramErrorCode::NONE) {
+        (ret = validateHistogramSamplePos(histogramConfig.samplePos)) != HistogramErrorCode::NONE ||
+        (ret = validateHistogramBlockingRoi(histogramConfig.blockingRoi)) !=
+                HistogramErrorCode::NONE) {
         return ret;
     }
 
@@ -813,11 +843,13 @@
 }
 
 HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramRoi(
-        const HistogramRoiRect &roi) const {
+        const HistogramRoiRect& roi, const char* roiType) const {
+    if (roi == DISABLED_ROI) return HistogramErrorCode::NONE;
+
     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__,
+        ALOGE("%s: BAD_ROI, %sroi: %s, full screen roi: (0,0)x(%dx%d)", __func__, roiType,
               toString(roi).c_str(), mHistogramCapability.fullResolutionWidth,
               mHistogramCapability.fullResolutionHeight);
         return HistogramErrorCode::BAD_ROI;
@@ -827,7 +859,7 @@
 }
 
 HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramWeights(
-        const HistogramWeights &weights) const {
+        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);
@@ -838,7 +870,7 @@
 }
 
 HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramSamplePos(
-        const HistogramSamplePos &samplePos) const {
+        const HistogramSamplePos& samplePos) const {
     for (HistogramSamplePos mSamplePos : mHistogramCapability.supportSamplePosList) {
         if (samplePos == mSamplePos) {
             return HistogramErrorCode::NONE;
@@ -849,13 +881,32 @@
     return HistogramErrorCode::BAD_POSITION;
 }
 
-int HistogramDevice::calculateThreshold(const HistogramRoiRect &roi) {
+HistogramDevice::HistogramErrorCode HistogramDevice::validateHistogramBlockingRoi(
+        const std::optional<HistogramRoiRect>& blockingRoi) const {
+    /* If the platform doesn't support blockingRoi, client should not enable blockingRoi  */
+    if (mHistogramCapability.supportBlockingRoi == false) {
+        if (blockingRoi.has_value() && blockingRoi.value() != DISABLED_ROI) {
+            ALOGE("%s: BAD_ROI, platform doesn't support blockingRoi, requested: %s", __func__,
+                  toString(blockingRoi.value()).c_str());
+            return HistogramErrorCode::BAD_ROI;
+        }
+        return HistogramErrorCode::NONE;
+    }
+
+    /* For the platform that supports blockingRoi, use the same validate rule as roi */
+    return validateHistogramRoi(blockingRoi.value_or(DISABLED_ROI), "blocking ");
+}
+
+int HistogramDevice::calculateThreshold(const HistogramRoiRect& roi) const {
+    /* If roi is disabled, the targeted region is entire screen. */
+    int32_t roiH = (roi != DISABLED_ROI) ? (roi.right - roi.left) : mDisplayActiveH;
+    int32_t roiV = (roi != DISABLED_ROI) ? (roi.bottom - roi.top) : mDisplayActiveV;
+    int threshold = (roiV * roiH) >> 16;
     // 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) {
+std::string HistogramDevice::toString(const ChannelStatus_t& status) {
     switch (status) {
         case ChannelStatus_t::RESERVED:
             return "RESERVED";
@@ -880,7 +931,9 @@
     return "UNDEFINED";
 }
 
-std::string HistogramDevice::toString(const HistogramRoiRect &roi) {
+std::string HistogramDevice::toString(const HistogramRoiRect& roi) {
+    if (roi == DISABLED_ROI) return "OFF";
+
     std::ostringstream os;
     os << "(" << roi.left << "," << roi.top << ")";
     os << "x";
@@ -888,7 +941,7 @@
     return os.str();
 }
 
-std::string HistogramDevice::toString(const HistogramWeights &weights) {
+std::string HistogramDevice::toString(const HistogramWeights& weights) {
     std::ostringstream os;
     os << "(";
     os << (int)weights.weightR << ",";
diff --git a/libhwc2.1/libdevice/HistogramDevice.h b/libhwc2.1/libdevice/HistogramDevice.h
index ee74cae..ce56e47 100644
--- a/libhwc2.1/libdevice/HistogramDevice.h
+++ b/libhwc2.1/libdevice/HistogramDevice.h
@@ -47,6 +47,9 @@
     using HistogramWeights = aidl::com::google::hardware::pixel::display::Weight;
     using HistogramChannelIoctl_t = ExynosDisplayDrmInterface::HistogramChannelIoctl_t;
 
+    /* For blocking roi and roi, (0, 0, 0, 0) means disabled */
+    static constexpr HistogramRoiRect DISABLED_ROI = {0, 0, 0, 0};
+
     /* Histogram weight constraint: weightR + weightG + weightB = WEIGHT_SUM */
     static constexpr size_t WEIGHT_SUM = 1024;
 
@@ -99,6 +102,9 @@
         /* requested roi from the client by registerHistogram or reconfigHistogram */
         HistogramRoiRect requestedRoi GUARDED_BY(channelInfoMutex);
 
+        /* requested blocking roi from the client by registerHistogram or reconfigHistogram */
+        HistogramRoiRect requestedBlockingRoi 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);
@@ -113,7 +119,7 @@
         std::condition_variable histDataCollecting_cv;
 
         ChannelInfo();
-        ChannelInfo(const ChannelInfo &other);
+        ChannelInfo(const ChannelInfo& other);
     };
 
     /* TokenInfo is not only used to stored the corresponding channel id but also passed to the
@@ -124,7 +130,7 @@
 
         /* pointer to the HistogramDevice, binderdied callback would use this pointer to cleanup the
          * channel in HistogramDevice by the member function unregisterHistogram */
-        HistogramDevice *histogramDevice;
+        HistogramDevice* histogramDevice;
 
         /* binderdied callback would call unregisterHistogram with this token */
         ndk::SpAIBinder token;
@@ -139,7 +145,7 @@
      * @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,
+    explicit HistogramDevice(ExynosDisplay* display, uint8_t channelCount,
                              std::vector<uint8_t> reservedChannels);
 
     /**
@@ -158,7 +164,7 @@
      *
      * @crtc drm crtc object which would contain histogram related information.
      */
-    void initDrm(const DrmCrtc &crtc);
+    void initDrm(const DrmCrtc& crtc);
 
     /**
      * getHistogramCapability
@@ -168,7 +174,7 @@
      * @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;
+    ndk::ScopedAStatus getHistogramCapability(HistogramCapability* histogramCapability) const;
 
     /**
      * registerHistogram
@@ -186,9 +192,9 @@
      * @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);
+    ndk::ScopedAStatus registerHistogram(const ndk::SpAIBinder& token,
+                                         const HistogramConfig& histogramConfig,
+                                         HistogramErrorCode* histogramErrorCode);
 
     /**
      * queryHistogram
@@ -204,9 +210,9 @@
      * @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);
+    ndk::ScopedAStatus queryHistogram(const ndk::SpAIBinder& token,
+                                      std::vector<char16_t>* histogramBuffer,
+                                      HistogramErrorCode* histogramErrorCode);
 
     /**
      * reconfigHistogram
@@ -221,9 +227,9 @@
      * @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);
+    ndk::ScopedAStatus reconfigHistogram(const ndk::SpAIBinder& token,
+                                         const HistogramConfig& histogramConfig,
+                                         HistogramErrorCode* histogramErrorCode);
 
     /**
      * unregisterHistogram
@@ -237,8 +243,8 @@
      * @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);
+    ndk::ScopedAStatus unregisterHistogram(const ndk::SpAIBinder& token,
+                                           HistogramErrorCode* histogramErrorCode);
 
     /**
      * handleDrmEvent
@@ -248,7 +254,7 @@
      *
      * @event histogram channel drm event pointer (struct exynos_drm_histogram_channel_event *)
      */
-    void handleDrmEvent(void *event);
+    void handleDrmEvent(void* event);
 
     /**
      * prepareAtomicCommit
@@ -257,7 +263,7 @@
      *
      * @drmReq drm atomic request object
      */
-    void prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq);
+    void prepareAtomicCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq& drmReq);
 
     /**
      * postAtomicCommit
@@ -276,7 +282,7 @@
      *
      * @result histogram channel dump information would be appended to this string
      */
-    void dump(String8 &result) const;
+    void dump(String8& result) const;
 
 protected:
     HistogramCapability mHistogramCapability;
@@ -284,12 +290,14 @@
 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::unordered_map<AIBinder*, TokenInfo> mTokenInfoMap GUARDED_BY(mAllocatorMutex);
     std::vector<ChannelInfo> mChannels;
-    ExynosDisplay *mDisplay = nullptr;
+    int32_t mDisplayActiveH = 0;
+    int32_t mDisplayActiveV = 0;
+    ExynosDisplay* mDisplay = nullptr;
 
     /* Death recipient for the binderdied callback, would be deleted in the destructor */
-    AIBinder_DeathRecipient *mDeathRecipient = nullptr;
+    AIBinder_DeathRecipient* mDeathRecipient = nullptr;
 
     /**
      * initChannels
@@ -299,7 +307,7 @@
      * @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);
+    void initChannels(uint8_t channelCount, const std::vector<uint8_t>& reservedChannels);
 
     /**
      * initHistogramCapability
@@ -312,11 +320,11 @@
     void initHistogramCapability(bool supportMultiChannel);
 
     /**
-     * initSupportSamplePosList
+     * initPlatformHistogramCapability
      *
-     * Initialize the supported sample position list.
+     * Initialize platform specific histogram capability.
      */
-    virtual void initSupportSamplePosList();
+    virtual void initPlatformHistogramCapability() {}
 
     /**
      * configHistogram
@@ -329,9 +337,9 @@
      * @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);
+    ndk::ScopedAStatus configHistogram(const ndk::SpAIBinder& token,
+                                       const HistogramConfig& histogramConfig,
+                                       HistogramErrorCode* histogramErrorCode, bool isReconfig);
 
     /**
      * getHistogramData
@@ -344,8 +352,8 @@
      * @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);
+    void getHistogramData(uint8_t channelId, std::vector<char16_t>* histogramBuffer,
+                          HistogramErrorCode* histogramErrorCode);
 
     /**
      * parseDrmEvent
@@ -359,7 +367,7 @@
      * @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;
+    virtual int parseDrmEvent(void* event, uint8_t& channelId, char16_t*& buffer) const;
 
     /**
      * acquireChannelLocked
@@ -371,7 +379,7 @@
      * @channelId store the acquired channel id.
      * @return HistogramErrorCode::NONE when success, or else otherwise.
      */
-    HistogramErrorCode acquireChannelLocked(const ndk::SpAIBinder &token, uint8_t &channelId)
+    HistogramErrorCode acquireChannelLocked(const ndk::SpAIBinder& token, uint8_t& channelId)
             REQUIRES(mAllocatorMutex);
 
     /**
@@ -392,7 +400,7 @@
      * @token binder object created by the client.
      * @return HistogramErrorCode::NONE when success, or else otherwise.
      */
-    HistogramErrorCode getChannelIdByTokenLocked(const ndk::SpAIBinder &token, uint8_t &channelId)
+    HistogramErrorCode getChannelIdByTokenLocked(const ndk::SpAIBinder& token, uint8_t& channelId)
             REQUIRES(mAllocatorMutex);
 
     /**
@@ -415,10 +423,9 @@
      * @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);
+    void fillupChannelInfo(uint8_t channelId, const ndk::SpAIBinder& token,
+                           const HistogramConfig& histogramConfig);
 
     /**
      * prepareChannelCommit
@@ -437,10 +444,13 @@
      *
      * @drmReq drm atomic request object
      * @channelId histogram channel id
+     * @moduleDisplayInterface display drm interface pointer
+     * @isResolutionChanged true if the resolution change is detected, false otherwise.
      * @return NO_ERROR on success, else otherwise
      */
-    int prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq &drmReq,
-                             uint8_t channelId);
+    int prepareChannelCommit(ExynosDisplayDrmInterface::DrmModeAtomicReq& drmReq, uint8_t channelId,
+                             ExynosDisplayDrmInterface* moduleDisplayInterface,
+                             bool isResolutionChanged);
 
     /**
      * createHistogramDrmConfigLocked
@@ -455,9 +465,9 @@
      * @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
+    virtual int createHistogramDrmConfigLocked(const ChannelInfo& channel,
+                                               std::shared_ptr<void>& configPtr,
+                                               size_t& length) const
             REQUIRES(channel.channelInfoMutex);
 
     /**
@@ -471,18 +481,21 @@
      * @workingRoi converted roi from the requested roi
      * @return NO_ERROR on success, else otherwise
      */
-    int convertRoiLocked(ExynosDisplayDrmInterface *moduleDisplayInterface,
-                         const HistogramRoiRect &requestedRoi, HistogramRoiRect &workingRoi) const;
+    int convertRoiLocked(ExynosDisplayDrmInterface* moduleDisplayInterface,
+                         const HistogramRoiRect& requestedRoi, HistogramRoiRect& workingRoi) const;
 
-    void dumpHistogramCapability(String8 &result) 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;
+    HistogramErrorCode validateHistogramConfig(const HistogramConfig& histogramConfig) const;
+    HistogramErrorCode validateHistogramRoi(const HistogramRoiRect& roi, const char* roiType) const;
+    HistogramErrorCode validateHistogramWeights(const HistogramWeights& weights) const;
+    HistogramErrorCode validateHistogramSamplePos(const HistogramSamplePos& samplePos) const;
+    HistogramErrorCode validateHistogramBlockingRoi(
+            const std::optional<HistogramRoiRect>& blockingRoi) 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);
+    int calculateThreshold(const HistogramRoiRect& roi) const;
+
+    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/ExynosDisplayDrmInterface.cpp b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
index e8a4001..badaa61 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.cpp
@@ -40,7 +40,6 @@
 constexpr uint32_t MAX_PLANE_NUM = 3;
 constexpr uint32_t CBCR_INDEX = 1;
 constexpr float DISPLAY_LUMINANCE_UNIT = 10000;
-constexpr auto nsecsPerSec = std::chrono::nanoseconds(1s).count();
 constexpr auto vsyncPeriodTag = "VsyncPeriod";
 
 typedef struct _drmModeAtomicReqItem drmModeAtomicReqItem, *drmModeAtomicReqItemPtr;
@@ -62,6 +61,19 @@
 extern struct exynos_hwc_control exynosHWCControl;
 static const int32_t kUmPerInch = 25400;
 
+int writeIntToKernelFile(const char* path, const int value) {
+    std::ofstream ofs(path);
+
+    if (!ofs.is_open()) {
+        ALOGW("%s(): unable to open %s (%s)", __func__, path, strerror(errno));
+        return -1;
+    }
+
+    ofs << value << std::endl;
+
+    return 0;
+}
+
 FramebufferManager::~FramebufferManager()
 {
     {
@@ -449,6 +461,16 @@
 }
 
 int32_t ExynosDisplayDrmInterface::getDisplayIdleTimerSupport(bool &outSupport) {
+    if (isFullVrrSupported()) {
+        outSupport = false;
+        return NO_ERROR;
+    } else if (isPseudoVrrSupported()) {
+        // Retuen true to avoid SF idle timer working. We insert frames manually
+        // for pseudo VRR, so ideally panel idle should be disabled in the driver.
+        outSupport = true;
+        return NO_ERROR;
+    }
+
     auto [ret, support] = mDrmConnector->panel_idle_support().value();
     if (ret) {
         ALOGI("no panel_idle_support drm property or invalid value (%d)", ret);
@@ -628,7 +650,7 @@
 
     for (auto &e : orientationEnums) {
         uint64_t enumValue;
-        std::tie(enumValue, err) = orientation.GetEnumValueWithName(e.second);
+        std::tie(enumValue, err) = orientation.getEnumValueWithName(e.second);
         if (!err && enumValue == drmOrientation) {
             mExynosDisplay->mMountOrientation = e.first;
             return;
@@ -723,7 +745,7 @@
         auto &plane = mDrmDevice->planes().at(i);
         uint32_t plane_id = plane->id();
 
-        if (!plane->zpos_property().is_immutable()) {
+        if (!plane->zpos_property().isImmutable()) {
             /* Plane can be used for composition */
             ExynosMPP *exynosMPP =
                 mExynosDisplay->mResourceManager->getOtfMPPWithChannel(i);
@@ -760,10 +782,10 @@
         parseRangeEnums(plane->range_property());
     }
 
-    chosePreferredConfig();
+    choosePreferredConfig();
 
-    // After chosePreferredConfig, the mDrmConnector->modes array is initialized, get the panel full
-    // resolution information here.
+    // After choosePreferredConfig, the mDrmConnector->modes array is initialized, get the panel
+    // full resolution information here.
     if (mExynosDisplay->mType == HWC_DISPLAY_PRIMARY) {
         retrievePanelFullResolution();
     }
@@ -841,6 +863,8 @@
         mExynosDisplay->mLastVsyncTimestamp = timestamp;
     }
 
+    mExynosDisplay->onVsync(timestamp);
+
     ExynosDevice *exynosDevice = mExynosDisplay->mDevice;
 
     if (exynosDevice->onVsync_2_4(mExynosDisplay->mDisplayId, timestamp,
@@ -902,12 +926,14 @@
     mExynosDisplay->mXres = mDozeDrmMode.h_display();
     mExynosDisplay->mYres = mDozeDrmMode.v_display();
     // in nanoseconds
-    mExynosDisplay->mVsyncPeriod = nsecsPerSec / mDozeDrmMode.v_refresh();
+    mExynosDisplay->mVsyncPeriod = static_cast<uint32_t>(mDozeDrmMode.te_period());
     // Dots per 1000 inches
     mExynosDisplay->mXdpi = mm_width ? (mDozeDrmMode.h_display() * kUmPerInch) / mm_width : -1;
     // Dots per 1000 inches
     mExynosDisplay->mYdpi = mm_height ? (mDozeDrmMode.v_display() * kUmPerInch) / mm_height : -1;
 
+    mExynosDisplay->mRefreshRate = static_cast<int32_t>(mDozeDrmMode.v_refresh());
+
     return setActiveDrmMode(mDozeDrmMode);
 }
 
@@ -949,8 +975,7 @@
     return NO_ERROR;
 }
 
-int32_t ExynosDisplayDrmInterface::chosePreferredConfig()
-{
+int32_t ExynosDisplayDrmInterface::choosePreferredConfig() {
     uint32_t num_configs = 0;
     int32_t err = getDisplayConfigs(&num_configs, NULL);
     if (err != HWC2_ERROR_NONE || !num_configs)
@@ -959,9 +984,10 @@
     int32_t config = -1;
     char modeStr[PROPERTY_VALUE_MAX] = "\0";
     int32_t width = 0, height = 0, fps = 0;
+    // only legacy products use this property, kernel preferred mode will be used going forward
     if (property_get("vendor.display.preferred_mode", modeStr, "") > 0 &&
         sscanf(modeStr, "%dx%d@%d", &width, &height, &fps) == 3) {
-        err = mExynosDisplay->lookupDisplayConfigs(width, height, fps, &config);
+        err = mExynosDisplay->lookupDisplayConfigs(width, height, fps, fps, &config);
     } else {
         err = HWC2_ERROR_BAD_CONFIG;
     }
@@ -997,19 +1023,25 @@
         uint32_t* outNumConfigs,
         hwc2_config_t* outConfigs)
 {
+    if (!mExynosDisplay || !(mExynosDisplay->mDevice)) {
+        return HWC2_ERROR_BAD_DISPLAY;
+    }
+
     std::lock_guard<std::recursive_mutex> lock(mDrmConnector->modesLock());
 
     if (!outConfigs) {
-        int ret = mDrmConnector->UpdateModes();
+        bool useVrrConfigs = isFullVrrSupported();
+        int ret = mDrmConnector->UpdateModes(useVrrConfigs);
         if (ret < 0) {
             ALOGE("Failed to update display modes %d", ret);
             return HWC2_ERROR_BAD_DISPLAY;
         }
-
         if (ret == 0) {
             // no need to update mExynosDisplay->mDisplayConfigs
             goto no_mode_changes;
         }
+        ALOGI("Select Vrr Config for display %s: %s", mExynosDisplay->mDisplayName.c_str(),
+              useVrrConfigs ? "full" : (isPseudoVrrSupported() ? "pseudo" : "non-Vrr"));
 
         if (mDrmConnector->state() == DRM_MODE_CONNECTED) {
             /*
@@ -1030,28 +1062,23 @@
 
         uint32_t mm_width = mDrmConnector->mm_width();
         uint32_t mm_height = mDrmConnector->mm_height();
+        ALOGD("%s: mm_width(%u) mm_height(%u)",
+              mExynosDisplay->mDisplayName.c_str(), mm_width, mm_height);
 
-        /* key: (width<<32 | height) */
-        std::map<uint64_t, uint32_t> groupIds;
-        uint32_t groupId = 0;
+        DisplayConfigGroupIdGenerator groupIdGenerator;
         float peakRr = -1;
-
         for (const DrmMode &mode : mDrmConnector->modes()) {
             displayConfigs_t configs;
             float rr = mode.v_refresh();
-            configs.vsyncPeriod = nsecsPerSec / rr;
+            configs.refreshRate = static_cast<int32_t>(rr);
+            configs.vsyncPeriod = static_cast<int32_t>(mode.te_period());
+            if (configs.vsyncPeriod <= 0.0f) {
+                ALOGE("%s:: invalid vsync period", __func__);
+                return HWC2_ERROR_BAD_DISPLAY;
+            }
+            configs.isOperationRateToBts = mode.is_operation_rate_to_bts();
             configs.width = mode.h_display();
             configs.height = mode.v_display();
-            uint64_t key = ((uint64_t)configs.width<<32) | configs.height;
-            auto it = groupIds.find(key);
-            if (it != groupIds.end()) {
-                configs.groupId = it->second;
-            } else {
-                configs.groupId = groupId;
-                groupIds.insert(std::make_pair(key, groupId));
-                groupId++;
-            }
-
             // Dots per 1000 inches
             configs.Xdpi = mm_width ? (mode.h_display() * kUmPerInch) / mm_width : -1;
             // Dots per 1000 inches
@@ -1059,10 +1086,29 @@
             // find peak rr
             if (rr > peakRr)
                   peakRr = rr;
+            // Configure VRR if it's turned on.
+            if (mode.is_vrr_mode()) {
+                VrrConfig_t vrrConfig;
+                vrrConfig.minFrameIntervalNs = static_cast<int>(std::nano::den / rr);
+                // TODO(b/290843234): FrameIntervalPowerHint is currently optional and omitted.
+                // Supply initial values for notifyExpectedPresentConfig; potential changes may come
+                // later.
+                vrrConfig.notifyExpectedPresentConfig.HeadsUpNs = mNotifyExpectedPresentHeadsUpNs;
+                vrrConfig.notifyExpectedPresentConfig.TimeoutNs = mNotifyExpectedPresentTimeoutNs;
+                configs.vrrConfig = std::make_optional(vrrConfig);
+                configs.groupId = groupIdGenerator.getGroupId(configs.width, configs.height,
+                                                              vrrConfig.minFrameIntervalNs,
+                                                              configs.vsyncPeriod);
+            } else {
+                configs.groupId = groupIdGenerator.getGroupId(configs.width, configs.height);
+            }
             mExynosDisplay->mDisplayConfigs.insert(std::make_pair(mode.id(), configs));
-            ALOGD("config group(%d), w(%d), h(%d), vsync(%d), xdpi(%d), ydpi(%d)",
-                    configs.groupId, configs.width, configs.height,
-                    configs.vsyncPeriod, configs.Xdpi, configs.Ydpi);
+            ALOGD("%s: config group(%d), w(%d), h(%d), rr(%f), TE(%d), xdpi(%d), ydpi(%d), "
+                  "vrr mode(%s), NS mode(%s)",
+                  mExynosDisplay->mDisplayName.c_str(),
+                  configs.groupId, configs.width, configs.height, rr, configs.vsyncPeriod,
+                  configs.Xdpi, configs.Ydpi, mode.is_vrr_mode() ? "true" : "false",
+                  mode.is_ns_mode() ? "true" : "false");
         }
         mExynosDisplay->setPeakRefreshRate(peakRr);
     }
@@ -1093,13 +1139,16 @@
     uint32_t num_modes = static_cast<uint32_t>(mDrmConnector->modes().size());
     for (uint32_t i = 0; i < num_modes; i++) {
         auto mode = mDrmConnector->modes().at(i);
-        ALOGD("%s display config[%d] %s:: id(%d), clock(%d), flags(%d), type(%d)",
-                mExynosDisplay->mDisplayName.c_str(), i, mode.name().c_str(), mode.id(), mode.clock(), mode.flags(), mode.type());
+        ALOGD("%s: config[%d] %s: id(%d), clock(%d), flags(0x%x), type(0x%x)",
+              mExynosDisplay->mDisplayName.c_str(), i, mode.name().c_str(), mode.id(),
+              mode.clock(), mode.flags(), mode.type());
         ALOGD("\th_display(%d), h_sync_start(%d), h_sync_end(%d), h_total(%d), h_skew(%d)",
-                mode.h_display(), mode.h_sync_start(), mode.h_sync_end(), mode.h_total(), mode.h_skew());
-        ALOGD("\tv_display(%d), v_sync_start(%d), v_sync_end(%d), v_total(%d), v_scan(%d), v_refresh(%f)",
-                mode.v_display(), mode.v_sync_start(), mode.v_sync_end(), mode.v_total(), mode.v_scan(), mode.v_refresh());
-
+              mode.h_display(), mode.h_sync_start(), mode.h_sync_end(), mode.h_total(),
+              mode.h_skew());
+        ALOGD("\tv_display(%d), v_sync_start(%d), v_sync_end(%d), v_total(%d), v_scan(%d), "
+              "v_refresh(%f)",
+              mode.v_display(), mode.v_sync_start(), mode.v_sync_end(), mode.v_total(),
+              mode.v_scan(), mode.v_refresh());
     }
 }
 
@@ -1110,7 +1159,7 @@
 
 int32_t ExynosDisplayDrmInterface::getConfigChangeDuration()
 {
-    const auto [ret, duration] = mDrmConnector->vrr_switch_duration().value();
+    const auto [ret, duration] = mDrmConnector->rr_switch_duration().value();
 
     if (!ret && duration > 0) {
         return duration;
@@ -1248,7 +1297,7 @@
     } 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());
+        mVsyncCallback.setDesiredVsyncPeriod(mActiveModeState.mode.te_period());
         mDrmVSyncWorker.VSyncControl(true);
         return HWC2_ERROR_NONE;
     }
@@ -1279,7 +1328,8 @@
         }
     } else {
         if (!isResSwitch) {
-            ret = setDisplayMode(drmReq, modeBlob ? modeBlob : mDesiredModeState.blob_id);
+            ret = setDisplayMode(drmReq, modeBlob ? modeBlob : mDesiredModeState.blob_id,
+                                 modeBlob ? mode->id() : mDesiredModeState.mode.id());
             if (ret < 0) {
                 HWC_LOGE(mExynosDisplay, "%s: Fail to apply display mode", __func__);
                 return ret;
@@ -1325,7 +1375,7 @@
         reconfig = true;
     }
 
-    if ((ret = setDisplayMode(drmReq, modeBlob)) != NO_ERROR) {
+    if ((ret = setDisplayMode(drmReq, modeBlob, mode.id())) != NO_ERROR) {
         drmReq.addOldBlob(modeBlob);
         HWC_LOGE(mExynosDisplay, "%s: Fail to apply display mode",
                 __func__);
@@ -1394,9 +1444,9 @@
     return NO_ERROR;
 }
 
-int32_t ExynosDisplayDrmInterface::setDisplayMode(
-        DrmModeAtomicReq &drmReq, const uint32_t modeBlob)
-{
+int32_t ExynosDisplayDrmInterface::setDisplayMode(DrmModeAtomicReq& drmReq,
+                                                  const uint32_t& modeBlob,
+                                                  const uint32_t& modeId) {
     int ret = NO_ERROR;
 
     if ((ret = drmReq.atomicAddProperty(mDrmCrtc->id(),
@@ -1411,6 +1461,10 @@
             mDrmConnector->crtc_id_property(), mDrmCrtc->id())) < 0)
         return ret;
 
+    if (mConfigChangeCallback) {
+        drmReq.setAckCallback(std::bind(mConfigChangeCallback, modeId));
+    }
+
     return NO_ERROR;
 }
 
@@ -1427,6 +1481,10 @@
     mExynosDisplay->mMaxAverageLuminance = 0;
     mExynosDisplay->mMinLuminance = 0;
 
+    if (mExynosDisplay->mType == HWC_DISPLAY_EXTERNAL) {
+        int upd_res = mDrmConnector->UpdateLuminanceAndHdrProperties();
+        if (!upd_res) ALOGW("%s: UpdateLuminanceAndHdrProperties failed (%d)", __func__, upd_res);
+    }
     const DrmProperty &prop_max_luminance = mDrmConnector->max_luminance();
     const DrmProperty &prop_max_avg_luminance = mDrmConnector->max_avg_luminance();
     const DrmProperty &prop_min_luminance = mDrmConnector->min_luminance();
@@ -1442,9 +1500,11 @@
         (prop_max_avg_luminance.id() == 0) ||
         (prop_min_luminance.id() == 0) ||
         (prop_hdr_formats.id() == 0)) {
-        ALOGE("%s:: there is no property for hdrCapabilities (max_luminance: %d, max_avg_luminance: %d, min_luminance: %d, hdr_formats: %d",
-                __func__, prop_max_luminance.id(), prop_max_avg_luminance.id(),
-                prop_min_luminance.id(), prop_hdr_formats.id());
+        HWC_LOGE(mExynosDisplay,
+                 "%s:: there is no property for hdrCapabilities (max_luminance: %d, "
+                 "max_avg_luminance: %d, min_luminance: %d, hdr_formats: %d",
+                 __func__, prop_max_luminance.id(), prop_max_avg_luminance.id(),
+                 prop_min_luminance.id(), prop_hdr_formats.id());
         return -1;
     }
 
@@ -1480,13 +1540,13 @@
     }
 
     uint32_t typeBit;
-    std::tie(typeBit, ret) = prop_hdr_formats.GetEnumValueWithName("Dolby Vision");
+    std::tie(typeBit, ret) = prop_hdr_formats.getEnumValueWithName("Dolby Vision");
     if ((ret == 0) && (hdr_formats & (1 << typeBit))) {
         mExynosDisplay->mHdrTypes.push_back(HAL_HDR_DOLBY_VISION);
         HDEBUGLOGD(eDebugHWC, "%s: supported hdr types : %d",
                 mExynosDisplay->mDisplayName.c_str(), HAL_HDR_DOLBY_VISION);
     }
-    std::tie(typeBit, ret) = prop_hdr_formats.GetEnumValueWithName("HDR10");
+    std::tie(typeBit, ret) = prop_hdr_formats.getEnumValueWithName("HDR10");
     if ((ret == 0) && (hdr_formats & (1 << typeBit))) {
         mExynosDisplay->mHdrTypes.push_back(HAL_HDR_HDR10);
         if (mExynosDisplay->mDevice->mResourceManager->hasHDR10PlusMPP()) {
@@ -1495,7 +1555,7 @@
         HDEBUGLOGD(eDebugHWC, "%s: supported hdr types : %d",
                 mExynosDisplay->mDisplayName.c_str(), HAL_HDR_HDR10);
     }
-    std::tie(typeBit, ret) = prop_hdr_formats.GetEnumValueWithName("HLG");
+    std::tie(typeBit, ret) = prop_hdr_formats.getEnumValueWithName("HLG");
     if ((ret == 0) && (hdr_formats & (1 << typeBit))) {
         mExynosDisplay->mHdrTypes.push_back(HAL_HDR_HLG);
         HDEBUGLOGD(eDebugHWC, "%s: supported hdr types : %d",
@@ -1591,12 +1651,11 @@
                     plane->blend_property(), drmEnum, true)) < 0)
         return ret;
 
-    if (plane->zpos_property().id() &&
-        !plane->zpos_property().is_immutable()) {
+    if (plane->zpos_property().id() && !plane->zpos_property().isImmutable()) {
         uint64_t min_zpos = 0;
 
         // Ignore ret and use min_zpos as 0 by default
-        std::tie(std::ignore, min_zpos) = plane->zpos_property().range_min();
+        std::tie(std::ignore, min_zpos) = plane->zpos_property().rangeMin();
 
         if ((ret = drmReq.atomicAddProperty(plane->id(),
                 plane->zpos_property(), configIndex + min_zpos)) < 0)
@@ -1606,8 +1665,8 @@
     if (plane->alpha_property().id()) {
         uint64_t min_alpha = 0;
         uint64_t max_alpha = 0;
-        std::tie(std::ignore, min_alpha) = plane->alpha_property().range_min();
-        std::tie(std::ignore, max_alpha) = plane->alpha_property().range_max();
+        std::tie(std::ignore, min_alpha) = plane->alpha_property().rangeMin();
+        std::tie(std::ignore, max_alpha) = plane->alpha_property().rangeMax();
         if ((ret = drmReq.atomicAddProperty(plane->id(),
                 plane->alpha_property(),
                 (uint64_t)(((max_alpha - min_alpha) * config.plane_alpha) + 0.5) + min_alpha, true)) < 0)
@@ -1855,7 +1914,8 @@
                 1 << mMipiSyncEnums[toUnderlying(HalMipiSyncType::HAL_MIPI_CMD_SYNC_REFRESH_RATE)];
         }
 
-        if ((ret = setDisplayMode(drmReq, mDesiredModeState.blob_id)) < 0) {
+        if ((ret = setDisplayMode(drmReq, mDesiredModeState.blob_id, mDesiredModeState.mode.id())) <
+            0) {
             HWC_LOGE(mExynosDisplay, "%s: Fail to apply display mode",
                     __func__);
             return ret;
@@ -2091,8 +2151,7 @@
             mDrmConnector->ResetLpMode();
             getLowPowerDrmModeModeInfo();
         }
-        mVsyncCallback.setDesiredVsyncPeriod(
-                nsecsPerSec/mActiveModeState.mode.v_refresh());
+        mVsyncCallback.setDesiredVsyncPeriod(mActiveModeState.mode.te_period());
         /* Enable vsync to check vsync period */
         mDrmVSyncWorker.VSyncControl(true);
     }
@@ -2139,6 +2198,15 @@
     return ret;
 }
 
+void ExynosDisplayDrmInterface::setVrrSettings(const VrrSettings_t& vrrSettings) {
+    if (vrrSettings.enabled) {
+        mIsVrrModeSupported = true;
+        mNotifyExpectedPresentHeadsUpNs = vrrSettings.notifyExpectedPresentConfig.HeadsUpNs;
+        mNotifyExpectedPresentTimeoutNs = vrrSettings.notifyExpectedPresentConfig.TimeoutNs;
+        mConfigChangeCallback = vrrSettings.configChangeCallback;
+    }
+}
+
 int32_t ExynosDisplayDrmInterface::clearDisplayPlanes(DrmModeAtomicReq &drmReq)
 {
     int ret = NO_ERROR;
@@ -2292,7 +2360,7 @@
         return -EINVAL;
     }
 
-    if (property.id()) {
+    if (property.id() && property.validateChange(value)) {
         int ret = drmModeAtomicAddProperty(mPset, id,
                 property.id(), value);
         if (ret < 0) {
@@ -2401,13 +2469,46 @@
         ALOGV("skip atomic commit error handling as kernel is in TUI");
         ret = NO_ERROR;
     } else if (ret < 0) {
+        if (ret == -EINVAL) {
+            dumpDrmAtomicCommitMessage(ret);
+        }
         HWC_LOGE(mDrmDisplayInterface->mExynosDisplay, "commit error: %d", ret);
         setError(ret);
     }
 
+    if (ret == 0 && mAckCallback) {
+        if (!(flags & DRM_MODE_ATOMIC_TEST_ONLY)) {
+            mAckCallback();
+        }
+    }
+
     return ret;
 }
 
+void ExynosDisplayDrmInterface::DrmModeAtomicReq::dumpDrmAtomicCommitMessage(int err) {
+    const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    const nsecs_t diffMs = ns2ms(now - mDrmDisplayInterface->mLastDumpDrmAtomicMessageTime);
+    if (diffMs < kAllowDumpDrmAtomicMessageTimeMs) {
+        return;
+    }
+
+    if (writeIntToKernelFile(kDrmModuleParametersDebugNode, kEnableDrmAtomicMessage)) {
+        return;
+    }
+
+    HWC_LOGE(mDrmDisplayInterface->mExynosDisplay,
+             "commit error, enable atomic message and test again");
+    int ret = drmModeAtomicCommit(mDrmDisplayInterface->mDrmDevice->fd(), mPset,
+                                  DRM_MODE_ATOMIC_TEST_ONLY, mDrmDisplayInterface->mDrmDevice);
+    if (ret != err) {
+        HWC_LOGE(mDrmDisplayInterface->mExynosDisplay,
+                 "re-try commit error(%d) is different from %d", ret, err);
+    }
+
+    writeIntToKernelFile(kDrmModuleParametersDebugNode, kDisableDrmDebugMessage);
+    mDrmDisplayInterface->mLastDumpDrmAtomicMessageTime = systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
 int32_t ExynosDisplayDrmInterface::getReadbackBufferAttributes(
         int32_t* /*android_pixel_format_t*/ outFormat,
         int32_t* /*android_dataspace_t*/ outDataspace)
@@ -2559,9 +2660,9 @@
 
 int32_t ExynosDisplayDrmInterface::getDisplayFakeEdid(uint8_t &outPort, uint32_t &outDataSize,
                                                       uint8_t *outData) {
-    int width = mExynosDisplay->mXres;
-    int height = mExynosDisplay->mYres;
-    int clock = (width) * (height) * 60 / 10000;
+    uint32_t width = mExynosDisplay->mXres;
+    uint32_t height = mExynosDisplay->mYres;
+    uint32_t clock = (width * height * kDefaultRefreshRateFrequency) / 10000;
     std::array<uint8_t, 128> edid_buf{
             0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */
             0x1C, 0xEC,                                     /* manufacturer GGL */
@@ -2771,3 +2872,20 @@
     ALOGE("%s: kernel doesn't support multi channel histogram ioctl", __func__);
     return INVALID_OPERATION;
 }
+
+static constexpr auto kDpHotplugErrorCodeSysfsPath =
+        "/sys/devices/platform/110f0000.drmdp/drm-displayport/dp_hotplug_error_code";
+
+int ExynosDisplayDrmInterface::readHotplugErrorCode() {
+    if (mExynosDisplay->mType != HWC_DISPLAY_EXTERNAL) return 0;
+    int hotplug_error_code = 0;
+    std::ifstream ifs(kDpHotplugErrorCodeSysfsPath);
+    if (ifs.is_open()) ifs >> hotplug_error_code;
+    return hotplug_error_code;
+}
+
+void ExynosDisplayDrmInterface::resetHotplugErrorCode() {
+    if (mExynosDisplay->mType != HWC_DISPLAY_EXTERNAL) return;
+    std::ofstream ofs(kDpHotplugErrorCodeSysfsPath);
+    if (ofs.is_open()) ofs << "0";
+}
diff --git a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
index d47e45a..0d28aa7 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayDrmInterface.h
@@ -51,6 +51,28 @@
 template <typename T>
 using DrmArray = std::array<T, HWC_DRM_BO_MAX_PLANES>;
 
+class DisplayConfigGroupIdGenerator {
+public:
+    DisplayConfigGroupIdGenerator() = default;
+    ~DisplayConfigGroupIdGenerator() = default;
+
+    // Vrr will utilize the last two parameters. In the case of non-vrr, they are automatically set
+    // to 0. Avoid using this class with a mix of Vrr and non-Vrr settings, as doing so may yield
+    // unexpected results.
+    int getGroupId(int width, int height, int minFrameInterval = 0, int vsyncPeriod = 0) {
+        const auto &key = std::make_tuple(width, height, minFrameInterval, vsyncPeriod);
+        if (groups_.count(key) > 0) {
+            return groups_[key];
+        }
+        size_t last_id = groups_.size();
+        groups_[key] = last_id;
+        return last_id;
+    }
+
+private:
+    std::map<std::tuple<int, int, int, int>, int> groups_;
+};
+
 class FramebufferManager {
     public:
         FramebufferManager(){};
@@ -234,6 +256,12 @@
                     mOldBlobs.clear();
                     return NO_ERROR;
                 };
+                void dumpDrmAtomicCommitMessage(int err);
+
+                void setAckCallback(std::function<void()> callback) {
+                    mAckCallback = std::move(callback);
+                };
+
             private:
                 drmModeAtomicReqPtr mPset;
                 drmModeAtomicReqPtr mSavedPset;
@@ -242,6 +270,15 @@
                 /* Destroy old blobs after commit */
                 std::vector<uint32_t> mOldBlobs;
                 int drmFd() const { return mDrmDisplayInterface->mDrmDevice->fd(); }
+
+                std::function<void()> mAckCallback;
+
+                static constexpr uint32_t kAllowDumpDrmAtomicMessageTimeMs = 5000U;
+                static constexpr const char* kDrmModuleParametersDebugNode =
+                        "/sys/module/drm/parameters/debug";
+                static constexpr const int kEnableDrmAtomicMessage = 16;
+                static constexpr const int kDisableDrmDebugMessage = 0;
+
         };
         class ExynosVsyncCallback {
             public:
@@ -369,6 +406,14 @@
         uint32_t getCrtcId() { return mDrmCrtc->id(); }
         int32_t triggerClearDisplayPlanes();
 
+        virtual void setVrrSettings(const VrrSettings_t& vrrSettings) override;
+        bool isFullVrrSupported() const {
+            return (mIsVrrModeSupported && mExynosDisplay->mDevice->isVrrApiSupported());
+        }
+        bool isPseudoVrrSupported() const {
+            return (mIsVrrModeSupported && !mExynosDisplay->mDevice->isVrrApiSupported());
+        }
+
     protected:
         enum class HalMipiSyncType : uint32_t {
             HAL_MIPI_CMD_SYNC_REFRESH_RATE = 0,
@@ -424,10 +469,11 @@
             }
         };
         int32_t createModeBlob(const DrmMode &mode, uint32_t &modeBlob);
-        int32_t setDisplayMode(DrmModeAtomicReq &drmReq, const uint32_t modeBlob);
+        int32_t setDisplayMode(DrmModeAtomicReq& drmReq, const uint32_t& modeBlob,
+                               const uint32_t& modeId);
         int32_t clearDisplayMode(DrmModeAtomicReq &drmReq);
         int32_t clearDisplayPlanes(DrmModeAtomicReq &drmReq);
-        int32_t chosePreferredConfig();
+        int32_t choosePreferredConfig();
         int getDeconChannel(ExynosMPP *otfMPP);
         /*
          * This function adds FB and gets new fb id if fbId is 0,
@@ -534,6 +580,7 @@
         DrmReadbackInfo mReadbackInfo;
         FramebufferManager mFBManager;
         std::array<uint8_t, MONITOR_DESCRIPTOR_DATA_LENGTH> mMonitorDescription;
+        nsecs_t mLastDumpDrmAtomicMessageTime;
 
     private:
         int32_t getDisplayFakeEdid(uint8_t &outPort, uint32_t &outDataSize, uint8_t *outData);
@@ -545,6 +592,12 @@
         int32_t mPanelFullResolutionHSize = 0;
         int32_t mPanelFullResolutionVSize = 0;
 
+        // Vrr related settings.
+        bool mIsVrrModeSupported = false;
+        int32_t mNotifyExpectedPresentHeadsUpNs = 0;
+        int32_t mNotifyExpectedPresentTimeoutNs = 0;
+        std::function<void(int)> mConfigChangeCallback;
+
         /**
          * retrievePanelFullResolution
          *
@@ -558,6 +611,8 @@
 
     public:
         virtual bool readHotplugStatus();
+        virtual int readHotplugErrorCode();
+        virtual void resetHotplugErrorCode();
 };
 
 #endif
diff --git a/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.cpp b/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.cpp
index 14d7ff4..4d99aaf 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.cpp
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.cpp
@@ -67,3 +67,5 @@
     return ((mExynosDisplay != nullptr) &&
             (mExynosDisplay->mType == HWC_DISPLAY_PRIMARY));
 }
+
+void ExynosDisplayInterface::setVrrSettings(const VrrSettings_t& vrrSettings) {}
diff --git a/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.h b/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.h
index 2855618..de68715 100644
--- a/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.h
+++ b/libhwc2.1/libdisplayinterface/ExynosDisplayInterface.h
@@ -17,13 +17,17 @@
 #ifndef _EXYNOSDISPLAYINTERFACE_H
 #define _EXYNOSDISPLAYINTERFACE_H
 
-#include <sys/types.h>
 #include <hardware/hwcomposer2.h>
+#include <sys/types.h>
 #include <utils/Errors.h>
+
 #include "ExynosHWCHelper.h"
 
 class ExynosDisplay;
 
+struct VrrSettings;
+typedef struct VrrSettings VrrSettings_t;
+
 using namespace android;
 class ExynosDisplayInterface {
     protected:
@@ -86,6 +90,10 @@
         virtual int32_t waitVBlank() { return 0; };
 
         virtual bool readHotplugStatus() { return true; };
+        virtual int readHotplugErrorCode() { return 0; };
+        virtual void resetHotplugErrorCode(){};
+
+        virtual void setVrrSettings(const VrrSettings_t& vrrSettings);
 
     public:
         uint32_t mType = INTERFACE_TYPE_NONE;
diff --git a/libhwc2.1/libdrmresource/drm/drmconnector.cpp b/libhwc2.1/libdrmresource/drm/drmconnector.cpp
index 6aad4bb..12de593 100644
--- a/libhwc2.1/libdrmresource/drm/drmconnector.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmconnector.cpp
@@ -17,17 +17,18 @@
 #define LOG_TAG "hwc-drm-connector"
 
 #include "drmconnector.h"
-#include "drmdevice.h"
 
+#include <cutils/properties.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <stdint.h>
+#include <xf86drmMode.h>
 
 #include <array>
 #include <sstream>
 
-#include <log/log.h>
-#include <xf86drmMode.h>
+#include "drmdevice.h"
 
 #ifndef DRM_MODE_CONNECTOR_WRITEBACK
 #define DRM_MODE_CONNECTOR_WRITEBACK 18
@@ -155,9 +156,9 @@
       ALOGE("Could not get panel_idle_support property\n");
   }
 
-  ret = drm_->GetConnectorProperty(*this, "vrr_switch_duration", &vrr_switch_duration_);
+  ret = drm_->GetConnectorProperty(*this, "rr_switch_duration", &rr_switch_duration_);
   if (ret) {
-    ALOGE("Could not get vrr_switch_duration property\n");
+      ALOGE("Could not get rr_switch_duration property\n");
   }
 
   ret = drm_->GetConnectorProperty(*this, "operation_rate", &operation_rate_);
@@ -191,7 +192,7 @@
   properties_.push_back(&lhbm_on_);
   properties_.push_back(&mipi_sync_);
   properties_.push_back(&panel_idle_support_);
-  properties_.push_back(&vrr_switch_duration_);
+  properties_.push_back(&rr_switch_duration_);
   properties_.push_back(&operation_rate_);
   properties_.push_back(&refresh_on_lp_);
 
@@ -251,7 +252,7 @@
   }
 }
 
-int DrmConnector::UpdateModes() {
+int DrmConnector::UpdateModes(bool is_vrr_mode) {
   std::lock_guard<std::recursive_mutex> lock(modes_lock_);
 
   int fd = drm_->fd();
@@ -276,6 +277,10 @@
 
   state_ = c->connection;
 
+  // Update mm_width_ and mm_height_ for xdpi/ydpi calculations
+  mm_width_ = c->mmWidth;
+  mm_height_ = c->mmHeight;
+
   bool preferred_mode_found = false;
   std::vector<DrmMode> new_modes;
   for (int i = 0; i < c->count_modes; ++i) {
@@ -288,6 +293,10 @@
       }
     }
     if (!exists) {
+      // Remove modes that mismatch with the VRR setting..
+      if (is_vrr_mode != ((c->modes[i].type & DRM_MODE_TYPE_VRR) != 0)) {
+        continue;
+      }
       DrmMode m(&c->modes[i]);
       m.set_id(drm_->next_mode_id());
       new_modes.push_back(m);
@@ -310,6 +319,24 @@
   return drm_->UpdateConnectorProperty(*this, &edid_property_);
 }
 
+int DrmConnector::UpdateLuminanceAndHdrProperties() {
+  int res = 0;
+
+  res = drm_->UpdateConnectorProperty(*this, &max_luminance_);
+  if (res)
+    return res;
+  res = drm_->UpdateConnectorProperty(*this, &max_avg_luminance_);
+  if (res)
+    return res;
+  res = drm_->UpdateConnectorProperty(*this, &min_luminance_);
+  if (res)
+    return res;
+  res = drm_->UpdateConnectorProperty(*this, &hdr_formats_);
+  if (res)
+    return res;
+  return 0;
+}
+
 const DrmMode &DrmConnector::active_mode() const {
   return active_mode_;
 }
@@ -435,8 +462,8 @@
     return panel_idle_support_;
 }
 
-const DrmProperty &DrmConnector::vrr_switch_duration() const {
-  return vrr_switch_duration_;
+const DrmProperty &DrmConnector::rr_switch_duration() const {
+    return rr_switch_duration_;
 }
 
 DrmEncoder *DrmConnector::encoder() const {
diff --git a/libhwc2.1/libdrmresource/drm/drmdevice.cpp b/libhwc2.1/libdrmresource/drm/drmdevice.cpp
index ced174c..d01f68a 100644
--- a/libhwc2.1/libdrmresource/drm/drmdevice.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmdevice.cpp
@@ -85,10 +85,6 @@
   max_resolution_ = std::pair<uint32_t, uint32_t>(res->max_width,
                                                   res->max_height);
 
-  // Assumes that the primary display will always be in the first
-  // drm_device opened.
-  bool found_primary = num_displays != 0;
-
   for (int i = 0; !ret && i < res->count_crtcs; ++i) {
     drmModeCrtcPtr c = drmModeGetCrtc(fd(), res->crtcs[i]);
     if (!c) {
@@ -177,31 +173,22 @@
       connectors_.emplace_back(std::move(conn));
   }
 
-  // First look for primary amongst internal connectors
+  // First look for primary amongst internal connectors and for
+  // the others assign consecutive display_numbers.
   for (auto &conn : connectors_) {
-    if (conn->internal() && !found_primary) {
+    if (conn->internal() && conn->display() < 0) {
       conn->set_display(num_displays);
       displays_[num_displays] = num_displays;
       ++num_displays;
-      found_primary = true;
-      break;
     }
   }
 
-  // Then pick first available as primary and for the others assign
-  // consecutive display_numbers.
+  // Assign consecutive display_numbers for external connectors.
   for (auto &conn : connectors_) {
-    if (conn->external() || conn->internal()) {
-      if (!found_primary) {
-        conn->set_display(num_displays);
-        displays_[num_displays] = num_displays;
-        found_primary = true;
-        ++num_displays;
-      } else if (conn->display() < 0) {
-        conn->set_display(num_displays);
-        displays_[num_displays] = num_displays;
-        ++num_displays;
-      }
+    if (conn->external() && conn->display() < 0) {
+      conn->set_display(num_displays);
+      displays_[num_displays] = num_displays;
+      ++num_displays;
     }
   }
 
@@ -484,14 +471,14 @@
   for (int i = 0; !found && (size_t)i < props->count_props; ++i) {
     drmModePropertyPtr p = drmModeGetProperty(fd(), props->props[i]);
     if (!strcmp(p->name, prop_name)) {
-      property->Init(p, props->prop_values[i]);
+      property->init(p, props->prop_values[i]);
       found = true;
     }
     drmModeFreeProperty(p);
   }
 
   if (!found)
-      property->SetName(prop_name);
+    property->setName(prop_name);
 
   drmModeFreeObjectProperties(props);
   return found ? 0 : -ENOENT;
@@ -525,7 +512,7 @@
     for (int i = 0; !found && (size_t)i < props->count_props; ++i) {
         drmModePropertyPtr p = drmModeGetProperty(fd(), props->props[i]);
         if (props->props[i] == property->id()) {
-            property->UpdateValue(props->prop_values[i]);
+            property->updateValue(props->prop_values[i]);
             found = true;
         }
         drmModeFreeProperty(p);
diff --git a/libhwc2.1/libdrmresource/drm/drmmode.cpp b/libhwc2.1/libdrmresource/drm/drmmode.cpp
index c3ab385..a883a20 100644
--- a/libhwc2.1/libdrmresource/drm/drmmode.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmmode.cpp
@@ -23,6 +23,12 @@
 
 namespace android {
 
+namespace {
+inline bool HasFlag(const int value, const int &flag) {
+  return ((value & flag) == flag);
+}
+}; // namespace
+
 DrmMode::DrmMode(drmModeModeInfoPtr m)
     : id_(0),
       clock_(m->clock),
@@ -123,9 +129,52 @@
 
 float DrmMode::v_refresh() const {
   // Always recalculate refresh to report correct float rate
+  if (v_total_ == 0 || h_total_ == 0) {
+    return 0.0f;
+  }
   return clock_ / (float)(v_total_ * h_total_) * 1000.0f;
 }
 
+float DrmMode::te_frequency() const {
+  auto freq = v_refresh();
+  if (is_vrr_mode()) {
+    if (HasFlag(flags_, DRM_MODE_FLAG_TE_FREQ_X2)) {
+      freq *= 2;
+    } else if (HasFlag(flags_, DRM_MODE_FLAG_TE_FREQ_X4)) {
+      freq *= 4;
+    } else {
+      if (!HasFlag(flags_, DRM_MODE_FLAG_TE_FREQ_X1)) {
+        return 0.0f;
+      }
+    }
+  }
+  return freq;
+}
+
+// vertical refresh period.
+float DrmMode::v_period(int64_t unit) const {
+  auto frequency = v_refresh();
+  if (frequency == 0.0f) {
+    return 0.0f;
+  }
+  return (1.0 / frequency) * unit;
+}
+
+float DrmMode::te_period(int64_t unit) const {
+  auto frequency = te_frequency();
+  if (frequency == 0.0f) {
+    return 0.0f;
+  }
+  return (1.0 / frequency) * unit;
+}
+
+bool DrmMode::is_operation_rate_to_bts() const {
+  if (!is_vrr_mode()) {
+    return HasFlag(flags_, DRM_MODE_FLAG_BTS_OP_RATE);
+  }
+  return false;
+}
+
 uint32_t DrmMode::flags() const {
   return flags_;
 }
diff --git a/libhwc2.1/libdrmresource/drm/drmproperty.cpp b/libhwc2.1/libdrmresource/drm/drmproperty.cpp
index 8f856d6..6c1fff4 100644
--- a/libhwc2.1/libdrmresource/drm/drmproperty.cpp
+++ b/libhwc2.1/libdrmresource/drm/drmproperty.cpp
@@ -26,6 +26,10 @@
 #include <inttypes.h>
 #include <utils/Errors.h>
 
+static inline int64_t U642I64(uint64_t val) {
+  return *(reinterpret_cast<int64_t *>(&val));
+}
+
 namespace android {
 
 DrmProperty::DrmPropertyEnum::DrmPropertyEnum(drm_mode_property_enum *e)
@@ -37,26 +41,22 @@
 
 DrmProperty::DrmProperty(drmModePropertyPtr p, uint64_t value)
     : id_(0), type_(DRM_PROPERTY_TYPE_INVALID), flags_(0), name_("") {
-  Init(p, value);
+  init(p, value);
 }
 
-void DrmProperty::Init(drmModePropertyPtr p, uint64_t value) {
+void DrmProperty::init(drmModePropertyPtr p, uint64_t value) {
   id_ = p->prop_id;
   flags_ = p->flags;
   name_ = p->name;
   value_ = value;
 
-  for (int i = 0; i < p->count_values; ++i)
-    values_.push_back(p->values[i]);
+  for (int i = 0; i < p->count_values; ++i) values_.push_back(p->values[i]);
 
-  for (int i = 0; i < p->count_enums; ++i)
-    enums_.push_back(DrmPropertyEnum(&p->enums[i]));
+  for (int i = 0; i < p->count_enums; ++i) enums_.push_back(DrmPropertyEnum(&p->enums[i]));
 
-  for (int i = 0; i < p->count_blobs; ++i)
-    blob_ids_.push_back(p->blob_ids[i]);
+  for (int i = 0; i < p->count_blobs; ++i) blob_ids_.push_back(p->blob_ids[i]);
 
-  if ((flags_ & DRM_MODE_PROP_RANGE) ||
-      (flags_ & DRM_MODE_PROP_SIGNED_RANGE))
+  if ((flags_ & DRM_MODE_PROP_RANGE) || (flags_ & DRM_MODE_PROP_SIGNED_RANGE))
     type_ = DRM_PROPERTY_TYPE_INT;
   else if (flags_ & DRM_MODE_PROP_ENUM)
     type_ = DRM_PROPERTY_TYPE_ENUM;
@@ -106,29 +106,37 @@
 
 void DrmProperty::printProperty() const
 {
-    ALOGD("=====================================================");
-    ALOGD("name: %s, type(%d), value_(%" PRId64 ")", name_.c_str(), type_, value_);
-    ALOGD("values.size(%zu)",  values_.size());
-    for(uint32_t i = 0; i < values_.size(); i++) {
-        ALOGD("[%d] %" PRId64 "", i, values_[i]);
-    }
-    ALOGD("enums.size(%zu)",  enums_.size());
-    uint32_t i = 0;
-    for (auto const &it : enums_) {
-        ALOGD("[%d] %s %" PRId64 "", i++, it.name_.c_str(), it.value_);
-    }
+  ALOGD("=====================================================");
+  ALOGD("name: %s, type(%d), value_(%" PRId64 ")", name_.c_str(), type_, value_);
+  ALOGD("values.size(%zu)", values_.size());
+  for (uint32_t i = 0; i < values_.size(); i++) {
+    ALOGD("[%d] %" PRId64 "", i, values_[i]);
+  }
+  ALOGD("enums.size(%zu)", enums_.size());
+  uint32_t i = 0;
+  for (auto const &it : enums_) {
+    ALOGD("[%d] %s %" PRId64 "", i++, it.name_.c_str(), it.value_);
+  }
 }
 
-bool DrmProperty::is_immutable() const {
+bool DrmProperty::isImmutable() const {
   return id_ && (flags_ & DRM_MODE_PROP_IMMUTABLE);
 }
 
-bool DrmProperty::is_range() const {
+bool DrmProperty::isRange() const {
   return id_ && (flags_ & DRM_MODE_PROP_RANGE);
 }
 
-std::tuple<int, uint64_t> DrmProperty::range_min() const {
-  if (!is_range())
+bool DrmProperty::isSignedRange() const {
+  return (flags_ & DRM_MODE_PROP_EXTENDED_TYPE) == DRM_MODE_PROP_SIGNED_RANGE;
+}
+
+bool DrmProperty::isBitmask() const {
+  return id_ && (flags_ & DRM_MODE_PROP_BITMASK);
+}
+
+std::tuple<int, uint64_t> DrmProperty::rangeMin() const {
+  if (!isRange())
     return std::make_tuple(-EINVAL, 0);
   if (values_.size() < 1)
     return std::make_tuple(-ENOENT, 0);
@@ -136,8 +144,8 @@
   return std::make_tuple(0, values_[0]);
 }
 
-std::tuple<int, uint64_t> DrmProperty::range_max() const {
-  if (!is_range())
+std::tuple<int, uint64_t> DrmProperty::rangeMax() const {
+  if (!isRange())
     return std::make_tuple(-EINVAL, 0);
   if (values_.size() < 2)
     return std::make_tuple(-ENOENT, 0);
@@ -145,8 +153,7 @@
   return std::make_tuple(0, values_[1]);
 }
 
-std::tuple<uint64_t, int> DrmProperty::GetEnumValueWithName(
-    std::string name) const {
+std::tuple<uint64_t, int> DrmProperty::getEnumValueWithName(std::string name) const {
   for (auto it : enums_) {
     if (it.name_.compare(name) == 0) {
       return std::make_tuple(it.value_, 0);
@@ -156,34 +163,68 @@
   return std::make_tuple(UINT64_MAX, -EINVAL);
 }
 
-void DrmProperty::UpdateValue(uint64_t value) {
+bool DrmProperty::validateChange(uint64_t value) const {
+  if (isImmutable()) {
+    ALOGE("%s: %s is immutable drm property (%zu)", __func__, name().c_str());
+    return false;
+  } else if (isRange()) {
+    if (value < values_[0] || value > values_[1]) {
+      ALOGE("%s: range property %s set to %" PRIu64 " is invalid [%" PRIu64 "-%" PRIu64 "]",
+            __func__, name().c_str(), value, values_[0], values_[1]);
+      return false;
+    }
+  } else if (isSignedRange()) {
+    int64_t svalue = U642I64(value);
+
+    if (svalue < U642I64(values_[0]) || svalue > U642I64(values_[1])) {
+      ALOGE("%s: signed property %s set to %" PRIi64 " is invalid [%" PRIi64 "-%" PRIi64 "]",
+            __func__, name().c_str(), svalue, U642I64(values_[0]), U642I64(values_[1]));
+      return false;
+    }
+  } else if (isBitmask()) {
+    uint64_t valid_mask = 0;
+
+    for (auto i = 0; i < values_.size(); i++) {
+      valid_mask |= (1ULL << values_[i]);
+    }
+    if (value & ~valid_mask) {
+      ALOGE("%s: bitmask property %s set to 0x%" PRIx64 " is invalid [0x%" PRIx64 "]", __func__,
+            name().c_str(), value, valid_mask);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void DrmProperty::updateValue(uint64_t value) {
   value_ = value;
 }
 
 std::tuple<uint64_t, int> DrmEnumParser::halToDrmEnum(const uint32_t halData,
                                                       const MapHal2DrmEnum& drmEnums) {
-    auto it = drmEnums.find(halData);
-    if (it != drmEnums.end()) {
-        return std::make_tuple(it->second, NO_ERROR);
-    } else {
-        ALOGE("%s::Failed to find standard enum(%d)",
-                __func__, halData);
-        return std::make_tuple(0, -EINVAL);
-    }
+  auto it = drmEnums.find(halData);
+  if (it != drmEnums.end()) {
+    return std::make_tuple(it->second, NO_ERROR);
+  } else {
+    ALOGE("%s: Failed to find standard enum(%d)", __func__, halData);
+    return std::make_tuple(0, -EINVAL);
+  }
 }
 
 void DrmEnumParser::parseEnums(const DrmProperty &property,
                                const std::vector<std::pair<uint32_t, const char *>> &enums,
                                MapHal2DrmEnum& out_enums) {
-    uint64_t value;
-    int err;
-    for (auto &e : enums) {
-        std::tie(value, err) = property.GetEnumValueWithName(e.second);
-        if (err)
-            ALOGE("Fail to find enum value with name %s", e.second);
-        else
-            out_enums[e.first] = value;
+  uint64_t value;
+  int err;
+  for (auto &e : enums) {
+    std::tie(value, err) = property.getEnumValueWithName(e.second);
+    if (err) {
+      ALOGE("%s: Fail to find enum value with name %s", __func__, e.second);
+    } else {
+      out_enums[e.first] = value;
     }
+  }
 }
 
 }  // namespace android
diff --git a/libhwc2.1/libdrmresource/drm/vsyncworker.cpp b/libhwc2.1/libdrmresource/drm/vsyncworker.cpp
index a795fb3..792946f 100644
--- a/libhwc2.1/libdrmresource/drm/vsyncworker.cpp
+++ b/libhwc2.1/libdrmresource/drm/vsyncworker.cpp
@@ -41,93 +41,99 @@
 
 VSyncWorker::VSyncWorker()
     : Worker("vsync", 2, true),
-      drm_(NULL),
-      display_(-1),
-      enabled_(false),
-      last_timestamp_(-1) {
-}
+      mDrmDevice(NULL),
+      mDisplay(-1),
+      mEnabled(false),
+      mLastTimestampNs(-1) {}
 
 VSyncWorker::~VSyncWorker() {
     Exit();
 }
 
-int VSyncWorker::Init(DrmDevice *drm, int display, const String8 &display_trace_name) {
-    drm_ = drm;
-    display_ = display;
-    display_trace_name_ = display_trace_name;
-    hw_vsync_period_tag_.appendFormat("HWVsyncPeriod for %s", display_trace_name.c_str());
-    hw_vsync_enabled_tag_.appendFormat("HWCVsync for %s", display_trace_name.c_str());
+int VSyncWorker::Init(DrmDevice *drm, int display, const String8 &displayTraceName) {
+    mDrmDevice = drm;
+    mDisplay = display;
+    mDisplayTraceName = displayTraceName;
+    mHwVsyncPeriodTag.appendFormat("HWVsyncPeriod for %s", displayTraceName.c_str());
+    mHwVsyncEnabledTag.appendFormat("HWCVsync for %s", displayTraceName.c_str());
 
     return InitWorker();
 }
 
 void VSyncWorker::RegisterCallback(std::shared_ptr<VsyncCallback> callback) {
     Lock();
-    callback_ = callback;
+    mCallback = callback;
     Unlock();
 }
 
 void VSyncWorker::VSyncControl(bool enabled) {
     Lock();
-    enabled_ = enabled;
-    last_timestamp_ = -1;
+    mEnabled = enabled;
+    mLastTimestampNs = -1;
     Unlock();
 
-    ATRACE_INT(hw_vsync_enabled_tag_.c_str(), static_cast<int32_t>(enabled));
-    ATRACE_INT64(hw_vsync_period_tag_.c_str(), 0);
+    ATRACE_INT(mHwVsyncEnabledTag.c_str(), static_cast<int32_t>(enabled));
+    ATRACE_INT64(mHwVsyncPeriodTag.c_str(), 0);
     Signal();
 }
 
 /*
- * Returns the timestamp of the next vsync in phase with last_timestamp_.
+ * Returns the timestamp of the next vsync in phase with mLastTimestampNs.
  * For example:
- *  last_timestamp_ = 137
- *  frame_ns = 50
- *  current = 683
+ *  mLastTimestampNs = 137
+ *  vsyncPeriodNs = 50
+ *  currentTimeNs = 683
  *
- *  expect = (50 * ((683 - 137)/50 + 1)) + 137
- *  expect = 687
+ *  expectTimeNs = (50 * ((683 - 137) / 50 + 1)) + 137
+ *  expectTimeNs = 687
  *
  *  Thus, we must sleep until timestamp 687 to maintain phase with the last
  *  timestamp. But if we don't know last vblank timestamp, sleep one vblank
  *  then try to get vblank from driver again.
  */
-int VSyncWorker::GetPhasedVSync(int64_t frame_ns, int64_t &expect) {
+int VSyncWorker::GetPhasedVSync(uint32_t vsyncPeriodNs, int64_t &expectTimeNs) {
     struct timespec now;
     if (clock_gettime(CLOCK_MONOTONIC, &now)) {
         ALOGE("clock_gettime failed %d", errno);
         return -EPERM;
     }
 
-    int64_t current = now.tv_sec * nsecsPerSec + now.tv_nsec;
-    if (last_timestamp_ < 0) {
-        expect = current + frame_ns;
+    int64_t currentTimeNs = now.tv_sec * nsecsPerSec + now.tv_nsec;
+    if (mLastTimestampNs < 0) {
+        expectTimeNs = currentTimeNs + vsyncPeriodNs;
         return -EAGAIN;
     }
 
-    expect = frame_ns * ((current - last_timestamp_) / frame_ns + 1) + last_timestamp_;
+    expectTimeNs = vsyncPeriodNs * ((currentTimeNs - mLastTimestampNs) / vsyncPeriodNs + 1)
+                    + mLastTimestampNs;
 
     return 0;
 }
 
-int VSyncWorker::SyntheticWaitVBlank(int64_t &timestamp) {
-    float refresh = 60.0f; // Default to 60Hz refresh rate
+int VSyncWorker::SyntheticWaitVBlank(int64_t &timestampNs) {
+    uint32_t vsyncPeriodNs = kDefaultVsyncPeriodNanoSecond;
+    int32_t refreshRate = kDefaultRefreshRateFrequency;
 
-    DrmConnector *conn = drm_->GetConnectorForDisplay(display_);
-    if (conn && conn->active_mode().v_refresh() != 0.0f) {
-        refresh = conn->active_mode().v_refresh();
+    DrmConnector *conn = mDrmDevice->GetConnectorForDisplay(mDisplay);
+    if (conn && conn->active_mode().te_period() != 0.0f &&
+            conn->active_mode().v_refresh() != 0.0f) {
+        vsyncPeriodNs = static_cast<uint32_t>(conn->active_mode().te_period());
+        refreshRate = static_cast<int32_t>(conn->active_mode().v_refresh());
     } else {
-        ALOGW("Vsync worker active with conn=%p refresh=%f\n", conn,
-              conn ? conn->active_mode().v_refresh() : 0.0f);
+        ALOGW("Vsync worker active with conn=%p vsync=%u refresh=%d\n", conn,
+            conn ? static_cast<uint32_t>(conn->active_mode().te_period()) :
+                    kDefaultVsyncPeriodNanoSecond,
+            conn ? static_cast<int32_t>(conn->active_mode().v_refresh()) :
+                    kDefaultRefreshRateFrequency);
     }
 
-    int64_t phased_timestamp;
-    int ret = GetPhasedVSync(nsecsPerSec / refresh, phased_timestamp);
+    int64_t phasedTimestampNs;
+    int ret = GetPhasedVSync(vsyncPeriodNs, phasedTimestampNs);
     if (ret && ret != -EAGAIN) return -1;
 
     struct timespec vsync;
-    vsync.tv_sec = phased_timestamp / nsecsPerSec;
-    vsync.tv_nsec = phased_timestamp % nsecsPerSec;
+    vsync.tv_sec = phasedTimestampNs / nsecsPerSec;
+    vsync.tv_nsec = phasedTimestampNs % nsecsPerSec;
 
     int err;
     do {
@@ -135,7 +141,7 @@
     } while (err == EINTR);
     if (err || ret) return -1;
 
-    timestamp = (int64_t)vsync.tv_sec * nsecsPerSec + (int64_t)vsync.tv_nsec;
+    timestampNs = (int64_t)vsync.tv_sec * nsecsPerSec + (int64_t)vsync.tv_nsec;
 
     return 0;
 }
@@ -144,7 +150,7 @@
     int ret;
 
     Lock();
-    if (!enabled_) {
+    if (!mEnabled) {
         ret = WaitForSignalOrExitLocked();
         if (ret == -EINTR) {
             Unlock();
@@ -152,32 +158,32 @@
         }
     }
 
-    int display = display_;
-    std::shared_ptr<VsyncCallback> callback(callback_);
+    int display = mDisplay;
+    std::shared_ptr<VsyncCallback> callback(mCallback);
     Unlock();
 
-    DrmCrtc *crtc = drm_->GetCrtcForDisplay(display);
+    DrmCrtc *crtc = mDrmDevice->GetCrtcForDisplay(display);
     if (!crtc) {
         ALOGE("Failed to get crtc for display");
         return;
     }
-    uint32_t high_crtc = (crtc->pipe() << DRM_VBLANK_HIGH_CRTC_SHIFT);
+    uint32_t highCrtc = (crtc->pipe() << DRM_VBLANK_HIGH_CRTC_SHIFT);
 
     drmVBlank vblank;
     memset(&vblank, 0, sizeof(vblank));
     vblank.request.type =
-            (drmVBlankSeqType)(DRM_VBLANK_RELATIVE | (high_crtc & DRM_VBLANK_HIGH_CRTC_MASK));
+        (drmVBlankSeqType)(DRM_VBLANK_RELATIVE | (highCrtc & DRM_VBLANK_HIGH_CRTC_MASK));
     vblank.request.sequence = 1;
 
-    int64_t timestamp;
-    ret = drmWaitVBlank(drm_->fd(), &vblank);
+    int64_t timestampNs;
+    ret = drmWaitVBlank(mDrmDevice->fd(), &vblank);
     if (ret) {
-        if (SyntheticWaitVBlank(timestamp)) {
+        if (SyntheticWaitVBlank(timestampNs)) {
             // postpone the callback until we get a real value from the hardware
             return;
         }
     } else {
-        timestamp = (int64_t)vblank.reply.tval_sec * nsecsPerSec +
+        timestampNs = (int64_t)vblank.reply.tval_sec * nsecsPerSec +
                 (int64_t)vblank.reply.tval_usec * 1000;
     }
 
@@ -199,24 +205,25 @@
      * Please note that issue described below is different one and it is related
      * to RegisterCallback, not to disabling vsync via VSyncControl.
      */
-    if (!enabled_) return;
+    if (!mEnabled)
+        return;
     /*
-     * There's a race here where a change in callback_ will not take effect until
+     * There's a race here where a change in mCallback will not take effect until
      * the next subsequent requested vsync. This is unavoidable since we can't
      * call the vsync hook while holding the thread lock.
      *
-     * We could shorten the race window by caching callback_ right before calling
-     * the hook. However, in practice, callback_ is only updated once, so it's not
+     * We could shorten the race window by caching mCallback right before calling
+     * the hook. However, in practice, mCallback is only updated once, so it's not
      * worth the overhead.
      */
-    if (callback) callback->Callback(display, timestamp);
+    if (callback) callback->Callback(display, timestampNs);
 
-    if (last_timestamp_ >= 0) {
-        int64_t period = timestamp - last_timestamp_;
-        ATRACE_INT64(hw_vsync_period_tag_.c_str(), period);
-        ALOGV("HW vsync period %" PRId64 "ns for %s", period, display_trace_name_.c_str());
+    if (mLastTimestampNs >= 0) {
+        int64_t period = timestampNs - mLastTimestampNs;
+        ATRACE_INT64(mHwVsyncPeriodTag.c_str(), period);
+        ALOGV("HW vsync period %" PRId64 "ns for %s", period, mDisplayTraceName.c_str());
     }
 
-    last_timestamp_ = timestamp;
+    mLastTimestampNs = timestampNs;
 }
 }  // namespace android
diff --git a/libhwc2.1/libdrmresource/include/drmconnector.h b/libhwc2.1/libdrmresource/include/drmconnector.h
index 98960b3..d05c1d5 100644
--- a/libhwc2.1/libdrmresource/include/drmconnector.h
+++ b/libhwc2.1/libdrmresource/include/drmconnector.h
@@ -53,8 +53,9 @@
 
   std::string name() const;
 
-  int UpdateModes();
+  int UpdateModes(bool is_vrr_mode = false);
   int UpdateEdidProperty();
+  int UpdateLuminanceAndHdrProperties();
 
   const std::vector<DrmMode> &modes() const {
     return modes_;
@@ -86,7 +87,7 @@
   const DrmProperty &lhbm_on() const;
   const DrmProperty &mipi_sync() const;
   const DrmProperty &panel_idle_support() const;
-  const DrmProperty &vrr_switch_duration() const;
+  const DrmProperty &rr_switch_duration() const;
   const DrmProperty &operation_rate() const;
   const DrmProperty &refresh_on_lp() const;
 
@@ -147,7 +148,7 @@
   DrmProperty lhbm_on_;
   DrmProperty mipi_sync_;
   DrmProperty panel_idle_support_;
-  DrmProperty vrr_switch_duration_;
+  DrmProperty rr_switch_duration_;
   DrmProperty operation_rate_;
   DrmProperty refresh_on_lp_;
   std::vector<DrmProperty *> properties_;
diff --git a/libhwc2.1/libdrmresource/include/drmeventlistener.h b/libhwc2.1/libdrmresource/include/drmeventlistener.h
index c26a458..ecaf68a 100644
--- a/libhwc2.1/libdrmresource/include/drmeventlistener.h
+++ b/libhwc2.1/libdrmresource/include/drmeventlistener.h
@@ -26,6 +26,9 @@
 
 namespace android {
 
+constexpr uint32_t kDefaultVsyncPeriodNanoSecond = 16666666;
+constexpr int32_t kDefaultRefreshRateFrequency = 60;
+
 class DrmDevice;
 
 class DrmEventHandler {
diff --git a/libhwc2.1/libdrmresource/include/drmmode.h b/libhwc2.1/libdrmresource/include/drmmode.h
index 4cc06b1..2dbcc60 100644
--- a/libhwc2.1/libdrmresource/include/drmmode.h
+++ b/libhwc2.1/libdrmresource/include/drmmode.h
@@ -19,8 +19,24 @@
 
 #include <stdint.h>
 #include <xf86drmMode.h>
+
+#include <ratio>
 #include <string>
 
+// Alternative definitions(alias) of DRM modes and flags for VRR.
+// The kernel contains corresponding defines that MUST align with those specified here..
+#define DRM_MODE_TYPE_VRR DRM_MODE_TYPE_USERDEF
+#define DRM_MODE_FLAG_NS DRM_MODE_FLAG_CLKDIV2
+#define DRM_MODE_FLAG_TE_FREQ_X1 DRM_MODE_FLAG_PHSYNC
+#define DRM_MODE_FLAG_TE_FREQ_X2 DRM_MODE_FLAG_NHSYNC
+#define DRM_MODE_FLAG_TE_FREQ_X4 DRM_MODE_FLAG_PVSYNC
+
+// BTS needs to take operation rate into account
+#define DRM_MODE_FLAG_BTS_OP_RATE DRM_MODE_FLAG_NVSYNC
+
+#define PANEL_REFRESH_CTRL_FI (1 << 0)
+#define PANEL_REFRESH_CTRL_IDLE (1 << 1)
+
 namespace android {
 
 class DrmMode {
@@ -31,6 +47,9 @@
   bool operator==(const drmModeModeInfo &m) const;
   void ToDrmModeModeInfo(drm_mode_modeinfo *m) const;
 
+  inline bool is_vrr_mode() const { return (type_ & DRM_MODE_TYPE_VRR); };
+  inline bool is_ns_mode() const { return (flags_ & DRM_MODE_FLAG_NS); };
+
   uint32_t id() const;
   void set_id(uint32_t id);
 
@@ -48,7 +67,12 @@
   uint32_t v_total() const;
   uint32_t v_scan() const;
   float v_refresh() const;
+  float te_frequency() const;
+  // Convert frequency to period, with the default unit being nanoseconds.
+  float v_period(int64_t unit = std::nano::den) const;
+  float te_period(int64_t unit = std::nano::den) const;
 
+  bool is_operation_rate_to_bts() const;
   uint32_t flags() const;
   uint32_t type() const;
 
diff --git a/libhwc2.1/libdrmresource/include/drmproperty.h b/libhwc2.1/libdrmresource/include/drmproperty.h
index 2c4eb7a..7cc6f91 100644
--- a/libhwc2.1/libdrmresource/include/drmproperty.h
+++ b/libhwc2.1/libdrmresource/include/drmproperty.h
@@ -41,21 +41,24 @@
   DrmProperty(const DrmProperty &) = delete;
   DrmProperty &operator=(const DrmProperty &) = delete;
 
-  void Init(drmModePropertyPtr p, uint64_t value);
-  void SetName(std::string name) { name_ = name; };
-  std::tuple<uint64_t, int> GetEnumValueWithName(std::string name) const;
+  void init(drmModePropertyPtr p, uint64_t value);
+  void setName(std::string name) { name_ = name; };
+  std::tuple<uint64_t, int> getEnumValueWithName(std::string name) const;
 
   uint32_t id() const;
   std::string name() const;
 
   std::tuple<int, uint64_t> value() const;
-  bool is_immutable() const;
+  bool isImmutable() const;
+  bool isRange() const;
+  bool isSignedRange() const;
+  bool isBitmask() const;
 
-  bool is_range() const;
-  std::tuple<int, uint64_t> range_min() const;
-  std::tuple<int, uint64_t> range_max() const;
+  std::tuple<int, uint64_t> rangeMin() const;
+  std::tuple<int, uint64_t> rangeMax() const;
 
-  void UpdateValue(const uint64_t value);
+  bool validateChange(uint64_t value) const;
+  void updateValue(const uint64_t value);
   void printProperty() const;
 
  private:
diff --git a/libhwc2.1/libdrmresource/include/vsyncworker.h b/libhwc2.1/libdrmresource/include/vsyncworker.h
index ce12eea..7e9c124 100644
--- a/libhwc2.1/libdrmresource/include/vsyncworker.h
+++ b/libhwc2.1/libdrmresource/include/vsyncworker.h
@@ -30,41 +30,41 @@
 namespace android {
 
 class VsyncCallback {
- public:
-     virtual ~VsyncCallback() {}
-     virtual void Callback(int display, int64_t timestamp) = 0;
+    public:
+        virtual ~VsyncCallback() {}
+        virtual void Callback(int display, int64_t timestamp) = 0;
 };
 
 class VSyncWorker : public Worker {
- public:
-     VSyncWorker();
-     ~VSyncWorker() override;
+    public:
+        VSyncWorker();
+        ~VSyncWorker() override;
 
-     int Init(DrmDevice *drm, int display, const String8 &display_trace_name);
-     void RegisterCallback(std::shared_ptr<VsyncCallback> callback);
+        int Init(DrmDevice* drm, int display, const String8& displayTraceName);
+        void RegisterCallback(std::shared_ptr<VsyncCallback> callback);
 
-     void VSyncControl(bool enabled);
+        void VSyncControl(bool enabled);
 
- protected:
-     void Routine() override;
+    protected:
+        void Routine() override;
 
- private:
-     int GetPhasedVSync(int64_t frame_ns, int64_t &expect);
-     int SyntheticWaitVBlank(int64_t &timestamp);
+    private:
+        int GetPhasedVSync(uint32_t vsyncPeriodNs, int64_t& expectTimeNs);
+        int SyntheticWaitVBlank(int64_t& timestamp);
 
-     DrmDevice *drm_;
+        DrmDevice* mDrmDevice;
 
-     // shared_ptr since we need to use this outside of the thread lock (to
-     // actually call the hook) and we don't want the memory freed until we're
-     // done
-     std::shared_ptr<VsyncCallback> callback_ = NULL;
+        // shared_ptr since we need to use this outside of the thread lock (to
+        // actually call the hook) and we don't want the memory freed until we're
+        // done
+        std::shared_ptr<VsyncCallback> mCallback = NULL;
 
-     int display_;
-     std::atomic_bool enabled_;
-     int64_t last_timestamp_;
-     String8 hw_vsync_period_tag_;
-     String8 hw_vsync_enabled_tag_;
-     String8 display_trace_name_;
+        int mDisplay;
+        std::atomic_bool mEnabled;
+        int64_t mLastTimestampNs;
+        String8 mHwVsyncPeriodTag;
+        String8 mHwVsyncEnabledTag;
+        String8 mDisplayTraceName;
 };
 }  // namespace android
 
diff --git a/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp b/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
index 84a6971..921fe3c 100644
--- a/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
+++ b/libhwc2.1/libexternaldisplay/ExynosExternalDisplay.cpp
@@ -48,6 +48,9 @@
     mIsSkipFrame = false;
     mVirtualDisplayState = 0;
 
+    mDRDefault = true;
+    mDREnable = false;
+
     //TODO : Hard coded currently
     mNumMaxPriorityAllowed = 1;
     mPowerModeState = (hwc2_power_mode_t)HWC_POWER_MODE_OFF;
@@ -136,7 +139,7 @@
 
         if (property_get("vendor.display.external.preferred_mode", modeStr, "") > 0) {
             if (sscanf(modeStr, "%dx%d@%d", &width, &height, &fps) == 3) {
-                err = lookupDisplayConfigs(width, height, fps, &config);
+                err = lookupDisplayConfigs(width, height, fps, fps, &config);
                 if (err != HWC2_ERROR_NONE) {
                     DISPLAY_LOGW("%s: display does not support preferred mode %dx%d@%d",
                                  __func__, width, height, fps);
@@ -157,6 +160,7 @@
         mXres = displayConfig.width;
         mYres = displayConfig.height;
         mVsyncPeriod = displayConfig.vsyncPeriod;
+        mRefreshRate = displayConfig.refreshRate;
 
         if (mDisplayInterface->mType == INTERFACE_TYPE_DRM) {
             ret = mDisplayInterface->setActiveConfig(mActiveConfig);
@@ -448,11 +452,16 @@
 {
     ALOGI("[ExternalDisplay] %s +", __func__);
 
-    if (!mEnabled)
-        return HWC2_ERROR_NONE;
-
     if (mHpdStatus) {
-        clearDisplay(false);
+        /*
+         * DP cable is connected and link is up
+         *
+         * Currently, we don't power down here for two reasons:
+         * - power up would require DP link re-training (slow)
+         * - DP audio can continue playing while display is blank
+         */
+        if (mEnabled)
+            clearDisplay(false);
         return HWC2_ERROR_NONE;
     }
 
@@ -556,10 +565,13 @@
             mHpdStatus = false;
             return;
         }
+        mDREnable = mDRDefault;
     } else {
         disable();
         closeExternalDisplay();
+        mDREnable = false;
     }
+    mDevice->checkDynamicRecompositionThread();
 
     ALOGI("HPD status changed to %s, mDisplayId %d, mDisplayFd %d", mHpdStatus ? "enabled" : "disabled", mDisplayId, mDisplayInterface->getDisplayFd());
 }
diff --git a/libhwc2.1/libhwcService/ExynosHWCService.cpp b/libhwc2.1/libhwcService/ExynosHWCService.cpp
index 4ae010a..6d84074 100644
--- a/libhwc2.1/libhwcService/ExynosHWCService.cpp
+++ b/libhwc2.1/libhwcService/ExynosHWCService.cpp
@@ -462,7 +462,7 @@
     auto display = mHWCCtx->device->getDisplay(display_id);
 
     if (display != nullptr) {
-        return display->setMinIdleRefreshRate(fps, VrrThrottleRequester::TEST);
+        return display->setMinIdleRefreshRate(fps, RrThrottleRequester::TEST);
     }
 
     return -EINVAL;
@@ -478,7 +478,7 @@
                 ->setRefreshRateThrottleNanos(std::chrono::duration_cast<std::chrono::nanoseconds>(
                                                       std::chrono::milliseconds(delayMs))
                                                       .count(),
-                                              VrrThrottleRequester::TEST);
+                                              RrThrottleRequester::TEST);
     }
 
     return -EINVAL;
diff --git a/libhwc2.1/libhwchelper/ExynosHWCHelper.h b/libhwc2.1/libhwchelper/ExynosHWCHelper.h
index 6e641c4..de99b99 100644
--- a/libhwc2.1/libhwchelper/ExynosHWCHelper.h
+++ b/libhwc2.1/libhwchelper/ExynosHWCHelper.h
@@ -26,6 +26,7 @@
 #include <optional>
 #include <sstream>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "DeconCommonHeader.h"
@@ -628,11 +629,18 @@
     CtrlValue() : value_(), dirty_(false) {}
     CtrlValue(const T& value) : value_(value), dirty_(false) {}
 
-    void store(T value) {
+    void store(const T& value) {
         if (value == value_) return;
         dirty_ = true;
         value_ = value;
     };
+
+    void store(T&& value) {
+        if (value == value_) return;
+        dirty_ = true;
+        value_ = std::move(value);
+    };
+
     const T &get() { return value_; };
     bool is_dirty() { return dirty_; };
     void clear_dirty() { dirty_ = false; };
@@ -664,6 +672,56 @@
     }
 };
 
+class FileNodeWriter {
+public:
+    FileNodeWriter(const std::string& nodePath) : mNodePath(nodePath) {}
+
+    ~FileNodeWriter() {
+        for (auto& node : mOperateNodes) {
+            close(node.second);
+        }
+    }
+
+    template <typename T>
+    bool WriteCommandString(const std::string& nodeName, T cmd) {
+        // ref: https://elixir.bootlin.com/linux/latest/source/include/linux/kstrtox.h
+        static_assert(std::is_integral_v<T>);
+
+        int fd = getOperateNodeFileHandle(nodeName);
+        if (fd >= 0) {
+            std::string cmdString = std::to_string(cmd);
+            int ret = write(fd, cmdString.c_str(), std::strlen(cmdString.c_str()));
+            if (ret < 0) {
+                ALOGE("Write to file node %s failed, ret = %d errno = %d", mNodePath.c_str(), ret,
+                      errno);
+                return false;
+            }
+        } else {
+            ALOGE("Write to invalid file node %s", mNodePath.c_str());
+            return false;
+        }
+        return true;
+    }
+
+private:
+    int getOperateNodeFileHandle(const std::string& nodeName) {
+        if (mOperateNodes.count(nodeName) > 0) {
+            return mOperateNodes[nodeName];
+        }
+        std::string fullPath = mNodePath + nodeName;
+        int fd = open(fullPath.c_str(), O_WRONLY, 0);
+        if (fd < 0) {
+            ALOGE("Open file node failed, fd = %d", fd);
+            return fd;
+        }
+        mOperateNodes[nodeName] = fd;
+        return fd;
+    }
+
+    std::string mNodePath;
+    std::unordered_map<std::string, int> mOperateNodes;
+};
+
 // Waits for a given property value, or returns std::nullopt if unavailable
 std::optional<std::string> waitForPropertyValue(const std::string &property, int64_t timeoutMs);
 
diff --git a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
index ec1e8ba..d440ac2 100644
--- a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
+++ b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.cpp
@@ -38,11 +38,26 @@
 extern struct exynos_hwc_control exynosHWCControl;
 
 using namespace SOC_VERSION;
+
+namespace {
+
 constexpr auto nsecsPerSec = std::chrono::nanoseconds(1s).count();
 
+inline constexpr int kDefaultNotifyExpectedPresentConfigHeadsUpNs =
+        std::chrono::nanoseconds(30ms).count();
+inline constexpr int kDefaultNotifyExpectedPresentConfigTimeoutNs =
+        std::chrono::nanoseconds(30ms).count();
+
+static constexpr int kMaximumPropertyIdentifierLength = 128;
+
 static const std::map<const DisplayType, const std::string> panelSysfsPath =
         {{DisplayType::DISPLAY_PRIMARY, "/sys/devices/platform/exynos-drm/primary-panel/"},
-         {DisplayType::DISPLAY_SECONDARY, "/sys/devices/platform/exynos-drm/secondary-panel/"}};
+#ifdef USES_IDISPLAY_INTF_SEC
+         {DisplayType::DISPLAY_SECONDARY, "/sys/devices/platform/exynos-drm/secondary-panel/"}
+#endif
+
+};
+} // namespace
 
 static String8 getPropertyBootModeStr(const int32_t dispId) {
     String8 str;
@@ -87,8 +102,8 @@
       : ExynosDisplay(HWC_DISPLAY_PRIMARY, index, device, displayName),
         mUseBlockingZoneForMinIdleRefreshRate(false),
         mMinIdleRefreshRate(0),
-        mVrrThrottleFps{0},
-        mVrrThrottleNanos{0},
+        mRrThrottleFps{0},
+        mRrThrottleNanos{0},
         mRefreshRateDelayNanos(0),
         mLastRefreshRateAppliedNanos(0),
         mAppliedActiveConfig(0),
@@ -117,6 +132,36 @@
               mDbvThresholdForBlockingZone);
     }
 
+    DisplayType displayType = getDcDisplayType();
+    std::string displayTypeIdentifier;
+    if (displayType == DisplayType::DISPLAY_PRIMARY) {
+        displayTypeIdentifier = "primarydisplay";
+    } else if (displayType == DisplayType::DISPLAY_EXTERNAL) {
+        displayTypeIdentifier = "externaldisplay";
+    }
+#ifdef USES_IDISPLAY_INTF_SEC
+    else if (displayType == DisplayType::DISPLAY_SECONDARY) {
+        displayTypeIdentifier = "secondarydisplay";
+    }
+#endif
+    if (!displayTypeIdentifier.empty()) {
+        char pathBuffer[kMaximumPropertyIdentifierLength];
+        sprintf(pathBuffer, "ro.vendor.%s.vrr.enabled", displayTypeIdentifier.c_str());
+        mVrrSettings.enabled = property_get_bool(pathBuffer, false);
+        if (mVrrSettings.enabled) {
+            sprintf(pathBuffer, "ro.vendor.%s.vrr.expected_present.headsup_ns",
+                    displayTypeIdentifier.c_str());
+            mVrrSettings.notifyExpectedPresentConfig.HeadsUpNs =
+                    property_get_int32(pathBuffer, kDefaultNotifyExpectedPresentConfigHeadsUpNs);
+            sprintf(pathBuffer, "ro.vendor.%s.vrr.expected_present.timeout_ns",
+                    displayTypeIdentifier.c_str());
+            mVrrSettings.notifyExpectedPresentConfig.TimeoutNs =
+                    property_get_int32(pathBuffer, kDefaultNotifyExpectedPresentConfigTimeoutNs);
+            mVrrSettings.configChangeCallback =
+                    std::bind(&ExynosPrimaryDisplay::onConfigChange, this, std::placeholders::_1);
+        }
+    }
+
     // Allow to enable dynamic recomposition after every power on
     // since it will always be disabled for every power off
     // TODO(b/268474771): to enable DR by default if video mode panel is detected
@@ -146,7 +191,7 @@
 
     char value[PROPERTY_VALUE_MAX];
     const char *earlyWakeupNodeBase = early_wakeup_node_0_base;
-    if (getDisplayTypeFromIndex(mIndex) == DisplayType::DISPLAY_SECONDARY &&
+    if (getDcDisplayType() == DisplayType::DISPLAY_SECONDARY &&
         property_get("vendor.display.secondary_early_wakeup_node", value, "") > 0) {
         earlyWakeupNodeBase = value;
     }
@@ -234,10 +279,14 @@
 }
 
 int32_t ExynosPrimaryDisplay::applyPendingConfig() {
-    if (!isConfigSettingEnabled()) return HWC2_ERROR_NONE;
+    if (!isConfigSettingEnabled()) {
+        ALOGI("%s:: config setting is disabled", __func__);
+        return HWC2_ERROR_NONE;
+    }
 
     hwc2_config_t config;
     if (mPendingConfig != UINT_MAX) {
+        ALOGI("%s:: mPendingConfig: %d", __func__, mPendingConfig);
         config = mPendingConfig;
         mPendingConfig = UINT_MAX;
     } else {
@@ -260,15 +309,14 @@
     if (mode.vsyncPeriod == 0)
         return HWC2_ERROR_BAD_CONFIG;
 
-    int refreshRate = round(nsecsPerSec / mode.vsyncPeriod * 0.1f) * 10;
+    int vsyncRate = round(static_cast<float>(nsecsPerSec) / mode.vsyncPeriod);
     char modeStr[PROPERTY_VALUE_MAX];
-    int ret = snprintf(modeStr, sizeof(modeStr), "%dx%d@%d",
-             mode.width, mode.height, refreshRate);
+    int ret = snprintf(modeStr, sizeof(modeStr), "%dx%d@%d:%d",
+             mode.width, mode.height, mode.refreshRate, vsyncRate);
     if (ret <= 0)
         return HWC2_ERROR_BAD_CONFIG;
 
-    ALOGD("%s: mode=%s (%d) vsyncPeriod=%d", __func__, modeStr, config,
-            mode.vsyncPeriod);
+    ALOGD("%s: mode=%s (%d)", __func__, modeStr, config);
     ret = property_set(getPropertyBootModeStr(mDisplayId).c_str(), modeStr);
 
     return !ret ? HWC2_ERROR_NONE : HWC2_ERROR_BAD_CONFIG;
@@ -290,15 +338,31 @@
     }
 
     int width, height;
-    int fps = 0;
+    int fps = 0, vsyncRate = 0;
 
-    ret = sscanf(modeStr, "%dx%d@%d", &width, &height, &fps);
-    if ((ret < 3) || !fps) {
-        ALOGD("%s: unable to find boot config for mode: %s", __func__, modeStr);
+    ret = sscanf(modeStr, "%dx%d@%d:%d", &width, &height, &fps, &vsyncRate);
+    if (ret < 4) {
+        ret = sscanf(modeStr, "%dx%d@%d", &width, &height, &fps);
+        if ((ret < 3) || !fps) {
+            ALOGW("%s: unable to find boot config for mode: %s", __func__, modeStr);
+            return HWC2_ERROR_BAD_CONFIG;
+        }
+        if (lookupDisplayConfigs(width, height, fps, fps, outConfig) != HWC2_ERROR_NONE) {
+            ALOGE("%s: kernel doesn't support mode: %s", __func__, modeStr);
+            return HWC2_ERROR_BAD_CONFIG;
+        }
+        ret = setBootDisplayConfig(*outConfig);
+        if (ret == HWC2_ERROR_NONE)
+            ALOGI("%s: succeeded to replace %s with new format", __func__, modeStr);
+        else
+            ALOGE("%s: failed to replace %s with new format", __func__, modeStr);
+        return ret;
+    }
+    if (!fps || !vsyncRate || (fps > vsyncRate)) {
+        ALOGE("%s: bad boot config: %s", __func__, modeStr);
         return HWC2_ERROR_BAD_CONFIG;
     }
-
-    return lookupDisplayConfigs(width, height, fps, outConfig);
+    return lookupDisplayConfigs(width, height, fps, vsyncRate, outConfig);
 }
 
 int32_t ExynosPrimaryDisplay::setPowerOn() {
@@ -430,27 +494,35 @@
                 mOperationRateManager->getTargetOperationRate());
     }
 
+    int32_t res = HWC2_ERROR_BAD_PARAMETER;
     switch (mode) {
         case HWC2_POWER_MODE_DOZE:
-        case HWC2_POWER_MODE_DOZE_SUSPEND:
+        case HWC2_POWER_MODE_DOZE_SUSPEND: {
             if (mode == HWC2_POWER_MODE_DOZE && mDisplayInterface->needRefreshOnLP()) {
                 ALOGI("Refresh before setting power doze.");
                 mDevice->onRefresh(mDisplayId);
             }
-            return setPowerDoze(static_cast<hwc2_power_mode_t>(mode));
+            res = setPowerDoze(static_cast<hwc2_power_mode_t>(mode));
+            break;
+        }
         case HWC2_POWER_MODE_OFF:
-            setPowerOff();
+            res = setPowerOff();
             break;
         case HWC2_POWER_MODE_ON:
-            setPowerOn();
+            res = setPowerOn();
             break;
         default:
-            return HWC2_ERROR_BAD_PARAMETER;
+            return res;
+    }
+    if (res != HWC2_ERROR_NONE) {
+        return res;
     }
 
     ExynosDisplay::updateRefreshRateHint();
-
-    return HWC2_ERROR_NONE;
+    if (mVariableRefreshRateController) {
+        mVariableRefreshRateController->setPowerMode(mode);
+    }
+    return res;
 }
 
 void ExynosPrimaryDisplay::firstPowerOn() {
@@ -474,19 +546,23 @@
                 __func__, interfaceType);
     mDisplayInterface->init(this);
 
+    if (mVrrSettings.enabled) {
+        mDisplayInterface->setVrrSettings(mVrrSettings);
+    }
+
     mDpuData.init(mMaxWindowNum, mDevice->getSpecialPlaneNum(mDisplayId));
     mLastDpuData.init(mMaxWindowNum, mDevice->getSpecialPlaneNum(mDisplayId));
     ALOGI("window configs size(%zu) rcd configs zie(%zu)", mDpuData.configs.size(),
           mDpuData.rcdConfigs.size());
 }
 
-std::string ExynosPrimaryDisplay::getPanelSysfsPath(const DisplayType &type) {
+std::string ExynosPrimaryDisplay::getPanelSysfsPath(const DisplayType& type) const {
     if ((type < DisplayType::DISPLAY_PRIMARY) || (type >= DisplayType::DISPLAY_MAX)) {
         ALOGE("Invalid display panel type %d", type);
         return {};
     }
 
-    auto iter = panelSysfsPath.find(type);
+    const auto& iter = panelSysfsPath.find(type);
     if (iter == panelSysfsPath.end()) {
         return {};
     }
@@ -578,7 +654,7 @@
 
 void ExynosPrimaryDisplay::enableConfigSetting(bool en) {
     DISPLAY_ATRACE_INT("ConfigSettingDisabled", !en);
-
+    ALOGI("%s:: mConfigSettingDisabled: %d", __func__, !en);
     if (!en) {
         mConfigSettingDisabled = true;
         mConfigSettingDisabledTimestamp = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -588,6 +664,57 @@
     mConfigSettingDisabled = false;
 }
 
+int32_t ExynosPrimaryDisplay::getDisplayConfigs(uint32_t* outNumConfigs,
+                                                hwc2_config_t* outConfigs) {
+    int32_t ret = ExynosDisplay::getDisplayConfigs(outNumConfigs, outConfigs);
+    if (ret == HWC2_ERROR_NONE) {
+        if (mVrrSettings.enabled && mDisplayConfigs.size()) {
+            if (!mVariableRefreshRateController) {
+                mVariableRefreshRateController =
+                        VariableRefreshRateController::CreateInstance(this);
+                std::unordered_map<hwc2_config_t, VrrConfig_t> vrrConfigs;
+                for (const auto& it : mDisplayConfigs) {
+                    if (!it.second.vrrConfig.has_value()) {
+                        return HWC2_ERROR_BAD_CONFIG;
+                    }
+                    vrrConfigs[it.first] = it.second.vrrConfig.value();
+                }
+                mVariableRefreshRateController->setVrrConfigurations(std::move(vrrConfigs));
+                hwc2_config_t activeConfig;
+                if (ExynosDisplay::getActiveConfig(&activeConfig) == HWC2_ERROR_NONE) {
+                    mVariableRefreshRateController->setActiveVrrConfiguration(activeConfig);
+                    mVariableRefreshRateController->setEnable(true);
+                }
+            }
+        }
+    }
+    return ret;
+}
+
+int32_t ExynosPrimaryDisplay::presentDisplay(int32_t* outRetireFence) {
+    auto res = ExynosDisplay::presentDisplay(outRetireFence);
+    // Forward presentDisplay if there is a listener.
+    const auto presentListener = getPresentListener();
+    if (res == HWC2_ERROR_NONE && presentListener) {
+        presentListener->onPresent(*outRetireFence);
+    }
+    return res;
+}
+
+void ExynosPrimaryDisplay::onVsync(int64_t timestamp) {
+    const auto vsyncListener = getVsyncListener();
+    if (vsyncListener) {
+        vsyncListener->onVsync(timestamp, 0);
+    }
+}
+
+int32_t ExynosPrimaryDisplay::notifyExpectedPresent(int64_t timestamp, int32_t frameIntervalNs) {
+    if (mVariableRefreshRateController) {
+        mVariableRefreshRateController->notifyExpectedPresent(timestamp, frameIntervalNs);
+    }
+    return NO_ERROR;
+}
+
 int32_t ExynosPrimaryDisplay::setLhbmDisplayConfigLocked(uint32_t peakRate) {
     auto hwConfig = mDisplayInterface->getActiveModeId();
     auto config = getConfigId(peakRate, mDisplayConfigs[hwConfig].width,
@@ -641,10 +768,11 @@
     {
         ATRACE_NAME("wait_for_power_on");
         std::unique_lock<std::mutex> lock(mPowerModeMutex);
-        if (mPowerModeState != HWC2_POWER_MODE_ON) {
+        if (!mPowerModeState.has_value() || (*mPowerModeState != HWC2_POWER_MODE_ON)) {
             mNotifyPowerOn = true;
             if (!mPowerOnCondition.wait_for(lock, std::chrono::milliseconds(2000), [this]() {
-                    return (mPowerModeState == HWC2_POWER_MODE_ON);
+                    return (mPowerModeState.has_value() &&
+                            (*mPowerModeState == HWC2_POWER_MODE_ON));
                 })) {
                 DISPLAY_LOGW("%s: wait for power mode on timeout !", __func__);
                 return TIMED_OUT;
@@ -786,6 +914,18 @@
     }
     return NO_ERROR;
 enable_err:
+    {
+        // We may receive LHBM request during the power off sequence due to the
+        // race condition between display and sensor. If the failure happens
+        // after requestLhbm(), we will get a wrong LHBM state in the 1st commit
+        // after power on. We should reset the state in this case.
+        std::unique_lock<std::mutex> lock(mPowerModeMutex);
+        if (!mPowerModeState.has_value() || (*mPowerModeState == HWC2_POWER_MODE_OFF)) {
+            DISPLAY_LOGW("%s: request lhbm during power off sequence, reset the state", __func__);
+            mBrightnessController->resetLhbmState();
+        }
+    }
+
     Mutex::Autolock lock(mDisplayMutex);
     restoreLhbmDisplayConfigLocked();
     return ret;
@@ -807,7 +947,7 @@
     setRefreshRateThrottleNanos(std::chrono::duration_cast<std::chrono::nanoseconds>(
                                         std::chrono::milliseconds(delayMs))
                                         .count(),
-                                VrrThrottleRequester::LHBM);
+                                RrThrottleRequester::LHBM);
 }
 
 void ExynosPrimaryDisplay::setEarlyWakeupDisplay() {
@@ -816,20 +956,33 @@
     }
 }
 
-void ExynosPrimaryDisplay::setExpectedPresentTime(uint64_t timestamp) {
-    mExpectedPresentTime.store(timestamp);
+void ExynosPrimaryDisplay::setExpectedPresentTime(uint64_t timestamp, int frameIntervalNs) {
+    mExpectedPresentTimeAndInterval.store(std::make_tuple(timestamp, frameIntervalNs));
+    // Forward presentDisplay if there is a listener.
+    const auto presentListener = getPresentListener();
+    if (presentListener) {
+        presentListener->setExpectedPresentTime(timestamp, frameIntervalNs);
+    }
 }
 
 uint64_t ExynosPrimaryDisplay::getPendingExpectedPresentTime() {
-    if (mExpectedPresentTime.is_dirty()) {
-        return mExpectedPresentTime.get();
+    if (mExpectedPresentTimeAndInterval.is_dirty()) {
+        return std::get<0>(mExpectedPresentTimeAndInterval.get());
+    }
+
+    return 0;
+}
+
+int ExynosPrimaryDisplay::getPendingFrameInterval() {
+    if (mExpectedPresentTimeAndInterval.is_dirty()) {
+        return std::get<1>(mExpectedPresentTimeAndInterval.get());
     }
 
     return 0;
 }
 
 void ExynosPrimaryDisplay::applyExpectedPresentTime() {
-    mExpectedPresentTime.clear_dirty();
+    mExpectedPresentTimeAndInterval.clear_dirty();
 }
 
 int32_t ExynosPrimaryDisplay::setDisplayIdleTimer(const int32_t timeoutMs) {
@@ -865,7 +1018,7 @@
         return HWC2_ERROR_UNSUPPORTED;
     }
 
-    const std::string path = getPanelSysfsPath(getDisplayTypeFromIndex(mIndex)) + "panel_idle";
+    const std::string path = getPanelSysfsPath() + "panel_idle";
     std::ifstream ifs(path);
     if (!ifs.is_open()) {
         ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno));
@@ -881,7 +1034,7 @@
 }
 
 int32_t ExynosPrimaryDisplay::setDisplayIdleTimerEnabled(const bool enabled) {
-    const std::string path = getPanelSysfsPath(getDisplayTypeFromIndex(mIndex)) + "panel_idle";
+    const std::string path = getPanelSysfsPath() + "panel_idle";
     std::ofstream ofs(path);
     if (!ofs.is_open()) {
         ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno));
@@ -915,7 +1068,7 @@
     const int32_t displayIdleDelayMs = std::chrono::duration_cast<std::chrono::milliseconds>(
                                                std::chrono::nanoseconds(mDisplayIdleDelayNanos))
                                                .count();
-    const std::string path = getPanelSysfsPath(DisplayType::DISPLAY_PRIMARY) + "idle_delay_ms";
+    const std::string path = getPanelSysfsPath() + "idle_delay_ms";
     std::ofstream ofs(path);
     if (!ofs.is_open()) {
         ALOGW("%s() unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno));
@@ -934,8 +1087,7 @@
         return;
     }
 
-    const std::string path =
-            getPanelSysfsPath(getDisplayTypeFromIndex(mIndex)) + "panel_need_handle_idle_exit";
+    const std::string path = getPanelSysfsPath() + "panel_need_handle_idle_exit";
     mDisplayNeedHandleIdleExitOfs.open(path, std::ofstream::out);
     if (!mDisplayNeedHandleIdleExitOfs.is_open()) {
         ALOGI("%s() '%s' doesn't exist(%s)", __func__, path.c_str(), strerror(errno));
@@ -972,18 +1124,23 @@
 }
 
 void ExynosPrimaryDisplay::handleDisplayIdleEnter(const uint32_t idleTeRefreshRate) {
-    Mutex::Autolock lock(mDisplayMutex);
-    uint32_t btsRefreshRate = getBtsRefreshRate();
-    if (idleTeRefreshRate <= btsRefreshRate) {
-        return;
+    {
+        Mutex::Autolock lock(mDisplayMutex);
+        uint32_t btsRefreshRate = getBtsRefreshRate();
+        if (idleTeRefreshRate <= btsRefreshRate) {
+            return;
+        }
     }
 
     bool needed = false;
-    for (size_t i = 0; i < mLayers.size(); i++) {
-        if (mLayers[i]->mOtfMPP && mLayers[i]->mM2mMPP == nullptr &&
-            !mLayers[i]->checkBtsCap(idleTeRefreshRate)) {
-            needed = true;
-            break;
+    {
+        Mutex::Autolock lock(mDRMutex);
+        for (size_t i = 0; i < mLayers.size(); i++) {
+            if (mLayers[i]->mOtfMPP && mLayers[i]->mM2mMPP == nullptr &&
+                !mLayers[i]->checkBtsCap(idleTeRefreshRate)) {
+                needed = true;
+                break;
+            }
         }
     }
 
@@ -991,28 +1148,28 @@
 }
 
 int ExynosPrimaryDisplay::setMinIdleRefreshRate(const int targetFps,
-                                                const VrrThrottleRequester requester) {
+                                                const RrThrottleRequester requester) {
     int fps = (targetFps <= 0) ? mDefaultMinIdleRefreshRate : targetFps;
-    if (requester == VrrThrottleRequester::BRIGHTNESS && mUseBlockingZoneForMinIdleRefreshRate) {
+    if (requester == RrThrottleRequester::BRIGHTNESS && mUseBlockingZoneForMinIdleRefreshRate) {
         uint32_t level = mBrightnessController->getBrightnessLevel();
         fps = (level < mDbvThresholdForBlockingZone) ? mMinIdleRefreshRateForBlockingZone
                                                      : mDefaultMinIdleRefreshRate;
     }
 
     std::lock_guard<std::mutex> lock(mMinIdleRefreshRateMutex);
-    if (fps == mVrrThrottleFps[toUnderlying(requester)]) return NO_ERROR;
+    if (fps == mRrThrottleFps[toUnderlying(requester)]) return NO_ERROR;
 
     ALOGD("%s requester %u, fps %d", __func__, toUnderlying(requester), fps);
-    mVrrThrottleFps[toUnderlying(requester)] = fps;
+    mRrThrottleFps[toUnderlying(requester)] = fps;
     int maxMinIdleFps = 0;
-    for (uint32_t i = 0; i < toUnderlying(VrrThrottleRequester::MAX); i++) {
-        if (mVrrThrottleFps[i] > maxMinIdleFps) {
-            maxMinIdleFps = mVrrThrottleFps[i];
+    for (uint32_t i = 0; i < toUnderlying(RrThrottleRequester::MAX); i++) {
+        if (mRrThrottleFps[i] > maxMinIdleFps) {
+            maxMinIdleFps = mRrThrottleFps[i];
         }
     }
     if (maxMinIdleFps == mMinIdleRefreshRate) return NO_ERROR;
 
-    const std::string path = getPanelSysfsPath(getDisplayTypeFromIndex(mIndex)) + "min_vrefresh";
+    const std::string path = getPanelSysfsPath() + "min_vrefresh";
     std::ofstream ofs(path);
     if (!ofs.is_open()) {
         ALOGW("%s Unable to open node '%s', error = %s", __func__, path.c_str(), strerror(errno));
@@ -1028,7 +1185,7 @@
 }
 
 int ExynosPrimaryDisplay::setRefreshRateThrottleNanos(const int64_t delayNanos,
-                                                      const VrrThrottleRequester requester) {
+                                                      const RrThrottleRequester requester) {
     ATRACE_CALL();
     if (delayNanos < 0) {
         ALOGW("%s() set invalid delay(%" PRId64 ")", __func__, delayNanos);
@@ -1036,15 +1193,15 @@
     }
 
     std::lock_guard<std::mutex> lock(mIdleRefreshRateThrottleMutex);
-    if (delayNanos == mVrrThrottleNanos[toUnderlying(requester)]) return NO_ERROR;
+    if (delayNanos == mRrThrottleNanos[toUnderlying(requester)]) return NO_ERROR;
 
     ALOGI("%s() requester(%u) set delay to %" PRId64 "ns", __func__, toUnderlying(requester),
           delayNanos);
-    mVrrThrottleNanos[toUnderlying(requester)] = delayNanos;
+    mRrThrottleNanos[toUnderlying(requester)] = delayNanos;
     int64_t maxDelayNanos = 0;
-    for (uint32_t i = 0; i < toUnderlying(VrrThrottleRequester::MAX); i++) {
-        if (mVrrThrottleNanos[i] > maxDelayNanos) {
-            maxDelayNanos = mVrrThrottleNanos[i];
+    for (uint32_t i = 0; i < toUnderlying(RrThrottleRequester::MAX); i++) {
+        if (mRrThrottleNanos[i] > maxDelayNanos) {
+            maxDelayNanos = mRrThrottleNanos[i];
         }
     }
 
@@ -1054,7 +1211,7 @@
     }
 
     mRefreshRateDelayNanos = maxDelayNanos;
-    return setDisplayIdleDelayNanos(mRefreshRateDelayNanos, DispIdleTimerRequester::VRR_THROTTLE);
+    return setDisplayIdleDelayNanos(mRefreshRateDelayNanos, DispIdleTimerRequester::RR_THROTTLE);
 }
 
 void ExynosPrimaryDisplay::dump(String8 &result) {
@@ -1074,13 +1231,13 @@
         result.appendFormat("\n");
     }
 
-    for (uint32_t i = 0; i < toUnderlying(VrrThrottleRequester::MAX); i++) {
-        result.appendFormat("\t[%u] vote to %d hz\n", i, mVrrThrottleFps[i]);
+    for (uint32_t i = 0; i < toUnderlying(RrThrottleRequester::MAX); i++) {
+        result.appendFormat("\t[%u] vote to %d hz\n", i, mRrThrottleFps[i]);
     }
 
     result.appendFormat("Refresh rate delay: %" PRId64 " ns\n", mRefreshRateDelayNanos);
-    for (uint32_t i = 0; i < toUnderlying(VrrThrottleRequester::MAX); i++) {
-        result.appendFormat("\t[%u] vote to %" PRId64 " ns\n", i, mVrrThrottleNanos[i]);
+    for (uint32_t i = 0; i < toUnderlying(RrThrottleRequester::MAX); i++) {
+        result.appendFormat("\t[%u] vote to %" PRId64 " ns\n", i, mRrThrottleNanos[i]);
     }
     result.appendFormat("\n");
 }
@@ -1154,8 +1311,8 @@
     mAppliedActiveConfig = newConfig;
 }
 
-void ExynosPrimaryDisplay::checkBtsReassignResource(const uint32_t vsyncPeriod,
-                                                    const uint32_t btsVsyncPeriod) {
+void ExynosPrimaryDisplay::checkBtsReassignResource(const int32_t vsyncPeriod,
+                                                    const int32_t btsVsyncPeriod) {
     ATRACE_CALL();
     uint32_t refreshRate = static_cast<uint32_t>(round(nsecsPerSec / vsyncPeriod * 0.1f) * 10);
 
@@ -1195,3 +1352,23 @@
     mBrightnessController->processDimBrightness(enabled);
     return NO_ERROR;
 }
+
+PresentListener* ExynosPrimaryDisplay::getPresentListener() {
+    if (mVariableRefreshRateController) {
+        return mVariableRefreshRateController.get();
+    }
+    return nullptr;
+}
+
+VsyncListener* ExynosPrimaryDisplay::getVsyncListener() {
+    if (mVariableRefreshRateController) {
+        return mVariableRefreshRateController.get();
+    }
+    return nullptr;
+}
+
+void ExynosPrimaryDisplay::onConfigChange(int configId) {
+    if (mVariableRefreshRateController) {
+        return mVariableRefreshRateController->setActiveVrrConfiguration(configId);
+    }
+}
diff --git a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.h b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.h
index f308d7d..f3b412d 100644
--- a/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.h
+++ b/libhwc2.1/libmaindisplay/ExynosPrimaryDisplay.h
@@ -19,7 +19,12 @@
 #include <map>
 
 #include "../libdevice/ExynosDisplay.h"
+#include "../libvrr/VariableRefreshRateController.h"
+#include "../libvrr/VariableRefreshRateInterface.h"
 
+using android::hardware::graphics::composer::PresentListener;
+using android::hardware::graphics::composer::VariableRefreshRateController;
+using android::hardware::graphics::composer::VsyncListener;
 using namespace displaycolor;
 
 class ExynosPrimaryDisplay : public ExynosDisplay {
@@ -40,8 +45,9 @@
 
         virtual bool getLhbmState();
         virtual void setEarlyWakeupDisplay();
-        virtual void setExpectedPresentTime(uint64_t timestamp);
-        virtual uint64_t getPendingExpectedPresentTime();
+        virtual void setExpectedPresentTime(uint64_t timestamp, int frameIntervalNs) override;
+        virtual uint64_t getPendingExpectedPresentTime() override;
+        virtual int getPendingFrameInterval() override;
         virtual void applyExpectedPresentTime();
         virtual int32_t setDisplayIdleTimer(const int32_t timeoutMs) override;
         virtual void handleDisplayIdleEnter(const uint32_t idleTeRefreshRate) override;
@@ -50,17 +56,17 @@
         virtual int32_t doDisplayConfigInternal(hwc2_config_t config) override;
 
         virtual int setMinIdleRefreshRate(const int fps,
-                                          const VrrThrottleRequester requester) override;
+                                          const RrThrottleRequester requester) override;
         virtual int setRefreshRateThrottleNanos(const int64_t delayNs,
-                                                const VrrThrottleRequester requester) override;
+                                                const RrThrottleRequester requester) override;
         virtual bool isDbmSupported() override;
         virtual int32_t setDbmState(bool enabled) override;
 
         virtual void dump(String8& result) override;
         virtual void updateAppliedActiveConfig(const hwc2_config_t newConfig,
                                                const int64_t ts) override;
-        virtual void checkBtsReassignResource(const uint32_t vsyncPeriod,
-                                              const uint32_t btsVsyncPeriod) override;
+        virtual void checkBtsReassignResource(const int32_t vsyncPeriod,
+                                              const int32_t btsVsyncPeriod) override;
 
         virtual int32_t setBootDisplayConfig(int32_t config) override;
         virtual int32_t clearBootDisplayConfig() override;
@@ -68,6 +74,14 @@
         virtual bool isConfigSettingEnabled() override;
         virtual void enableConfigSetting(bool en) override;
 
+        virtual int32_t getDisplayConfigs(uint32_t* outNumConfigs,
+                                          hwc2_config_t* outConfigs) override;
+        virtual int32_t presentDisplay(int32_t* outRetireFence) override;
+
+        virtual void onVsync(int64_t timestamp) override;
+
+        int32_t notifyExpectedPresent(int64_t timestamp, int32_t frameIntervalNs) override;
+
     protected:
         /* setPowerMode(int32_t mode)
          * Descriptor: HWC2_FUNCTION_SET_POWER_MODE
@@ -81,15 +95,14 @@
         virtual bool getHDRException(ExynosLayer* __unused layer);
         virtual int32_t setActiveConfigInternal(hwc2_config_t config, bool force) override;
         virtual int32_t getActiveConfigInternal(hwc2_config_t* outConfig) override;
-        DisplayType getDisplayTypeFromIndex(uint32_t index) {
-            return (index >= DisplayType::DISPLAY_MAX) ? DisplayType::DISPLAY_PRIMARY
-                                                       : DisplayType(mIndex);
-        };
 
     public:
         // Prepare multi resolution
         ResolutionInfo mResolutionInfo;
-        std::string getPanelSysfsPath(const displaycolor::DisplayType& type);
+        std::string getPanelSysfsPath() const override {
+            return getPanelSysfsPath(getDcDisplayType());
+        }
+        std::string getPanelSysfsPath(const displaycolor::DisplayType& type) const;
 
         uint32_t mRcdId = -1;
 
@@ -120,6 +133,8 @@
         int32_t setLhbmDisplayConfigLocked(uint32_t peakRate);
         void restoreLhbmDisplayConfigLocked();
 
+        void onConfigChange(int configId);
+
         // LHBM
         FILE* mLhbmFd;
         std::atomic<bool> mLhbmOn;
@@ -141,7 +156,7 @@
         static constexpr const char* kWakeupDispFilePath =
                 "/sys/devices/platform/1c300000.drmdecon/early_wakeup";
 
-        CtrlValue<uint64_t> mExpectedPresentTime;
+        CtrlValue<std::tuple<int64_t, int>> mExpectedPresentTimeAndInterval;
 
         void calculateTimeline(hwc2_config_t config,
                                hwc_vsync_period_change_constraints_t* vsyncPeriodChangeConstraints,
@@ -155,11 +170,11 @@
         uint32_t mDbvThresholdForBlockingZone;
         bool mUseBlockingZoneForMinIdleRefreshRate;
         int mMinIdleRefreshRate;
-        int mVrrThrottleFps[toUnderlying(VrrThrottleRequester::MAX)];
+        int mRrThrottleFps[toUnderlying(RrThrottleRequester::MAX)];
         std::mutex mMinIdleRefreshRateMutex;
 
         std::mutex mIdleRefreshRateThrottleMutex;
-        int64_t mVrrThrottleNanos[toUnderlying(VrrThrottleRequester::MAX)];
+        int64_t mRrThrottleNanos[toUnderlying(RrThrottleRequester::MAX)];
         int64_t mRefreshRateDelayNanos;
         int64_t mLastRefreshRateAppliedNanos;
         hwc2_config_t mAppliedActiveConfig;
@@ -170,6 +185,13 @@
         std::ofstream mDisplayNeedHandleIdleExitOfs;
         int64_t mDisplayIdleDelayNanos;
         bool mDisplayNeedHandleIdleExit;
+
+        // Function and variables related to Vrr.
+        PresentListener* getPresentListener();
+        VsyncListener* getVsyncListener();
+
+        VrrSettings_t mVrrSettings;
+        std::shared_ptr<VariableRefreshRateController> mVariableRefreshRateController;
 };
 
 #endif
diff --git a/libhwc2.1/libresource/ExynosMPP.cpp b/libhwc2.1/libresource/ExynosMPP.cpp
index 406996a..622e46a 100644
--- a/libhwc2.1/libresource/ExynosMPP.cpp
+++ b/libhwc2.1/libresource/ExynosMPP.cpp
@@ -1669,12 +1669,10 @@
 }
 
 /**
- * @param src
  * @param dst
- * @return int32_t releaseFenceFd of src buffer
+ * @return int32_t 0 on success, or a negative error code on failure.
  */
-int32_t ExynosMPP::doPostProcessing(struct exynos_image &src, struct exynos_image &dst)
-{
+int32_t ExynosMPP::doPostProcessing(struct exynos_image& dst) {
     ATRACE_CALL();
     MPP_LOGD(eDebugMPP, "total assigned sources (%zu)++++++++", mAssignedSources.size());
 
diff --git a/libhwc2.1/libresource/ExynosMPP.h b/libhwc2.1/libresource/ExynosMPP.h
index 7ea3fbe..2ea51c9 100644
--- a/libhwc2.1/libresource/ExynosMPP.h
+++ b/libhwc2.1/libresource/ExynosMPP.h
@@ -583,8 +583,7 @@
     int32_t allocOutBuf(uint32_t w, uint32_t h, uint32_t format, uint64_t usage, uint32_t index);
     int32_t setOutBuf(buffer_handle_t outbuf, int32_t fence);
     int32_t freeOutBuf(exynos_mpp_img_info dst);
-    int32_t doPostProcessing(struct exynos_image &src, struct exynos_image &dst);
-    int32_t doPostProcessing(uint32_t totalImags, uint32_t imageIndex, struct exynos_image &src, struct exynos_image &dst);
+    int32_t doPostProcessing(struct exynos_image& dst);
     int32_t setupRestriction();
     int32_t getSrcReleaseFence(uint32_t srcIndex);
     int32_t resetSrcReleaseFence();
diff --git a/libhwc2.1/libvrr/RingBuffer.h b/libhwc2.1/libvrr/RingBuffer.h
new file mode 100644
index 0000000..e899cbc
--- /dev/null
+++ b/libhwc2.1/libvrr/RingBuffer.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 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 <stddef.h>
+#include <array>
+
+namespace android::hardware::graphics::composer {
+
+template <class T, size_t SIZE>
+class RingBuffer {
+    // RingBuffer(const RingBuffer&) = delete;
+    // void operator=(const RingBuffer&) = delete;
+
+public:
+    RingBuffer() = default;
+    ~RingBuffer() = default;
+
+    constexpr size_t capacity() const { return SIZE; }
+
+    size_t size() const { return mCount; }
+
+    T& next() {
+        mHead = static_cast<size_t>(mHead + 1) % SIZE;
+        if (mCount < SIZE) {
+            mCount++;
+        }
+        return mBuffer[static_cast<size_t>(mHead)];
+    }
+
+    T& operator[](size_t index) {
+        return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+    }
+
+    const T& operator[](size_t index) const {
+        return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+    }
+
+    void clear() {
+        mCount = 0;
+        mHead = -1;
+    }
+
+private:
+    std::array<T, SIZE> mBuffer;
+    int mHead = -1;
+    size_t mCount = 0;
+};
+
+} // namespace android::hardware::graphics::composer
diff --git a/libhwc2.1/libvrr/VariableRefreshRateController.cpp b/libhwc2.1/libvrr/VariableRefreshRateController.cpp
new file mode 100644
index 0000000..a93c3b9
--- /dev/null
+++ b/libhwc2.1/libvrr/VariableRefreshRateController.cpp
@@ -0,0 +1,583 @@
+/*
+ * 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 "VariableRefreshRateController.h"
+
+#include <android-base/logging.h>
+#include <sync/sync.h>
+#include <utils/Trace.h>
+
+#include "ExynosHWCHelper.h"
+#include "drmmode.h"
+
+#include <chrono>
+#include <tuple>
+
+namespace android::hardware::graphics::composer {
+
+const std::string VariableRefreshRateController::kFrameInsertionNodeName = "refresh_ctrl";
+
+namespace {
+
+int64_t getNowNs() {
+    const auto t = std::chrono::high_resolution_clock::now();
+    return std::chrono::duration_cast<std::chrono::nanoseconds>(t.time_since_epoch()).count();
+}
+
+} // namespace
+
+auto VariableRefreshRateController::CreateInstance(ExynosDisplay* display)
+        -> std::shared_ptr<VariableRefreshRateController> {
+    if (!display) {
+        LOG(ERROR)
+                << "VrrController: create VariableRefreshRateController without display handler.";
+        return nullptr;
+    }
+    auto controller = std::shared_ptr<VariableRefreshRateController>(
+            new VariableRefreshRateController(display));
+    std::thread thread = std::thread(&VariableRefreshRateController::threadBody, controller.get());
+    std::string threadName = "VrrCtrl_";
+    threadName += display->mIndex == 0 ? "Primary" : "Second";
+    int error = pthread_setname_np(thread.native_handle(), threadName.c_str());
+    if (error != 0) {
+        LOG(WARNING) << "VrrController: Unable to set thread name, error = " << strerror(error);
+    }
+    thread.detach();
+    return controller;
+}
+
+VariableRefreshRateController::VariableRefreshRateController(ExynosDisplay* display)
+      : mDisplay(display) {
+    mState = VrrControllerState::kDisable;
+    std::string displayFileNodePath = mDisplay->getPanelSysfsPath();
+    if (displayFileNodePath.empty()) {
+        LOG(WARNING) << "VrrController: Cannot find file node of display: "
+                     << mDisplay->mDisplayName;
+    } else {
+        mFileNodeWritter = std::make_unique<FileNodeWriter>(displayFileNodePath);
+    }
+}
+
+VariableRefreshRateController::~VariableRefreshRateController() {
+    stopThread(true);
+
+    const std::lock_guard<std::mutex> lock(mMutex);
+    if (mLastPresentFence.has_value()) {
+        if (close(mLastPresentFence.value())) {
+            LOG(ERROR) << "VrrController: close fence file failed, errno = " << errno;
+        }
+        mLastPresentFence = std::nullopt;
+    }
+};
+
+int VariableRefreshRateController::notifyExpectedPresent(int64_t timestamp,
+                                                         int32_t frameIntervalNs) {
+    ATRACE_CALL();
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        mRecord.mNextExpectedPresentTime = {mVrrActiveConfig, timestamp, frameIntervalNs};
+        // Post kNotifyExpectedPresentConfig event.
+        postEvent(VrrControllerEventType::kNotifyExpectedPresentConfig, getNowNs());
+    }
+    mCondition.notify_all();
+    return 0;
+}
+
+void VariableRefreshRateController::reset() {
+    ATRACE_CALL();
+
+    const std::lock_guard<std::mutex> lock(mMutex);
+    mEventQueue = std::priority_queue<VrrControllerEvent>();
+    mRecord.clear();
+    dropEventLocked();
+    if (mLastPresentFence.has_value()) {
+        if (close(mLastPresentFence.value())) {
+            LOG(ERROR) << "VrrController: close fence file failed, errno = " << errno;
+        }
+        mLastPresentFence = std::nullopt;
+    }
+}
+
+void VariableRefreshRateController::setActiveVrrConfiguration(hwc2_config_t config) {
+    LOG(INFO) << "VrrController: Set active Vrr configuration = " << config
+              << ", power mode = " << mPowerMode;
+    ATRACE_CALL();
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (mVrrConfigs.count(config) == 0) {
+            LOG(ERROR) << "VrrController: Set an undefined active configuration";
+            return;
+        }
+        mVrrActiveConfig = config;
+        if (mState == VrrControllerState::kDisable) {
+            return;
+        }
+        mState = VrrControllerState::kRendering;
+        dropEventLocked(kRenderingTimeout);
+
+        const auto& vrrConfig = mVrrConfigs[mVrrActiveConfig];
+        postEvent(VrrControllerEventType::kRenderingTimeout,
+                  getNowNs() + vrrConfig.notifyExpectedPresentConfig.TimeoutNs);
+    }
+    mCondition.notify_all();
+}
+
+void VariableRefreshRateController::setEnable(bool isEnabled) {
+    ATRACE_CALL();
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (mEnabled == isEnabled) {
+            return;
+        }
+        mEnabled = isEnabled;
+        if (mEnabled == false) {
+            dropEventLocked();
+        }
+    }
+    mCondition.notify_all();
+}
+
+void VariableRefreshRateController::setPowerMode(int32_t powerMode) {
+    ATRACE_CALL();
+    LOG(INFO) << "VrrController: Set power mode to " << powerMode;
+
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (mPowerMode == powerMode) {
+            return;
+        }
+        switch (powerMode) {
+            case HWC_POWER_MODE_OFF:
+            case HWC_POWER_MODE_DOZE:
+            case HWC_POWER_MODE_DOZE_SUSPEND: {
+                mState = VrrControllerState::kDisable;
+                dropEventLocked();
+                break;
+            }
+            case HWC_POWER_MODE_NORMAL: {
+                // We should transition from either HWC_POWER_MODE_OFF, HWC_POWER_MODE_DOZE, or
+                // HWC_POWER_MODE_DOZE_SUSPEND. At this point, there should be no pending events
+                // posted.
+                if (!mEventQueue.empty()) {
+                    LOG(WARNING) << "VrrController: there should be no pending event when resume "
+                                    "from power mode = "
+                                 << mPowerMode << " to power mode = " << powerMode;
+                    LOG(INFO) << dumpEventQueueLocked();
+                }
+                mState = VrrControllerState::kRendering;
+                const auto& vrrConfig = mVrrConfigs[mVrrActiveConfig];
+                postEvent(VrrControllerEventType::kRenderingTimeout,
+                          getNowNs() + vrrConfig.notifyExpectedPresentConfig.TimeoutNs);
+                break;
+            }
+            default: {
+                LOG(ERROR) << "VrrController: Unknown power mode = " << powerMode;
+                return;
+            }
+        }
+        mPowerMode = powerMode;
+    }
+    mCondition.notify_all();
+}
+
+void VariableRefreshRateController::setVrrConfigurations(
+        std::unordered_map<hwc2_config_t, VrrConfig_t> configs) {
+    ATRACE_CALL();
+
+    for (const auto& it : configs) {
+        LOG(INFO) << "VrrController: set Vrr configuration id = " << it.first;
+    }
+
+    const std::lock_guard<std::mutex> lock(mMutex);
+    mVrrConfigs = std::move(configs);
+}
+
+void VariableRefreshRateController::stopThread(bool exit) {
+    ATRACE_CALL();
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        mThreadExit = exit;
+        mEnabled = false;
+        mState = VrrControllerState::kDisable;
+    }
+    mCondition.notify_all();
+}
+
+void VariableRefreshRateController::onPresent(int fence) {
+    if (fence < 0) {
+        return;
+    }
+    ATRACE_CALL();
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (mState == VrrControllerState::kDisable) {
+            return;
+        }
+        if (!mRecord.mPendingCurrentPresentTime.has_value()) {
+            LOG(WARNING) << "VrrController: VrrController: Present without expected present time "
+                            "information";
+            return;
+        } else {
+            mRecord.mPresentHistory.next() = mRecord.mPendingCurrentPresentTime.value();
+            mRecord.mPendingCurrentPresentTime = std::nullopt;
+        }
+        if (mState == VrrControllerState::kHibernate) {
+            LOG(WARNING) << "VrrController: Present during hibernation without prior notification "
+                            "via notifyExpectedPresent.";
+            mState = VrrControllerState::kRendering;
+            dropEventLocked(kHibernateTimeout);
+        }
+    }
+
+    // Prior to pushing the most recent fence update, verify the release timestamps of all preceding
+    // fences.
+    // TODO(b/309873055): delegate the task of executing updateVsyncHistory to the Vrr controller's
+    // loop thread in order to reduce the workload of calling thread.
+    updateVsyncHistory();
+    int dupFence = dup(fence);
+    if (dupFence < 0) {
+        LOG(ERROR) << "VrrController: duplicate fence file failed." << errno;
+    }
+
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (mLastPresentFence.has_value()) {
+            LOG(WARNING) << "VrrController: last present fence remains open.";
+        }
+        mLastPresentFence = dupFence;
+        // Drop the out of date timeout.
+        dropEventLocked(kRenderingTimeout);
+        cancelFrameInsertionLocked();
+        // Post next rendering timeout.
+        postEvent(VrrControllerEventType::kRenderingTimeout,
+                  getNowNs() + mVrrConfigs[mVrrActiveConfig].notifyExpectedPresentConfig.TimeoutNs);
+        // Post next frmae insertion event.
+        mPendingFramesToInsert = kDefaultNumFramesToInsert;
+        postEvent(VrrControllerEventType::kNextFrameInsertion,
+                  getNowNs() + kDefaultFrameInsertionTimer);
+    }
+    mCondition.notify_all();
+}
+
+void VariableRefreshRateController::setExpectedPresentTime(int64_t timestampNanos,
+                                                           int frameIntervalNs) {
+    ATRACE_CALL();
+
+    const std::lock_guard<std::mutex> lock(mMutex);
+    mRecord.mPendingCurrentPresentTime = {mVrrActiveConfig, timestampNanos, frameIntervalNs};
+}
+
+void VariableRefreshRateController::onVsync(int64_t timestampNanos,
+                                            int32_t __unused vsyncPeriodNanos) {
+    const std::lock_guard<std::mutex> lock(mMutex);
+    mRecord.mVsyncHistory
+            .next() = {.mType = VariableRefreshRateController::VsyncEvent::Type::kVblank,
+                       .mTime = timestampNanos};
+}
+
+void VariableRefreshRateController::cancelFrameInsertionLocked() {
+    dropEventLocked(kNextFrameInsertion);
+    mPendingFramesToInsert = 0;
+}
+
+int VariableRefreshRateController::doFrameInsertionLocked() {
+    ATRACE_CALL();
+
+    if (mState == VrrControllerState::kDisable) {
+        cancelFrameInsertionLocked();
+        return 0;
+    }
+    if (mPendingFramesToInsert <= 0) {
+        LOG(ERROR) << "VrrController: the number of frames to be inserted should >= 1, but is "
+                   << mPendingFramesToInsert << " now.";
+        return -1;
+    }
+    bool ret = mFileNodeWritter->WriteCommandString(kFrameInsertionNodeName, PANEL_REFRESH_CTRL_FI);
+    if (!ret) {
+        LOG(ERROR) << "VrrController: write command to file node failed. " << getStateName(mState)
+                   << " " << mPowerMode;
+        return -1;
+    }
+    if (--mPendingFramesToInsert > 0) {
+        postEvent(VrrControllerEventType::kNextFrameInsertion,
+                  getNowNs() + mVrrConfigs[mVrrActiveConfig].minFrameIntervalNs);
+    }
+    return 0;
+}
+
+int VariableRefreshRateController::doFrameInsertionLocked(int frames) {
+    mPendingFramesToInsert = frames;
+    return doFrameInsertionLocked();
+}
+
+void VariableRefreshRateController::dropEventLocked() {
+    mEventQueue = std::priority_queue<VrrControllerEvent>();
+    mPendingFramesToInsert = 0;
+}
+
+void VariableRefreshRateController::dropEventLocked(VrrControllerEventType event_type) {
+    std::priority_queue<VrrControllerEvent> q;
+    while (!mEventQueue.empty()) {
+        const auto& it = mEventQueue.top();
+        if (it.mEventType != event_type) {
+            q.push(it);
+        }
+        mEventQueue.pop();
+    }
+    mEventQueue = std::move(q);
+}
+
+std::string VariableRefreshRateController::dumpEventQueueLocked() {
+    std::string content;
+    if (mEventQueue.empty()) {
+        return content;
+    }
+
+    std::priority_queue<VrrControllerEvent> q;
+    while (!mEventQueue.empty()) {
+        const auto& it = mEventQueue.top();
+        content += "VrrController: event = ";
+        content += it.toString();
+        content += "\n";
+        q.push(it);
+        mEventQueue.pop();
+    }
+    mEventQueue = std::move(q);
+    return content;
+}
+
+int64_t VariableRefreshRateController::getLastFenceSignalTimeUnlocked(int fd) {
+    if (fd == -1) {
+        return SIGNAL_TIME_INVALID;
+    }
+    struct sync_file_info* finfo = sync_file_info(fd);
+    if (finfo == nullptr) {
+        LOG(ERROR) << "VrrController: sync_file_info returned NULL for fd " << fd;
+        return SIGNAL_TIME_INVALID;
+    }
+    if (finfo->status != 1) {
+        const auto status = finfo->status;
+        if (status < 0) {
+            LOG(ERROR) << "VrrController: sync_file_info contains an error: " << status;
+        }
+        sync_file_info_free(finfo);
+        return status < 0 ? SIGNAL_TIME_INVALID : SIGNAL_TIME_PENDING;
+    }
+    uint64_t timestamp = 0;
+    struct sync_fence_info* pinfo = sync_get_fence_info(finfo);
+    if (finfo->num_fences != 1) {
+        LOG(WARNING) << "VrrController:: there is more than one fence in the file descriptor = "
+                     << fd;
+    }
+    for (size_t i = 0; i < finfo->num_fences; i++) {
+        if (pinfo[i].timestamp_ns > timestamp) {
+            timestamp = pinfo[i].timestamp_ns;
+        }
+    }
+    sync_file_info_free(finfo);
+    return timestamp;
+}
+
+int64_t VariableRefreshRateController::getNextEventTimeLocked() const {
+    if (mEventQueue.empty()) {
+        LOG(WARNING) << "VrrController: event queue should NOT be empty.";
+        return -1;
+    }
+    const auto& event = mEventQueue.top();
+    return event.mWhenNs;
+}
+
+std::string VariableRefreshRateController::getStateName(VrrControllerState state) const {
+    switch (state) {
+        case VrrControllerState::kDisable:
+            return "Disable";
+        case VrrControllerState::kRendering:
+            return "Rendering";
+        case VrrControllerState::kHibernate:
+            return "Hibernate";
+        default:
+            return "Unknown";
+    }
+}
+
+void VariableRefreshRateController::handleCadenceChange() {
+    ATRACE_CALL();
+    if (!mRecord.mNextExpectedPresentTime.has_value()) {
+        LOG(WARNING) << "VrrController: cadence change occurs without the expected present timing "
+                        "information.";
+        return;
+    }
+    // TODO(b/305311056): handle frame rate change.
+    mRecord.mNextExpectedPresentTime = std::nullopt;
+}
+
+void VariableRefreshRateController::handleResume() {
+    ATRACE_CALL();
+    if (!mRecord.mNextExpectedPresentTime.has_value()) {
+        LOG(WARNING)
+                << "VrrController: resume occurs without the expected present timing information.";
+        return;
+    }
+    // TODO(b/305311281): handle panel resume.
+    mRecord.mNextExpectedPresentTime = std::nullopt;
+}
+
+void VariableRefreshRateController::handleHibernate() {
+    ATRACE_CALL();
+    // TODO(b/305311206): handle entering panel hibernate.
+    postEvent(VrrControllerEventType::kHibernateTimeout,
+              getNowNs() + kDefaultWakeUpTimeInPowerSaving);
+}
+
+void VariableRefreshRateController::handleStayHibernate() {
+    ATRACE_CALL();
+    // TODO(b/305311698): handle keeping panel hibernate.
+    postEvent(VrrControllerEventType::kHibernateTimeout,
+              getNowNs() + kDefaultWakeUpTimeInPowerSaving);
+}
+
+void VariableRefreshRateController::threadBody() {
+    struct sched_param param = {.sched_priority = 2};
+    if (sched_setscheduler(0, SCHED_FIFO, &param) != 0) {
+        LOG(ERROR) << "VrrController: fail to set scheduler to SCHED_FIFO.";
+        return;
+    }
+    for (;;) {
+        bool stateChanged = false;
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            if (mThreadExit) break;
+            if (!mEnabled) mCondition.wait(lock);
+            if (!mEnabled) continue;
+
+            if (mEventQueue.empty()) {
+                mCondition.wait(lock);
+            }
+            int64_t whenNs = getNextEventTimeLocked();
+            int64_t nowNs = getNowNs();
+            if (whenNs > nowNs) {
+                int64_t delayNs = whenNs - nowNs;
+                auto res = mCondition.wait_for(lock, std::chrono::nanoseconds(delayNs));
+                if (res != std::cv_status::timeout) {
+                    continue;
+                }
+            }
+            if (mEventQueue.empty()) {
+                LOG(ERROR) << "VrrController: event queue should NOT be empty.";
+                break;
+            }
+            const auto event = mEventQueue.top();
+            mEventQueue.pop();
+
+            if (mState == VrrControllerState::kRendering) {
+                if (event.mEventType == VrrControllerEventType::kHibernateTimeout) {
+                    LOG(ERROR) << "VrrController: receiving a hibernate timeout event while in the "
+                                  "rendering state.";
+                }
+                switch (event.mEventType) {
+                    case VrrControllerEventType::kRenderingTimeout: {
+                        handleHibernate();
+                        mState = VrrControllerState::kHibernate;
+                        stateChanged = true;
+                        break;
+                    }
+                    case VrrControllerEventType::kNotifyExpectedPresentConfig: {
+                        handleCadenceChange();
+                        break;
+                    }
+                    case VrrControllerEventType::kNextFrameInsertion: {
+                        doFrameInsertionLocked();
+                        break;
+                    }
+                    default: {
+                        break;
+                    }
+                }
+            } else {
+                if (event.mEventType == VrrControllerEventType::kRenderingTimeout) {
+                    LOG(ERROR) << "VrrController: receiving a rendering timeout event while in the "
+                                  "hibernate state.";
+                }
+                if (mState != VrrControllerState::kHibernate) {
+                    LOG(ERROR) << "VrrController: expecting to be in hibernate, but instead in "
+                                  "state = "
+                               << getStateName(mState);
+                }
+                switch (event.mEventType) {
+                    case VrrControllerEventType::kHibernateTimeout: {
+                        handleStayHibernate();
+                        break;
+                    }
+                    case VrrControllerEventType::kNotifyExpectedPresentConfig: {
+                        handleResume();
+                        mState = VrrControllerState::kRendering;
+                        stateChanged = true;
+                        break;
+                    }
+                    default: {
+                        break;
+                    }
+                }
+            }
+        }
+        // TODO(b/309873055): implement a handler to serialize all outer function calls to the same
+        // thread owned by the VRR controller.
+        if (stateChanged) {
+            updateVsyncHistory();
+        }
+    }
+}
+
+void VariableRefreshRateController::postEvent(VrrControllerEventType type, int64_t when) {
+    VrrControllerEvent event;
+    event.mEventType = type;
+    event.mWhenNs = when;
+    mEventQueue.emplace(event);
+}
+
+void VariableRefreshRateController::updateVsyncHistory() {
+    int fence = -1;
+
+    {
+        const std::lock_guard<std::mutex> lock(mMutex);
+        if (!mLastPresentFence.has_value()) {
+            return;
+        }
+        fence = mLastPresentFence.value();
+        mLastPresentFence = std::nullopt;
+    }
+
+    // Execute the following logic unlocked to enhance performance.
+    int64_t lastSignalTime = getLastFenceSignalTimeUnlocked(fence);
+    if (close(fence)) {
+        LOG(ERROR) << "VrrController: close fence file failed, errno = " << errno;
+        return;
+    } else if (lastSignalTime == SIGNAL_TIME_PENDING || lastSignalTime == SIGNAL_TIME_INVALID) {
+        return;
+    }
+
+    {
+        // Acquire the mutex again to store the vsync record.
+        const std::lock_guard<std::mutex> lock(mMutex);
+        mRecord.mVsyncHistory
+                .next() = {.mType = VariableRefreshRateController::VsyncEvent::Type::kReleaseFence,
+                           .mTime = lastSignalTime};
+    }
+}
+
+} // namespace android::hardware::graphics::composer
diff --git a/libhwc2.1/libvrr/VariableRefreshRateController.h b/libhwc2.1/libvrr/VariableRefreshRateController.h
new file mode 100644
index 0000000..1a20109
--- /dev/null
+++ b/libhwc2.1/libvrr/VariableRefreshRateController.h
@@ -0,0 +1,207 @@
+/*
+ * 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 <utils/Mutex.h>
+#include <condition_variable>
+#include <list>
+#include <map>
+#include <optional>
+#include <queue>
+#include <thread>
+
+#include "../libdevice/ExynosDisplay.h"
+#include "RingBuffer.h"
+#include "VariableRefreshRateInterface.h"
+
+namespace android::hardware::graphics::composer {
+
+constexpr uint64_t kMillisecondToNanoSecond = 1000000;
+
+class VariableRefreshRateController : public VsyncListener, public PresentListener {
+public:
+    ~VariableRefreshRateController();
+
+    auto static CreateInstance(ExynosDisplay* display)
+            -> std::shared_ptr<VariableRefreshRateController>;
+
+    int notifyExpectedPresent(int64_t timestamp, int32_t frameIntervalNs);
+
+    // Clear historical record data.
+    void reset();
+
+    // After setting the active Vrr configuration, we will automatically transition into the
+    // rendering state and post the timeout event.
+    void setActiveVrrConfiguration(hwc2_config_t config);
+
+    void setEnable(bool isEnabled);
+
+    void setPowerMode(int32_t mode);
+
+    void setVrrConfigurations(std::unordered_map<hwc2_config_t, VrrConfig_t> configs);
+
+private:
+    static constexpr int kDefaultRingBufferCapacity = 128;
+    static constexpr int64_t kDefaultWakeUpTimeInPowerSaving =
+            500 * (std::nano::den / std::milli::den); // 500 ms
+    static constexpr int64_t SIGNAL_TIME_PENDING = INT64_MAX;
+    static constexpr int64_t SIGNAL_TIME_INVALID = -1;
+
+    static const std::string kFrameInsertionNodeName;
+    static constexpr int kDefaultNumFramesToInsert = 2;
+    static constexpr int64_t kDefaultFrameInsertionTimer =
+            33 * (std::nano::den / std::milli::den); // 33 ms
+
+    enum class VrrControllerState {
+        kDisable = 0,
+        kRendering,
+        kHibernate,
+    };
+
+    typedef struct PresentEvent {
+        hwc2_config_t config;
+        int64_t mTime;
+        int mDuration;
+    } PresentEvent;
+
+    typedef struct VsyncEvent {
+        enum class Type {
+            kVblank,
+            kReleaseFence,
+        };
+        Type mType;
+        int64_t mTime;
+    } VsyncEvent;
+
+    typedef struct VrrRecord {
+        static constexpr int kDefaultRingBufferCapacity = 128;
+
+        void clear() {
+            mNextExpectedPresentTime = std::nullopt;
+            mPendingCurrentPresentTime = std::nullopt;
+            mPresentHistory.clear();
+            mVsyncHistory.clear();
+        }
+
+        std::optional<PresentEvent> mNextExpectedPresentTime = std::nullopt;
+        std::optional<PresentEvent> mPendingCurrentPresentTime = std::nullopt;
+
+        typedef RingBuffer<PresentEvent, kDefaultRingBufferCapacity> PresentTimeRecord;
+        typedef RingBuffer<VsyncEvent, kDefaultRingBufferCapacity> VsyncRecord;
+        PresentTimeRecord mPresentHistory;
+        VsyncRecord mVsyncHistory;
+    } VrrRecord;
+
+    enum VrrControllerEventType {
+        kRenderingTimeout = 0,
+        kHibernateTimeout,
+        kNotifyExpectedPresentConfig,
+        kNextFrameInsertion,
+        // Sensors, outer events...
+    };
+
+    struct VrrControllerEvent {
+        bool operator<(const VrrControllerEvent& b) const { return mWhenNs > b.mWhenNs; }
+        std::string getName() const {
+            switch (mEventType) {
+                case kRenderingTimeout:
+                    return "RenderingTimeout";
+                case kHibernateTimeout:
+                    return "kHibernateTimeout";
+                case kNotifyExpectedPresentConfig:
+                    return "NotifyExpectedPresentConfig";
+                case kNextFrameInsertion:
+                    return "kNextFrameInsertion";
+                default:
+                    return "Unknown";
+            }
+        }
+
+        std::string toString() const {
+            std::ostringstream os;
+            os << "Vrr event: [";
+            os << "type = " << getName() << ", ";
+            os << "when = " << mWhenNs << "ns]";
+            return os.str();
+        }
+        int64_t mDisplay;
+        VrrControllerEventType mEventType;
+        int64_t mWhenNs;
+    };
+
+    VariableRefreshRateController(ExynosDisplay* display);
+
+    // Implement interface PresentListener.
+    virtual void onPresent(int32_t fence) override;
+    virtual void setExpectedPresentTime(int64_t timestampNanos, int frameIntervalNs) override;
+
+    // Implement interface VsyncListener.
+    virtual void onVsync(int64_t timestamp, int32_t vsyncPeriodNanos) override;
+
+    void cancelFrameInsertionLocked();
+
+    int doFrameInsertionLocked();
+    int doFrameInsertionLocked(int frames);
+
+    void dropEventLocked();
+    void dropEventLocked(VrrControllerEventType event_type);
+
+    std::string dumpEventQueueLocked();
+
+    int64_t getLastFenceSignalTimeUnlocked(int fd);
+
+    int64_t getNextEventTimeLocked() const;
+
+    std::string getStateName(VrrControllerState state) const;
+
+    // Functions responsible for state machine transitions.
+    void handleCadenceChange();
+    void handleResume();
+    void handleHibernate();
+    void handleStayHibernate();
+
+    void postEvent(VrrControllerEventType type, int64_t when);
+
+    void stopThread(bool exit);
+
+    // The core function of the VRR controller thread.
+    void threadBody();
+
+    void updateVsyncHistory();
+
+    ExynosDisplay* mDisplay;
+
+    // The subsequent variables must be guarded by mMutex when accessed.
+    int mPendingFramesToInsert = 0;
+    std::priority_queue<VrrControllerEvent> mEventQueue;
+    VrrRecord mRecord;
+    int32_t mPowerMode = -1;
+    VrrControllerState mState;
+    hwc2_config_t mVrrActiveConfig;
+    std::unordered_map<hwc2_config_t, VrrConfig_t> mVrrConfigs;
+    std::optional<int> mLastPresentFence;
+
+    std::unique_ptr<FileNodeWriter> mFileNodeWritter;
+
+    bool mEnabled = false;
+    bool mThreadExit = false;
+
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+};
+
+} // namespace android::hardware::graphics::composer
diff --git a/libhwc2.1/libvrr/VariableRefreshRateInterface.h b/libhwc2.1/libvrr/VariableRefreshRateInterface.h
new file mode 100644
index 0000000..51d4848
--- /dev/null
+++ b/libhwc2.1/libvrr/VariableRefreshRateInterface.h
@@ -0,0 +1,37 @@
+/*
+ * 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
+
+namespace android::hardware::graphics::composer {
+
+class PresentListener {
+public:
+    virtual ~PresentListener() = default;
+
+    virtual void setExpectedPresentTime(int64_t timestampNanos, int frameIntervalNs) = 0;
+
+    virtual void onPresent(int32_t fence) = 0;
+};
+
+class VsyncListener {
+public:
+    virtual ~VsyncListener() = default;
+
+    virtual void onVsync(int64_t timestamp, int32_t vsyncPeriodNanos) = 0;
+};
+
+} // namespace android::hardware::graphics::composer
diff --git a/libhwc2.1/pixel-display-default.xml b/libhwc2.1/pixel-display-default.xml
index 50835eb..ca2892b 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>9</version>
+        <version>10</version>
         <fqname>IDisplay/default</fqname>
     </hal>
 </manifest>
diff --git a/libhwc2.1/pixel-display-secondary.xml b/libhwc2.1/pixel-display-secondary.xml
index 3cf2883..907cf27 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>9</version>
+        <version>10</version>
         <fqname>IDisplay/secondary</fqname>
     </hal>
 </manifest>
diff --git a/libhwc2.1/pixel-display.cpp b/libhwc2.1/pixel-display.cpp
index c958346..5edd194 100644
--- a/libhwc2.1/pixel-display.cpp
+++ b/libhwc2.1/pixel-display.cpp
@@ -175,7 +175,7 @@
 
 ndk::ScopedAStatus Display::setMinIdleRefreshRate(int fps, int *_aidl_return) {
     if (mDisplay) {
-        *_aidl_return = mDisplay->setMinIdleRefreshRate(fps, VrrThrottleRequester::PIXEL_DISP);
+        *_aidl_return = mDisplay->setMinIdleRefreshRate(fps, RrThrottleRequester::PIXEL_DISP);
         return ndk::ScopedAStatus::ok();
     }
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
@@ -194,7 +194,7 @@
                                                               std::chrono::nanoseconds>(
                                                               std::chrono::milliseconds(delayMs))
                                                               .count(),
-                                                      VrrThrottleRequester::PIXEL_DISP);
+                                                      RrThrottleRequester::PIXEL_DISP);
         return ndk::ScopedAStatus::ok();
     }
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
@@ -209,13 +209,13 @@
         std::scoped_lock lock(mMediator.mConfigMutex);
         isConfigChanged = mMediator.mConfig != pendingConfig;
 
-        if (isConfigChanged &&
-            mMediator.setRoiWeightThreshold(roi, weight, pos) != HistogramErrorCode::NONE) {
-            ALOGE("histogram error, SET_ROI_WEIGHT_THRESHOLD ERROR\n");
-            return false;
+        if (isConfigChanged) {
+            if (mMediator.setRoiWeightThreshold(roi, weight, pos) != HistogramErrorCode::NONE) {
+                ALOGE("histogram error, SET_ROI_WEIGHT_THRESHOLD ERROR\n");
+                return false;
+            }
+            mMediator.mConfig = pendingConfig;
         }
-
-        mMediator.mConfig = pendingConfig;
     }
 
     if (!mMediator.histRequested() &&