Update kernel to ab/13436313

Change-Id: Ib03e9382381b8a174741c9e7838412462253411d
diff --git a/.checkpatch.conf b/.checkpatch.conf
new file mode 100644
index 0000000..f3ac810
--- /dev/null
+++ b/.checkpatch.conf
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# The configs of the Linux checkpatch script
+#
+# Copyright (c) 2022 Google LLC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+
+# Showing the diff position is clearer with git-based information
+--showfile
+
+# The Change-Id line is always reserved before submitting
+--ignore GERRIT_CHANGE_ID
+
+# The MAINTAINERS file patterns are not used in current gerrit
+--ignore FILE_PATH_CHANGES
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2aedace
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,683 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# clang-format configuration file. Intended for clang-format >= 11.
+#
+# For more information, see:
+#
+#   Documentation/process/clang-format.rst
+#   https://clang.llvm.org/docs/ClangFormat.html
+#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+#
+---
+AccessModifierOffset: -4
+AlignAfterOpenBracket: true
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BasedOnStyle: LLVM
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: true
+  AfterNamespace: true
+  AfterObjCDeclaration: false
+  AfterStruct: false
+  AfterUnion: false
+  AfterExternBlock: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Linux
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 100
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+
+# Taken from:
+#   git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \
+#   | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$,  - '\1'," \
+#   | LC_ALL=C sort -u
+ForEachMacros:
+  - '__ata_qc_for_each'
+  - '__bio_for_each_bvec'
+  - '__bio_for_each_segment'
+  - '__evlist__for_each_entry'
+  - '__evlist__for_each_entry_continue'
+  - '__evlist__for_each_entry_from'
+  - '__evlist__for_each_entry_reverse'
+  - '__evlist__for_each_entry_safe'
+  - '__for_each_mem_range'
+  - '__for_each_mem_range_rev'
+  - '__for_each_thread'
+  - '__hlist_for_each_rcu'
+  - '__map__for_each_symbol_by_name'
+  - '__perf_evlist__for_each_entry'
+  - '__perf_evlist__for_each_entry_reverse'
+  - '__perf_evlist__for_each_entry_safe'
+  - '__rq_for_each_bio'
+  - '__shost_for_each_device'
+  - 'apei_estatus_for_each_section'
+  - 'ata_for_each_dev'
+  - 'ata_for_each_link'
+  - 'ata_qc_for_each'
+  - 'ata_qc_for_each_raw'
+  - 'ata_qc_for_each_with_internal'
+  - 'ax25_for_each'
+  - 'ax25_uid_for_each'
+  - 'bio_for_each_bvec'
+  - 'bio_for_each_bvec_all'
+  - 'bio_for_each_folio_all'
+  - 'bio_for_each_integrity_vec'
+  - 'bio_for_each_segment'
+  - 'bio_for_each_segment_all'
+  - 'bio_list_for_each'
+  - 'bip_for_each_vec'
+  - 'bond_for_each_slave'
+  - 'bond_for_each_slave_rcu'
+  - 'bpf__perf_for_each_map'
+  - 'bpf__perf_for_each_map_named'
+  - 'bpf_for_each_spilled_reg'
+  - 'bpf_object__for_each_map'
+  - 'bpf_object__for_each_program'
+  - 'bpf_object__for_each_safe'
+  - 'bpf_perf_object__for_each'
+  - 'btree_for_each_safe128'
+  - 'btree_for_each_safe32'
+  - 'btree_for_each_safe64'
+  - 'btree_for_each_safel'
+  - 'card_for_each_dev'
+  - 'cgroup_taskset_for_each'
+  - 'cgroup_taskset_for_each_leader'
+  - 'cpufreq_for_each_efficient_entry_idx'
+  - 'cpufreq_for_each_entry'
+  - 'cpufreq_for_each_entry_idx'
+  - 'cpufreq_for_each_valid_entry'
+  - 'cpufreq_for_each_valid_entry_idx'
+  - 'css_for_each_child'
+  - 'css_for_each_descendant_post'
+  - 'css_for_each_descendant_pre'
+  - 'damon_for_each_region'
+  - 'damon_for_each_region_safe'
+  - 'damon_for_each_scheme'
+  - 'damon_for_each_scheme_safe'
+  - 'damon_for_each_target'
+  - 'damon_for_each_target_safe'
+  - 'data__for_each_file'
+  - 'data__for_each_file_new'
+  - 'data__for_each_file_start'
+  - 'device_for_each_child_node'
+  - 'displayid_iter_for_each'
+  - 'dma_fence_array_for_each'
+  - 'dma_fence_chain_for_each'
+  - 'dma_fence_unwrap_for_each'
+  - 'dma_resv_for_each_fence'
+  - 'dma_resv_for_each_fence_unlocked'
+  - 'do_for_each_ftrace_op'
+  - 'drm_atomic_crtc_for_each_plane'
+  - 'drm_atomic_crtc_state_for_each_plane'
+  - 'drm_atomic_crtc_state_for_each_plane_state'
+  - 'drm_atomic_for_each_plane_damage'
+  - 'drm_client_for_each_connector_iter'
+  - 'drm_client_for_each_modeset'
+  - 'drm_connector_for_each_possible_encoder'
+  - 'drm_for_each_bridge_in_chain'
+  - 'drm_for_each_connector_iter'
+  - 'drm_for_each_crtc'
+  - 'drm_for_each_crtc_reverse'
+  - 'drm_for_each_encoder'
+  - 'drm_for_each_encoder_mask'
+  - 'drm_for_each_fb'
+  - 'drm_for_each_legacy_plane'
+  - 'drm_for_each_plane'
+  - 'drm_for_each_plane_mask'
+  - 'drm_for_each_privobj'
+  - 'drm_mm_for_each_hole'
+  - 'drm_mm_for_each_node'
+  - 'drm_mm_for_each_node_in_range'
+  - 'drm_mm_for_each_node_safe'
+  - 'dsa_switch_for_each_available_port'
+  - 'dsa_switch_for_each_cpu_port'
+  - 'dsa_switch_for_each_port'
+  - 'dsa_switch_for_each_port_continue_reverse'
+  - 'dsa_switch_for_each_port_safe'
+  - 'dsa_switch_for_each_user_port'
+  - 'dsa_tree_for_each_user_port'
+  - 'dso__for_each_symbol'
+  - 'dsos__for_each_with_build_id'
+  - 'elf_hash_for_each_possible'
+  - 'elf_section__for_each_rel'
+  - 'elf_section__for_each_rela'
+  - 'elf_symtab__for_each_symbol'
+  - 'evlist__for_each_cpu'
+  - 'evlist__for_each_entry'
+  - 'evlist__for_each_entry_continue'
+  - 'evlist__for_each_entry_from'
+  - 'evlist__for_each_entry_reverse'
+  - 'evlist__for_each_entry_safe'
+  - 'flow_action_for_each'
+  - 'for_each_acpi_dev_match'
+  - 'for_each_active_dev_scope'
+  - 'for_each_active_drhd_unit'
+  - 'for_each_active_iommu'
+  - 'for_each_aggr_pgid'
+  - 'for_each_available_child_of_node'
+  - 'for_each_bench'
+  - 'for_each_bio'
+  - 'for_each_board_func_rsrc'
+  - 'for_each_btf_ext_rec'
+  - 'for_each_btf_ext_sec'
+  - 'for_each_bvec'
+  - 'for_each_card_auxs'
+  - 'for_each_card_auxs_safe'
+  - 'for_each_card_components'
+  - 'for_each_card_dapms'
+  - 'for_each_card_pre_auxs'
+  - 'for_each_card_prelinks'
+  - 'for_each_card_rtds'
+  - 'for_each_card_rtds_safe'
+  - 'for_each_card_widgets'
+  - 'for_each_card_widgets_safe'
+  - 'for_each_cgroup_storage_type'
+  - 'for_each_child_of_node'
+  - 'for_each_clear_bit'
+  - 'for_each_clear_bit_from'
+  - 'for_each_clear_bitrange'
+  - 'for_each_clear_bitrange_from'
+  - 'for_each_cmd'
+  - 'for_each_cmsghdr'
+  - 'for_each_collection'
+  - 'for_each_comp_order'
+  - 'for_each_compatible_node'
+  - 'for_each_component_dais'
+  - 'for_each_component_dais_safe'
+  - 'for_each_console'
+  - 'for_each_cpu'
+  - 'for_each_cpu_and'
+  - 'for_each_cpu_not'
+  - 'for_each_cpu_wrap'
+  - 'for_each_dapm_widgets'
+  - 'for_each_dedup_cand'
+  - 'for_each_dev_addr'
+  - 'for_each_dev_scope'
+  - 'for_each_dma_cap_mask'
+  - 'for_each_dpcm_be'
+  - 'for_each_dpcm_be_rollback'
+  - 'for_each_dpcm_be_safe'
+  - 'for_each_dpcm_fe'
+  - 'for_each_drhd_unit'
+  - 'for_each_dss_dev'
+  - 'for_each_efi_memory_desc'
+  - 'for_each_efi_memory_desc_in_map'
+  - 'for_each_element'
+  - 'for_each_element_extid'
+  - 'for_each_element_id'
+  - 'for_each_endpoint_of_node'
+  - 'for_each_event'
+  - 'for_each_event_tps'
+  - 'for_each_evictable_lru'
+  - 'for_each_fib6_node_rt_rcu'
+  - 'for_each_fib6_walker_rt'
+  - 'for_each_free_mem_pfn_range_in_zone'
+  - 'for_each_free_mem_pfn_range_in_zone_from'
+  - 'for_each_free_mem_range'
+  - 'for_each_free_mem_range_reverse'
+  - 'for_each_func_rsrc'
+  - 'for_each_group_evsel'
+  - 'for_each_group_member'
+  - 'for_each_hstate'
+  - 'for_each_if'
+  - 'for_each_inject_fn'
+  - 'for_each_insn'
+  - 'for_each_insn_prefix'
+  - 'for_each_intid'
+  - 'for_each_iommu'
+  - 'for_each_ip_tunnel_rcu'
+  - 'for_each_irq_nr'
+  - 'for_each_lang'
+  - 'for_each_link_codecs'
+  - 'for_each_link_cpus'
+  - 'for_each_link_platforms'
+  - 'for_each_lru'
+  - 'for_each_matching_node'
+  - 'for_each_matching_node_and_match'
+  - 'for_each_mem_pfn_range'
+  - 'for_each_mem_range'
+  - 'for_each_mem_range_rev'
+  - 'for_each_mem_region'
+  - 'for_each_member'
+  - 'for_each_memory'
+  - 'for_each_migratetype_order'
+  - 'for_each_missing_reg'
+  - 'for_each_net'
+  - 'for_each_net_continue_reverse'
+  - 'for_each_net_rcu'
+  - 'for_each_netdev'
+  - 'for_each_netdev_continue'
+  - 'for_each_netdev_continue_rcu'
+  - 'for_each_netdev_continue_reverse'
+  - 'for_each_netdev_feature'
+  - 'for_each_netdev_in_bond_rcu'
+  - 'for_each_netdev_rcu'
+  - 'for_each_netdev_reverse'
+  - 'for_each_netdev_safe'
+  - 'for_each_new_connector_in_state'
+  - 'for_each_new_crtc_in_state'
+  - 'for_each_new_mst_mgr_in_state'
+  - 'for_each_new_plane_in_state'
+  - 'for_each_new_plane_in_state_reverse'
+  - 'for_each_new_private_obj_in_state'
+  - 'for_each_new_reg'
+  - 'for_each_node'
+  - 'for_each_node_by_name'
+  - 'for_each_node_by_type'
+  - 'for_each_node_mask'
+  - 'for_each_node_state'
+  - 'for_each_node_with_cpus'
+  - 'for_each_node_with_property'
+  - 'for_each_nonreserved_multicast_dest_pgid'
+  - 'for_each_of_allnodes'
+  - 'for_each_of_allnodes_from'
+  - 'for_each_of_cpu_node'
+  - 'for_each_of_pci_range'
+  - 'for_each_old_connector_in_state'
+  - 'for_each_old_crtc_in_state'
+  - 'for_each_old_mst_mgr_in_state'
+  - 'for_each_old_plane_in_state'
+  - 'for_each_old_private_obj_in_state'
+  - 'for_each_oldnew_connector_in_state'
+  - 'for_each_oldnew_crtc_in_state'
+  - 'for_each_oldnew_mst_mgr_in_state'
+  - 'for_each_oldnew_plane_in_state'
+  - 'for_each_oldnew_plane_in_state_reverse'
+  - 'for_each_oldnew_private_obj_in_state'
+  - 'for_each_online_cpu'
+  - 'for_each_online_node'
+  - 'for_each_online_pgdat'
+  - 'for_each_path'
+  - 'for_each_pci_bridge'
+  - 'for_each_pci_dev'
+  - 'for_each_pcm_streams'
+  - 'for_each_physmem_range'
+  - 'for_each_populated_zone'
+  - 'for_each_possible_cpu'
+  - 'for_each_present_cpu'
+  - 'for_each_prime_number'
+  - 'for_each_prime_number_from'
+  - 'for_each_probe_cache_entry'
+  - 'for_each_process'
+  - 'for_each_process_thread'
+  - 'for_each_prop_codec_conf'
+  - 'for_each_prop_dai_codec'
+  - 'for_each_prop_dai_cpu'
+  - 'for_each_prop_dlc_codecs'
+  - 'for_each_prop_dlc_cpus'
+  - 'for_each_prop_dlc_platforms'
+  - 'for_each_property_of_node'
+  - 'for_each_reg'
+  - 'for_each_reg_filtered'
+  - 'for_each_registered_fb'
+  - 'for_each_requested_gpio'
+  - 'for_each_requested_gpio_in_range'
+  - 'for_each_reserved_mem_range'
+  - 'for_each_reserved_mem_region'
+  - 'for_each_rtd_codec_dais'
+  - 'for_each_rtd_components'
+  - 'for_each_rtd_cpu_dais'
+  - 'for_each_rtd_dais'
+  - 'for_each_script'
+  - 'for_each_sec'
+  - 'for_each_set_bit'
+  - 'for_each_set_bit_from'
+  - 'for_each_set_bitrange'
+  - 'for_each_set_bitrange_from'
+  - 'for_each_set_clump8'
+  - 'for_each_sg'
+  - 'for_each_sg_dma_page'
+  - 'for_each_sg_page'
+  - 'for_each_sgtable_dma_page'
+  - 'for_each_sgtable_dma_sg'
+  - 'for_each_sgtable_page'
+  - 'for_each_sgtable_sg'
+  - 'for_each_shell_test'
+  - 'for_each_sibling_event'
+  - 'for_each_subelement'
+  - 'for_each_subelement_extid'
+  - 'for_each_subelement_id'
+  - 'for_each_sublist'
+  - 'for_each_subsystem'
+  - 'for_each_supported_activate_fn'
+  - 'for_each_supported_inject_fn'
+  - 'for_each_test'
+  - 'for_each_thread'
+  - 'for_each_token'
+  - 'for_each_unicast_dest_pgid'
+  - 'for_each_vsi'
+  - 'for_each_wakeup_source'
+  - 'for_each_zone'
+  - 'for_each_zone_zonelist'
+  - 'for_each_zone_zonelist_nodemask'
+  - 'func_for_each_insn'
+  - 'fwnode_for_each_available_child_node'
+  - 'fwnode_for_each_child_node'
+  - 'fwnode_graph_for_each_endpoint'
+  - 'gadget_for_each_ep'
+  - 'genradix_for_each'
+  - 'genradix_for_each_from'
+  - 'hash_for_each'
+  - 'hash_for_each_possible'
+  - 'hash_for_each_possible_rcu'
+  - 'hash_for_each_possible_rcu_notrace'
+  - 'hash_for_each_possible_safe'
+  - 'hash_for_each_rcu'
+  - 'hash_for_each_safe'
+  - 'hashmap__for_each_entry'
+  - 'hashmap__for_each_entry_safe'
+  - 'hashmap__for_each_key_entry'
+  - 'hashmap__for_each_key_entry_safe'
+  - 'hctx_for_each_ctx'
+  - 'hists__for_each_format'
+  - 'hists__for_each_sort_list'
+  - 'hlist_bl_for_each_entry'
+  - 'hlist_bl_for_each_entry_rcu'
+  - 'hlist_bl_for_each_entry_safe'
+  - 'hlist_for_each'
+  - 'hlist_for_each_entry'
+  - 'hlist_for_each_entry_continue'
+  - 'hlist_for_each_entry_continue_rcu'
+  - 'hlist_for_each_entry_continue_rcu_bh'
+  - 'hlist_for_each_entry_from'
+  - 'hlist_for_each_entry_from_rcu'
+  - 'hlist_for_each_entry_rcu'
+  - 'hlist_for_each_entry_rcu_bh'
+  - 'hlist_for_each_entry_rcu_notrace'
+  - 'hlist_for_each_entry_safe'
+  - 'hlist_for_each_entry_srcu'
+  - 'hlist_for_each_safe'
+  - 'hlist_nulls_for_each_entry'
+  - 'hlist_nulls_for_each_entry_from'
+  - 'hlist_nulls_for_each_entry_rcu'
+  - 'hlist_nulls_for_each_entry_safe'
+  - 'i3c_bus_for_each_i2cdev'
+  - 'i3c_bus_for_each_i3cdev'
+  - 'idr_for_each_entry'
+  - 'idr_for_each_entry_continue'
+  - 'idr_for_each_entry_continue_ul'
+  - 'idr_for_each_entry_ul'
+  - 'in_dev_for_each_ifa_rcu'
+  - 'in_dev_for_each_ifa_rtnl'
+  - 'inet_bind_bucket_for_each'
+  - 'inet_lhash2_for_each_icsk'
+  - 'inet_lhash2_for_each_icsk_continue'
+  - 'inet_lhash2_for_each_icsk_rcu'
+  - 'intlist__for_each_entry'
+  - 'intlist__for_each_entry_safe'
+  - 'kcore_copy__for_each_phdr'
+  - 'key_for_each'
+  - 'key_for_each_safe'
+  - 'klp_for_each_func'
+  - 'klp_for_each_func_safe'
+  - 'klp_for_each_func_static'
+  - 'klp_for_each_object'
+  - 'klp_for_each_object_safe'
+  - 'klp_for_each_object_static'
+  - 'kunit_suite_for_each_test_case'
+  - 'kvm_for_each_memslot'
+  - 'kvm_for_each_memslot_in_gfn_range'
+  - 'kvm_for_each_vcpu'
+  - 'libbpf_nla_for_each_attr'
+  - 'list_for_each'
+  - 'list_for_each_codec'
+  - 'list_for_each_codec_safe'
+  - 'list_for_each_continue'
+  - 'list_for_each_entry'
+  - 'list_for_each_entry_continue'
+  - 'list_for_each_entry_continue_rcu'
+  - 'list_for_each_entry_continue_reverse'
+  - 'list_for_each_entry_from'
+  - 'list_for_each_entry_from_rcu'
+  - 'list_for_each_entry_from_reverse'
+  - 'list_for_each_entry_lockless'
+  - 'list_for_each_entry_rcu'
+  - 'list_for_each_entry_reverse'
+  - 'list_for_each_entry_safe'
+  - 'list_for_each_entry_safe_continue'
+  - 'list_for_each_entry_safe_from'
+  - 'list_for_each_entry_safe_reverse'
+  - 'list_for_each_entry_srcu'
+  - 'list_for_each_from'
+  - 'list_for_each_prev'
+  - 'list_for_each_prev_safe'
+  - 'list_for_each_safe'
+  - 'llist_for_each'
+  - 'llist_for_each_entry'
+  - 'llist_for_each_entry_safe'
+  - 'llist_for_each_safe'
+  - 'map__for_each_symbol'
+  - 'map__for_each_symbol_by_name'
+  - 'map_for_each_event'
+  - 'map_for_each_metric'
+  - 'maps__for_each_entry'
+  - 'maps__for_each_entry_safe'
+  - 'mci_for_each_dimm'
+  - 'media_device_for_each_entity'
+  - 'media_device_for_each_intf'
+  - 'media_device_for_each_link'
+  - 'media_device_for_each_pad'
+  - 'msi_for_each_desc'
+  - 'nanddev_io_for_each_page'
+  - 'netdev_for_each_lower_dev'
+  - 'netdev_for_each_lower_private'
+  - 'netdev_for_each_lower_private_rcu'
+  - 'netdev_for_each_mc_addr'
+  - 'netdev_for_each_uc_addr'
+  - 'netdev_for_each_upper_dev_rcu'
+  - 'netdev_hw_addr_list_for_each'
+  - 'nft_rule_for_each_expr'
+  - 'nla_for_each_attr'
+  - 'nla_for_each_nested'
+  - 'nlmsg_for_each_attr'
+  - 'nlmsg_for_each_msg'
+  - 'nr_neigh_for_each'
+  - 'nr_neigh_for_each_safe'
+  - 'nr_node_for_each'
+  - 'nr_node_for_each_safe'
+  - 'of_for_each_phandle'
+  - 'of_property_for_each_string'
+  - 'of_property_for_each_u32'
+  - 'pci_bus_for_each_resource'
+  - 'pcl_for_each_chunk'
+  - 'pcl_for_each_segment'
+  - 'pcm_for_each_format'
+  - 'perf_config_items__for_each_entry'
+  - 'perf_config_sections__for_each_entry'
+  - 'perf_config_set__for_each_entry'
+  - 'perf_cpu_map__for_each_cpu'
+  - 'perf_evlist__for_each_entry'
+  - 'perf_evlist__for_each_entry_reverse'
+  - 'perf_evlist__for_each_entry_safe'
+  - 'perf_evlist__for_each_evsel'
+  - 'perf_evlist__for_each_mmap'
+  - 'perf_hpp_list__for_each_format'
+  - 'perf_hpp_list__for_each_format_safe'
+  - 'perf_hpp_list__for_each_sort_list'
+  - 'perf_hpp_list__for_each_sort_list_safe'
+  - 'perf_pmu__for_each_hybrid_pmu'
+  - 'ping_portaddr_for_each_entry'
+  - 'plist_for_each'
+  - 'plist_for_each_continue'
+  - 'plist_for_each_entry'
+  - 'plist_for_each_entry_continue'
+  - 'plist_for_each_entry_safe'
+  - 'plist_for_each_safe'
+  - 'pnp_for_each_card'
+  - 'pnp_for_each_dev'
+  - 'protocol_for_each_card'
+  - 'protocol_for_each_dev'
+  - 'queue_for_each_hw_ctx'
+  - 'radix_tree_for_each_slot'
+  - 'radix_tree_for_each_tagged'
+  - 'rb_for_each'
+  - 'rbtree_postorder_for_each_entry_safe'
+  - 'rdma_for_each_block'
+  - 'rdma_for_each_port'
+  - 'rdma_umem_for_each_dma_block'
+  - 'resort_rb__for_each_entry'
+  - 'resource_list_for_each_entry'
+  - 'resource_list_for_each_entry_safe'
+  - 'rhl_for_each_entry_rcu'
+  - 'rhl_for_each_rcu'
+  - 'rht_for_each'
+  - 'rht_for_each_entry'
+  - 'rht_for_each_entry_from'
+  - 'rht_for_each_entry_rcu'
+  - 'rht_for_each_entry_rcu_from'
+  - 'rht_for_each_entry_safe'
+  - 'rht_for_each_from'
+  - 'rht_for_each_rcu'
+  - 'rht_for_each_rcu_from'
+  - 'rq_for_each_bvec'
+  - 'rq_for_each_segment'
+  - 'rq_list_for_each'
+  - 'rq_list_for_each_safe'
+  - 'scsi_for_each_prot_sg'
+  - 'scsi_for_each_sg'
+  - 'sctp_for_each_hentry'
+  - 'sctp_skb_for_each'
+  - 'sec_for_each_insn'
+  - 'sec_for_each_insn_continue'
+  - 'sec_for_each_insn_from'
+  - 'shdma_for_each_chan'
+  - 'shost_for_each_device'
+  - 'sk_for_each'
+  - 'sk_for_each_bound'
+  - 'sk_for_each_entry_offset_rcu'
+  - 'sk_for_each_from'
+  - 'sk_for_each_rcu'
+  - 'sk_for_each_safe'
+  - 'sk_nulls_for_each'
+  - 'sk_nulls_for_each_from'
+  - 'sk_nulls_for_each_rcu'
+  - 'snd_array_for_each'
+  - 'snd_pcm_group_for_each_entry'
+  - 'snd_soc_dapm_widget_for_each_path'
+  - 'snd_soc_dapm_widget_for_each_path_safe'
+  - 'snd_soc_dapm_widget_for_each_sink_path'
+  - 'snd_soc_dapm_widget_for_each_source_path'
+  - 'strlist__for_each_entry'
+  - 'strlist__for_each_entry_safe'
+  - 'sym_for_each_insn'
+  - 'sym_for_each_insn_continue_reverse'
+  - 'symbols__for_each_entry'
+  - 'tb_property_for_each'
+  - 'tcf_act_for_each_action'
+  - 'tcf_exts_for_each_action'
+  - 'udp_portaddr_for_each_entry'
+  - 'udp_portaddr_for_each_entry_rcu'
+  - 'usb_hub_for_each_child'
+  - 'v4l2_device_for_each_subdev'
+  - 'v4l2_m2m_for_each_dst_buf'
+  - 'v4l2_m2m_for_each_dst_buf_safe'
+  - 'v4l2_m2m_for_each_src_buf'
+  - 'v4l2_m2m_for_each_src_buf_safe'
+  - 'virtio_device_for_each_vq'
+  - 'while_for_each_ftrace_op'
+  - 'xa_for_each'
+  - 'xa_for_each_marked'
+  - 'xa_for_each_range'
+  - 'xa_for_each_start'
+  - 'xas_for_each'
+  - 'xas_for_each_conflict'
+  - 'xas_for_each_marked'
+  - 'xbc_array_for_each_value'
+  - 'xbc_for_each_key_value'
+  - 'xbc_node_for_each_array_value'
+  - 'xbc_node_for_each_child'
+  - 'xbc_node_for_each_key_value'
+  - 'xbc_node_for_each_subkey'
+  - 'zorro_for_each_dev'
+
+IncludeBlocks: Preserve
+IncludeCategories:
+  - Regex: '.*'
+    Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentGotoLabels: false
+IndentPPDirectives: None
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 8
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+
+# Taken from git's rules
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: false
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatementsExceptForEachMacros
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp03
+TabWidth: 8
+UseTab: Always
+...
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..0ce8234
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+load("//build/kernel/kleaf:kernel.bzl", "ddk_headers", "ddk_module")
+
+package(
+    default_visibility = [
+        "//private/devices/google:__subpackages__",
+    ],
+)
+
+_PANEL_LOCAL_DEFINES = [
+    "DYNAMIC_DEBUG_MODULE",
+] + select({
+    "//private/devices/google/common:factory_build": ["PANEL_FACTORY_BUILD"],
+    "//conditions:default": [],
+})
+
+ddk_headers(
+    name = "common_headers",
+    hdrs = [
+        "//common:all_headers_aarch64",
+        "//private/google-modules/display/common:ddk_headers",
+    ],
+    visibility = ["//visibility:private"],
+)
+
+# Note: please add additional drivers as separate ddk modules
+
+ddk_module(
+    name = "panel-gs-km4",
+    srcs = [
+        "panel-gs-km4.c",
+    ],
+    out = "panel-gs-km4.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_drm",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
+
+ddk_module(
+    name = "panel-gs-cm4",
+    srcs = [
+        "panel-gs-cm4.c",
+    ],
+    out = "panel-gs-cm4.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_drm",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
+
+ddk_module(
+    name = "panel-gs-tk4a",
+    srcs = [
+        "panel-gs-tk4a.c",
+    ],
+    out = "panel-gs-tk4a.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
+
+ddk_module(
+    name = "panel-gs-tk4b",
+    srcs = [
+        "panel-gs-tk4b.c",
+    ],
+    out = "panel-gs-tk4b.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
+
+ddk_module(
+    name = "panel-gs-tk4c",
+    srcs = [
+        "panel-gs-tk4c.c",
+    ],
+    out = "panel-gs-tk4c.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
+
+ddk_module(
+    name = "panel-gs-tk4c-test",
+    srcs = [
+        "panel-gs-tk4c-test.c",
+    ],
+    out = "panel-gs-tk4c-test.ko",
+    kernel_build = "//private/devices/google/common:kernel",
+    local_defines = _PANEL_LOCAL_DEFINES,
+    deps = [
+        ":common_headers",
+        "//private/google-modules/display/common/gs_panel",
+    ],
+)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..2682d33
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,11 @@
+[Options]
+ignore_merged_commits = true
+
+[Builtin Hooks]
+checkpatch = true
+commit_msg_bug_field = true
+commit_msg_changeid_field = true
+commit_msg_test_field = true
+
+[Builtin Hooks Options]
+checkpatch = --root ${REPO_ROOT}/aosp
diff --git a/panel-gs-cm4.c b/panel-gs-cm4.c
new file mode 100644
index 0000000..33df51d
--- /dev/null
+++ b/panel-gs-cm4.c
@@ -0,0 +1,2930 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * MIPI-DSI based CM4 panel driver.
+ *
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_vblank.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/thermal.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+/**
+ * struct cm4_panel - panel specific info
+ *
+ * This struct maintains cm4 panel specific info. The variables with the prefix hw_ keep
+ * track of the features that were actually committed to hardware, and should be modified
+ * after sending cmds to panel, i.e., updating hw state.
+ */
+struct cm4_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+	/** @force_changeable_te: force changeable TE (instead of fixed) during early exit */
+	bool force_changeable_te;
+	/** @force_changeable_te2: force changeable TE2 for monitoring refresh rate */
+	bool force_changeable_te2;
+	/** @force_za_off: force to turn off zonal attenuation */
+	bool force_za_off;
+	/**
+	 * @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
+	 *		  panel can recover to normal mode after entering pixel-off state.
+	 */
+	bool is_pixel_off;
+	/**
+	 * @is_mrr_v1: indicates panel is running in mrr v1 mode
+	 */
+	bool is_mrr_v1;
+	/**
+	 * @frame_rate: real time frame rate
+	 */
+	u16 frame_rate;
+	/**
+	 * @dbi_frame_count: frame counter to alternatively set dbi ref if frame rate > 60
+	 */
+	u64 dbi_frame_count;
+};
+
+#define to_spanel(ctx) container_of(ctx, struct cm4_panel, base)
+
+/* DSCv1.2a 1280x2856 */
+static const struct drm_dsc_config wqhd_pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_count = 2,
+	.slice_width = 640,
+	.slice_height = 42,
+	.simple_422 = false,
+	.pic_width = 1280,
+	.pic_height = 2856,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 577,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 8,
+	.scale_increment_interval = 1130,
+	.nfl_bpg_offset = 600,
+	.slice_bpg_offset = 522,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 640,
+	.dsc_version_minor = 2,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+/* DSC v1.2a 960x2142 */
+static const struct drm_dsc_config fhd_pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_count = 2,
+	.slice_width = 480,
+	.slice_height = 42,
+	.simple_422 = false,
+	.pic_width = 960,
+	.pic_height = 2142,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 496,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 6,
+	.scale_increment_interval = 977,
+	.nfl_bpg_offset = 600,
+	.slice_bpg_offset = 698,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 480,
+	.dsc_version_minor = 2,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+#define CM4_WRCTRLD_DIMMING_BIT 0x08
+#define CM4_WRCTRLD_BCTRL_BIT 0x20
+#define CM4_WRCTRLD_HBM_BIT 0xC0
+
+#define CM4_TE2_CHANGEABLE 0x04
+#define CM4_TE2_FIXED_120HZ 0x51
+#define CM4_TE2_FIXED_240HZ 0x41
+#define CM4_TE2_RISING_EDGE_OFFSET 0x20
+#define CM4_TE2_FALLING_EDGE_OFFSET 0x57
+
+#define CM4_TE_USEC_120HZ_HS 273
+#define CM4_TE_USEC_60HZ_HS 8500
+#define CM4_TE_USEC_60HZ_NS 1633
+
+#define CM4_TE_USEC_VRR_HS 273
+#define CM4_TE_USEC_VRR_NS 1633
+
+#define CM4_DBI_REF_DEFAULT 60
+
+#define WIDTH_MM 66
+#define HEIGHT_MM 147
+
+#define MIPI_DSI_FREQ_MBPS_DEFAULT 1368
+#define MIPI_DSI_FREQ_MBPS_ALTERNATIVE 1288
+
+#define COMP_TEMP_MIN 10
+#define COMP_TEMP_MAX 49
+#define COMP_TEMP_OFFSET -10
+
+#define PROJECT "CM4"
+
+static const u8 unlock_cmd_f0[] = { 0xF0, 0x5A, 0x5A };
+static const u8 lock_cmd_f0[] = { 0xF0, 0xA5, 0xA5 };
+static const u8 freq_update[] = { 0xF7, 0x0F };
+static const u8 aod_on[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24 };
+static const u8 aod_off[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20 };
+static const u8 pixel_off[] = { 0x22 };
+
+static const struct gs_dsi_cmd cm4_lp_night_cmds[] = {
+	/* AOD Night Mode, 2nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0xB0),
+};
+
+static const struct gs_dsi_cmd cm4_lp_low_cmds[] = {
+	/* AOD Low Mode, 10nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x01, 0x6D),
+};
+
+static const struct gs_dsi_cmd cm4_lp_high_cmds[] = {
+	/* AOD High Mode, 50nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x02, 0xF6),
+};
+
+static const struct gs_binned_lp cm4_binned_lp[] = {
+	/* night threshold 4 nits */
+	BINNED_LP_MODE_TIMING("night", 240, cm4_lp_night_cmds, CM4_TE2_RISING_EDGE_OFFSET,
+			      CM4_TE2_FALLING_EDGE_OFFSET),
+	/* low threshold 40 nits */
+	BINNED_LP_MODE_TIMING("low", 686, cm4_lp_low_cmds, CM4_TE2_RISING_EDGE_OFFSET,
+			      CM4_TE2_FALLING_EDGE_OFFSET),
+	BINNED_LP_MODE_TIMING("high", 3271, cm4_lp_high_cmds, CM4_TE2_RISING_EDGE_OFFSET,
+			      CM4_TE2_FALLING_EDGE_OFFSET),
+};
+
+static unsigned int cm4_get_te_usec(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (vrefresh != 60 || gs_is_vrr_mode(pmode)) {
+		return pmode->gs_mode.te_usec;
+	} else {
+		if (spanel->is_mrr_v1) {
+			return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? CM4_TE_USEC_60HZ_NS :
+									    CM4_TE_USEC_60HZ_HS);
+		} else {
+			return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? CM4_TE_USEC_VRR_NS :
+									    CM4_TE_USEC_VRR_HS);
+		}
+	}
+}
+
+/* In HS 60Hz mode, TE period is 16.6ms but DDIC vsync period is 8.3ms. */
+#define CM4_HS_VSYNC_PERIOD_US 8333
+/**
+ * cm4_check_command_timing_for_te2 - control timing between a command and DDIC vsync
+ * @ctx: gs_panel struct
+ *
+ * Control the timing of sending the command in the 2nd DDIC vsync period within two contiguous
+ * TE to avoid a 120Hz frame in HS 60Hz mode. This function should be called if the command could
+ * cause a 120Hz frame and mess up the timing, e.g. TE2. The below diagram illustrates the desired
+ * timing of sending the command, where vsync ~= TE rising (vblank) + TE width (te_usec).
+ *
+ *                       send the command
+ *                      /
+ *   TE             .  v          TE
+ *   |              .             |
+ * ----------------------------------
+ *    <------    16.6ms   ---- -->
+ *
+ * vsync          vsync         vsync
+ *   |              |             |
+ * ----------------------------------
+ *    <-- 8.3ms  --> <-- 8.3ms -->
+ *         1st            2nd
+ */
+static void cm4_check_command_timing_for_te2(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct drm_crtc *crtc = NULL;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	ktime_t last_te, last_vsync, now;
+	s64 since_last_vsync_us, temp_us, delay_us;
+
+	if (!pmode) {
+		dev_dbg(dev, "%s: unable to get current mode\n", __func__);
+		return;
+	}
+
+	/* only HS 60Hz mode and changeable TE2 need the timing control */
+	if (drm_mode_vrefresh(&pmode->mode) == ctx->op_hz || ctx->te2.option == TEX_OPT_FIXED)
+		return;
+
+	if (ctx->gs_connector->base.state)
+		crtc = ctx->gs_connector->base.state->crtc;
+	if (!crtc) {
+		dev_dbg(dev, "%s: unable to get crtc\n", __func__);
+		return;
+	}
+
+	drm_crtc_vblank_count_and_time(crtc, &last_te);
+	if (!last_te) {
+		dev_dbg(dev, "%s: unable to get last vblank\n", __func__);
+		return;
+	}
+
+	last_vsync = last_te + cm4_get_te_usec(ctx, pmode);
+	now = ktime_get();
+	since_last_vsync_us = ktime_us_delta(now, last_vsync);
+	temp_us = since_last_vsync_us;
+
+	/**
+	 * While DPU enters/exits hibernation, we may not get the nearest vblank successfully.
+	 * Divided by TE period (vsync period * 2) then we can get the remaining time (remainder).
+	 */
+	temp_us %= (CM4_HS_VSYNC_PERIOD_US * 2);
+
+	/**
+	 * Do nothing if it's greater than a vsync time, i.e. sent in the 2nd vsync period.
+	 * The additional 1ms is for the tolerance.
+	 */
+	if (temp_us > (CM4_HS_VSYNC_PERIOD_US + 1000))
+		return;
+
+	/* Adding 1ms tolerance to make sure the command will be sent in the 2nd vsync period. */
+	delay_us = CM4_HS_VSYNC_PERIOD_US - temp_us + 1000;
+
+	dev_dbg(dev, "%s: te %lld, vsync %lld, now %lld, since_vsync %lld, delay %lld\n", __func__,
+		last_te, last_vsync, now, since_last_vsync_us, delay_us);
+
+	PANEL_ATRACE_BEGIN(__func__);
+	usleep_range(delay_us, delay_us + 100);
+	PANEL_ATRACE_END(__func__);
+}
+
+/* Read temperature and apply appropriate gain into DDIC for burn-in compensation if needed */
+static void cm4_update_disp_therm(struct gs_panel *ctx)
+{
+	/* temperature*1000 in celsius */
+	int temp, ret;
+	const int offset = (ctx->panel_rev > PANEL_REV_PROTO1_1) ? COMP_TEMP_OFFSET : 0;
+	struct device *dev = ctx->dev;
+
+	if (!ctx->thermal || IS_ERR_OR_NULL(ctx->thermal->tz))
+		return;
+
+	if (ctx->panel_state != GPANEL_STATE_NORMAL)
+		return;
+
+	ctx->thermal->pending_temp_update = false;
+
+	ret = thermal_zone_get_temp(ctx->thermal->tz, &temp);
+	if (ret) {
+		dev_err(dev, "%s: fail to read temperature ret:%d\n", __func__, ret);
+		return;
+	}
+
+	temp = DIV_ROUND_CLOSEST(temp, 1000);
+	dev_dbg(dev, "%s: temp=%d\n", __func__, temp);
+	temp = clamp(temp + offset, COMP_TEMP_MIN, COMP_TEMP_MAX);
+	if (temp == ctx->thermal->hw_temp)
+		return;
+
+	dev_dbg(dev, "%s: apply gain into ddic at %ddeg c (offset=%d)\n", __func__, temp, offset);
+
+	PANEL_ATRACE_BEGIN(__func__);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x03, 0x67);
+	GS_DCS_BUF_ADD_CMD(dev, 0x67, temp);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	PANEL_ATRACE_END(__func__);
+
+	ctx->thermal->hw_temp = temp;
+}
+
+static void cm4_update_te2_option(struct gs_panel *ctx, u8 val)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, val);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	notify_panel_te2_option_changed(ctx);
+	dev_dbg(dev, "te2 option is updated to %s\n",
+		(val == CM4_TE2_CHANGEABLE) ? "changeable" :
+		 ((val == CM4_TE2_FIXED_240HZ) ? "fixed:240" : "fixed:120"));
+}
+
+static void cm4_update_te2(struct gs_panel *ctx)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+
+	if (spanel->force_changeable_te2 && ctx->te2.option == TEX_OPT_FIXED) {
+		dev_dbg(ctx->dev, "force to changeable TE2\n");
+		ctx->te2.option = TEX_OPT_CHANGEABLE;
+		cm4_update_te2_option(ctx, CM4_TE2_CHANGEABLE);
+	}
+}
+
+static void cm4_te2_setting(struct gs_panel *ctx)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u32 rising = CM4_TE2_RISING_EDGE_OFFSET;
+	u32 falling = CM4_TE2_FALLING_EDGE_OFFSET;
+	u8 option;
+
+	if (ctx->te2.option == TEX_OPT_FIXED && !spanel->force_changeable_te2)
+		option = (ctx->te2.rate_hz == 240) ? CM4_TE2_FIXED_240HZ :
+						     CM4_TE2_FIXED_120HZ;
+	else
+		option = CM4_TE2_CHANGEABLE;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* TE2 on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x42, 0xF2);
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x0D);
+	/* changeable or 240/120Hz fixed TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, option);
+	/* changeable TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x1E, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF);
+	/* 120Hz fixed TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x22, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF,
+				      (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF);
+	/* 240Hz fixed TE2: set the same width as 120Hz */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x2E, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00, 0x21, 0x00, 0x35, 0x05, 0x7B);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	notify_panel_te2_rate_changed(ctx, 0);
+	notify_panel_te2_option_changed(ctx);
+	dev_dbg(dev, "TE2 setting: option %s, rising=0x%X falling=0x%X\n",
+		(option == TEX_OPT_CHANGEABLE) ? "changeable" :
+		 ((ctx->te2.rate_hz == 240) ? "fixed:240" : "fixed:120"),
+		rising, falling);
+}
+
+static bool cm4_set_te2_rate(struct gs_panel *ctx, u32 rate_hz)
+{
+	struct device *dev = ctx->dev;
+
+	if (ctx->te2.rate_hz == rate_hz)
+		return false;
+
+	if (ctx->te2.option == TEX_OPT_FIXED) {
+		bool lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
+
+		if ((!lp_mode && rate_hz != 120 && rate_hz != 240) ||
+		    (lp_mode && rate_hz != 30)) {
+			dev_warn(dev, "unsupported fixed TE2 rate (%u) in %s mode\n",
+				 rate_hz, lp_mode ? "lp" : "normal");
+			return false;
+		}
+
+		ctx->te2.rate_hz = rate_hz;
+		/**
+		 * Fixed TE2 rate will be limited at 30Hz automatically in AOD mode,
+		 * so we don't need to send any commands.
+		 */
+		if (!lp_mode)
+			cm4_update_te2_option(ctx, (rate_hz == 240) ? CM4_TE2_FIXED_240HZ :
+								      CM4_TE2_FIXED_120HZ);
+	} else if (ctx->te2.option == TEX_OPT_CHANGEABLE) {
+		dev_dbg(dev, "set changeable TE2 rate %uhz\n", rate_hz);
+		ctx->te2.rate_hz = rate_hz;
+	} else {
+		dev_warn(dev, "TE2 option is unsupported (%u)\n", ctx->te2.option);
+		return false;
+	}
+
+	PANEL_ATRACE_INT("te2_rate", ctx->te2.rate_hz);
+
+	return true;
+}
+
+static u32 cm4_get_te2_rate(struct gs_panel *ctx)
+{
+	return ctx->te2.rate_hz;
+}
+
+static bool cm4_set_te2_option(struct gs_panel *ctx, u32 option)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u8 val;
+
+	if (option == ctx->te2.option)
+		return false;
+
+	if (option == TEX_OPT_FIXED) {
+		if (spanel->force_changeable_te2) {
+			dev_dbg(dev, "force changeable TE2 is set\n");
+			return false;
+		}
+		val = (ctx->te2.rate_hz == 240) ? CM4_TE2_FIXED_240HZ : CM4_TE2_FIXED_120HZ;
+	} else if (option == TEX_OPT_CHANGEABLE) {
+		val = CM4_TE2_CHANGEABLE;
+	} else {
+		dev_warn(dev, "unsupported TE2 option (%u)\n", option);
+		return false;
+	}
+
+	cm4_update_te2_option(ctx, val);
+	ctx->te2.option = option;
+
+	return true;
+}
+
+static enum gs_panel_tex_opt cm4_get_te2_option(struct gs_panel *ctx)
+{
+	return ctx->te2.option;
+}
+
+static inline bool is_auto_mode_allowed(struct gs_panel *ctx)
+{
+	/* don't want to enable auto mode/early exit during dimming on */
+	if (ctx->dimming_on)
+		return false;
+
+	if (ctx->idle_data.idle_delay_ms) {
+		const unsigned int delta_ms = gs_panel_get_idle_time_delta(ctx);
+
+		if (delta_ms < ctx->idle_data.idle_delay_ms)
+			return false;
+	}
+
+	return ctx->idle_data.panel_idle_enabled;
+}
+
+static u32 cm4_get_idle_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (spanel->is_mrr_v1)
+		return (vrefresh == 60) ? GIDLE_MODE_ON_SELF_REFRESH : GIDLE_MODE_ON_INACTIVITY;
+
+	return pmode->idle_mode;
+}
+
+static u32 cm4_get_min_idle_vrefresh(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+	int min_idle_vrefresh = ctx->min_vrefresh;
+
+	if ((min_idle_vrefresh < 0) || !is_auto_mode_allowed(ctx))
+		return 0;
+
+	if (min_idle_vrefresh <= 1)
+		min_idle_vrefresh = 1;
+	else if (min_idle_vrefresh <= 10)
+		min_idle_vrefresh = 10;
+	else if (min_idle_vrefresh <= 30)
+		min_idle_vrefresh = 30;
+	else
+		return 0;
+
+	if (min_idle_vrefresh >= vrefresh) {
+		dev_dbg(ctx->dev, "min idle vrefresh (%d) higher than target (%d)\n",
+			min_idle_vrefresh, vrefresh);
+		return 0;
+	}
+
+	dev_dbg(ctx->dev, "%s: min_idle_vrefresh %d\n", __func__, min_idle_vrefresh);
+
+	return min_idle_vrefresh;
+}
+
+static void cm4_set_panel_feat_manual_mode_fi(struct gs_panel *ctx, bool enabled)
+{
+	struct device *dev = ctx->dev;
+	u8 val;
+
+	val = enabled ? 0x33 : 0x00;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val);
+
+	if (!enabled) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x80, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x16);
+	}
+
+	dev_dbg(ctx->dev, "%s: auto fi %s\n", __func__, enabled ? "enabled" : "disabled");
+}
+
+static void cm4_set_panel_feat_te(struct gs_panel *ctx, unsigned long *feat,
+				  const struct gs_panel_mode *pmode)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	bool is_vrr = gs_is_vrr_mode(pmode);
+	u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
+	__maybe_unused u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (test_bit(FEAT_EARLY_EXIT, feat) && !spanel->force_changeable_te) {
+		if (is_vrr && te_freq == 240) {
+			/* 240Hz multi TE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x61);
+			/* TE width */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
+			if (test_bit(FEAT_OP_NS, feat))
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x4A, 0x00, 0x1F, 0x02, 0xC2,
+							0x00, 0x1F);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x1F, 0x00, 0x1F, 0x05, 0x6F,
+							0x00, 0x1F);
+		} else {
+			/* Fixed TE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+			/* TE width */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
+			if (test_bit(FEAT_OP_NS, feat))
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0x60, 0x00, 0x1F, 0x0A,
+							   0x60, 0x00, 0x1F);
+			else if (ctx->panel_rev >= PANEL_REV_EVT1)
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x1E, 0x00, 0x1F, 0x0B, 0x1E,
+							0x00, 0x1F);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x0E, 0x00, 0x1F, 0x0B, 0x0E,
+							0x00, 0x1F);
+#ifndef PANEL_FACTORY_BUILD
+			/* TE Freq */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x02, 0xB9);
+			if (!test_bit(FEAT_OP_NS, feat) && vrefresh == 60)
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x01);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00);
+#endif
+		}
+		ctx->hw_status.te.option = TEX_OPT_FIXED;
+	} else {
+		/* Changeable TE */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04);
+		/* TE width */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x04, 0xB9);
+		if (test_bit(FEAT_OP_NS, feat))
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0x60, 0x00, 0x1F);
+		else if (ctx->panel_rev >= PANEL_REV_EVT1)
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x1E, 0x00, 0x1F);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x0E, 0x00, 0x1F);
+		ctx->hw_status.te.option = TEX_OPT_CHANGEABLE;
+	}
+}
+
+static void cm4_set_panel_feat_hbm_irc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	/*
+	 * "Flat mode" is used to replace IRC on for normal mode and HDR video,
+	 * and "Flat Z mode" is used to replace IRC off for sunlight
+	 * environment.
+	 */
+
+	if (ctx->panel_rev <= PANEL_REV_DVT1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x01, 0x9B, 0x92);
+		if (unlikely(sw_status->irc_mode == IRC_OFF))
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x07);
+		else /* IRC_FLAT_DEFAULT or IRC_FLAT_Z */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x27);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0x00, 0x92);
+		if (sw_status->irc_mode == IRC_FLAT_Z)
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x70, 0x26, 0xFF, 0xDC);
+		else /* IRC_FLAT_DEFAULT or IRC_OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
+	} else {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0x00, 0x92);
+		if (sw_status->irc_mode == IRC_FLAT_Z)
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x70, 0x26, 0xFF, 0xDC);
+		else /* IRC_FLAT_DEFAULT or IRC_OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
+	}
+
+	/* SP settings (burn-in compensation) */
+	if (ctx->panel_rev >= PANEL_REV_DVT1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0xF3, 0x68);
+		if (ctx->panel_rev < PANEL_REV_PVT) {
+			if (sw_status->irc_mode == IRC_FLAT_Z)
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x77, 0x77, 0x86, 0xE1, 0xE1, 0xF0);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x11, 0x1A, 0x13, 0x18, 0x21, 0x18);
+		} else {
+			if (sw_status->irc_mode == IRC_FLAT_Z)
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x79, 0x78, 0x8A, 0xE2, 0xE1, 0xF4);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x10, 0x18, 0x13, 0x18, 0x20, 0x18);
+		}
+	}
+
+	ctx->hw_status.irc_mode = sw_status->irc_mode;
+	dev_info(dev, "%s: irc_mode=%d\n", __func__, ctx->hw_status.irc_mode);
+}
+
+
+static void cm4_set_panel_feat_early_exit(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh)
+{
+	struct device *dev = ctx->dev;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	u8 val = (test_bit(FEAT_EARLY_EXIT, feat) && vrefresh != 80) ? 0x01 : 0x81;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+	if (spanel->is_mrr_v1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+		val = test_bit(FEAT_EARLY_EXIT, feat) ? 0x22 : 0x00;
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val, val, val);
+	}
+}
+
+static void cm4_set_panel_feat_tsp_sync(struct gs_panel *ctx) {
+	struct device *dev = ctx->dev;
+
+	/* Fixed 240Hz TSP Vsync */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x3C, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x19, 0x09); /* Sync On */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x05, 0xF2); /* Global para */
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0xD0); /* 240Hz setting */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x41, 0xB9); /* Global para */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x02); /* TSP Sync setting */
+}
+
+static void cm4_set_panel_feat_frequency(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh,
+					 u32 idle_vrefresh, u32 te_freq, bool is_vrr)
+{
+	struct device *dev = ctx->dev;
+	u8 val;
+	const bool is_ns_mode = test_bit(FEAT_OP_NS, feat);
+
+	/*
+	 * Description: this sequence possibly overrides some configs early-exit
+	 * and operation set, depending on FI mode.
+	 */
+	if (test_bit(FEAT_FRAME_AUTO, feat)) {
+		if (is_ns_mode) {
+			/* threshold setting */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x0C, 0xBD);
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00);
+		} else {
+			/* initial frequency */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x92, 0xBD);
+			if (vrefresh == 60) {
+				val = 0x02;
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported init freq %d (hs)\n",
+						 __func__, vrefresh);
+				/* 120Hz */
+				val = 0x00;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, val);
+		}
+		/* target frequency */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x12, 0xBD);
+		if (is_ns_mode) {
+			if (idle_vrefresh == 30) {
+				val = 0x04;
+			} else if (idle_vrefresh == 10) {
+				val = 0x14;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported target freq %d (ns)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEC;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
+		} else {
+			if (idle_vrefresh == 30) {
+				val = 0x06;
+			} else if (idle_vrefresh == 10) {
+				val = 0x16;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported target freq %d (hs)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEE;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
+		}
+		/* step setting */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x9E, 0xBD);
+		if (is_ns_mode)
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x02, 0x00, 0x06, 0x00, 0x16);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xAE, 0xBD);
+		if (is_ns_mode) {
+			if (idle_vrefresh == 30) {
+				/* 60Hz -> 30Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
+			} else if (idle_vrefresh == 10) {
+				/* 60Hz -> 10Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported freq step to %d (ns)\n",
+						 __func__, idle_vrefresh);
+				/* 60Hz -> 1Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x03, 0x00);
+			}
+		} else {
+			if (vrefresh == 60) {
+				if (idle_vrefresh == 30) {
+					/* 60Hz -> 30Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
+				} else if (idle_vrefresh == 10) {
+					/* 60Hz -> 10Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x00);
+				} else {
+					if (idle_vrefresh != 1)
+						dev_warn(dev,
+							 "%s: unsupported freq step to %d (hs)\n",
+							 __func__, vrefresh);
+					/* 60Hz -> 1Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x03);
+				}
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported freq step from %d (hs)\n",
+						 __func__, vrefresh);
+				if (idle_vrefresh == 30) {
+					/* 120Hz -> 30Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
+				} else if (idle_vrefresh == 10) {
+					/* 120Hz -> 10Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x03, 0x00);
+				} else {
+					if (idle_vrefresh != 1)
+						dev_warn(dev,
+							 "%s: unsupported freq step to %d (hs)\n",
+							 __func__, idle_vrefresh);
+					/* 120Hz -> 1Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x01, 0x03);
+				}
+			}
+		}
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA3);
+	} else { /* manual */
+		if (is_vrr) {
+			/* set gating TE 240hz or 120hz */
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21, (te_freq == 240) ? 0x41 : 0x01);
+		} else {
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+		}
+		if (is_ns_mode) {
+			if (vrefresh == 1) {
+				val = 0x1F;
+			} else if (vrefresh == 10) {
+				val = 0x1B;
+			} else if (vrefresh == 30) {
+				val = 0x19;
+			} else {
+				if (vrefresh != 60)
+					dev_warn(dev, "%s: unsupported manual freq %d (ns)\n",
+						 __func__, vrefresh);
+				/* 60Hz */
+				val = 0x18;
+			}
+		} else {
+			if (vrefresh == 1) {
+				val = 0x07;
+			} else if (vrefresh == 10) {
+				val = 0x03;
+			} else if (vrefresh == 30) {
+				val = 0x02;
+			} else if (vrefresh == 60) {
+				val = 0x01;
+			} else if (vrefresh == 80) {
+				val = 0x04;
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported manual freq %d (hs)\n",
+						 __func__, vrefresh);
+				/* 120Hz */
+				val = 0x00;
+			}
+		}
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+}
+
+static void cm4_set_panel_feat_dbi(struct gs_panel *ctx, u8 value)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x67, 0x69);
+	GS_DCS_BUF_ADD_CMD(dev, 0x69, value);
+	dev_dbg(dev, "%s: DBI: setting %#02X\n", __func__, value);
+}
+
+static u8 cm4_calc_dbi_ref(struct gs_panel *ctx)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	u16 frame_rate = spanel->frame_rate;
+	u8 value = 60 / frame_rate;
+
+	if (frame_rate <= 60)
+		return value;
+
+	if (spanel->dbi_frame_count++ % (frame_rate == 120 ? 2 : 4))
+		return 1;
+	else
+		return 0;
+}
+
+static void cm4_set_dbi_ref(struct gs_panel *ctx, u8 value)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	cm4_set_panel_feat_dbi(ctx, value);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+static void cm4_set_frame_rate(struct gs_panel *ctx, u16 frame_rate)
+{
+	struct device *dev = ctx->dev;
+	struct cm4_panel *spanel = to_spanel(ctx);
+
+	if (frame_rate == spanel->frame_rate ||
+		test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) ||
+		ctx->sw_status.idle_vrefresh == drm_mode_vrefresh(&ctx->current_mode->mode))
+		return;
+
+	dev_dbg(dev, "%s: DBI: updating for %u fps\n", __func__, frame_rate);
+	spanel->frame_rate = frame_rate;
+	cm4_set_dbi_ref(ctx, cm4_calc_dbi_ref(ctx));
+}
+
+/**
+ * cm4_set_panel_feat - configure panel features
+ * @ctx: gs_panel struct
+ * @pmode: gs_panel_mode struct, target panel mode
+ * @idle_vrefresh: target vrefresh rate in auto mode, 0 if disabling auto mode
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context.
+ */
+static void cm4_set_panel_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
+			       bool enforce)
+{
+	struct device *dev = ctx->dev;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+	unsigned long *feat = sw_status->feat;
+	u32 idle_vrefresh = sw_status->idle_vrefresh;
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
+	bool is_vrr = !spanel->is_mrr_v1 && gs_is_vrr_mode(pmode);
+	bool irc_mode_changed;
+	u8 val;
+	DECLARE_BITMAP(changed_feat, FEAT_MAX);
+
+	/* override settings if mrr v2 or vrr */
+	if (!spanel->is_mrr_v1) {
+		if (!test_bit(FEAT_FRAME_AUTO, feat)) {
+			vrefresh = idle_vrefresh ? idle_vrefresh : 1;
+			idle_vrefresh = 0;
+		}
+		set_bit(FEAT_EARLY_EXIT, feat);
+		if (is_vrr) {
+			if (pmode->mode.flags & DRM_MODE_FLAG_NS)
+				set_bit(FEAT_OP_NS, feat);
+			else
+				clear_bit(FEAT_OP_NS, feat);
+		}
+	}
+
+	/* Create bitmap of changed feature values to modify */
+	if (enforce) {
+		bitmap_fill(changed_feat, FEAT_MAX);
+		irc_mode_changed = true;
+	} else {
+		bitmap_xor(changed_feat, feat, hw_status->feat, FEAT_MAX);
+		irc_mode_changed = (sw_status->irc_mode != hw_status->irc_mode);
+		if (bitmap_empty(changed_feat, FEAT_MAX) && vrefresh == hw_status->vrefresh &&
+		    idle_vrefresh == hw_status->idle_vrefresh &&
+		    te_freq == hw_status->te.rate_hz &&
+		    !irc_mode_changed) {
+			dev_dbg(dev, "%s: no changes, skip update\n", __func__);
+			return;
+		}
+	}
+
+	dev_dbg(dev, "hbm=%u irc=%u ns=%u vrr=%u fi=%u@a,%u@m ee=%u rr=%u-%u:%u\n",
+		test_bit(FEAT_HBM, feat), sw_status->irc_mode, test_bit(FEAT_OP_NS, feat),
+		is_vrr, test_bit(FEAT_FRAME_AUTO, feat), test_bit(FEAT_FRAME_MANUAL_FI, feat),
+		test_bit(FEAT_EARLY_EXIT, feat), idle_vrefresh ? idle_vrefresh : vrefresh,
+		drm_mode_vrefresh(&pmode->mode), te_freq);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	/* Unlock */
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	/* TE setting */
+	sw_status->te.rate_hz = te_freq;
+	if (test_bit(FEAT_EARLY_EXIT, changed_feat) || test_bit(FEAT_OP_NS, changed_feat) ||
+	    hw_status->te.rate_hz != te_freq)
+		cm4_set_panel_feat_te(ctx, feat, pmode);
+
+	/*
+	 * HBM IRC setting
+	 */
+	if (irc_mode_changed)
+		cm4_set_panel_feat_hbm_irc(ctx);
+
+	/*
+	 * Operating Mode: NS or HS
+	 *
+	 * Description: the configs could possibly be overrided by frequency setting,
+	 * depending on FI mode.
+	 */
+	if (test_bit(FEAT_OP_NS, changed_feat)) {
+		/* mode set */
+		GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x01);
+		val = test_bit(FEAT_OP_NS, feat) ? 0x18 : 0x00;
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
+	}
+
+	/*
+	 * Early-exit: enable or disable
+	 */
+	cm4_set_panel_feat_early_exit(ctx, feat, vrefresh);
+
+	/*
+	 * Manual FI: enable or disable manual mode FI
+	 */
+	if (test_bit(FEAT_FRAME_MANUAL_FI, changed_feat))
+		cm4_set_panel_feat_manual_mode_fi(ctx, test_bit(FEAT_FRAME_MANUAL_FI, feat));
+
+	/* TSP Sync setting */
+	if (enforce)
+		cm4_set_panel_feat_tsp_sync(ctx);
+
+	/* Reset DBI Reference frequency */
+	if (test_bit(FEAT_FRAME_AUTO, feat) || vrefresh == 120)
+		cm4_set_panel_feat_dbi(ctx, CM4_DBI_REF_DEFAULT);
+
+	/*
+	 * Frequency setting: FI, frequency, idle frequency
+	 */
+	cm4_set_panel_feat_frequency(ctx, feat, vrefresh, idle_vrefresh, te_freq, is_vrr);
+
+	/* Lock */
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+
+	hw_status->vrefresh = vrefresh;
+	hw_status->idle_vrefresh = idle_vrefresh;
+	hw_status->te.rate_hz = te_freq;
+	bitmap_copy(hw_status->feat, feat, FEAT_MAX);
+}
+
+/**
+ * cm4_update_panel_feat - configure panel features with current refresh rate
+ * @ctx: gs_panel struct
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context without changing current refresh rate
+ * and idle setting.
+ */
+static void cm4_update_panel_feat(struct gs_panel *ctx, bool enforce)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	cm4_set_panel_feat(ctx, pmode, enforce);
+}
+
+static void cm4_update_refresh_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
+				    const u32 idle_vrefresh)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	/* TODO: b/308978878 - move refresh control logic to HWC */
+
+	/*
+	 * Skip idle update if going through RRS without refresh rate change. If
+	 * we're switching resolution and refresh rate in the same atomic commit
+	 * (MODE_RES_AND_RR_IN_PROGRESS), we shouldn't skip the update to
+	 * ensure the refresh rate will be set correctly to avoid problems.
+	 */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS) {
+		dev_dbg(ctx->dev, "%s: RRS in progress without RR change, skip\n", __func__);
+		notify_panel_mode_changed(ctx);
+		return;
+	}
+
+	dev_dbg(ctx->dev, "%s: mode: %s set idle_vrefresh: %u\n", __func__, pmode->mode.name,
+		idle_vrefresh);
+
+	if (spanel->is_mrr_v1) {
+		u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+		if (idle_vrefresh)
+			set_bit(FEAT_FRAME_AUTO, sw_status->feat);
+		else
+			clear_bit(FEAT_FRAME_AUTO, sw_status->feat);
+		if (vrefresh == 120 || idle_vrefresh)
+			set_bit(FEAT_EARLY_EXIT, sw_status->feat);
+		else
+			clear_bit(FEAT_EARLY_EXIT, sw_status->feat);
+	}
+	sw_status->idle_vrefresh = idle_vrefresh;
+	/*
+	 * Note: when mode is explicitly set, panel performs early exit to get out
+	 * of idle at next vsync, and will not back to idle until not seeing new
+	 * frame traffic for a while. If idle_vrefresh != 0, try best to guess what
+	 * panel_idle_vrefresh will be soon, and cm4_update_idle_state() in
+	 * new frame commit will correct it if the guess is wrong.
+	 */
+	ctx->idle_data.panel_idle_vrefresh = idle_vrefresh;
+	cm4_set_panel_feat(ctx, pmode, false);
+	notify_panel_mode_changed(ctx);
+
+	dev_dbg(ctx->dev, "%s: display state is notified\n", __func__);
+}
+
+static void cm4_change_frequency(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 idle_vrefresh = 0;
+
+	if (vrefresh > ctx->op_hz) {
+		/* resolution may have been changed without refresh rate change */
+		if (ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
+			notify_panel_mode_changed(ctx);
+		dev_err(ctx->dev, "invalid freq setting: op_hz=%u, vrefresh=%u\n", ctx->op_hz,
+			vrefresh);
+		return;
+	}
+
+	if (cm4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY)
+		idle_vrefresh = cm4_get_min_idle_vrefresh(ctx, pmode);
+
+	/**
+	 * While TE2 is changeable, the idle_vrefresh should be set. We should use the
+	 * idle_vrefresh instead of 0 for the proximity sensor.
+	 */
+	if ((!spanel->is_mrr_v1 && test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat)) ||
+	    (ctx->te2.option == TEX_OPT_CHANGEABLE && !idle_vrefresh))
+		idle_vrefresh = ctx->sw_status.idle_vrefresh;
+
+	cm4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+	ctx->sw_status.te.rate_hz = gs_drm_mode_te_freq(&pmode->mode);
+
+	dev_dbg(ctx->dev, "change to %u hz\n", vrefresh);
+}
+
+static void cm4_panel_idle_notification(struct gs_panel *ctx, u32 display_id, u32 vrefresh,
+					u32 idle_te_vrefresh)
+{
+	char event_string[64];
+	char *envp[] = { event_string, NULL };
+	struct drm_device *dev = ctx->bridge.dev;
+
+	if (!dev) {
+		dev_warn(ctx->dev, "%s: drm_device is null\n", __func__);
+	} else {
+		scnprintf(event_string, sizeof(event_string), "PANEL_IDLE_ENTER=%u,%u,%u",
+			  display_id, vrefresh, idle_te_vrefresh);
+		kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
+	}
+}
+
+static void cm4_wait_one_vblank(struct gs_panel *ctx)
+{
+	struct drm_crtc *crtc = NULL;
+
+	if (ctx->gs_connector->base.state)
+		crtc = ctx->gs_connector->base.state->crtc;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	if (crtc) {
+		int ret = drm_crtc_vblank_get(crtc);
+
+		if (!ret) {
+			drm_crtc_wait_one_vblank(crtc);
+			drm_crtc_vblank_put(crtc);
+		} else {
+			usleep_range(8350, 8500);
+		}
+	} else {
+		usleep_range(8350, 8500);
+	}
+	PANEL_ATRACE_END(__func__);
+}
+
+static bool cm4_set_self_refresh(struct gs_panel *ctx, bool enable)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	u32 idle_vrefresh;
+
+	if (ctx->thermal && ctx->thermal->pending_temp_update && enable)
+		cm4_update_disp_therm(ctx);
+
+	PANEL_ATRACE_INT(__func__, enable);
+
+	if (!spanel->is_mrr_v1) {
+		u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		idle_vrefresh = ctx->sw_status.idle_vrefresh;
+		/* notify the changes of TE2 rate in case DPU enters/exits hibernation */
+		if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != idle_vrefresh &&
+		    test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
+		    cm4_set_te2_rate(ctx, enable ? idle_vrefresh : vrefresh))
+			notify_panel_te2_rate_changed(ctx, 0);
+		return false;
+	}
+
+	dev_dbg(ctx->dev, "%s: %d\n", __func__, enable);
+
+	if (unlikely(!pmode))
+		return false;
+
+	/* self refresh is not supported in lp mode since that always makes use of early exit */
+	if (pmode->gs_mode.is_lp_mode) {
+		/* set 1Hz while self refresh is active, otherwise clear it */
+		ctx->idle_data.panel_idle_vrefresh = enable ? 1 : 0;
+		notify_panel_mode_changed(ctx);
+		return false;
+	}
+
+	idle_vrefresh = cm4_get_min_idle_vrefresh(ctx, pmode);
+
+	if (cm4_get_idle_mode(ctx, pmode) != GIDLE_MODE_ON_SELF_REFRESH) {
+		/*
+		 * if idle mode is on inactivity, may need to update the target fps for auto mode,
+		 * or switch to manual mode if idle should be disabled (idle_vrefresh=0)
+		 */
+		if ((cm4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY) &&
+		    (ctx->sw_status.idle_vrefresh != idle_vrefresh)) {
+			cm4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+			return true;
+		}
+		return false;
+	}
+
+	if (!enable)
+		idle_vrefresh = 0;
+
+	/* if there's no change in idle state then skip cmds */
+	if (ctx->idle_data.panel_idle_vrefresh == idle_vrefresh)
+		return false;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	cm4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+
+	if (idle_vrefresh) {
+		const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		cm4_panel_idle_notification(ctx, 0, vrefresh, 120);
+	} else if (ctx->idle_data.panel_need_handle_idle_exit) {
+		/*
+		 * after exit idle mode with fixed TE at non-120hz, TE may still keep at 120hz.
+		 * If any layer that already be assigned to DPU that can't be handled at 120hz,
+		 * panel_need_handle_idle_exit will be set then we need to wait one vblank to
+		 * avoid underrun issue.
+		 */
+		dev_dbg(ctx->dev, "wait one vblank after exit idle\n");
+		cm4_wait_one_vblank(ctx);
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return true;
+}
+
+static void cm4_set_panel_lp_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	unsigned long *feat = ctx->sw_status.feat;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	u32 idle_vrefresh = sw_status->idle_vrefresh;
+	bool is_auto = (test_bit(FEAT_FRAME_AUTO, feat) || spanel->is_mrr_v1) ? true : false;
+
+	if (!pmode->gs_mode.is_lp_mode)
+		return;
+
+	dev_dbg(dev, "%s: auto=%u rr=%u-%u\n",
+		__func__, is_auto, idle_vrefresh, drm_mode_vrefresh(&pmode->mode));
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	if (is_auto) {
+		/* Default is 1 Hz */
+		u8 val = 0x74;
+
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x18, 0xBD);
+		if (idle_vrefresh == 10)
+			val = 0x08;
+		else if (idle_vrefresh != 1)
+			dev_warn(dev, "%s: unsupported idle vrefresh %u\n",
+				__func__, idle_vrefresh);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x04, 0x00, val);
+		/* Step settings */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xB8, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x08);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xC8, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x03);
+		/* Auto mode */
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA7);
+	} else {
+		/* Manual mode */
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+		/* 30 Hz */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
+	}
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+#ifndef PANEL_FACTORY_BUILD
+static void cm4_update_refresh_ctrl_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const u32 ctrl = ctx->refresh_ctrl;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	unsigned long *feat = ctx->sw_status.feat;
+	u32 min_vrefresh = ctx->sw_status.idle_vrefresh;
+	bool mrr_changed = false;
+	bool idle_vrefresh_changed = false;
+	bool feat_frame_auto_changed = false;
+	bool prev_feat_frame_auto_enabled = test_bit(FEAT_FRAME_AUTO, feat);
+	u32 vrefresh;
+	bool lp_mode;
+
+	if (!pmode)
+		return;
+
+	dev_dbg(ctx->dev, "%s: ctrl=0x%X\n", __func__, ctrl);
+
+	vrefresh = drm_mode_vrefresh(&pmode->mode);
+	lp_mode =  pmode->gs_mode.is_lp_mode;
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) {
+		min_vrefresh = (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) >>
+				GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_OFFSET;
+
+		if (min_vrefresh > vrefresh) {
+			dev_warn(ctx->dev, "%s: min RR %uHz requested, but valid range is 1-%uHz\n",
+				 __func__, min_vrefresh, vrefresh);
+			min_vrefresh = vrefresh;
+		}
+		ctx->sw_status.idle_vrefresh = min_vrefresh;
+		idle_vrefresh_changed = true;
+	}
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_FI_AUTO) {
+		if (min_vrefresh == vrefresh) {
+			clear_bit(FEAT_FRAME_AUTO, feat);
+			clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+		} else if ((min_vrefresh > 1) || lp_mode) {
+			set_bit(FEAT_FRAME_AUTO, feat);
+			clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+		} else {
+			set_bit(FEAT_FRAME_MANUAL_FI, feat);
+			clear_bit(FEAT_FRAME_AUTO, feat);
+		}
+	} else {
+		clear_bit(FEAT_FRAME_AUTO, feat);
+		clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+	}
+
+	if (lp_mode) {
+		cm4_set_panel_lp_feat(ctx, pmode);
+		return;
+	}
+
+	/**
+	 * TODO(b/344478264): avoid setting idle 60Hz in the composer.
+	 *
+	 * 60Hz idle_vrefresh is not supported in non-60Hz mode. Default to 1Hz to save power.
+	 * Otherwise, set to 30Hz to respect proximity sensor's working frequency.
+	 */
+	if (test_bit(FEAT_FRAME_AUTO, feat) && ctx->sw_status.idle_vrefresh == 60 &&
+	    vrefresh != 60) {
+		dev_warn(ctx->dev, "%s: idle_vrefresh 60Hz is not supported in %uHz mode\n",
+			 __func__, vrefresh);
+		ctx->sw_status.idle_vrefresh = (ctx->te2.option == TEX_OPT_CHANGEABLE) ? 30 : 1;
+	}
+
+	if (prev_feat_frame_auto_enabled != test_bit(FEAT_FRAME_AUTO, feat))
+		feat_frame_auto_changed = true;
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_MRR_V1_OVER_V2) {
+		if (gs_is_vrr_mode(ctx->current_mode)) {
+			dev_err(ctx->dev, "%s: using vrr display mode for mrr\n", __func__);
+		} else if (!spanel->is_mrr_v1) {
+			mrr_changed = true;
+			spanel->is_mrr_v1 = true;
+			ctx->gs_connector->ignore_op_rate = true;
+		}
+	} else if (spanel->is_mrr_v1) {
+		mrr_changed = true;
+		spanel->is_mrr_v1 = false;
+		ctx->gs_connector->ignore_op_rate = false;
+	}
+
+	PANEL_ATRACE_INT("idle_vrefresh", ctx->sw_status.idle_vrefresh);
+	PANEL_ATRACE_INT("FEAT_FRAME_AUTO", test_bit(FEAT_FRAME_AUTO, feat));
+
+	/**
+	 * While DPU is not in hibernation, it may keep transferring frames and TE2 will remain
+	 * at peak refresh rate, e.g. 60Hz or 120Hz. Set and notify peak refresh rate even though
+	 * idle_vrefresh is at a lower rate (e.g. 30Hz) to avoid misalignment between the display
+	 * and ALSP.
+	 */
+	if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != ctx->sw_status.idle_vrefresh &&
+	    test_bit(FEAT_FRAME_AUTO, feat) && !ctx->idle_data.self_refresh_active &&
+	    cm4_set_te2_rate(ctx, vrefresh))
+		notify_panel_te2_rate_changed(ctx, 0);
+
+	/**
+	 * The changes of idle vrefresh and frame auto could trigger a 120Hz frame.
+	 * Check whether we need to adjust the timing of sending the commands in these
+	 * conditions.
+	 */
+	if (idle_vrefresh_changed && feat_frame_auto_changed &&
+	    !test_bit(FEAT_FRAME_MANUAL_FI, feat))
+		cm4_check_command_timing_for_te2(ctx);
+
+	if (mrr_changed)
+		cm4_change_frequency(ctx, pmode);
+	else
+		cm4_set_panel_feat(ctx, pmode, false);
+}
+
+static void cm4_refresh_ctrl(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	const u32 ctrl = ctx->refresh_ctrl;
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	cm4_update_refresh_ctrl_feat(ctx, ctx->current_mode);
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_FI_FRAME_COUNT_MASK){
+		/* TODO(b/323251635): parse frame count for inserting multiple frames */
+
+		dev_dbg(dev, "%s: manually inserting frame\n", __func__);
+		PANEL_ATRACE_BEGIN("insert_frame");
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMD(dev, 0xF7, 0x02);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+		PANEL_ATRACE_END("insert_frame");
+	}
+
+	PANEL_ATRACE_END(__func__);
+}
+#endif
+
+static int cm4_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state =
+		drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+	    !new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	if ((ctx->sw_status.idle_vrefresh && old_crtc_state->self_refresh_active) ||
+	    !drm_atomic_crtc_effectively_active(old_crtc_state)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		/* set clock to max refresh rate on self refresh exit or resume due to early exit */
+		mode->clock = gs_bts_fps_to_drm_mode_clock(mode, 120);
+
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n", mode->name,
+				old_crtc_state->self_refresh_active ? "self refresh exit" :
+								      "resume");
+		}
+	} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
+		/* clock hacked in last commit due to self refresh exit or resume, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after self refresh exit or resume\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void cm4_write_display_mode(struct gs_panel *ctx, const struct drm_display_mode *mode)
+{
+	struct device *dev = ctx->dev;
+	u8 val = CM4_WRCTRLD_BCTRL_BIT;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	if (GS_IS_HBM_ON(ctx->hbm_mode))
+		val |= CM4_WRCTRLD_HBM_BIT;
+
+	if (ctx->dimming_on)
+		val |= CM4_WRCTRLD_DIMMING_BIT;
+
+	dev_dbg(dev, "%s(wrctrld:0x%x, hbm: %s, dimming: %s)\n", __func__, val,
+		GS_IS_HBM_ON(ctx->hbm_mode) ? "on" : "off", ctx->dimming_on ? "on" : "off");
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
+	PANEL_ATRACE_END(__func__);
+}
+
+#define CM4_OPR_VAL_LEN 2
+#define CM4_MAX_OPR_VAL 0x3FF
+/* Get OPR (on pixel ratio), the unit is percent */
+static int cm4_get_opr(struct gs_panel *ctx, u8 *opr)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	struct device *dev = ctx->dev;
+	u8 buf[CM4_OPR_VAL_LEN] = { 0 };
+	u16 val;
+	int ret;
+
+	/*TODO(tknelms)DPU_ATRACE_BEGIN(__func__);*/
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB0, 0x00, 0xE7, 0x91);
+	ret = mipi_dsi_dcs_read(dsi, 0x91, buf, CM4_OPR_VAL_LEN);
+	GS_DCS_WRITE_CMDLIST(dev, lock_cmd_f0);
+
+	/*TODO(tknelms)DPU_ATRACE_END(__func__);*/
+
+	if (ret != CM4_OPR_VAL_LEN) {
+		dev_warn(dev, "Failed to read OPR (%d)\n", ret);
+		return ret;
+	}
+
+	val = (buf[0] << 8) | buf[1];
+	*opr = DIV_ROUND_CLOSEST(val * 100, CM4_MAX_OPR_VAL);
+
+	return 0;
+}
+
+static void cm4_disable_acl_mode(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+
+	if (hw_status->acl_mode != ACL_OFF) {
+		GS_DCS_WRITE_CMD(dev, 0x55, 0x00);
+		hw_status->acl_mode = ACL_OFF;
+		dev_info(dev, "%s : set acl_mode off\n", __func__);
+	}
+}
+
+static u8 get_acl_mode_setting(enum gs_acl_mode acl_mode)
+{
+	/*
+	 * CM4 ACL mode and setting:
+	 *
+	 * DVT1 and Later
+	 *    NORMAL     - 10%   (0x01)
+	 *    ENHANCED   - 15%   (0x02)
+	 */
+
+	switch (acl_mode) {
+	case ACL_OFF:
+		return 0x00;
+	case ACL_NORMAL:
+		return 0x01;
+	case ACL_ENHANCED:
+		return 0x02;
+	}
+}
+
+#define CM4_ZA_THRESHOLD_OPR 85
+#define CM4_ACL_ENHANCED_THRESHOLD_DBV 3726
+/* Manage the ACL settings to DDIC that consider the dbv and opr value */
+static void cm4_acl_modes_manager(struct gs_panel *ctx)
+{	struct device *dev = ctx->dev;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+
+	// Check if ACL can be enabled based on conditions
+	bool can_enable_acl = hw_status->dbv >= CM4_ACL_ENHANCED_THRESHOLD_DBV;
+	u8 opr;
+	u8 target_acl_state;
+	bool update_acl_settings;
+
+	if (!can_enable_acl) {
+		cm4_disable_acl_mode(ctx);
+		return;
+	}
+
+	// Check if ACL settings can be wrote based on conditions
+	if (!cm4_get_opr(ctx, &opr)) {
+		update_acl_settings = (opr > CM4_ZA_THRESHOLD_OPR);
+	} else {
+		dev_warn(ctx->dev, "Unable to update acl mode\n");
+		return;
+	}
+
+	if (update_acl_settings) {
+		if (sw_status->acl_mode == hw_status->acl_mode) {
+			dev_dbg(dev, "%s : skip to update acl_mode\n", __func__);
+			return;
+		}
+		target_acl_state = get_acl_mode_setting(sw_status->acl_mode);
+		GS_DCS_WRITE_CMD(dev, 0x55, target_acl_state);
+		hw_status->acl_mode = sw_status->acl_mode;
+		dev_info(dev, "%s: set acl : %d, opr : %hhu\n", __func__, target_acl_state, opr);
+	} else {
+		cm4_disable_acl_mode(ctx);
+	}
+}
+
+/* updated za when acl mode changed */
+static void cm4_set_acl_mode(struct gs_panel *ctx, enum gs_acl_mode mode)
+{
+	bool can_enable_acl = ctx->hw_status.dbv >= CM4_ACL_ENHANCED_THRESHOLD_DBV;
+
+	ctx->sw_status.acl_mode = mode;
+
+	if (can_enable_acl) {
+		if (ctx->sw_status.acl_mode != ctx->hw_status.acl_mode)
+			cm4_acl_modes_manager(ctx);
+	} else
+		cm4_disable_acl_mode(ctx);
+}
+
+static int cm4_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	int ret;
+	u16 brightness;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+		const struct gs_panel_funcs *funcs;
+
+		/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
+		if (spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+			spanel->is_pixel_off = false;
+		}
+
+		funcs = ctx->desc->gs_panel_func;
+		if (funcs && funcs->set_binned_lp)
+			funcs->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	/* Use pixel off command instead of setting DBV 0 */
+	if (!br) {
+		if (!spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMDLIST(dev, pixel_off);
+			spanel->is_pixel_off = true;
+			dev_dbg(dev, "%s: pixel off instead of dbv 0\n", __func__);
+		}
+		return 0;
+	} else if (br && spanel->is_pixel_off) {
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+		spanel->is_pixel_off = false;
+	}
+
+	brightness = (br & 0xff) << 8 | br >> 8;
+	cm4_check_command_timing_for_te2(ctx);
+	ret = gs_dcs_set_brightness(ctx, brightness);
+	if (!ret) {
+		ctx->hw_status.dbv = br;
+		cm4_set_acl_mode(ctx, ctx->sw_status.acl_mode);
+	}
+
+	return ret;
+}
+
+static void cm4_wait_for_vsync_done(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	PANEL_ATRACE_BEGIN(__func__);
+	gs_panel_wait_for_vsync_done(ctx, cm4_get_te_usec(ctx, pmode),
+				     GS_VREFRESH_TO_PERIOD_USEC(ctx->hw_status.vrefresh));
+	PANEL_ATRACE_END(__func__);
+}
+
+static void cm4_enforce_manual_and_peak(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	if (!ctx->current_mode)
+		return;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* manual mode */
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+	/* peak refresh rate */
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
+	} else {
+		GS_DCS_BUF_ADD_CMD(dev, 0x60,
+				   !test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? 0x00 : 0x18);
+	}
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+static void cm4_set_lp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	const u16 brightness = gs_panel_get_brightness(ctx);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	/* enforce manual and peak to have a smooth transition */
+	cm4_enforce_manual_and_peak(ctx);
+
+	/* TODO(b/336580972): Support minRR and FI setting during AOD */
+
+	cm4_wait_for_vsync_done(ctx, ctx->current_mode);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMDLIST(dev, aod_on);
+	/* Fixed TE: sync on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+	/* Enable early exit */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xE8, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22, 0x22, 0x22, 0x22);
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+#ifndef PANEL_FACTORY_BUILD
+	cm4_update_refresh_ctrl_feat(ctx, pmode);
+#else
+	cm4_set_panel_lp_feat(ctx, pmode);
+#endif
+	cm4_set_dbi_ref(ctx, CM4_DBI_REF_DEFAULT);
+	gs_panel_set_binned_lp_helper(ctx, brightness);
+
+	ctx->hw_status.vrefresh = 30;
+	ctx->hw_status.te.rate_hz = 30;
+	ctx->sw_status.te.rate_hz = 30;
+	ctx->sw_status.te.option = TEX_OPT_FIXED;
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(dev, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
+}
+
+static void cm4_set_nolp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	cm4_wait_for_vsync_done(ctx, ctx->current_mode);
+	/* manual mode 30Hz */
+	cm4_enforce_manual_and_peak(ctx);
+
+	/* b/346215549
+	 * ensure AOD off command is sent a bit later than vsync done
+	 */
+	cm4_wait_for_vsync_done(ctx, ctx->current_mode);
+	usleep_range(5000, 5000 + 100);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMDLIST(dev, aod_off);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	cm4_wait_for_vsync_done(ctx, ctx->current_mode);
+#ifndef PANEL_FACTORY_BUILD
+	cm4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+	cm4_set_panel_feat(ctx, pmode, true);
+	/* backlight control and dimming */
+	cm4_write_display_mode(ctx, &pmode->mode);
+	cm4_change_frequency(ctx, pmode);
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(dev, "exit LP mode\n");
+}
+
+static const struct gs_dsi_cmd cm4_init_cmds[] = {
+	/* Enable TE*/
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
+
+	/* CASET: 1279 */
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0xFF),
+	/* PASET: 2855 */
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x0B, 0x27),
+
+	GS_DSI_CMDLIST(unlock_cmd_f0),
+
+	/* FFC: off, 165MHz, MIPI Speed 1368 Mbps */
+	GS_DSI_CMD(0xB0, 0x00, 0x36, 0xC5),
+	GS_DSI_CMD(0xC5, 0x10, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+		   0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
+		   0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00),
+
+	/* enable OPEC (auto still IMG detect off) */
+	GS_DSI_CMD(0xB0, 0x00, 0x1D, 0x63),
+	GS_DSI_CMD(0x63, 0x02, 0x18),
+
+	/* PMIC Fast Discharge off */
+	GS_DSI_CMD(0xB0, 0x00, 0x13, 0xB1),
+	GS_DSI_CMD(0xB1, 0x80),
+	GS_DSI_CMDLIST(freq_update),
+	GS_DSI_CMDLIST(lock_cmd_f0),
+};
+static DEFINE_GS_CMDSET(cm4_init);
+
+static int cm4_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	const struct drm_display_mode *mode;
+	const bool needs_reset = !gs_is_panel_enabled(ctx);
+	bool is_fhd;
+
+	if (!pmode) {
+		dev_err(dev, "no current mode set\n");
+		return -EINVAL;
+	}
+	mode = &pmode->mode;
+	is_fhd = mode->hdisplay == 960;
+
+	dev_info(dev, "%s (%s)\n", __func__, is_fhd ? "fhd" : "wqhd");
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (needs_reset)
+		gs_panel_reset_helper(ctx);
+
+	/* wait TE falling for RRS since DSC and framestart must in the same VSYNC */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
+		cm4_wait_for_vsync_done(ctx, pmode);
+
+	/* DSC related configuration */
+	GS_DCS_WRITE_CMD(dev, 0x9D, 0x01);
+	gs_dcs_write_dsc_config(dev, is_fhd ? &fhd_pps_config : &wqhd_pps_config);
+
+	if (needs_reset) {
+		struct cm4_panel *spanel = to_spanel(ctx);
+
+		GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
+		gs_panel_send_cmdset(ctx, &cm4_init_cmdset);
+		cm4_te2_setting(ctx);
+		spanel->is_pixel_off = false;
+		ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT;
+		if (test_bit(FEAT_OP_NS, ctx->sw_status.feat))
+			spanel->frame_rate = 60;
+		else
+			spanel->frame_rate = 120;
+		spanel->dbi_frame_count = 0;
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC3, is_fhd ? 0x0D : 0x0C);
+	/* 8/10bit config for QHD/FHD */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xF2);
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, is_fhd ? 0x81 : 0x01);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	if (pmode->gs_mode.is_lp_mode) {
+		cm4_set_lp_mode(ctx, pmode);
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+	} else {
+
+#ifndef PANEL_FACTORY_BUILD
+		cm4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+		cm4_update_panel_feat(ctx, true);
+		cm4_write_display_mode(ctx, mode); /* dimming and HBM */
+		cm4_change_frequency(ctx, pmode);
+
+		if (needs_reset || (ctx->panel_state == GPANEL_STATE_BLANK)) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+		}
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int cm4_disable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	int ret;
+
+	dev_info(dev, "%s\n", __func__);
+
+	/* skip disable sequence if going through RRS */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RR_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
+		dev_dbg(dev, "%s: RRS in progress, skip\n", __func__);
+		return 0;
+	}
+
+	ret = gs_panel_disable(panel);
+	if (ret)
+		return ret;
+
+	/* panel register state gets reset after disabling hardware */
+	bitmap_clear(ctx->hw_status.feat, 0, FEAT_MAX);
+	ctx->hw_status.vrefresh = 60;
+	ctx->sw_status.te.rate_hz = 60;
+	ctx->hw_status.te.rate_hz = 60;
+	ctx->hw_status.idle_vrefresh = 0;
+	ctx->hw_status.acl_mode = 0;
+	ctx->hw_status.dbv = 0;
+	ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
+
+	/* set manual and peak before turning off display */
+	cm4_enforce_manual_and_peak(ctx);
+
+	GS_DCS_WRITE_DELAY_CMD(dev, 20, MIPI_DCS_SET_DISPLAY_OFF);
+
+	if (ctx->panel_state == GPANEL_STATE_OFF)
+		GS_DCS_WRITE_DELAY_CMD(dev, 100, MIPI_DCS_ENTER_SLEEP_MODE);
+
+	return 0;
+}
+
+/*
+ * 120hz auto mode takes at least 2 frames to start lowering refresh rate in addition to
+ * time to next vblank. Use just over 2 frames time to consider worst case scenario
+ */
+#define EARLY_EXIT_THRESHOLD_US 17000
+
+/**
+ * cm4_update_idle_state - update panel auto frame insertion state
+ * @ctx: panel struct
+ *
+ * - update timestamp of switching to manual mode in case its been a while since the
+ *   last frame update and auto mode may have started to lower refresh rate.
+ * - trigger early exit by command if it's changeable TE and no switching delay, which
+ *   could result in fast 120 Hz boost and seeing 120 Hz TE earlier, otherwise disable
+ *   auto refresh mode to avoid lowering frequency too fast.
+ */
+static void cm4_update_idle_state(struct gs_panel *ctx)
+{
+	s64 delta_us;
+	struct cm4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (!spanel->is_mrr_v1)
+		return;
+
+	ctx->idle_data.panel_idle_vrefresh = 0;
+	if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat))
+		return;
+
+	delta_us = ktime_us_delta(ktime_get(), ctx->timestamps.last_commit_ts);
+	if (delta_us < EARLY_EXIT_THRESHOLD_US) {
+		dev_dbg(dev, "skip early exit. %lldus since last commit\n", delta_us);
+		return;
+	}
+
+	/* triggering early exit causes a switch to 120hz */
+	ctx->timestamps.last_mode_set_ts = ktime_get();
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (!ctx->idle_data.idle_delay_ms && spanel->force_changeable_te) {
+		dev_dbg(dev, "sending early exit out cmd\n");
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	} else {
+		/* turn off auto mode to prevent panel from lowering frequency too fast */
+		cm4_update_refresh_mode(ctx, ctx->current_mode, 0);
+	}
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void cm4_commit_done(struct gs_panel *ctx)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+
+	if (ctx->current_mode->gs_mode.is_lp_mode)
+		return;
+
+	if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
+		spanel->frame_rate > 60 &&
+		ctx->sw_status.idle_vrefresh < drm_mode_vrefresh(&ctx->current_mode->mode)) {
+		cm4_set_dbi_ref(ctx, cm4_calc_dbi_ref(ctx));
+	}
+
+	/* skip idle update if going through RRS */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
+		dev_dbg(ctx->dev, "%s: RRS in progress, skip\n", __func__);
+		return;
+	}
+
+	cm4_update_idle_state(ctx);
+
+	cm4_acl_modes_manager(ctx);
+
+	if (ctx->thermal && ctx->thermal->pending_temp_update)
+		cm4_update_disp_therm(ctx);
+}
+
+static void cm4_set_hbm_mode(struct gs_panel *ctx, enum gs_hbm_mode mode)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	if (mode == ctx->hbm_mode)
+		return;
+
+	if (unlikely(!pmode))
+		return;
+
+	ctx->hbm_mode = mode;
+
+	if (GS_IS_HBM_ON(mode)) {
+		set_bit(FEAT_HBM, sw_status->feat);
+		/* enforce IRC on for factory builds */
+#ifndef PANEL_FACTORY_BUILD
+		if (mode == GS_HBM_ON_IRC_ON)
+			sw_status->irc_mode = IRC_FLAT_DEFAULT;
+		else
+			sw_status->irc_mode = IRC_FLAT_Z;
+#endif
+		cm4_update_panel_feat(ctx, false);
+		cm4_write_display_mode(ctx, &pmode->mode);
+	} else {
+		clear_bit(FEAT_HBM, sw_status->feat);
+		sw_status->irc_mode = IRC_FLAT_DEFAULT;
+		cm4_write_display_mode(ctx, &pmode->mode);
+		cm4_update_panel_feat(ctx, false);
+	}
+}
+
+static void cm4_set_dimming(struct gs_panel *ctx, bool dimming_on)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	ctx->dimming_on = dimming_on;
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_info(ctx->dev, "in lp mode, skip to update");
+		return;
+	}
+	cm4_write_display_mode(ctx, &pmode->mode);
+}
+
+static void cm4_mode_set(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	cm4_change_frequency(ctx, pmode);
+
+	/**
+	 * TODO(b/344478250): update the refresh rate change in the composer
+	 *
+	 * Notify the changes of TE2 rate while switching the modes with different vrefresh in
+	 * case we miss the refresh rate change report from the composer.
+	 */
+	if (ctx->current_mode && ctx->te2.option == TEX_OPT_CHANGEABLE) {
+		u32 current_vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
+		u32 target_vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		if (current_vrefresh != target_vrefresh && !ctx->idle_data.self_refresh_active &&
+		    cm4_set_te2_rate(ctx, target_vrefresh))
+			notify_panel_te2_rate_changed(ctx, 0);
+	}
+}
+
+static bool cm4_is_mode_seamless(const struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const struct drm_display_mode *c = &ctx->current_mode->mode;
+	const struct drm_display_mode *n = &pmode->mode;
+
+	/* seamless mode set can happen if active region resolution is same */
+	return (c->vdisplay == n->vdisplay) && (c->hdisplay == n->hdisplay);
+}
+
+static int cm4_set_op_hz(struct gs_panel *ctx, unsigned int hz)
+{
+	u32 vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
+
+	if (gs_is_vrr_mode(ctx->current_mode)) {
+		dev_warn(ctx->dev, "%s: should be set via mode switch\n", __func__);
+		return -EINVAL;
+	}
+
+	if (vrefresh > hz || (hz != 60 && hz != 120)) {
+		dev_err(ctx->dev, "invalid op_hz=%d for vrefresh=%d\n", hz, vrefresh);
+		return -EINVAL;
+	}
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	ctx->op_hz = hz;
+	if (hz == 60)
+		set_bit(FEAT_OP_NS, ctx->sw_status.feat);
+	else
+		clear_bit(FEAT_OP_NS, ctx->sw_status.feat);
+
+	if (gs_is_panel_active(ctx))
+		cm4_update_panel_feat(ctx, false);
+	dev_info(ctx->dev, "%s op_hz at %d\n", gs_is_panel_active(ctx) ? "set" : "cache", hz);
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int cm4_read_id(struct gs_panel *ctx)
+{
+	return gs_panel_read_slsi_ddic_id(ctx);
+}
+
+static void cm4_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	u8 build_code = (id & 0xFF00) >> 8;
+	u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
+
+	/* b/306527241 - Ensure EVT 1.0 panels use the correct revision */
+	if (id == 0x22A4040A)
+		rev = 8;
+
+	gs_panel_get_panel_rev(ctx, rev);
+}
+
+static void cm4_normal_mode_work(struct gs_panel *ctx)
+{
+	if (ctx->idle_data.self_refresh_active) {
+		cm4_update_disp_therm(ctx);
+	} else {
+		if (ctx->thermal)
+			ctx->thermal->pending_temp_update = true;
+	}
+}
+
+static void cm4_pre_update_ffc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* FFC off */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void cm4_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s: hs_clk_mbps: current=%d, target=%d\n", __func__,
+		ctx->dsi_hs_clk_mbps, hs_clk_mbps);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	if (hs_clk_mbps != MIPI_DSI_FREQ_MBPS_DEFAULT &&
+	    hs_clk_mbps != MIPI_DSI_FREQ_MBPS_ALTERNATIVE) {
+		dev_warn(dev, "%s: invalid hs_clk_mbps=%d for FFC\n", __func__, hs_clk_mbps);
+	} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
+		dev_info(dev, "%s: updating for hs_clk_mbps=%d\n", __func__, hs_clk_mbps);
+		ctx->dsi_hs_clk_mbps = hs_clk_mbps;
+
+		/* Update FFC */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x37, 0xC5);
+		if (hs_clk_mbps == MIPI_DSI_FREQ_MBPS_DEFAULT)
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00,
+					   0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
+					   0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00,
+					   0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40,
+					   0x00);
+		else /* MIPI_DSI_FREQ_MBPS_ALTERNATIVE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x51, 0xFD, 0x40, 0x00,
+					   0x40, 0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40,
+					   0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00,
+					   0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00, 0x40,
+					   0x00);
+	}
+
+	/* FFC on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x11);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 350,
+	.te_var = 1,
+};
+
+static const u32 cm4_bl_range[] = {
+	94, 180, 270, 360, 3271
+};
+
+#define CM4_WQHD_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &wqhd_pps_config,\
+}
+#define CM4_FHD_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &fhd_pps_config,\
+}
+
+static const struct gs_panel_mode_array cm4_modes = {
+#ifdef PANEL_FACTORY_BUILD
+	.num_modes = 6,
+#else
+	.num_modes = 8,
+#endif
+	.modes = {
+		/* MRR modes */
+#ifdef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				.name = "1280x2856x1@1",
+				DRM_MODE_TIMING(1, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = 0,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1280x2856x10@10",
+				DRM_MODE_TIMING(10, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = 0,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1280x2856x30@30",
+				DRM_MODE_TIMING(30, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = 0,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1280x2856x80@80",
+				DRM_MODE_TIMING(80, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = 0,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif /* PANEL_FACTORY_BUILD */
+		{
+			.mode = {
+				/* 60Hz supports HS/NS, see cm4_get_te_usec for widths used */
+				.name = "1280x2856x60@60",
+				DRM_MODE_TIMING(60, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1280x2856x120@120",
+				DRM_MODE_TIMING(120, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_120HZ_HS,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#ifndef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				/* 60Hz supports HS/NS, see cm4_get_te_usec for widths used */
+				.name = "960x2142x60@60",
+				DRM_MODE_TIMING(60, 960, 80, 24, 46, 2142, 12, 4, 32),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = CM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "960x2142x120@120",
+				DRM_MODE_TIMING(120, 960, 80, 24, 46, 2142, 12, 4, 32),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_120HZ_HS,
+				.bpc = 8,
+				.dsc = CM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		/* VRR modes */
+		{
+			.mode = {
+				.name = "1280x2856x120@240",
+				DRM_VRR_MODE_TIMING(120, 240, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				/* aligned to bootloader resolution */
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "960x2142x120@240",
+				DRM_VRR_MODE_TIMING(120, 240, 960, 80, 24, 46, 2142, 12, 4, 32),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1280x2856x120@120",
+				DRM_VRR_MODE_TIMING(120, 120, 1280, 80, 24, 46, 2856, 12, 4, 28),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "960x2142x120@120",
+				DRM_VRR_MODE_TIMING(120, 120, 960, 80, 24, 46, 2142, 12, 4, 32),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = CM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = CM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = CM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = CM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif /* !PANEL_FACTORY_BUILD */
+	},/* .modes */
+}; /* cm4_modes */
+
+/* change hsa and hbp to avoid conflicting to normal mode 30Hz */
+static const struct gs_panel_mode_array cm4_lp_modes = {
+#ifdef PANEL_FACTORY_BUILD
+	.num_modes = 1,
+#else
+	.num_modes = 2,
+#endif
+	.modes = {
+		{
+			.mode = {
+				.name = "1280x2856x30@30",
+				/* hsa and hbp are different from normal 30 Hz */
+				DRM_MODE_TIMING(30, 1280, 80, 26, 44, 2856, 12, 4, 28),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1096,
+				.bpc = 8,
+				.dsc = CM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+#ifndef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				.name = "960x2142x30@30",
+				DRM_MODE_TIMING(30, 960, 80, 26, 44, 2142, 12, 4, 32),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1096,
+				.bpc = 8,
+				.dsc = CM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+#endif
+	}, /* .modes */
+}; /* cm4_lp_modes */
+
+static struct gs_thermal_data cm4_thermal_data = {
+	/* ddic default temp */
+	.hw_temp = 25,
+	.pending_temp_update = false,
+};
+
+static void cm4_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+	struct cm4_panel *spanel;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot) {
+		goto panel_out;
+	}
+
+	spanel = to_spanel(ctx);
+
+	gs_panel_debugfs_create_cmdset(csroot, &cm4_init_cmdset, "init");
+	debugfs_create_bool("force_changeable_te", 0644, panel_root, &spanel->force_changeable_te);
+	debugfs_create_bool("force_changeable_te2", 0644, panel_root,
+			    &spanel->force_changeable_te2);
+	debugfs_create_bool("force_za_off", 0644, panel_root, &spanel->force_za_off);
+	debugfs_create_u32("hw_acl_setting", 0644, panel_root, &ctx->hw_status.acl_mode);
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+#endif
+}
+
+static void cm4_panel_init(struct gs_panel *ctx)
+{
+	struct cm4_panel *spanel = to_spanel(ctx);
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+#ifdef PANEL_FACTORY_BUILD
+	spanel->is_mrr_v1 = true;
+	ctx->idle_data.panel_idle_enabled = false;
+	set_bit(FEAT_FRAME_MANUAL_FI, ctx->sw_status.feat);
+#else
+	spanel->is_mrr_v1 = false;
+	cm4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+	spanel->frame_rate = 120;
+	spanel->dbi_frame_count = 0;
+	ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
+	/* default fixed TE2 120Hz */
+	ctx->te2.option = TEX_OPT_FIXED;
+	ctx->te2.rate_hz = 120;
+
+	if (!ctx->thermal) {
+		dev_err(ctx->dev, "%s: error retrieving thermal data\n", __func__);
+		return;
+	}
+
+	ctx->thermal->tz = thermal_zone_get_zone_by_name("disp_therm");
+	if (IS_ERR_OR_NULL(ctx->thermal->tz))
+		dev_err(ctx->dev, "%s: failed to get thermal zone disp_therm\n", __func__);
+	/* re-init panel to decouple bootloader settings */
+	if (pmode) {
+		dev_info(ctx->dev, "%s: set mode: %s\n", __func__, pmode->mode.name);
+		ctx->sw_status.idle_vrefresh = 0;
+		cm4_set_panel_feat(ctx, pmode, true);
+		cm4_change_frequency(ctx, pmode);
+		cm4_te2_setting(ctx);
+	}
+}
+
+static int cm4_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct cm4_panel *spanel;
+	struct gs_panel *ctx;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+	ctx = &spanel->base;
+	ctx->thermal = devm_kzalloc(&dsi->dev, sizeof(*ctx->thermal), GFP_KERNEL);
+	if (!ctx->thermal) {
+		devm_kfree(&dsi->dev, spanel);
+		return -ENOMEM;
+	}
+
+	ctx->op_hz = 120;
+	ctx->hw_status.vrefresh = 60;
+	ctx->hw_status.te.rate_hz = 60;
+	ctx->hw_status.acl_mode = ACL_OFF;
+	ctx->hw_status.dbv = 0;
+	ctx->thermal = &cm4_thermal_data;
+	clear_bit(FEAT_ZA, ctx->hw_status.feat);
+	spanel->is_pixel_off = false;
+
+	return gs_dsi_panel_common_init(dsi, ctx);
+}
+
+static int cm4_panel_config(struct gs_panel *ctx);
+
+static const struct drm_panel_funcs cm4_drm_funcs = {
+	.disable = cm4_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = cm4_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = cm4_debugfs_init,
+};
+
+static const struct gs_panel_funcs cm4_gs_funcs = {
+	.set_brightness = cm4_set_brightness,
+	.set_lp_mode = cm4_set_lp_mode,
+	.set_nolp_mode = cm4_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_hbm_mode = cm4_set_hbm_mode,
+	.set_dimming = cm4_set_dimming,
+	.is_mode_seamless = cm4_is_mode_seamless,
+	.mode_set = cm4_mode_set,
+	.panel_init = cm4_panel_init,
+	.panel_config = cm4_panel_config,
+	.get_panel_rev = cm4_get_panel_rev,
+	.get_te2_edges = gs_panel_get_te2_edges_helper,
+	.set_te2_edges = gs_panel_set_te2_edges_helper,
+	.update_te2 = cm4_update_te2,
+	.commit_done = cm4_commit_done,
+	.atomic_check = cm4_atomic_check,
+	.set_self_refresh = cm4_set_self_refresh,
+#ifndef PANEL_FACTORY_BUILD
+	.refresh_ctrl = cm4_refresh_ctrl,
+#endif
+	.set_frame_rate = cm4_set_frame_rate,
+	.set_op_hz = cm4_set_op_hz,
+	.read_id = cm4_read_id,
+	.get_te_usec = cm4_get_te_usec,
+	.set_acl_mode = cm4_set_acl_mode,
+	.run_normal_mode_work = cm4_normal_mode_work,
+	.pre_update_ffc = cm4_pre_update_ffc,
+	.update_ffc = cm4_update_ffc,
+	.set_te2_rate = cm4_set_te2_rate,
+	.get_te2_rate = cm4_get_te2_rate,
+	.set_te2_option = cm4_set_te2_option,
+	.get_te2_option = cm4_get_te2_option,
+};
+
+static const struct gs_brightness_configuration cm4_btr_configs[] = {
+	{
+		.panel_rev = PANEL_REV_EVT1 | PANEL_REV_LATEST,
+		.default_brightness = 1209, /* dbv_for_140_nits */
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1250,
+				},
+				.level = {
+					.min = 176,
+					.max = 3271,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 61,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1250,
+					.max = 2050,
+				},
+				.level = {
+					.min = 3272,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 61,
+					.max = 100,
+				},
+			},
+		},
+	},
+	{
+		.panel_rev = PANEL_REV_PROTO1_1,
+		.default_brightness = 1209, /* dbv_for_140_nits */
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1250,
+				},
+				.level = {
+					.min = 184,
+					.max = 3427,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 68,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1250,
+					.max = 1850,
+				},
+				.level = {
+					.min = 3428,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 68,
+					.max = 100,
+				},
+			},
+		},
+	},
+	{
+		.panel_rev = PANEL_REV_PROTO1,
+		.default_brightness = 1209, /* dbv_for_140_nits */
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1200,
+				},
+				.level = {
+					.min = 186,
+					.max = 3406,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 67,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1200,
+					.max = 1800,
+				},
+				.level = {
+					.min = 3407,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 67,
+					.max = 100,
+				},
+			},
+		},
+	},
+};
+
+static struct gs_panel_brightness_desc cm4_brightness_desc = {
+	.max_luminance = 10000000,
+	.max_avg_luminance = 10000000,
+	.min_luminance = 5,
+};
+
+static struct gs_panel_reg_ctrl_desc cm4_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 1},
+		{PANEL_REG_ID_VCI, 10},
+	},
+	.reg_ctrl_post_enable = {
+		{PANEL_REG_ID_VDDD, 1},
+	},
+	.reg_ctrl_pre_disable = {
+		{PANEL_REG_ID_VDDD, 1},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VCI, 1},
+		{PANEL_REG_ID_VDDI, 1},
+	},
+};
+
+static struct gs_panel_desc gs_cm4 = {
+	.data_lane_cnt = 4,
+	.dbv_extra_frame = true,
+	.brightness_desc = &cm4_brightness_desc,
+	.reg_ctrl_desc = &cm4_reg_ctrl_desc,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.bl_range = cm4_bl_range,
+	.bl_num_ranges = ARRAY_SIZE(cm4_bl_range),
+	.modes = &cm4_modes,
+	.lp_modes = &cm4_lp_modes,
+	.binned_lp = cm4_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(cm4_binned_lp),
+	.rr_switch_duration = 1,
+	.has_off_binned_lp_entry = false,
+	.is_idle_supported = true,
+	.panel_func = &cm4_drm_funcs,
+	.gs_panel_func = &cm4_gs_funcs,
+	.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT,
+	.reset_timing_ms = { 1, 1, 5 },
+	.normal_mode_work_delay_ms = 30000,
+	/* TODO(b/335574896): delay time in NS mode */
+	/**
+	 * While the proximity is active, we will set the min vrefresh to 30Hz with auto
+	 * frame insertion. Thus when the display is idle, we will have the refresh rate
+	 * change from 120Hz to 30Hz. According to the measurement, the pattern is: 3x120Hz
+	 * frame > 1x60Hz frame > 30Hz. With additional tolerance due to scheduler in the
+	 * kernel, the delay of notification is estimated to be ~50ms.
+	 */
+	.notify_te2_rate_changed_work_delay_ms = 50,
+};
+
+static int cm4_panel_config(struct gs_panel *ctx)
+{
+	gs_panel_model_init(ctx, PROJECT, 0);
+
+	return gs_panel_update_brightness_desc(&cm4_brightness_desc, cm4_btr_configs,
+					       ARRAY_SIZE(cm4_btr_configs), ctx->panel_rev);
+}
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-cm4", .data = &gs_cm4 },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_of_match);
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = cm4_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-cm4",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Taylor Nelms <tknelms@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google CM4 panel driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/panel-gs-km4.c b/panel-gs-km4.c
new file mode 100644
index 0000000..d4b8d0b
--- /dev/null
+++ b/panel-gs-km4.c
@@ -0,0 +1,2939 @@
+/* SPDX-License-Identifier: MIT */
+
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_vblank.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/thermal.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+/**
+ * struct km4_panel - panel specific info
+ *
+ * This struct maintains km4 panel specific info. The variables with the prefix hw_ keep
+ * track of the features that were actually committed to hardware, and should be modified
+ * after sending cmds to panel, i.e., updating hw state.
+ */
+struct km4_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+	/** @force_changeable_te: force changeable TE (instead of fixed) during early exit */
+	bool force_changeable_te;
+	/** @force_changeable_te2: force changeable TE2 for monitoring refresh rate */
+	bool force_changeable_te2;
+	/** @force_za_off: force to turn off zonal attenuation */
+	bool force_za_off;
+	/**
+	 * @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
+	 *		  panel can recover to normal mode after entering pixel-off state.
+	 */
+	bool is_pixel_off;
+	/**
+	 * @is_mrr_v1: indicates panel is running in mrr v1 mode
+	 */
+	bool is_mrr_v1;
+	/**
+	 * @frame_rate: real time frame rate
+	 */
+	u16 frame_rate;
+	/**
+	 * @dbi_frame_count: frame counter to alternatively set dbi ref if frame rate > 60
+	 */
+	u64 dbi_frame_count;
+};
+
+#define to_spanel(ctx) container_of(ctx, struct km4_panel, base)
+
+/* DSCv1.2a 1344x2992 */
+static const struct drm_dsc_config wqhd_pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_width = 672,
+	.slice_height = 34,
+	.slice_count = 2,
+	.simple_422 = false,
+	.pic_width = 1344,
+	.pic_height = 2992,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 592,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 9,
+	.scale_increment_interval = 932,
+	.nfl_bpg_offset = 745,
+	.slice_bpg_offset = 616,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 672,
+	.dsc_version_minor = 2,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+/* DSCv1.2a 1008x2244 */
+static const struct drm_dsc_config fhd_pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_width = 504,
+	.slice_height = 34,
+	.slice_count = 2,
+	.simple_422 = false,
+	.pic_width = 1008,
+	.pic_height = 2244,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 508,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 7,
+	.scale_increment_interval = 810,
+	.nfl_bpg_offset = 745,
+	.slice_bpg_offset = 821,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 504,
+	.dsc_version_minor = 2,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+#define KM4_WRCTRLD_DIMMING_BIT 0x08
+#define KM4_WRCTRLD_BCTRL_BIT 0x20
+#define KM4_WRCTRLD_HBM_BIT 0xC0
+
+#define KM4_TE2_CHANGEABLE 0x04
+#define KM4_TE2_FIXED_120HZ 0x51
+#define KM4_TE2_FIXED_240HZ 0x41
+#define KM4_TE2_RISING_EDGE_OFFSET 0x20
+#define KM4_TE2_FALLING_EDGE_OFFSET 0x57
+
+#define KM4_TE_USEC_120HZ_HS 273
+#define KM4_TE_USEC_60HZ_HS 8500
+#define KM4_TE_USEC_60HZ_NS 1588
+
+#define KM4_TE_USEC_VRR_HS 273
+#define KM4_TE_USEC_VRR_NS 1588
+
+#define KM4_DBI_REF_DEFAULT 60
+
+#define WIDTH_MM 70
+#define HEIGHT_MM 156
+
+#define MIPI_DSI_FREQ_MBPS_DEFAULT 1368
+#define MIPI_DSI_FREQ_MBPS_ALTERNATIVE 1288
+
+#define COMP_TEMP_MIN 10
+#define COMP_TEMP_MAX 49
+#define COMP_TEMP_OFFSET -10
+
+#define PROJECT "KM4"
+
+static const u8 unlock_cmd_f0[] = { 0xF0, 0x5A, 0x5A };
+static const u8 lock_cmd_f0[] = { 0xF0, 0xA5, 0xA5 };
+static const u8 freq_update[] = { 0xF7, 0x0F };
+static const u8 aod_on[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24 };
+static const u8 aod_off[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20 };
+static const u8 pixel_off[] = { 0x22 };
+
+static const struct gs_dsi_cmd km4_lp_night_cmds[] = {
+	/* AOD Night Mode, 2nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0xB0),
+};
+
+static const struct gs_dsi_cmd km4_lp_low_cmds[] = {
+	/* AOD Low Mode, 10nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x01, 0x6D),
+};
+
+static const struct gs_dsi_cmd km4_lp_high_cmds[] = {
+	/* AOD High Mode, 50nit */
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x02, 0xF6),
+};
+
+static const struct gs_binned_lp km4_binned_lp[] = {
+	/* night threshold 4 nits */
+	BINNED_LP_MODE_TIMING("night", 240, km4_lp_night_cmds, KM4_TE2_RISING_EDGE_OFFSET,
+			      KM4_TE2_FALLING_EDGE_OFFSET),
+	/* low threshold 40 nits */
+	BINNED_LP_MODE_TIMING("low", 686, km4_lp_low_cmds, KM4_TE2_RISING_EDGE_OFFSET,
+			      KM4_TE2_FALLING_EDGE_OFFSET),
+	BINNED_LP_MODE_TIMING("high", 3271, km4_lp_high_cmds, KM4_TE2_RISING_EDGE_OFFSET,
+			      KM4_TE2_FALLING_EDGE_OFFSET),
+};
+
+static unsigned int km4_get_te_usec(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (vrefresh != 60 || gs_is_vrr_mode(pmode)) {
+		return pmode->gs_mode.te_usec;
+	} else {
+		if (spanel->is_mrr_v1) {
+			return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? KM4_TE_USEC_60HZ_NS :
+									    KM4_TE_USEC_60HZ_HS);
+		} else {
+			return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? KM4_TE_USEC_VRR_NS :
+									    KM4_TE_USEC_VRR_HS);
+		}
+	}
+}
+
+/* In HS 60Hz mode, TE period is 16.6ms but DDIC vsync period is 8.3ms. */
+#define KM4_HS_VSYNC_PERIOD_US 8333
+/**
+ * km4_check_command_timing_for_te2 - control timing between a command and DDIC vsync
+ * @ctx: gs_panel struct
+ *
+ * Control the timing of sending the command in the 2nd DDIC vsync period within two contiguous
+ * TE to avoid a 120Hz frame in HS 60Hz mode. This function should be called if the command could
+ * cause a 120Hz frame and mess up the timing, e.g. TE2. The below diagram illustrates the desired
+ * timing of sending the command, where vsync ~= TE rising (vblank) + TE width (te_usec).
+ *
+ *                       send the command
+ *                      /
+ *   TE             .  v          TE
+ *   |              .             |
+ * ----------------------------------
+ *    <------    16.6ms   ---- -->
+ *
+ * vsync          vsync         vsync
+ *   |              |             |
+ * ----------------------------------
+ *    <-- 8.3ms  --> <-- 8.3ms -->
+ *         1st            2nd
+ */
+static void km4_check_command_timing_for_te2(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct drm_crtc *crtc = NULL;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	ktime_t last_te, last_vsync, now;
+	s64 since_last_vsync_us, temp_us, delay_us;
+
+	if (!pmode) {
+		dev_dbg(dev, "%s: unable to get current mode\n", __func__);
+		return;
+	}
+
+	/* only HS 60Hz mode and changeable TE2 need the timing control */
+	if (drm_mode_vrefresh(&pmode->mode) == ctx->op_hz || ctx->te2.option == TEX_OPT_FIXED)
+		return;
+
+	if (ctx->gs_connector->base.state)
+		crtc = ctx->gs_connector->base.state->crtc;
+	if (!crtc) {
+		dev_dbg(dev, "%s: unable to get crtc\n", __func__);
+		return;
+	}
+
+	drm_crtc_vblank_count_and_time(crtc, &last_te);
+	if (!last_te) {
+		dev_dbg(dev, "%s: unable to get last vblank\n", __func__);
+		return;
+	}
+
+	last_vsync = last_te + km4_get_te_usec(ctx, pmode);
+	now = ktime_get();
+	since_last_vsync_us = ktime_us_delta(now, last_vsync);
+	temp_us = since_last_vsync_us;
+
+	/**
+	 * While DPU enters/exits hibernation, we may not get the nearest vblank successfully.
+	 * Divided by TE period (vsync period * 2) then we can get the remaining time (remainder).
+	 */
+	temp_us %= (KM4_HS_VSYNC_PERIOD_US * 2);
+
+	/**
+	 * Do nothing if it's greater than a vsync time, i.e. sent in the 2nd vsync period.
+	 * The additional 1ms is for the tolerance.
+	 */
+	if (temp_us > (KM4_HS_VSYNC_PERIOD_US + 1000))
+		return;
+
+	/* Adding 1ms tolerance to make sure the command will be sent in the 2nd vsync period. */
+	delay_us = KM4_HS_VSYNC_PERIOD_US - temp_us + 1000;
+
+	dev_dbg(dev, "%s: te %lld, vsync %lld, now %lld, since_vsync %lld, delay %lld\n", __func__,
+		last_te, last_vsync, now, since_last_vsync_us, delay_us);
+
+	PANEL_ATRACE_BEGIN(__func__);
+	usleep_range(delay_us, delay_us + 100);
+	PANEL_ATRACE_END(__func__);
+}
+
+/* Read temperature and apply appropriate gain into DDIC for burn-in compensation if needed */
+static void km4_update_disp_therm(struct gs_panel *ctx)
+{
+	/* temperature*1000 in celsius */
+	int temp, ret;
+	const int offset = (ctx->panel_rev > PANEL_REV_PROTO1_1) ? COMP_TEMP_OFFSET : 0;
+	struct device *dev = ctx->dev;
+
+	if (!ctx->thermal || IS_ERR_OR_NULL(ctx->thermal->tz))
+		return;
+
+	if (ctx->panel_state != GPANEL_STATE_NORMAL)
+		return;
+
+	ctx->thermal->pending_temp_update = false;
+
+	ret = thermal_zone_get_temp(ctx->thermal->tz, &temp);
+	if (ret) {
+		dev_err(dev, "%s: fail to read temperature ret:%d\n", __func__, ret);
+		return;
+	}
+
+	temp = DIV_ROUND_CLOSEST(temp, 1000);
+	dev_dbg(dev, "%s: temp=%d\n", __func__, temp);
+	temp = clamp(temp + offset, COMP_TEMP_MIN, COMP_TEMP_MAX);
+	if (temp == ctx->thermal->hw_temp)
+		return;
+
+	dev_dbg(dev, "%s: apply gain into ddic at %ddeg c (offset=%d)\n", __func__, temp, offset);
+
+	PANEL_ATRACE_BEGIN(__func__);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x03, 0x67);
+	GS_DCS_BUF_ADD_CMD(dev, 0x67, temp);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	PANEL_ATRACE_END(__func__);
+
+	ctx->thermal->hw_temp = temp;
+}
+
+static void km4_update_te2_option(struct gs_panel *ctx, u8 val)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, val);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	notify_panel_te2_option_changed(ctx);
+	dev_dbg(dev, "te2 option is updated to %s\n",
+		(val == KM4_TE2_CHANGEABLE) ? "changeable" :
+		 ((val == KM4_TE2_FIXED_240HZ) ? "fixed:240" : "fixed:120"));
+}
+
+static void km4_update_te2(struct gs_panel *ctx)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+
+	if (spanel->force_changeable_te2 && ctx->te2.option == TEX_OPT_FIXED) {
+		dev_dbg(ctx->dev, "force to changeable TE2\n");
+		ctx->te2.option = TEX_OPT_CHANGEABLE;
+		km4_update_te2_option(ctx, KM4_TE2_CHANGEABLE);
+	}
+}
+
+static void km4_te2_setting(struct gs_panel *ctx)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u32 rising = KM4_TE2_RISING_EDGE_OFFSET;
+	u32 falling = KM4_TE2_FALLING_EDGE_OFFSET;
+	u8 option;
+
+	if (ctx->te2.option == TEX_OPT_FIXED && !spanel->force_changeable_te2)
+		option = (ctx->te2.rate_hz == 240) ? KM4_TE2_FIXED_240HZ :
+						     KM4_TE2_FIXED_120HZ;
+	else
+		option = KM4_TE2_CHANGEABLE;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* TE2 on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x42, 0xF2);
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x0D);
+	/* changeable or 240/120Hz fixed TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, option);
+	/* changeable TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x1E, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF);
+	/* 120Hz fixed TE2 */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x22, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF,
+				      (rising >> 8) & 0xF, rising & 0xFF,
+				      (falling >> 8) & 0xF, falling & 0xFF);
+	/* 240Hz fixed TE2: set the same width as 120Hz */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x2E, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00, 0x21, 0x00, 0x37, 0x05, 0xB9);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	notify_panel_te2_rate_changed(ctx, 0);
+	notify_panel_te2_option_changed(ctx);
+	dev_dbg(dev, "TE2 setting: option %s, rising=0x%X falling=0x%X\n",
+		(option == TEX_OPT_CHANGEABLE) ? "changeable" :
+		 ((ctx->te2.rate_hz == 240) ? "fixed:240" : "fixed:120"),
+		rising, falling);
+}
+
+static bool km4_set_te2_rate(struct gs_panel *ctx, u32 rate_hz)
+{
+	struct device *dev = ctx->dev;
+
+	if (ctx->te2.rate_hz == rate_hz)
+		return false;
+
+	if (ctx->te2.option == TEX_OPT_FIXED) {
+		bool lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
+
+		if ((!lp_mode && rate_hz != 120 && rate_hz != 240) ||
+		    (lp_mode && rate_hz != 30)) {
+			dev_warn(dev, "unsupported fixed TE2 rate (%u) in %s mode\n",
+				 rate_hz, lp_mode ? "lp" : "normal");
+			return false;
+		}
+
+		ctx->te2.rate_hz = rate_hz;
+		/**
+		 * Fixed TE2 rate will be limited at 30Hz automatically in AOD mode,
+		 * so we don't need to send any commands.
+		 */
+		if (!lp_mode)
+			km4_update_te2_option(ctx, (rate_hz == 240) ? KM4_TE2_FIXED_240HZ :
+								      KM4_TE2_FIXED_120HZ);
+	} else if (ctx->te2.option == TEX_OPT_CHANGEABLE) {
+		dev_dbg(dev, "set changeable TE2 rate %uhz\n", rate_hz);
+		ctx->te2.rate_hz = rate_hz;
+	} else {
+		dev_warn(dev, "TE2 option is unsupported (%u)\n", ctx->te2.option);
+		return false;
+	}
+
+	PANEL_ATRACE_INT("te2_rate", ctx->te2.rate_hz);
+
+	return true;
+}
+
+static u32 km4_get_te2_rate(struct gs_panel *ctx)
+{
+	return ctx->te2.rate_hz;
+}
+
+static bool km4_set_te2_option(struct gs_panel *ctx, u32 option)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	u8 val;
+
+	if (option == ctx->te2.option)
+		return false;
+
+	if (option == TEX_OPT_FIXED) {
+		if (spanel->force_changeable_te2) {
+			dev_dbg(dev, "force changeable TE2 is set\n");
+			return false;
+		}
+		val = (ctx->te2.rate_hz == 240) ? KM4_TE2_FIXED_240HZ : KM4_TE2_FIXED_120HZ;
+	} else if (option == TEX_OPT_CHANGEABLE) {
+		val = KM4_TE2_CHANGEABLE;
+	} else {
+		dev_warn(dev, "unsupported TE2 option (%u)\n", option);
+		return false;
+	}
+
+	km4_update_te2_option(ctx, val);
+	ctx->te2.option = option;
+
+	return true;
+}
+
+static enum gs_panel_tex_opt km4_get_te2_option(struct gs_panel *ctx)
+{
+	return ctx->te2.option;
+}
+
+static inline bool is_auto_mode_allowed(struct gs_panel *ctx)
+{
+	/* don't want to enable auto mode/early exit during dimming on */
+	if (ctx->dimming_on)
+		return false;
+
+	if (ctx->idle_data.idle_delay_ms) {
+		const unsigned int delta_ms = gs_panel_get_idle_time_delta(ctx);
+
+		if (delta_ms < ctx->idle_data.idle_delay_ms)
+			return false;
+	}
+
+	return ctx->idle_data.panel_idle_enabled;
+}
+
+static u32 km4_get_idle_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (spanel->is_mrr_v1)
+		return (vrefresh == 60) ? GIDLE_MODE_ON_SELF_REFRESH : GIDLE_MODE_ON_INACTIVITY;
+
+	return pmode->idle_mode;
+}
+
+static u32 km4_get_min_idle_vrefresh(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+	int min_idle_vrefresh = ctx->min_vrefresh;
+
+	if ((min_idle_vrefresh < 0) || !is_auto_mode_allowed(ctx))
+		return 0;
+
+	if (min_idle_vrefresh <= 1)
+		min_idle_vrefresh = 1;
+	else if (min_idle_vrefresh <= 10)
+		min_idle_vrefresh = 10;
+	else if (min_idle_vrefresh <= 30)
+		min_idle_vrefresh = 30;
+	else
+		return 0;
+
+	if (min_idle_vrefresh >= vrefresh) {
+		dev_dbg(ctx->dev, "min idle vrefresh (%d) higher than target (%d)\n",
+			min_idle_vrefresh, vrefresh);
+		return 0;
+	}
+
+	dev_dbg(ctx->dev, "%s: min_idle_vrefresh %d\n", __func__, min_idle_vrefresh);
+
+	return min_idle_vrefresh;
+}
+
+static void km4_set_panel_feat_manual_mode_fi(struct gs_panel *ctx, bool enabled)
+{
+	struct device *dev = ctx->dev;
+	u8 val;
+
+	val = enabled ? 0x33 : 0x00;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val);
+
+	if (!enabled) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x80, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x16);
+	}
+
+	dev_dbg(ctx->dev, "%s: manual mode fi %s\n", __func__, enabled ? "enabled" : "disabled");
+}
+
+static void km4_set_panel_feat_te(struct gs_panel *ctx, unsigned long *feat,
+				  const struct gs_panel_mode *pmode)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+	bool is_vrr = gs_is_vrr_mode(pmode);
+	u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
+	__maybe_unused u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (test_bit(FEAT_EARLY_EXIT, feat) && !spanel->force_changeable_te) {
+		if (is_vrr && te_freq == 240) {
+			/* 240Hz multi TE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x61);
+			/* TE width */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
+			if (test_bit(FEAT_OP_NS, feat))
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0xC8, 0x00, 0x1F, 0x02, 0xE0,
+						   0x00, 0x1F);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9B, 0x00, 0x1F, 0x05, 0xAB,
+						   0x00, 0x1F);
+		} else {
+			/* Fixed TE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+			/* TE width */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
+			if (test_bit(FEAT_OP_NS, feat))
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0xDC, 0x00, 0x1F, 0x0A, 0xDC,
+						   0x00, 0x1F);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9A, 0x00, 0x1F, 0x0B, 0x9A,
+						   0x00, 0x1F);
+#ifndef PANEL_FACTORY_BUILD
+			/* TE Freq */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x02, 0xB9);
+			if (!test_bit(FEAT_OP_NS, feat) && vrefresh == 60)
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x01);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00);
+#endif
+		}
+		ctx->hw_status.te.option = TEX_OPT_FIXED;
+	} else {
+		/* Changeable TE */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04);
+		/* TE width */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x04, 0xB9);
+		if (test_bit(FEAT_OP_NS, feat))
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0xDC, 0x00);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9A, 0x00, 0x1F);
+		ctx->hw_status.te.option = TEX_OPT_CHANGEABLE;
+	}
+}
+
+static void km4_set_panel_feat_hbm_irc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	/*
+	 * "Flat mode" is used to replace IRC on for normal mode and HDR video,
+	 * and "Flat Z mode" is used to replace IRC off for sunlight
+	 * environment.
+	 */
+
+	if (ctx->panel_rev <= PANEL_REV_DVT1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x01, 0x9B, 0x92);
+		if (unlikely(sw_status->irc_mode == IRC_OFF))
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x07);
+		else /* IRC_FLAT_DEFAULT or IRC_FLAT_Z */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x27);
+	}
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0x00, 0x92);
+	if (ctx->panel_rev == PANEL_REV_EVT1_1) {
+		if (sw_status->irc_mode == IRC_FLAT_Z)
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x78, 0x2D, 0xFF, 0xDC);
+		else /* IRC_FLAT_DEFAULT or IRC_OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
+	} else if (ctx->panel_rev <= PANEL_REV_DVT1) {
+		if (sw_status->irc_mode == IRC_FLAT_Z)
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x70, 0x26, 0xFF, 0xDC);
+		else /* IRC_FLAT_DEFAULT or IRC_OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
+	} else {
+		if (sw_status->irc_mode == IRC_FLAT_Z)
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x1E, 0xE5, 0xFF, 0xD4);
+		else /* IRC_FLAT_DEFAULT or IRC_OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
+	}
+
+	/* SP settings (burn-in compensation) */
+	if (ctx->panel_rev >= PANEL_REV_DVT1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0xF3, 0x68);
+		if (ctx->panel_rev < PANEL_REV_PVT) {
+			if (sw_status->irc_mode == IRC_FLAT_Z)
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x77, 0x77, 0x86, 0xE1, 0xE1, 0xF0);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x11, 0x1A, 0x13, 0x18, 0x21, 0x18);
+		} else {
+			if (sw_status->irc_mode == IRC_FLAT_Z)
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x76, 0x78, 0x86, 0xE0, 0xE1, 0xF0);
+			else
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x12, 0x16, 0x0B, 0x22, 0x23, 0x19);
+		}
+	}
+
+	ctx->hw_status.irc_mode = sw_status->irc_mode;
+	dev_info(dev, "%s: irc_mode=%d\n", __func__, ctx->hw_status.irc_mode);
+}
+
+
+static void km4_set_panel_feat_early_exit(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh)
+{
+	struct device *dev = ctx->dev;
+	struct km4_panel *spanel = to_spanel(ctx);
+	u8 val = (test_bit(FEAT_EARLY_EXIT, feat) && vrefresh != 80) ? 0x01 : 0x81;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+	if (spanel->is_mrr_v1) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+		val = test_bit(FEAT_EARLY_EXIT, feat) ? 0x22 : 0x00;
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val, val, val);
+	}
+}
+
+static void km4_set_panel_feat_tsp_sync(struct gs_panel *ctx) {
+	struct device *dev = ctx->dev;
+
+	/* Fixed 240Hz TSP Vsync */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x3C, 0xB9);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x19, 0x09); /* Sync On */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x05, 0xF2); /* Global para */
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0xD0); /* 240Hz setting */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x41, 0xB9); /* Global para */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x02); /* TSP Sync setting */
+}
+
+static void km4_set_panel_feat_frequency(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh,
+					 u32 idle_vrefresh, u32 te_freq, bool is_vrr)
+{
+	struct device *dev = ctx->dev;
+	u8 val;
+	const bool is_ns_mode = test_bit(FEAT_OP_NS, feat);
+
+	/*
+	 * Description: this sequence possibly overrides some configs early-exit
+	 * and operation set, depending on FI mode.
+	 */
+	if (test_bit(FEAT_FRAME_AUTO, feat)) {
+		if (is_ns_mode) {
+			/* threshold setting */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x0C, 0xBD);
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00);
+		} else {
+			/* initial frequency */
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x92, 0xBD);
+			if (vrefresh == 60) {
+				val = 0x02;
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported init freq %d (hs)\n",
+						 __func__, vrefresh);
+				/* 120Hz */
+				val = 0x00;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, val);
+		}
+		/* target frequency */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x12, 0xBD);
+		if (is_ns_mode) {
+			if (idle_vrefresh == 30) {
+				val = 0x04;
+			} else if (idle_vrefresh == 10) {
+				val = 0x14;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported target freq %d (ns)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEC;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
+		} else {
+			if (idle_vrefresh == 30) {
+				val = 0x06;
+			} else if (idle_vrefresh == 10) {
+				val = 0x16;
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported target freq %d (hs)\n",
+						 __func__, idle_vrefresh);
+				/* 1Hz */
+				val = 0xEE;
+			}
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
+		}
+		/* step setting */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x9E, 0xBD);
+		if (is_ns_mode)
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00);
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x02, 0x00, 0x06, 0x00, 0x16);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xAE, 0xBD);
+		if (is_ns_mode) {
+			if (idle_vrefresh == 30) {
+				/* 60Hz -> 30Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
+			} else if (idle_vrefresh == 10) {
+				/* 60Hz -> 10Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
+			} else {
+				if (idle_vrefresh != 1)
+					dev_warn(dev, "%s: unsupported freq step to %d (ns)\n",
+						 __func__, idle_vrefresh);
+				/* 60Hz -> 1Hz idle */
+				GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x03, 0x00);
+			}
+		} else {
+			if (vrefresh == 60) {
+				if (idle_vrefresh == 30) {
+					/* 60Hz -> 30Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
+				} else if (idle_vrefresh == 10) {
+					/* 60Hz -> 10Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x00);
+				} else {
+					if (idle_vrefresh != 1)
+						dev_warn(dev,
+							 "%s: unsupported freq step to %d (hs)\n",
+							 __func__, vrefresh);
+					/* 60Hz -> 1Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x03);
+				}
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported freq step from %d (hs)\n",
+						 __func__, vrefresh);
+				if (idle_vrefresh == 30) {
+					/* 120Hz -> 30Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
+				} else if (idle_vrefresh == 10) {
+					/* 120Hz -> 10Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x03, 0x00);
+				} else {
+					if (idle_vrefresh != 1)
+						dev_warn(dev,
+							 "%s: unsupported freq step to %d (hs)\n",
+							 __func__, idle_vrefresh);
+					/* 120Hz -> 1Hz idle */
+					GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x01, 0x03);
+				}
+			}
+		}
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA3);
+	} else { /* manual */
+		if (is_vrr) {
+			/* set gating TE 240hz or 120hz */
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21, (te_freq == 240) ? 0x41 : 0x01);
+		} else {
+			GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+		}
+		if (is_ns_mode) {
+			if (vrefresh == 1) {
+				val = 0x1F;
+			} else if (vrefresh == 10) {
+				val = 0x1B;
+			} else if (vrefresh == 30) {
+				val = 0x19;
+			} else {
+				if (vrefresh != 60)
+					dev_warn(dev, "%s: unsupported manual freq %d (ns)\n",
+						 __func__, vrefresh);
+				/* 60Hz */
+				val = 0x18;
+			}
+		} else {
+			if (vrefresh == 1) {
+				val = 0x07;
+			} else if (vrefresh == 10) {
+				val = 0x03;
+			} else if (vrefresh == 30) {
+				val = 0x02;
+			} else if (vrefresh == 60) {
+				val = 0x01;
+			} else if (vrefresh == 80) {
+				val = 0x04;
+			} else {
+				if (vrefresh != 120)
+					dev_warn(dev, "%s: unsupported manual freq %d (hs)\n",
+						 __func__, vrefresh);
+				/* 120Hz */
+				val = 0x00;
+			}
+		}
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+}
+
+static void km4_set_panel_feat_dbi(struct gs_panel *ctx, u8 value)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x67, 0x69);
+	GS_DCS_BUF_ADD_CMD(dev, 0x69, value);
+	dev_dbg(dev, "%s: DBI: setting %#02X\n", __func__, value);
+}
+
+static u8 km4_calc_dbi_ref(struct gs_panel *ctx)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	u16 frame_rate = spanel->frame_rate;
+	u8 value = 60 / frame_rate;
+
+	if (frame_rate <= 60)
+		return value;
+
+	if (spanel->dbi_frame_count++ % (frame_rate == 120 ? 2 : 4))
+		return 1;
+	else
+		return 0;
+}
+
+static void km4_set_dbi_ref(struct gs_panel *ctx, u8 value)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	km4_set_panel_feat_dbi(ctx, value);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+static void km4_set_frame_rate(struct gs_panel *ctx, u16 frame_rate)
+{
+	struct device *dev = ctx->dev;
+	struct km4_panel *spanel = to_spanel(ctx);
+
+	if (frame_rate == spanel->frame_rate ||
+		test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) ||
+		ctx->sw_status.idle_vrefresh == drm_mode_vrefresh(&ctx->current_mode->mode))
+		return;
+
+	dev_dbg(dev, "%s: DBI: updating for %u fps\n", __func__, frame_rate);
+	spanel->frame_rate = frame_rate;
+	km4_set_dbi_ref(ctx, km4_calc_dbi_ref(ctx));
+}
+
+/**
+ * km4_set_panel_feat - configure panel features
+ * @ctx: gs_panel struct
+ * @pmode: gs_panel_mode struct, target panel mode
+ * @idle_vrefresh: target vrefresh rate in auto mode, 0 if disabling auto mode
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context.
+ */
+static void km4_set_panel_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
+			       bool enforce)
+{
+	struct device *dev = ctx->dev;
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+	unsigned long *feat = sw_status->feat;
+	u32 idle_vrefresh = sw_status->idle_vrefresh;
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
+	bool is_vrr = !spanel->is_mrr_v1 && gs_is_vrr_mode(pmode);
+	bool irc_mode_changed;
+	u8 val;
+	DECLARE_BITMAP(changed_feat, FEAT_MAX);
+
+	/* override settings if mrr v2 or vrr */
+	if (!spanel->is_mrr_v1) {
+		if (!test_bit(FEAT_FRAME_AUTO, feat)) {
+			vrefresh = idle_vrefresh ?: 1;
+			idle_vrefresh = 0;
+		}
+		set_bit(FEAT_EARLY_EXIT, feat);
+		if (is_vrr) {
+			if (pmode->mode.flags & DRM_MODE_FLAG_NS)
+				set_bit(FEAT_OP_NS, feat);
+			else
+				clear_bit(FEAT_OP_NS, feat);
+		}
+	}
+
+	/* Create bitmap of changed feature values to modify */
+	if (enforce) {
+		bitmap_fill(changed_feat, FEAT_MAX);
+		irc_mode_changed = true;
+	} else {
+		bitmap_xor(changed_feat, feat, hw_status->feat, FEAT_MAX);
+		irc_mode_changed = (sw_status->irc_mode != hw_status->irc_mode);
+		if (bitmap_empty(changed_feat, FEAT_MAX) && vrefresh == hw_status->vrefresh &&
+		    idle_vrefresh == hw_status->idle_vrefresh &&
+		    te_freq == hw_status->te.rate_hz &&
+		    !irc_mode_changed) {
+			dev_dbg(dev, "%s: no changes, skip update\n", __func__);
+			return;
+		}
+	}
+
+	dev_dbg(dev, "hbm=%u irc=%u ns=%u vrr=%u fi=%u@a,%u@m ee=%u rr=%u-%u:%u\n",
+		test_bit(FEAT_HBM, feat), sw_status->irc_mode, test_bit(FEAT_OP_NS, feat),
+		is_vrr, test_bit(FEAT_FRAME_AUTO, feat), test_bit(FEAT_FRAME_MANUAL_FI, feat),
+		test_bit(FEAT_EARLY_EXIT, feat), idle_vrefresh ?: vrefresh,
+		drm_mode_vrefresh(&pmode->mode), te_freq);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	/* Unlock */
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	/* TE setting */
+	sw_status->te.rate_hz = te_freq;
+	if (test_bit(FEAT_EARLY_EXIT, changed_feat) || test_bit(FEAT_OP_NS, changed_feat) ||
+	    hw_status->te.rate_hz != te_freq)
+		km4_set_panel_feat_te(ctx, feat, pmode);
+
+	/*
+	 * HBM IRC setting
+	 */
+	if (irc_mode_changed)
+		km4_set_panel_feat_hbm_irc(ctx);
+
+	/*
+	 * Operating Mode: NS or HS
+	 *
+	 * Description: the configs could possibly be overrided by frequency setting,
+	 * depending on FI mode.
+	 */
+	if (test_bit(FEAT_OP_NS, changed_feat)) {
+		/* mode set */
+		GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x01);
+		val = test_bit(FEAT_OP_NS, feat) ? 0x18 : 0x00;
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
+	}
+
+	/*
+	 * Early-exit: enable or disable
+	 */
+	km4_set_panel_feat_early_exit(ctx, feat, vrefresh);
+
+	/*
+	 * Manual FI: enable or disable manual mode FI
+	 */
+	if (test_bit(FEAT_FRAME_MANUAL_FI, changed_feat))
+		km4_set_panel_feat_manual_mode_fi(ctx, test_bit(FEAT_FRAME_MANUAL_FI, feat));
+
+	/* TSP Sync setting */
+	if (enforce)
+		km4_set_panel_feat_tsp_sync(ctx);
+
+	/* Reset DBI Reference frequency */
+	if (test_bit(FEAT_FRAME_AUTO, feat) || vrefresh == 120)
+		km4_set_panel_feat_dbi(ctx, KM4_DBI_REF_DEFAULT);
+
+	/*
+	 * Frequency setting: FI, frequency, idle frequency
+	 */
+	km4_set_panel_feat_frequency(ctx, feat, vrefresh, idle_vrefresh, te_freq, is_vrr);
+
+	/* Lock */
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+
+	hw_status->vrefresh = vrefresh;
+	hw_status->idle_vrefresh = idle_vrefresh;
+	hw_status->te.rate_hz = te_freq;
+	bitmap_copy(hw_status->feat, feat, FEAT_MAX);
+}
+
+/**
+ * km4_update_panel_feat - configure panel features with current refresh rate
+ * @ctx: gs_panel struct
+ * @enforce: force to write all of registers even if no feature state changes
+ *
+ * Configure panel features based on the context without changing current refresh rate
+ * and idle setting.
+ */
+static void km4_update_panel_feat(struct gs_panel *ctx, bool enforce)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	km4_set_panel_feat(ctx, pmode, enforce);
+}
+
+static void km4_update_refresh_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
+				    const u32 idle_vrefresh)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	/* TODO: b/308978878 - move refresh control logic to HWC */
+
+	/*
+	 * Skip idle update if going through RRS without refresh rate change. If
+	 * we're switching resolution and refresh rate in the same atomic commit
+	 * (MODE_RES_AND_RR_IN_PROGRESS), we shouldn't skip the update to
+	 * ensure the refresh rate will be set correctly to avoid problems.
+	 */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS) {
+		dev_dbg(ctx->dev, "%s: RRS in progress without RR change, skip\n", __func__);
+		notify_panel_mode_changed(ctx);
+		return;
+	}
+
+	dev_dbg(ctx->dev, "%s: mode: %s set idle_vrefresh: %u\n", __func__, pmode->mode.name,
+		idle_vrefresh);
+
+	if (spanel->is_mrr_v1) {
+		u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+		if (idle_vrefresh)
+			set_bit(FEAT_FRAME_AUTO, sw_status->feat);
+		else
+			clear_bit(FEAT_FRAME_AUTO, sw_status->feat);
+		if (vrefresh == 120 || idle_vrefresh)
+			set_bit(FEAT_EARLY_EXIT, sw_status->feat);
+		else
+			clear_bit(FEAT_EARLY_EXIT, sw_status->feat);
+	}
+	sw_status->idle_vrefresh = idle_vrefresh;
+	/*
+	 * Note: when mode is explicitly set, panel performs early exit to get out
+	 * of idle at next vsync, and will not back to idle until not seeing new
+	 * frame traffic for a while. If idle_vrefresh != 0, try best to guess what
+	 * panel_idle_vrefresh will be soon, and km4_update_idle_state() in
+	 * new frame commit will correct it if the guess is wrong.
+	 */
+	ctx->idle_data.panel_idle_vrefresh = idle_vrefresh;
+	km4_set_panel_feat(ctx, pmode, false);
+	notify_panel_mode_changed(ctx);
+
+	dev_dbg(ctx->dev, "%s: display state is notified\n", __func__);
+}
+
+static void km4_change_frequency(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+	u32 idle_vrefresh = 0;
+
+	if (vrefresh > ctx->op_hz) {
+		/* resolution may have been changed without refresh rate change */
+		if (ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
+			notify_panel_mode_changed(ctx);
+		dev_err(ctx->dev, "invalid freq setting: op_hz=%u, vrefresh=%u\n", ctx->op_hz,
+			vrefresh);
+		return;
+	}
+
+	if (km4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY)
+		idle_vrefresh = km4_get_min_idle_vrefresh(ctx, pmode);
+
+	/**
+	 * While TE2 is changeable, the idle_vrefresh should be set. We should use the
+	 * idle_vrefresh instead of 0 for the proximity sensor.
+	 */
+	if ((!spanel->is_mrr_v1 && test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat)) ||
+	    (ctx->te2.option == TEX_OPT_CHANGEABLE && !idle_vrefresh))
+		idle_vrefresh = ctx->sw_status.idle_vrefresh;
+
+	km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+	ctx->sw_status.te.rate_hz = gs_drm_mode_te_freq(&pmode->mode);
+
+	dev_dbg(ctx->dev, "change to %u hz\n", vrefresh);
+}
+
+static void km4_panel_idle_notification(struct gs_panel *ctx, u32 display_id, u32 vrefresh,
+					u32 idle_te_vrefresh)
+{
+	char event_string[64];
+	char *envp[] = { event_string, NULL };
+	struct drm_device *dev = ctx->bridge.dev;
+
+	if (!dev) {
+		dev_warn(ctx->dev, "%s: drm_device is null\n", __func__);
+	} else {
+		scnprintf(event_string, sizeof(event_string), "PANEL_IDLE_ENTER=%u,%u,%u",
+			  display_id, vrefresh, idle_te_vrefresh);
+		kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
+	}
+}
+
+static void km4_wait_one_vblank(struct gs_panel *ctx)
+{
+	struct drm_crtc *crtc = NULL;
+
+	if (ctx->gs_connector->base.state)
+		crtc = ctx->gs_connector->base.state->crtc;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	if (crtc) {
+		int ret = drm_crtc_vblank_get(crtc);
+
+		if (!ret) {
+			drm_crtc_wait_one_vblank(crtc);
+			drm_crtc_vblank_put(crtc);
+		} else {
+			usleep_range(8350, 8500);
+		}
+	} else {
+		usleep_range(8350, 8500);
+	}
+	PANEL_ATRACE_END(__func__);
+}
+
+static bool km4_set_self_refresh(struct gs_panel *ctx, bool enable)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct km4_panel *spanel = to_spanel(ctx);
+	u32 idle_vrefresh;
+
+	if (ctx->thermal && ctx->thermal->pending_temp_update && enable)
+		km4_update_disp_therm(ctx);
+
+	PANEL_ATRACE_INT(__func__, enable);
+
+	if (!spanel->is_mrr_v1) {
+		u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		idle_vrefresh = ctx->sw_status.idle_vrefresh;
+		/* notify the changes of TE2 rate in case DPU enters/exits hibernation */
+		if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != idle_vrefresh &&
+		    test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
+		    km4_set_te2_rate(ctx, enable ? idle_vrefresh : vrefresh))
+			notify_panel_te2_rate_changed(ctx, 0);
+		return false;
+	}
+
+	dev_dbg(ctx->dev, "%s: %d\n", __func__, enable);
+
+	if (unlikely(!pmode))
+		return false;
+
+	/* self refresh is not supported in lp mode since that always makes use of early exit */
+	if (pmode->gs_mode.is_lp_mode) {
+		/* set 1Hz while self refresh is active, otherwise clear it */
+		ctx->idle_data.panel_idle_vrefresh = enable ? 1 : 0;
+		notify_panel_mode_changed(ctx);
+		return false;
+	}
+
+	idle_vrefresh = km4_get_min_idle_vrefresh(ctx, pmode);
+
+	if (km4_get_idle_mode(ctx, pmode) != GIDLE_MODE_ON_SELF_REFRESH) {
+		/*
+		 * if idle mode is on inactivity, may need to update the target fps for auto mode,
+		 * or switch to manual mode if idle should be disabled (idle_vrefresh=0)
+		 */
+		if ((km4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY) &&
+		    (ctx->sw_status.idle_vrefresh != idle_vrefresh)) {
+			km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+			return true;
+		}
+		return false;
+	}
+
+	if (!enable)
+		idle_vrefresh = 0;
+
+	/* if there's no change in idle state then skip cmds */
+	if (ctx->idle_data.panel_idle_vrefresh == idle_vrefresh)
+		return false;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
+
+	if (idle_vrefresh) {
+		const int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		km4_panel_idle_notification(ctx, 0, vrefresh, 120);
+	} else if (ctx->idle_data.panel_need_handle_idle_exit) {
+		/*
+		 * after exit idle mode with fixed TE at non-120hz, TE may still keep at 120hz.
+		 * If any layer that already be assigned to DPU that can't be handled at 120hz,
+		 * panel_need_handle_idle_exit will be set then we need to wait one vblank to
+		 * avoid underrun issue.
+		 */
+		dev_dbg(ctx->dev, "wait one vblank after exit idle\n");
+		km4_wait_one_vblank(ctx);
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return true;
+}
+
+static void km4_set_panel_lp_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	struct km4_panel *spanel = to_spanel(ctx);
+	unsigned long *feat = ctx->sw_status.feat;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	u32 idle_vrefresh = sw_status->idle_vrefresh;
+	bool is_auto = (test_bit(FEAT_FRAME_AUTO, feat) || spanel->is_mrr_v1) ? true : false;
+
+	if (!pmode->gs_mode.is_lp_mode)
+		return;
+
+	dev_dbg(dev, "%s: auto=%u rr=%u-%u\n",
+		__func__, is_auto, idle_vrefresh, drm_mode_vrefresh(&pmode->mode));
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	if (is_auto) {
+		/* Default is 1 Hz */
+		u8 val = 0x74;
+
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x18, 0xBD);
+		if (idle_vrefresh == 10)
+			val = 0x08;
+		else if (idle_vrefresh != 1)
+			dev_warn(dev, "%s: unsupported idle vrefresh %u\n",
+				__func__, idle_vrefresh);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x04, 0x00, val);
+		/* Step settings */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xB8, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x08);
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xC8, 0xBD);
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x03);
+		/* Auto mode */
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA7);
+	} else {
+		/* Manual mode */
+		GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+		/* 30 Hz */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
+	}
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+#ifndef PANEL_FACTORY_BUILD
+static void km4_update_refresh_ctrl_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const u32 ctrl = ctx->refresh_ctrl;
+	struct km4_panel *spanel = to_spanel(ctx);
+	unsigned long *feat = ctx->sw_status.feat;
+	u32 min_vrefresh = ctx->sw_status.idle_vrefresh;
+	bool mrr_changed = false;
+	bool idle_vrefresh_changed = false;
+	bool feat_frame_auto_changed = false;
+	bool prev_feat_frame_auto_enabled = test_bit(FEAT_FRAME_AUTO, feat);
+	u32 vrefresh;
+	bool lp_mode;
+
+	if (!pmode)
+		return;
+
+	dev_dbg(ctx->dev, "%s: ctrl=0x%X\n", __func__, ctrl);
+
+	vrefresh = drm_mode_vrefresh(&pmode->mode);
+	lp_mode =  pmode->gs_mode.is_lp_mode;
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) {
+		min_vrefresh = (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) >>
+				GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_OFFSET;
+
+		if (min_vrefresh > vrefresh) {
+			dev_warn(ctx->dev, "%s: min RR %uHz requested, but valid range is 1-%uHz\n",
+				 __func__, min_vrefresh, vrefresh);
+			min_vrefresh = vrefresh;
+		}
+		ctx->sw_status.idle_vrefresh = min_vrefresh;
+		idle_vrefresh_changed = true;
+	}
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_FI_AUTO) {
+		if (min_vrefresh == vrefresh) {
+			clear_bit(FEAT_FRAME_AUTO, feat);
+			clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+		} else if ((min_vrefresh > 1) || lp_mode) {
+			set_bit(FEAT_FRAME_AUTO, feat);
+			clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+		} else {
+			set_bit(FEAT_FRAME_MANUAL_FI, feat);
+			clear_bit(FEAT_FRAME_AUTO, feat);
+		}
+	} else {
+		clear_bit(FEAT_FRAME_AUTO, feat);
+		clear_bit(FEAT_FRAME_MANUAL_FI, feat);
+	}
+
+	if (lp_mode) {
+		km4_set_panel_lp_feat(ctx, pmode);
+		return;
+	}
+
+	/**
+	 * TODO(b/344478264): avoid setting idle 60Hz in the composer.
+	 *
+	 * 60Hz idle_vrefresh is not supported in non-60Hz mode. Default to 1Hz to save power.
+	 * Otherwise, set to 30Hz to respect proximity sensor's working frequency.
+	 */
+	if (test_bit(FEAT_FRAME_AUTO, feat) && ctx->sw_status.idle_vrefresh == 60 &&
+	    vrefresh != 60) {
+		dev_warn(ctx->dev, "%s: idle_vrefresh 60Hz is not supported in %uHz mode\n",
+			 __func__, vrefresh);
+		ctx->sw_status.idle_vrefresh = (ctx->te2.option == TEX_OPT_CHANGEABLE) ? 30 : 1;
+	}
+
+	if (prev_feat_frame_auto_enabled != test_bit(FEAT_FRAME_AUTO, feat))
+		feat_frame_auto_changed = true;
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_MRR_V1_OVER_V2) {
+		if (gs_is_vrr_mode(pmode)) {
+			dev_err(ctx->dev, "%s: using vrr display mode for mrr\n", __func__);
+		} else if (!spanel->is_mrr_v1) {
+			mrr_changed = true;
+			spanel->is_mrr_v1 = true;
+			ctx->gs_connector->ignore_op_rate = true;
+		}
+	} else if (spanel->is_mrr_v1) {
+		mrr_changed = true;
+		spanel->is_mrr_v1 = false;
+		ctx->gs_connector->ignore_op_rate = false;
+	}
+
+	PANEL_ATRACE_INT("idle_vrefresh", ctx->sw_status.idle_vrefresh);
+	PANEL_ATRACE_INT("FEAT_FRAME_AUTO", test_bit(FEAT_FRAME_AUTO, feat));
+
+	/**
+	 * While DPU is not in hibernation, it may keep transferring frames and TE2 will remain
+	 * at peak refresh rate, e.g. 60Hz or 120Hz. Set and notify peak refresh rate even though
+	 * idle_vrefresh is at a lower rate (e.g. 30Hz) to avoid misalignment between the display
+	 * and ALSP.
+	 */
+	if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != ctx->sw_status.idle_vrefresh &&
+	    test_bit(FEAT_FRAME_AUTO, feat) && !ctx->idle_data.self_refresh_active &&
+	    km4_set_te2_rate(ctx, vrefresh))
+		notify_panel_te2_rate_changed(ctx, 0);
+
+	/**
+	 * The changes of idle vrefresh and frame auto could trigger a 120Hz frame.
+	 * Check whether we need to adjust the timing of sending the commands in these
+	 * conditions.
+	 */
+	if (idle_vrefresh_changed && feat_frame_auto_changed &&
+	    !test_bit(FEAT_FRAME_MANUAL_FI, feat))
+		km4_check_command_timing_for_te2(ctx);
+
+	if (mrr_changed)
+		km4_change_frequency(ctx, pmode);
+	else
+		km4_set_panel_feat(ctx, pmode, false);
+}
+
+static void km4_refresh_ctrl(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	const u32 ctrl = ctx->refresh_ctrl;
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	km4_update_refresh_ctrl_feat(ctx, ctx->current_mode);
+
+	if (ctrl & GS_PANEL_REFRESH_CTRL_FI_FRAME_COUNT_MASK){
+		/* TODO(b/323251635): parse frame count for inserting multiple frames */
+
+		dev_dbg(dev, "%s: manually inserting frame\n", __func__);
+		PANEL_ATRACE_BEGIN("insert_frame");
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMD(dev, 0xF7, 0x02);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+		PANEL_ATRACE_END("insert_frame");
+	}
+
+	PANEL_ATRACE_END(__func__);
+}
+#endif
+
+static int km4_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state =
+		drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+	    !new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	if ((ctx->sw_status.idle_vrefresh && old_crtc_state->self_refresh_active) ||
+	    !drm_atomic_crtc_effectively_active(old_crtc_state)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		/* set clock to max refresh rate on self refresh exit or resume due to early exit */
+		mode->clock = gs_bts_fps_to_drm_mode_clock(mode, 120);
+
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n", mode->name,
+				old_crtc_state->self_refresh_active ? "self refresh exit" :
+								      "resume");
+		}
+	} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
+		/* clock hacked in last commit due to self refresh exit or resume, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after self refresh exit or resume\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void km4_write_display_mode(struct gs_panel *ctx, const struct drm_display_mode *mode)
+{
+	struct device *dev = ctx->dev;
+	u8 val = KM4_WRCTRLD_BCTRL_BIT;
+
+	PANEL_ATRACE_BEGIN(__func__);
+	if (GS_IS_HBM_ON(ctx->hbm_mode))
+		val |= KM4_WRCTRLD_HBM_BIT;
+
+	if (ctx->dimming_on)
+		val |= KM4_WRCTRLD_DIMMING_BIT;
+
+	dev_dbg(dev, "%s(wrctrld:0x%x, hbm: %s, dimming: %s)\n", __func__, val,
+		GS_IS_HBM_ON(ctx->hbm_mode) ? "on" : "off", ctx->dimming_on ? "on" : "off");
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
+	PANEL_ATRACE_END(__func__);
+}
+
+#define KM4_OPR_VAL_LEN 2
+#define KM4_MAX_OPR_VAL 0x3FF
+/* Get OPR (on pixel ratio), the unit is percent */
+static int km4_get_opr(struct gs_panel *ctx, u8 *opr)
+{
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	struct device *dev = ctx->dev;
+	u8 buf[KM4_OPR_VAL_LEN] = { 0 };
+	u16 val;
+	int ret;
+
+	/*TODO(tknelms)DPU_ATRACE_BEGIN(__func__);*/
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB0, 0x00, 0xE7, 0x91);
+	ret = mipi_dsi_dcs_read(dsi, 0x91, buf, KM4_OPR_VAL_LEN);
+	GS_DCS_WRITE_CMDLIST(dev, lock_cmd_f0);
+	/*TODO(tknelms)DPU_ATRACE_END(__func__);*/
+
+	if (ret != KM4_OPR_VAL_LEN) {
+		dev_warn(dev, "Failed to read OPR (%d)\n", ret);
+		return ret;
+	}
+
+	val = (buf[0] << 8) | buf[1];
+	*opr = DIV_ROUND_CLOSEST(val * 100, KM4_MAX_OPR_VAL);
+
+	return 0;
+}
+
+static void km4_disable_acl_mode(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+
+	if (hw_status->acl_mode != ACL_OFF) {
+		GS_DCS_WRITE_CMD(dev, 0x55, 0x00);
+		hw_status->acl_mode = ACL_OFF;
+		dev_info(dev, "%s : set acl_mode off\n", __func__);
+	}
+}
+
+static u8 get_acl_mode_setting(enum gs_acl_mode acl_mode)
+{
+	/*
+	 * KM4 ACL mode and setting:
+	 *
+	 * DVT1 and Later
+	 *    NORMAL     - 10%   (0x01)
+	 *    ENHANCED   - 15%   (0x02)
+	 */
+
+	switch (acl_mode) {
+	case ACL_OFF:
+		return 0x00;
+	case ACL_NORMAL:
+		return 0x01;
+	case ACL_ENHANCED:
+		return 0x02;
+	}
+}
+
+#define KM4_ZA_THRESHOLD_OPR 85
+#define KM4_ACL_ENHANCED_THRESHOLD_DBV 3726
+/* Manage the ACL settings to DDIC that consider the dbv and opr value */
+static void km4_acl_modes_manager(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+	struct gs_panel_status *hw_status = &ctx->hw_status;
+
+	// Check if ACL can be enabled based on conditions
+	bool can_enable_acl = hw_status->dbv >= KM4_ACL_ENHANCED_THRESHOLD_DBV;
+	u8 opr;
+	u8 target_acl_state;
+	bool update_acl_settings;
+
+	if (!can_enable_acl) {
+		km4_disable_acl_mode(ctx);
+		return;
+	}
+
+	// Check if ACL settings can be wrote based on conditions
+	if (!km4_get_opr(ctx, &opr)) {
+		update_acl_settings = (opr > KM4_ZA_THRESHOLD_OPR);
+	} else {
+		dev_warn(ctx->dev, "Unable to update acl mode\n");
+		return;
+	}
+
+	if (update_acl_settings) {
+		if (sw_status->acl_mode == hw_status->acl_mode) {
+			dev_dbg(dev, "%s : skip to update acl_mode\n", __func__);
+			return;
+		}
+		target_acl_state = get_acl_mode_setting(sw_status->acl_mode);
+		GS_DCS_WRITE_CMD(dev, 0x55, target_acl_state);
+		hw_status->acl_mode = sw_status->acl_mode;
+		dev_info(dev, "%s: set acl : %d, opr : %hhu\n", __func__, target_acl_state, opr);
+	} else {
+		km4_disable_acl_mode(ctx);
+	}
+}
+
+/* updated za when acl mode changed */
+static void km4_set_acl_mode(struct gs_panel *ctx, enum gs_acl_mode mode)
+{
+	bool can_enable_acl = ctx->hw_status.dbv >= KM4_ACL_ENHANCED_THRESHOLD_DBV;
+
+	ctx->sw_status.acl_mode = mode;
+
+	if (can_enable_acl) {
+		if (ctx->sw_status.acl_mode != ctx->hw_status.acl_mode)
+			km4_acl_modes_manager(ctx);
+	} else
+		km4_disable_acl_mode(ctx);
+}
+
+static int km4_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	int ret;
+	u16 brightness;
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+		const struct gs_panel_funcs *funcs;
+
+		/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
+		if (spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+			spanel->is_pixel_off = false;
+		}
+
+		funcs = ctx->desc->gs_panel_func;
+		if (funcs && funcs->set_binned_lp)
+			funcs->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	/* Use pixel off command instead of setting DBV 0 */
+	if (!br) {
+		if (!spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMDLIST(dev, pixel_off);
+			spanel->is_pixel_off = true;
+			dev_dbg(dev, "%s: pixel off instead of dbv 0\n", __func__);
+		}
+		return 0;
+	} else if (br && spanel->is_pixel_off) {
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+		spanel->is_pixel_off = false;
+	}
+
+	brightness = (br & 0xff) << 8 | br >> 8;
+	km4_check_command_timing_for_te2(ctx);
+	ret = gs_dcs_set_brightness(ctx, brightness);
+	if (!ret) {
+		ctx->hw_status.dbv = br;
+		km4_set_acl_mode(ctx, ctx->sw_status.acl_mode);
+	}
+
+	return ret;
+}
+
+static void km4_wait_for_vsync_done(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	PANEL_ATRACE_BEGIN(__func__);
+	gs_panel_wait_for_vsync_done(ctx, km4_get_te_usec(ctx, pmode),
+				     GS_VREFRESH_TO_PERIOD_USEC(ctx->hw_status.vrefresh));
+	PANEL_ATRACE_END(__func__);
+}
+
+static void km4_enforce_manual_and_peak(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	if (!ctx->current_mode)
+		return;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* manual mode */
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
+	/* peak refresh rate */
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
+		GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
+	} else {
+		GS_DCS_BUF_ADD_CMD(dev, 0x60,
+				   !test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? 0x00 : 0x18);
+	}
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+}
+
+static void km4_set_lp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	const u16 brightness = gs_panel_get_brightness(ctx);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+	/* enforce manual and peak to have a smooth transition */
+	km4_enforce_manual_and_peak(ctx);
+
+	km4_wait_for_vsync_done(ctx, ctx->current_mode);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMDLIST(dev, aod_on);
+	/* Fixed TE: sync on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
+	/* Enable early exit */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xE8, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22, 0x22, 0x22, 0x22);
+	GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+#ifndef PANEL_FACTORY_BUILD
+	km4_update_refresh_ctrl_feat(ctx, pmode);
+#else
+	km4_set_panel_lp_feat(ctx, pmode);
+#endif
+	km4_set_dbi_ref(ctx, KM4_DBI_REF_DEFAULT);
+	gs_panel_set_binned_lp_helper(ctx, brightness);
+
+	ctx->hw_status.vrefresh = 30;
+	ctx->hw_status.te.rate_hz = 30;
+	ctx->sw_status.te.rate_hz = 30;
+	ctx->sw_status.te.option = TEX_OPT_FIXED;
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(dev, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
+}
+
+static void km4_set_nolp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	km4_wait_for_vsync_done(ctx, ctx->current_mode);
+	/* manual mode 30Hz */
+	km4_enforce_manual_and_peak(ctx);
+
+	/* b/346215549
+	 * ensure AOD off command is sent a bit later than vsync done
+	 */
+	km4_wait_for_vsync_done(ctx, ctx->current_mode);
+	usleep_range(5000, 5000 + 100);
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMDLIST(dev, aod_off);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	km4_wait_for_vsync_done(ctx, ctx->current_mode);
+#ifndef PANEL_FACTORY_BUILD
+	km4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+	km4_set_panel_feat(ctx, pmode, true);
+	/* backlight control and dimming */
+	km4_write_display_mode(ctx, &pmode->mode);
+	km4_change_frequency(ctx, pmode);
+
+	PANEL_ATRACE_END(__func__);
+
+	dev_info(dev, "exit LP mode\n");
+}
+
+static const struct gs_dsi_cmd km4_init_cmds[] = {
+	/* Enable TE*/
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
+
+	/* CASET: 1343 */
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x05, 0x3F),
+	/* PASET: 2991 */
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x0B, 0xAF),
+
+	GS_DSI_CMDLIST(unlock_cmd_f0),
+
+	/* FFC: off, 165MHz, MIPI Speed 1368 Mbps */
+	GS_DSI_CMD(0xB0, 0x00, 0x36, 0xC5),
+	GS_DSI_CMD(0xC5, 0x10, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
+		   0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
+		   0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00),
+
+	/* enable OPEC (auto still IMG detect off) */
+	GS_DSI_CMD(0xB0, 0x00, 0x1D, 0x63),
+	GS_DSI_CMD(0x63, 0x02, 0x18),
+
+	/* PMIC Fast Discharge off */
+	GS_DSI_CMD(0xB0, 0x00, 0x13, 0xB1),
+	GS_DSI_CMD(0xB1, 0x80),
+	GS_DSI_CMDLIST(freq_update),
+	GS_DSI_CMDLIST(lock_cmd_f0),
+};
+static DEFINE_GS_CMDSET(km4_init);
+
+static int km4_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	const struct drm_display_mode *mode;
+	const bool needs_reset = !gs_is_panel_enabled(ctx);
+	bool is_fhd;
+
+	if (!pmode) {
+		dev_err(dev, "no current mode set\n");
+		return -EINVAL;
+	}
+	mode = &pmode->mode;
+	is_fhd = mode->hdisplay == 1008;
+
+	dev_info(dev, "%s (%s)\n", __func__, is_fhd ? "fhd" : "wqhd");
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (needs_reset)
+		gs_panel_reset_helper(ctx);
+
+	/* wait TE falling for RRS since DSC and framestart must in the same VSYNC */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
+		km4_wait_for_vsync_done(ctx, pmode);
+
+	/* DSC related configuration */
+	GS_DCS_WRITE_CMD(dev, 0x9D, 0x01);
+	gs_dcs_write_dsc_config(dev, is_fhd ? &fhd_pps_config : &wqhd_pps_config);
+
+	if (needs_reset) {
+		struct km4_panel *spanel = to_spanel(ctx);
+
+		GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
+		gs_panel_send_cmdset(ctx, &km4_init_cmdset);
+		km4_te2_setting(ctx);
+		spanel->is_pixel_off = false;
+		ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT;
+		if (test_bit(FEAT_OP_NS, ctx->sw_status.feat))
+			spanel->frame_rate = 60;
+		else
+			spanel->frame_rate = 120;
+		spanel->dbi_frame_count = 0;
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC3, is_fhd ? 0x0D : 0x0C);
+	/* 8/10bit config for QHD/FHD */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xF2);
+	GS_DCS_BUF_ADD_CMD(dev, 0xF2, is_fhd ? 0x81 : 0x01);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	if (pmode->gs_mode.is_lp_mode) {
+		km4_set_lp_mode(ctx, pmode);
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+	} else {
+
+#ifndef PANEL_FACTORY_BUILD
+		km4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+		km4_update_panel_feat(ctx, true);
+		km4_write_display_mode(ctx, mode); /* dimming and HBM */
+		km4_change_frequency(ctx, pmode);
+
+		if (needs_reset || (ctx->panel_state == GPANEL_STATE_BLANK)) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+		}
+	}
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int km4_disable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	int ret;
+
+	dev_info(dev, "%s\n", __func__);
+
+	/* skip disable sequence if going through RRS */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RR_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
+		dev_dbg(dev, "%s: RRS in progress, skip\n", __func__);
+		return 0;
+	}
+
+	ret = gs_panel_disable(panel);
+	if (ret)
+		return ret;
+
+	/* panel register state gets reset after disabling hardware */
+	bitmap_clear(ctx->hw_status.feat, 0, FEAT_MAX);
+	ctx->hw_status.vrefresh = 60;
+	ctx->sw_status.te.rate_hz = 60;
+	ctx->hw_status.te.rate_hz = 60;
+	ctx->hw_status.idle_vrefresh = 0;
+	ctx->hw_status.acl_mode = 0;
+	ctx->hw_status.dbv = 0;
+	ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
+
+	/* set manual and peak before turning off display */
+	km4_enforce_manual_and_peak(ctx);
+
+	GS_DCS_WRITE_DELAY_CMD(dev, 20, MIPI_DCS_SET_DISPLAY_OFF);
+
+	if (ctx->panel_state == GPANEL_STATE_OFF)
+		GS_DCS_WRITE_DELAY_CMD(dev, 100, MIPI_DCS_ENTER_SLEEP_MODE);
+
+	return 0;
+}
+
+/*
+ * 120hz auto mode takes at least 2 frames to start lowering refresh rate in addition to
+ * time to next vblank. Use just over 2 frames time to consider worst case scenario
+ */
+#define EARLY_EXIT_THRESHOLD_US 17000
+
+/**
+ * km4_update_idle_state - update panel auto frame insertion state
+ * @ctx: panel struct
+ *
+ * - update timestamp of switching to manual mode in case its been a while since the
+ *   last frame update and auto mode may have started to lower refresh rate.
+ * - trigger early exit by command if it's changeable TE and no switching delay, which
+ *   could result in fast 120 Hz boost and seeing 120 Hz TE earlier, otherwise disable
+ *   auto refresh mode to avoid lowering frequency too fast.
+ */
+static void km4_update_idle_state(struct gs_panel *ctx)
+{
+	s64 delta_us;
+	struct km4_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (!spanel->is_mrr_v1)
+		return;
+
+	ctx->idle_data.panel_idle_vrefresh = 0;
+	if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat))
+		return;
+
+	delta_us = ktime_us_delta(ktime_get(), ctx->timestamps.last_commit_ts);
+	if (delta_us < EARLY_EXIT_THRESHOLD_US) {
+		dev_dbg(dev, "skip early exit. %lldus since last commit\n", delta_us);
+		return;
+	}
+
+	/* triggering early exit causes a switch to 120hz */
+	ctx->timestamps.last_mode_set_ts = ktime_get();
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (!ctx->idle_data.idle_delay_ms && spanel->force_changeable_te) {
+		dev_dbg(dev, "sending early exit out cmd\n");
+		GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+		GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
+		GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	} else {
+		/* turn off auto mode to prevent panel from lowering frequency too fast */
+		km4_update_refresh_mode(ctx, ctx->current_mode, 0);
+	}
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void km4_commit_done(struct gs_panel *ctx)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+
+	if (ctx->current_mode->gs_mode.is_lp_mode)
+		return;
+
+	if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
+		spanel->frame_rate > 60 &&
+		ctx->sw_status.idle_vrefresh < drm_mode_vrefresh(&ctx->current_mode->mode)) {
+		km4_set_dbi_ref(ctx, km4_calc_dbi_ref(ctx));
+	}
+
+	/* skip idle update if going through RRS */
+	if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
+	    ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
+		dev_dbg(ctx->dev, "%s: RRS in progress, skip\n", __func__);
+		return;
+	}
+
+	km4_update_idle_state(ctx);
+
+	km4_acl_modes_manager(ctx);
+
+	if (ctx->thermal && ctx->thermal->pending_temp_update)
+		km4_update_disp_therm(ctx);
+}
+
+static void km4_set_hbm_mode(struct gs_panel *ctx, enum gs_hbm_mode mode)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	struct gs_panel_status *sw_status = &ctx->sw_status;
+
+	if (mode == ctx->hbm_mode)
+		return;
+
+	if (unlikely(!pmode))
+		return;
+
+	ctx->hbm_mode = mode;
+
+	if (GS_IS_HBM_ON(mode)) {
+		set_bit(FEAT_HBM, sw_status->feat);
+		/* enforce IRC on for factory builds */
+#ifndef PANEL_FACTORY_BUILD
+		if (mode == GS_HBM_ON_IRC_ON)
+			sw_status->irc_mode = IRC_FLAT_DEFAULT;
+		else
+			sw_status->irc_mode = IRC_FLAT_Z;
+#endif
+		km4_update_panel_feat(ctx, false);
+		km4_write_display_mode(ctx, &pmode->mode);
+	} else {
+		clear_bit(FEAT_HBM, sw_status->feat);
+		sw_status->irc_mode = IRC_FLAT_DEFAULT;
+		km4_write_display_mode(ctx, &pmode->mode);
+		km4_update_panel_feat(ctx, false);
+	}
+}
+
+static void km4_set_dimming(struct gs_panel *ctx, bool dimming_on)
+{
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	ctx->dimming_on = dimming_on;
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_info(ctx->dev, "in lp mode, skip to update");
+		return;
+	}
+	km4_write_display_mode(ctx, &pmode->mode);
+}
+
+static void km4_mode_set(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	km4_change_frequency(ctx, pmode);
+
+	/**
+	 * TODO(b/344478250): update the refresh rate change in the composer
+	 *
+	 * Notify the changes of TE2 rate while switching the modes with different vrefresh in
+	 * case we miss the refresh rate change report from the composer.
+	 */
+	if (ctx->current_mode && ctx->te2.option == TEX_OPT_CHANGEABLE) {
+		u32 current_vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
+		u32 target_vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+		if (current_vrefresh != target_vrefresh && !ctx->idle_data.self_refresh_active &&
+		    km4_set_te2_rate(ctx, target_vrefresh))
+			notify_panel_te2_rate_changed(ctx, 0);
+	}
+}
+
+static bool km4_is_mode_seamless(const struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	const struct drm_display_mode *c = &ctx->current_mode->mode;
+	const struct drm_display_mode *n = &pmode->mode;
+
+	/* seamless mode set can happen if active region resolution is same */
+	return (c->vdisplay == n->vdisplay) && (c->hdisplay == n->hdisplay);
+}
+
+static int km4_set_op_hz(struct gs_panel *ctx, unsigned int hz)
+{
+	u32 vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
+
+	if (gs_is_vrr_mode(ctx->current_mode)) {
+		dev_warn(ctx->dev, "%s: should be set via mode switch\n", __func__);
+		return -EINVAL;
+	}
+
+	if (vrefresh > hz || (hz != 60 && hz != 120)) {
+		dev_err(ctx->dev, "invalid op_hz=%d for vrefresh=%d\n", hz, vrefresh);
+		return -EINVAL;
+	}
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	ctx->op_hz = hz;
+	if (hz == 60)
+		set_bit(FEAT_OP_NS, ctx->sw_status.feat);
+	else
+		clear_bit(FEAT_OP_NS, ctx->sw_status.feat);
+
+	if (gs_is_panel_active(ctx))
+		km4_update_panel_feat(ctx, false);
+	dev_info(ctx->dev, "%s op_hz at %d\n", gs_is_panel_active(ctx) ? "set" : "cache", hz);
+
+	PANEL_ATRACE_END(__func__);
+
+	return 0;
+}
+
+static int km4_read_id(struct gs_panel *ctx)
+{
+	return gs_panel_read_slsi_ddic_id(ctx);
+}
+
+static void km4_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	u8 build_code = (id & 0xFF00) >> 8;
+	u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
+
+	/* b/306527241 - Ensure EVT 1.0 panels use the correct revision */
+	if (id == 0x20A4040A)
+		rev = 8;
+
+	gs_panel_get_panel_rev(ctx, rev);
+}
+
+static void km4_normal_mode_work(struct gs_panel *ctx)
+{
+	if (ctx->idle_data.self_refresh_active) {
+		km4_update_disp_therm(ctx);
+	} else {
+		if (ctx->thermal)
+			ctx->thermal->pending_temp_update = true;
+	}
+}
+
+static void km4_pre_update_ffc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	/* FFC off */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void km4_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(dev, "%s: hs_clk_mbps: current=%d, target=%d\n", __func__,
+		ctx->dsi_hs_clk_mbps, hs_clk_mbps);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+
+	if (hs_clk_mbps != MIPI_DSI_FREQ_MBPS_DEFAULT &&
+	    hs_clk_mbps != MIPI_DSI_FREQ_MBPS_ALTERNATIVE) {
+		dev_warn(dev, "%s: invalid hs_clk_mbps=%d for FFC\n", __func__, hs_clk_mbps);
+	} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
+		dev_info(dev, "%s: updating for hs_clk_mbps=%d\n", __func__, hs_clk_mbps);
+		ctx->dsi_hs_clk_mbps = hs_clk_mbps;
+
+		/* Update FFC */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x37, 0xC5);
+		if (hs_clk_mbps == MIPI_DSI_FREQ_MBPS_DEFAULT)
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00,
+					   0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
+					   0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00,
+					   0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40,
+					   0x00);
+		else /* MIPI_DSI_FREQ_MBPS_ALTERNATIVE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x51, 0xFD, 0x40, 0x00,
+					   0x40, 0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40,
+					   0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00,
+					   0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00, 0x40,
+					   0x00);
+	}
+
+	/* FFC on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x11);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 350,
+	.te_var = 1,
+};
+
+static const u32 km4_bl_range[] = {
+	94, 180, 270, 360, 3271
+};
+
+#define KM4_WQHD_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &wqhd_pps_config,\
+}
+#define KM4_FHD_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &fhd_pps_config,\
+}
+
+static const struct gs_panel_mode_array km4_modes = {
+#ifdef PANEL_FACTORY_BUILD
+	.num_modes = 6,
+#else
+	.num_modes = 8,
+#endif
+	.modes = {
+/* MRR modes */
+#ifdef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				.name = "1344x2992x1@1",
+				DRM_MODE_TIMING(1, 1344, 80, 24, 52, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1344x2992x10@10",
+				DRM_MODE_TIMING(10, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1344x2992x30@30",
+				DRM_MODE_TIMING(30, 1344, 80, 22, 44, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1344x2992x80@80",
+				DRM_MODE_TIMING(80, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif
+		{
+			.mode = {
+				/* 60Hz supports HS/NS, see km4_get_te_usec for widths used */
+				.name = "1344x2992x60@60",
+				DRM_MODE_TIMING(60, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				/* aligned to bootloader resolution */
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1344x2992x120@120",
+				DRM_MODE_TIMING(120, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_120HZ_HS,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#ifndef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				/* 60Hz supports HS/NS, see km4_get_te_usec for widths used */
+				.name = "1008x2244x60@60",
+				DRM_MODE_TIMING(60, 1008, 80, 24, 38, 2244, 12, 4, 20),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = KM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1008x2244x120@120",
+				DRM_MODE_TIMING(120, 1008, 80, 24, 38, 2244, 12, 4, 20),
+				.flags = DRM_MODE_FLAG_BTS_OP_RATE,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_120HZ_HS,
+				.bpc = 8,
+				.dsc = KM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		/* VRR modes */
+		{
+			.mode = {
+				.name = "1344x2992x120@240",
+				DRM_VRR_MODE_TIMING(120, 240, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				/* aligned to bootloader resolution */
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1008x2244x120@240",
+				DRM_VRR_MODE_TIMING(120, 240, 1008, 80, 24, 38, 2244, 12, 4, 20),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = KM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1344x2992x120@120",
+				DRM_VRR_MODE_TIMING(120, 120, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+		{
+			.mode = {
+				.name = "1008x2244x120@120",
+				DRM_VRR_MODE_TIMING(120, 120, 1008, 80, 24, 38, 2244, 12, 4, 20),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = KM4_TE_USEC_VRR_HS,
+				.bpc = 8,
+				.dsc = KM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
+				.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
+			},
+			.idle_mode = GIDLE_MODE_UNSUPPORTED,
+		},
+#endif
+	},/* modes */
+};
+
+static const struct gs_panel_mode_array km4_lp_modes = {
+#ifdef PANEL_FACTORY_BUILD
+	.num_modes = 1,
+#else
+	.num_modes = 2,
+#endif
+	.modes = {
+		{
+			.mode = {
+				.name = "1344x2992x30@30",
+				/* hsa and hbp are different from normal 30 Hz */
+				DRM_MODE_TIMING(30, 1344, 80, 24, 42, 2992, 12, 4, 22),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1096,
+				.bpc = 8,
+				.dsc = KM4_WQHD_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+#ifndef PANEL_FACTORY_BUILD
+		{
+			.mode = {
+				.name = "1008x2244x30@30",
+				DRM_MODE_TIMING(30, 1008, 80, 24, 38, 2244, 12, 4, 20),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1096,
+				.bpc = 8,
+				.dsc = KM4_FHD_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+#endif
+	}, /* modes */
+};
+
+static struct gs_thermal_data km4_thermal_data = {
+	/* ddic default temp */
+	.hw_temp = 25,
+	.pending_temp_update = false,
+};
+
+static void km4_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+#ifdef CONFIG_DEBUG_FS
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+	struct km4_panel *spanel;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot) {
+		goto panel_out;
+	}
+
+	spanel = to_spanel(ctx);
+
+	gs_panel_debugfs_create_cmdset(csroot, &km4_init_cmdset, "init");
+	debugfs_create_bool("force_changeable_te", 0644, panel_root, &spanel->force_changeable_te);
+	debugfs_create_bool("force_changeable_te2", 0644, panel_root,
+			    &spanel->force_changeable_te2);
+	debugfs_create_bool("force_za_off", 0644, panel_root, &spanel->force_za_off);
+	debugfs_create_u32("hw_acl_mode", 0644, panel_root, &ctx->hw_status.acl_mode);
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+#endif
+}
+
+static void km4_set_ssc_en(struct gs_panel *ctx, bool enabled)
+{
+	struct device *dev = ctx->dev;
+	const bool ssc_mode_update = ctx->ssc_en != enabled;
+
+	if (!ssc_mode_update) {
+		dev_dbg(ctx->dev, "ssc_mode skip update\n");
+		return;
+	}
+
+	ctx->ssc_en = enabled;
+	GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
+	GS_DCS_BUF_ADD_CMD(dev, 0xFC, 0x5A, 0x5A); /* test_key_on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x6E, 0xC5); /* global para */
+
+	if (enabled)
+		GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x07, 0x7F, 0x00, 0x00); /* ssc enable */
+	else
+		GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x04, 0x00); /* ssc disable */
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xFC, 0xA5, 0xA5); /* test_key_off */
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
+	dev_info(ctx->dev, "ssc_mode=%d \n", ctx->ssc_en);
+}
+
+/* this function is called at drm bridge atomic_enable() while presenting 1st frame */
+static void km4_panel_init(struct gs_panel *ctx)
+{
+	struct km4_panel *spanel = to_spanel(ctx);
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+#ifdef PANEL_FACTORY_BUILD
+	spanel->is_mrr_v1 = true;
+	ctx->idle_data.panel_idle_enabled = false;
+	set_bit(FEAT_FRAME_MANUAL_FI, ctx->sw_status.feat);
+#else
+	spanel->is_mrr_v1 = false;
+	km4_update_refresh_ctrl_feat(ctx, pmode);
+#endif
+	spanel->frame_rate = 120;
+	spanel->dbi_frame_count = 0;
+	ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
+	/* default fixed TE2 120Hz */
+	ctx->te2.option = TEX_OPT_FIXED;
+	ctx->te2.rate_hz = 120;
+
+	if (!ctx->thermal) {
+		dev_err(ctx->dev, "%s: error retrieving thermal data\n", __func__);
+		return;
+	}
+
+	ctx->thermal->tz = thermal_zone_get_zone_by_name("disp_therm");
+	if (IS_ERR_OR_NULL(ctx->thermal->tz))
+		dev_err(ctx->dev, "%s: failed to get thermal zone disp_therm\n", __func__);
+	/* re-init panel to decouple bootloader settings */
+	if (pmode) {
+		dev_info(ctx->dev, "%s: set mode: %s\n", __func__, pmode->mode.name);
+		ctx->sw_status.idle_vrefresh = 0;
+		km4_set_panel_feat(ctx, pmode, true);
+		km4_change_frequency(ctx, pmode);
+		km4_te2_setting(ctx);
+	}
+}
+
+static int km4_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct km4_panel *spanel;
+	struct gs_panel *ctx;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+
+	ctx = &spanel->base;
+
+	ctx->op_hz = 120;
+	ctx->hw_status.vrefresh = 60;
+	ctx->hw_status.te.rate_hz = 60;
+	ctx->hw_status.acl_mode = ACL_OFF;
+	ctx->hw_status.dbv = 0;
+	ctx->thermal = &km4_thermal_data;
+	clear_bit(FEAT_ZA, ctx->hw_status.feat);
+	spanel->is_pixel_off = false;
+
+	return gs_dsi_panel_common_init(dsi, ctx);
+}
+
+static int km4_panel_config(struct gs_panel *ctx);
+
+static const struct drm_panel_funcs km4_drm_funcs = {
+	.disable = km4_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = km4_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = km4_debugfs_init,
+};
+
+static const struct gs_panel_funcs km4_gs_funcs = {
+	.set_brightness = km4_set_brightness,
+	.set_lp_mode = km4_set_lp_mode,
+	.set_nolp_mode = km4_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_hbm_mode = km4_set_hbm_mode,
+	.set_dimming = km4_set_dimming,
+	.is_mode_seamless = km4_is_mode_seamless,
+	.mode_set = km4_mode_set,
+	.panel_init = km4_panel_init,
+	.panel_config = km4_panel_config,
+	.get_panel_rev = km4_get_panel_rev,
+	.get_te2_edges = gs_panel_get_te2_edges_helper,
+	.set_te2_edges = gs_panel_set_te2_edges_helper,
+	.update_te2 = km4_update_te2,
+	.commit_done = km4_commit_done,
+	.atomic_check = km4_atomic_check,
+	.set_self_refresh = km4_set_self_refresh,
+#ifndef PANEL_FACTORY_BUILD
+	.refresh_ctrl = km4_refresh_ctrl,
+#endif
+	.set_frame_rate = km4_set_frame_rate,
+	.set_op_hz = km4_set_op_hz,
+	.read_id = km4_read_id,
+	.get_te_usec = km4_get_te_usec,
+	.set_acl_mode = km4_set_acl_mode,
+	.run_normal_mode_work = km4_normal_mode_work,
+	.pre_update_ffc = km4_pre_update_ffc,
+	.update_ffc = km4_update_ffc,
+	.set_ssc_en = km4_set_ssc_en,
+	.set_te2_rate = km4_set_te2_rate,
+	.get_te2_rate = km4_get_te2_rate,
+	.set_te2_option = km4_set_te2_option,
+	.get_te2_option = km4_get_te2_option,
+};
+
+static const struct gs_brightness_configuration km4_btr_configs[] = {
+	{
+		.panel_rev = PANEL_REV_EVT1 | PANEL_REV_LATEST,
+		.default_brightness = 1209,
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1250,
+				},
+				.level = {
+					.min = 176,
+					.max = 3271,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 61,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1250,
+					.max = 2050,
+				},
+				.level = {
+					.min = 3272,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 61,
+					.max = 100,
+				},
+			},
+		},
+	},
+	{
+		.panel_rev = PANEL_REV_PROTO1_1,
+		.default_brightness = 1319,
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1250,
+				},
+				.level = {
+					.min = 184,
+					.max = 3427,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 68,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1250,
+					.max = 1850,
+				},
+				.level = {
+					.min = 3428,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 68,
+					.max = 100,
+				},
+			},
+		},
+	},
+	{
+		.panel_rev = PANEL_REV_PROTO1,
+		.default_brightness = 1313,
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1200,
+				},
+				.level = {
+					.min = 186,
+					.max = 3406,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 67,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1200,
+					.max = 1800,
+				},
+				.level = {
+					.min = 3407,
+					.max = 4095,
+				},
+				.percentage = {
+					.min = 67,
+					.max = 100,
+				},
+			},
+		},
+	},
+};
+
+static struct gs_panel_brightness_desc km4_brightness_desc = {
+	.max_luminance = 10000000,
+	.max_avg_luminance = 10000000,
+	.min_luminance = 5,
+};
+
+static struct gs_panel_reg_ctrl_desc km4_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 1},
+		{PANEL_REG_ID_VCI, 10},
+	},
+	.reg_ctrl_post_enable = {
+		{PANEL_REG_ID_VDDD, 2},
+	},
+	.reg_ctrl_pre_disable = {
+		{PANEL_REG_ID_VDDD, 5},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VCI, 1},
+		{PANEL_REG_ID_VDDI, 1},
+	},
+};
+
+static struct gs_panel_desc gs_km4 = {
+	.data_lane_cnt = 4,
+	.dbv_extra_frame = true,
+	.brightness_desc = &km4_brightness_desc,
+	.reg_ctrl_desc = &km4_reg_ctrl_desc,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.bl_range = km4_bl_range,
+	.bl_num_ranges = ARRAY_SIZE(km4_bl_range),
+	.modes = &km4_modes,
+	.lp_modes = &km4_lp_modes,
+	.binned_lp = km4_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(km4_binned_lp),
+	.rr_switch_duration = 1,
+	.has_off_binned_lp_entry = false,
+	.is_idle_supported = true,
+	.panel_func = &km4_drm_funcs,
+	.gs_panel_func = &km4_gs_funcs,
+	.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT,
+	.reset_timing_ms = { 1, 1, 5 },
+	.normal_mode_work_delay_ms = 30000,
+	/* TODO(b/335574896): delay time in NS mode */
+	/**
+	 * While the proximity is active, we will set the min vrefresh to 30Hz with auto
+	 * frame insertion. Thus when the display is idle, we will have the refresh rate
+	 * change from 120Hz to 30Hz. According to the measurement, the pattern is: 3x120Hz
+	 * frame > 1x60Hz frame > 30Hz. With additional tolerance due to scheduler in the
+	 * kernel, the delay of notification is estimated to be ~50ms.
+	 */
+	.notify_te2_rate_changed_work_delay_ms = 50,
+};
+
+static int km4_panel_config(struct gs_panel *ctx)
+{
+	gs_panel_model_init(ctx, PROJECT, 0);
+
+	return gs_panel_update_brightness_desc(&km4_brightness_desc, km4_btr_configs,
+					       ARRAY_SIZE(km4_btr_configs), ctx->panel_rev);
+}
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-km4", .data = &gs_km4 },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_of_match);
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = km4_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-km4",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Taylor Nelms <tknelms@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google KM4 panel driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/panel-gs-tk4a.c b/panel-gs-tk4a.c
new file mode 100644
index 0000000..bc2dce2
--- /dev/null
+++ b/panel-gs-tk4a.c
@@ -0,0 +1,707 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MIPI-DSI based tk4a AMOLED LCD panel driver.
+ *
+ * Copyright (c) 2023 Google LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <drm/display/drm_dsc_helper.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+static const struct drm_dsc_config pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_width = 1080,
+	.slice_count = 1,
+	.slice_height = 24,
+	.simple_422 = false,
+	.pic_width = 1080,
+	.pic_height = 2424,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 796,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 15, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 15,
+	.scale_increment_interval = 786,
+	.nfl_bpg_offset = 1069,
+	.slice_bpg_offset = 543,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 1080,
+	.dsc_version_minor = 1,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+#define TK4A_WRCTRLD_DIMMING_BIT    0x08
+#define TK4A_WRCTRLD_BCTRL_BIT      0x20
+
+static const u8 test_key_enable[] = { 0xF0, 0x5A, 0x5A };
+static const u8 test_key_disable[] = { 0xF0, 0xA5, 0xA5 };
+static const u8 ltps_update[] = { 0xF7, 0x0F };
+static const u8 pixel_off[] = { 0x22 };
+
+static const struct gs_dsi_cmd tk4a_off_cmds[] = {
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_OFF),
+	GS_DSI_DELAY_CMD(120, MIPI_DCS_ENTER_SLEEP_MODE),
+};
+static DEFINE_GS_CMDSET(tk4a_off);
+
+static const struct gs_dsi_cmd tk4a_lp_cmds[] = {
+	GS_DSI_CMD(MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24),
+};
+static DEFINE_GS_CMDSET(tk4a_lp);
+
+static const struct gs_dsi_cmd tk4a_lp_night_cmd[] = {
+	GS_DSI_CMD(0x51, 0x00, 0xB8),
+};
+
+static const struct gs_dsi_cmd tk4a_lp_low_cmd[] = {
+	GS_DSI_CMD(0x51, 0x01, 0x7E),
+};
+static const struct gs_dsi_cmd tk4a_lp_high_cmd[] = {
+	GS_DSI_CMD(0x51, 0x03, 0x1A),
+};
+
+static const struct gs_binned_lp tk4a_binned_lp[] = {
+	/* night threshold 4 nits */
+	BINNED_LP_MODE_TIMING("night", 252, tk4a_lp_night_cmd, 12, 12 + 50),
+	/* low threshold 40 nits */
+	BINNED_LP_MODE_TIMING("low", 716, tk4a_lp_low_cmd, 12, 12 + 50),
+	BINNED_LP_MODE_TIMING("high", 4095, tk4a_lp_high_cmd, 12, 12 + 50),
+};
+
+static const struct gs_dsi_cmd tk4a_init_cmds[] = {
+	/* TE on */
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
+
+	/* TE width setting */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMD(0xB9, 0x01), /* 120HS, 60HS, AOD */
+	GS_DSI_CMDLIST(test_key_disable),
+
+	/* TE2 setting */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMD(0xB0, 0x00, 0x69, 0xCB),
+	GS_DSI_CMD(0xCB, 0x10, 0x00, 0x2D), /* 60HS TE2 ON */
+	GS_DSI_CMD(0xB0, 0x00, 0xE9, 0xCB),
+	GS_DSI_CMD(0xCB, 0x10, 0x00, 0x2D), /* 120HS & 90HS TE2 ON */
+	GS_DSI_CMD(0xB0, 0x01, 0x69, 0xCB),
+	GS_DSI_CMD(0xCB, 0x10, 0x00, 0x2D), /* AOD TE2 ON */
+	GS_DSI_CMDLIST(ltps_update),
+	GS_DSI_CMDLIST(test_key_disable),
+
+	/* CASET: 1080 */
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37),
+
+	/* PASET: 2424 */
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x77),
+
+	/* FFC 756Mbps @ fosc 180Mhz */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMD(0xFC, 0x5A, 0x5A),
+	GS_DSI_CMD(0xB0, 0x00, 0x2A, 0xC5),
+	GS_DSI_CMD(0xC5, 0x0D, 0x10, 0x80, 0x05),
+	GS_DSI_CMD(0xB0, 0x00, 0x2E, 0xC5),
+	GS_DSI_CMD(0xC5, 0x79, 0xE8),
+	GS_DSI_CMD(0xFC, 0xA5, 0xA5),
+	GS_DSI_CMDLIST(test_key_disable),
+
+	/* FREQ CON Set */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMD(0xB0, 0x00, 0x27, 0xF2),
+	GS_DSI_CMD(0xF2, 0x02),
+	GS_DSI_CMDLIST(ltps_update),
+	GS_DSI_CMDLIST(test_key_disable),
+};
+static DEFINE_GS_CMDSET(tk4a_init);
+
+/**
+ * struct tk4a_panel - panel specific runtime info
+ *
+ * This struct maintains tk4a panel specific runtime info, any fixed details about panel
+ * should most likely go into struct gs_panel_desc
+ */
+struct tk4a_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+	/**
+	 * @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
+	 *		  panel can recover to normal mode after entering pixel-off state.
+	 */
+	bool is_pixel_off;
+};
+#define to_spanel(ctx) container_of(ctx, struct tk4a_panel, base)
+
+static void tk4a_change_frequency(struct gs_panel *ctx,
+									const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (unlikely(!ctx))
+		return;
+
+	if (vrefresh != 60 && vrefresh != 120) {
+		dev_warn(ctx->dev, "%s: invalid refresh rate %uhz\n", __func__, vrefresh);
+		return;
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0x60, vrefresh == 60 ? 0x00 : 0x08, 0x00);
+	GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	dev_info(dev, "%s: change to %uHz\n", __func__, vrefresh);
+	return;
+}
+
+static void tk4a_update_wrctrld(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	u8 val = TK4A_WRCTRLD_BCTRL_BIT;
+
+	if (ctx->dimming_on)
+		val |= TK4A_WRCTRLD_DIMMING_BIT;
+
+	dev_dbg(dev,
+		"%s(wrctrld:0x%x, hbm: %s, dimming: %s)\n",
+		__func__, val, GS_IS_HBM_ON(ctx->hbm_mode) ? "on" : "off",
+		ctx->dimming_on ? "on" : "off");
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
+}
+
+static int tk4a_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	u16 brightness;
+	u32 max_brightness;
+	struct tk4a_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (ctx->current_mode && ctx->current_mode->gs_mode.is_lp_mode) {
+		/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
+		if (spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+			spanel->is_pixel_off = false;
+		}
+		if (gs_panel_has_func(ctx, set_binned_lp))
+			ctx->desc->gs_panel_func->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	/* Use pixel off command instead of setting DBV 0 */
+	if (!br) {
+		if (!spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMDLIST(dev, pixel_off);
+			spanel->is_pixel_off = true;
+			dev_dbg(dev, "%s: pixel off instead of dbv 0\n", __func__);
+		}
+		return 0;
+	} else if (br && spanel->is_pixel_off) {
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+		spanel->is_pixel_off = false;
+	}
+
+	if (!ctx->desc->brightness_desc->brt_capability) {
+		dev_err(dev, "no available brightness capability\n");
+		return -EINVAL;
+	}
+
+	max_brightness = ctx->desc->brightness_desc->brt_capability->hbm.level.max;
+
+	if (br > max_brightness) {
+		br = max_brightness;
+		dev_warn(dev, "%s: capped to dbv(%d)\n", __func__,
+			max_brightness);
+	}
+
+	/* swap endianness because panel expects MSB first */
+	brightness = swab16(br);
+
+	return gs_dcs_set_brightness(ctx, brightness);
+}
+
+static void tk4a_set_hbm_mode(struct gs_panel *ctx,
+				enum gs_hbm_mode mode)
+{
+	struct device *dev = ctx->dev;
+
+	ctx->hbm_mode = mode;
+
+	/* FGZ mode */
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x01, 0x22, 0x68);
+	if (GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode)) {
+		if (ctx->panel_rev == PANEL_REV_EVT1)
+			GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x1C, 0xE3, 0xFF, 0x94); /* FGZ Mode ON */
+		else
+			GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x28, 0xED, 0xFF, 0x94); /* FGZ Mode ON */;
+	} else {
+		GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x00, 0x00, 0xFF, 0x90); /* FGZ Mode OFF */
+	}
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	dev_info(dev, "hbm_on=%d hbm_ircoff=%d.\n", GS_IS_HBM_ON(ctx->hbm_mode),
+		 GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode));
+}
+
+static void tk4a_set_dimming_on(struct gs_panel *gs_panel,
+                                bool dimming_on)
+{
+	const struct gs_panel_mode *pmode = gs_panel->current_mode;
+
+	gs_panel->dimming_on = dimming_on;
+
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_warn(gs_panel->dev, "in lp mode, skip to update dimming usage\n");
+		return;
+	}
+
+	tk4a_update_wrctrld(gs_panel);
+}
+
+static void tk4a_mode_set(struct gs_panel *ctx,
+                          const struct gs_panel_mode *pmode)
+{
+	tk4a_change_frequency(ctx, pmode);
+}
+
+static bool tk4a_is_mode_seamless(const struct gs_panel *ctx,
+                                  const struct gs_panel_mode *pmode)
+{
+	/* seamless mode switch is possible if only changing refresh rate */
+	return drm_mode_equal_no_clocks(&ctx->current_mode->mode, &pmode->mode);
+}
+
+static void tk4a_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+#ifdef CONFIG_DEBUG_FS
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot) {
+		goto panel_out;
+	}
+
+	gs_panel_debugfs_create_cmdset(csroot, &tk4a_init_cmdset, "init");
+
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+#endif
+}
+
+static void tk4a_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	u8 build_code = (id & 0xFF00) >> 8;
+	u8 main = (build_code & 0xE0) >> 3;
+	u8 sub = (build_code & 0x0C) >> 2;
+	u8 rev = main | sub;
+
+	gs_panel_get_panel_rev(ctx, rev);
+}
+
+static int tk4a_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state = drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	const struct gs_panel_mode *pmode;
+	bool was_lp_mode, is_lp_mode;
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+		!new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	was_lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
+	/* don't skip update when switching between AoD and normal mode */
+	pmode = gs_panel_get_mode(ctx, &new_crtc_state->mode);
+	if (pmode) {
+		is_lp_mode = pmode->gs_mode.is_lp_mode;
+		if ((was_lp_mode && !is_lp_mode) || (!was_lp_mode && is_lp_mode))
+			new_crtc_state->color_mgmt_changed = true;
+	} else {
+		dev_err(ctx->dev, "%s: no new mode\n", __func__);
+	}
+
+	if (!drm_atomic_crtc_effectively_active(old_crtc_state) ||
+		(was_lp_mode && drm_mode_vrefresh(&new_crtc_state->mode) == 60)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		/* set clock to max refresh rate on resume or AOD exit to 60Hz */
+		mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			ctx->gs_connector->needs_commit = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n",
+				mode->name,
+				!drm_atomic_crtc_effectively_active(old_crtc_state) ?
+				"resume" : "lp exit");
+		}
+	} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
+		/* clock hacked in last commit due to resume or lp exit, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		ctx->gs_connector->needs_commit = false;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after resume or lp exit\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void tk4a_set_nolp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *current_mode = ctx->current_mode;
+	unsigned int vrefresh = current_mode ? drm_mode_vrefresh(&current_mode->mode) : 30;
+	unsigned int te_usec = current_mode ? current_mode->gs_mode.te_usec : 1109;
+
+	if (!gs_is_panel_active(ctx))
+		return;
+
+	GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_OFF);
+
+	/* backlight control and dimming */
+	tk4a_update_wrctrld(ctx);
+	tk4a_change_frequency(ctx, pmode);
+
+	PANEL_ATRACE_BEGIN("tk4a_wait_one_vblank");
+	gs_panel_wait_for_vsync_done(ctx, te_usec,
+			GS_VREFRESH_TO_PERIOD_USEC(vrefresh));
+
+	/* Additional sleep time to account for TE variability */
+	usleep_range(1000, 1010);
+	PANEL_ATRACE_END("tk4a_wait_one_vblank");
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_DISPLAY_ON);
+
+	dev_info(dev, "exit LP mode\n");
+}
+
+static void tk4a_10bit_set(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x28, 0xF2);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xF2, 0xCC);  /* 10bit */
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+}
+
+static int tk4a_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	if (!pmode) {
+		dev_err(dev, "no current mode set\n");
+		return -EINVAL;
+	}
+
+	dev_info(dev, "%s\n", __func__);
+
+	gs_panel_reset_helper(ctx);
+
+	/* sleep out */
+	GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
+
+	tk4a_10bit_set(ctx);
+
+	/* initial command */
+	gs_panel_send_cmdset(ctx, &tk4a_init_cmdset);
+
+	/* frequency */
+	tk4a_change_frequency(ctx, pmode);
+
+	/* DSC related configuration */
+	mipi_dsi_compression_mode(to_mipi_dsi_device(dev), true);
+	gs_dcs_write_dsc_config(dev, &pps_config);
+	/* DSC Enable */
+	GS_DCS_BUF_ADD_CMD(dev, 0x9D, 0x01);
+
+	/* dimming and HBM */
+	tk4a_update_wrctrld(ctx);
+
+	if (pmode->gs_mode.is_lp_mode)
+		gs_panel_set_lp_mode_helper(ctx, pmode);
+
+	/* display on */
+	GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+
+	return 0;
+}
+
+static int tk4a_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct tk4a_panel *spanel;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+
+	spanel->is_pixel_off = false;
+
+	return gs_dsi_panel_common_init(dsi, &spanel->base);
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 500,
+	.te_var = 1,
+};
+
+static const u16 WIDTH_MM = 65, HEIGHT_MM = 146;
+static const u16 HDISPLAY = 1080, VDISPLAY = 2424;
+static const u16 HFP = 32, HSA = 12, HBP = 16;
+static const u16 VFP = 12, VSA = 4, VBP = 15;
+
+#define TK4A_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &pps_config,\
+}
+
+static const struct gs_panel_mode_array tk4a_modes = {
+	.num_modes = 2,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@60:60",
+				DRM_MODE_TIMING(60, HDISPLAY, HFP, HSA, HBP, VDISPLAY, VFP, VSA, VBP),
+				/* aligned to bootloader setting */
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 8450,
+				.bpc = 8,
+				.dsc = TK4A_DSC,
+				.underrun_param = &underrun_param,
+			},
+		},
+		{
+			.mode = {
+				.name = "1080x2424@120:120",
+				DRM_MODE_TIMING(120, HDISPLAY, HFP, HSA, HBP, VDISPLAY, VFP, VSA, VBP),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 276,
+				.bpc = 8,
+				.dsc = TK4A_DSC,
+				.underrun_param = &underrun_param,
+			},
+		},
+	}, /* modes */
+};
+
+const struct brightness_capability tk4a_brightness_capability = {
+	.normal = {
+		.nits = {
+			.min = 2,
+			.max = 1200,
+		},
+		.level = {
+			.min = 184,
+			.max = 3427,
+		},
+		.percentage = {
+			.min = 0,
+			.max = 67,
+		},
+	},
+	.hbm = {
+		.nits = {
+			.min = 1200,
+			.max = 1800,
+		},
+		.level = {
+			.min = 3428,
+			.max = 4095,
+		},
+		.percentage = {
+			.min = 67,
+			.max = 100,
+		},
+	},
+};
+
+static const struct gs_panel_mode_array tk4a_lp_modes = {
+	.num_modes = 2,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@30:30",
+				DRM_MODE_TIMING(30, HDISPLAY, HFP, HSA, HBP, VDISPLAY, VFP, VSA, VBP),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1109,
+				.bpc = 8,
+				.dsc = TK4A_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+	},
+};
+
+static const struct drm_panel_funcs tk4a_drm_funcs = {
+	.disable = gs_panel_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = tk4a_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = tk4a_debugfs_init,
+};
+
+static const struct gs_panel_funcs tk4a_gs_funcs = {
+	.set_brightness = tk4a_set_brightness,
+	.set_lp_mode = gs_panel_set_lp_mode_helper,
+	.set_nolp_mode = tk4a_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_dimming = tk4a_set_dimming_on,
+	.set_hbm_mode = tk4a_set_hbm_mode,
+	.is_mode_seamless = tk4a_is_mode_seamless,
+	.mode_set = tk4a_mode_set,
+	.get_panel_rev = tk4a_get_panel_rev,
+	.read_id = gs_panel_read_slsi_ddic_id,
+	.atomic_check = tk4a_atomic_check,
+};
+
+const struct gs_panel_brightness_desc tk4a_brightness_desc = {
+	.max_brightness = 4095,
+	.min_brightness = 2,
+	.max_luminance = 10000000,
+	.max_avg_luminance = 1200000,
+	.min_luminance = 5,
+	.default_brightness = 1290,    /* 140 nits */
+	.brt_capability = &tk4a_brightness_capability,
+};
+
+const struct gs_panel_reg_ctrl_desc tk4a_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 0},
+		{PANEL_REG_ID_VCI, 0},
+		{PANEL_REG_ID_VDDD, 10},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VDDD, 0},
+		{PANEL_REG_ID_VCI, 0},
+		{PANEL_REG_ID_VDDI, 0},
+	},
+};
+
+const struct gs_panel_desc google_tk4a = {
+	.data_lane_cnt = 4,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.brightness_desc = &tk4a_brightness_desc,
+	.modes = &tk4a_modes,
+	.off_cmdset = &tk4a_off_cmdset,
+	.lp_modes = &tk4a_lp_modes,
+	.lp_cmdset = &tk4a_lp_cmdset,
+	.binned_lp = tk4a_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(tk4a_binned_lp),
+	.reg_ctrl_desc = &tk4a_reg_ctrl_desc,
+	.panel_func = &tk4a_drm_funcs,
+	.gs_panel_func = &tk4a_gs_funcs,
+	.reset_timing_ms = { 1, 1, 5 },
+};
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-tk4a", .data = &google_tk4a },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_of_match);
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = tk4a_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-tk4a",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Safayat Ullah <safayat@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google tk4a panel driver");
+MODULE_LICENSE("GPL");
diff --git a/panel-gs-tk4b.c b/panel-gs-tk4b.c
new file mode 100644
index 0000000..62f320d
--- /dev/null
+++ b/panel-gs-tk4b.c
@@ -0,0 +1,1133 @@
+/* SPDX-License-Identifier: MIT */
+
+#include <drm/display/drm_dsc_helper.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/thermal.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+#define TK4B_DDIC_ID_LEN 8
+#define TK4B_DIMMING_FRAME 32
+
+#define MIPI_DSI_FREQ_MBPS_DEFAULT 756
+#define MIPI_DSI_FREQ_MBPS_ALTERNATIVE 776
+
+#define WIDTH_MM 64
+#define HEIGHT_MM 145
+
+#define PROJECT "TK4B"
+
+/**
+ * struct tk4b_panel - panel specific runtime info
+ *
+ * This struct maintains tk4b panel specific runtime info, any fixed details about panel
+ * should most likely go into struct gs_panel_desc.
+ */
+struct tk4b_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+	/** @is_hbm2_enabled: indicates panel is running in HBM mode 2 */
+	bool is_hbm2_enabled;
+};
+
+#define to_spanel(ctx) container_of(ctx, struct tk4b_panel, base)
+
+static const struct gs_dsi_cmd tk4b_lp_cmds[] = {
+	/* Disable the Black insertion in AoD */
+	GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
+	GS_DSI_CMD(0xC0, 0x54),
+
+	/* disable dimming */
+	GS_DSI_CMD(0x53, 0x20),
+	/* enter AOD */
+	GS_DSI_CMD(MIPI_DCS_ENTER_IDLE_MODE),
+	/* Settings AOD Hclk */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x81),
+	GS_DSI_CMD(0x6F, 0x0E),
+	GS_DSI_CMD(0xF5, 0x20),
+	/* Lock TE2 30Hz */
+	GS_DSI_CMD(0x5A, 0x04),
+};
+static DEFINE_GS_CMDSET(tk4b_lp);
+
+static const struct gs_dsi_cmd tk4b_lp_off_cmds[] = {
+	GS_DSI_CMD(0x6F, 0x04),
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0x00),
+};
+
+static const struct gs_dsi_cmd tk4b_lp_night_cmds[] = {
+	/* 2 nit */
+	GS_DSI_CMD(0x6F, 0x04),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1_1),
+				MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0x03),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1_1),
+				MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x03, 0x33),
+};
+
+static const struct gs_dsi_cmd tk4b_lp_low_cmds[] = {
+	/* 10 nit */
+	GS_DSI_CMD(0x6F, 0x04),
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1_1),
+				MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x07, 0xB2),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1_1),
+				MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x03, 0x33),
+};
+
+static const struct gs_dsi_cmd tk4b_lp_high_cmds[] = {
+	/* 50 nit */
+	GS_DSI_CMD(0x6F, 0x04),
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFE),
+};
+
+static const struct gs_binned_lp tk4b_binned_lp[] = {
+	BINNED_LP_MODE("off", 0, tk4b_lp_off_cmds),
+	/* night threshold 4 nits */
+	BINNED_LP_MODE_TIMING("night", 104, tk4b_lp_night_cmds, 0, 45),
+	/* low threshold 40 nits */
+	BINNED_LP_MODE_TIMING("low", 932, tk4b_lp_low_cmds, 0, 45),
+	/* rising = 0, falling = 45 */
+	BINNED_LP_MODE_TIMING("high", 3574, tk4b_lp_high_cmds, 0, 45),
+};
+
+static const struct gs_dsi_cmd tk4b_set_freq_120_cmds[] = {
+	GS_DSI_QUEUE_CMD(0x2F, 0x00),
+	GS_DSI_FLUSH_CMD(MIPI_DCS_SET_GAMMA_CURVE, 0x00),
+};
+static DEFINE_GS_CMDSET(tk4b_set_freq_120);
+
+static const struct gs_dsi_cmd tk4b_set_freq_60_cmds[] = {
+	GS_DSI_QUEUE_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x2F, 0x30),
+	GS_DSI_QUEUE_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0x2F, 0x02),
+	GS_DSI_QUEUE_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
+	GS_DSI_QUEUE_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0xB0),
+	GS_DSI_FLUSH_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBA, 0x41),
+	GS_DSI_QUEUE_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x03),
+	GS_DSI_FLUSH_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0xC0, 0x10),
+};
+static DEFINE_GS_CMDSET(tk4b_set_freq_60);
+
+static const struct gs_dsi_cmd tk4b_off_cmds[] = {
+	GS_DSI_DELAY_CMD(100, MIPI_DCS_SET_DISPLAY_OFF),
+	GS_DSI_DELAY_CMD(120, MIPI_DCS_ENTER_SLEEP_MODE),
+};
+static DEFINE_GS_CMDSET(tk4b_off);
+
+static const struct gs_dsi_cmd tk4b_init_cmds[] = {
+	/* CMD2, Page0 */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
+
+	/* b/314125080 Add ELVSS Settings for avoid exit AOD flashing */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x6F, 0x06),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB5, 0x7F, 0x00, 0x2C, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x6F, 0x11),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB5, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x6F, 0x2D),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB5, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+				0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x25, 0x25,
+				0x1B, 0x1B, 0x13, 0x13, 0x0C, 0x0C, 0x0C, 0x0C, 0x07),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x6F, 0x44),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB5, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+				0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x25, 0x25,
+				0x1B, 0x1B, 0x13, 0x13, 0x0C, 0x0C, 0x0C, 0x0C, 0x07),
+
+	/* b/295134228 correct EM pulse and EM-off time */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x18),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xB2, 0x38, 0xB0, 0x3F, 0xFF),
+
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x1B),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBA, 0x18),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x1C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x2C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+				0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x3C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03,
+				0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x4C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x0B, 0x0B, 0x0B, 0x0B,
+				0x0B, 0x0B, 0x0B, 0x0B, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x5C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+				0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x6C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+				0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x7C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+				0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x8C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x9C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0xA4),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBA, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0xA8),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0xB0),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1),
+				0xBA, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x08),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBB, 0x01, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x18),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBB, 0x01, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x1C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xBB, 0x01, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x0A),
+	/* VGSP adjust */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xE3, 0x00, 0x00, 0x00, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x18),
+	/* Vin source adjust */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0xD8, 0x38),
+
+	/* CMD2, Page1 */
+	GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
+	GS_DSI_CMD(0x6F, 0x05),
+	GS_DSI_CMD(0xC5, 0x15, 0x15, 0x15, 0xDD),
+
+	/* FFC Off */
+	GS_DSI_CMD(0xC3, 0x00),
+	/* FFC setting (MIPI: 756Mbps) */
+	GS_DSI_CMD(0xC3, 0x00, 0x06, 0x20, 0x0C, 0xFF, 0x00, 0x06, 0x20,
+				 0x0C, 0xFF, 0x00, 0x04, 0x63, 0x0C, 0x05, 0xD9,
+				 0x10, 0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10, 0x04,
+				 0x63, 0x0C, 0x05, 0xD9, 0x10, 0x04, 0x63, 0x0C,
+				 0x05, 0xD9, 0x10, 0x04, 0x63, 0x0C, 0x05, 0xD9,
+				 0x10),
+
+	/* CMD2, Page3 */
+	GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
+	/* Disable AOD power saving */
+	GS_DSI_CMD(0xC7, 0x00),
+
+	/* CMD2, Page4 */
+	GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
+	/* Extend DBI flash data update cycle time */
+	GS_DSI_CMD(0xBB, 0xB3, 0x01, 0xBC),
+
+	/* CMD2, Page7 */
+	GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x07),
+	/* Round algorithm OFF */
+	GS_DSI_CMD(0xC0, 0x00),
+
+	/* CMD2, Page8 */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x08),
+	/* Deburn in setting */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xC2, 0x33, 0x00, 0x78, 0x03, 0x82, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0x6F, 0x06),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xC2, 0x79, 0xA5, 0xBF, 0xD2, 0xE1, 0xEF, 0xFA),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xC3, 0x00, 0x00, 0x02, 0x00, 0x02, 0x08, 0x00, 0x08,
+				0x16, 0x00, 0x16, 0x47, 0x00, 0x47, 0x8D, 0x10, 0x8D,
+				0x87, 0x41, 0x87, 0x2E, 0x84, 0x2E, 0x00, 0xE8, 0x00,
+				0x4B, 0xEE, 0x4B, 0x4B, 0xFE, 0x4B, 0xFF),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xC7, 0x00),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xC8, 0x00, 0x00, 0x01, 0x02, 0x04, 0x09, 0x13, 0x26, 0x4C),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xC9, 0x00, 0x01, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xCA, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xCB, 0x11, 0x00, 0x00, 0x32, 0x00, 0x00, 0x75, 0x00,
+				0x00, 0xBB, 0x00, 0x00, 0x9A, 0x00, 0x00, 0xF9, 0x00, 0xFF),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xCC, 0x22, 0x00, 0x00, 0x32, 0x00, 0x00, 0x52, 0x00,
+				0x00, 0xA7, 0x00, 0x00, 0xBB, 0x00, 0x00, 0xFB, 0x00, 0xFF),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xCE, 0x22, 0x00, 0x00, 0x32, 0x00, 0x00, 0x63, 0x00,
+				0x00, 0xAA, 0x00, 0x00, 0xAC, 0x00, 0x00, 0xFA, 0x00, 0xFF),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT),
+				0xCF, 0x56, 0x00, 0x00, 0x45, 0x00, 0x00, 0x54, 0x00,
+				0x00, 0x45, 0x00, 0x00, 0x44, 0x00, 0x00, 0x34, 0x00,
+				0x00, 0x33, 0x00, 0x00, 0x33, 0x66, 0x00, 0x23, 0x00,
+				0x00, 0x22, 0x00, 0x00, 0x22, 0x00, 0x00),
+
+	/* CMD3, Page0 */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x80),
+	GS_DSI_CMD(0x6F, 0x19),
+	GS_DSI_CMD(0xF2, 0x00),
+	GS_DSI_CMD(0x6F, 0x1A),
+	GS_DSI_CMD(0xF4, 0x55),
+	GS_DSI_CMD(0x6F, 0x2D),
+	GS_DSI_CMD(0xFC, 0x44),
+	GS_DSI_CMD(0x6F, 0x11),
+	GS_DSI_CMD(0xF8, 0x01, 0x7B),
+	GS_DSI_CMD(0x6F, 0x2D),
+	GS_DSI_CMD(0xF8, 0x01, 0x1D),
+
+	/* CMD3, Page1 */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x81),
+	GS_DSI_CMD(0x6F, 0x05),
+	GS_DSI_CMD(0xFE, 0x3C),
+	GS_DSI_CMD(0x6F, 0x02),
+	GS_DSI_CMD(0xF9, 0x04),
+	GS_DSI_CMD(0x6F, 0x1E),
+	GS_DSI_CMD(0xFB, 0x0F),
+	GS_DSI_CMD(0x6F, 0x0D),
+	GS_DSI_CMD(0xFB, 0x84),
+	GS_DSI_CMD(0x6F, 0x0F),
+	GS_DSI_CMD(0xF5, 0x20),
+	/* CMD3, Page2 */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x82),
+	GS_DSI_CMD(0x6F, 0x09),
+	GS_DSI_CMD(0xF2, 0x55),
+	/* CMD3, Page3 */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x83),
+	GS_DSI_CMD(0x6F, 0x12),
+	GS_DSI_CMD(0xFE, 0x41),
+
+	/* CMD, Disable */
+	GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x00),
+
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x00),
+	/* TE2 width 45H */
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON, 0x00, 0x2D),
+	/* BC Dimming OFF */
+	GS_DSI_CMD(MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20),
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37),
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x77),
+	/* Normal GMA */
+	GS_DSI_CMD(MIPI_DCS_SET_GAMMA_CURVE, 0x00),
+
+	/* CMD1, DPC Temperature 25 */
+	GS_DSI_CMD(0x81, 0x01, 0x19),
+	GS_DSI_CMD(0x03, 0x01),
+	GS_DSI_CMD(0x90, 0x03, 0x03),
+	/* 2DSC & slice high 24 DSC v1.2a */
+	GS_DSI_CMD(0x91, 0x89, 0xA8, 0x00, 0x18, 0xD2, 0x00, 0x02, 0x25, 0x02,
+				0x35, 0x00, 0x07, 0x04, 0x86, 0x04, 0x3D, 0x10, 0xF0),
+	/* Long V = 60Hz */
+	GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1), 0x2F, 0x02),
+	/* 60Hz */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x2F, 0x30),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1), 0x6D, 0x00, 0x00),
+	GS_DSI_DELAY_CMD(60, MIPI_DCS_EXIT_SLEEP_MODE)
+};
+static DEFINE_GS_CMDSET(tk4b_init);
+
+static void tk4b_update_te2(struct gs_panel *ctx)
+{
+	struct gs_panel_te2_timing timing;
+	struct device *dev = ctx->dev;
+	u8 width = 0x2D; /* default width 45H */
+	u32 rising = 0, falling;
+	int ret;
+
+	if (!ctx)
+		return;
+
+	ret = gs_panel_get_current_mode_te2(ctx, &timing);
+	if (!ret) {
+		falling = timing.falling_edge;
+		if (falling >= timing.rising_edge) {
+			rising = timing.rising_edge;
+			width = falling - rising;
+		} else {
+			dev_warn(ctx->dev, "invalid timing, use default setting\n");
+		}
+	} else if (ret == -EAGAIN) {
+		dev_dbg(ctx->dev, "Panel is not ready, use default setting\n");
+	} else {
+		return;
+	}
+
+	dev_dbg(ctx->dev, "TE2 updated: rising= 0x%x, width= 0x%x", rising, width);
+
+	GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, rising);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_TEAR_ON, 0x00, width);
+}
+
+static void tk4b_update_irc(struct gs_panel *ctx,
+				const enum gs_hbm_mode hbm_mode,
+				const int vrefresh)
+{
+	struct device *dev = ctx->dev;
+	struct tk4b_panel *spanel = to_spanel(ctx);
+	const u16 level = gs_panel_get_brightness(ctx);
+
+	if (GS_IS_HBM_ON_IRC_OFF(hbm_mode)) {
+		if (level == ctx->desc->brightness_desc->brt_capability->hbm.level.max) {
+			/* set brightness to hbm2 */
+			GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFF);
+			spanel->is_hbm2_enabled = true;
+			/* set ACD Level 3 */
+			GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x04);
+			GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+			GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0C);
+			GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x0E, 0x2C, 0x32);
+		} else {
+			if (spanel->is_hbm2_enabled) {
+				/* set ACD off */
+				GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x00);
+			}
+			spanel->is_hbm2_enabled = false;
+		}
+
+		dev_info(ctx->dev, "%s: is HBM2 enabled : %d\n",
+					__func__, spanel->is_hbm2_enabled);
+
+		/* IRC Off */
+		GS_DCS_BUF_ADD_CMD(dev, 0x5F, 0x01);
+		if (vrefresh == 120) {
+			GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x00);
+			GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
+			if (ctx->panel_rev < PANEL_REV_PVT) {
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				if (ctx->panel_rev < PANEL_REV_EVT1)
+					GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x32);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x40);
+			}
+		} else {
+			if (ctx->panel_rev < PANEL_REV_EVT1) {
+				GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x30);
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0xB0);
+				GS_DCS_BUF_ADD_CMD(dev, 0xBA, 0x44);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x32);
+			} else {
+				GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x02);
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x40);
+			}
+		}
+	} else {
+		const u8 val1 = level >> 8;
+		const u8 val2 = level & 0xff;
+
+		/* IRC ON */
+		GS_DCS_BUF_ADD_CMD(dev, 0x5F, 0x00);
+		if (vrefresh == 120) {
+			GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x00);
+			GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_GAMMA_CURVE, 0x00);
+			if (ctx->panel_rev < PANEL_REV_PVT) {
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				if (ctx->panel_rev < PANEL_REV_EVT1)
+					GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x30);
+				else
+					GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x10);
+			}
+		} else {
+			if (ctx->panel_rev < PANEL_REV_EVT1) {
+				GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x30);
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0xB0);
+				GS_DCS_BUF_ADD_CMD(dev, 0xBA, 0x41);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x30);
+			} else {
+				GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x02);
+				GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+				GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
+				GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x10);
+			}
+		}
+		/* sync from bigSurf : restore the dbv value while IRC ON */
+		GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, val1, val2);
+	}
+	/* Empty command is for flush */
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0x00);
+}
+
+static void tk4b_change_frequency(struct gs_panel *ctx,
+				    const struct gs_panel_mode *pmode)
+{
+	int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (!ctx || (vrefresh != 60 && vrefresh != 120))
+		return;
+
+	PANEL_ATRACE_BEGIN("panel change frequency to %dHz", vrefresh);
+
+	if (!GS_IS_HBM_ON(ctx->hbm_mode)) {
+		if (vrefresh == 120)
+			gs_panel_send_cmdset(ctx, &tk4b_set_freq_120_cmdset);
+		else
+			gs_panel_send_cmdset(ctx, &tk4b_set_freq_60_cmdset);
+	} else {
+		tk4b_update_irc(ctx, ctx->hbm_mode, vrefresh);
+	}
+
+	PANEL_ATRACE_END("panel change frequency to %dHz", vrefresh);
+}
+
+static void tk4b_set_dimming(struct gs_panel *ctx,
+				 bool dimming_on)
+{
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_warn(ctx->dev, "in lp mode, skip dimming update\n");
+		return;
+	}
+
+	ctx->dimming_on = dimming_on;
+	GS_DCS_WRITE_CMD(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+					ctx->dimming_on ? 0x28 : 0x20);
+	dev_dbg(ctx->dev, "%s dimming_on=%d\n", __func__, dimming_on);
+}
+
+static void tk4b_set_nolp_mode(struct gs_panel *ctx,
+				  const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+
+	int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (!gs_is_panel_active(ctx))
+		return;
+
+	/* exit AOD */
+	GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x54);
+	GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_EXIT_IDLE_MODE);
+	GS_DCS_BUF_ADD_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x81);
+	GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0E);
+	GS_DCS_BUF_ADD_CMD(dev, 0xF5, 0x2B);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0x5A, 0x04);
+
+	tk4b_change_frequency(ctx, pmode);
+	ctx->timestamps.idle_exit_dimming_delay_ts = ktime_add_us(
+		ktime_get(), 100 + GS_VREFRESH_TO_PERIOD_USEC(vrefresh) * 2);
+
+	dev_info(ctx->dev, "exit LP mode\n");
+}
+
+static void tk4b_dimming_frame_setting(struct gs_panel *ctx, u8 dimming_frame)
+{
+	struct device *dev = ctx->dev;
+
+	/* Fixed time 1 frame */
+	if (!dimming_frame)
+		dimming_frame = 0x01;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB2, 0x19);
+	GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x05);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, dimming_frame, dimming_frame);
+}
+
+static int tk4b_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	if (!pmode) {
+		dev_err(ctx->dev, "no current mode set\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(ctx->dev, "%s\n", __func__);
+
+	/* toggle reset gpio */
+	gs_panel_reset_helper(ctx);
+
+	/* toggle reset gpio */
+	gs_panel_send_cmdset(ctx, &tk4b_init_cmdset);
+
+	/* frequency */
+	tk4b_change_frequency(ctx, pmode);
+
+	/* dimming frame */
+	tk4b_dimming_frame_setting(ctx, TK4B_DIMMING_FRAME);
+	ctx->timestamps.idle_exit_dimming_delay_ts = 0;
+
+	if (pmode->gs_mode.is_lp_mode)
+		gs_panel_set_lp_mode_helper(ctx, pmode);
+
+	GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+
+	ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT;
+
+	return 0;
+}
+
+static int tk4b_disable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct tk4b_panel *spanel = to_spanel(ctx);
+	int ret;
+
+	spanel->is_hbm2_enabled = false;
+
+	ret = gs_panel_disable(panel);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int tk4b_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state =
+					drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	const struct gs_panel_mode *pmode;
+	bool was_lp_mode, is_lp_mode;
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+	    !new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	was_lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
+	/* don't skip update when switching between AoD and normal mode */
+	pmode = gs_panel_get_mode(ctx, &new_crtc_state->mode);
+	if (pmode) {
+		is_lp_mode = pmode->gs_mode.is_lp_mode;
+		if (was_lp_mode != is_lp_mode)
+			new_crtc_state->color_mgmt_changed = true;
+	} else {
+		dev_err(ctx->dev, "%s: no new mode\n", __func__);
+	}
+
+	if (!drm_atomic_crtc_effectively_active(old_crtc_state) ||
+	    (was_lp_mode && drm_mode_vrefresh(&new_crtc_state->mode) == 60)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			ctx->gs_connector->needs_commit = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n",
+				mode->name,
+				!drm_atomic_crtc_effectively_active(old_crtc_state) ?
+				"resume" : "lp exit");
+		}
+	} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
+		/* clock hacked in last commit due to resume or lp exit, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		ctx->gs_connector->needs_commit = false;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after resume or lp exit\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void tk4b_pre_update_ffc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(ctx->dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	/* FFC off */
+	GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xC3, 0x00);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void tk4b_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(ctx->dev, "%s: hs_clk_mbps: current=%d, target=%d\n",
+		__func__, ctx->dsi_hs_clk_mbps, hs_clk_mbps);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	if (hs_clk_mbps != MIPI_DSI_FREQ_MBPS_DEFAULT &&
+	    hs_clk_mbps != MIPI_DSI_FREQ_MBPS_ALTERNATIVE) {
+		dev_warn(ctx->dev, "invalid hs_clk_mbps=%d for FFC\n", hs_clk_mbps);
+	} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
+		dev_info(ctx->dev, "%s: updating for hs_clk_mbps=%d\n", __func__, hs_clk_mbps);
+		ctx->dsi_hs_clk_mbps = hs_clk_mbps;
+
+		/* Update FFC */
+		GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
+		if (hs_clk_mbps == MIPI_DSI_FREQ_MBPS_DEFAULT)
+			GS_DCS_BUF_ADD_CMD(dev, 0xC3, 0x00, 0x06, 0x20, 0x0C, 0xFF,
+						0x00, 0x06, 0x20, 0x0C, 0xFF, 0x00,
+						0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
+						0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
+						0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
+						0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
+						0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10);
+		else /* MIPI_DSI_FREQ_MBPS_ALTERNATIVE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xC3, 0x00, 0x06, 0x20, 0x0C, 0xFF,
+						0x00, 0x06, 0x20, 0x0C, 0xFF, 0x00,
+						0x04, 0x46, 0x0C, 0x06, 0x0D, 0x11,
+						0x04, 0x46, 0x0C, 0x06, 0x0D, 0x11,
+						0x04, 0x46, 0x0C, 0x06, 0x0D, 0x11,
+						0x04, 0x46, 0x0C, 0x06, 0x0D, 0x11,
+						0x04, 0x46, 0x0C, 0x06, 0x0D, 0x11);
+	}
+
+	/* FFC on */
+	GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xC3, 0xDD);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static int tk4b_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	struct device *dev = ctx->dev;
+	struct tk4b_panel *spanel = to_spanel(ctx);
+
+	if (ctx->current_mode->gs_mode.is_lp_mode) {
+		const struct gs_panel_funcs *funcs;
+
+		funcs = ctx->desc->gs_panel_func;
+		if (funcs && funcs->set_binned_lp)
+			funcs->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	if (ctx->timestamps.idle_exit_dimming_delay_ts &&
+		(ktime_sub(ctx->timestamps.idle_exit_dimming_delay_ts, ktime_get()) <= 0)) {
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY,
+					ctx->dimming_on ? 0x28 : 0x20);
+		ctx->timestamps.idle_exit_dimming_delay_ts = 0;
+	}
+
+	if (GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode)
+			&& br == ctx->desc->brightness_desc->brt_capability->hbm.level.max) {
+
+		/* set brightness to hbm2 */
+		GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFF);
+		spanel->is_hbm2_enabled = true;
+
+		/* set ACD Level 3 */
+		GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x04);
+		GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+		GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0C);
+		GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB0, 0x0E, 0x2C, 0x32);
+		dev_info(ctx->dev, "%s: is HBM2 enabled : %d\n",
+				__func__, spanel->is_hbm2_enabled);
+	} else {
+
+		if (spanel->is_hbm2_enabled) {
+			/* set ACD off */
+			GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x00);
+			dev_info(ctx->dev, "%s: is HBM2 enabled : off\n", __func__);
+		}
+		spanel->is_hbm2_enabled = false;
+		GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+						br >> 8, br & 0xff);
+	}
+	return 0;
+}
+
+static void tk4b_set_hbm_mode(struct gs_panel *ctx,
+				 enum gs_hbm_mode hbm_mode)
+{
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+	int vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (ctx->hbm_mode == hbm_mode)
+		return;
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
+	GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x11);
+	if (hbm_mode == GS_HBM_OFF)
+		GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, 0x01, 0x01, 0x43);
+	else
+		GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, 0x00, 0x00, 0x41);
+
+	tk4b_update_irc(ctx, hbm_mode, vrefresh);
+
+	ctx->hbm_mode = hbm_mode;
+	dev_info(ctx->dev, "hbm_on=%d hbm_ircoff=%d\n", GS_IS_HBM_ON(ctx->hbm_mode),
+		 GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode));
+}
+
+static void tk4b_mode_set(struct gs_panel *ctx,
+			     const struct gs_panel_mode *pmode)
+{
+	tk4b_change_frequency(ctx, pmode);
+}
+
+static void tk4b_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	const u8 build_code = (id & 0xFF00) >> 8;
+	const u8 main = (build_code & 0xE0) >> 3;
+	const u8 sub = (build_code & 0x0C) >> 2;
+
+	gs_panel_get_panel_rev(ctx, main | sub);
+}
+
+static int tk4b_read_id(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+	char buf[TK4B_DDIC_ID_LEN] = {0};
+	int ret;
+
+	GS_DCS_WRITE_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x81);
+	ret = mipi_dsi_dcs_read(dsi, 0xF2, buf, TK4B_DDIC_ID_LEN);
+	if (ret != TK4B_DDIC_ID_LEN) {
+		dev_warn(ctx->dev, "Unable to read DDIC id (%d)\n", ret);
+		goto done;
+	} else {
+		ret = 0;
+	}
+
+	bin2hex(ctx->panel_id, buf, TK4B_DDIC_ID_LEN);
+done:
+	GS_DCS_WRITE_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x00);
+	return ret;
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 350,
+	.te_var = 1,
+};
+
+/* Truncate 8-bit signed value to 6-bit signed value */
+#define TO_6BIT_SIGNED(v) ((v) & 0x3F)
+
+static const struct drm_dsc_config tk4b_dsc_cfg = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_count = 2,
+	.slice_width = 540,
+	.slice_height = 24,
+	.simple_422 = false,
+	.pic_width = 1080,
+	.pic_height = 2424,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 549,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 13,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{0, 4, TO_6BIT_SIGNED(2)},
+		{0, 4, TO_6BIT_SIGNED(0)},
+		{1, 5, TO_6BIT_SIGNED(0)},
+		{1, 6, TO_6BIT_SIGNED(-2)},
+		{3, 7, TO_6BIT_SIGNED(-4)},
+		{3, 7, TO_6BIT_SIGNED(-6)},
+		{3, 7, TO_6BIT_SIGNED(-8)},
+		{3, 8, TO_6BIT_SIGNED(-8)},
+		{3, 9, TO_6BIT_SIGNED(-8)},
+		{4, 10, TO_6BIT_SIGNED(-10)},
+		{5, 10, TO_6BIT_SIGNED(-10)},
+		{5, 11, TO_6BIT_SIGNED(-10)},
+		{5, 11, TO_6BIT_SIGNED(-12)},
+		{8, 12, TO_6BIT_SIGNED(-12)},
+		{12, 13, TO_6BIT_SIGNED(-12)},
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 7,
+	.scale_increment_interval = 565,
+	.nfl_bpg_offset = 1158,
+	.slice_bpg_offset = 1085,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 540,
+	/* Used DSC v1.2 */
+	.dsc_version_major = 1,
+	.dsc_version_minor = 2,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+#define TK4B_DSC {\
+		.enabled = true, \
+		.dsc_count = 2, \
+		.cfg = &tk4b_dsc_cfg, \
+}
+
+static const struct gs_panel_mode_array tk4b_modes = {
+	.num_modes = 2,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@60:60",
+				DRM_MODE_TIMING(60, 1080, 32, 12, 16, 2424, 12, 4, 15),
+				/* aligned to bootloader setting */
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 8370,
+				.bpc = 8,
+				.dsc = TK4B_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = 0,
+				.falling_edge = 45,
+			},
+		},
+		{
+			.mode = {
+				.name = "1080x2424@120:120",
+				DRM_MODE_TIMING(120, 1080, 32, 12, 16, 2424, 12, 4, 15),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 276,
+				.bpc = 8,
+				.dsc = TK4B_DSC,
+				.underrun_param = &underrun_param,
+			},
+			.te2_timing = {
+				.rising_edge = 0,
+				.falling_edge = 45,
+			},
+		},
+	},/* modes */
+};
+
+static const struct gs_panel_mode_array tk4b_lp_modes = {
+	.num_modes = 1,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@30:30",
+				DRM_MODE_TIMING(30, 1080, 32, 12, 16, 2424, 12, 4, 15),
+				.type = DRM_MODE_TYPE_DRIVER,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.bpc = 8,
+				.dsc = TK4B_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+	},/* modes */
+};
+
+static void tk4b_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot)
+		goto panel_out;
+
+	gs_panel_debugfs_create_cmdset(csroot, &tk4b_init_cmdset, "init");
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+}
+
+static void tk4b_panel_init(struct gs_panel *ctx)
+{
+	tk4b_dimming_frame_setting(ctx, TK4B_DIMMING_FRAME);
+}
+
+static int tk4b_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct tk4b_panel *spanel;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+
+	spanel->is_hbm2_enabled = false;
+	return gs_dsi_panel_common_init(dsi, &spanel->base);
+}
+
+static const struct drm_panel_funcs tk4b_drm_funcs = {
+	.disable = tk4b_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = tk4b_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = tk4b_debugfs_init,
+};
+
+static int tk4b_panel_config(struct gs_panel *ctx);
+
+static const struct gs_panel_funcs tk4b_gs_funcs = {
+	.set_brightness = tk4b_set_brightness,
+	.set_lp_mode = gs_panel_set_lp_mode_helper,
+	.set_nolp_mode = tk4b_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_hbm_mode = tk4b_set_hbm_mode,
+	.set_dimming = tk4b_set_dimming,
+	.is_mode_seamless = gs_panel_is_mode_seamless_helper,
+	.mode_set = tk4b_mode_set,
+	.panel_init = tk4b_panel_init,
+	.panel_config = tk4b_panel_config,
+	.get_panel_rev = tk4b_get_panel_rev,
+	.get_te2_edges = gs_panel_get_te2_edges_helper,
+	.set_te2_edges = gs_panel_set_te2_edges_helper,
+	.update_te2 = tk4b_update_te2,
+	.read_id = tk4b_read_id,
+	.atomic_check = tk4b_atomic_check,
+	.pre_update_ffc = tk4b_pre_update_ffc,
+	.update_ffc = tk4b_update_ffc,
+};
+
+static const struct gs_brightness_configuration tk4b_btr_configs[] = {
+	{
+		.panel_rev = PANEL_REV_LATEST,
+		.default_brightness = 1816,
+		.brt_capability = {
+			.normal = {
+				.nits = {
+					.min = 2,
+					.max = 1200,
+				},
+				.level = {
+					.min = 1,
+					.max = 3628,
+				},
+				.percentage = {
+					.min = 0,
+					.max = 67,
+				},
+			},
+			.hbm = {
+				.nits = {
+					.min = 1200,
+					.max = 1800,
+				},
+				.level = {
+					.min = 3629,
+					.max = 3939,
+				},
+				.percentage = {
+					.min = 67,
+					.max = 100,
+				},
+			},
+		},
+	},
+};
+
+static struct gs_panel_brightness_desc tk4b_brightness_desc = {
+	.max_luminance = 10000000,
+	.max_avg_luminance = 10000000,
+	.min_luminance = 5,
+};
+
+static struct gs_panel_reg_ctrl_desc tk4b_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 0},
+		{PANEL_REG_ID_VCI, 0},
+		{PANEL_REG_ID_VDDD, 10},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VDDD, 0},
+		{PANEL_REG_ID_VCI, 0},
+		{PANEL_REG_ID_VDDI, 0},
+	},
+};
+
+static struct gs_panel_desc gs_tk4b = {
+	.data_lane_cnt = 4,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.brightness_desc = &tk4b_brightness_desc,
+	.modes = &tk4b_modes,
+	.off_cmdset = &tk4b_off_cmdset,
+	.lp_modes = &tk4b_lp_modes,
+	.lp_cmdset = &tk4b_lp_cmdset,
+	.binned_lp = tk4b_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(tk4b_binned_lp),
+	.has_off_binned_lp_entry = true,
+	.reg_ctrl_desc = &tk4b_reg_ctrl_desc,
+	.panel_func = &tk4b_drm_funcs,
+	.gs_panel_func = &tk4b_gs_funcs,
+	.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT,
+	.reset_timing_ms = {1, 1, 20},
+	.refresh_on_lp = true,
+};
+
+static int tk4b_panel_config(struct gs_panel *ctx)
+{
+	gs_panel_model_init(ctx, PROJECT, 0);
+
+	return gs_panel_update_brightness_desc(&tk4b_brightness_desc, tk4b_btr_configs,
+						ARRAY_SIZE(tk4b_btr_configs), ctx->panel_rev);
+}
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-tk4b", .data = &gs_tk4b },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_of_match);
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = tk4b_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-tk4b",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Gil Liu <gilliu@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google tk4b panel driver");
+MODULE_LICENSE("GPL");
diff --git a/panel-gs-tk4c-test.c b/panel-gs-tk4c-test.c
new file mode 100644
index 0000000..f910733
--- /dev/null
+++ b/panel-gs-tk4c-test.c
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: MIT */
+
+#include "gs_panel/gs_panel_test.h"
+
+/* Registers */
+
+static const u8 test_key_enable[] = { 0xF0, 0x5A, 0x5A };
+static const u8 test_key_disable[] = { 0xF0, 0xA5, 0xA5 };
+
+const struct gs_dsi_cmd irc_read_cmds[] = {
+	GS_DSI_CMD(0xB0, 0x00, 0x03, 0x68),
+};
+static DEFINE_GS_CMDSET(irc_read);
+
+const struct gs_dsi_cmd fgz_read_cmds[] = {
+	GS_DSI_CMD(0xB0, 0x00, 0x61, 0x68),
+};
+static DEFINE_GS_CMDSET(fgz_read);
+
+const struct gs_dsi_cmd opr_read_cmds[] = {
+	GS_DSI_CMD(0xB0, 0x00, 0xD8, 0x63),
+};
+static DEFINE_GS_CMDSET(opr_read);
+
+const struct gs_dsi_cmd test_key_enable_cmds[] = {
+	GS_DSI_CMDLIST(test_key_enable),
+};
+static DEFINE_GS_CMDSET(test_key_enable);
+
+const struct gs_dsi_cmd test_key_disable_cmds[] = {
+	GS_DSI_CMDLIST(test_key_disable),
+};
+static DEFINE_GS_CMDSET(test_key_disable);
+
+/* sorted by register address */
+static const struct gs_panel_register tk4c_registers[] = {
+	GS_PANEL_REG("lp_status", 0x54),
+	GS_PANEL_REG_LONG_WITH_CMDS("opr", 0x63, 2, &opr_read_cmdset),
+	GS_PANEL_REG_WITH_CMDS("irc", 0x68, &irc_read_cmdset),
+	GS_PANEL_REG_LONG_WITH_CMDS("fgz", 0x68, 8, &fgz_read_cmdset),
+	GS_PANEL_REG_LONG("refresh_rate", 0x84, 2),
+};
+
+static const struct gs_panel_registers_desc tk4c_reg_desc = {
+	.register_count = ARRAY_SIZE(tk4c_registers),
+	.registers = tk4c_registers,
+
+	.global_pre_read_cmdset = &test_key_enable_cmdset,
+	.global_post_read_cmdset = &test_key_disable_cmdset,
+};
+
+/* Query functions */
+
+static bool array_is_equal(const u8 *l, const u8 *r, int count)
+{
+	return memcmp(l, r, count * sizeof(u8)) == 0;
+}
+
+struct array_to_value {
+	const u8 *array;
+	const int value;
+	const u64 rev;
+};
+
+struct gs_panel_register_query {
+	const struct gs_panel_register *reg;
+	const struct array_to_value *map;
+	int map_size;
+	int default_result;
+};
+
+int get_query_result_from_register(struct gs_panel_test *test,
+				   const struct gs_panel_register_query *query)
+{
+	struct gs_panel *ctx = test->ctx;
+	int i, ret = -1;
+	u8 *read_result;
+
+	if (!query || !query->reg)
+		return ret;
+
+	read_result = kmalloc_array(query->reg->size, sizeof(u8), GFP_KERNEL);
+
+	if (gs_panel_read_register_value(test, query->reg, read_result))
+		goto free_mem;
+
+	ret = query->default_result;
+	for (i = 0; i < query->map_size; i++) {
+		if (query->map[i].rev && !(ctx->panel_rev & query->map[i].rev))
+			continue;
+
+		if (array_is_equal(read_result, query->map[i].array, query->reg->size)) {
+			ret = query->map[i].value;
+			goto free_mem;
+		}
+	}
+
+free_mem:
+	kfree(read_result);
+	return ret;
+}
+
+int tk4c_query_is_aod_on(struct gs_panel_test *test)
+{
+	static const u8 aod_on_value[] = { 0x24 };
+
+	static struct array_to_value aod_read_map[] = {
+		{ .array = aod_on_value, .value = 1 },
+	};
+
+	const struct gs_panel_register_query aod_query = {
+		.reg = &tk4c_registers[0],
+		.map = aod_read_map,
+		.map_size = ARRAY_SIZE(aod_read_map),
+		.default_result = 0,
+	};
+
+	return get_query_result_from_register(test, &aod_query);
+}
+
+int tk4c_query_get_refresh_rate(struct gs_panel_test *test)
+{
+	static const u8 rr_120[] = { 0x00, 0x00 };
+	static const u8 rr_60[] = { 0x08, 0x00 };
+
+	static const struct array_to_value rr_read_map[] = {
+		{ .array = rr_120, .value = 120 },
+		{ .array = rr_60, .value = 60 },
+	};
+
+	static const struct gs_panel_register_query refresh_rate_query = {
+		.reg = &tk4c_registers[4],
+		.map = rr_read_map,
+		.map_size = ARRAY_SIZE(rr_read_map),
+		.default_result = 0,
+	};
+
+	if (tk4c_query_is_aod_on(test) == 1)
+		return 30;
+
+	return get_query_result_from_register(test, &refresh_rate_query);
+}
+
+int tk4c_query_is_irc_on(struct gs_panel_test *test)
+{
+	static const u8 irc_on[] = { 0x25 };
+	static const u8 ifc_off[] = { 0x05 };
+
+	static const struct array_to_value irc_read_map[] = {
+		{ .array = irc_on, .value = 1 },
+		{ .array = ifc_off, .value = 0 },
+	};
+
+	static const struct gs_panel_register_query irc_query = {
+		.reg = &tk4c_registers[2],
+		.map = irc_read_map,
+		.map_size = ARRAY_SIZE(irc_read_map),
+		.default_result = -1,
+	};
+
+	return get_query_result_from_register(test, &irc_query);
+}
+
+const struct gs_panel_query_funcs tk4c_gs_query_func = {
+	.get_refresh_rate = tk4c_query_get_refresh_rate,
+	.get_irc_on = tk4c_query_is_irc_on,
+	.get_aod_on = tk4c_query_is_aod_on,
+};
+
+/* Custom query functions */
+
+static int tk4c_query_fgz_on(struct gs_panel_test *test)
+{
+	static u8 fgz_off_value[] = { 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0x00, 0x00 };
+	static u8 fgz_on_value_EVT[] = { 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0xF5, 0xC4 };
+	static u8 fgz_on_value_DVT[] = { 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0xE4, 0xB6 };
+	static u8 fgz_on_value_PVT[] = { 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0xE4, 0xB6 };
+
+	static const struct array_to_value fgz_read_map[] = {
+		{ .array = fgz_off_value, .value = 0 },
+		{ .array = fgz_on_value_EVT, .value = 1, .rev = PANEL_REV_LT(PANEL_REV_DVT1) },
+		{ .array = fgz_on_value_DVT,
+		  .value = 1,
+		  .rev = (PANEL_REV_DVT1 | PANEL_REV_DVT1_1) },
+		{ .array = fgz_on_value_PVT, .value = 1, .rev = PANEL_REV_GE(PANEL_REV_PVT) },
+	};
+
+	static const struct gs_panel_register_query fgz_query = {
+		.reg = &tk4c_registers[3],
+		.map = fgz_read_map,
+		.map_size = ARRAY_SIZE(fgz_read_map),
+		.default_result = -1,
+	};
+
+	return get_query_result_from_register(test, &fgz_query);
+}
+
+static int tk4c_fgz_show(struct seq_file *m, void *data)
+{
+	struct gs_panel_test *test = m->private;
+
+	if (!test)
+		return -EFAULT;
+
+	seq_printf(m, "%d\n", tk4c_query_fgz_on(test));
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(tk4c_fgz);
+
+int tk4c_add_custom_query_nodes(struct gs_panel_test *test, struct dentry *test_root)
+{
+	struct dentry *query_root;
+
+	query_root = debugfs_lookup("query", test_root);
+	if (!query_root)
+		return -EFAULT;
+
+	debugfs_create_file("fgz_on", 0600, query_root, test, &tk4c_fgz_fops);
+
+	/* TODO: add OPR */
+
+	return 0;
+}
+
+/**
+ * struct tk4c_panel_test - panel specific test runtime info
+ *
+ * Only maintains tk4c panel test specific runtime info, any fixed details about panel
+ * should most likely go into struct gs_panel_test_desc
+ */
+struct tk4c_panel_test {
+	/** @base: base panel struct */
+	struct gs_panel_test base;
+
+	/* add panel specific test data here */
+};
+#define to_spanel_test(test) container_of(test, struct tk4c_panel_test, base)
+
+static void tk4c_test_debugfs_init(struct gs_panel_test *test, struct dentry *test_root)
+{
+	tk4c_add_custom_query_nodes(test, test_root);
+}
+
+static const struct gs_panel_test_funcs tk4c_test_func = {
+	.debugfs_init = tk4c_test_debugfs_init,
+};
+
+static const struct gs_panel_test_desc google_tk4c_test = {
+	.test_funcs = &tk4c_test_func,
+	.regs_desc = &tk4c_reg_desc,
+	.query_desc = &tk4c_gs_query_func,
+};
+
+static int tk4c_panel_test_probe(struct platform_device *pdev)
+{
+	struct tk4c_panel_test *stest;
+
+	stest = devm_kzalloc(&pdev->dev, sizeof(*stest), GFP_KERNEL);
+	if (!stest)
+		return -ENOMEM;
+
+	return gs_panel_test_common_init(pdev, &stest->base);
+}
+
+static const struct of_device_id gs_panel_test_of_match[] = {
+	{ .compatible = "google,gs-tk4c-test", .data = &google_tk4c_test },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_test_of_match);
+
+static struct platform_driver gs_panel_test_driver = {
+	.probe = tk4c_panel_test_probe,
+	.remove = gs_panel_test_common_remove,
+	.driver = {
+		.name = "gs-tk4c-test",
+		.of_match_table = gs_panel_test_of_match,
+	},
+};
+module_platform_driver(gs_panel_test_driver);
+
+MODULE_AUTHOR("Safayat Ullah <safayat@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google tk4c panel test driver");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/panel-gs-tk4c.c b/panel-gs-tk4c.c
new file mode 100644
index 0000000..bc39050
--- /dev/null
+++ b/panel-gs-tk4c.c
@@ -0,0 +1,792 @@
+/* SPDX-License-Identifier: MIT */
+
+#include <drm/display/drm_dsc_helper.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <video/mipi_display.h>
+
+#include "trace/panel_trace.h"
+
+#include "gs_panel/drm_panel_funcs_defaults.h"
+#include "gs_panel/gs_panel.h"
+#include "gs_panel/gs_panel_funcs_defaults.h"
+
+/* PPS Setting DSC 1.2a */
+static const struct drm_dsc_config pps_config = {
+	.line_buf_depth = 9,
+	.bits_per_component = 8,
+	.convert_rgb = true,
+	.slice_width = 540,
+	.slice_height = 101,
+	.slice_count = 2,
+	.simple_422 = false,
+	.pic_width = 1080,
+	.pic_height = 2424,
+	.rc_tgt_offset_high = 3,
+	.rc_tgt_offset_low = 3,
+	.bits_per_pixel = 128,
+	.rc_edge_factor = 6,
+	.rc_quant_incr_limit1 = 11,
+	.rc_quant_incr_limit0 = 11,
+	.initial_xmit_delay = 512,
+	.initial_dec_delay = 526,
+	.block_pred_enable = true,
+	.first_line_bpg_offset = 12,
+	.initial_offset = 6144,
+	.rc_buf_thresh = {
+		14, 28, 42, 56,
+		70, 84, 98, 105,
+		112, 119, 121, 123,
+		125, 126
+	},
+	.rc_range_params = {
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
+		{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
+		{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
+		{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
+		{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
+		{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
+		{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
+		{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
+	},
+	.rc_model_size = 8192,
+	.flatness_min_qp = 3,
+	.flatness_max_qp = 12,
+	.initial_scale_value = 32,
+	.scale_decrement_interval = 7,
+	.scale_increment_interval = 2517,
+	.nfl_bpg_offset = 246,
+	.slice_bpg_offset = 258,
+	.final_offset = 4336,
+	.vbr_enable = false,
+	.slice_chunk_size = 540,
+	.dsc_version_minor = 2,
+	.dsc_version_major = 1,
+	.native_422 = false,
+	.native_420 = false,
+	.second_line_bpg_offset = 0,
+	.nsl_bpg_offset = 0,
+	.second_line_offset_adj = 0,
+};
+
+#define TK4C_WRCTRLD_DIMMING_BIT 0x08
+#define TK4C_WRCTRLD_BCTRL_BIT 0x20
+
+#define MIPI_DSI_FREQ_DEFAULT 756
+#define MIPI_DSI_FREQ_ALTERNATIVE 776
+
+static const u8 test_key_enable[] = { 0xF0, 0x5A, 0x5A };
+static const u8 test_key_disable[] = { 0xF0, 0xA5, 0xA5 };
+static const u8 test_key_fc_enable[] = { 0xFC, 0x5A, 0x5A };
+static const u8 test_key_fc_disable[] = { 0xFC, 0xA5, 0xA5 };
+static const u8 ltps_update[] = { 0xF7, 0x2F };
+static const u8 pixel_off[] = { 0x22 };
+
+static const struct gs_dsi_cmd tk4c_off_cmds[] = {
+	GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_OFF),
+	GS_DSI_DELAY_CMD(120, MIPI_DCS_ENTER_SLEEP_MODE),
+};
+static DEFINE_GS_CMDSET(tk4c_off);
+
+static const struct gs_dsi_cmd tk4c_lp_cmds[] = {
+	/* AOD Power Setting */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMD(0xB0, 0x00, 0x04, 0xF6),
+	GS_DSI_CMD(0xF6, 0x25), /* Default */
+	GS_DSI_CMDLIST(test_key_disable),
+
+	/* AOD Mode On Setting */
+	GS_DSI_CMD(MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24),
+};
+static DEFINE_GS_CMDSET(tk4c_lp);
+
+static const struct gs_dsi_cmd tk4c_lp_night_cmd[] = {
+	GS_DSI_CMD(0x51, 0x00, 0xB8),
+};
+
+static const struct gs_dsi_cmd tk4c_lp_low_cmd[] = {
+	GS_DSI_CMD(0x51, 0x01, 0x7E),
+};
+static const struct gs_dsi_cmd tk4c_lp_high_cmd[] = {
+	GS_DSI_CMD(0x51, 0x03, 0x1A),
+};
+
+static const struct gs_binned_lp tk4c_binned_lp[] = {
+	/* night threshold 4 nits */
+	BINNED_LP_MODE_TIMING("night", 252, tk4c_lp_night_cmd, 12, 12 + 50),
+	/* low threshold 40 nits */
+	BINNED_LP_MODE_TIMING("low", 716, tk4c_lp_low_cmd, 12, 12 + 50),
+	BINNED_LP_MODE_TIMING("high", 4095, tk4c_lp_high_cmd, 12, 12 + 50),
+};
+
+static const struct gs_dsi_cmd tk4c_init_cmds[] = {
+	/* TE on */
+	GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
+
+	/* TE width setting (MTP'ed) */
+	/* TE2 width setting (MTP'ed) */
+
+	/* CASET: 1080 */
+	GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37),
+
+	/* PASET: 2424 */
+	GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x77),
+
+	/* FFC Off (756Mpbs) Setting */
+	GS_DSI_CMDLIST(test_key_enable),
+	GS_DSI_CMDLIST(test_key_fc_enable),
+	GS_DSI_CMD(0xB0, 0x00, 0x3A, 0xC5),
+	GS_DSI_CMD(0xC5, 0x6C, 0x5C),
+	GS_DSI_CMD(0xB0, 0x00, 0x36, 0xC5),
+	GS_DSI_CMD(0xC5, 0x10),
+	GS_DSI_CMDLIST(test_key_disable),
+	GS_DSI_CMDLIST(test_key_fc_disable),
+
+	/* VDDD LDO Setting, before PVT */
+	GS_DSI_REV_CMDLIST(PANEL_REV_LT(PANEL_REV_PVT), test_key_fc_enable),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xB0, 0x00, 0x58, 0xD7),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xD7, 0x0A),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xB0, 0x00, 0x5B, 0xD7),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xD7, 0x0A),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xFE, 0x80),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_PVT), 0xFE, 0x00),
+	GS_DSI_REV_CMDLIST(PANEL_REV_LT(PANEL_REV_PVT), test_key_fc_disable),
+
+	/* TSP HSYNC setting, MTP'ed from DVT */
+	GS_DSI_REV_CMDLIST(PANEL_REV_LT(PANEL_REV_DVT1), test_key_enable),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB0, 0x00, 0x42, 0xB9),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB9, 0x19),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB0, 0x00, 0x46, 0xB9),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB9, 0xB0),
+
+	/* FGZ common settings, MTP'ed from DVT */
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB0, 0x00, 0x30, 0x68),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x68, 0x32, 0xFF, 0x04,
+		0x08, 0x10, 0x15, 0x29, 0x67, 0xA5),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0xB0, 0x00, 0x1C, 0x62),
+	GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_DVT1), 0x62, 0x1D, 0x5F),
+	GS_DSI_REV_CMDLIST(PANEL_REV_LT(PANEL_REV_DVT1), test_key_disable),
+};
+static DEFINE_GS_CMDSET(tk4c_init);
+
+/**
+ * struct tk4c_panel - panel specific runtime info
+ *
+ * This struct maintains tk4c panel specific runtime info, any fixed details about panel
+ * should most likely go into struct gs_panel_desc
+ */
+struct tk4c_panel {
+	/** @base: base panel struct */
+	struct gs_panel base;
+	/**
+	 * @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
+	 *		  panel can recover to normal mode after entering pixel-off state.
+	 */
+	bool is_pixel_off;
+};
+#define to_spanel(ctx) container_of(ctx, struct tk4c_panel, base)
+
+static void tk4c_change_frequency(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	struct device *dev = ctx->dev;
+
+	u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
+
+	if (unlikely(!ctx))
+		return;
+
+	if (vrefresh != 60 && vrefresh != 120) {
+		dev_warn(dev, "%s: invalid refresh rate %uhz\n", __func__, vrefresh);
+		return;
+	}
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0x83, vrefresh == 60 ? 0x08 : 0x00);
+	GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	dev_info(dev, "%s: change to %uHz\n", __func__, vrefresh);
+	return;
+}
+
+static void tk4c_update_wrctrld(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+	u8 val = TK4C_WRCTRLD_BCTRL_BIT;
+
+	if (ctx->dimming_on)
+		val |= TK4C_WRCTRLD_DIMMING_BIT;
+
+	dev_dbg(dev, "%s(wrctrld:0x%x, hbm: %d, dimming: %d)\n", __func__, val,
+		GS_IS_HBM_ON(ctx->hbm_mode), ctx->dimming_on);
+
+	GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
+}
+
+static int tk4c_set_brightness(struct gs_panel *ctx, u16 br)
+{
+	u16 brightness;
+	u32 max_brightness;
+	struct tk4c_panel *spanel = to_spanel(ctx);
+	struct device *dev = ctx->dev;
+
+	if (ctx->current_mode && ctx->current_mode->gs_mode.is_lp_mode) {
+		/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
+		if (spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+			spanel->is_pixel_off = false;
+		}
+		if (gs_panel_has_func(ctx, set_binned_lp))
+			ctx->desc->gs_panel_func->set_binned_lp(ctx, br);
+		return 0;
+	}
+
+	/* Use pixel off command instead of setting DBV 0 */
+	if (!br) {
+		if (!spanel->is_pixel_off) {
+			GS_DCS_WRITE_CMDLIST(dev, pixel_off);
+			spanel->is_pixel_off = true;
+			dev_dbg(dev, "%s: pixel off instead of dbv 0\n", __func__);
+		}
+		return 0;
+	} else if (br && spanel->is_pixel_off) {
+		GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
+		spanel->is_pixel_off = false;
+	}
+
+	if (!ctx->desc->brightness_desc->brt_capability) {
+		dev_err(dev, "no available brightness capability\n");
+		return -EINVAL;
+	}
+
+	max_brightness = ctx->desc->brightness_desc->brt_capability->hbm.level.max;
+
+	if (br > max_brightness) {
+		br = max_brightness;
+		dev_warn(dev, "%s: capped to dbv(%d)\n", __func__, max_brightness);
+	}
+
+	/* swap endianness because panel expects MSB first */
+	brightness = swab16(br);
+
+	return gs_dcs_set_brightness(ctx, brightness);
+}
+
+static void tk4c_set_hbm_mode(struct gs_panel *ctx, enum gs_hbm_mode mode)
+{
+	struct device *dev = ctx->dev;
+
+	ctx->hbm_mode = mode;
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	/* FGZ mode setting */
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x61, 0x68);
+	if (GS_IS_HBM_ON(ctx->hbm_mode)) {
+		if (GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode)) {
+			/* FGZ Mode ON */
+			if (ctx->panel_rev < PANEL_REV_DVT1) {
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0xF5,
+					0xC4);
+			} else if (ctx->panel_rev == PANEL_REV_DVT1) {
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0xE4,
+					0xB6);
+			} else { /* PVT/MP */
+				GS_DCS_BUF_ADD_CMD(dev, 0x68, 0xB4, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0x00,
+					0xCD);
+			}
+		} else {
+			/* FGZ Mode OFF */
+			GS_DCS_BUF_ADD_CMD(dev, 0x68, 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0x00,
+					   0x00);
+		}
+	} else {
+		/* FGZ Mode OFF */
+		GS_DCS_BUF_ADD_CMD(dev, 0x68, 0xB0, 0x2C, 0x6A, 0x80, 0x00, 0x00, 0x00, 0x00);
+	}
+
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, ctx->hbm_mode ? 0x80 : 0x81);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x2E, 0xBD);
+	GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, ctx->hbm_mode ? 0x01 : 0x02);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, ltps_update);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	dev_info(dev, "hbm_on=%d hbm_ircoff=%d.\n", GS_IS_HBM_ON(ctx->hbm_mode),
+		 GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode));
+}
+
+static void tk4c_set_dimming(struct gs_panel *ctx, bool dimming_on)
+{
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	ctx->dimming_on = dimming_on;
+
+	if (pmode->gs_mode.is_lp_mode) {
+		dev_warn(dev, "in lp mode, skip to update dimming usage\n");
+		return;
+	}
+
+	tk4c_update_wrctrld(ctx);
+}
+
+static void tk4c_mode_set(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	tk4c_change_frequency(ctx, pmode);
+}
+
+static bool tk4c_is_mode_seamless(const struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	/* seamless mode switch is possible if only changing refresh rate */
+	return drm_mode_equal_no_clocks(&ctx->current_mode->mode, &pmode->mode);
+}
+
+static void tk4c_debugfs_init(struct drm_panel *panel, struct dentry *root)
+{
+#ifdef CONFIG_DEBUG_FS
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct dentry *panel_root, *csroot;
+
+	if (!ctx)
+		return;
+
+	panel_root = debugfs_lookup("panel", root);
+	if (!panel_root)
+		return;
+
+	csroot = debugfs_lookup("cmdsets", panel_root);
+	if (!csroot) {
+		goto panel_out;
+	}
+
+	gs_panel_debugfs_create_cmdset(csroot, &tk4c_init_cmdset, "init");
+
+	dput(csroot);
+panel_out:
+	dput(panel_root);
+#endif
+}
+
+static void tk4c_get_panel_rev(struct gs_panel *ctx, u32 id)
+{
+	/* extract command 0xDB */
+	u8 build_code = (id & 0xFF00) >> 8;
+	u8 main = (build_code & 0xE0) >> 3;
+	u8 sub = (build_code & 0x0C) >> 2;
+	u8 rev = main | sub;
+
+	gs_panel_get_panel_rev(ctx, rev);
+}
+
+static int tk4c_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
+{
+	struct drm_connector *conn = &ctx->gs_connector->base;
+	struct drm_connector_state *new_conn_state = drm_atomic_get_new_connector_state(state, conn);
+	struct drm_crtc_state *old_crtc_state, *new_crtc_state;
+	const struct gs_panel_mode *pmode;
+	bool was_lp_mode, is_lp_mode;
+
+	if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
+		!new_conn_state || !new_conn_state->crtc)
+		return 0;
+
+	new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
+	old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
+	if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
+		return 0;
+
+	was_lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
+	/* don't skip update when switching between AoD and normal mode */
+	pmode = gs_panel_get_mode(ctx, &new_crtc_state->mode);
+	if (pmode) {
+		is_lp_mode = pmode->gs_mode.is_lp_mode;
+		if ((was_lp_mode && !is_lp_mode) || (!was_lp_mode && is_lp_mode))
+			new_crtc_state->color_mgmt_changed = true;
+	} else {
+		dev_err(ctx->dev, "%s: no new mode\n", __func__);
+	}
+
+	if (!drm_atomic_crtc_effectively_active(old_crtc_state) ||
+		(was_lp_mode && drm_mode_vrefresh(&new_crtc_state->mode) == 60)) {
+		struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
+
+		/* set clock to max refresh rate on resume or AOD exit to 60Hz */
+		mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
+		if (mode->clock != new_crtc_state->mode.clock) {
+			new_crtc_state->mode_changed = true;
+			ctx->gs_connector->needs_commit = true;
+			dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n",
+				mode->name,
+				!drm_atomic_crtc_effectively_active(old_crtc_state) ?
+				"resume" : "lp exit");
+		}
+	} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
+		/* clock hacked in last commit due to resume or lp exit, undo that */
+		new_crtc_state->mode_changed = true;
+		new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
+		ctx->gs_connector->needs_commit = false;
+		dev_dbg(ctx->dev, "restore mode (%s) clock after resume or lp exit\n",
+			new_crtc_state->mode.name);
+	}
+
+	return 0;
+}
+
+static void tk4c_set_nolp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
+{
+	if (!gs_is_panel_active(ctx))
+		return;
+
+	/* AOD Mode Off Setting */
+	tk4c_update_wrctrld(ctx);
+	tk4c_change_frequency(ctx, pmode);
+
+	dev_info(ctx->dev, "exit LP mode\n");
+}
+
+static int tk4c_enable(struct drm_panel *panel)
+{
+	struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
+	struct device *dev = ctx->dev;
+	const struct gs_panel_mode *pmode = ctx->current_mode;
+
+	if (!pmode) {
+		dev_err(dev, "no current mode set\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	/* toggle reset gpio */
+	gs_panel_reset_helper(ctx);
+
+	/* sleep out */
+	GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
+
+	/* initial command */
+	gs_panel_send_cmdset(ctx, &tk4c_init_cmdset);
+
+	/* frequency */
+	tk4c_change_frequency(ctx, pmode);
+
+	/* DSC related configuration */
+	mipi_dsi_compression_mode(to_mipi_dsi_device(dev), true);
+	gs_dcs_write_dsc_config(dev, &pps_config);
+	/* DSC Enable */
+	GS_DCS_BUF_ADD_CMD(dev, 0x9D, 0x01);
+
+	/* dimming and HBM */
+	tk4c_update_wrctrld(ctx);
+
+	/* display on */
+	if (pmode->gs_mode.is_lp_mode)
+		gs_panel_set_lp_mode_helper(ctx, pmode);
+
+	GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
+
+	ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_DEFAULT;
+
+	return 0;
+}
+
+static int tk4c_panel_probe(struct mipi_dsi_device *dsi)
+{
+	struct tk4c_panel *spanel;
+
+	spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
+	if (!spanel)
+		return -ENOMEM;
+
+	spanel->is_pixel_off = false;
+
+	return gs_dsi_panel_common_init(dsi, &spanel->base);
+}
+
+static void tk4c_pre_update_ffc(struct gs_panel *ctx)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(ctx->dev, "%s\n", __func__);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	/* FFC off */
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_disable);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void tk4c_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
+{
+	struct device *dev = ctx->dev;
+
+	dev_dbg(ctx->dev, "%s: hs_clk_mbps: current=%u, target=%u\n",
+		__func__, ctx->dsi_hs_clk_mbps, hs_clk_mbps);
+
+	PANEL_ATRACE_BEGIN(__func__);
+
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_enable);
+
+	if (hs_clk_mbps != MIPI_DSI_FREQ_DEFAULT && hs_clk_mbps != MIPI_DSI_FREQ_ALTERNATIVE) {
+		dev_warn(ctx->dev, "%s: invalid hs_clk_mbps=%u for FFC\n", __func__, hs_clk_mbps);
+	} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
+		dev_info(ctx->dev, "%s: updating for hs_clk_mbps=%u\n", __func__, hs_clk_mbps);
+		ctx->dsi_hs_clk_mbps = hs_clk_mbps;
+
+		/* Update FFC */
+		GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x3A, 0xC5);
+		if (hs_clk_mbps == MIPI_DSI_FREQ_DEFAULT) {
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x6C, 0x5C);
+		} else { /* MIPI_DSI_FREQ_ALTERNATIVE */
+			GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x69, 0x91);
+		}
+	}
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
+	GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x11, 0x10, 0x50, 0x05);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_disable);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+
+	PANEL_ATRACE_END(__func__);
+}
+
+static void tk4c_set_ssc_en(struct gs_panel *ctx, bool enabled)
+{
+	struct device *dev = ctx->dev;
+	const bool ssc_mode_update = ctx->ssc_en != enabled;
+
+	if (!ssc_mode_update) {
+		dev_dbg(ctx->dev, "ssc_mode skip update\n");
+		return;
+	}
+
+	ctx->ssc_en = enabled;
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_enable);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_enable);
+	GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x6E, 0xC5); /* global para */
+	if (enabled)
+		GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x07, 0x7F, 0x00, 0x00);
+	else
+		GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x04, 0x00);
+	GS_DCS_BUF_ADD_CMDLIST(dev, test_key_fc_disable);
+	GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, test_key_disable);
+	dev_info(dev, "ssc_mode=%d\n", ctx->ssc_en);
+}
+
+static const struct gs_display_underrun_param underrun_param = {
+	.te_idle_us = 350,
+	.te_var = 1,
+};
+
+static const u16 WIDTH_MM = 65, HEIGHT_MM = 146;
+static const u16 HDISPLAY = 1080, VDISPLAY = 2424;
+static const u16 HFP = 32, HSA = 12, HBP = 16;
+static const u16 VFP = 8, VSA = 2, VBP = 16;
+
+#define TK4C_DSC {\
+	.enabled = true,\
+	.dsc_count = 2,\
+	.cfg = &pps_config,\
+}
+
+static const struct gs_panel_mode_array tk4c_modes = {
+	.num_modes = 2,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@60:60",
+				DRM_MODE_TIMING(60, HDISPLAY, HFP, HSA, HBP,
+						VDISPLAY, VFP, VSA, VBP),
+				/* aligned to bootloader setting */
+				.type = DRM_MODE_TYPE_PREFERRED,
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 8360,
+				.bpc = 8,
+				.dsc = TK4C_DSC,
+				.underrun_param = &underrun_param,
+			},
+		},
+		{
+			.mode = {
+				.name = "1080x2424@120:120",
+				DRM_MODE_TIMING(120, HDISPLAY, HFP, HSA, HBP,
+						VDISPLAY, VFP, VSA, VBP),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 276,
+				.bpc = 8,
+				.dsc = TK4C_DSC,
+				.underrun_param = &underrun_param,
+			},
+		},
+	},/* modes */
+};
+
+const struct brightness_capability tk4c_brightness_capability = {
+	.normal = {
+		.nits = {
+			.min = 2,
+			.max = 1200,
+		},
+		.level = {
+			.min = 184,
+			.max = 3427,
+		},
+		.percentage = {
+			.min = 0,
+			.max = 67,
+		},
+	},
+	.hbm = {
+		.nits = {
+			.min = 1200,
+			.max = 1800,
+		},
+		.level = {
+			.min = 3428,
+			.max = 4095,
+		},
+		.percentage = {
+			.min = 67,
+			.max = 100,
+		},
+	},
+};
+
+static const struct gs_panel_mode_array tk4c_lp_modes = {
+	.num_modes = 1,
+	.modes = {
+		{
+			.mode = {
+				.name = "1080x2424@30:30",
+				DRM_MODE_TIMING(30, HDISPLAY, HFP, HSA, HBP,
+						VDISPLAY, VFP, VSA, VBP),
+				.width_mm = WIDTH_MM,
+				.height_mm = HEIGHT_MM,
+			},
+			.gs_mode = {
+				.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
+				.vblank_usec = 120,
+				.te_usec = 1109,
+				.bpc = 8,
+				.dsc = TK4C_DSC,
+				.underrun_param = &underrun_param,
+				.is_lp_mode = true,
+			},
+		},
+	}, /* modes */
+};
+
+static const struct drm_panel_funcs tk4c_drm_funcs = {
+	.disable = gs_panel_disable,
+	.unprepare = gs_panel_unprepare,
+	.prepare = gs_panel_prepare,
+	.enable = tk4c_enable,
+	.get_modes = gs_panel_get_modes,
+	.debugfs_init = tk4c_debugfs_init,
+};
+
+static const struct gs_panel_funcs tk4c_gs_funcs = {
+	.set_brightness = tk4c_set_brightness,
+	.set_lp_mode = gs_panel_set_lp_mode_helper,
+	.set_nolp_mode = tk4c_set_nolp_mode,
+	.set_binned_lp = gs_panel_set_binned_lp_helper,
+	.set_dimming = tk4c_set_dimming,
+	.set_hbm_mode = tk4c_set_hbm_mode,
+	.is_mode_seamless = tk4c_is_mode_seamless,
+	.mode_set = tk4c_mode_set,
+	.get_panel_rev = tk4c_get_panel_rev,
+	.read_id = gs_panel_read_slsi_ddic_id,
+	.atomic_check = tk4c_atomic_check,
+	.pre_update_ffc = tk4c_pre_update_ffc,
+	.update_ffc = tk4c_update_ffc,
+	.set_ssc_en = tk4c_set_ssc_en,
+};
+
+const struct gs_panel_brightness_desc tk4c_brightness_desc = {
+	.max_brightness = 4095,
+	.min_brightness = 2,
+	.max_luminance = 10000000,
+	.max_avg_luminance = 10000000,
+	.min_luminance = 5,
+	.default_brightness = 1290, /* 140 nits */
+	.brt_capability = &tk4c_brightness_capability,
+};
+
+const struct gs_panel_reg_ctrl_desc tk4c_reg_ctrl_desc = {
+	.reg_ctrl_enable = {
+		{PANEL_REG_ID_VDDI, 0},
+		{PANEL_REG_ID_VCI, 10},
+	},
+	.reg_ctrl_post_enable = {
+		{PANEL_REG_ID_VDDD, 5},
+	},
+	.reg_ctrl_pre_disable = {
+		{PANEL_REG_ID_VDDD, 0},
+	},
+	.reg_ctrl_disable = {
+		{PANEL_REG_ID_VCI, 0},
+		{PANEL_REG_ID_VDDI, 0},
+	},
+};
+
+const struct gs_panel_desc google_tk4c = {
+	.data_lane_cnt = 4,
+	/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
+	.hdr_formats = BIT(2) | BIT(3),
+	.brightness_desc = &tk4c_brightness_desc,
+	.modes = &tk4c_modes,
+	.off_cmdset = &tk4c_off_cmdset,
+	.lp_modes = &tk4c_lp_modes,
+	.lp_cmdset = &tk4c_lp_cmdset,
+	.binned_lp = tk4c_binned_lp,
+	.num_binned_lp = ARRAY_SIZE(tk4c_binned_lp),
+	.reg_ctrl_desc = &tk4c_reg_ctrl_desc,
+	.panel_func = &tk4c_drm_funcs,
+	.gs_panel_func = &tk4c_gs_funcs,
+	.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_DEFAULT,
+	.reset_timing_ms = { -1, 1, 1 },
+};
+
+static const struct of_device_id gs_panel_of_match[] = {
+	{ .compatible = "google,gs-tk4c", .data = &google_tk4c },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gs_panel_of_match);
+
+static struct mipi_dsi_driver gs_panel_driver = {
+	.probe = tk4c_panel_probe,
+	.remove = gs_dsi_panel_common_remove,
+	.driver = {
+		.name = "panel-gs-tk4c",
+		.of_match_table = gs_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(gs_panel_driver);
+
+MODULE_AUTHOR("Taylor Nelms <tknelms@google.com>");
+MODULE_DESCRIPTION("MIPI-DSI based Google tk4c panel driver");
+MODULE_LICENSE("Dual MIT/GPL");