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(¤t_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");