├── .clang-format ├── .gitignore ├── .gitlab-ci.yml ├── LICENSE ├── Makefile ├── README.md ├── deb └── module-name │ ├── DEBIAN │ ├── control │ ├── postinst │ └── prerm │ └── usr │ └── share │ └── doc │ └── module-name │ ├── changelog.gz │ └── copyright ├── dkms.conf ├── src ├── clevo_acpi.c ├── clevo_interfaces.h ├── clevo_keyboard.h ├── clevo_leds.h ├── clevo_wmi.c ├── tuxedo_io │ ├── tuxedo_io.c │ └── tuxedo_io_ioctl.h ├── tuxedo_keyboard.c ├── tuxedo_keyboard_common.h ├── uniwill_interfaces.h ├── uniwill_keyboard.h ├── uniwill_leds.h └── uniwill_wmi.c ├── src_pkg ├── dkms_postinst └── rpm_pkg.spec ├── trans_pkg_from_dkms └── deb │ └── tuxedo-keyboard-dkms │ └── DEBIAN │ └── control └── tuxedo_keyboard.conf /.clang-format: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # clang-format configuration file. Intended for clang-format >= 4. 4 | # 5 | # For more information, see: 6 | # 7 | # Documentation/process/clang-format.rst 8 | # https://clang.llvm.org/docs/ClangFormat.html 9 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 10 | # 11 | --- 12 | AccessModifierOffset: -4 13 | AlignAfterOpenBracket: Align 14 | AlignConsecutiveAssignments: false 15 | AlignConsecutiveDeclarations: false 16 | #AlignEscapedNewlines: Left # Unknown to clang-format-4.0 17 | AlignOperands: true 18 | AlignTrailingComments: false 19 | AllowAllParametersOfDeclarationOnNextLine: false 20 | AllowShortBlocksOnASingleLine: false 21 | AllowShortCaseLabelsOnASingleLine: false 22 | AllowShortFunctionsOnASingleLine: None 23 | AllowShortIfStatementsOnASingleLine: false 24 | AllowShortLoopsOnASingleLine: false 25 | AlwaysBreakAfterDefinitionReturnType: None 26 | AlwaysBreakAfterReturnType: None 27 | AlwaysBreakBeforeMultilineStrings: false 28 | AlwaysBreakTemplateDeclarations: false 29 | BinPackArguments: true 30 | BinPackParameters: true 31 | BraceWrapping: 32 | AfterClass: false 33 | AfterControlStatement: false 34 | AfterEnum: false 35 | AfterFunction: true 36 | AfterNamespace: true 37 | AfterObjCDeclaration: false 38 | AfterStruct: false 39 | AfterUnion: false 40 | #AfterExternBlock: false # Unknown to clang-format-5.0 41 | BeforeCatch: false 42 | BeforeElse: false 43 | IndentBraces: false 44 | #SplitEmptyFunction: true # Unknown to clang-format-4.0 45 | #SplitEmptyRecord: true # Unknown to clang-format-4.0 46 | #SplitEmptyNamespace: true # Unknown to clang-format-4.0 47 | BreakBeforeBinaryOperators: None 48 | BreakBeforeBraces: Custom 49 | #BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 50 | BreakBeforeTernaryOperators: false 51 | BreakConstructorInitializersBeforeComma: false 52 | #BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 53 | BreakAfterJavaFieldAnnotations: false 54 | BreakStringLiterals: false 55 | ColumnLimit: 80 56 | CommentPragmas: '^ IWYU pragma:' 57 | #CompactNamespaces: false # Unknown to clang-format-4.0 58 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 59 | ConstructorInitializerIndentWidth: 8 60 | ContinuationIndentWidth: 8 61 | Cpp11BracedListStyle: false 62 | DerivePointerAlignment: false 63 | DisableFormat: false 64 | ExperimentalAutoDetectBinPacking: false 65 | #FixNamespaceComments: false # Unknown to clang-format-4.0 66 | 67 | # Taken from: 68 | # git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ 69 | # | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ 70 | # | sort | uniq 71 | ForEachMacros: 72 | - 'apei_estatus_for_each_section' 73 | - 'ata_for_each_dev' 74 | - 'ata_for_each_link' 75 | - '__ata_qc_for_each' 76 | - 'ata_qc_for_each' 77 | - 'ata_qc_for_each_raw' 78 | - 'ata_qc_for_each_with_internal' 79 | - 'ax25_for_each' 80 | - 'ax25_uid_for_each' 81 | - '__bio_for_each_bvec' 82 | - 'bio_for_each_bvec' 83 | - 'bio_for_each_integrity_vec' 84 | - '__bio_for_each_segment' 85 | - 'bio_for_each_segment' 86 | - 'bio_for_each_segment_all' 87 | - 'bio_list_for_each' 88 | - 'bip_for_each_vec' 89 | - 'bitmap_for_each_clear_region' 90 | - 'bitmap_for_each_set_region' 91 | - 'blkg_for_each_descendant_post' 92 | - 'blkg_for_each_descendant_pre' 93 | - 'blk_queue_for_each_rl' 94 | - 'bond_for_each_slave' 95 | - 'bond_for_each_slave_rcu' 96 | - 'bpf_for_each_spilled_reg' 97 | - 'btree_for_each_safe128' 98 | - 'btree_for_each_safe32' 99 | - 'btree_for_each_safe64' 100 | - 'btree_for_each_safel' 101 | - 'card_for_each_dev' 102 | - 'cgroup_taskset_for_each' 103 | - 'cgroup_taskset_for_each_leader' 104 | - 'cpufreq_for_each_entry' 105 | - 'cpufreq_for_each_entry_idx' 106 | - 'cpufreq_for_each_valid_entry' 107 | - 'cpufreq_for_each_valid_entry_idx' 108 | - 'css_for_each_child' 109 | - 'css_for_each_descendant_post' 110 | - 'css_for_each_descendant_pre' 111 | - 'device_for_each_child_node' 112 | - 'dma_fence_chain_for_each' 113 | - 'drm_atomic_crtc_for_each_plane' 114 | - 'drm_atomic_crtc_state_for_each_plane' 115 | - 'drm_atomic_crtc_state_for_each_plane_state' 116 | - 'drm_atomic_for_each_plane_damage' 117 | - 'drm_client_for_each_connector_iter' 118 | - 'drm_client_for_each_modeset' 119 | - 'drm_connector_for_each_possible_encoder' 120 | - 'drm_for_each_bridge_in_chain' 121 | - 'drm_for_each_connector_iter' 122 | - 'drm_for_each_crtc' 123 | - 'drm_for_each_encoder' 124 | - 'drm_for_each_encoder_mask' 125 | - 'drm_for_each_fb' 126 | - 'drm_for_each_legacy_plane' 127 | - 'drm_for_each_plane' 128 | - 'drm_for_each_plane_mask' 129 | - 'drm_for_each_privobj' 130 | - 'drm_mm_for_each_hole' 131 | - 'drm_mm_for_each_node' 132 | - 'drm_mm_for_each_node_in_range' 133 | - 'drm_mm_for_each_node_safe' 134 | - 'flow_action_for_each' 135 | - 'for_each_active_dev_scope' 136 | - 'for_each_active_drhd_unit' 137 | - 'for_each_active_iommu' 138 | - 'for_each_available_child_of_node' 139 | - 'for_each_bio' 140 | - 'for_each_board_func_rsrc' 141 | - 'for_each_bvec' 142 | - 'for_each_card_auxs' 143 | - 'for_each_card_auxs_safe' 144 | - 'for_each_card_components' 145 | - 'for_each_card_dapms' 146 | - 'for_each_card_pre_auxs' 147 | - 'for_each_card_prelinks' 148 | - 'for_each_card_rtds' 149 | - 'for_each_card_rtds_safe' 150 | - 'for_each_card_widgets' 151 | - 'for_each_card_widgets_safe' 152 | - 'for_each_cgroup_storage_type' 153 | - 'for_each_child_of_node' 154 | - 'for_each_clear_bit' 155 | - 'for_each_clear_bit_from' 156 | - 'for_each_cmsghdr' 157 | - 'for_each_compatible_node' 158 | - 'for_each_component_dais' 159 | - 'for_each_component_dais_safe' 160 | - 'for_each_comp_order' 161 | - 'for_each_console' 162 | - 'for_each_cpu' 163 | - 'for_each_cpu_and' 164 | - 'for_each_cpu_not' 165 | - 'for_each_cpu_wrap' 166 | - 'for_each_dapm_widgets' 167 | - 'for_each_dev_addr' 168 | - 'for_each_dev_scope' 169 | - 'for_each_displayid_db' 170 | - 'for_each_dma_cap_mask' 171 | - 'for_each_dpcm_be' 172 | - 'for_each_dpcm_be_rollback' 173 | - 'for_each_dpcm_be_safe' 174 | - 'for_each_dpcm_fe' 175 | - 'for_each_drhd_unit' 176 | - 'for_each_dss_dev' 177 | - 'for_each_efi_memory_desc' 178 | - 'for_each_efi_memory_desc_in_map' 179 | - 'for_each_element' 180 | - 'for_each_element_extid' 181 | - 'for_each_element_id' 182 | - 'for_each_endpoint_of_node' 183 | - 'for_each_evictable_lru' 184 | - 'for_each_fib6_node_rt_rcu' 185 | - 'for_each_fib6_walker_rt' 186 | - 'for_each_free_mem_pfn_range_in_zone' 187 | - 'for_each_free_mem_pfn_range_in_zone_from' 188 | - 'for_each_free_mem_range' 189 | - 'for_each_free_mem_range_reverse' 190 | - 'for_each_func_rsrc' 191 | - 'for_each_hstate' 192 | - 'for_each_if' 193 | - 'for_each_iommu' 194 | - 'for_each_ip_tunnel_rcu' 195 | - 'for_each_irq_nr' 196 | - 'for_each_link_codecs' 197 | - 'for_each_link_cpus' 198 | - 'for_each_link_platforms' 199 | - 'for_each_lru' 200 | - 'for_each_matching_node' 201 | - 'for_each_matching_node_and_match' 202 | - 'for_each_member' 203 | - 'for_each_memblock' 204 | - 'for_each_memblock_type' 205 | - 'for_each_memcg_cache_index' 206 | - 'for_each_mem_pfn_range' 207 | - 'for_each_mem_range' 208 | - 'for_each_mem_range_rev' 209 | - 'for_each_migratetype_order' 210 | - 'for_each_msi_entry' 211 | - 'for_each_msi_entry_safe' 212 | - 'for_each_net' 213 | - 'for_each_net_continue_reverse' 214 | - 'for_each_netdev' 215 | - 'for_each_netdev_continue' 216 | - 'for_each_netdev_continue_rcu' 217 | - 'for_each_netdev_continue_reverse' 218 | - 'for_each_netdev_feature' 219 | - 'for_each_netdev_in_bond_rcu' 220 | - 'for_each_netdev_rcu' 221 | - 'for_each_netdev_reverse' 222 | - 'for_each_netdev_safe' 223 | - 'for_each_net_rcu' 224 | - 'for_each_new_connector_in_state' 225 | - 'for_each_new_crtc_in_state' 226 | - 'for_each_new_mst_mgr_in_state' 227 | - 'for_each_new_plane_in_state' 228 | - 'for_each_new_private_obj_in_state' 229 | - 'for_each_node' 230 | - 'for_each_node_by_name' 231 | - 'for_each_node_by_type' 232 | - 'for_each_node_mask' 233 | - 'for_each_node_state' 234 | - 'for_each_node_with_cpus' 235 | - 'for_each_node_with_property' 236 | - 'for_each_of_allnodes' 237 | - 'for_each_of_allnodes_from' 238 | - 'for_each_of_cpu_node' 239 | - 'for_each_of_pci_range' 240 | - 'for_each_old_connector_in_state' 241 | - 'for_each_old_crtc_in_state' 242 | - 'for_each_old_mst_mgr_in_state' 243 | - 'for_each_oldnew_connector_in_state' 244 | - 'for_each_oldnew_crtc_in_state' 245 | - 'for_each_oldnew_mst_mgr_in_state' 246 | - 'for_each_oldnew_plane_in_state' 247 | - 'for_each_oldnew_plane_in_state_reverse' 248 | - 'for_each_oldnew_private_obj_in_state' 249 | - 'for_each_old_plane_in_state' 250 | - 'for_each_old_private_obj_in_state' 251 | - 'for_each_online_cpu' 252 | - 'for_each_online_node' 253 | - 'for_each_online_pgdat' 254 | - 'for_each_pci_bridge' 255 | - 'for_each_pci_dev' 256 | - 'for_each_pci_msi_entry' 257 | - 'for_each_pcm_streams' 258 | - 'for_each_populated_zone' 259 | - 'for_each_possible_cpu' 260 | - 'for_each_present_cpu' 261 | - 'for_each_prime_number' 262 | - 'for_each_prime_number_from' 263 | - 'for_each_process' 264 | - 'for_each_process_thread' 265 | - 'for_each_property_of_node' 266 | - 'for_each_registered_fb' 267 | - 'for_each_reserved_mem_region' 268 | - 'for_each_rtd_codec_dais' 269 | - 'for_each_rtd_codec_dais_rollback' 270 | - 'for_each_rtd_components' 271 | - 'for_each_rtd_cpu_dais' 272 | - 'for_each_rtd_cpu_dais_rollback' 273 | - 'for_each_rtd_dais' 274 | - 'for_each_set_bit' 275 | - 'for_each_set_bit_from' 276 | - 'for_each_set_clump8' 277 | - 'for_each_sg' 278 | - 'for_each_sg_dma_page' 279 | - 'for_each_sg_page' 280 | - 'for_each_sibling_event' 281 | - 'for_each_subelement' 282 | - 'for_each_subelement_extid' 283 | - 'for_each_subelement_id' 284 | - '__for_each_thread' 285 | - 'for_each_thread' 286 | - 'for_each_wakeup_source' 287 | - 'for_each_zone' 288 | - 'for_each_zone_zonelist' 289 | - 'for_each_zone_zonelist_nodemask' 290 | - 'fwnode_for_each_available_child_node' 291 | - 'fwnode_for_each_child_node' 292 | - 'fwnode_graph_for_each_endpoint' 293 | - 'gadget_for_each_ep' 294 | - 'genradix_for_each' 295 | - 'genradix_for_each_from' 296 | - 'hash_for_each' 297 | - 'hash_for_each_possible' 298 | - 'hash_for_each_possible_rcu' 299 | - 'hash_for_each_possible_rcu_notrace' 300 | - 'hash_for_each_possible_safe' 301 | - 'hash_for_each_rcu' 302 | - 'hash_for_each_safe' 303 | - 'hctx_for_each_ctx' 304 | - 'hlist_bl_for_each_entry' 305 | - 'hlist_bl_for_each_entry_rcu' 306 | - 'hlist_bl_for_each_entry_safe' 307 | - 'hlist_for_each' 308 | - 'hlist_for_each_entry' 309 | - 'hlist_for_each_entry_continue' 310 | - 'hlist_for_each_entry_continue_rcu' 311 | - 'hlist_for_each_entry_continue_rcu_bh' 312 | - 'hlist_for_each_entry_from' 313 | - 'hlist_for_each_entry_from_rcu' 314 | - 'hlist_for_each_entry_rcu' 315 | - 'hlist_for_each_entry_rcu_bh' 316 | - 'hlist_for_each_entry_rcu_notrace' 317 | - 'hlist_for_each_entry_safe' 318 | - '__hlist_for_each_rcu' 319 | - 'hlist_for_each_safe' 320 | - 'hlist_nulls_for_each_entry' 321 | - 'hlist_nulls_for_each_entry_from' 322 | - 'hlist_nulls_for_each_entry_rcu' 323 | - 'hlist_nulls_for_each_entry_safe' 324 | - 'i3c_bus_for_each_i2cdev' 325 | - 'i3c_bus_for_each_i3cdev' 326 | - 'ide_host_for_each_port' 327 | - 'ide_port_for_each_dev' 328 | - 'ide_port_for_each_present_dev' 329 | - 'idr_for_each_entry' 330 | - 'idr_for_each_entry_continue' 331 | - 'idr_for_each_entry_continue_ul' 332 | - 'idr_for_each_entry_ul' 333 | - 'in_dev_for_each_ifa_rcu' 334 | - 'in_dev_for_each_ifa_rtnl' 335 | - 'inet_bind_bucket_for_each' 336 | - 'inet_lhash2_for_each_icsk_rcu' 337 | - 'key_for_each' 338 | - 'key_for_each_safe' 339 | - 'klp_for_each_func' 340 | - 'klp_for_each_func_safe' 341 | - 'klp_for_each_func_static' 342 | - 'klp_for_each_object' 343 | - 'klp_for_each_object_safe' 344 | - 'klp_for_each_object_static' 345 | - 'kunit_suite_for_each_test_case' 346 | - 'kvm_for_each_memslot' 347 | - 'kvm_for_each_vcpu' 348 | - 'list_for_each' 349 | - 'list_for_each_codec' 350 | - 'list_for_each_codec_safe' 351 | - 'list_for_each_continue' 352 | - 'list_for_each_entry' 353 | - 'list_for_each_entry_continue' 354 | - 'list_for_each_entry_continue_rcu' 355 | - 'list_for_each_entry_continue_reverse' 356 | - 'list_for_each_entry_from' 357 | - 'list_for_each_entry_from_rcu' 358 | - 'list_for_each_entry_from_reverse' 359 | - 'list_for_each_entry_lockless' 360 | - 'list_for_each_entry_rcu' 361 | - 'list_for_each_entry_reverse' 362 | - 'list_for_each_entry_safe' 363 | - 'list_for_each_entry_safe_continue' 364 | - 'list_for_each_entry_safe_from' 365 | - 'list_for_each_entry_safe_reverse' 366 | - 'list_for_each_prev' 367 | - 'list_for_each_prev_safe' 368 | - 'list_for_each_safe' 369 | - 'llist_for_each' 370 | - 'llist_for_each_entry' 371 | - 'llist_for_each_entry_safe' 372 | - 'llist_for_each_safe' 373 | - 'mci_for_each_dimm' 374 | - 'media_device_for_each_entity' 375 | - 'media_device_for_each_intf' 376 | - 'media_device_for_each_link' 377 | - 'media_device_for_each_pad' 378 | - 'nanddev_io_for_each_page' 379 | - 'netdev_for_each_lower_dev' 380 | - 'netdev_for_each_lower_private' 381 | - 'netdev_for_each_lower_private_rcu' 382 | - 'netdev_for_each_mc_addr' 383 | - 'netdev_for_each_uc_addr' 384 | - 'netdev_for_each_upper_dev_rcu' 385 | - 'netdev_hw_addr_list_for_each' 386 | - 'nft_rule_for_each_expr' 387 | - 'nla_for_each_attr' 388 | - 'nla_for_each_nested' 389 | - 'nlmsg_for_each_attr' 390 | - 'nlmsg_for_each_msg' 391 | - 'nr_neigh_for_each' 392 | - 'nr_neigh_for_each_safe' 393 | - 'nr_node_for_each' 394 | - 'nr_node_for_each_safe' 395 | - 'of_for_each_phandle' 396 | - 'of_property_for_each_string' 397 | - 'of_property_for_each_u32' 398 | - 'pci_bus_for_each_resource' 399 | - 'pcm_for_each_format' 400 | - 'ping_portaddr_for_each_entry' 401 | - 'plist_for_each' 402 | - 'plist_for_each_continue' 403 | - 'plist_for_each_entry' 404 | - 'plist_for_each_entry_continue' 405 | - 'plist_for_each_entry_safe' 406 | - 'plist_for_each_safe' 407 | - 'pnp_for_each_card' 408 | - 'pnp_for_each_dev' 409 | - 'protocol_for_each_card' 410 | - 'protocol_for_each_dev' 411 | - 'queue_for_each_hw_ctx' 412 | - 'radix_tree_for_each_slot' 413 | - 'radix_tree_for_each_tagged' 414 | - 'rbtree_postorder_for_each_entry_safe' 415 | - 'rdma_for_each_block' 416 | - 'rdma_for_each_port' 417 | - 'resource_list_for_each_entry' 418 | - 'resource_list_for_each_entry_safe' 419 | - 'rhl_for_each_entry_rcu' 420 | - 'rhl_for_each_rcu' 421 | - 'rht_for_each' 422 | - 'rht_for_each_entry' 423 | - 'rht_for_each_entry_from' 424 | - 'rht_for_each_entry_rcu' 425 | - 'rht_for_each_entry_rcu_from' 426 | - 'rht_for_each_entry_safe' 427 | - 'rht_for_each_from' 428 | - 'rht_for_each_rcu' 429 | - 'rht_for_each_rcu_from' 430 | - '__rq_for_each_bio' 431 | - 'rq_for_each_bvec' 432 | - 'rq_for_each_segment' 433 | - 'scsi_for_each_prot_sg' 434 | - 'scsi_for_each_sg' 435 | - 'sctp_for_each_hentry' 436 | - 'sctp_skb_for_each' 437 | - 'shdma_for_each_chan' 438 | - '__shost_for_each_device' 439 | - 'shost_for_each_device' 440 | - 'sk_for_each' 441 | - 'sk_for_each_bound' 442 | - 'sk_for_each_entry_offset_rcu' 443 | - 'sk_for_each_from' 444 | - 'sk_for_each_rcu' 445 | - 'sk_for_each_safe' 446 | - 'sk_nulls_for_each' 447 | - 'sk_nulls_for_each_from' 448 | - 'sk_nulls_for_each_rcu' 449 | - 'snd_array_for_each' 450 | - 'snd_pcm_group_for_each_entry' 451 | - 'snd_soc_dapm_widget_for_each_path' 452 | - 'snd_soc_dapm_widget_for_each_path_safe' 453 | - 'snd_soc_dapm_widget_for_each_sink_path' 454 | - 'snd_soc_dapm_widget_for_each_source_path' 455 | - 'tb_property_for_each' 456 | - 'tcf_exts_for_each_action' 457 | - 'udp_portaddr_for_each_entry' 458 | - 'udp_portaddr_for_each_entry_rcu' 459 | - 'usb_hub_for_each_child' 460 | - 'v4l2_device_for_each_subdev' 461 | - 'v4l2_m2m_for_each_dst_buf' 462 | - 'v4l2_m2m_for_each_dst_buf_safe' 463 | - 'v4l2_m2m_for_each_src_buf' 464 | - 'v4l2_m2m_for_each_src_buf_safe' 465 | - 'virtio_device_for_each_vq' 466 | - 'xa_for_each' 467 | - 'xa_for_each_marked' 468 | - 'xa_for_each_range' 469 | - 'xa_for_each_start' 470 | - 'xas_for_each' 471 | - 'xas_for_each_conflict' 472 | - 'xas_for_each_marked' 473 | - 'xbc_array_for_each_value' 474 | - 'xbc_for_each_key_value' 475 | - 'xbc_node_for_each_array_value' 476 | - 'xbc_node_for_each_child' 477 | - 'xbc_node_for_each_key_value' 478 | - 'zorro_for_each_dev' 479 | 480 | #IncludeBlocks: Preserve # Unknown to clang-format-5.0 481 | IncludeCategories: 482 | - Regex: '.*' 483 | Priority: 1 484 | IncludeIsMainRegex: '(Test)?$' 485 | IndentCaseLabels: false 486 | #IndentPPDirectives: None # Unknown to clang-format-5.0 487 | IndentWidth: 8 488 | IndentWrappedFunctionNames: false 489 | JavaScriptQuotes: Leave 490 | JavaScriptWrapImports: true 491 | KeepEmptyLinesAtTheStartOfBlocks: false 492 | MacroBlockBegin: '' 493 | MacroBlockEnd: '' 494 | MaxEmptyLinesToKeep: 1 495 | NamespaceIndentation: None 496 | #ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 497 | ObjCBlockIndentWidth: 8 498 | ObjCSpaceAfterProperty: true 499 | ObjCSpaceBeforeProtocolList: true 500 | 501 | # Taken from git's rules 502 | #PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 503 | PenaltyBreakBeforeFirstCallParameter: 30 504 | PenaltyBreakComment: 10 505 | PenaltyBreakFirstLessLess: 0 506 | PenaltyBreakString: 10 507 | PenaltyExcessCharacter: 100 508 | PenaltyReturnTypeOnItsOwnLine: 60 509 | 510 | PointerAlignment: Right 511 | ReflowComments: false 512 | SortIncludes: false 513 | #SortUsingDeclarations: false # Unknown to clang-format-4.0 514 | SpaceAfterCStyleCast: false 515 | SpaceAfterTemplateKeyword: true 516 | SpaceBeforeAssignmentOperators: true 517 | #SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 518 | #SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 519 | SpaceBeforeParens: ControlStatements 520 | #SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 521 | SpaceInEmptyParentheses: false 522 | SpacesBeforeTrailingComments: 1 523 | SpacesInAngles: false 524 | SpacesInContainerLiterals: false 525 | SpacesInCStyleCastParentheses: false 526 | SpacesInParentheses: false 527 | SpacesInSquareBrackets: false 528 | Standard: Cpp03 529 | TabWidth: 8 530 | UseTab: Always 531 | ... 532 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Kernel Module Compile Results 11 | *.mod* 12 | *.cmd 13 | .tmp_versions/ 14 | modules.order 15 | Module.symvers 16 | Mkfile.old 17 | 18 | *.mk 19 | 20 | .vscode/ 21 | *.kdev4 22 | 23 | # Packaging 24 | rpm 25 | deb/tuxedo-keyboard-* 26 | *.deb 27 | *.rpm 28 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: debian:latest 2 | 3 | variables: 4 | DEBIAN_FRONTEND: noninteractive 5 | PACKAGES_FOLDER_NAME: $CI_PROJECT_DIR/packages 6 | 7 | before_script: 8 | - apt-get update -qq 9 | - apt-get install -qq -y dpkg dpkg-dev make rpm tar > /dev/null 2>&1 10 | 11 | stages: 12 | - build_packages 13 | 14 | packaging: 15 | stage: build_packages 16 | only: 17 | refs: 18 | - release 19 | script: 20 | - make package 21 | - mkdir $PACKAGES_FOLDER_NAME 22 | - mv *.deb $PACKAGES_FOLDER_NAME/ 23 | - mv *.rpm $PACKAGES_FOLDER_NAME/ 24 | artifacts: 25 | name: tuxedo-keyboard-${CI_COMMIT_REF_SLUG} 26 | when: on_success 27 | paths: 28 | - $PACKAGES_FOLDER_NAME/* 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | # 4 | # This file is part of tuxedo-keyboard. 5 | # 6 | # tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This software is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this software. If not, see . 18 | # 19 | obj-m := ./src/tuxedo_keyboard.o \ 20 | ./src/clevo_wmi.o \ 21 | ./src/clevo_acpi.o \ 22 | ./src/tuxedo_io/tuxedo_io.o \ 23 | ./src/uniwill_wmi.o 24 | 25 | PWD := $(shell pwd) 26 | KDIR := /lib/modules/$(shell uname -r)/build 27 | 28 | all: 29 | make -C $(KDIR) M=$(PWD) modules 30 | 31 | clean: 32 | make -C $(KDIR) M=$(PWD) clean 33 | 34 | install: 35 | make -C $(KDIR) M=$(PWD) modules_install 36 | 37 | # Package version and name from dkms.conf 38 | VER := $(shell sed -n 's/^PACKAGE_VERSION=\([^\n]*\)/\1/p' dkms.conf 2>&1 /dev/null) 39 | MODULE_NAME := $(shell sed -n 's/^PACKAGE_NAME=\([^\n]*\)/\1/p' dkms.conf 2>&1 /dev/null) 40 | 41 | dkmsinstall: 42 | cp -R . /usr/src/$(MODULE_NAME)-$(VER) 43 | dkms install -m $(MODULE_NAME) -v $(VER) 44 | 45 | dkmsremove: 46 | dkms remove -m $(MODULE_NAME) -v $(VER) --all 47 | rm -rf /usr/src/$(MODULE_NAME)-$(VER) 48 | 49 | # -------------- 50 | # Packaging only 51 | # --------------- 52 | 53 | DEB_PACKAGE_NAME := $(MODULE_NAME)-$(VER) 54 | 55 | # Deb package folder variables 56 | DEB_PACKAGE_BASE := deb/$(DEB_PACKAGE_NAME) 57 | DEB_PACKAGE_SRC := $(DEB_PACKAGE_BASE)/usr/src/$(DEB_PACKAGE_NAME) 58 | DEB_PACKAGE_CTRL := $(DEB_PACKAGE_BASE)/DEBIAN 59 | 60 | package: package-deb package-rpm 61 | package-clean: package-deb-clean package-rpm-clean 62 | 63 | package-deb: 64 | # Create/complete folder structure according to current version 65 | rm -rf $(DEB_PACKAGE_BASE) || true 66 | cp -rf deb/module-name $(DEB_PACKAGE_BASE) 67 | mv $(DEB_PACKAGE_BASE)/usr/share/doc/module-name $(DEB_PACKAGE_BASE)/usr/share/doc/$(MODULE_NAME) 68 | mkdir -p $(DEB_PACKAGE_BASE)/usr/src || true 69 | mkdir -p $(DEB_PACKAGE_SRC) || true 70 | mkdir -p $(DEB_PACKAGE_BASE)/usr/share/$(MODULE_NAME) || true 71 | 72 | # Replace name/version numbers in control/script files 73 | sed -i 's/^Version:[^\n]*/Version: $(VER)/g' $(DEB_PACKAGE_CTRL)/control 74 | sed -i 's/^Package:[^\n]*/Package: $(MODULE_NAME)/g' $(DEB_PACKAGE_CTRL)/control 75 | sed -i 's/^version=[^\n]*/version=$(VER)/g' $(DEB_PACKAGE_CTRL)/postinst 76 | sed -i 's/^module=[^\n]*/module=$(MODULE_NAME)/g' $(DEB_PACKAGE_CTRL)/postinst 77 | sed -i 's/^version=[^\n]*/version=$(VER)/g' $(DEB_PACKAGE_CTRL)/prerm 78 | sed -i 's/^module=[^\n]*/module=$(MODULE_NAME)/g' $(DEB_PACKAGE_CTRL)/prerm 79 | # Copy source 80 | cp -rf dkms.conf $(DEB_PACKAGE_SRC) 81 | cp -rf Makefile $(DEB_PACKAGE_SRC) 82 | cp -rf src $(DEB_PACKAGE_SRC) 83 | cp -rf src_pkg/dkms_postinst $(DEB_PACKAGE_BASE)/usr/share/$(MODULE_NAME)/postinst 84 | cp -rf tuxedo_keyboard.conf $(DEB_PACKAGE_BASE)/usr/share/$(MODULE_NAME)/tuxedo_keyboard.conf 85 | # Make sure files and folders have acceptable permissions 86 | chmod -R 755 $(DEB_PACKAGE_CTRL) 87 | chmod 644 $(DEB_PACKAGE_CTRL)/control 88 | find deb/$(DEB_PACKAGE_NAME)/usr -type d -exec chmod 755 {} \; 89 | find deb/$(DEB_PACKAGE_NAME)/usr -type f -exec chmod 644 {} \; 90 | chmod 755 $(DEB_PACKAGE_BASE)/usr/share/$(MODULE_NAME)/postinst 91 | chmod 644 $(DEB_PACKAGE_BASE)/usr/share/$(MODULE_NAME)/tuxedo_keyboard.conf 92 | 93 | gunzip $(DEB_PACKAGE_BASE)/usr/share/doc/$(MODULE_NAME)/changelog.gz 94 | gzip -n9 $(DEB_PACKAGE_BASE)/usr/share/doc/$(MODULE_NAME)/changelog 95 | 96 | # Make deb package 97 | dpkg-deb --root-owner-group -b $(DEB_PACKAGE_BASE) $(DEB_PACKAGE_NAME).deb 98 | 99 | package-deb-clean: 100 | rm -rf deb/$(MODULE_NAME)-* > /dev/null 2>&1 || true 101 | rm *.deb > /dev/null 2>&1 || true 102 | 103 | RPM_PACKAGE_NAME := $(MODULE_NAME)-$(VER) 104 | RPM_PACKAGE_SRC := rpm/SOURCES/$(RPM_PACKAGE_NAME) 105 | RPM_SPEC := rpm/SPECS/$(MODULE_NAME).spec 106 | RELEASE := 1 107 | 108 | package-rpm: 109 | # Create folder source structure according to current version 110 | rm -rf rpm || true 111 | mkdir -p $(RPM_PACKAGE_SRC) 112 | mkdir -p rpm/SPECS 113 | # Copy spec template 114 | cp -rf src_pkg/rpm_pkg.spec $(RPM_SPEC) 115 | # Modify spec file with version etc. 116 | sed -i 's/^%define module[^\n]*/%define module $(MODULE_NAME)/g' $(RPM_SPEC) 117 | sed -i 's/^Version:[^\n]*/Version: $(VER)/g' $(RPM_SPEC) 118 | sed -i 's/^Release:[^\n]*/Release: $(RELEASE)/g' $(RPM_SPEC) 119 | # Copy source 120 | cp -rf dkms.conf $(RPM_PACKAGE_SRC) 121 | cp -rf Makefile $(RPM_PACKAGE_SRC) 122 | cp -rf src $(RPM_PACKAGE_SRC) 123 | cp -rf LICENSE $(RPM_PACKAGE_SRC) 124 | cp -rf src_pkg/dkms_postinst $(RPM_PACKAGE_SRC)/postinst 125 | cp -rf tuxedo_keyboard.conf $(RPM_PACKAGE_SRC) 126 | # Compress/package source 127 | cd rpm/SOURCES && tar cjvf $(RPM_PACKAGE_NAME).tar.bz2 $(RPM_PACKAGE_NAME) 128 | # Make rpm package 129 | rpmbuild --debug -bb --define "_topdir `pwd`/rpm" $(RPM_SPEC) 130 | # Copy built package 131 | cp rpm/RPMS/noarch/*.rpm . 132 | 133 | package-rpm-clean: 134 | rm -rf rpm > /dev/null 2>&1 || true 135 | rm *.rpm > /dev/null 2>&1 || true 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Content 2 | - Description 3 | - Building and Install 4 | - Using 5 | 6 | # Description 7 | TUXEDO Computers kernel module drivers for keyboard, keyboard backlight & general hardware I/O using the SysFS interface (since version 3.2.0) 8 | 9 | Features 10 | - Driver for Fn-keys 11 | - SysFS control of brightness/color/mode for most TUXEDO keyboards 12 | - [https://docs.kernel.org/leds/leds-class.html](https://docs.kernel.org/leds/leds-class.html) 13 | - [https://docs.kernel.org/leds/leds-class-multicolor.html](https://docs.kernel.org/leds/leds-class-multicolor.html) 14 | - Hardware I/O driver for TUXEDO Control Center 15 | 16 | Modules included in this package 17 | - tuxedo-keyboard 18 | - tuxedo-io 19 | - clevo-wmi 20 | - clevo-acpi 21 | - uniwill-wmi 22 | 23 | # Building and Install 24 | 25 | ## Dependencies: 26 | - make 27 | - gcc or clang 28 | - linux-headers 29 | - dkms (Only when using this module with DKMS functionality) 30 | 31 | ## Warning when installing the module: 32 | 33 | Use either method only. Do not combine installation methods, such as starting with the build step below and proceeding to use the same build artifacts with the DKMS module. Otherwise the module built via dkms will fail to load with an `exec_format` error on newer kernels due to a mismatched version magic. 34 | 35 | This is why the DKMS build step begins with a `make clean` step. 36 | 37 | For convenience, on platforms where DKMS is in use, skip to the DKMS section directly. 38 | 39 | ## Clone the Git Repo: 40 | 41 | ```sh 42 | git clone https://github.com/tuxedocomputers/tuxedo-keyboard.git 43 | 44 | cd tuxedo-keyboard 45 | 46 | git checkout release 47 | ``` 48 | 49 | ## Build the Module: 50 | 51 | ```sh 52 | make clean && make 53 | ``` 54 | 55 | ## The DKMS route: 56 | 57 | ### Add as DKMS Module: 58 | 59 | Install the Module: 60 | ```sh 61 | make clean 62 | 63 | sudo make dkmsinstall 64 | ``` 65 | 66 | Load the Module with modprobe: 67 | ```sh 68 | modprobe tuxedo_keyboard 69 | ``` 70 | or 71 | ```sh 72 | sudo modprobe tuxedo_keyboard 73 | ``` 74 | 75 | You might also want to activate `tuxedo_io` module the same way if you are using [TCC](https://github.com/tuxedocomputers/tuxedo-control-center). 76 | 77 | ### Uninstalling the DKMS module: 78 | 79 | Remove the DKMS module and source: 80 | ```sh 81 | sudo make dkmsremove 82 | 83 | sudo rm /etc/modprobe.d/tuxedo_keyboard.conf 84 | ``` 85 | 86 | # Using 87 | 88 | ## modprobe 89 | 90 | ```sh 91 | modprobe tuxedo_keyboard 92 | ``` 93 | 94 | ## Load the Module on boot: 95 | 96 | If a module is relevant it will be loaded automatically on boot. If it is not loaded after a reboot, it most likely means that it is not needed. 97 | 98 | Add Module to /etc/modules 99 | ```sh 100 | sudo su 101 | 102 | echo tuxedo_keyboard >> /etc/modules 103 | ``` 104 | -------------------------------------------------------------------------------- /deb/module-name/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: module-name 2 | Version: x.x.x 3 | Section: admin 4 | Priority: optional 5 | Depends: dkms (>= 1.95) 6 | Conflicts: tuxedo-keyboard-dkms (<= 2.0.0), tuxedo-wmi-dkms (<= 1.5.1), tuxedo-xp-xc-touchpad-key-fix, tuxedo-touchpad-fix (<= 1.0.8), tuxedo-xp-xc-airplane-mode-fix (<= 0.6), tuxedo-cc-wmi 7 | Replaces: tuxedo-keyboard-dkms (<= 2.0.0), tuxedo-wmi-dkms (<= 1.5.1), tuxedo-xp-xc-touchpad-key-fix, tuxedo-touchpad-fix (<= 1.0.8), tuxedo-xp-xc-airplane-mode-fix (<= 0.6), tuxedo-cc-wmi 8 | Maintainer: TUXEDO Computers GmbH 9 | Homepage: https://www.tuxedocomputers.com 10 | Architecture: all 11 | Description: Keyboard driver for TUXEDO notebooks. 12 | Keyboard & keyboard backlight driver for TUXEDO notebooks. 13 | Meant for DKMS framework. 14 | -------------------------------------------------------------------------------- /deb/module-name/DEBIAN/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) 2020 TUXEDO Computers GmbH 3 | 4 | set -e 5 | 6 | module=module-name 7 | version=x.x.x 8 | 9 | 10 | NAME=${module} 11 | PACKAGE_NAME=${NAME} 12 | 13 | # From dkms standard postinst 14 | # Copyright (C) 2002-2005 Flavio Stanchina 15 | # Copyright (C) 2005-2006 Aric Cyr 16 | # Copyright (C) 2007 Mario Limonciello 17 | # Copyright (C) 2009 Alberto Milone 18 | DEB_NAME=$(echo $PACKAGE_NAME | sed 's,_,-,') 19 | CVERSION=`dpkg-query -W -f='${Version}' $DEB_NAME | awk -F "-" '{print $1}' | cut -d\: -f2` 20 | ARCH=`dpkg --print-architecture` 21 | 22 | dkms_configure () { 23 | for POSTINST in /usr/lib/dkms/common.postinst "/usr/share/$PACKAGE_NAME/postinst"; do 24 | if [ -f "$POSTINST" ]; then 25 | "$POSTINST" "$NAME" "$CVERSION" "/usr/share/$PACKAGE_NAME" "$ARCH" "$2" 26 | return $? 27 | fi 28 | echo "WARNING: $POSTINST does not exist." >&2 29 | done 30 | echo "ERROR: DKMS version is too old and $PACKAGE_NAME was not" >&2 31 | echo "built with legacy DKMS support." >&2 32 | echo "You must either rebuild $PACKAGE_NAME with legacy postinst" >&2 33 | echo "support or upgrade DKMS to a more current version." >&2 34 | return 1 35 | } 36 | 37 | # End dkms standard postinst 38 | 39 | case "$1" in 40 | configure) 41 | # Run standard dkms build/install for all kernels with headers installed 42 | dkms_configure 43 | 44 | # Attempt to (re-)load module immediately, fail silently if not possible at this stage 45 | 46 | # Also stop tccd service if running before 47 | echo "Check tccd running status" 48 | if systemctl is-active --quiet tccd.service; then 49 | TCCD_RUNNING=true 50 | else 51 | TCCD_RUNNING=false 52 | fi 53 | 54 | if $TCCD_RUNNING; then 55 | echo "Stop tccd temporarily" 56 | systemctl stop tccd 2>&1 || true 57 | fi 58 | 59 | echo "(Re)load modules if possible" 60 | rmmod tuxedo_io > /dev/null 2>&1 || true 61 | rmmod uniwill_wmi > /dev/null 2>&1 || true 62 | rmmod clevo_wmi > /dev/null 2>&1 || true 63 | rmmod clevo_acpi > /dev/null 2>&1 || true 64 | rmmod tuxedo_keyboard > /dev/null 2>&1 || true 65 | 66 | modprobe tuxedo_keyboard > /dev/null 2>&1 || true 67 | modprobe uniwill_wmi > /dev/null 2>&1 || true 68 | modprobe clevo_wmi > /dev/null 2>&1 || true 69 | modprobe clevo_acpi > /dev/null 2>&1 || true 70 | modprobe tuxedo_io > /dev/null 2>&1 || true 71 | 72 | # Restart tccd after reload if it was running 73 | if $TCCD_RUNNING; then 74 | echo "Start tccd again" 75 | systemctl start tccd 2>&1 || true 76 | fi 77 | 78 | # Install default config if none exist already 79 | if [ ! -f "/etc/modprobe.d/tuxedo_keyboard.conf" ]; then 80 | cp -f /usr/share/tuxedo-keyboard/tuxedo_keyboard.conf /etc/modprobe.d/tuxedo_keyboard.conf 81 | fi 82 | 83 | ;; 84 | 85 | abort-upgrade|abort-remove|abort-deconfigure) 86 | ;; 87 | 88 | *) 89 | echo "postinst called with unknown argument \`$1'" >&2 90 | exit 1 91 | ;; 92 | esac 93 | 94 | exit 0 95 | -------------------------------------------------------------------------------- /deb/module-name/DEBIAN/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | module=module-name 4 | version=x.x.x 5 | 6 | set -e 7 | 8 | case "$1" in 9 | remove) 10 | if [ "`dkms status -m $module`" ] 11 | then 12 | dkms remove -m $module -v $version --all 13 | # Attempt to remove module, fail silently if module is already unloaded 14 | rmmod -s $module > /dev/null 2>&1 || true 15 | fi 16 | rm -f /etc/modprobe.d/tuxedo_keyboard.conf || true 17 | ;; 18 | 19 | upgrade|deconfigure) 20 | if [ "`dkms status -m $module`" ] 21 | then 22 | dkms remove -m $module -v $version --all 23 | # Attempt to remove module, fail silently if module is already unloaded 24 | rmmod -s $module > /dev/null 2>&1 || true 25 | fi 26 | ;; 27 | 28 | failed-upgrade) 29 | ;; 30 | 31 | *) 32 | echo "prerm called with unknown argument \`$1'" >&2 33 | exit 1 34 | ;; 35 | esac 36 | 37 | 38 | 39 | exit 0 40 | 41 | 42 | -------------------------------------------------------------------------------- /deb/module-name/usr/share/doc/module-name/changelog.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuxedocomputers/tuxedo-keyboard/e06054b9c9671d4a9ab3f97b4a7f79ab734d3db4/deb/module-name/usr/share/doc/module-name/changelog.gz -------------------------------------------------------------------------------- /deb/module-name/usr/share/doc/module-name/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: tuxedo-keyboard 3 | Upstream-Contact: TUXEDO Computers GmbH 4 | 5 | Files: * 6 | Copyright: 2020 TUXEDO Computers GmbH 7 | License: GPL-3+ 8 | 9 | License: GPL-3+ 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | . 15 | This package is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | . 20 | You should have received a copy of the GNU General Public License 21 | along with this program. If not, see . 22 | . 23 | On Debian systems, the complete text of the GNU General 24 | Public License version 3 can be found in '/usr/share/common-licenses/GPL-3'. 25 | -------------------------------------------------------------------------------- /dkms.conf: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME=tuxedo-keyboard 2 | PACKAGE_VERSION=3.2.14 3 | 4 | DEST_MODULE_LOCATION[0]="/kernel/lib/" 5 | BUILT_MODULE_NAME[0]="tuxedo_keyboard" 6 | BUILT_MODULE_LOCATION[0]="src/" 7 | 8 | DEST_MODULE_LOCATION[1]="/kernel/lib/" 9 | BUILT_MODULE_NAME[1]="clevo_wmi" 10 | BUILT_MODULE_LOCATION[1]="src/" 11 | 12 | DEST_MODULE_LOCATION[2]="/kernel/lib/" 13 | BUILT_MODULE_NAME[2]="clevo_acpi" 14 | BUILT_MODULE_LOCATION[2]="src/" 15 | 16 | DEST_MODULE_LOCATION[3]="/kernel/lib/" 17 | BUILT_MODULE_NAME[3]="tuxedo_io" 18 | BUILT_MODULE_LOCATION[3]="src/tuxedo_io" 19 | 20 | DEST_MODULE_LOCATION[4]="/kernel/lib/" 21 | BUILT_MODULE_NAME[4]="uniwill_wmi" 22 | BUILT_MODULE_LOCATION[4]="src/" 23 | 24 | MAKE[0]="make KDIR=/lib/modules/${kernelver}/build" 25 | CLEAN="make clean" 26 | AUTOINSTALL="yes" 27 | -------------------------------------------------------------------------------- /src/clevo_acpi.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "clevo_interfaces.h" 25 | 26 | #define DRIVER_NAME "clevo_acpi" 27 | 28 | struct clevo_acpi_driver_data_t { 29 | struct acpi_device *adev; 30 | struct clevo_interface_t *clevo_interface; 31 | }; 32 | 33 | static struct clevo_acpi_driver_data_t *active_driver_data = NULL; 34 | 35 | static int clevo_acpi_evaluate(struct acpi_device *device, u8 cmd, u32 arg, union acpi_object **result) 36 | { 37 | int status; 38 | acpi_handle handle; 39 | u64 dsm_rev_dummy = 0x00; // Dummy 0 value since not used 40 | u64 dsm_func = cmd; 41 | // Integer package data for argument 42 | union acpi_object dsm_argv4_package_data[] = { 43 | { 44 | .integer.type = ACPI_TYPE_INTEGER, 45 | .integer.value = arg 46 | } 47 | }; 48 | 49 | // Package argument 50 | union acpi_object dsm_argv4 = { 51 | .package.type = ACPI_TYPE_PACKAGE, 52 | .package.count = 1, 53 | .package.elements = dsm_argv4_package_data 54 | }; 55 | 56 | union acpi_object *out_obj; 57 | 58 | guid_t clevo_acpi_dsm_uuid; 59 | 60 | status = guid_parse(CLEVO_ACPI_DSM_UUID, &clevo_acpi_dsm_uuid); 61 | if (status < 0) 62 | return -ENOENT; 63 | 64 | handle = acpi_device_handle(device); 65 | if (handle == NULL) 66 | return -ENODEV; 67 | 68 | out_obj = acpi_evaluate_dsm(handle, &clevo_acpi_dsm_uuid, dsm_rev_dummy, dsm_func, &dsm_argv4); 69 | if (!out_obj) { 70 | pr_err("failed to evaluate _DSM\n"); 71 | status = -1; 72 | } 73 | else { 74 | if (!IS_ERR_OR_NULL(result)) { 75 | *result = out_obj; 76 | } 77 | } 78 | 79 | return status; 80 | } 81 | 82 | int clevo_acpi_interface_method_call(u8 cmd, u32 arg, union acpi_object **result_value) 83 | { 84 | int status = 0; 85 | 86 | if (!IS_ERR_OR_NULL(active_driver_data)) { 87 | status = clevo_acpi_evaluate(active_driver_data->adev, cmd, arg, result_value); 88 | } else { 89 | pr_err("acpi method call exec, no driver data found\n"); 90 | pr_err("..for method_call: %0#4x arg: %0#10x\n", cmd, arg); 91 | status = -ENODATA; 92 | } 93 | // pr_debug("clevo_acpi method_call: %0#4x arg: %0#10x result: %0#10x\n", cmd, arg, !IS_ERR_OR_NULL(result_value) ? *result_value : 0); 94 | 95 | return status; 96 | } 97 | 98 | struct clevo_interface_t clevo_acpi_interface = { 99 | .string_id = CLEVO_INTERFACE_ACPI_STRID, 100 | .method_call = clevo_acpi_interface_method_call, 101 | }; 102 | 103 | static int clevo_acpi_add(struct acpi_device *device) 104 | { 105 | struct clevo_acpi_driver_data_t *driver_data; 106 | 107 | driver_data = devm_kzalloc(&device->dev, sizeof(*driver_data), GFP_KERNEL); 108 | if (!driver_data) 109 | return -ENOMEM; 110 | 111 | driver_data->adev = device; 112 | driver_data->clevo_interface = &clevo_acpi_interface; 113 | 114 | active_driver_data = driver_data; 115 | 116 | pr_debug("clevo_acpi driver add\n"); 117 | 118 | // Add this interface 119 | clevo_keyboard_add_interface(&clevo_acpi_interface); 120 | 121 | pr_info("interface initialized\n"); 122 | 123 | return 0; 124 | } 125 | 126 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 127 | static int clevo_acpi_remove(struct acpi_device *device) 128 | #else 129 | static void clevo_acpi_remove(struct acpi_device *device) 130 | #endif 131 | { 132 | pr_debug("clevo_acpi driver remove\n"); 133 | clevo_keyboard_remove_interface(&clevo_acpi_interface); 134 | active_driver_data = NULL; 135 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 136 | return 0; 137 | #endif 138 | } 139 | 140 | void clevo_acpi_notify(struct acpi_device *device, u32 event) 141 | { 142 | u32 event_value; 143 | union acpi_object *out_obj; 144 | int status; 145 | // struct clevo_acpi_driver_data_t *clevo_acpi_driver_data; 146 | 147 | status = clevo_acpi_evaluate(device, 0x01, 0, &out_obj); 148 | if (!status) { 149 | if (out_obj->type == ACPI_TYPE_INTEGER) { 150 | event_value = (u32)out_obj->integer.value; 151 | } else { 152 | pr_err("return type not integer, use clevo_evaluate_method2\n"); 153 | } 154 | ACPI_FREE(out_obj); 155 | } 156 | pr_debug("clevo_acpi event: %0#6x, clevo event value: %0#6x\n", event, event_value); 157 | 158 | // clevo_acpi_driver_data = container_of(&device, struct clevo_acpi_driver_data_t, adev); 159 | if (!IS_ERR_OR_NULL(clevo_acpi_interface.event_callb)) { 160 | // Execute registered callback 161 | clevo_acpi_interface.event_callb(event); 162 | } 163 | } 164 | 165 | #ifdef CONFIG_PM 166 | static int driver_suspend_callb(struct device *dev) 167 | { 168 | pr_debug("driver suspend\n"); 169 | return 0; 170 | } 171 | 172 | static int driver_resume_callb(struct device *dev) 173 | { 174 | pr_debug("driver resume\n"); 175 | return 0; 176 | } 177 | 178 | static SIMPLE_DEV_PM_OPS(clevo_driver_pm_ops, driver_suspend_callb, driver_resume_callb); 179 | #endif 180 | 181 | static const struct acpi_device_id clevo_acpi_device_ids[] = { 182 | {CLEVO_ACPI_RESOURCE_HID, 0}, 183 | {"", 0} 184 | }; 185 | 186 | static struct acpi_driver clevo_acpi_driver = { 187 | .name = DRIVER_NAME, 188 | .class = DRIVER_NAME, 189 | .owner = THIS_MODULE, 190 | .ids = clevo_acpi_device_ids, 191 | .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, 192 | .ops = { 193 | .add = clevo_acpi_add, 194 | .remove = clevo_acpi_remove, 195 | .notify = clevo_acpi_notify, 196 | }, 197 | #ifdef CONFIG_PM 198 | .drv.pm = &clevo_driver_pm_ops 199 | #endif 200 | }; 201 | 202 | module_acpi_driver(clevo_acpi_driver); 203 | 204 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 205 | MODULE_DESCRIPTION("Driver for Clevo ACPI interface"); 206 | MODULE_VERSION("0.1.1"); 207 | MODULE_LICENSE("GPL"); 208 | 209 | MODULE_DEVICE_TABLE(acpi, clevo_acpi_device_ids); 210 | -------------------------------------------------------------------------------- /src/clevo_interfaces.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2020-2021 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #ifndef CLEVO_INTERFACES_H 20 | #define CLEVO_INTERFACES_H 21 | 22 | #include 23 | #include 24 | 25 | #define CLEVO_WMI_EVENT_GUID "ABBC0F6B-8EA1-11D1-00A0-C90629100000" 26 | #define CLEVO_WMI_EMAIL_GUID "ABBC0F6C-8EA1-11D1-00A0-C90629100000" 27 | #define CLEVO_WMI_METHOD_GUID "ABBC0F6D-8EA1-11D1-00A0-C90629100000" 28 | 29 | #define CLEVO_ACPI_RESOURCE_HID "CLV0001" 30 | #define CLEVO_ACPI_DSM_UUID "93f224e4-fbdc-4bbf-add6-db71bdc0afad" 31 | 32 | // The clevo get commands expect no parameters 33 | #define CLEVO_CMD_GET_FANINFO1 0x63 34 | #define CLEVO_CMD_GET_FANINFO2 0x64 35 | #define CLEVO_CMD_GET_FANINFO3 0x6e 36 | 37 | #define CLEVO_CMD_GET_WEBCAM_SW 0x06 38 | #define CLEVO_CMD_GET_FLIGHTMODE_SW 0x07 39 | #define CLEVO_CMD_GET_TOUCHPAD_SW 0x09 40 | 41 | #define CLEVO_CMD_GET_EVENT 0x01 42 | 43 | #define CLEVO_CMD_GET_SPECS 0x0D // Returns buffer -> only works with clevo_evaluate_method2 44 | #define CLEVO_CMD_GET_BIOS_FEATURES_1 0x52 45 | #define CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_WHITE_ONLY_KB 0x40000000 46 | #define CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_3_ZONE_RGB_KB 0x00400000 47 | 48 | #define CLEVO_CMD_GET_BIOS_FEATURES_2 0x7A 49 | #define CLEVO_CMD_GET_BIOS_FEATURES_2_SUB_WHITE_ONLY_KB_MAX_5 0x4000 50 | 51 | #define CLEVO_CMD_GET_KB_WHITE_LEDS 0x3D // Get brightness of white only keyboard backlights 52 | 53 | // The clevo set commands expect a parameter 54 | #define CLEVO_CMD_SET_FANSPEED_VALUE 0x68 55 | #define CLEVO_CMD_SET_FANSPEED_AUTO 0x69 56 | 57 | #define CLEVO_CMD_SET_WEBCAM_SW 0x22 58 | #define CLEVO_CMD_SET_FLIGHTMODE_SW 0x20 59 | #define CLEVO_CMD_SET_TOUCHPAD_SW 0x2a 60 | 61 | #define CLEVO_CMD_SET_EVENTS_ENABLED 0x46 62 | 63 | #define CLEVO_CMD_SET_KB_WHITE_LEDS 0x27 // Set brightness of white only keyboard backlights 64 | #define CLEVO_CMD_SET_KB_RGB_LEDS 0x67 // Used to set color, brightness, blinking pattern, etc. 65 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_0 0xF0000000 // 1-zone RGB and 3-zone RGB left 66 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_1 0xF1000000 // 3-zone RGB center 67 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_2 0xF2000000 // 3-Zone RGB right 68 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_3 0xF3000000 // Unused on all known Clevo devices 69 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_BRIGHTNESS 0xF4000000 70 | 71 | #define CLEVO_CMD_OPT 0x79 72 | #define CLEVO_CMD_OPT_SUB_SET_PERF_PROF 0x19 73 | 74 | struct clevo_interface_t { 75 | char *string_id; 76 | void (*event_callb)(u32); 77 | int (*method_call)(u8, u32, union acpi_object **); 78 | }; 79 | 80 | int clevo_keyboard_add_interface(struct clevo_interface_t *new_interface); 81 | int clevo_keyboard_remove_interface(struct clevo_interface_t *interface); 82 | int clevo_evaluate_method(u8 cmd, u32 arg, u32 *result); 83 | int clevo_evaluate_method2(u8 cmd, u32 arg, union acpi_object **result); 84 | int clevo_get_active_interface_id(char **id_str); 85 | 86 | #define MODULE_ALIAS_CLEVO_WMI() \ 87 | MODULE_ALIAS("wmi:" CLEVO_WMI_EVENT_GUID); \ 88 | MODULE_ALIAS("wmi:" CLEVO_WMI_METHOD_GUID); 89 | 90 | #define CLEVO_INTERFACE_WMI_STRID "clevo_wmi" 91 | #define CLEVO_INTERFACE_ACPI_STRID "clevo_acpi" 92 | 93 | #define MODULE_ALIAS_CLEVO_ACPI() \ 94 | MODULE_ALIAS("acpi*:" CLEVO_ACPI_RESOURCE_HID ":*"); 95 | 96 | #define MODULE_ALIAS_CLEVO_INTERFACES() \ 97 | MODULE_ALIAS_CLEVO_WMI(); \ 98 | MODULE_ALIAS_CLEVO_ACPI(); 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /src/clevo_keyboard.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #ifndef CLEVO_KEYBOARD_H 20 | #define CLEVO_KEYBOARD_H 21 | 22 | #include "tuxedo_keyboard_common.h" 23 | #include "clevo_interfaces.h" 24 | #include "clevo_leds.h" 25 | 26 | // Clevo event codes 27 | #define CLEVO_EVENT_KB_LEDS_DECREASE 0x81 28 | #define CLEVO_EVENT_KB_LEDS_INCREASE 0x82 29 | #define CLEVO_EVENT_KB_LEDS_CYCLE_MODE 0x83 30 | #define CLEVO_EVENT_KB_LEDS_CYCLE_BRIGHTNESS 0x8A 31 | #define CLEVO_EVENT_KB_LEDS_TOGGLE 0x9F 32 | 33 | #define CLEVO_EVENT_KB_LEDS_DECREASE2 0x20 34 | #define CLEVO_EVENT_KB_LEDS_INCREASE2 0x21 35 | #define CLEVO_EVENT_KB_LEDS_TOGGLE2 0x3f 36 | 37 | #define CLEVO_EVENT_TOUCHPAD_TOGGLE 0x5D 38 | #define CLEVO_EVENT_TOUCHPAD_OFF 0xFC 39 | #define CLEVO_EVENT_TOUCHPAD_ON 0xFD 40 | 41 | #define CLEVO_EVENT_RFKILL1 0x85 42 | #define CLEVO_EVENT_RFKILL2 0x86 43 | 44 | #define CLEVO_EVENT_GAUGE_KEY 0x59 45 | 46 | #define CLEVO_KB_MODE_DEFAULT 0 // "CUSTOM"/Static Color 47 | 48 | static struct clevo_interfaces_t { 49 | struct clevo_interface_t *wmi; 50 | struct clevo_interface_t *acpi; 51 | } clevo_interfaces; 52 | 53 | static struct clevo_interface_t *active_clevo_interface; 54 | 55 | static struct tuxedo_keyboard_driver clevo_keyboard_driver; 56 | 57 | static DEFINE_MUTEX(clevo_keyboard_interface_modification_lock); 58 | 59 | static struct key_entry clevo_keymap[] = { 60 | // Keyboard backlight (RGB versions) 61 | { KE_KEY, CLEVO_EVENT_KB_LEDS_DECREASE, { KEY_KBDILLUMDOWN } }, 62 | { KE_KEY, CLEVO_EVENT_KB_LEDS_INCREASE, { KEY_KBDILLUMUP } }, 63 | { KE_KEY, CLEVO_EVENT_KB_LEDS_TOGGLE, { KEY_KBDILLUMTOGGLE } }, 64 | { KE_KEY, CLEVO_EVENT_KB_LEDS_CYCLE_MODE, { KEY_LIGHTS_TOGGLE } }, 65 | // Single cycle key (white only versions) (currently handled in driver) 66 | // { KE_KEY, CLEVO_EVENT_KB_LEDS_CYCLE_BRIGHTNESS, { KEY_KBDILLUMTOGGLE } }, 67 | 68 | // Alternative events (ex. 6 step white kbd) 69 | { KE_KEY, CLEVO_EVENT_KB_LEDS_DECREASE2, { KEY_KBDILLUMDOWN } }, 70 | { KE_KEY, CLEVO_EVENT_KB_LEDS_INCREASE2, { KEY_KBDILLUMUP } }, 71 | { KE_KEY, CLEVO_EVENT_KB_LEDS_TOGGLE2, { KEY_KBDILLUMTOGGLE } }, 72 | 73 | // Touchpad 74 | // The weirdly named touchpad toggle key that is implemented as KEY_F21 "everywhere" 75 | // (instead of KEY_TOUCHPAD_TOGGLE or on/off) 76 | // Most "new" devices just provide one toggle event 77 | { KE_KEY, CLEVO_EVENT_TOUCHPAD_TOGGLE, { KEY_F21 } }, 78 | // Some "old" devices produces on/off events 79 | { KE_KEY, CLEVO_EVENT_TOUCHPAD_OFF, { KEY_F21 } }, 80 | { KE_KEY, CLEVO_EVENT_TOUCHPAD_ON, { KEY_F21 } }, 81 | // The alternative key events (currently not used) 82 | // { KE_KEY, CLEVO_EVENT_TOUCHPAD_OFF, { KEY_TOUCHPAD_OFF } }, 83 | // { KE_KEY, CLEVO_EVENT_TOUCHPAD_ON, { KEY_TOUCHPAD_ON } }, 84 | // { KE_KEY, CLEVO_EVENT_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } }, 85 | 86 | // Rfkill still needed by some devices 87 | { KE_KEY, CLEVO_EVENT_RFKILL1, { KEY_RFKILL } }, 88 | { KE_IGNORE, CLEVO_EVENT_RFKILL2, { KEY_RFKILL } }, // Older rfkill event 89 | // Note: Volume events need to be ignored as to not interfere with built-in functionality 90 | { KE_IGNORE, 0xfa, { KEY_UNKNOWN } }, // Appears by volume up/down 91 | { KE_IGNORE, 0xfb, { KEY_UNKNOWN } }, // Appears by mute toggle 92 | 93 | // Only used to put ev bits 94 | { KE_KEY, 0xffff, { KEY_F6 } }, 95 | { KE_KEY, 0xffff, { KEY_LEFTALT } }, 96 | { KE_KEY, 0xffff, { KEY_LEFTMETA } }, 97 | 98 | { KE_END, 0 } 99 | }; 100 | 101 | // Keyboard struct 102 | static struct kbd_led_state_t { 103 | u8 has_mode; 104 | u8 mode; 105 | u8 whole_kbd_color; 106 | } kbd_led_state = { 107 | .has_mode = 1, 108 | .mode = CLEVO_KB_MODE_DEFAULT, 109 | .whole_kbd_color = 7, 110 | }; 111 | 112 | static struct kbd_backlight_mode_t { 113 | u8 key; 114 | u32 value; 115 | const char *const name; 116 | } kbd_backlight_modes[] = { 117 | { .key = 0, .value = 0x00000000, .name = "CUSTOM"}, 118 | { .key = 1, .value = 0x1002a000, .name = "BREATHE"}, 119 | { .key = 2, .value = 0x33010000, .name = "CYCLE"}, 120 | { .key = 3, .value = 0x80000000, .name = "DANCE"}, 121 | { .key = 4, .value = 0xA0000000, .name = "FLASH"}, 122 | { .key = 5, .value = 0x70000000, .name = "RANDOM_COLOR"}, 123 | { .key = 6, .value = 0x90000000, .name = "TEMPO"}, 124 | { .key = 7, .value = 0xB0000000, .name = "WAVE"} 125 | }; 126 | 127 | int clevo_evaluate_method2(u8 cmd, u32 arg, union acpi_object **result) 128 | { 129 | if (IS_ERR_OR_NULL(active_clevo_interface)) { 130 | pr_err("clevo_keyboard: no active interface while attempting cmd %02x arg %08x\n", cmd, arg); 131 | return -ENODEV; 132 | } 133 | return active_clevo_interface->method_call(cmd, arg, result); 134 | } 135 | EXPORT_SYMBOL(clevo_evaluate_method2); 136 | 137 | int clevo_evaluate_method(u8 cmd, u32 arg, u32 *result) 138 | { 139 | int status = 0; 140 | union acpi_object *out_obj; 141 | 142 | status = clevo_evaluate_method2(cmd, arg, &out_obj); 143 | if (status) { 144 | return status; 145 | } 146 | else { 147 | if (out_obj->type == ACPI_TYPE_INTEGER) { 148 | if (!IS_ERR_OR_NULL(result)) 149 | *result = (u32) out_obj->integer.value; 150 | } else { 151 | pr_err("return type not integer, use clevo_evaluate_method2\n"); 152 | status = -ENODATA; 153 | } 154 | ACPI_FREE(out_obj); 155 | } 156 | 157 | return status; 158 | } 159 | EXPORT_SYMBOL(clevo_evaluate_method); 160 | 161 | int clevo_get_active_interface_id(char **id_str) 162 | { 163 | if (IS_ERR_OR_NULL(active_clevo_interface)) 164 | return -ENODEV; 165 | 166 | if (!IS_ERR_OR_NULL(id_str)) 167 | *id_str = active_clevo_interface->string_id; 168 | 169 | return 0; 170 | } 171 | EXPORT_SYMBOL(clevo_get_active_interface_id); 172 | 173 | static void set_next_color_whole_kb(void) 174 | { 175 | /* "Calculate" new to-be color */ 176 | u32 new_color_id; 177 | u32 new_color_code; 178 | 179 | new_color_id = kbd_led_state.whole_kbd_color + 1; 180 | if (new_color_id >= color_list.size) { 181 | new_color_id = 1; // Skip black 182 | } 183 | new_color_code = color_list.colors[new_color_id].code; 184 | 185 | TUXEDO_INFO("set_next_color_whole_kb(): new_color_id: %i, new_color_code %X", 186 | new_color_id, new_color_code); 187 | 188 | /* Set color on all four regions*/ 189 | clevo_leds_set_color_extern(new_color_code); 190 | kbd_led_state.whole_kbd_color = new_color_id; 191 | } 192 | 193 | static void set_kbd_backlight_mode(u8 kbd_backlight_mode) 194 | { 195 | TUXEDO_INFO("Set keyboard backlight mode on %s", kbd_backlight_modes[kbd_backlight_mode].name); 196 | 197 | if (!clevo_evaluate_method(CLEVO_CMD_SET_KB_RGB_LEDS, kbd_backlight_modes[kbd_backlight_mode].value, NULL)) { 198 | // method was succesfull so update ur internal state struct 199 | kbd_led_state.mode = kbd_backlight_mode; 200 | } 201 | } 202 | 203 | // Sysfs Interface for the keyboard backlight mode 204 | static ssize_t list_kbd_backlight_modes_fs(struct device *child, struct device_attribute *attr, 205 | char *buffer) 206 | { 207 | return sprintf(buffer, "%d\n", kbd_led_state.mode); 208 | } 209 | 210 | static ssize_t set_kbd_backlight_mode_fs(struct device *child, 211 | struct device_attribute *attr, 212 | const char *buffer, size_t size) 213 | { 214 | unsigned int kbd_backlight_mode; 215 | 216 | int err = kstrtouint(buffer, 0, &kbd_backlight_mode); 217 | if (err) { 218 | return err; 219 | } 220 | 221 | kbd_backlight_mode = clamp_t(u8, kbd_backlight_mode, 0, ARRAY_SIZE(kbd_backlight_modes) - 1); 222 | set_kbd_backlight_mode(kbd_backlight_mode); 223 | 224 | return size; 225 | } 226 | 227 | // Sysfs attribute file permissions and method linking 228 | static DEVICE_ATTR(kbd_backlight_mode, 0644, list_kbd_backlight_modes_fs, set_kbd_backlight_mode_fs); 229 | 230 | static int kbd_backlight_mode_id_validator(const char *value, 231 | const struct kernel_param *kbd_backlight_mode_param) 232 | { 233 | int kbd_backlight_mode = 0; 234 | 235 | if (kstrtoint(value, 10, &kbd_backlight_mode) != 0 236 | || kbd_backlight_mode < 0 237 | || kbd_backlight_mode > (ARRAY_SIZE(kbd_backlight_modes) - 1)) { 238 | return -EINVAL; 239 | } 240 | 241 | return param_set_int(value, kbd_backlight_mode_param); 242 | } 243 | 244 | static const struct kernel_param_ops param_ops_mode_ops = { 245 | .set = kbd_backlight_mode_id_validator, 246 | .get = param_get_int, 247 | }; 248 | 249 | static u8 param_kbd_backlight_mode = CLEVO_KB_MODE_DEFAULT; 250 | module_param_cb(kbd_backlight_mode, ¶m_ops_mode_ops, ¶m_kbd_backlight_mode, S_IRUSR); 251 | MODULE_PARM_DESC(kbd_backlight_mode, "Set the keyboard backlight mode"); 252 | 253 | static void clevo_send_cc_combo(void) 254 | { 255 | // Special key combination. Opens TCC by default when installed. 256 | input_report_key(clevo_keyboard_driver.input_device, KEY_LEFTMETA, 1); 257 | input_report_key(clevo_keyboard_driver.input_device, KEY_LEFTALT, 1); 258 | input_report_key(clevo_keyboard_driver.input_device, KEY_F6, 1); 259 | input_sync(clevo_keyboard_driver.input_device); 260 | input_report_key(clevo_keyboard_driver.input_device, KEY_F6, 0); 261 | input_report_key(clevo_keyboard_driver.input_device, KEY_LEFTALT, 0); 262 | input_report_key(clevo_keyboard_driver.input_device, KEY_LEFTMETA, 0); 263 | input_sync(clevo_keyboard_driver.input_device); 264 | } 265 | 266 | static void clevo_keyboard_event_callb(u32 event) 267 | { 268 | u32 key_event = event; 269 | 270 | TUXEDO_DEBUG("Clevo event: %0#6x\n", event); 271 | 272 | switch (key_event) { 273 | case CLEVO_EVENT_GAUGE_KEY: 274 | clevo_send_cc_combo(); 275 | break; 276 | case CLEVO_EVENT_KB_LEDS_CYCLE_MODE: 277 | if (clevo_leds_get_backlight_type() == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 278 | clevo_send_cc_combo(); 279 | } else { 280 | set_next_color_whole_kb(); 281 | } 282 | break; 283 | case CLEVO_EVENT_KB_LEDS_CYCLE_BRIGHTNESS: 284 | clevo_leds_notify_brightness_change_extern(); 285 | break; 286 | default: 287 | break; 288 | } 289 | 290 | if (current_driver != NULL && current_driver->input_device != NULL) { 291 | if (!sparse_keymap_report_known_event(current_driver->input_device, key_event, 1, true)) { 292 | TUXEDO_DEBUG("Unknown key - %d (%0#6x)\n", key_event, key_event); 293 | } 294 | } 295 | } 296 | 297 | static void clevo_keyboard_init_device_interface(struct platform_device *dev) 298 | { 299 | // Setup sysfs 300 | if (clevo_leds_get_backlight_type() == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 301 | if (device_create_file(&dev->dev, &dev_attr_kbd_backlight_mode) != 0) { 302 | TUXEDO_ERROR("Sysfs attribute file creation failed for blinking pattern\n"); 303 | } 304 | else { 305 | kbd_led_state.has_mode = 1; 306 | } 307 | } 308 | } 309 | 310 | /** 311 | * strstr version of dmi_match 312 | */ 313 | static bool dmi_string_in(enum dmi_field f, const char *str) 314 | { 315 | const char *info = dmi_get_system_info(f); 316 | 317 | if (info == NULL || str == NULL) 318 | return info == str; 319 | 320 | return strstr(info, str) != NULL; 321 | } 322 | 323 | static void clevo_keyboard_init(void) 324 | { 325 | bool performance_profile_set_workaround; 326 | 327 | kbd_led_state.mode = param_kbd_backlight_mode; 328 | set_kbd_backlight_mode(kbd_led_state.mode); 329 | 330 | clevo_evaluate_method(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL); 331 | 332 | // Workaround for firmware issue not setting selected performance profile. 333 | // Explicitly set "performance" perf. profile on init regardless of what is chosen 334 | // for these devices (Aura, XP14, IBS14v5) 335 | performance_profile_set_workaround = false 336 | || dmi_string_in(DMI_BOARD_NAME, "AURA1501") 337 | || dmi_string_in(DMI_BOARD_NAME, "EDUBOOK1502") 338 | || dmi_string_in(DMI_BOARD_NAME, "NL5xRU") 339 | || dmi_string_in(DMI_BOARD_NAME, "NV4XMB,ME,MZ") 340 | || dmi_string_in(DMI_BOARD_NAME, "L140CU") 341 | || dmi_string_in(DMI_BOARD_NAME, "NS50MU") 342 | || dmi_string_in(DMI_BOARD_NAME, "NS50_70MU") 343 | || dmi_string_in(DMI_BOARD_NAME, "PCX0DX") 344 | || dmi_string_in(DMI_BOARD_NAME, "PCx0Dx_GN20") 345 | || dmi_string_in(DMI_BOARD_NAME, "L14xMU") 346 | ; 347 | if (performance_profile_set_workaround) { 348 | TUXEDO_INFO("Performance profile 'performance' set workaround applied\n"); 349 | clevo_evaluate_method(CLEVO_CMD_OPT, 0x19000002, NULL); 350 | } 351 | } 352 | 353 | static int clevo_keyboard_probe(struct platform_device *dev) 354 | { 355 | clevo_leds_init(dev); 356 | // clevo_keyboard_init_device_interface() must come after clevo_leds_init() 357 | // to know keyboard backlight type 358 | clevo_keyboard_init_device_interface(dev); 359 | clevo_keyboard_init(); 360 | 361 | return 0; 362 | } 363 | 364 | static void clevo_keyboard_remove_device_interface(struct platform_device *dev) 365 | { 366 | if (kbd_led_state.has_mode == 1) { 367 | device_remove_file(&dev->dev, &dev_attr_kbd_backlight_mode); 368 | } 369 | } 370 | 371 | static int clevo_keyboard_remove(struct platform_device *dev) 372 | { 373 | clevo_keyboard_remove_device_interface(dev); 374 | clevo_leds_remove(dev); 375 | return 0; 376 | } 377 | 378 | static int clevo_keyboard_suspend(struct platform_device *dev, pm_message_t state) 379 | { 380 | clevo_leds_suspend(dev); 381 | return 0; 382 | } 383 | 384 | static int clevo_keyboard_resume(struct platform_device *dev) 385 | { 386 | clevo_evaluate_method(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL); 387 | clevo_leds_restore_state_extern(); // Sometimes clevo devices forget their last state after 388 | // suspend, so let the kernel ensure it. 389 | clevo_leds_resume(dev); 390 | return 0; 391 | } 392 | 393 | static struct platform_driver platform_driver_clevo = { 394 | .remove = clevo_keyboard_remove, 395 | .suspend = clevo_keyboard_suspend, 396 | .resume = clevo_keyboard_resume, 397 | .driver = 398 | { 399 | .name = DRIVER_NAME, 400 | .owner = THIS_MODULE, 401 | }, 402 | }; 403 | 404 | static struct tuxedo_keyboard_driver clevo_keyboard_driver = { 405 | .platform_driver = &platform_driver_clevo, 406 | .probe = clevo_keyboard_probe, 407 | .key_map = clevo_keymap, 408 | }; 409 | 410 | int clevo_keyboard_add_interface(struct clevo_interface_t *new_interface) 411 | { 412 | mutex_lock(&clevo_keyboard_interface_modification_lock); 413 | 414 | if (strcmp(new_interface->string_id, CLEVO_INTERFACE_WMI_STRID) == 0) { 415 | clevo_interfaces.wmi = new_interface; 416 | clevo_interfaces.wmi->event_callb = clevo_keyboard_event_callb; 417 | 418 | // Only use wmi if there is no other current interface 419 | if (ZERO_OR_NULL_PTR(active_clevo_interface)) { 420 | pr_debug("enable wmi events\n"); 421 | clevo_interfaces.wmi->method_call(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL); 422 | 423 | active_clevo_interface = clevo_interfaces.wmi; 424 | } 425 | } else if (strcmp(new_interface->string_id, CLEVO_INTERFACE_ACPI_STRID) == 0) { 426 | clevo_interfaces.acpi = new_interface; 427 | clevo_interfaces.acpi->event_callb = clevo_keyboard_event_callb; 428 | 429 | pr_debug("enable acpi events (takes priority)\n"); 430 | clevo_interfaces.acpi->method_call(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL); 431 | active_clevo_interface = clevo_interfaces.acpi; 432 | } else { 433 | // Not recognized interface 434 | pr_err("unrecognized interface\n"); 435 | mutex_unlock(&clevo_keyboard_interface_modification_lock); 436 | return -EINVAL; 437 | } 438 | 439 | mutex_unlock(&clevo_keyboard_interface_modification_lock); 440 | 441 | if (active_clevo_interface != NULL) 442 | tuxedo_keyboard_init_driver(&clevo_keyboard_driver); 443 | 444 | return 0; 445 | } 446 | EXPORT_SYMBOL(clevo_keyboard_add_interface); 447 | 448 | int clevo_keyboard_remove_interface(struct clevo_interface_t *interface) 449 | { 450 | mutex_lock(&clevo_keyboard_interface_modification_lock); 451 | 452 | if (strcmp(interface->string_id, CLEVO_INTERFACE_WMI_STRID) == 0) { 453 | clevo_interfaces.wmi = NULL; 454 | } else if (strcmp(interface->string_id, CLEVO_INTERFACE_ACPI_STRID) == 0) { 455 | clevo_interfaces.acpi = NULL; 456 | } else { 457 | mutex_unlock(&clevo_keyboard_interface_modification_lock); 458 | return -EINVAL; 459 | } 460 | 461 | if (active_clevo_interface == interface) { 462 | tuxedo_keyboard_remove_driver(&clevo_keyboard_driver); 463 | active_clevo_interface = NULL; 464 | } 465 | 466 | 467 | mutex_unlock(&clevo_keyboard_interface_modification_lock); 468 | 469 | return 0; 470 | } 471 | EXPORT_SYMBOL(clevo_keyboard_remove_interface); 472 | 473 | #endif // CLEVO_KEYBOARD_H 474 | -------------------------------------------------------------------------------- /src/clevo_leds.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #ifndef CLEVO_LEDS_H 21 | #define CLEVO_LEDS_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | enum clevo_kb_backlight_types { 28 | CLEVO_KB_BACKLIGHT_TYPE_NONE = 0x00, 29 | CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR = 0x01, 30 | CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB = 0x02, 31 | CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB = 0x06, 32 | CLEVO_KB_BACKLIGHT_TYPE_PER_KEY_RGB = 0xf3 33 | }; 34 | 35 | int clevo_leds_init(struct platform_device *dev); 36 | int clevo_leds_remove(struct platform_device *dev); 37 | int clevo_leds_suspend(struct platform_device *dev); 38 | int clevo_leds_resume(struct platform_device *dev); 39 | enum clevo_kb_backlight_types clevo_leds_get_backlight_type(void); 40 | void clevo_leds_restore_state_extern(void); 41 | void clevo_leds_notify_brightness_change_extern(void); 42 | void clevo_leds_set_brightness_extern(enum led_brightness brightness); 43 | void clevo_leds_set_color_extern(u32 color); 44 | 45 | // TODO The following should go into a seperate .c file, but for this to work more reworking is required in the tuxedo_keyboard structure. 46 | 47 | #include "clevo_leds.h" 48 | 49 | #include "clevo_interfaces.h" 50 | 51 | #include 52 | #include 53 | 54 | #define CLEVO_KBD_BRIGHTNESS_MAX 0xff 55 | #define CLEVO_KBD_BRIGHTNESS_DEFAULT 0x00 56 | 57 | #define CLEVO_KBD_BRIGHTNESS_WHITE_MAX 0x02 // White only keyboards can only be off, half, or full brightness 58 | #define CLEVO_KBD_BRIGHTNESS_WHITE_DEFAULT 0x00 59 | 60 | #define CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5 0x05 // Devices <= Intel 7th gen had a different white control with 5 brightness values + off 61 | #define CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5_DEFAULT 0x00 62 | 63 | #define CLEVO_KB_COLOR_DEFAULT_RED 0xff 64 | #define CLEVO_KB_COLOR_DEFAULT_GREEN 0xff 65 | #define CLEVO_KB_COLOR_DEFAULT_BLUE 0xff 66 | #define CLEVO_KB_COLOR_DEFAULT ((CLEVO_KB_COLOR_DEFAULT_RED << 16) + (CLEVO_KB_COLOR_DEFAULT_GREEN << 8) + CLEVO_KB_COLOR_DEFAULT_BLUE) 67 | 68 | static enum clevo_kb_backlight_types clevo_kb_backlight_type = CLEVO_KB_BACKLIGHT_TYPE_NONE; 69 | static bool leds_initialized = false; 70 | 71 | /** 72 | * Color scaling quirk list 73 | */ 74 | static void color_scaling(enum clevo_kb_backlight_types *type, u8 *red, u8 *green, u8 *blue) 75 | { 76 | if (*type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 77 | *red = (180 * *red) / 255; 78 | *blue = (200 * *blue) / 255; 79 | } 80 | } 81 | 82 | static int clevo_evaluate_set_white_brightness(u8 brightness) 83 | { 84 | pr_debug("Set white brightness to %d\n", brightness); 85 | 86 | return clevo_evaluate_method (CLEVO_CMD_SET_KB_WHITE_LEDS, brightness, NULL); 87 | } 88 | 89 | static int clevo_evaluate_set_rgb_brightness(u8 brightness) 90 | { 91 | pr_debug("Set RGB brightness to %d\n", brightness); 92 | 93 | return clevo_evaluate_method (CLEVO_CMD_SET_KB_RGB_LEDS, CLEVO_CMD_SET_KB_LEDS_SUB_RGB_BRIGHTNESS | brightness, NULL); 94 | } 95 | 96 | static int clevo_evaluate_set_rgb_color(u32 zone, u32 color) 97 | { 98 | u32 cset = ((color & 0x0000FF) << 16) | ((color & 0xFF0000) >> 8) | ((color & 0x00FF00) >> 8); 99 | u32 clevo_submethod_arg = zone | cset; 100 | 101 | pr_debug("Set Color 0x%08x for region 0x%08x\n", color, zone); 102 | 103 | return clevo_evaluate_method(CLEVO_CMD_SET_KB_RGB_LEDS, clevo_submethod_arg, NULL); 104 | } 105 | 106 | static int clevo_evaluate_set_keyboard_status(u8 state) 107 | { 108 | u32 cmd = 0xE0000000; 109 | TUXEDO_INFO("Set keyboard enabled to: %d\n", state); 110 | 111 | if (state == 0) { 112 | cmd |= 0x003001; 113 | } else { 114 | cmd |= 0x07F001; 115 | } 116 | 117 | return clevo_evaluate_method(CLEVO_CMD_SET_KB_RGB_LEDS, cmd, NULL); 118 | } 119 | 120 | static void clevo_leds_set_brightness(struct led_classdev *led_cdev __always_unused, enum led_brightness brightness) { 121 | int ret = clevo_evaluate_set_white_brightness(brightness); 122 | if (ret) { 123 | pr_debug("clevo_leds_set_brightness(): clevo_evaluate_set_white_brightness() failed\n"); 124 | return; 125 | } 126 | led_cdev->brightness = brightness; 127 | } 128 | 129 | /*static void clevo_leds_set_brightness_mc(struct led_classdev *led_cdev, enum led_brightness brightness) { 130 | int ret; 131 | u32 zone, color; 132 | struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); 133 | 134 | ret = clevo_evaluate_set_rgb_brightness(CLEVO_KBD_BRIGHTNESS_MAX); 135 | if (ret) { 136 | pr_debug("clevo_leds_set_brightness_mc(): clevo_evaluate_set_rgb_brightness() failed\n"); 137 | return; 138 | } 139 | 140 | zone = mcled_cdev->subled_info[0].channel; 141 | 142 | led_mc_calc_color_components(mcled_cdev, brightness); 143 | color = (mcled_cdev->subled_info[0].brightness << 16) + 144 | (mcled_cdev->subled_info[1].brightness << 8) + 145 | mcled_cdev->subled_info[2].brightness; 146 | 147 | ret = clevo_evaluate_set_rgb_color(zone, color); 148 | if (ret) { 149 | pr_debug("clevo_leds_set_brightness_mc(): clevo_evaluate_set_rgb_color() failed\n"); 150 | return; 151 | } 152 | led_cdev->brightness = brightness; 153 | }*/ 154 | 155 | // Temprary fix for KDE: KDE does only set one kbd_backlight brightness value, this version of the 156 | // function uses clevos built in brightness setting to set the whole keyboard brightness at once. 157 | // -> use clevo_evaluate_set_rgb_brightness() to set overall brightness via firmware instead of scaling 158 | // the RGB values 159 | // -> update all clevo_mcled_cdevs brightness levels to refect that the firmware method sets the 160 | // the whole keyboard brightness and not just one zone 161 | // This is a temporary fix until KDE handles multiple keyboard backlights correctly 162 | static struct led_classdev_mc clevo_mcled_cdevs[3]; // forward declaration 163 | static void clevo_leds_set_brightness_mc(struct led_classdev *led_cdev, enum led_brightness brightness) { 164 | int ret; 165 | u32 zone, color; 166 | u8 red, green, blue; 167 | struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); 168 | 169 | ret = clevo_evaluate_set_rgb_brightness(brightness); 170 | if (ret) { 171 | pr_debug("clevo_leds_set_brightness_mc(): clevo_evaluate_set_rgb_brightness() failed\n"); 172 | return; 173 | } 174 | clevo_mcled_cdevs[0].led_cdev.brightness = brightness; 175 | clevo_mcled_cdevs[1].led_cdev.brightness = brightness; 176 | clevo_mcled_cdevs[2].led_cdev.brightness = brightness; 177 | 178 | zone = mcled_cdev->subled_info[0].channel; 179 | 180 | red = mcled_cdev->subled_info[0].intensity; 181 | green = mcled_cdev->subled_info[1].intensity; 182 | blue = mcled_cdev->subled_info[2].intensity; 183 | 184 | color_scaling(&clevo_kb_backlight_type, &red, &green, &blue); 185 | 186 | color = (red << 16) + 187 | (green << 8) + 188 | blue; 189 | 190 | ret = clevo_evaluate_set_rgb_color(zone, color); 191 | if (ret) { 192 | pr_debug("clevo_leds_set_brightness_mc(): clevo_evaluate_set_rgb_color() failed\n"); 193 | } 194 | } 195 | 196 | static struct led_classdev clevo_led_cdev = { 197 | .name = "white:" LED_FUNCTION_KBD_BACKLIGHT, 198 | .max_brightness = CLEVO_KBD_BRIGHTNESS_WHITE_MAX, 199 | .brightness_set = &clevo_leds_set_brightness, 200 | .brightness = CLEVO_KBD_BRIGHTNESS_WHITE_DEFAULT, 201 | .flags = LED_BRIGHT_HW_CHANGED 202 | }; 203 | 204 | static struct mc_subled clevo_mcled_cdevs_subleds[3][3] = { 205 | { 206 | { 207 | .color_index = LED_COLOR_ID_RED, 208 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 209 | .intensity = CLEVO_KB_COLOR_DEFAULT_RED, 210 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_0 211 | }, 212 | { 213 | .color_index = LED_COLOR_ID_GREEN, 214 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 215 | .intensity = CLEVO_KB_COLOR_DEFAULT_GREEN, 216 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_0 217 | }, 218 | { 219 | .color_index = LED_COLOR_ID_BLUE, 220 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 221 | .intensity = CLEVO_KB_COLOR_DEFAULT_BLUE, 222 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_0 223 | } 224 | }, 225 | { 226 | { 227 | .color_index = LED_COLOR_ID_RED, 228 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 229 | .intensity = CLEVO_KB_COLOR_DEFAULT_RED, 230 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_1 231 | }, 232 | { 233 | .color_index = LED_COLOR_ID_GREEN, 234 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 235 | .intensity = CLEVO_KB_COLOR_DEFAULT_GREEN, 236 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_1 237 | }, 238 | { 239 | .color_index = LED_COLOR_ID_BLUE, 240 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 241 | .intensity = CLEVO_KB_COLOR_DEFAULT_BLUE, 242 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_1 243 | } 244 | }, 245 | { 246 | { 247 | .color_index = LED_COLOR_ID_RED, 248 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 249 | .intensity = CLEVO_KB_COLOR_DEFAULT_RED, 250 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_2 251 | }, 252 | { 253 | .color_index = LED_COLOR_ID_GREEN, 254 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 255 | .intensity = CLEVO_KB_COLOR_DEFAULT_GREEN, 256 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_2 257 | }, 258 | { 259 | .color_index = LED_COLOR_ID_BLUE, 260 | .brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 261 | .intensity = CLEVO_KB_COLOR_DEFAULT_BLUE, 262 | .channel = CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_2 263 | } 264 | } 265 | }; 266 | 267 | static struct led_classdev_mc clevo_mcled_cdevs[3] = { 268 | { 269 | .led_cdev.name = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, 270 | .led_cdev.max_brightness = CLEVO_KBD_BRIGHTNESS_MAX, 271 | .led_cdev.brightness_set = &clevo_leds_set_brightness_mc, 272 | .led_cdev.brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 273 | .num_colors = 3, 274 | .subled_info = clevo_mcled_cdevs_subleds[0] 275 | }, 276 | { 277 | .led_cdev.name = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, 278 | .led_cdev.max_brightness = CLEVO_KBD_BRIGHTNESS_MAX, 279 | .led_cdev.brightness_set = &clevo_leds_set_brightness_mc, 280 | .led_cdev.brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 281 | .num_colors = 3, 282 | .subled_info = clevo_mcled_cdevs_subleds[1] 283 | }, 284 | { 285 | .led_cdev.name = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, 286 | .led_cdev.max_brightness = CLEVO_KBD_BRIGHTNESS_MAX, 287 | .led_cdev.brightness_set = &clevo_leds_set_brightness_mc, 288 | .led_cdev.brightness = CLEVO_KBD_BRIGHTNESS_DEFAULT, 289 | .num_colors = 3, 290 | .subled_info = clevo_mcled_cdevs_subleds[2] 291 | } 292 | }; 293 | 294 | int clevo_leds_init(struct platform_device *dev) 295 | { 296 | int ret, i; 297 | int status; 298 | union acpi_object *result; 299 | u32 result_fallback; 300 | 301 | for (i = 0; i < 3; ++i) { 302 | status = clevo_evaluate_method2(CLEVO_CMD_GET_SPECS, 0, &result); 303 | if (!status) { 304 | if (result->type == ACPI_TYPE_BUFFER) { 305 | pr_debug("CLEVO_CMD_GET_SPECS result->buffer.pointer[0x0f]: 0x%02x\n", result->buffer.pointer[0x0f]); 306 | clevo_kb_backlight_type = result->buffer.pointer[0x0f]; 307 | if (clevo_kb_backlight_type) { 308 | status = clevo_evaluate_method(CLEVO_CMD_GET_BIOS_FEATURES_2, 0, &result_fallback); 309 | if (!status) { 310 | pr_debug("CLEVO_CMD_GET_BIOS_FEATURES_2 result_fallback: 0x%08x\n", result_fallback); 311 | if (result_fallback & CLEVO_CMD_GET_BIOS_FEATURES_2_SUB_WHITE_ONLY_KB_MAX_5) { 312 | clevo_led_cdev.max_brightness = CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5; 313 | clevo_led_cdev.brightness = CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5_DEFAULT; 314 | } 315 | } 316 | break; 317 | } else { 318 | pr_debug("clevo_kb_backlight_type 0x00 probably wrong, retrying...\n"); 319 | msleep(50); 320 | } 321 | } 322 | else { 323 | pr_err("CLEVO_CMD_GET_SPECS does not exist on this device or return value has wrong type, trying CLEVO_CMD_GET_BIOS_FEATURES\n"); 324 | status = -EINVAL; 325 | } 326 | ACPI_FREE(result); 327 | } 328 | else { 329 | pr_notice("CLEVO_CMD_GET_SPECS does not exist on this device or failed, trying CLEVO_CMD_GET_BIOS_FEATURES_1\n"); 330 | } 331 | } 332 | 333 | if (status || clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_NONE) { 334 | // check for devices <= Intel 8th gen (only white only, 3 zone RGB, or no backlight on these devices) 335 | status = clevo_evaluate_method(CLEVO_CMD_GET_BIOS_FEATURES_1, 0, &result_fallback); 336 | if (!status) { 337 | pr_debug("CLEVO_CMD_GET_BIOS_FEATURES_1 result_fallback: 0x%08x\n", result_fallback); 338 | if (result_fallback & CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_3_ZONE_RGB_KB) { 339 | clevo_kb_backlight_type = CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB; 340 | } 341 | else if (result_fallback & CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_WHITE_ONLY_KB) { 342 | clevo_kb_backlight_type = CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR; 343 | 344 | status = clevo_evaluate_method(CLEVO_CMD_GET_BIOS_FEATURES_2, 0, &result_fallback); 345 | if (!status) { 346 | pr_debug("CLEVO_CMD_GET_BIOS_FEATURES_2 result_fallback: 0x%08x\n", result_fallback); 347 | if (result_fallback & CLEVO_CMD_GET_BIOS_FEATURES_2_SUB_WHITE_ONLY_KB_MAX_5) { 348 | clevo_led_cdev.max_brightness = CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5; 349 | clevo_led_cdev.brightness = CLEVO_KBD_BRIGHTNESS_WHITE_MAX_5_DEFAULT; 350 | } 351 | } 352 | else { 353 | pr_notice("CLEVO_CMD_GET_BIOS_FEATURES_2 does not exist on this device or failed\n"); 354 | } 355 | } 356 | } 357 | else { 358 | pr_notice("CLEVO_CMD_GET_BIOS_FEATURES_1 does not exist on this device or failed\n"); 359 | } 360 | } 361 | pr_debug("Keyboard backlight type: 0x%02x\n", clevo_kb_backlight_type); 362 | 363 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) 364 | clevo_leds_set_brightness_extern(clevo_led_cdev.brightness); 365 | else 366 | clevo_leds_set_color_extern(CLEVO_KB_COLOR_DEFAULT); 367 | 368 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 369 | pr_debug("Registering fixed color leds interface\n"); 370 | ret = led_classdev_register(&dev->dev, &clevo_led_cdev); 371 | if (ret) { 372 | pr_err("Registering fixed color leds interface failed\n"); 373 | return ret; 374 | } 375 | } 376 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 377 | clevo_evaluate_set_keyboard_status(1); 378 | pr_debug("Registering single zone rgb leds interface\n"); 379 | ret = devm_led_classdev_multicolor_register(&dev->dev, &clevo_mcled_cdevs[0]); 380 | if (ret) { 381 | pr_err("Registering single zone rgb leds interface failed\n"); 382 | return ret; 383 | } 384 | } 385 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 386 | clevo_evaluate_set_keyboard_status(1); 387 | pr_debug("Registering three zone rgb leds interface\n"); 388 | ret = devm_led_classdev_multicolor_register(&dev->dev, &clevo_mcled_cdevs[0]); 389 | if (ret) { 390 | pr_err("Registering three zone rgb zone 0 leds interface failed\n"); 391 | return ret; 392 | } 393 | ret = devm_led_classdev_multicolor_register(&dev->dev, &clevo_mcled_cdevs[1]); 394 | if (ret) { 395 | pr_err("Registering three zone rgb zone 1 leds interface failed\n"); 396 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[0]); 397 | return ret; 398 | } 399 | ret = devm_led_classdev_multicolor_register(&dev->dev, &clevo_mcled_cdevs[2]); 400 | if (ret) { 401 | pr_err("Registering three zone rgb zone 2 leds interface failed\n"); 402 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[0]); 403 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[1]); 404 | return ret; 405 | } 406 | } 407 | 408 | leds_initialized = true; 409 | return 0; 410 | } 411 | EXPORT_SYMBOL(clevo_leds_init); 412 | 413 | int clevo_leds_suspend(struct platform_device *dev) 414 | { 415 | switch (clevo_kb_backlight_type) { 416 | case CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB: 417 | case CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB: 418 | clevo_evaluate_set_keyboard_status(0); 419 | break; 420 | default: 421 | break; 422 | } 423 | return 0; 424 | } 425 | EXPORT_SYMBOL(clevo_leds_suspend); 426 | 427 | int clevo_leds_resume(struct platform_device *dev) 428 | { 429 | switch (clevo_kb_backlight_type) { 430 | case CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB: 431 | case CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB: 432 | clevo_evaluate_set_keyboard_status(1); 433 | break; 434 | default: 435 | break; 436 | } 437 | return 0; 438 | } 439 | EXPORT_SYMBOL(clevo_leds_resume); 440 | 441 | int clevo_leds_remove(struct platform_device *dev) { 442 | if (leds_initialized) { 443 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 444 | led_classdev_unregister(&clevo_led_cdev); 445 | } 446 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 447 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[0]); 448 | } 449 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 450 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[0]); 451 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[1]); 452 | devm_led_classdev_multicolor_unregister(&dev->dev, &clevo_mcled_cdevs[2]); 453 | } 454 | } 455 | 456 | leds_initialized = false; 457 | 458 | return 0; 459 | } 460 | EXPORT_SYMBOL(clevo_leds_remove); 461 | 462 | enum clevo_kb_backlight_types clevo_leds_get_backlight_type(void) { 463 | return clevo_kb_backlight_type; 464 | } 465 | EXPORT_SYMBOL(clevo_leds_get_backlight_type); 466 | 467 | // TODO Don't reuse brightness_set as it is writing back the same brightness which could lead to race conditions. 468 | // Reimplement brightness_set instead without writing back brightness value like in uniwill_leds.h. 469 | void clevo_leds_restore_state_extern(void) { 470 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 471 | clevo_led_cdev.brightness_set(&clevo_led_cdev, clevo_led_cdev.brightness); 472 | } 473 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 474 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, clevo_mcled_cdevs[0].led_cdev.brightness); 475 | } 476 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 477 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, clevo_mcled_cdevs[0].led_cdev.brightness); 478 | clevo_mcled_cdevs[1].led_cdev.brightness_set(&clevo_mcled_cdevs[1].led_cdev, clevo_mcled_cdevs[1].led_cdev.brightness); 479 | clevo_mcled_cdevs[2].led_cdev.brightness_set(&clevo_mcled_cdevs[2].led_cdev, clevo_mcled_cdevs[2].led_cdev.brightness); 480 | } 481 | } 482 | EXPORT_SYMBOL(clevo_leds_restore_state_extern); 483 | 484 | void clevo_leds_notify_brightness_change_extern(void) { 485 | int status; 486 | u32 result; 487 | 488 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 489 | status = clevo_evaluate_method(CLEVO_CMD_GET_KB_WHITE_LEDS, 0, &result); 490 | pr_debug("Firmware set brightness: %u\n", result); 491 | clevo_led_cdev.brightness = result; 492 | led_classdev_notify_brightness_hw_changed(&clevo_led_cdev, result); 493 | } 494 | } 495 | EXPORT_SYMBOL(clevo_leds_notify_brightness_change_extern); 496 | 497 | // TODO Not used externaly, but only on init. Should not be exposed because it would require a correct 498 | // led_classdev_notify_brightness_hw_changed implementation when used outside of init. 499 | void clevo_leds_set_brightness_extern(enum led_brightness brightness) { 500 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 501 | clevo_led_cdev.brightness_set(&clevo_led_cdev, brightness); 502 | } 503 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 504 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, brightness); 505 | } 506 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 507 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, brightness); 508 | clevo_mcled_cdevs[1].led_cdev.brightness_set(&clevo_mcled_cdevs[1].led_cdev, brightness); 509 | clevo_mcled_cdevs[2].led_cdev.brightness_set(&clevo_mcled_cdevs[2].led_cdev, brightness); 510 | } 511 | } 512 | EXPORT_SYMBOL(clevo_leds_set_brightness_extern); 513 | 514 | // TODO Not used externaly, but only on init. Should not be exposed because it would require a correct 515 | // led_classdev_notify_brightness_hw_changed equivalent for color implementation when used outside of init. 516 | void clevo_leds_set_color_extern(u32 color) { 517 | if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 518 | clevo_mcled_cdevs[0].subled_info[0].intensity = (color >> 16) & 0xff; 519 | clevo_mcled_cdevs[0].subled_info[1].intensity = (color >> 8) & 0xff; 520 | clevo_mcled_cdevs[0].subled_info[2].intensity = color & 0xff; 521 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, clevo_mcled_cdevs[0].led_cdev.brightness); 522 | } 523 | else if (clevo_kb_backlight_type == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) { 524 | clevo_mcled_cdevs[0].subled_info[0].intensity = (color >> 16) & 0xff; 525 | clevo_mcled_cdevs[0].subled_info[1].intensity = (color >> 8) & 0xff; 526 | clevo_mcled_cdevs[0].subled_info[2].intensity = color & 0xff; 527 | clevo_mcled_cdevs[0].led_cdev.brightness_set(&clevo_mcled_cdevs[0].led_cdev, clevo_mcled_cdevs[0].led_cdev.brightness); 528 | clevo_mcled_cdevs[1].subled_info[0].intensity = (color >> 16) & 0xff; 529 | clevo_mcled_cdevs[1].subled_info[1].intensity = (color >> 8) & 0xff; 530 | clevo_mcled_cdevs[1].subled_info[2].intensity = color & 0xff; 531 | clevo_mcled_cdevs[1].led_cdev.brightness_set(&clevo_mcled_cdevs[1].led_cdev, clevo_mcled_cdevs[1].led_cdev.brightness); 532 | clevo_mcled_cdevs[2].subled_info[0].intensity = (color >> 16) & 0xff; 533 | clevo_mcled_cdevs[2].subled_info[1].intensity = (color >> 8) & 0xff; 534 | clevo_mcled_cdevs[2].subled_info[2].intensity = color & 0xff; 535 | clevo_mcled_cdevs[2].led_cdev.brightness_set(&clevo_mcled_cdevs[2].led_cdev, clevo_mcled_cdevs[2].led_cdev.brightness); 536 | } 537 | } 538 | EXPORT_SYMBOL(clevo_leds_set_color_extern); 539 | 540 | MODULE_LICENSE("GPL"); 541 | 542 | #endif // CLEVO_LEDS_H 543 | -------------------------------------------------------------------------------- /src/clevo_wmi.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "clevo_interfaces.h" 25 | 26 | static int clevo_wmi_evaluate(u32 wmi_method_id, u32 wmi_arg, union acpi_object **result) 27 | { 28 | struct acpi_buffer acpi_buffer_in = { (acpi_size)sizeof(wmi_arg), 29 | &wmi_arg }; 30 | struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL }; 31 | union acpi_object *acpi_result; 32 | acpi_status status_acpi; 33 | int return_status = 0; 34 | 35 | status_acpi = 36 | wmi_evaluate_method(CLEVO_WMI_METHOD_GUID, 0x00, wmi_method_id, 37 | &acpi_buffer_in, &acpi_buffer_out); 38 | 39 | if (unlikely(ACPI_FAILURE(status_acpi))) { 40 | pr_err("failed to evaluate wmi method\n"); 41 | return -EIO; 42 | } 43 | 44 | acpi_result = (union acpi_object *)acpi_buffer_out.pointer; 45 | if (!acpi_result) { 46 | pr_err("failed to evaluate WMI method\n"); 47 | return_status = -1; 48 | } 49 | else { 50 | if (!IS_ERR_OR_NULL(result)) { 51 | *result = acpi_result; 52 | } 53 | } 54 | 55 | return return_status; 56 | } 57 | 58 | int clevo_wmi_interface_method_call(u8 cmd, u32 arg, union acpi_object **result_value) 59 | { 60 | return clevo_wmi_evaluate(cmd, arg, result_value); 61 | } 62 | 63 | struct clevo_interface_t clevo_wmi_interface = { 64 | .string_id = CLEVO_INTERFACE_WMI_STRID, 65 | .method_call = clevo_wmi_interface_method_call, 66 | }; 67 | 68 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 69 | static int clevo_wmi_probe(struct wmi_device *wdev) 70 | #else 71 | static int clevo_wmi_probe(struct wmi_device *wdev, const void *dummy_context) 72 | #endif 73 | { 74 | int status; 75 | union acpi_object *out_obj; 76 | 77 | pr_debug("clevo_wmi driver probe\n"); 78 | 79 | if (!wmi_has_guid(CLEVO_WMI_EVENT_GUID)) { 80 | pr_debug("probe: Clevo event guid missing\n"); 81 | return -ENODEV; 82 | } 83 | 84 | if (!wmi_has_guid(CLEVO_WMI_METHOD_GUID)) { 85 | pr_debug("probe: Clevo method guid missing\n"); 86 | return -ENODEV; 87 | } 88 | 89 | // Since the WMI GUIDs aren't unique let's (at least) 90 | // check the return of some "known existing general" method 91 | status = clevo_wmi_evaluate(0x52, 0, &out_obj); 92 | if (status < 0) { 93 | pr_debug("probe: Clevo GUIDs present but method call failed\n"); 94 | return -ENODEV; 95 | } 96 | if (out_obj->type != ACPI_TYPE_INTEGER || (out_obj->type == ACPI_TYPE_INTEGER && (u32)out_obj->integer.value == 0xffffffff)) { 97 | pr_debug( 98 | "probe: Clevo GUIDs present but method returned unexpected value\n"); 99 | ACPI_FREE(out_obj); 100 | return -ENODEV; 101 | } 102 | ACPI_FREE(out_obj); 103 | 104 | // Add this interface 105 | clevo_keyboard_add_interface(&clevo_wmi_interface); 106 | 107 | pr_info("interface initialized\n"); 108 | 109 | return 0; 110 | } 111 | 112 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 113 | static int clevo_wmi_remove(struct wmi_device *wdev) 114 | #else 115 | static void clevo_wmi_remove(struct wmi_device *wdev) 116 | #endif 117 | { 118 | pr_debug("clevo_wmi driver remove\n"); 119 | clevo_keyboard_remove_interface(&clevo_wmi_interface); 120 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 121 | return 0; 122 | #endif 123 | } 124 | 125 | static void clevo_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) 126 | { 127 | u32 event_value; 128 | union acpi_object *out_obj; 129 | int status; 130 | 131 | status = clevo_wmi_evaluate(0x01, 0, &out_obj); 132 | if (!status) { 133 | if (out_obj->type == ACPI_TYPE_INTEGER) { 134 | event_value = (u32)out_obj->integer.value; 135 | } else { 136 | pr_err("return type not integer, use clevo_evaluate_method2\n"); 137 | } 138 | ACPI_FREE(out_obj); 139 | } 140 | pr_debug("clevo_wmi notify\n"); 141 | if (!IS_ERR_OR_NULL(clevo_wmi_interface.event_callb)) { 142 | // Execute registered callback 143 | clevo_wmi_interface.event_callb(event_value); 144 | } 145 | } 146 | 147 | static const struct wmi_device_id clevo_wmi_device_ids[] = { 148 | // Listing one should be enough, for a driver that "takes care of all anyways" 149 | // also prevents probe (and handling) per "device" 150 | { .guid_string = CLEVO_WMI_EVENT_GUID }, 151 | { } 152 | }; 153 | 154 | static struct wmi_driver clevo_wmi_driver = { 155 | .driver = { 156 | .name = CLEVO_INTERFACE_WMI_STRID, 157 | .owner = THIS_MODULE 158 | }, 159 | .id_table = clevo_wmi_device_ids, 160 | .probe = clevo_wmi_probe, 161 | .remove = clevo_wmi_remove, 162 | .notify = clevo_wmi_notify, 163 | }; 164 | 165 | module_wmi_driver(clevo_wmi_driver); 166 | 167 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 168 | MODULE_DESCRIPTION("Driver for Clevo WMI interface"); 169 | MODULE_VERSION("0.1.1"); 170 | MODULE_LICENSE("GPL"); 171 | 172 | MODULE_DEVICE_TABLE(wmi, clevo_wmi_device_ids); 173 | MODULE_ALIAS_CLEVO_WMI(); 174 | -------------------------------------------------------------------------------- /src/tuxedo_io/tuxedo_io.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019-2023 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-io. 5 | * 6 | * tuxedo-io is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "../clevo_interfaces.h" 32 | #include "../uniwill_interfaces.h" 33 | #include "tuxedo_io_ioctl.h" 34 | 35 | MODULE_DESCRIPTION("Hardware interface for TUXEDO laptops"); 36 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 37 | MODULE_VERSION("0.3.9"); 38 | MODULE_LICENSE("GPL"); 39 | 40 | MODULE_ALIAS_CLEVO_INTERFACES(); 41 | MODULE_ALIAS("wmi:" CLEVO_WMI_METHOD_GUID); 42 | MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BA); 43 | MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BB); 44 | MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC); 45 | 46 | // Initialized in module init, global for ioctl interface 47 | static u32 id_check_clevo; 48 | static u32 id_check_uniwill; 49 | 50 | static struct uniwill_device_features_t *uw_feats; 51 | 52 | /** 53 | * strstr version of dmi_match 54 | */ 55 | static bool __attribute__ ((unused)) dmi_string_in(enum dmi_field f, const char *str) 56 | { 57 | const char *info = dmi_get_system_info(f); 58 | 59 | if (info == NULL || str == NULL) 60 | return info == str; 61 | 62 | return strstr(info, str) != NULL; 63 | } 64 | 65 | static u32 clevo_identify(void) 66 | { 67 | return clevo_get_active_interface_id(NULL) == 0 ? 1 : 0; 68 | } 69 | 70 | /* 71 | * TDP boundary definitions per device 72 | */ 73 | static int tdp_min_ph4tux[] = { 0x05, 0x05, 0x00 }; 74 | static int tdp_max_ph4tux[] = { 0x26, 0x26, 0x00 }; 75 | 76 | static int tdp_min_ph4trx[] = { 0x05, 0x05, 0x00 }; 77 | static int tdp_max_ph4trx[] = { 0x32, 0x32, 0x00 }; 78 | 79 | static int tdp_min_ph4tqx[] = { 0x05, 0x05, 0x00 }; 80 | static int tdp_max_ph4tqx[] = { 0x32, 0x32, 0x00 }; 81 | 82 | static int tdp_min_ph4axx[] = { 0x05, 0x05, 0x00 }; 83 | static int tdp_max_ph4axx[] = { 0x2d, 0x3c, 0x00 }; 84 | 85 | static int tdp_min_phxpxx[] = { 0x05, 0x05, 0x05 }; 86 | static int tdp_max_phxpxx[] = { 0x2d, 0x3c, 0x6e }; 87 | 88 | static int tdp_min_pfxluxg[] = { 0x05, 0x05, 0x05 }; 89 | static int tdp_max_pfxluxg[] = { 0x23, 0x23, 0x28 }; 90 | 91 | static int tdp_min_gmxngxx[] = { 0x05, 0x05, 0x05 }; 92 | static int tdp_max_gmxngxx[] = { 0x50, 0x50, 0x5f }; 93 | 94 | static int tdp_min_gmxmgxx[] = { 0x05, 0x05, 0x05 }; 95 | static int tdp_max_gmxmgxx[] = { 0x78, 0x78, 0xc8 }; 96 | 97 | static int tdp_min_gmxtgxx[] = { 0x05, 0x05, 0x05 }; 98 | static int tdp_max_gmxtgxx[] = { 0x78, 0x78, 0xc8 }; 99 | 100 | static int tdp_min_gmxzgxx[] = { 0x05, 0x05, 0x05 }; 101 | static int tdp_max_gmxzgxx[] = { 0x50, 0x50, 0x5f }; 102 | 103 | static int tdp_min_gmxagxx[] = { 0x05, 0x05, 0x05 }; 104 | static int tdp_max_gmxagxx[] = { 0x78, 0x78, 0xd7 }; 105 | 106 | static int tdp_min_gmxrgxx[] = { 0x05, 0x05, 0x05 }; 107 | static int tdp_max_gmxrgxx[] = { 0x64, 0x64, 0x6e }; 108 | 109 | static int tdp_min_gmxpxxx[] = { 0x05, 0x05, 0x05 }; 110 | static int tdp_max_gmxpxxx[] = { 0x82, 0x82, 0xc8 }; 111 | 112 | static int tdp_min_gmxxgxx[] = { 0x05, 0x05, 0x05 }; 113 | static int tdp_max_gmxxgxx[] = { 0x50, 0x50, 0x64 }; 114 | 115 | static int *tdp_min_defs = NULL; 116 | static int *tdp_max_defs = NULL; 117 | 118 | void uw_id_tdp(void) 119 | { 120 | if (uw_feats->model == UW_MODEL_PH4TUX) { 121 | tdp_min_defs = tdp_min_ph4tux; 122 | tdp_max_defs = tdp_max_ph4tux; 123 | } else if (uw_feats->model == UW_MODEL_PH4TRX) { 124 | tdp_min_defs = tdp_min_ph4trx; 125 | tdp_max_defs = tdp_max_ph4trx; 126 | } else if (uw_feats->model == UW_MODEL_PH4TQF) { 127 | tdp_min_defs = tdp_min_ph4tqx; 128 | tdp_max_defs = tdp_max_ph4tqx; 129 | } else if (uw_feats->model == UW_MODEL_PH4AQF_ARX) { 130 | tdp_min_defs = tdp_min_ph4axx; 131 | tdp_max_defs = tdp_max_ph4axx; 132 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0) 133 | } else if (dmi_match(DMI_PRODUCT_SKU, "IBP1XI08MK1") || 134 | dmi_match(DMI_PRODUCT_SKU, "IBP1XI08MK2") || 135 | dmi_match(DMI_PRODUCT_SKU, "IBP14I08MK2") || 136 | dmi_match(DMI_PRODUCT_SKU, "IBP16I08MK2") || 137 | dmi_match(DMI_PRODUCT_SKU, "OMNIA08IMK2")) { 138 | tdp_min_defs = tdp_min_phxpxx; 139 | tdp_max_defs = tdp_max_phxpxx; 140 | } else if (dmi_match(DMI_PRODUCT_SKU, "PULSE1502")) { 141 | tdp_min_defs = tdp_min_pfxluxg; 142 | tdp_max_defs = tdp_max_pfxluxg; 143 | } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA02")) { 144 | tdp_min_defs = tdp_min_gmxngxx; 145 | tdp_max_defs = tdp_max_gmxngxx; 146 | } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI02")) { 147 | tdp_min_defs = tdp_min_gmxmgxx; 148 | tdp_max_defs = tdp_max_gmxmgxx; 149 | } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XI03") 150 | || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI03")) { 151 | tdp_min_defs = tdp_min_gmxtgxx; 152 | tdp_max_defs = tdp_max_gmxtgxx; 153 | } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA03") 154 | || dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XA03")) { 155 | tdp_min_defs = tdp_min_gmxzgxx; 156 | tdp_max_defs = tdp_max_gmxzgxx; 157 | } else if (dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI04")) { 158 | tdp_min_defs = tdp_min_gmxagxx; 159 | tdp_max_defs = tdp_max_gmxagxx; 160 | } else if (dmi_match(DMI_PRODUCT_SKU, "STEPOL1XA04")) { 161 | tdp_min_defs = tdp_min_gmxrgxx; 162 | tdp_max_defs = tdp_max_gmxrgxx; 163 | } else if (dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XI05")) { 164 | tdp_min_defs = tdp_min_gmxpxxx; 165 | tdp_max_defs = tdp_max_gmxpxxx; 166 | } else if (dmi_match(DMI_PRODUCT_SKU, "POLARIS1XA05")) { 167 | tdp_min_defs = tdp_min_gmxxgxx; 168 | tdp_max_defs = tdp_max_gmxxgxx; 169 | } else if (dmi_match(DMI_PRODUCT_SKU, "STELLARIS1XA05")) { 170 | tdp_min_defs = tdp_min_gmxxgxx; 171 | tdp_max_defs = tdp_max_gmxxgxx; 172 | #endif 173 | } else { 174 | tdp_min_defs = NULL; 175 | tdp_max_defs = NULL; 176 | } 177 | } 178 | 179 | static u32 uniwill_identify(void) 180 | { 181 | u32 result = uniwill_get_active_interface_id(NULL) == 0 ? 1 : 0; 182 | if (result) { 183 | uw_feats = uniwill_get_device_features(); 184 | uw_id_tdp(); 185 | } 186 | return result; 187 | } 188 | 189 | /*static int fop_open(struct inode *inode, struct file *file) 190 | { 191 | return 0; 192 | } 193 | 194 | static int fop_release(struct inode *inode, struct file *file) 195 | { 196 | return 0; 197 | }*/ 198 | 199 | static long clevo_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg) 200 | { 201 | u32 result = 0, status; 202 | u32 copy_result; 203 | u32 argument = (u32) arg; 204 | 205 | u32 clevo_arg; 206 | 207 | const char str_no_if[] = ""; 208 | char *str_clevo_if; 209 | 210 | switch (cmd) { 211 | case R_CL_HW_IF_STR: 212 | if (clevo_get_active_interface_id(&str_clevo_if) == 0) { 213 | copy_result = copy_to_user((char *) arg, str_clevo_if, strlen(str_clevo_if) + 1); 214 | } else { 215 | copy_result = copy_to_user((char *) arg, str_no_if, strlen(str_no_if) + 1); 216 | } 217 | break; 218 | case R_CL_FANINFO1: 219 | status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO1, 0, &result); 220 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 221 | break; 222 | case R_CL_FANINFO2: 223 | status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO2, 0, &result); 224 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 225 | break; 226 | case R_CL_FANINFO3: 227 | status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO3, 0, &result); 228 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 229 | break; 230 | /*case R_CL_FANINFO4: 231 | status = clevo_evaluate_method(CLEVO_CMD_GET_FANINFO4, 0); 232 | copy_to_user((int32_t *) arg, &result, sizeof(result)); 233 | break;*/ 234 | case R_CL_WEBCAM_SW: 235 | if (dmi_match(DMI_PRODUCT_SKU, "AURA14GEN3") || 236 | dmi_match(DMI_PRODUCT_SKU, "AURA15GEN3")) 237 | return -ENODEV; 238 | status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result); 239 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 240 | break; 241 | case R_CL_FLIGHTMODE_SW: 242 | status = clevo_evaluate_method(CLEVO_CMD_GET_FLIGHTMODE_SW, 0, &result); 243 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 244 | break; 245 | case R_CL_TOUCHPAD_SW: 246 | status = clevo_evaluate_method(CLEVO_CMD_GET_TOUCHPAD_SW, 0, &result); 247 | copy_result = copy_to_user((int32_t *) arg, &result, sizeof(result)); 248 | break; 249 | } 250 | 251 | switch (cmd) { 252 | case W_CL_FANSPEED: 253 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 254 | clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_VALUE, argument, &result); 255 | // Note: Delay needed to let hardware catch up with the written value. 256 | // No known ready flag. If the value is read too soon, the old value 257 | // will still be read out. 258 | // (Theoretically needed for other methods as well.) 259 | // Can it be lower? 50ms is too low 260 | msleep(100); 261 | break; 262 | case W_CL_FANAUTO: 263 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 264 | clevo_evaluate_method(CLEVO_CMD_SET_FANSPEED_AUTO, argument, &result); 265 | break; 266 | case W_CL_WEBCAM_SW: 267 | if (dmi_match(DMI_PRODUCT_SKU, "AURA14GEN3") || 268 | dmi_match(DMI_PRODUCT_SKU, "AURA15GEN3")) 269 | return -ENODEV; 270 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 271 | status = clevo_evaluate_method(CLEVO_CMD_GET_WEBCAM_SW, 0, &result); 272 | // Only set status if it isn't already the right value 273 | // (workaround for old and/or buggy WMI interfaces that toggle on write) 274 | if ((argument & 0x01) != (result & 0x01)) { 275 | clevo_evaluate_method(CLEVO_CMD_SET_WEBCAM_SW, argument, &result); 276 | } 277 | break; 278 | case W_CL_FLIGHTMODE_SW: 279 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 280 | clevo_evaluate_method(CLEVO_CMD_SET_FLIGHTMODE_SW, argument, &result); 281 | break; 282 | case W_CL_TOUCHPAD_SW: 283 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 284 | clevo_evaluate_method(CLEVO_CMD_SET_TOUCHPAD_SW, argument, &result); 285 | break; 286 | case W_CL_PERF_PROFILE: 287 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 288 | clevo_arg = (CLEVO_CMD_OPT_SUB_SET_PERF_PROF << 0x18) | (argument & 0xff); 289 | clevo_evaluate_method(CLEVO_CMD_OPT, clevo_arg, &result); 290 | break; 291 | } 292 | 293 | return 0; 294 | } 295 | 296 | static int set_full_fan_mode(bool enable) { 297 | u8 mode_data; 298 | 299 | uniwill_read_ec_ram(0x0751, &mode_data); 300 | 301 | if (enable && !(mode_data & 0x40)) { 302 | // If not "full fan mode" (i.e. 0x40 bit not set) switch to it (required for old fancontrol) 303 | return uniwill_write_ec_ram(0x0751, mode_data | 0x40); 304 | } 305 | else if (mode_data & 0x40){ 306 | // If "full fan mode" (i.e. 0x40 bit set) turn it off (required for new fancontrol) 307 | return uniwill_write_ec_ram(0x0751, mode_data & ~0x40); 308 | } 309 | 310 | return 0; 311 | } 312 | 313 | static bool fans_initialized = false; 314 | 315 | static int uw_init_fan(void) { 316 | int i; 317 | 318 | u16 addr_use_custom_fan_table_0 = 0x07c5; // use different tables for both fans (0x0f00-0x0f2f and 0x0f30-0x0f5f respectivly) 319 | u16 addr_use_custom_fan_table_1 = 0x07c6; // enable 0x0fxx fantables 320 | u8 offset_use_custom_fan_table_0 = 7; 321 | u8 offset_use_custom_fan_table_1 = 2; 322 | u8 value_use_custom_fan_table_0; 323 | u8 value_use_custom_fan_table_1; 324 | u16 addr_cpu_custom_fan_table_end_temp = 0x0f00; 325 | u16 addr_cpu_custom_fan_table_start_temp = 0x0f10; 326 | u16 addr_cpu_custom_fan_table_fan_speed = 0x0f20; 327 | u16 addr_gpu_custom_fan_table_end_temp = 0x0f30; 328 | u16 addr_gpu_custom_fan_table_start_temp = 0x0f40; 329 | u16 addr_gpu_custom_fan_table_fan_speed = 0x0f50; 330 | 331 | if (!fans_initialized && uw_feats->uniwill_has_universal_ec_fan_control) { 332 | set_full_fan_mode(false); 333 | 334 | uniwill_read_ec_ram(addr_use_custom_fan_table_0, &value_use_custom_fan_table_0); 335 | if (!((value_use_custom_fan_table_0 >> offset_use_custom_fan_table_0) & 1)) { 336 | uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_0, value_use_custom_fan_table_0 + (1 << offset_use_custom_fan_table_0), 3); 337 | } 338 | 339 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_end_temp, 0xff, 3); 340 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_start_temp, 0x00, 3); 341 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_fan_speed, 0x00, 3); 342 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_end_temp, 0xff, 3); 343 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_start_temp, 0x00, 3); 344 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_fan_speed, 0x00, 3); 345 | for (i = 0x1; i <= 0xf; ++i) { 346 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_end_temp + i, 0xff, 3); 347 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_start_temp + i, 0xff, 3); 348 | uniwill_write_ec_ram_with_retry(addr_cpu_custom_fan_table_fan_speed + i, 0x00, 3); 349 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_end_temp + i, 0xff, 3); 350 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_start_temp + i, 0xff, 3); 351 | uniwill_write_ec_ram_with_retry(addr_gpu_custom_fan_table_fan_speed + i, 0x00, 3); 352 | } 353 | 354 | uniwill_read_ec_ram(addr_use_custom_fan_table_1, &value_use_custom_fan_table_1); 355 | if (!((value_use_custom_fan_table_1 >> offset_use_custom_fan_table_1) & 1)) { 356 | uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_1, value_use_custom_fan_table_1 + (1 << offset_use_custom_fan_table_1), 3); 357 | } 358 | } 359 | 360 | fans_initialized = true; 361 | 362 | return 0; 363 | } 364 | 365 | static u32 uw_set_fan(u32 fan_index, u8 fan_speed) 366 | { 367 | u32 i; 368 | u8 mode_data; 369 | u16 addr_fan0 = 0x1804; 370 | u16 addr_fan1 = 0x1809; 371 | u16 addr_for_fan; 372 | 373 | u16 addr_cpu_custom_fan_table_fan_speed = 0x0f20; 374 | u16 addr_gpu_custom_fan_table_fan_speed = 0x0f50; 375 | 376 | if (uw_feats->uniwill_has_universal_ec_fan_control) { 377 | uw_init_fan(); 378 | 379 | if (fan_index == 0) 380 | addr_for_fan = addr_cpu_custom_fan_table_fan_speed; 381 | else if (fan_index == 1) 382 | addr_for_fan = addr_gpu_custom_fan_table_fan_speed; 383 | else 384 | return -EINVAL; 385 | 386 | if (fan_speed == 0) { 387 | // Avoid hard coded EC behaviour: Setting fan speed = 0x00 spins the fan up 388 | // to 0x3c (30%) for 3 minutes before going to 0x00. Setting fan speed = 1 389 | // also causes the fan to stop since on 2020 or later TF devices the 390 | // microcontroller in the fan itself is intelligent enough to not try to 391 | // start up the motor when the speed is to slow. Older devices don't use 392 | // this fan controll anyway, but the else case below. 393 | fan_speed = 1; 394 | } 395 | 396 | uniwill_write_ec_ram(addr_for_fan, fan_speed & 0xff); 397 | } 398 | else { // old workaround using full fan mode 399 | if (fan_index == 0) 400 | addr_for_fan = addr_fan0; 401 | else if (fan_index == 1) 402 | addr_for_fan = addr_fan1; 403 | else 404 | return -EINVAL; 405 | 406 | // Check current mode 407 | uniwill_read_ec_ram(0x0751, &mode_data); 408 | if (!(mode_data & 0x40)) { 409 | // If not "full fan mode" (i.e. 0x40 bit set) switch to it (required for fancontrol) 410 | set_full_fan_mode(true); 411 | // Attempt to write both fans as quick as possible before complete ramp-up 412 | pr_debug("prevent ramp-up start\n"); 413 | for (i = 0; i < 10; ++i) { 414 | uniwill_write_ec_ram(addr_fan0, fan_speed & 0xff); 415 | uniwill_write_ec_ram(addr_fan1, fan_speed & 0xff); 416 | msleep(10); 417 | } 418 | pr_debug("prevent ramp-up done\n"); 419 | } else { 420 | // Otherwise just set the chosen fan 421 | uniwill_write_ec_ram(addr_for_fan, fan_speed & 0xff); 422 | } 423 | } 424 | 425 | return 0; 426 | } 427 | 428 | static u32 uw_set_fan_auto(void) 429 | { 430 | u8 mode_data; 431 | 432 | if (uw_feats->uniwill_has_universal_ec_fan_control) { 433 | u16 addr_use_custom_fan_table_0 = 0x07c5; // use different tables for both fans (0x0f00-0x0f2f and 0x0f30-0x0f5f respectivly) 434 | u16 addr_use_custom_fan_table_1 = 0x07c6; // enable 0x0fxx fantables 435 | u8 offset_use_custom_fan_table_0 = 7; 436 | u8 offset_use_custom_fan_table_1 = 2; 437 | u8 value_use_custom_fan_table_0; 438 | u8 value_use_custom_fan_table_1; 439 | uniwill_read_ec_ram(addr_use_custom_fan_table_1, &value_use_custom_fan_table_1); 440 | if ((value_use_custom_fan_table_1 >> offset_use_custom_fan_table_1) & 1) { 441 | uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_1, value_use_custom_fan_table_1 - (1 << offset_use_custom_fan_table_1), 3); 442 | } 443 | uniwill_read_ec_ram(addr_use_custom_fan_table_0, &value_use_custom_fan_table_0); 444 | if ((value_use_custom_fan_table_0 >> offset_use_custom_fan_table_0) & 1) { 445 | uniwill_write_ec_ram_with_retry(addr_use_custom_fan_table_0, value_use_custom_fan_table_0 - (1 << offset_use_custom_fan_table_0), 3); 446 | } 447 | fans_initialized = false; 448 | } 449 | else { 450 | // Get current mode 451 | uniwill_read_ec_ram(0x0751, &mode_data); 452 | // Switch off "full fan mode" (i.e. unset 0x40 bit) 453 | uniwill_write_ec_ram(0x0751, mode_data & 0xbf); 454 | } 455 | 456 | return 0; 457 | } 458 | 459 | static int uw_get_tdp_min(u8 tdp_index) 460 | { 461 | if (tdp_index > 2) 462 | return -EINVAL; 463 | 464 | if (tdp_min_defs == NULL) 465 | return -ENODEV; 466 | 467 | if (tdp_min_defs[tdp_index] <= 0) { 468 | return -ENODEV; 469 | } 470 | 471 | return tdp_min_defs[tdp_index]; 472 | } 473 | 474 | static int uw_get_tdp_max(u8 tdp_index) 475 | { 476 | if (tdp_index > 2) 477 | return -EINVAL; 478 | 479 | if (tdp_max_defs == NULL) 480 | return -ENODEV; 481 | 482 | if (tdp_max_defs[tdp_index] <= 0) { 483 | return -ENODEV; 484 | } 485 | 486 | return tdp_max_defs[tdp_index]; 487 | } 488 | 489 | static int uw_get_tdp(u8 tdp_index) 490 | { 491 | u8 tdp_data; 492 | u16 tdp_base_addr = 0x0783; 493 | u16 tdp_current_addr = tdp_base_addr + tdp_index; 494 | int status; 495 | 496 | // Use min tdp to detect support for chosen tdp parameter 497 | int min_tdp_status = uw_get_tdp_min(tdp_index); 498 | if (min_tdp_status < 0) 499 | return min_tdp_status; 500 | 501 | status = uniwill_read_ec_ram(tdp_current_addr, &tdp_data); 502 | if (status < 0) 503 | return status; 504 | 505 | return tdp_data; 506 | } 507 | 508 | static int uw_set_tdp(u8 tdp_index, u8 tdp_data) 509 | { 510 | int tdp_min, tdp_max; 511 | u16 tdp_base_addr = 0x0783; 512 | u16 tdp_current_addr = tdp_base_addr + tdp_index; 513 | 514 | // Use min tdp to detect support for chosen tdp parameter 515 | int min_tdp_status = uw_get_tdp_min(tdp_index); 516 | if (min_tdp_status < 0) 517 | return min_tdp_status; 518 | 519 | tdp_min = uw_get_tdp_min(tdp_index); 520 | tdp_max = uw_get_tdp_max(tdp_index); 521 | if (tdp_data < tdp_min || tdp_data > tdp_max) 522 | return -EINVAL; 523 | 524 | uniwill_write_ec_ram(tdp_current_addr, tdp_data); 525 | 526 | return 0; 527 | } 528 | 529 | /** 530 | * Set profile 1-3 to 0xa0, 0x00 or 0x10 depending on 531 | * device support. 532 | */ 533 | static u32 uw_set_performance_profile_v1(u8 profile_index) 534 | { 535 | u8 current_value = 0x00, next_value; 536 | u8 clear_bits = 0xa0 | 0x10; 537 | u32 result; 538 | result = uniwill_read_ec_ram(0x0751, ¤t_value); 539 | if (result >= 0) { 540 | next_value = current_value & ~clear_bits; 541 | switch (profile_index) { 542 | case 0x01: 543 | next_value |= 0xa0; 544 | break; 545 | case 0x02: 546 | next_value |= 0x00; 547 | break; 548 | case 0x03: 549 | next_value |= 0x10; 550 | break; 551 | default: 552 | result = -EINVAL; 553 | break; 554 | } 555 | 556 | if (result != -EINVAL) { 557 | result = uniwill_write_ec_ram(0x0751, next_value); 558 | } 559 | } 560 | 561 | return result; 562 | } 563 | 564 | static long uniwill_ioctl_interface(struct file *file, unsigned int cmd, unsigned long arg) 565 | { 566 | u32 result = 0; 567 | u32 copy_result; 568 | u32 argument; 569 | u8 byte_data; 570 | const char str_no_if[] = ""; 571 | char *str_uniwill_if; 572 | 573 | #ifdef DEBUG 574 | union uw_ec_read_return reg_read_return; 575 | union uw_ec_write_return reg_write_return; 576 | u32 uw_arg[10]; 577 | u32 uw_result[10]; 578 | int i; 579 | for (i = 0; i < 10; ++i) { 580 | uw_result[i] = 0xdeadbeef; 581 | } 582 | #endif 583 | 584 | switch (cmd) { 585 | case R_UW_HW_IF_STR: 586 | if (uniwill_get_active_interface_id(&str_uniwill_if) == 0) { 587 | copy_result = copy_to_user((char *) arg, str_uniwill_if, strlen(str_uniwill_if) + 1); 588 | } else { 589 | copy_result = copy_to_user((char *) arg, str_no_if, strlen(str_no_if) + 1); 590 | } 591 | break; 592 | case R_UW_MODEL_ID: 593 | result = uw_feats->model; 594 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 595 | break; 596 | case R_UW_FANSPEED: 597 | uniwill_read_ec_ram(0x1804, &byte_data); 598 | result = byte_data; 599 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 600 | break; 601 | case R_UW_FANSPEED2: 602 | uniwill_read_ec_ram(0x1809, &byte_data); 603 | result = byte_data; 604 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 605 | break; 606 | case R_UW_FAN_TEMP: 607 | uniwill_read_ec_ram(0x043e, &byte_data); 608 | result = byte_data; 609 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 610 | break; 611 | case R_UW_FAN_TEMP2: 612 | uniwill_read_ec_ram(0x044f, &byte_data); 613 | result = byte_data; 614 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 615 | break; 616 | case R_UW_MODE: 617 | uniwill_read_ec_ram(0x0751, &byte_data); 618 | result = byte_data; 619 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 620 | break; 621 | case R_UW_MODE_ENABLE: 622 | uniwill_read_ec_ram(0x0741, &byte_data); 623 | result = byte_data; 624 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 625 | break; 626 | case R_UW_FANS_OFF_AVAILABLE: 627 | /*result = uw_feats->uniwill_has_universal_ec_fan_control ? 1 : 0; 628 | if (result == 1) { 629 | result = 0; 630 | } 631 | else if (result == 0) { 632 | result = 1; 633 | }*/ 634 | result = 1; 635 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 636 | break; 637 | case R_UW_FANS_MIN_SPEED: 638 | /*result = uw_feats->uniwill_has_universal_ec_fan_control? 1 : 0; 639 | if (result == 1) { 640 | result = 20; 641 | } 642 | else if (result == 0) { 643 | result = 0; 644 | }*/ 645 | result = 20; 646 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 647 | break; 648 | case R_UW_TDP0: 649 | result = uw_get_tdp(0); 650 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 651 | break; 652 | case R_UW_TDP1: 653 | result = uw_get_tdp(1); 654 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 655 | break; 656 | case R_UW_TDP2: 657 | result = uw_get_tdp(2); 658 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 659 | break; 660 | case R_UW_TDP0_MIN: 661 | result = uw_get_tdp_min(0); 662 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 663 | break; 664 | case R_UW_TDP1_MIN: 665 | result = uw_get_tdp_min(1); 666 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 667 | break; 668 | case R_UW_TDP2_MIN: 669 | result = uw_get_tdp_min(2); 670 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 671 | break; 672 | case R_UW_TDP0_MAX: 673 | result = uw_get_tdp_max(0); 674 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 675 | break; 676 | case R_UW_TDP1_MAX: 677 | result = uw_get_tdp_max(1); 678 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 679 | break; 680 | case R_UW_TDP2_MAX: 681 | result = uw_get_tdp_max(2); 682 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 683 | break; 684 | case R_UW_PROFS_AVAILABLE: 685 | result = 0; 686 | if (uw_feats->uniwill_profile_v1_two_profs) 687 | result = 2; 688 | else if (uw_feats->uniwill_profile_v1_three_profs || uw_feats->uniwill_profile_v1_three_profs_leds_only) 689 | result = 3; 690 | copy_result = copy_to_user((void *) arg, &result, sizeof(result)); 691 | break; 692 | #ifdef DEBUG 693 | case R_TF_BC: 694 | copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg)); 695 | reg_read_return.dword = 0; 696 | result = uniwill_read_ec_ram((uw_arg[1] << 8) | uw_arg[0], ®_read_return.bytes.data_low); 697 | copy_result = copy_to_user((void *) arg, ®_read_return.dword, sizeof(reg_read_return.dword)); 698 | // pr_info("R_TF_BC args [%0#2x, %0#2x, %0#2x, %0#2x]\n", uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3]); 699 | /*if (uniwill_ec_direct) { 700 | result = uw_ec_read_addr_direct(uw_arg[0], uw_arg[1], ®_read_return); 701 | copy_result = copy_to_user((void *) arg, ®_read_return.dword, sizeof(reg_read_return.dword)); 702 | } else { 703 | result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 1, uw_result); 704 | copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result)); 705 | }*/ 706 | break; 707 | #endif 708 | } 709 | 710 | switch (cmd) { 711 | case W_UW_FANSPEED: 712 | // Get fan speed argument 713 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 714 | uw_set_fan(0, argument); 715 | break; 716 | case W_UW_FANSPEED2: 717 | // Get fan speed argument 718 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 719 | uw_set_fan(1, argument); 720 | break; 721 | case W_UW_MODE: 722 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 723 | uniwill_write_ec_ram(0x0751, argument & 0xff); 724 | break; 725 | case W_UW_MODE_ENABLE: 726 | // Note: Is for the moment set and cleared on init/exit of module (uniwill mode) 727 | /* 728 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 729 | uniwill_write_ec_ram(0x0741, argument & 0x01); 730 | */ 731 | break; 732 | case W_UW_FANAUTO: 733 | uw_set_fan_auto(); 734 | break; 735 | case W_UW_TDP0: 736 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 737 | uw_set_tdp(0, argument); 738 | break; 739 | case W_UW_TDP1: 740 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 741 | uw_set_tdp(1, argument); 742 | break; 743 | case W_UW_TDP2: 744 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 745 | uw_set_tdp(2, argument); 746 | break; 747 | case W_UW_PERF_PROF: 748 | copy_result = copy_from_user(&argument, (int32_t *) arg, sizeof(argument)); 749 | uw_set_performance_profile_v1(argument); 750 | break; 751 | #ifdef DEBUG 752 | case W_TF_BC: 753 | reg_write_return.dword = 0; 754 | copy_result = copy_from_user(&uw_arg, (void *) arg, sizeof(uw_arg)); 755 | uniwill_write_ec_ram((uw_arg[1] << 8) | uw_arg[0], uw_arg[2]); 756 | copy_result = copy_to_user((void *) arg, ®_write_return.dword, sizeof(reg_write_return.dword)); 757 | /*if (uniwill_ec_direct) { 758 | result = uw_ec_write_addr_direct(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], ®_write_return); 759 | copy_result = copy_to_user((void *) arg, ®_write_return.dword, sizeof(reg_write_return.dword)); 760 | } else { 761 | result = uw_wmi_ec_evaluate(uw_arg[0], uw_arg[1], uw_arg[2], uw_arg[3], 0, uw_result); 762 | copy_result = copy_to_user((void *) arg, &uw_result, sizeof(uw_result)); 763 | reg_write_return.dword = uw_result[0]; 764 | }*/ 765 | /*pr_info("data_high %0#2x\n", reg_write_return.bytes.data_high); 766 | pr_info("data_low %0#2x\n", reg_write_return.bytes.data_low); 767 | pr_info("addr_high %0#2x\n", reg_write_return.bytes.addr_high); 768 | pr_info("addr_low %0#2x\n", reg_write_return.bytes.addr_low);*/ 769 | break; 770 | #endif 771 | } 772 | 773 | return 0; 774 | } 775 | 776 | static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 777 | { 778 | u32 status; 779 | // u32 result = 0; 780 | u32 copy_result; 781 | 782 | const char *module_version = THIS_MODULE->version; 783 | switch (cmd) { 784 | case R_MOD_VERSION: 785 | copy_result = copy_to_user((char *) arg, module_version, strlen(module_version) + 1); 786 | break; 787 | // Hardware id checks, 1 = positive, 0 = negative 788 | case R_HWCHECK_CL: 789 | id_check_clevo = clevo_identify(); 790 | copy_result = copy_to_user((void *) arg, (void *) &id_check_clevo, sizeof(id_check_clevo)); 791 | break; 792 | case R_HWCHECK_UW: 793 | id_check_uniwill = uniwill_identify(); 794 | copy_result = copy_to_user((void *) arg, (void *) &id_check_uniwill, sizeof(id_check_uniwill)); 795 | break; 796 | } 797 | 798 | status = clevo_ioctl_interface(file, cmd, arg); 799 | if (status != 0) return status; 800 | status = uniwill_ioctl_interface(file, cmd, arg); 801 | if (status != 0) return status; 802 | 803 | return 0; 804 | } 805 | 806 | static struct file_operations fops_dev = { 807 | .owner = THIS_MODULE, 808 | .unlocked_ioctl = fop_ioctl 809 | // .open = fop_open, 810 | // .release = fop_release 811 | }; 812 | 813 | struct class *tuxedo_io_device_class; 814 | dev_t tuxedo_io_device_handle; 815 | 816 | static struct cdev tuxedo_io_cdev; 817 | 818 | static int __init tuxedo_io_init(void) 819 | { 820 | int err; 821 | 822 | // Hardware identification 823 | id_check_clevo = clevo_identify(); 824 | id_check_uniwill = uniwill_identify(); 825 | 826 | #ifdef DEBUG 827 | pr_debug("DEBUG is defined\n"); 828 | 829 | if (id_check_clevo == 0 && id_check_uniwill == 0) { 830 | pr_debug("No matching hardware found on module load\n"); 831 | } 832 | #endif 833 | 834 | err = alloc_chrdev_region(&tuxedo_io_device_handle, 0, 1, "tuxedo_io_cdev"); 835 | if (err != 0) { 836 | pr_err("Failed to allocate chrdev region\n"); 837 | return err; 838 | } 839 | cdev_init(&tuxedo_io_cdev, &fops_dev); 840 | err = (cdev_add(&tuxedo_io_cdev, tuxedo_io_device_handle, 1)); 841 | if (err < 0) { 842 | pr_err("Failed to add cdev\n"); 843 | unregister_chrdev_region(tuxedo_io_device_handle, 1); 844 | } 845 | 846 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) 847 | tuxedo_io_device_class = class_create(THIS_MODULE, "tuxedo_io"); 848 | #else 849 | tuxedo_io_device_class = class_create("tuxedo_io"); 850 | #endif 851 | 852 | device_create(tuxedo_io_device_class, NULL, tuxedo_io_device_handle, NULL, "tuxedo_io"); 853 | pr_debug("Module init successful\n"); 854 | 855 | return 0; 856 | } 857 | 858 | static void __exit tuxedo_io_exit(void) 859 | { 860 | device_destroy(tuxedo_io_device_class, tuxedo_io_device_handle); 861 | class_destroy(tuxedo_io_device_class); 862 | cdev_del(&tuxedo_io_cdev); 863 | unregister_chrdev_region(tuxedo_io_device_handle, 1); 864 | pr_debug("Module exit\n"); 865 | } 866 | 867 | module_init(tuxedo_io_init); 868 | module_exit(tuxedo_io_exit); 869 | -------------------------------------------------------------------------------- /src/tuxedo_io/tuxedo_io_ioctl.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019-2022 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-io. 5 | * 6 | * tuxedo-io is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #ifndef TUXEDO_IO_IOCTL_H 20 | #define TUXEDO_IO_IOCTL_H 21 | 22 | #define IOCTL_MAGIC 0xEC 23 | 24 | #define MAGIC_READ_CL IOCTL_MAGIC + 1 25 | #define MAGIC_WRITE_CL IOCTL_MAGIC + 2 26 | 27 | #define MAGIC_READ_UW IOCTL_MAGIC + 3 28 | #define MAGIC_WRITE_UW IOCTL_MAGIC + 4 29 | 30 | 31 | // General 32 | #define R_MOD_VERSION _IOR(IOCTL_MAGIC, 0x00, char*) 33 | 34 | #define R_HWCHECK_CL _IOR(IOCTL_MAGIC, 0x05, int32_t*) 35 | #define R_HWCHECK_UW _IOR(IOCTL_MAGIC, 0x06, int32_t*) 36 | 37 | /** 38 | * Clevo interface 39 | */ 40 | 41 | // Read 42 | #define R_CL_HW_IF_STR _IOR(MAGIC_READ_CL, 0x00, char*) 43 | #define R_CL_FANINFO1 _IOR(MAGIC_READ_CL, 0x10, int32_t*) 44 | #define R_CL_FANINFO2 _IOR(MAGIC_READ_CL, 0x11, int32_t*) 45 | #define R_CL_FANINFO3 _IOR(MAGIC_READ_CL, 0x12, int32_t*) 46 | // #define R_FANINFO4 _IOR(MAGIC_READ_CL, 0x04, int32_t*) 47 | 48 | #define R_CL_WEBCAM_SW _IOR(MAGIC_READ_CL, 0x13, int32_t*) 49 | #define R_CL_FLIGHTMODE_SW _IOR(MAGIC_READ_CL, 0x14, int32_t*) 50 | #define R_CL_TOUCHPAD_SW _IOR(MAGIC_READ_CL, 0x15, int32_t*) 51 | 52 | #ifdef DEBUG 53 | #define R_TF_BC _IOW(MAGIC_READ_CL, 0x91, uint32_t*) 54 | #endif 55 | 56 | // Write 57 | #define W_CL_FANSPEED _IOW(MAGIC_WRITE_CL, 0x10, int32_t*) 58 | #define W_CL_FANAUTO _IOW(MAGIC_WRITE_CL, 0x11, int32_t*) 59 | 60 | #define W_CL_WEBCAM_SW _IOW(MAGIC_WRITE_CL, 0x12, int32_t*) 61 | #define W_CL_FLIGHTMODE_SW _IOW(MAGIC_WRITE_CL, 0x13, int32_t*) 62 | #define W_CL_TOUCHPAD_SW _IOW(MAGIC_WRITE_CL, 0x14, int32_t*) 63 | #define W_CL_PERF_PROFILE _IOW(MAGIC_WRITE_CL, 0x15, int32_t*) 64 | 65 | #ifdef DEBUG 66 | #define W_TF_BC _IOW(MAGIC_WRITE_CL, 0x91, uint32_t*) 67 | #endif 68 | 69 | /** 70 | * Uniwill interface 71 | */ 72 | 73 | // Read 74 | #define R_UW_HW_IF_STR _IOR(MAGIC_READ_UW, 0x00, char*) 75 | #define R_UW_MODEL_ID _IOR(MAGIC_READ_UW, 0x01, int32_t*) 76 | #define R_UW_FANSPEED _IOR(MAGIC_READ_UW, 0x10, int32_t*) 77 | #define R_UW_FANSPEED2 _IOR(MAGIC_READ_UW, 0x11, int32_t*) 78 | #define R_UW_FAN_TEMP _IOR(MAGIC_READ_UW, 0x12, int32_t*) 79 | #define R_UW_FAN_TEMP2 _IOR(MAGIC_READ_UW, 0x13, int32_t*) 80 | 81 | #define R_UW_MODE _IOR(MAGIC_READ_UW, 0x14, int32_t*) 82 | #define R_UW_MODE_ENABLE _IOR(MAGIC_READ_UW, 0x15, int32_t*) 83 | #define R_UW_FANS_OFF_AVAILABLE _IOR(MAGIC_READ_UW, 0x16, int32_t*) 84 | #define R_UW_FANS_MIN_SPEED _IOR(MAGIC_READ_UW, 0x17, int32_t*) 85 | 86 | #define R_UW_TDP0 _IOR(MAGIC_READ_UW, 0x18, int32_t*) 87 | #define R_UW_TDP1 _IOR(MAGIC_READ_UW, 0x19, int32_t*) 88 | #define R_UW_TDP2 _IOR(MAGIC_READ_UW, 0x1a, int32_t*) 89 | #define R_UW_TDP0_MIN _IOR(MAGIC_READ_UW, 0x1b, int32_t*) 90 | #define R_UW_TDP1_MIN _IOR(MAGIC_READ_UW, 0x1c, int32_t*) 91 | #define R_UW_TDP2_MIN _IOR(MAGIC_READ_UW, 0x1d, int32_t*) 92 | #define R_UW_TDP0_MAX _IOR(MAGIC_READ_UW, 0x1e, int32_t*) 93 | #define R_UW_TDP1_MAX _IOR(MAGIC_READ_UW, 0x1f, int32_t*) 94 | #define R_UW_TDP2_MAX _IOR(MAGIC_READ_UW, 0x20, int32_t*) 95 | 96 | #define R_UW_PROFS_AVAILABLE _IOR(MAGIC_READ_UW, 0x21, int32_t*) 97 | 98 | // Write 99 | #define W_UW_FANSPEED _IOW(MAGIC_WRITE_UW, 0x10, int32_t*) 100 | #define W_UW_FANSPEED2 _IOW(MAGIC_WRITE_UW, 0x11, int32_t*) 101 | #define W_UW_MODE _IOW(MAGIC_WRITE_UW, 0x12, int32_t*) 102 | #define W_UW_MODE_ENABLE _IOW(MAGIC_WRITE_UW, 0x13, int32_t*) 103 | #define W_UW_FANAUTO _IO(MAGIC_WRITE_UW, 0x14) // undo all previous calls of W_UW_FANSPEED and W_UW_FANSPEED2 104 | 105 | #define W_UW_TDP0 _IOW(MAGIC_WRITE_UW, 0x15, int32_t*) 106 | #define W_UW_TDP1 _IOW(MAGIC_WRITE_UW, 0x16, int32_t*) 107 | #define W_UW_TDP2 _IOW(MAGIC_WRITE_UW, 0x17, int32_t*) 108 | 109 | #define W_UW_PERF_PROF _IOW(MAGIC_WRITE_UW, 0x18, int32_t*) 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /src/tuxedo_keyboard.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #define pr_fmt(fmt) "tuxedo_keyboard" ": " fmt 20 | 21 | #include "tuxedo_keyboard_common.h" 22 | #include "clevo_keyboard.h" 23 | #include "uniwill_keyboard.h" 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 30 | MODULE_DESCRIPTION("TUXEDO Computers keyboard & keyboard backlight Driver"); 31 | MODULE_LICENSE("GPL"); 32 | MODULE_VERSION("3.2.14"); 33 | 34 | static DEFINE_MUTEX(tuxedo_keyboard_init_driver_lock); 35 | 36 | // static struct tuxedo_keyboard_driver *driver_list[] = { }; 37 | 38 | static int tuxedo_input_init(const struct key_entry key_map[]) 39 | { 40 | int err; 41 | 42 | tuxedo_input_device = input_allocate_device(); 43 | if (unlikely(!tuxedo_input_device)) { 44 | TUXEDO_ERROR("Error allocating input device\n"); 45 | return -ENOMEM; 46 | } 47 | 48 | tuxedo_input_device->name = "TUXEDO Keyboard"; 49 | tuxedo_input_device->phys = DRIVER_NAME "/input0"; 50 | tuxedo_input_device->id.bustype = BUS_HOST; 51 | tuxedo_input_device->dev.parent = &tuxedo_platform_device->dev; 52 | 53 | if (key_map != NULL) { 54 | err = sparse_keymap_setup(tuxedo_input_device, key_map, NULL); 55 | if (err) { 56 | TUXEDO_ERROR("Failed to setup sparse keymap\n"); 57 | goto err_free_input_device; 58 | } 59 | } 60 | 61 | err = input_register_device(tuxedo_input_device); 62 | if (unlikely(err)) { 63 | TUXEDO_ERROR("Error registering input device\n"); 64 | goto err_free_input_device; 65 | } 66 | 67 | return 0; 68 | 69 | err_free_input_device: 70 | input_free_device(tuxedo_input_device); 71 | 72 | return err; 73 | } 74 | 75 | struct platform_device *tuxedo_keyboard_init_driver(struct tuxedo_keyboard_driver *tk_driver) 76 | { 77 | int err; 78 | struct platform_device *new_platform_device = NULL; 79 | 80 | TUXEDO_DEBUG("init driver start\n"); 81 | 82 | mutex_lock(&tuxedo_keyboard_init_driver_lock); 83 | 84 | if (!IS_ERR_OR_NULL(tuxedo_platform_device)) { 85 | // If already initialized, don't proceed 86 | TUXEDO_DEBUG("platform device already initialized\n"); 87 | goto init_driver_exit; 88 | } else { 89 | // Otherwise, attempt to initialize structures 90 | TUXEDO_DEBUG("create platform bundle\n"); 91 | new_platform_device = platform_create_bundle( 92 | tk_driver->platform_driver, tk_driver->probe, NULL, 0, NULL, 0); 93 | 94 | tuxedo_platform_device = new_platform_device; 95 | 96 | if (IS_ERR_OR_NULL(tuxedo_platform_device)) { 97 | // Normal case probe failed, no init 98 | goto init_driver_exit; 99 | } 100 | 101 | TUXEDO_DEBUG("initialize input device\n"); 102 | if (tk_driver->key_map != NULL) { 103 | err = tuxedo_input_init(tk_driver->key_map); 104 | if (unlikely(err)) { 105 | TUXEDO_ERROR("Could not register input device\n"); 106 | tk_driver->input_device = NULL; 107 | } else { 108 | TUXEDO_DEBUG("input device registered\n"); 109 | tk_driver->input_device = tuxedo_input_device; 110 | } 111 | } 112 | 113 | current_driver = tk_driver; 114 | } 115 | 116 | init_driver_exit: 117 | mutex_unlock(&tuxedo_keyboard_init_driver_lock); 118 | return new_platform_device; 119 | } 120 | EXPORT_SYMBOL(tuxedo_keyboard_init_driver); 121 | 122 | static void __exit tuxedo_input_exit(void) 123 | { 124 | if (unlikely(!tuxedo_input_device)) { 125 | return; 126 | } 127 | 128 | input_unregister_device(tuxedo_input_device); 129 | { 130 | tuxedo_input_device = NULL; 131 | } 132 | } 133 | 134 | void tuxedo_keyboard_remove_driver(struct tuxedo_keyboard_driver *tk_driver) 135 | { 136 | bool specified_driver_differ_from_used = 137 | tk_driver != NULL && 138 | ( 139 | strcmp( 140 | tk_driver->platform_driver->driver.name, 141 | current_driver->platform_driver->driver.name 142 | ) != 0 143 | ); 144 | 145 | if (specified_driver_differ_from_used) 146 | return; 147 | 148 | TUXEDO_DEBUG("tuxedo_input_exit()\n"); 149 | tuxedo_input_exit(); 150 | TUXEDO_DEBUG("platform_device_unregister()\n"); 151 | if (!IS_ERR_OR_NULL(tuxedo_platform_device)) { 152 | platform_device_unregister(tuxedo_platform_device); 153 | tuxedo_platform_device = NULL; 154 | } 155 | TUXEDO_DEBUG("platform_driver_unregister()\n"); 156 | if (!IS_ERR_OR_NULL(current_driver)) { 157 | platform_driver_unregister(current_driver->platform_driver); 158 | current_driver = NULL; 159 | } 160 | 161 | } 162 | EXPORT_SYMBOL(tuxedo_keyboard_remove_driver); 163 | 164 | // Defines that might be missing in older kernel headers 165 | #define INTEL_FAM6_SAPPHIRERAPIDS_X 0x8F 166 | #define INTEL_FAM6_EMERALDRAPIDS_X 0xCF 167 | #define INTEL_FAM6_ALDERLAKE 0x97 168 | #define INTEL_FAM6_ALDERLAKE_L 0x9A 169 | #define INTEL_FAM6_ALDERLAKE_N 0xBE 170 | #define INTEL_FAM6_RAPTORLAKE 0xB7 171 | #define INTEL_FAM6_RAPTORLAKE_P 0xBA 172 | #define INTEL_FAM6_RAPTORLAKE_S 0xBF 173 | 174 | static const struct x86_cpu_id skip_tuxedo_dmi_string_check_match[] __initconst = { 175 | X86_MATCH_INTEL_FAM6_MODEL(CORE_YONAH, NULL), 176 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_MEROM, NULL), 177 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_MEROM_L, NULL), 178 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_PENRYN, NULL), 179 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_DUNNINGTON, NULL), 180 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM, NULL), 181 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_G, NULL), 182 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_EP, NULL), 183 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_EX, NULL), 184 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE, NULL), 185 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE_EP, NULL), 186 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE_EX, NULL), 187 | X86_MATCH_INTEL_FAM6_MODEL(SANDYBRIDGE, NULL), 188 | X86_MATCH_INTEL_FAM6_MODEL(SANDYBRIDGE_X, NULL), 189 | X86_MATCH_INTEL_FAM6_MODEL(IVYBRIDGE, NULL), 190 | X86_MATCH_INTEL_FAM6_MODEL(IVYBRIDGE_X, NULL), 191 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL, NULL), 192 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_X, NULL), 193 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_L, NULL), 194 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_G, NULL), 195 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL, NULL), 196 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), 197 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), 198 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), 199 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, NULL), 200 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, NULL), 201 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), 202 | X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, NULL), 203 | X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, NULL), 204 | X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL), 205 | X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, NULL), 206 | X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, NULL), 207 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), 208 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), 209 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE, NULL), 210 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, NULL), 211 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, NULL), 212 | X86_MATCH_INTEL_FAM6_MODEL(LAKEFIELD, NULL), 213 | X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, NULL), 214 | X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, NULL), 215 | X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, NULL), 216 | X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), // 12th Gen Xeon 217 | //X86_MATCH_INTEL_FAM6_MODEL(EMERALDRAPIDS_X, NULL), // 13th Gen Xeon 218 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL), // 12th Gen 219 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL), // 12th Gen 220 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_N, NULL), // 12th Gen Atom 221 | //X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, NULL), // 13th Gen 222 | //X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, NULL), // 13th Gen 223 | //X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, NULL), // 13th Gen 224 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_BONNELL, NULL), 225 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_BONNELL_MID, NULL), 226 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL, NULL), 227 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_MID, NULL), 228 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_TABLET, NULL), 229 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL), 230 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_D, NULL), 231 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, NULL), 232 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL), 233 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT_MID, NULL), 234 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT_NP, NULL), 235 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, NULL), 236 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_D, NULL), 237 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, NULL), 238 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_D, NULL), 239 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, NULL), 240 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, NULL), 241 | X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNL, NULL), 242 | X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNM, NULL), 243 | X86_MATCH_VENDOR_FAM_MODEL(INTEL, 5, INTEL_FAM5_QUARK_X1000, NULL), 244 | X86_MATCH_VENDOR_FAM(AMD, 5, NULL), 245 | X86_MATCH_VENDOR_FAM(AMD, 6, NULL), 246 | X86_MATCH_VENDOR_FAM(AMD, 15, NULL), 247 | X86_MATCH_VENDOR_FAM(AMD, 16, NULL), 248 | X86_MATCH_VENDOR_FAM(AMD, 17, NULL), 249 | X86_MATCH_VENDOR_FAM(AMD, 18, NULL), 250 | X86_MATCH_VENDOR_FAM(AMD, 19, NULL), 251 | X86_MATCH_VENDOR_FAM(AMD, 20, NULL), 252 | X86_MATCH_VENDOR_FAM(AMD, 21, NULL), 253 | X86_MATCH_VENDOR_FAM(AMD, 22, NULL), 254 | X86_MATCH_VENDOR_FAM(AMD, 23, NULL), // Zen, Zen+, Zen 2 255 | X86_MATCH_VENDOR_FAM(AMD, 24, NULL), // Zen 256 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x01, NULL), // Zen 3 Epyc 257 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x08, NULL), // Zen 3 Threadripper 258 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x21, NULL), // Zen 3 Vermeer 259 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x40, NULL), // Zen 3+ Rembrandt 260 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x44, NULL), // Zen 3+ Rembrandt 261 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x50, NULL), // Zen 3 Cezanne 262 | { } 263 | }; 264 | 265 | static const struct x86_cpu_id force_tuxedo_dmi_string_check_match[] __initconst = { 266 | { } 267 | }; 268 | 269 | static const struct dmi_system_id tuxedo_dmi_string_match[] __initconst = { 270 | { 271 | .matches = { 272 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 273 | }, 274 | }, 275 | { 276 | .matches = { 277 | DMI_MATCH(DMI_BOARD_VENDOR, "TUXEDO"), 278 | }, 279 | }, 280 | { 281 | .matches = { 282 | DMI_MATCH(DMI_CHASSIS_VENDOR, "TUXEDO"), 283 | }, 284 | }, 285 | { } 286 | }; 287 | 288 | static int __init tuxedo_keyboard_init(void) 289 | { 290 | TUXEDO_INFO("module init\n"); 291 | 292 | if (!(dmi_check_system(tuxedo_dmi_string_match) 293 | || (x86_match_cpu(skip_tuxedo_dmi_string_check_match) 294 | && !x86_match_cpu(force_tuxedo_dmi_string_check_match)))) { 295 | return -ENODEV; 296 | } 297 | 298 | return 0; 299 | } 300 | 301 | static void __exit tuxedo_keyboard_exit(void) 302 | { 303 | TUXEDO_INFO("module exit\n"); 304 | 305 | if (tuxedo_platform_device != NULL) 306 | tuxedo_keyboard_remove_driver(NULL); 307 | } 308 | 309 | module_init(tuxedo_keyboard_init); 310 | module_exit(tuxedo_keyboard_exit); 311 | -------------------------------------------------------------------------------- /src/tuxedo_keyboard_common.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #ifndef TUXEDO_KEYBOARD_COMMON_H 20 | #define TUXEDO_KEYBOARD_COMMON_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | /* :::: Module specific Constants and simple Macros :::: */ 31 | #define __TUXEDO_PR(lvl, fmt, ...) do { pr_##lvl(fmt, ##__VA_ARGS__); } while (0) 32 | #define TUXEDO_INFO(fmt, ...) __TUXEDO_PR(info, fmt, ##__VA_ARGS__) 33 | #define TUXEDO_ERROR(fmt, ...) __TUXEDO_PR(err, fmt, ##__VA_ARGS__) 34 | #define TUXEDO_DEBUG(fmt, ...) __TUXEDO_PR(debug, "[%s:%u] " fmt, __func__, __LINE__, ##__VA_ARGS__) 35 | 36 | #ifndef DRIVER_NAME 37 | #define DRIVER_NAME "tuxedo_keyboard" 38 | #endif 39 | 40 | struct tuxedo_keyboard_driver { 41 | // Platform driver provided by driver 42 | struct platform_driver *platform_driver; 43 | // Probe method provided by driver 44 | int (*probe)(struct platform_device *); 45 | // Keymap provided by driver 46 | struct key_entry *key_map; 47 | // Input device reference filled in on module init after probe success 48 | struct input_dev *input_device; 49 | }; 50 | 51 | // Global module devices 52 | static struct platform_device *tuxedo_platform_device = NULL; 53 | static struct input_dev *tuxedo_input_device = NULL; 54 | 55 | // Currently chosen driver 56 | static struct tuxedo_keyboard_driver *current_driver = NULL; 57 | 58 | struct platform_device *tuxedo_keyboard_init_driver(struct tuxedo_keyboard_driver *tk_driver); 59 | void tuxedo_keyboard_remove_driver(struct tuxedo_keyboard_driver *tk_driver); 60 | 61 | /** 62 | * Basically a copy of the existing report event but doesn't report unknown events 63 | */ 64 | bool sparse_keymap_report_known_event(struct input_dev *dev, unsigned int code, 65 | unsigned int value, bool autorelease) 66 | { 67 | const struct key_entry *ke = 68 | sparse_keymap_entry_from_scancode(dev, code); 69 | 70 | if (ke) { 71 | sparse_keymap_report_entry(dev, ke, value, autorelease); 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | struct color_t { 79 | u32 code; 80 | char* name; 81 | }; 82 | 83 | struct color_list_t { 84 | uint size; 85 | struct color_t colors[]; 86 | }; 87 | 88 | /** 89 | * Commonly used standard colors 90 | */ 91 | static struct color_list_t color_list = { 92 | .size = 8, 93 | .colors = { 94 | { .name = "BLACK", .code = 0x000000 }, // 0 95 | { .name = "RED", .code = 0xFF0000 }, // 1 96 | { .name = "GREEN", .code = 0x00FF00 }, // 2 97 | { .name = "BLUE", .code = 0x0000FF }, // 3 98 | { .name = "YELLOW", .code = 0xFFFF00 }, // 4 99 | { .name = "MAGENTA", .code = 0xFF00FF }, // 5 100 | { .name = "CYAN", .code = 0x00FFFF }, // 6 101 | { .name = "WHITE", .code = 0xFFFFFF }, // 7 102 | } 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /src/uniwill_interfaces.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2021 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #ifndef UNIWILL_INTERFACES_H 20 | #define UNIWILL_INTERFACES_H 21 | 22 | #include 23 | 24 | #define UNIWILL_WMI_MGMT_GUID_BA "ABBC0F6D-8EA1-11D1-00A0-C90629100000" 25 | #define UNIWILL_WMI_MGMT_GUID_BB "ABBC0F6E-8EA1-11D1-00A0-C90629100000" 26 | #define UNIWILL_WMI_MGMT_GUID_BC "ABBC0F6F-8EA1-11D1-00A0-C90629100000" 27 | 28 | #define UNIWILL_WMI_EVENT_GUID_0 "ABBC0F70-8EA1-11D1-00A0-C90629100000" 29 | #define UNIWILL_WMI_EVENT_GUID_1 "ABBC0F71-8EA1-11D1-00A0-C90629100000" 30 | #define UNIWILL_WMI_EVENT_GUID_2 "ABBC0F72-8EA1-11D1-00A0-C90629100000" 31 | 32 | #define MODULE_ALIAS_UNIWILL_WMI() \ 33 | MODULE_ALIAS("wmi:" UNIWILL_WMI_EVENT_GUID_2); \ 34 | MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC); 35 | 36 | #define UNIWILL_INTERFACE_WMI_STRID "uniwill_wmi" 37 | 38 | typedef int (uniwill_read_ec_ram_t)(u16, u8*); 39 | typedef int (uniwill_read_ec_ram_with_retry_t)(u16, u8*, int); 40 | typedef int (uniwill_write_ec_ram_t)(u16, u8); 41 | typedef int (uniwill_write_ec_ram_with_retry_t)(u16, u8, int); 42 | typedef void (uniwill_event_callb_t)(u32); 43 | 44 | // UW_EC_REG_* known relevant EC address exposing some information or function 45 | // UW_EC_REG_*_BIT_* single bit from byte holding information, should be handled with bit-wise operations 46 | // UW_EC_REG_*_VALUE_* discrete value of the whole byte with special meaning 47 | // UW_EC_REG_*_SUBCMD_* writing this discrete value triggers special behaviour 48 | 49 | #define UW_EC_REG_KBD_BL_STATUS 0x078c 50 | #define UW_EC_REG_KBD_BL_STATUS_BIT_WHITE_ONLY_KB 0x01 51 | #define UW_EC_REG_KBD_BL_STATUS_SUBCMD_RESET 0x10 52 | 53 | #define UW_EC_REG_KBD_BL_MAX_BRIGHTNESS 0x1801 54 | #define UW_EC_REG_KBD_BL_WHITE_BRIGHTNESS 0x1802 55 | #define UW_EC_REG_KBD_BL_RGB_RED_BRIGHTNESS 0x1803 56 | #define UW_EC_REG_KBD_BL_RGB_GREEN_BRIGHTNESS 0x1805 57 | #define UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS 0x1808 58 | 59 | #define UW_EC_REG_BAREBONE_ID 0x0740 60 | #define UW_EC_REG_BAREBONE_ID_VALUE_PFxxxxx 0x09 61 | #define UW_EC_REG_BAREBONE_ID_VALUE_PFxMxxx 0x0e 62 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TRX1 0x12 63 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TUX1 0x13 64 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TQx1 0x14 65 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH6TRX1 0x15 66 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH6TQxx 0x16 67 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4Axxx 0x17 68 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4Pxxx 0x18 69 | 70 | #define UW_EC_REG_FEATURES_0 0x0765 71 | #define UW_EC_REG_FEATURES_1 0x0766 72 | #define UW_EC_REG_FEATURES_1_BIT_1_ZONE_RGB_KB 0x04 73 | 74 | #define UW_EC_REG_ROMID_START 0x0770 75 | #define UW_EC_REG_ROMID_SPECIAL_1 0x077e 76 | #define UW_EC_REG_ROMID_SPECIAL_2 0x077f 77 | 78 | struct uniwill_interface_t { 79 | char *string_id; 80 | uniwill_event_callb_t *event_callb; 81 | uniwill_read_ec_ram_t *read_ec_ram; 82 | uniwill_write_ec_ram_t *write_ec_ram; 83 | }; 84 | 85 | int uniwill_add_interface(struct uniwill_interface_t *new_interface); 86 | int uniwill_remove_interface(struct uniwill_interface_t *interface); 87 | uniwill_read_ec_ram_t uniwill_read_ec_ram; 88 | uniwill_write_ec_ram_t uniwill_write_ec_ram; 89 | uniwill_write_ec_ram_with_retry_t uniwill_write_ec_ram_with_retry; 90 | uniwill_read_ec_ram_with_retry_t uniwill_read_ec_ram_with_retry; 91 | int uniwill_get_active_interface_id(char **id_str); 92 | 93 | #define UW_MODEL_PF5LUXG 0x09 94 | #define UW_MODEL_PH4TUX 0x13 95 | #define UW_MODEL_PH4TRX 0x12 96 | #define UW_MODEL_PH4TQF 0x14 97 | #define UW_MODEL_PH4AQF_ARX 0x17 98 | 99 | struct uniwill_device_features_t { 100 | u8 model; 101 | /** 102 | * Identification for uniwill_power_profile_v1 103 | * 104 | * - Two profiles present in low power devices often called 105 | * "power save" and "balanced". 106 | * - Three profiles present mainly in devices with discrete 107 | * graphics card often called "power save", "balanced" 108 | * and "enthusiast" 109 | */ 110 | bool uniwill_profile_v1; 111 | bool uniwill_profile_v1_two_profs; 112 | bool uniwill_profile_v1_three_profs; 113 | bool uniwill_profile_v1_three_profs_leds_only; 114 | bool uniwill_has_charging_prio; 115 | bool uniwill_has_charging_profile; 116 | bool uniwill_has_universal_ec_fan_control; 117 | }; 118 | 119 | struct uniwill_device_features_t *uniwill_get_device_features(void); 120 | 121 | union uw_ec_read_return { 122 | u32 dword; 123 | struct { 124 | u8 data_low; 125 | u8 data_high; 126 | } bytes; 127 | }; 128 | 129 | union uw_ec_write_return { 130 | u32 dword; 131 | struct { 132 | u8 addr_low; 133 | u8 addr_high; 134 | u8 data_low; 135 | u8 data_high; 136 | } bytes; 137 | }; 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /src/uniwill_leds.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #ifndef UNIWILL_LEDS_H 21 | #define UNIWILL_LEDS_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | enum uniwill_kb_backlight_types { 29 | UNIWILL_KB_BACKLIGHT_TYPE_NONE, 30 | UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR, 31 | UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB, 32 | UNIWILL_KB_BACKLIGHT_TYPE_PER_KEY_RGB 33 | }; 34 | 35 | #define UNIWILL_KBD_BRIGHTNESS_MAX 0xff 36 | #define UNIWILL_KBD_BRIGHTNESS_DEFAULT 0x00 37 | 38 | #define UNIWILL_KBD_BRIGHTNESS_WHITE_MAX 0x02 39 | #define UNIWILL_KBD_BRIGHTNESS_WHITE_DEFAULT 0x00 40 | 41 | #define UNIWILL_KB_COLOR_DEFAULT_RED 0xff 42 | #define UNIWILL_KB_COLOR_DEFAULT_GREEN 0xff 43 | #define UNIWILL_KB_COLOR_DEFAULT_BLUE 0xff 44 | #define UNIWILL_KB_COLOR_DEFAULT ((UNIWILL_KB_COLOR_DEFAULT_RED << 16) + (UNIWILL_KB_COLOR_DEFAULT_GREEN << 8) + UNIWILL_KB_COLOR_DEFAULT_BLUE) 45 | 46 | int uniwill_leds_init_early(struct platform_device *dev); 47 | int uniwill_leds_init_late(struct platform_device *dev); 48 | int uniwill_leds_remove(struct platform_device *dev); 49 | enum uniwill_kb_backlight_types uniwill_leds_get_backlight_type(void); 50 | int uniwill_leds_notify_brightness_change_extern(void); 51 | void uniwill_leds_restore_state_extern(void); 52 | void uniwill_leds_set_brightness_extern(enum led_brightness brightness); 53 | void uniwill_leds_set_color_extern(u32 color); 54 | 55 | // TODO The following should go into a seperate .c file, but for this to work more reworking is required in the tuxedo_keyboard structure. 56 | 57 | #include "uniwill_leds.h" 58 | 59 | #include "uniwill_interfaces.h" 60 | 61 | #include 62 | 63 | static enum uniwill_kb_backlight_types uniwill_kb_backlight_type = UNIWILL_KB_BACKLIGHT_TYPE_NONE; 64 | static bool uw_leds_initialized = false; 65 | 66 | static int uniwill_write_kbd_bl_white(u8 brightness) 67 | { 68 | u8 data; 69 | 70 | uniwill_read_ec_ram(UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS, &data); 71 | // When keyboard backlight is off, new settings to 0x078c do not get applied automatically 72 | // on Pulse Gen1/2 until next keypress or manual change to 0x1808 (immediate brightness 73 | // value for some reason. 74 | // Sidenote: IBP Gen6/7 has immediate brightness value on 0x1802 and not on 0x1808, but does 75 | // not need this workaround. 76 | if (!data && brightness) { 77 | uniwill_write_ec_ram(UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS, 0x01); 78 | } 79 | 80 | data = 0; 81 | uniwill_read_ec_ram(UW_EC_REG_KBD_BL_STATUS, &data); 82 | data &= 0x0f; // lower bits must be preserved 83 | data |= UW_EC_REG_KBD_BL_STATUS_SUBCMD_RESET; 84 | data |= brightness << 5; 85 | return uniwill_write_ec_ram(UW_EC_REG_KBD_BL_STATUS, data); 86 | } 87 | 88 | static int uniwill_write_kbd_bl_rgb(u8 red, u8 green, u8 blue) 89 | { 90 | int result = 0; 91 | 92 | result = uniwill_write_ec_ram(UW_EC_REG_KBD_BL_RGB_RED_BRIGHTNESS, red); 93 | if (result) { 94 | return result; 95 | } 96 | result = uniwill_write_ec_ram(UW_EC_REG_KBD_BL_RGB_GREEN_BRIGHTNESS, green); 97 | if (result) { 98 | return result; 99 | } 100 | result = uniwill_write_ec_ram(UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS, blue); 101 | if (result) { 102 | return result; 103 | } 104 | 105 | pr_debug("Wrote kbd color [%0#4x, %0#4x, %0#4x]\n", red, green, blue); 106 | 107 | return result; 108 | } 109 | 110 | static void uniwill_leds_set_brightness(struct led_classdev *led_cdev __always_unused, enum led_brightness brightness) { 111 | int ret = uniwill_write_kbd_bl_white(brightness); 112 | if (ret) { 113 | pr_debug("uniwill_leds_set_brightness(): uniwill_write_kbd_bl_white() failed\n"); 114 | return; 115 | } 116 | led_cdev->brightness = brightness; 117 | } 118 | 119 | static void uniwill_leds_set_brightness_mc(struct led_classdev *led_cdev, enum led_brightness brightness) { 120 | int ret; 121 | struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); 122 | 123 | led_mc_calc_color_components(mcled_cdev, brightness); 124 | 125 | ret = uniwill_write_kbd_bl_rgb(mcled_cdev->subled_info[0].brightness, 126 | mcled_cdev->subled_info[1].brightness, 127 | mcled_cdev->subled_info[2].brightness); 128 | if (ret) { 129 | pr_debug("uniwill_leds_set_brightness_mc(): uniwill_write_kbd_bl_rgb() failed\n"); 130 | return; 131 | } 132 | led_cdev->brightness = brightness; 133 | } 134 | 135 | static struct led_classdev uniwill_led_cdev = { 136 | .name = "white:" LED_FUNCTION_KBD_BACKLIGHT, 137 | .max_brightness = UNIWILL_KBD_BRIGHTNESS_WHITE_MAX, 138 | .brightness_set = &uniwill_leds_set_brightness, 139 | .brightness = UNIWILL_KBD_BRIGHTNESS_WHITE_DEFAULT, 140 | .flags = LED_BRIGHT_HW_CHANGED 141 | }; 142 | 143 | static struct mc_subled uw_mcled_cdev_subleds[3] = { 144 | { 145 | .color_index = LED_COLOR_ID_RED, 146 | .brightness = UNIWILL_KBD_BRIGHTNESS_MAX, 147 | .intensity = UNIWILL_KB_COLOR_DEFAULT_RED, 148 | .channel = 0 149 | }, 150 | { 151 | .color_index = LED_COLOR_ID_GREEN, 152 | .brightness = UNIWILL_KBD_BRIGHTNESS_MAX, 153 | .intensity = UNIWILL_KB_COLOR_DEFAULT_GREEN, 154 | .channel = 0 155 | }, 156 | { 157 | .color_index = LED_COLOR_ID_BLUE, 158 | .brightness = UNIWILL_KBD_BRIGHTNESS_MAX, 159 | .intensity = UNIWILL_KB_COLOR_DEFAULT_BLUE, 160 | .channel = 0 161 | } 162 | }; 163 | 164 | static struct led_classdev_mc uniwill_mcled_cdev = { 165 | .led_cdev.name = "rgb:" LED_FUNCTION_KBD_BACKLIGHT, 166 | .led_cdev.max_brightness = UNIWILL_KBD_BRIGHTNESS_MAX, 167 | .led_cdev.brightness_set = &uniwill_leds_set_brightness_mc, 168 | .led_cdev.brightness = UNIWILL_KBD_BRIGHTNESS_DEFAULT, 169 | .led_cdev.flags = LED_BRIGHT_HW_CHANGED, 170 | .num_colors = 3, 171 | .subled_info = uw_mcled_cdev_subleds 172 | }; 173 | 174 | int uniwill_leds_init_early(struct platform_device *dev) 175 | { 176 | // FIXME Use mutexes 177 | int ret; 178 | u8 data; 179 | 180 | ret = uniwill_read_ec_ram(UW_EC_REG_BAREBONE_ID, &data); 181 | if (ret) { 182 | pr_err("Reading barebone ID failed.\n"); 183 | return ret; 184 | } 185 | pr_debug("EC Barebone ID: %#04x\n", data); 186 | 187 | if (data == UW_EC_REG_BAREBONE_ID_VALUE_PFxxxxx || 188 | data == UW_EC_REG_BAREBONE_ID_VALUE_PFxMxxx || 189 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH4TRX1 || 190 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH4TUX1 || 191 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH4TQx1 || 192 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH6TRX1 || 193 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH6TQxx || 194 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH4Axxx || 195 | data == UW_EC_REG_BAREBONE_ID_VALUE_PH4Pxxx) { 196 | ret = uniwill_read_ec_ram(UW_EC_REG_KBD_BL_STATUS, &data); 197 | if (ret) { 198 | pr_err("Reading keyboard backlight status failed.\n"); 199 | return ret; 200 | } 201 | if (data & UW_EC_REG_KBD_BL_STATUS_BIT_WHITE_ONLY_KB) { 202 | uniwill_kb_backlight_type = UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR; 203 | } 204 | } 205 | else { 206 | ret = uniwill_read_ec_ram(UW_EC_REG_FEATURES_1, &data); 207 | if (ret) { 208 | pr_err("Reading features 1 failed.\n"); 209 | return ret; 210 | } 211 | if (data & UW_EC_REG_FEATURES_1_BIT_1_ZONE_RGB_KB) { 212 | uniwill_kb_backlight_type = UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB; 213 | } 214 | } 215 | pr_debug("Keyboard backlight type: 0x%02x\n", uniwill_kb_backlight_type); 216 | 217 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 218 | pr_debug("Registering fixed color leds interface\n"); 219 | ret = led_classdev_register(&dev->dev, &uniwill_led_cdev); 220 | if (ret) { 221 | pr_err("Registering fixed color leds interface failed\n"); 222 | return ret; 223 | } 224 | } 225 | else if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 226 | pr_debug("Registering single zone rgb leds interface\n"); 227 | ret = devm_led_classdev_multicolor_register(&dev->dev, &uniwill_mcled_cdev); 228 | if (ret) { 229 | pr_err("Registering single zone rgb leds interface failed\n"); 230 | return ret; 231 | } 232 | } 233 | 234 | uw_leds_initialized = true; 235 | return 0; 236 | } 237 | EXPORT_SYMBOL(uniwill_leds_init_early); 238 | 239 | int uniwill_leds_init_late(struct platform_device *dev) 240 | { 241 | // FIXME Use mutexes 242 | int ret; 243 | 244 | ret = uniwill_write_ec_ram(UW_EC_REG_KBD_BL_MAX_BRIGHTNESS, 0xff); 245 | if (ret) { 246 | pr_err("Setting max keyboard brightness value failed\n"); 247 | uniwill_leds_remove(dev); 248 | return ret; 249 | } 250 | 251 | uniwill_leds_restore_state_extern(); 252 | 253 | return 0; 254 | } 255 | EXPORT_SYMBOL(uniwill_leds_init_late); 256 | 257 | int uniwill_leds_remove(struct platform_device *dev) 258 | { 259 | // FIXME Use mutexes 260 | int ret = 0; 261 | 262 | if (uw_leds_initialized) { 263 | uw_leds_initialized = false; 264 | 265 | uniwill_leds_set_brightness_extern(0x00); 266 | ret = uniwill_write_ec_ram(UW_EC_REG_KBD_BL_MAX_BRIGHTNESS, 0xc8); 267 | if (ret) { 268 | pr_err("Resetting max keyboard brightness value failed\n"); 269 | } 270 | 271 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 272 | led_classdev_unregister(&uniwill_led_cdev); 273 | } 274 | else if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 275 | devm_led_classdev_multicolor_unregister(&dev->dev, &uniwill_mcled_cdev); 276 | } 277 | } 278 | 279 | return ret; 280 | } 281 | EXPORT_SYMBOL(uniwill_leds_remove); 282 | 283 | enum uniwill_kb_backlight_types uniwill_leds_get_backlight_type(void) { 284 | return uniwill_kb_backlight_type; 285 | } 286 | EXPORT_SYMBOL(uniwill_leds_get_backlight_type); 287 | 288 | int uniwill_leds_notify_brightness_change_extern(void) { 289 | u8 data = 0; 290 | 291 | if (uw_leds_initialized) { 292 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 293 | uniwill_read_ec_ram(UW_EC_REG_KBD_BL_STATUS, &data); 294 | data = (data >> 5) & 0x3; 295 | uniwill_led_cdev.brightness = data; 296 | led_classdev_notify_brightness_hw_changed(&uniwill_led_cdev, data); 297 | return true; 298 | } 299 | } 300 | return false; 301 | } 302 | 303 | void uniwill_leds_restore_state_extern(void) { 304 | u8 data; 305 | 306 | if (uw_leds_initialized) { 307 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 308 | if (uniwill_write_kbd_bl_white(uniwill_led_cdev.brightness)) { 309 | pr_debug("uniwill_leds_restore_state_extern(): uniwill_write_kbd_bl_white() failed\n"); 310 | } 311 | } 312 | else if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 313 | // reset 314 | uniwill_read_ec_ram(UW_EC_REG_KBD_BL_STATUS, &data); 315 | data |= UW_EC_REG_KBD_BL_STATUS_SUBCMD_RESET; 316 | uniwill_write_ec_ram(UW_EC_REG_KBD_BL_STATUS, data); 317 | 318 | // write 319 | if (uniwill_write_kbd_bl_rgb(uniwill_mcled_cdev.subled_info[0].brightness, 320 | uniwill_mcled_cdev.subled_info[1].brightness, 321 | uniwill_mcled_cdev.subled_info[2].brightness)) { 322 | pr_debug("uniwill_leds_restore_state_extern(): uniwill_write_kbd_bl_rgb() failed\n"); 323 | } 324 | } 325 | } 326 | } 327 | EXPORT_SYMBOL(uniwill_leds_restore_state_extern); 328 | 329 | void uniwill_leds_set_brightness_extern(enum led_brightness brightness) { 330 | if (uw_leds_initialized) { 331 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_FIXED_COLOR) { 332 | uniwill_led_cdev.brightness_set(&uniwill_led_cdev, brightness); 333 | } 334 | else if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 335 | uniwill_mcled_cdev.led_cdev.brightness_set(&uniwill_mcled_cdev.led_cdev, brightness); 336 | } 337 | } 338 | } 339 | EXPORT_SYMBOL(uniwill_leds_set_brightness_extern); 340 | 341 | void uniwill_leds_set_color_extern(u32 color) { 342 | if (uw_leds_initialized) { 343 | if (uniwill_kb_backlight_type == UNIWILL_KB_BACKLIGHT_TYPE_1_ZONE_RGB) { 344 | uniwill_mcled_cdev.subled_info[0].intensity = (color >> 16) & 0xff; 345 | uniwill_mcled_cdev.subled_info[1].intensity = (color >> 8) & 0xff; 346 | uniwill_mcled_cdev.subled_info[2].intensity = color & 0xff; 347 | uniwill_mcled_cdev.led_cdev.brightness_set(&uniwill_mcled_cdev.led_cdev, uniwill_mcled_cdev.led_cdev.brightness); 348 | } 349 | } 350 | } 351 | EXPORT_SYMBOL(uniwill_leds_set_color_extern); 352 | 353 | MODULE_LICENSE("GPL"); 354 | 355 | #endif // UNIWILL_LEDS_H 356 | -------------------------------------------------------------------------------- /src/uniwill_wmi.c: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2021 TUXEDO Computers GmbH 3 | * 4 | * This file is part of tuxedo-keyboard. 5 | * 6 | * tuxedo-keyboard is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see . 18 | */ 19 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "uniwill_interfaces.h" 26 | 27 | #define UNIWILL_EC_REG_LDAT 0x8a 28 | #define UNIWILL_EC_REG_HDAT 0x8b 29 | #define UNIWILL_EC_REG_FLAGS 0x8c 30 | #define UNIWILL_EC_REG_CMDL 0x8d 31 | #define UNIWILL_EC_REG_CMDH 0x8e 32 | 33 | #define UNIWILL_EC_BIT_RFLG 0 34 | #define UNIWILL_EC_BIT_WFLG 1 35 | #define UNIWILL_EC_BIT_BFLG 2 36 | #define UNIWILL_EC_BIT_CFLG 3 37 | #define UNIWILL_EC_BIT_DRDY 7 38 | 39 | #define UW_EC_BUSY_WAIT_CYCLES 30 40 | #define UW_EC_BUSY_WAIT_DELAY 15 41 | 42 | static bool uniwill_ec_direct = true; 43 | 44 | DEFINE_MUTEX(uniwill_ec_lock); 45 | 46 | static int uw_wmi_ec_evaluate(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, u8 read_flag, u32 *return_buffer) 47 | { 48 | acpi_status status; 49 | union acpi_object *out_acpi; 50 | int e_result = 0; 51 | 52 | // Kernel buffer for input argument 53 | u32 *wmi_arg = (u32 *) kmalloc(sizeof(u32)*10, GFP_KERNEL); 54 | // Byte reference to the input buffer 55 | u8 *wmi_arg_bytes = (u8 *) wmi_arg; 56 | 57 | u8 wmi_instance = 0x00; 58 | u32 wmi_method_id = 0x04; 59 | struct acpi_buffer wmi_in = { (acpi_size) sizeof(wmi_arg), wmi_arg}; 60 | struct acpi_buffer wmi_out = { ACPI_ALLOCATE_BUFFER, NULL }; 61 | 62 | mutex_lock(&uniwill_ec_lock); 63 | 64 | // Zero input buffer 65 | memset(wmi_arg, 0x00, 10 * sizeof(u32)); 66 | 67 | // Configure the input buffer 68 | wmi_arg_bytes[0] = addr_low; 69 | wmi_arg_bytes[1] = addr_high; 70 | wmi_arg_bytes[2] = data_low; 71 | wmi_arg_bytes[3] = data_high; 72 | 73 | if (read_flag != 0) { 74 | wmi_arg_bytes[5] = 0x01; 75 | } 76 | 77 | status = wmi_evaluate_method(UNIWILL_WMI_MGMT_GUID_BC, wmi_instance, wmi_method_id, &wmi_in, &wmi_out); 78 | out_acpi = (union acpi_object *) wmi_out.pointer; 79 | 80 | if (out_acpi && out_acpi->type == ACPI_TYPE_BUFFER) { 81 | memcpy(return_buffer, out_acpi->buffer.pointer, out_acpi->buffer.length); 82 | } /* else if (out_acpi && out_acpi->type == ACPI_TYPE_INTEGER) { 83 | e_result = (u32) out_acpi->integer.value; 84 | }*/ 85 | if (ACPI_FAILURE(status)) { 86 | pr_err("uniwill_wmi.h: Error evaluating method\n"); 87 | e_result = -EIO; 88 | } 89 | 90 | kfree(out_acpi); 91 | kfree(wmi_arg); 92 | 93 | mutex_unlock(&uniwill_ec_lock); 94 | 95 | return e_result; 96 | } 97 | 98 | /** 99 | * EC address read through WMI 100 | */ 101 | static int uw_ec_read_addr_wmi(u8 addr_low, u8 addr_high, union uw_ec_read_return *output) 102 | { 103 | u32 uw_data[10]; 104 | int ret = uw_wmi_ec_evaluate(addr_low, addr_high, 0x00, 0x00, 1, uw_data); 105 | output->dword = uw_data[0]; 106 | // pr_debug("addr: 0x%02x%02x value: %0#4x (high: %0#4x) result: %d\n", addr_high, addr_low, output->bytes.data_low, output->bytes.data_high, ret); 107 | return ret; 108 | } 109 | 110 | /** 111 | * EC address write through WMI 112 | */ 113 | static int uw_ec_write_addr_wmi(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output) 114 | { 115 | u32 uw_data[10]; 116 | int ret = uw_wmi_ec_evaluate(addr_low, addr_high, data_low, data_high, 0, uw_data); 117 | output->dword = uw_data[0]; 118 | return ret; 119 | } 120 | 121 | /** 122 | * Direct EC address read 123 | */ 124 | static int uw_ec_read_addr_direct(u8 addr_low, u8 addr_high, union uw_ec_read_return *output) 125 | { 126 | int result; 127 | int count; 128 | u8 tmp, flags; 129 | bool ready; 130 | bool bflag = false; 131 | 132 | mutex_lock(&uniwill_ec_lock); 133 | 134 | ec_read(UNIWILL_EC_REG_FLAGS, &flags); 135 | if ((flags & (1 << UNIWILL_EC_BIT_BFLG)) > 0) { 136 | pr_debug("read: BFLG set\n"); 137 | bflag = true; 138 | } 139 | 140 | flags |= (1 << UNIWILL_EC_BIT_BFLG); 141 | ec_write(UNIWILL_EC_REG_FLAGS, flags); 142 | 143 | ec_write(UNIWILL_EC_REG_LDAT, addr_low); 144 | ec_write(UNIWILL_EC_REG_HDAT, addr_high); 145 | 146 | flags &= ~(1 << UNIWILL_EC_BIT_DRDY); 147 | flags |= (1 << UNIWILL_EC_BIT_RFLG); 148 | ec_write(UNIWILL_EC_REG_FLAGS, flags); 149 | 150 | // Wait for ready flag 151 | count = UW_EC_BUSY_WAIT_CYCLES; 152 | ready = false; 153 | while (!ready && count != 0) { 154 | msleep(UW_EC_BUSY_WAIT_DELAY); 155 | ec_read(UNIWILL_EC_REG_FLAGS, &tmp); 156 | ready = (tmp & (1 << UNIWILL_EC_BIT_DRDY)) != 0; 157 | count -= 1; 158 | } 159 | 160 | if (count != 0) { 161 | output->dword = 0; 162 | ec_read(UNIWILL_EC_REG_CMDL, &tmp); 163 | output->bytes.data_low = tmp; 164 | ec_read(UNIWILL_EC_REG_CMDH, &tmp); 165 | output->bytes.data_high = tmp; 166 | result = 0; 167 | } else { 168 | pr_err("uw ec read timeout, addr: 0x%02x%02x\n", addr_high, addr_low); 169 | output->dword = 0xfefefefe; 170 | result = -EIO; 171 | } 172 | 173 | ec_write(UNIWILL_EC_REG_FLAGS, 0x00); 174 | 175 | mutex_unlock(&uniwill_ec_lock); 176 | 177 | if (bflag) 178 | pr_debug("addr: 0x%02x%02x value: %0#4x result: %d\n", addr_high, addr_low, output->bytes.data_low, result); 179 | 180 | if ((UW_EC_BUSY_WAIT_CYCLES - count) > 1) 181 | pr_debug("read wait count: %i", (UW_EC_BUSY_WAIT_CYCLES - count)); 182 | 183 | // pr_debug("addr: 0x%02x%02x value: %0#4x result: %d\n", addr_high, addr_low, output->bytes.data_low, result); 184 | 185 | return result; 186 | } 187 | 188 | static int uw_ec_write_addr_direct(u8 addr_low, u8 addr_high, u8 data_low, u8 data_high, union uw_ec_write_return *output) 189 | { 190 | int result = 0; 191 | int count; 192 | u8 tmp, flags; 193 | bool ready; 194 | bool bflag = false; 195 | 196 | mutex_lock(&uniwill_ec_lock); 197 | 198 | ec_read(UNIWILL_EC_REG_FLAGS, &flags); 199 | if ((flags & (1 << UNIWILL_EC_BIT_BFLG)) > 0) { 200 | pr_debug("write: BFLG set\n"); 201 | bflag = true; 202 | } 203 | 204 | flags |= (1 << UNIWILL_EC_BIT_BFLG); 205 | ec_write(UNIWILL_EC_REG_FLAGS, flags); 206 | 207 | ec_write(UNIWILL_EC_REG_LDAT, addr_low); 208 | ec_write(UNIWILL_EC_REG_HDAT, addr_high); 209 | ec_write(UNIWILL_EC_REG_CMDL, data_low); 210 | ec_write(UNIWILL_EC_REG_CMDH, data_high); 211 | 212 | flags &= ~(1 << UNIWILL_EC_BIT_DRDY); 213 | flags |= (1 << UNIWILL_EC_BIT_WFLG); 214 | ec_write(UNIWILL_EC_REG_FLAGS, flags); 215 | 216 | // Wait for ready flag 217 | count = UW_EC_BUSY_WAIT_CYCLES; 218 | ready = false; 219 | while (!ready && count != 0) { 220 | msleep(UW_EC_BUSY_WAIT_DELAY); 221 | ec_read(UNIWILL_EC_REG_FLAGS, &tmp); 222 | ready = (tmp & (1 << UNIWILL_EC_BIT_DRDY)) != 0; 223 | count -= 1; 224 | } 225 | 226 | // Replicate wmi output depending on success 227 | if (count != 0) { 228 | output->bytes.addr_low = addr_low; 229 | output->bytes.addr_high = addr_high; 230 | output->bytes.data_low = data_low; 231 | output->bytes.data_high = data_high; 232 | result = 0; 233 | } else { 234 | pr_err("uw ec write timeout, addr: 0x%02x%02x, value: %0#4x\n", addr_high, addr_low, data_low); 235 | output->dword = 0xfefefefe; 236 | result = -EIO; 237 | } 238 | 239 | ec_write(UNIWILL_EC_REG_FLAGS, 0x00); 240 | 241 | if (bflag) 242 | pr_debug("addr: 0x%02x%02x value: %0#4x result: %d\n", addr_high, addr_low, data_low, result); 243 | 244 | if ((UW_EC_BUSY_WAIT_CYCLES - count) > 1) 245 | pr_debug("write wait count: %i", (UW_EC_BUSY_WAIT_CYCLES - count)); 246 | 247 | mutex_unlock(&uniwill_ec_lock); 248 | 249 | return result; 250 | } 251 | 252 | int uw_wmi_read_ec_ram(u16 addr, u8 *data) 253 | { 254 | int result; 255 | u8 addr_low, addr_high; 256 | union uw_ec_read_return output; 257 | 258 | if (IS_ERR_OR_NULL(data)) 259 | return -EINVAL; 260 | 261 | addr_low = addr & 0xff; 262 | addr_high = (addr >> 8) & 0xff; 263 | 264 | if (uniwill_ec_direct) { 265 | result = uw_ec_read_addr_direct(addr_low, addr_high, &output); 266 | } else { 267 | result = uw_ec_read_addr_wmi(addr_low, addr_high, &output); 268 | } 269 | 270 | *data = output.bytes.data_low; 271 | return result; 272 | } 273 | 274 | int uw_wmi_write_ec_ram(u16 addr, u8 data) 275 | { 276 | int result; 277 | u8 addr_low, addr_high, data_low, data_high; 278 | union uw_ec_write_return output; 279 | 280 | addr_low = addr & 0xff; 281 | addr_high = (addr >> 8) & 0xff; 282 | data_low = data; 283 | data_high = 0x00; 284 | 285 | if (uniwill_ec_direct) 286 | result = uw_ec_write_addr_direct(addr_low, addr_high, data_low, data_high, &output); 287 | else 288 | result = uw_ec_write_addr_wmi(addr_low, addr_high, data_low, data_high, &output); 289 | 290 | return result; 291 | } 292 | 293 | struct uniwill_interface_t uniwill_wmi_interface = { 294 | .string_id = UNIWILL_INTERFACE_WMI_STRID, 295 | .read_ec_ram = uw_wmi_read_ec_ram, 296 | .write_ec_ram = uw_wmi_write_ec_ram 297 | }; 298 | 299 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 300 | static int uniwill_wmi_probe(struct wmi_device *wdev) 301 | #else 302 | static int uniwill_wmi_probe(struct wmi_device *wdev, const void *dummy_context) 303 | #endif 304 | { 305 | int status; 306 | 307 | // Look for for GUIDs used on uniwill devices 308 | status = 309 | wmi_has_guid(UNIWILL_WMI_EVENT_GUID_0) && 310 | wmi_has_guid(UNIWILL_WMI_EVENT_GUID_1) && 311 | wmi_has_guid(UNIWILL_WMI_EVENT_GUID_2) && 312 | wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BA) && 313 | wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BB) && 314 | wmi_has_guid(UNIWILL_WMI_MGMT_GUID_BC); 315 | 316 | if (!status) { 317 | pr_debug("probe: At least one Uniwill GUID missing\n"); 318 | return -ENODEV; 319 | } 320 | 321 | uniwill_add_interface(&uniwill_wmi_interface); 322 | 323 | pr_info("interface initialized\n"); 324 | 325 | return 0; 326 | } 327 | 328 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 329 | static int uniwill_wmi_remove(struct wmi_device *wdev) 330 | #else 331 | static void uniwill_wmi_remove(struct wmi_device *wdev) 332 | #endif 333 | { 334 | pr_debug("uniwill_wmi driver remove\n"); 335 | uniwill_remove_interface(&uniwill_wmi_interface); 336 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 337 | return 0; 338 | #endif 339 | } 340 | 341 | static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj) 342 | { 343 | u32 code; 344 | 345 | if (!IS_ERR_OR_NULL(uniwill_wmi_interface.event_callb)) { 346 | if (obj) { 347 | if (obj->type == ACPI_TYPE_INTEGER) { 348 | code = obj->integer.value; 349 | // Execute registered callback 350 | uniwill_wmi_interface.event_callb(code); 351 | } else { 352 | pr_debug("unknown event type - %d (%0#6x)\n", obj->type, obj->type); 353 | } 354 | } else { 355 | pr_debug("expected ACPI object doesn't exist\n"); 356 | } 357 | } else { 358 | pr_debug("no registered callback\n"); 359 | } 360 | } 361 | 362 | static const struct wmi_device_id uniwill_wmi_device_ids[] = { 363 | // Listing one should be enough, for a driver that "takes care of all anyways" 364 | // also prevents probe (and handling) per "device" 365 | { .guid_string = UNIWILL_WMI_EVENT_GUID_2 }, 366 | { } 367 | }; 368 | 369 | static struct wmi_driver uniwill_wmi_driver = { 370 | .driver = { 371 | .name = UNIWILL_INTERFACE_WMI_STRID, 372 | .owner = THIS_MODULE 373 | }, 374 | .id_table = uniwill_wmi_device_ids, 375 | .probe = uniwill_wmi_probe, 376 | .remove = uniwill_wmi_remove, 377 | .notify = uniwill_wmi_notify, 378 | }; 379 | 380 | module_wmi_driver(uniwill_wmi_driver); 381 | 382 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 383 | MODULE_DESCRIPTION("Driver for Uniwill WMI interface"); 384 | MODULE_VERSION("0.0.4"); 385 | MODULE_LICENSE("GPL"); 386 | 387 | /* 388 | * If set to true, the module will use the replicated WMI functions 389 | * (direct ec_read/ec_write) to read and write to the EC RAM instead 390 | * of the original. Since the original functions, in all observed cases, 391 | * use excessive delays, they are not preferred. 392 | */ 393 | module_param_cb(ec_direct_io, ¶m_ops_bool, &uniwill_ec_direct, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH); 394 | MODULE_PARM_DESC(ec_direct_io, "Do not use WMI methods to read/write EC RAM (default: true)."); 395 | 396 | MODULE_DEVICE_TABLE(wmi, uniwill_wmi_device_ids); 397 | MODULE_ALIAS_UNIWILL_WMI(); 398 | -------------------------------------------------------------------------------- /src_pkg/dkms_postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2002-2005 Flavio Stanchina 3 | # Copyright (C) 2005-2006 Aric Cyr 4 | # Copyright (C) 2007 Mario Limonciello 5 | # Copyright (C) 2009 Alberto Milone 6 | 7 | set -e 8 | 9 | uname_s=$(uname -s) 10 | 11 | _get_kernel_dir() { 12 | KVER=$1 13 | case ${uname_s} in 14 | Linux) DIR="/lib/modules/$KVER/build" ;; 15 | GNU/kFreeBSD) DIR="/usr/src/kfreebsd-headers-$KVER/sys" ;; 16 | esac 17 | echo $DIR 18 | } 19 | 20 | _check_kernel_dir() { 21 | DIR=$(_get_kernel_dir $1) 22 | case ${uname_s} in 23 | Linux) test -e $DIR/include ;; 24 | GNU/kFreeBSD) test -e $DIR/kern && test -e $DIR/conf/kmod.mk ;; 25 | *) return 1 ;; 26 | esac 27 | return $? 28 | } 29 | 30 | # Check the existence of a kernel named as $1 31 | _is_kernel_name_correct() { 32 | CORRECT="no" 33 | KERNEL_NAME=$1 34 | 35 | for kernel in /boot/config-*; do 36 | KERNEL=${kernel#*-} 37 | if [ "${KERNEL}" = "${KERNEL_NAME}" ]; then 38 | CORRECT="yes" 39 | break 40 | fi 41 | done 42 | 43 | echo $CORRECT 44 | } 45 | 46 | 47 | # Get the most recent kernel on Debian based systems. This keeps 48 | # into account both the version and the ABI. If the current kernel 49 | # is the most recent kernel then the function will print a null string. 50 | _get_newest_kernel_debian() { 51 | NEWEST_KERNEL= 52 | NEWEST_VERSION= 53 | NEWEST_ABI= 54 | 55 | for kernel in /boot/config-*; do 56 | KERNEL=${kernel#*-} 57 | KERNEL_VERSION=${KERNEL%%-*} 58 | ABI=${KERNEL#*-} 59 | ABI=${ABI%%-*} 60 | 61 | if [ -z "$NEWEST_KERNEL" ]; then 62 | # The 1st time get a version which is bigger than $1 63 | COMPARE_TO=$1 64 | else 65 | # Get the biggest version 66 | COMPARE_TO="$NEWEST_VERSION-$NEWEST_ABI" 67 | fi 68 | 69 | # if $kernel is greater than $COMPARE_TO 70 | if [ `dpkg --compare-versions "$KERNEL_VERSION-$ABI" gt "$COMPARE_TO" && echo "yes" || \ 71 | echo "no"` = "yes" ]; then 72 | NEWEST_KERNEL=$KERNEL 73 | NEWEST_VERSION=$KERNEL_VERSION 74 | NEWEST_ABI=$ABI 75 | fi 76 | done 77 | 78 | echo "$NEWEST_KERNEL" 79 | } 80 | 81 | # Get the most recent kernel in Rhel based systems. If the current kernel 82 | # is the most recent kernel then the function will print a null string. 83 | _get_newest_kernel_rhel() { 84 | NEWEST_KERNEL= 85 | 86 | LAST_INSTALLED_KERNEL=$(rpm -q --whatprovides kernel --last | grep kernel -m1 | cut -f1 -d' ') 87 | 88 | LIK_FORMATTED_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{VERSION}-%{RELEASE}.%{ARCH}\n") 89 | 90 | if [ `echo $LIK_FORMATTED_NAME | grep 2.6 >/dev/null` ]; then 91 | # Fedora and Suse 92 | NEWEST_KERNEL=$LIK_FORMATTED_NAME 93 | else 94 | # Hack for Mandriva where $LIK_FORMATTED_NAME is broken 95 | LIK_NAME=$(rpm -q $LAST_INSTALLED_KERNEL --queryformat="%{NAME}\n") 96 | LIK_TYPE=${LIK_NAME#kernel-} 97 | LIK_TYPE=${LIK_TYPE%%-*} 98 | LIK_STRIPPED=${LIK_NAME#kernel-} 99 | LIK_STRIPPED=${LIK_STRIPPED#$LIK_TYPE-} 100 | LIK_STRIPPED_BASE=${LIK_STRIPPED%%-*} 101 | LIK_STRIPPED_END=${LIK_STRIPPED#$LIK_STRIPPED_BASE-} 102 | LIK_FINAL=$LIK_STRIPPED_BASE-$LIK_TYPE-$LIK_STRIPPED_END 103 | 104 | NEWEST_KERNEL=$LIK_FINAL 105 | fi 106 | 107 | echo $NEWEST_KERNEL 108 | } 109 | 110 | # Get the newest kernel on Debian and Rhel based systems. 111 | get_newest_kernel() { 112 | NEWEST_KERNEL= 113 | # Try Debian first as rpm can be installed in Debian based distros 114 | if [ -e /usr/bin/dpkg ]; then 115 | # If DEB based 116 | CURRENT_KERNEL=$1 117 | CURRENT_VERSION=${CURRENT_KERNEL%%-*} 118 | CURRENT_ABI=${CURRENT_KERNEL#*-} 119 | CURRENT_FLAVOUR=${CURRENT_ABI#*-} 120 | CURRENT_ABI=${CURRENT_ABI%%-*} 121 | NEWEST_KERNEL=$(_get_newest_kernel_debian "$CURRENT_VERSION-$CURRENT_ABI") 122 | 123 | elif [ `which rpm >/dev/null` ]; then 124 | # If RPM based 125 | NEWEST_KERNEL=$(_get_newest_kernel_rhel) 126 | fi 127 | 128 | # Make sure that kernel name that we extracted corresponds to an installed 129 | # kernel 130 | if [ -n "$NEWEST_KERNEL" ] && [ `_is_kernel_name_correct $NEWEST_KERNEL` = "no" ]; then 131 | NEWEST_KERNEL= 132 | fi 133 | 134 | echo $NEWEST_KERNEL 135 | } 136 | 137 | NAME=$1 138 | VERSION=$2 139 | TARBALL_ROOT=$3 140 | ARCH=$4 141 | UPGRADE=$5 142 | 143 | if [ -z "$NAME" ] || [ -z "$VERSION" ]; then 144 | echo "Need NAME, and VERSION defined" 145 | echo "ARCH is optional" 146 | exit 1 147 | fi 148 | 149 | # read framework configuration options 150 | if [ -r /etc/dkms/framework.conf ]; then 151 | . /etc/dkms/framework.conf 152 | fi 153 | 154 | KERNELS=$(ls /lib/modules/ 2>/dev/null || true) 155 | CURRENT_KERNEL=$(uname -r) 156 | 157 | #We never want to keep an older version side by side to prevent conflicts 158 | if [ -e "/var/lib/dkms/$NAME/$VERSION" ]; then 159 | echo "Removing old $NAME-$VERSION DKMS files..." 160 | dkms remove -m $NAME -v $VERSION --all 161 | fi 162 | 163 | #Load new files, by source package and by tarball 164 | if [ -f "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz" ]; then 165 | if ! dkms ldtarball --archive "$TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz"; then 166 | echo "" 167 | echo "" 168 | echo "Unable to load DKMS tarball $TARBALL_ROOT/$NAME-$VERSION.dkms.tar.gz." 169 | echo "Common causes include: " 170 | echo " - You must be using DKMS 2.1.0.0 or later to support binaries only" 171 | echo " distribution specific archives." 172 | echo " - Corrupt distribution specific archive" 173 | echo "" 174 | echo "" 175 | exit 2 176 | fi 177 | elif [ -d "/usr/src/$NAME-$VERSION" ]; then 178 | echo "Loading new $NAME-$VERSION DKMS files..." 179 | dkms add -m $NAME -v $VERSION > /dev/null 180 | fi 181 | 182 | # On 1st installation, let us look for a directory 183 | # in /lib/modules which matches `uname -r`. If none 184 | # is found it is possible that buildd is being used 185 | # and that uname -r is giving us the name of the 186 | # kernel used by the buildd machine. 187 | # 188 | # If this is the case we try to build the kernel 189 | # module for each kernel which has a directory in 190 | # /lib/modules. Furthermore we will have to tell 191 | # DKMS which architecture it should build the module 192 | # for (e.g. if the buildd machine is using a 193 | # 2.6.24-23-xen 64bit kernel). 194 | # 195 | # NOTE: if the headers are not installed then the 196 | # module won't be built, as usual 197 | 198 | # Here we look for the most recent kernel so that we can 199 | # build the module for it (in addition to doing it for the 200 | # current kernel. 201 | NEWEST_KERNEL=$(get_newest_kernel "$KERNELS") 202 | 203 | if [ -z "$autoinstall_all_kernels" ]; then 204 | # If the current kernel is installed on the system or chroot 205 | if [ `_is_kernel_name_correct $CURRENT_KERNEL` = "yes" ]; then 206 | if [ -n "$NEWEST_KERNEL" ] && [ ${CURRENT_KERNEL} != ${NEWEST_KERNEL} ]; then 207 | KERNELS="$CURRENT_KERNEL $NEWEST_KERNEL" 208 | else 209 | KERNELS=$CURRENT_KERNEL 210 | fi 211 | # The current kernel is not useful as it's not installed 212 | else 213 | echo "It is likely that $CURRENT_KERNEL belongs to a chroot's host" 214 | 215 | # Let's use only the newest kernel if this is not a first installation 216 | # otherwise build for all kernels 217 | if [ -n "$NEWEST_KERNEL" -a -n "$UPGRADE" ]; then 218 | KERNELS="$NEWEST_KERNEL" 219 | fi 220 | fi 221 | fi 222 | 223 | # Take care of displaying newline separated list 224 | echo "Building for $KERNELS" | tr '\n' ',' \ 225 | | sed -e 's/,/, /g; s/, $/\n/; s/, \([^,]\+\)$/ and \1/' 226 | 227 | if [ -n "$ARCH" ]; then 228 | if which lsb_release >/dev/null && [ $(lsb_release -s -i) = "Ubuntu" ]; then 229 | case $ARCH in 230 | amd64) 231 | ARCH="x86_64" 232 | ;; 233 | lpia|i?86) 234 | ARCH="i686" 235 | ;; 236 | esac 237 | fi 238 | echo "Building for architecture $ARCH" 239 | ARCH="-a $ARCH" 240 | fi 241 | 242 | for KERNEL in $KERNELS; do 243 | dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` 244 | if [ `echo $KERNEL | grep -c "BOOT"` -gt 0 ]; then 245 | echo "" 246 | echo "Module build and install for $KERNEL was skipped as " 247 | echo "it is a BOOT variant" 248 | continue 249 | fi 250 | 251 | 252 | #if the module isn't yet built, try to build it 253 | if [ `echo $dkms_status | grep -c ": built"` -eq 0 ]; then 254 | if [ ! -L /var/lib/dkms/$NAME/$VERSION/source ]; then 255 | echo "This package appears to be a binaries-only package" 256 | echo " you will not be able to build against kernel $KERNEL" 257 | echo " since the package source was not provided" 258 | continue 259 | fi 260 | if _check_kernel_dir $KERNEL; then 261 | echo "Building initial module for $KERNEL" 262 | set +e 263 | dkms build -m $NAME -v $VERSION -k $KERNEL $ARCH > /dev/null 264 | case $? in 265 | 9) 266 | set -e 267 | echo "Skipped." 268 | continue 269 | ;; 270 | 0) 271 | set -e 272 | echo "Done." 273 | ;; 274 | *) 275 | exit $? 276 | ;; 277 | esac 278 | dkms_status=`dkms status -m $NAME -v $VERSION -k $KERNEL $ARCH` 279 | else 280 | echo "Module build for kernel $KERNEL was skipped since the" 281 | echo "kernel headers for this kernel does not seem to be installed." 282 | fi 283 | fi 284 | 285 | #if the module is built (either pre-built or just now), install it 286 | if [ `echo $dkms_status | grep -c ": built"` -eq 1 ] && 287 | [ `echo $dkms_status | grep -c ": installed"` -eq 0 ]; then 288 | dkms install -m $NAME -v $VERSION -k $KERNEL $ARCH 289 | fi 290 | done 291 | 292 | -------------------------------------------------------------------------------- /src_pkg/rpm_pkg.spec: -------------------------------------------------------------------------------- 1 | %define module module-name 2 | 3 | # 4 | # spec file for package tuxedo-keyboard 5 | # 6 | # Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. 7 | # 8 | # All modifications and additions to the file contributed by third parties 9 | # remain the property of their copyright owners, unless otherwise agreed 10 | # upon. The license for this file, and modifications and additions to the 11 | # file, is the same license as for the pristine package itself (unless the 12 | # license for the pristine package is not an Open Source License, in which 13 | # case the license is the MIT License). An "Open Source License" is a 14 | # license that conforms to the Open Source Definition (Version 1.9) 15 | # published by the Open Source Initiative. 16 | 17 | # Please submit bugfixes or comments via http://bugs.opensuse.org/ 18 | # 19 | 20 | 21 | Summary: Kernel module for TUXEDO keyboards 22 | Name: %{module} 23 | Version: x.x.x 24 | Release: x 25 | License: GPLv3+ 26 | Group: Hardware/Other 27 | BuildArch: noarch 28 | Url: https://www.tuxedocomputers.com 29 | Source: %{module}-%{version}.tar.bz2 30 | Provides: tuxedo_keyboard = %{version}-%{release} 31 | Obsoletes: tuxedo_keyboard < %{version}-%{release} 32 | Obsoletes: tuxedo-xp-xc-touchpad-key-fix 33 | Obsoletes: tuxedo-touchpad-fix <= 1.0.1 34 | Obsoletes: tuxedo-cc-wmi 35 | Requires: dkms >= 1.95 36 | BuildRoot: %{_tmppath} 37 | Packager: TUXEDO Computers GmbH 38 | 39 | %description 40 | Keyboard & keyboard backlight driver for TUXEDO notebooks 41 | meant for DKMS framework. 42 | 43 | %prep 44 | %setup -n %{module}-%{version} -q 45 | 46 | %install 47 | rm -rf %{buildroot} 48 | mkdir -p %{buildroot}/usr/src/%{module}-%{version}/ 49 | cp dkms.conf Makefile %{buildroot}/usr/src/%{module}-%{version} 50 | cp -R src/ %{buildroot}/usr/src/%{module}-%{version} 51 | mkdir -p %{buildroot}/usr/share/ 52 | mkdir -p %{buildroot}/usr/share/%{module}/ 53 | cp postinst %{buildroot}/usr/share/%{module} 54 | cp tuxedo_keyboard.conf %{buildroot}/usr/share/%{module} 55 | 56 | %clean 57 | rm -rf %{buildroot} 58 | 59 | %files 60 | %defattr(0644,root,root,0755) 61 | %attr(0755,root,root) /usr/src/%{module}-%{version}/ 62 | %attr(0644,root,root) /usr/src/%{module}-%{version}/* 63 | %attr(0755,root,root) /usr/src/%{module}-%{version}/src/ 64 | %attr(0644,root,root) /usr/src/%{module}-%{version}/src/* 65 | %attr(0755,root,root) /usr/src/%{module}-%{version}/src/tuxedo_io/ 66 | %attr(0644,root,root) /usr/src/%{module}-%{version}/src/tuxedo_io/* 67 | %attr(0755,root,root) /usr/share/%{module}/ 68 | %attr(0755,root,root) /usr/share/%{module}/postinst 69 | %attr(0644,root,root) /usr/share/%{module}/tuxedo_keyboard.conf 70 | %license LICENSE 71 | 72 | %post 73 | for POSTINST in /usr/lib/dkms/common.postinst /usr/share/%{module}/postinst; do 74 | if [ -f $POSTINST ]; then 75 | $POSTINST %{module} %{version} /usr/share/%{module} 76 | RET=$? 77 | 78 | # Attempt to (re-)load module immediately, fail silently if not possible at this stage 79 | 80 | # Also stop tccd service if running before 81 | echo "Check tccd running status" 82 | if systemctl is-active --quiet tccd.service; then 83 | TCCD_RUNNING=true 84 | else 85 | TCCD_RUNNING=false 86 | fi 87 | 88 | if $TCCD_RUNNING; then 89 | echo "Stop tccd temporarily" 90 | systemctl stop tccd 2>&1 || true 91 | fi 92 | 93 | % Explicitly unload old tuxedo_cc_wmi if loaded at this point 94 | rmmod tuxedo_cc_wmi > /dev/null 2>&1 || true 95 | 96 | echo "(Re)load modules if possible" 97 | 98 | rmmod tuxedo_io > /dev/null 2>&1 || true 99 | rmmod uniwill_wmi > /dev/null 2>&1 || true 100 | rmmod clevo_wmi > /dev/null 2>&1 || true 101 | rmmod clevo_acpi > /dev/null 2>&1 || true 102 | rmmod tuxedo_keyboard > /dev/null 2>&1 || true 103 | 104 | modprobe tuxedo_keyboard > /dev/null 2>&1 || true 105 | modprobe uniwill_wmi > /dev/null 2>&1 || true 106 | modprobe clevo_wmi > /dev/null 2>&1 || true 107 | modprobe clevo_acpi > /dev/null 2>&1 || true 108 | modprobe tuxedo_io > /dev/null 2>&1 || true 109 | 110 | # Install default config if none exist already 111 | if [ ! -f "/etc/modprobe.d/tuxedo_keyboard.conf" ]; then 112 | cp -f /usr/share/tuxedo-keyboard/tuxedo_keyboard.conf /etc/modprobe.d/tuxedo_keyboard.conf 113 | fi 114 | 115 | # Restart tccd after reload if it was running 116 | if $TCCD_RUNNING; then 117 | echo "Start tccd again" 118 | systemctl start tccd 2>&1 || true 119 | fi 120 | 121 | exit $RET 122 | fi 123 | echo "WARNING: $POSTINST does not exist." 124 | done 125 | 126 | echo -e "ERROR: DKMS version is too old and %{module} was not" 127 | echo -e "built with legacy DKMS support." 128 | echo -e "You must either rebuild %{module} with legacy postinst" 129 | echo -e "support or upgrade DKMS to a more current version." 130 | exit 1 131 | 132 | 133 | %preun 134 | echo -e 135 | echo -e "Uninstall of %{module} module (version %{version}-%{release}) beginning:" 136 | dkms remove -m %{module} -v %{version} --all --rpm_safe_upgrade 137 | if [ $1 != 1 ];then 138 | /usr/sbin/rmmod %{module} > /dev/null 2>&1 || true 139 | rm -f /etc/modprobe.d/tuxedo_keyboard.conf || true 140 | fi 141 | exit 0 142 | 143 | 144 | %changelog 145 | * Thu Nov 16 2023 C Sandberg 3.2.14-1 146 | - Tweak IBPG8 tdp limits 147 | * Fri Nov 10 2023 C Sandberg 3.2.13-1 148 | - Add missing IBPG8 TDP definitions 149 | * Tue Oct 24 2023 C Sandberg 3.2.12-1 150 | - Polaris/Stellaris AMD Gen5 support 151 | * Thu Sep 21 2023 C Sandberg 3.2.11-1 152 | - Aura Gen3 support 153 | * Thu Aug 10 2023 C Sandberg 3.2.10-1 154 | - Fix build on certain systems 155 | * Tue Aug 01 2023 C Sandberg 3.2.9-1 156 | - Quirks for setting missing IBP16Gen8 keyboard layouts 157 | - Fix IBP 16 Gen8 mk1 layout set to 14inch variant layout 158 | * Fri Jul 21 2023 C Sandberg 3.2.8-1 159 | - Keyboard backlight support for IBS17Gen8 (white-only) 160 | - Fn backlight key support for IBS17Gen8 161 | * Wed Jun 28 2023 C Sandberg 3.2.7-1 162 | - Fix for certain white kbd bl identification on boot (XA15) 163 | - General white-only kbd bl rework to use firmware set on Fn+brightness 164 | switch 165 | - Kernel 6.4 build compatibility fix 166 | * Tue Jun 13 2023 C Sandberg 3.2.6-1 167 | - Fallback ROM ID set quirk support 168 | * Mon May 19 2023 C Sandberg 3.2.5-1 169 | - IBP Gen8 keyboard backlight support 170 | - IBP Gen8 TDP support 171 | - Color scaling for certain one-zone RGB keyboards 172 | - Fix for certain white kbd bl devices (like Pulse) not setting brightness 173 | to zero on init 174 | * Thu Apr 20 2023 C Sandberg 3.2.3-1 175 | - Fix missing state write on resume for some devices which woke up with "default blue" keyboard backlight 176 | - Add TDP device definitions for Stellaris Intel Gen5 177 | - Add device check on newer cpu gens 178 | * Mon Mar 27 2023 C Sandberg 3.2.1-1 179 | - Fix "lost fan control" in some circumstances (on eg. IBPGen7) 180 | * Wed Mar 22 2023 C Sandberg 3.2.0-1 181 | - KBD BL: Interface rewrite, now generally exported through /sys/class/leds :kbd_backlight 182 | - KBD BL: New interface impl. for white backlight keyboards (also :kbd_backlight) 183 | - Note: Old interface is hereby deprecated (and removed) 184 | * Fri Feb 17 2023 C Sandberg 3.1.4-1 185 | - Fix upcoming 6.2 kernel build issue (from github Buddy-Matt) 186 | - Re-write last set charging priority on barrel plug connect 187 | - UW interface performance tweaks (should help with lagging keyboard issues on certain devices) 188 | * Wed Jan 11 2023 C Sandberg 3.1.3-1 189 | - Fix IBP14Gen6 second fan not spinning (alternative fan ctl approach) 190 | - Fix some error-lookalike messages in kernel log (aka prevent uw feature 191 | id when interface not available) 192 | * Mon Dec 19 2022 C Sandberg 3.1.2-1 193 | - Enables dynamic boost (max offset) for certain devices needing sw ctl 194 | - Adds charging profile interface for devices supporting charging profiles 195 | - Adds charging priority interface for devices supporting USB-C PD charging 196 | priority setting 197 | * Mon Oct 17 2022 C Sandberg 3.1.1-1 198 | - Reenable fans-off for some devices that got it turned of as a temporary workaround 199 | - Fix default fan curve not being reenabled when tccd is stopped 200 | * Mon Oct 10 2022 C Sandberg 3.1.0-1 201 | - Add power profiles and tdp functionality (uw) 202 | * Thu Oct 06 2022 C Sandberg 3.0.11-1 203 | - Introduce alternative fan control (uw) 204 | - Fan control parameters from driver "has fan off" and "min fan speed" 205 | - Fixes missing/broken fan control on newer devices 206 | * Thu Apr 28 2022 C Sandberg 3.0.10-1 207 | - Add Stellaris Intel gen 4 lightbar support 208 | - Default lightbar to off 209 | * Mon Oct 10 2021 C Sandberg 3.0.9-1 210 | - Add IBS15v6 & IBS17v6 new module name to perf. prof workaround 211 | - Interface modularization (uw) 212 | - Fix Pulse14/15 gen 1 keyboard backlight ctrl dissapearing 213 | * Fri Jul 9 2021 C Sandberg 3.0.8-1 214 | - Add IBS14v6 to perf. prof workaround 215 | * Thu Jun 24 2021 C Sandberg 3.0.7-1 216 | - Add new Polaris devices gen 2 & gen 3 keyb bl support 217 | - Add Stellaris (gen3) lightbar support 218 | - Fix kernel 5.13 build issue (from github BlackIkeEagle) 219 | - Add another Fusion lightbar ID (from github ArlindoFNeto) 220 | * Mon Jun 07 2021 C Sandberg 3.0.6-1 221 | - Add tuxedo-io performance profile set (cl) 222 | * Fri Apr 23 2021 C Sandberg 3.0.5-1 223 | - Add NS50MU to perf. profile workaround 224 | - Add EDUBOOK1502 to perf. profile workaround 225 | - Add XP gen 11 & 12 to perf. profile workaround 226 | - Clean-up cl driver state init (should fix some init color issues) 227 | * Fri Mar 19 2021 C Sandberg 3.0.4-1 228 | - Fixed various possible race conditions on driver init 229 | - Added IBS14v5 to perf. profile workaround 230 | - Added new Aura board name to perf. profile workaround 231 | - Fixed non-initialized firmware fan curve for silent mode (UW) 232 | - Changed default perf. profile to balanced (UW) 233 | * Fri Mar 5 2021 C Sandberg 3.0.3-1 234 | - Added XP14 to perf. profile workaround 235 | * Fri Jan 29 2021 C Sandberg 3.0.2-1 236 | - Fixed clevo keyboard init order 237 | - Added Aura perf. profile workaround 238 | * Mon Dec 21 2020 C Sandberg 3.0.1-1 239 | - Added device support (Trinity) 240 | - Fixed uw fan ramp up issues to some extent (workaround) 241 | * Wed Dec 9 2020 C Sandberg 3.0.0-1 242 | - Changed structure of clevo interfaces 243 | - Added separate clevo-wmi module with existing functionality 244 | - Added clevo-acpi module with implementation of the "new" clevo ACPI interface 245 | - Added tuxedo-io module (former tuxedo-cc-wmi) into package 246 | * Fri Nov 13 2020 C Sandberg 2.1.0-1 247 | - Added device support (XMG Fusion) 248 | - Added uniwill lightbar driver (with led_classdev interface) 249 | - Added uniwill keymapping brightness up/down 250 | - Fixed uniwill touchpad toggle (some platforms) 251 | - Fixed module cleanup crash 252 | * Fri Sep 25 2020 C Sandberg 2.0.6-1 253 | - Added uw kbd color backlight support 254 | * Thu Jun 18 2020 C Sandberg 2.0.5-1 255 | - Restructure to allow for more devices 256 | - Added device support 257 | - Added rudimentary device detection 258 | * Tue May 26 2020 C Sandberg 2.0.4-1 259 | - Added rfkill key event 260 | - Fix volume button events, ignore 261 | * Tue May 19 2020 C Sandberg 2.0.3-1 262 | - General key event mapping support 263 | - Events added for backlight and touchpad 264 | - Fix not removing module on rpm update 265 | * Tue Apr 14 2020 C Sandberg 2.0.2-0 266 | - Mark old named packages as conflicting and obsolete 267 | - Fix not restoring state on resume 268 | - Fix autoload issues 269 | - Add standard config tuxedo_keyboard.conf to package 270 | * Tue Mar 17 2020 C Sandberg 2.0.1-0 271 | - New packaging 272 | * Wed Dec 18 2019 Richard Sailer 2.0.0-1 273 | - Initial DKMS package for back-lit keyboard 2nd generation 274 | -------------------------------------------------------------------------------- /trans_pkg_from_dkms/deb/tuxedo-keyboard-dkms/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: tuxedo-keyboard-dkms 2 | Version: 2.0.1 3 | Section: oldlibs 4 | Priority: optional 5 | Depends: tuxedo-keyboard 6 | Maintainer: TUXEDO Computers GmbH 7 | Homepage: https://www.tuxedocomputers.com 8 | Architecture: all 9 | Description: Transitional package: tuxedo-keyboard-dkms -> tuxedo-keyboard 10 | This is a transitional package, effectively renaming the package. 11 | -------------------------------------------------------------------------------- /tuxedo_keyboard.conf: -------------------------------------------------------------------------------- 1 | options tuxedo-keyboard kbd_backlight_mode=0 2 | --------------------------------------------------------------------------------