├── LICENSE ├── Kbuild ├── debian ├── source │ └── format ├── gbp.conf ├── rules ├── copyright ├── control └── tuxedo-drivers.dkms ├── src ├── gxtp7380 │ ├── Kbuild │ └── gxtp7380.c ├── ite_8291 │ └── Kbuild ├── ite_8297 │ ├── Kbuild │ └── ite_8297.c ├── ite_829x │ └── Kbuild ├── stk8321 │ └── Kbuild ├── tuxedo_io │ ├── Kbuild │ └── tuxedo_io_ioctl.h ├── ite_8291_lb │ └── Kbuild ├── tuxedo_compatibility_check │ ├── Kbuild │ ├── tuxedo_compatibility_check.h │ └── tuxedo_compatibility_check.c ├── tuxedo_nb02_nvidia_power_ctrl │ ├── Kbuild │ └── tuxedo_nb02_nvidia_power_ctrl.c ├── tuxedo_tuxi │ ├── Kbuild │ ├── tuxi_acpi.h │ ├── tuxi_acpi.c │ └── tuxedo_tuxi_fan_control.c ├── tuxedo_nb04 │ ├── Kbuild │ ├── tuxedo_nb04_wmi_bs.h │ ├── tuxedo_nb04_wmi_ab.h │ ├── tuxedo_nb04_wmi_bs.c │ ├── tuxedo_nb04_kbd_backlight.c │ ├── tuxedo_nb04_keyboard.c │ ├── tuxedo_nb04_sensors.c │ ├── tuxedo_nb04_power_profiles.c │ └── tuxedo_nb04_wmi_ab.c ├── tuxedo_nb05 │ ├── Kbuild │ ├── tuxedo_nb05_kbd_backlight.h │ ├── tuxedo_nb05_power_profiles.h │ ├── tuxedo_nb05_ec.h │ ├── tuxedo_nb05_kbd_backlight.c │ ├── tuxedo_nb05_ec.c │ ├── tuxedo_nb05_sensors.c │ ├── tuxedo_nb05_keyboard.c │ └── tuxedo_nb05_power_profiles.c ├── Kbuild ├── tuxedo_keyboard_common.h ├── clevo_interfaces.h ├── clevo_wmi.c ├── tuxedo_keyboard.c ├── uniwill_interfaces.h └── clevo_acpi.c ├── .editorconfig ├── usr └── lib │ ├── modprobe.d │ └── tuxedo-drivers-backlist-upstream-conflicts.conf │ └── udev │ ├── rules.d │ ├── 99-tuxedo-fix-intel-gen13-sleep-state.rules │ ├── 99-tuxedo-fix-realtek-rts522a-idle-behaviour.rules │ ├── 99-tuxedo-fix-systemd-led-bootdelay.rules │ ├── 99-tuxedo-fix-infinity-flex-touchpanel-toggle.rules │ ├── 99-tuxedo-fix-nb02-touchpad-mouse.rules │ ├── 99-tuxedo-fix-pulse-gen2-wakeup-through-nvme-controller.rules │ └── 99-tuxedo-fix-huawei-lte-modules-correct-drivers.rules │ └── hwdb.d │ ├── 61-sensor-tuxedo.hwdb │ └── 61-keyboard-tuxedo.hwdb ├── .gitignore ├── debian-changelog-to-rpm-changelog.awk ├── .gitlab-ci.yml ├── tuxedo-drivers.spec.in ├── Makefile └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | debian/copyright -------------------------------------------------------------------------------- /Kbuild: -------------------------------------------------------------------------------- 1 | obj-y += src/ 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /src/gxtp7380/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += gxtp7380.o 2 | -------------------------------------------------------------------------------- /src/ite_8291/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += ite_8291.o 2 | -------------------------------------------------------------------------------- /src/ite_8297/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += ite_8297.o 2 | -------------------------------------------------------------------------------- /src/ite_829x/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += ite_829x.o 2 | -------------------------------------------------------------------------------- /src/stk8321/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += stk8321.o 2 | -------------------------------------------------------------------------------- /src/tuxedo_io/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += tuxedo_io.o 2 | -------------------------------------------------------------------------------- /src/ite_8291_lb/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += ite_8291_lb.o 2 | -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [tag] 2 | debian-tag = v%(version)s 3 | -------------------------------------------------------------------------------- /src/tuxedo_compatibility_check/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += tuxedo_compatibility_check.o 2 | -------------------------------------------------------------------------------- /src/tuxedo_nb02_nvidia_power_ctrl/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m := tuxedo_nb02_nvidia_power_ctrl.o 2 | -------------------------------------------------------------------------------- /src/tuxedo_tuxi/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += tuxi_acpi.o 2 | obj-m += tuxedo_tuxi_fan_control.o 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{c,h}] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | indent_style = tab 6 | indent_size = 8 7 | -------------------------------------------------------------------------------- /usr/lib/modprobe.d/tuxedo-drivers-backlist-upstream-conflicts.conf: -------------------------------------------------------------------------------- 1 | # This is a temporary blacklist until uniwill_laptop has reached feature parity 2 | # with tuxedo_keyboard for NB02 devices and can replace it. 3 | blacklist uniwill_laptop 4 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += tuxedo_nb04_keyboard.o 2 | obj-m += tuxedo_nb04_wmi_ab.o 3 | obj-m += tuxedo_nb04_wmi_bs.o 4 | obj-m += tuxedo_nb04_sensors.o 5 | obj-m += tuxedo_nb04_power_profiles.o 6 | obj-m += tuxedo_nb04_kbd_backlight.o 7 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += tuxedo_nb05_keyboard.o 2 | obj-m += tuxedo_nb05_kbd_backlight.o 3 | obj-m += tuxedo_nb05_power_profiles.o 4 | obj-m += tuxedo_nb05_ec.o 5 | obj-m += tuxedo_nb05_sensors.o 6 | obj-m += tuxedo_nb05_fan_control.o 7 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-intel-gen13-sleep-state.rules: -------------------------------------------------------------------------------- 1 | # Intel Gen13 devices: Allow sleeping when not in use 2 | ACTION=="add", \ 3 | SUBSYSTEM=="pci", ATTR{vendor}=="0x8086", ATTR{device}=="0x1135", \ 4 | RUN+="/bin/sh -c 'echo auto | tee /sys/bus/pci/devices/$kernel/power/control'" 5 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-realtek-rts522a-idle-behaviour.rules: -------------------------------------------------------------------------------- 1 | # Realtek Cardreader RTS522A: Force cardreader to stay on when device goes into idle 2 | ACTION=="add", \ 3 | SUBSYSTEM=="pci", ATTR{vendor}=="0x10ec", ATTR{device}=="0x522a", \ 4 | RUN+="/bin/sh -c 'echo on > /sys/bus/pci/devices/$kernel/power/control'" -------------------------------------------------------------------------------- /usr/lib/udev/hwdb.d/61-sensor-tuxedo.hwdb: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # TUXEDO Computers 3 | ########################################################### 4 | 5 | # TUXEDO InfinityFlex 14 Gen1 6 | sensor:accel-base:modalias:i2c:stkh8321:dmi:*:svnTUXEDO:*:skuIFLX14I01:* 7 | ACCEL_MOUNT_MATRIX=1, 0, 0; 0, 1, 0; 0, 0, -1 8 | -------------------------------------------------------------------------------- /usr/lib/udev/hwdb.d/61-keyboard-tuxedo.hwdb: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # TUXEDO Computers 3 | ########################################################### 4 | 5 | # TUXEDO Computers notebooks with board_vendor NB02 6 | evdev:atkbd:dmi:*:svnTUXEDO:*:rvnNB02:* 7 | KEYBOARD_KEY_f8=fn 8 | 9 | # TUXEDO InfinityFlex 14 Gen1 10 | evdev:atkbd:dmi:*:svnTUXEDO:*:rnDN50Z-140HC-YD:* 11 | KEYBOARD_KEY_6e=fn 12 | -------------------------------------------------------------------------------- /src/Kbuild: -------------------------------------------------------------------------------- 1 | obj-m += clevo_acpi.o 2 | obj-m += clevo_wmi.o 3 | obj-m += tuxedo_keyboard.o 4 | obj-m += uniwill_wmi.o 5 | 6 | obj-y += ite_8291/ 7 | obj-y += ite_8291_lb/ 8 | obj-y += ite_8297/ 9 | obj-y += ite_829x/ 10 | obj-y += tuxedo_compatibility_check/ 11 | obj-y += tuxedo_io/ 12 | obj-y += tuxedo_nb02_nvidia_power_ctrl/ 13 | obj-y += tuxedo_nb05/ 14 | obj-y += tuxedo_nb04/ 15 | obj-y += tuxedo_tuxi/ 16 | obj-y += stk8321/ 17 | obj-y += gxtp7380/ 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # make artifacts 2 | *.cmd 3 | *.ko 4 | *.mod 5 | *.mod.c 6 | *.o 7 | *.o.d 8 | modules.order 9 | Module.symvers 10 | 11 | # debuild artifacts 12 | /debian/*.debhelper 13 | /debian/*.debhelper.log 14 | /debian/*.substvars 15 | /debian/debhelper-build-stamp 16 | /debian/files 17 | /debian/tuxedo-drivers 18 | 19 | # rpmbuild artifacts 20 | /src/dkms.conf 21 | /tuxedo-drivers.spec 22 | 23 | # IDE artifacts 24 | *.kate-swp 25 | /*.kdev4 26 | /.vscode 27 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | include /usr/share/dpkg/pkg-info.mk 4 | 5 | %: 6 | dh $@ 7 | 8 | override_dh_install: 9 | dh_install src/. -X*.cmd -X*.d -X*.ko -X*.mod -X*.mod.c -X*.o -Xmodules.order -Xdkms.conf usr/src/$(DEB_SOURCE)-$(DEB_VERSION_UPSTREAM) 10 | dh_install usr / 11 | 12 | override_dh_dkms: 13 | dh_dkms -V 14 | 15 | # do nothing 16 | override_dh_auto_configure override_dh_auto_build override_dh_auto_test override_dh_auto_install override_dh_auto_clean: 17 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-systemd-led-bootdelay.rules: -------------------------------------------------------------------------------- 1 | # Workaround for a systemd bug, that is causing a boot delay, when there are too 2 | # many kbd_backlight devices. 3 | SUBSYSTEM=="leds", KERNEL=="*kbd_backlight*", TAG-="systemd" 4 | SUBSYSTEM=="leds", KERNEL=="*kbd_backlight", TAG+="systemd" 5 | SUBSYSTEM=="leds", KERNEL=="*kbd_backlight_1", TAG+="systemd" 6 | SUBSYSTEM=="leds", KERNEL=="*kbd_backlight_2", TAG+="systemd" 7 | SUBSYSTEM=="leds", KERNEL=="*kbd_backlight_3", TAG+="systemd" 8 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-infinity-flex-touchpanel-toggle.rules: -------------------------------------------------------------------------------- 1 | # TUXEDO Infinity Flex: Required for touchpanel toggle working correctly 2 | ACTION=="add|change", SUBSYSTEM=="acpi", ATTR{hid}=="GXTP7380", \ 3 | ATTR{status}=="15", \ 4 | RUN+="/bin/bash -c \"echo 0 | tee /sys/bus/i2c/devices/i2c-GXTP7380*/0018:27C6*/input/input*/inhibited\"" 5 | ACTION=="add|change", SUBSYSTEM=="acpi", ATTR{hid}=="GXTP7380", \ 6 | ATTR{status}=="0", \ 7 | RUN+="/bin/bash -c \"echo 1 | tee /sys/bus/i2c/devices/i2c-GXTP7380*/0018:27C6*/input/input*/inhibited\"" 8 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-nb02-touchpad-mouse.rules: -------------------------------------------------------------------------------- 1 | # TUXEDO NB02 devices: The touchpad registers an additional bogus mouse device. 2 | # This fix makes libinput ignore that device. This is required for 3 | # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6a9e76f75c1a8fffbf45d4665daaf24e7d30095f 4 | # to take effect, as otherwise this mouse device stays permanently open. 5 | ACTION!="remove", KERNEL=="event[0-9]*", \ 6 | ENV{LIBINPUT_DEVICE_GROUP}=="18/93a/255:i2c-UNIW0001:00", \ 7 | ENV{ID_INPUT_MOUSE}=="1", ENV{LIBINPUT_IGNORE_DEVICE}="1" 8 | -------------------------------------------------------------------------------- /debian-changelog-to-rpm-changelog.awk: -------------------------------------------------------------------------------- 1 | #!/bin/gawk -f 2 | 3 | BEGIN { 4 | RS="(^|\n)tuxedo-(keyboard-ite|keyboard|drivers) " 5 | FS="\n" 6 | print "%changelog" 7 | } 8 | 9 | NR>1 { 10 | split($1, a, " ") 11 | split($(NF-1), b, ">") 12 | split(b[2], c, " ") 13 | gsub(/,/, "", c[1]) 14 | gsub(/ -- /, "", b[1]) 15 | gsub(/\(/, "", a[1]) 16 | gsub(/\)/, "", a[1]) 17 | print "* " c[1] " " c[3] " " c[2] " " c[4] " " b[1] " " a[1] "-1" 18 | for (i=3; i 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_NB05_KBD_BACKLIGHT_H 22 | #define TUXEDO_NB05_KBD_BACKLIGHT_H 23 | #include 24 | void nb05_leds_notify_brightness_change_extern(u8); 25 | #endif 26 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_power_profiles.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_NB05_POWER_PROFILES_H 22 | #define TUXEDO_NB05_POWER_PROFILES_H 23 | void rewrite_last_profile(void); 24 | bool profile_changed_by_driver(void); 25 | #endif 26 | -------------------------------------------------------------------------------- /src/tuxedo_compatibility_check/tuxedo_compatibility_check.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_COMPATIBILITY_CHECK_H 22 | #define TUXEDO_COMPATIBILITY_CHECK_H 23 | 24 | #include 25 | 26 | bool tuxedo_is_compatible(void); 27 | 28 | #endif // TUXEDO_COMPATIBILITY_CHECK_H 29 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers 3 | Upstream-Name: tuxedo-drivers 4 | Upstream-Contact: tux@tuxedocomputers.com 5 | 6 | Files: * 7 | Copyright: 2018-2025 TUXEDO Computers GmbH 8 | License: GPL-2.0+ 9 | This program is free software; you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation; either version 2 of the License, or 12 | (at your option) any later version. 13 | . 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | . 19 | You should have received a copy of the GNU General Public License along 20 | with this program; if not, see . 21 | Comment: 22 | On Debian systems, the complete text of the GNU Lesser General Public 23 | License, can be found in /usr/share/common-licenses/GPL-2. 24 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0+ 2 | 3 | test-build: 4 | rules: 5 | - if: $CI_COMMIT_BRANCH == "main" 6 | - if: $CI_COMMIT_BRANCH == "upcoming-release" 7 | - when: manual 8 | image: registry.gitlab.com/tuxedocomputers/development/installation-testing/docker-images/drivers-test-builder:$DOCKER_TAG 9 | script: 10 | - test-build.sh "$PARAM_TYPE" "$PARAM_LLVM" 11 | parallel: 12 | matrix: 13 | - PARAM_TYPE: ubuntu 14 | DOCKER_TAG: 15 | - ubuntu-20.04 16 | - ubuntu-22.04 17 | - ubuntu-24.04 18 | - PARAM_TYPE: fedora 19 | DOCKER_TAG: 20 | - fedora-41 21 | - fedora-42 22 | - fedora-43 23 | - PARAM_TYPE: debian 24 | DOCKER_TAG: 25 | - debian-12 26 | - debian-exp 27 | - debian-13 28 | - PARAM_TYPE: debian 29 | PARAM_LLVM: llvm 30 | DOCKER_TAG: 31 | - debian-12-llvm 32 | - PARAM_TYPE: arch 33 | DOCKER_TAG: 34 | - archbtw 35 | - PARAM_TYPE: opensuse 36 | DOCKER_TAG: 37 | - opensuse-15.5 38 | - opensuse-15.6 39 | - opensuse-tumbleweed 40 | -------------------------------------------------------------------------------- /usr/lib/udev/rules.d/99-tuxedo-fix-huawei-lte-modules-correct-drivers.rules: -------------------------------------------------------------------------------- 1 | # Huawei LTE Modules: Without this udev rule, the modules have the wrong USB configuration value. 2 | # This leads to the wrong network driver being loaded for the module or the module being not detected at all. 3 | # Either way, no internet connection via this module is possible. 4 | 5 | # Huawei ME936 -> needs the cdc_mbim network driver. 6 | ACTION=="add|change", \ 7 | SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \ 8 | ATTR{idVendor}=="12d1", ATTR{idProduct}=="15bb", ATTR{bNumConfigurations}=="3", \ 9 | ATTR{bConfigurationValue}!="3" ATTR{bConfigurationValue}="3" 10 | 11 | # Huawei ME906s -> needs the cdc_ether network driver. 12 | ACTION=="add|change", \ 13 | SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \ 14 | ATTR{idVendor}=="12d1", ATTR{idProduct}=="15c1", ATTR{bNumConfigurations}=="3", \ 15 | ATTR{bConfigurationValue}!="2" ATTR{bConfigurationValue}="2" 16 | 17 | # HP lt4132 (Huawei ME906s rebranded) -> needs the cdc_ether network driver. 18 | ACTION=="add|change", \ 19 | SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \ 20 | ATTR{idVendor}=="03f0", ATTR{idProduct}=="a31d", ATTR{bNumConfigurations}=="3", \ 21 | ATTR{bConfigurationValue}!="2" ATTR{bConfigurationValue}="2" 22 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: tuxedo-drivers 2 | Section: devel 3 | Priority: optional 4 | Maintainer: TUXEDO Computers GmbH 5 | Build-Depends: debhelper-compat (= 13), dh-sequence-dkms 6 | Rules-Requires-Root: no 7 | Standards-Version: 4.6.0 8 | Homepage: https://tuxedocomputers.com 9 | Vcs-Browser: https://github.com/tuxedocomputers/tuxedo-drivers 10 | Vcs-Git: https://github.com/tuxedocomputers/tuxedo-drivers.git 11 | 12 | Package: tuxedo-drivers 13 | Architecture: all 14 | Depends: ${misc:Depends} 15 | Recommends: udev-hid-bpf 16 | Provides: tuxedo-keyboard (= 4.0.0) 17 | Replaces: tuxedo-cc-wmi (<< 4.0.0~), tuxedo-keyboard (<< 4.0.0~), 18 | tuxedo-keyboard-dkms (<< 4.0.0~), tuxedo-keyboard-ite (<< 4.0.0~), 19 | tuxedo-touchpad-fix (<< 4.0.0~), tuxedo-wmi-dkms (<< 4.0.0~), 20 | tuxedo-xp-xc-airplane-mode-fix (<< 4.0.0~), 21 | tuxedo-xp-xc-touchpad-key-fix (<< 4.0.0~) 22 | Breaks: tuxedo-cc-wmi (<< 4.0.0~), tuxedo-keyboard (<< 4.0.0~), 23 | tuxedo-keyboard-dkms (<< 4.0.0~), tuxedo-keyboard-ite (<< 4.0.0~), 24 | tuxedo-touchpad-fix (<< 4.0.0~), tuxedo-wmi-dkms (<< 4.0.0~), 25 | tuxedo-xp-xc-airplane-mode-fix (<< 4.0.0~), 26 | tuxedo-xp-xc-touchpad-key-fix (<< 4.0.0~) 27 | Description: Kernel modules for TUXEDO devices 28 | Drivers for several platform devices for TUXEDO notebooks meant for DKMS. 29 | -------------------------------------------------------------------------------- /src/tuxedo_tuxi/tuxi_acpi.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXI_ACPI_H 22 | #define TUXI_ACPI_H 23 | 24 | #define TUXI_ACPI_RESOURCE_HID "TUXI0000" 25 | 26 | MODULE_ALIAS("acpi*:" TUXI_ACPI_RESOURCE_HID ":*"); 27 | 28 | enum tuxi_fan_type { 29 | GENERAL = 0, 30 | CPU = 1, 31 | GPU = 2, 32 | }; 33 | 34 | enum tuxi_fan_mode { 35 | AUTO = 0, 36 | MANUAL = 1, 37 | }; 38 | 39 | int tuxi_set_fan_speed(u8 fan_index, u8 fan_speed); 40 | int tuxi_get_fan_speed(u8 fan_index, u8 *fan_speed); 41 | int tuxi_get_nr_fans(u8 *nr_fans); 42 | int tuxi_set_fan_mode(enum tuxi_fan_mode mode); 43 | int tuxi_get_fan_mode(enum tuxi_fan_mode *mode); 44 | int tuxi_get_fan_type(u8 fan_index, enum tuxi_fan_type *type); 45 | int tuxi_get_fan_temp(u8 index, u16 *temp); 46 | int tuxi_get_fan_rpm(u8 index, u16 *rpm); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_ec.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_NB05_EC_H 22 | #define TUXEDO_NB05_EC_H 23 | 24 | #define PULSE1403 "TUXEDO Pulse 14 Gen3" 25 | #define PULSE1404 "TUXEDO Pulse 14 Gen4" 26 | #define IFLX14I01 "TUXEDO InfinityFlex 14 Gen1" 27 | 28 | MODULE_ALIAS("platform:tuxedo_nb05_ec"); 29 | 30 | struct nb05_ec_data_t { 31 | u8 ver_major; 32 | u8 ver_minor; 33 | struct nb05_device_data_t *dev_data; 34 | }; 35 | 36 | struct nb05_device_data_t { 37 | int number_fans; 38 | bool fanctl_onereg; 39 | }; 40 | 41 | void nb05_read_ec_ram(u16 addr, u8 *data); 42 | void nb05_write_ec_ram(u16 addr, u8 data); 43 | void nb05_read_ec_fw_version(u8 *major, u8 *minor); 44 | void nb05_get_ec_data(struct nb05_ec_data_t **ec_data); 45 | 46 | const struct dmi_system_id *nb05_match_device(void); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_wmi_bs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_NB04_WMI_BS_H 22 | #define TUXEDO_NB04_WMI_BS_H 23 | 24 | #define NB04_WMI_BS_GUID "1F174999-3A4E-4311-900D-7BE7166D5055" 25 | MODULE_ALIAS("wmi:" NB04_WMI_BS_GUID); 26 | 27 | enum wmi_system_mode { 28 | WMI_SYSTEM_MODE_BATTERY = 0, 29 | WMI_SYSTEM_MODE_HUMAN = 1, 30 | WMI_SYSTEM_MODE_BEAST = 2, 31 | WMI_SYSTEM_MODE_END, 32 | }; 33 | 34 | enum wmi_return_status { 35 | WMI_RETURN_STATUS_SUCCESS = 0, 36 | WMI_RETURN_STATUS_UNSUPPORTED = 1, 37 | WMI_RETURN_STATUS_INVALID_PARAMETER = 2, 38 | WMI_RETURN_STATUS_UNDEFINED_DEVICE = 3, 39 | WMI_RETURN_STATUS_DEVICE_ERROR = 4, 40 | WMI_RETURN_STATUS_UNEXPECTED_ERROR = 5, 41 | WMI_RETURN_STATUS_TIMEOUT = 6, 42 | WMI_RETURN_STATUS_EC_BUSY = 7, 43 | }; 44 | 45 | #define BS_INPUT_BUFFER_LENGTH 8 46 | #define BS_OUTPUT_BUFFER_LENGTH 80 47 | bool nb04_wmi_bs_available(void); 48 | int nb04_wmi_bs_method(u32 wmi_method_id, u8 *in, u8 *out); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/gxtp7380/gxtp7380.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define DRIVER_NAME "gxtp7380" 28 | 29 | static int gxtp7380_add(struct acpi_device *device) 30 | { 31 | kobject_uevent(&device->dev.kobj, KOBJ_ADD); 32 | return 0; 33 | } 34 | 35 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 36 | static int gxtp7380_remove(struct acpi_device *device) 37 | #else 38 | static void gxtp7380_remove(struct acpi_device *device) 39 | #endif 40 | { 41 | kobject_uevent(&device->dev.kobj, KOBJ_REMOVE); 42 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 43 | return 0; 44 | #endif 45 | } 46 | 47 | static void gxtp7380_notify(struct acpi_device *device, u32 event) 48 | { 49 | kobject_uevent(&device->dev.kobj, KOBJ_CHANGE); 50 | } 51 | 52 | static const struct acpi_device_id gxtp7380_device_ids[] = { 53 | { "GXTP7380", 0 }, 54 | { "", 0 } 55 | }; 56 | 57 | static struct acpi_driver gxtp7380_driver = { 58 | .name = DRIVER_NAME, 59 | .class = DRIVER_NAME, 60 | .ids = gxtp7380_device_ids, 61 | .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, 62 | .ops = { 63 | .add = gxtp7380_add, 64 | .remove = gxtp7380_remove, 65 | .notify = gxtp7380_notify, 66 | }, 67 | }; 68 | 69 | module_acpi_driver(gxtp7380_driver); 70 | 71 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 72 | MODULE_DESCRIPTION("Touch panel disable, notify driver"); 73 | MODULE_LICENSE("GPL"); 74 | 75 | MODULE_DEVICE_TABLE(acpi, gxtp7380_device_ids); 76 | -------------------------------------------------------------------------------- /tuxedo-drivers.spec.in: -------------------------------------------------------------------------------- 1 | Name: tuxedo-drivers 2 | Version: #MODULE_VERSION# 3 | Release: 1%{?dist} 4 | Summary: Kernel modules for TUXEDO devices 5 | 6 | License: GPLv2+ 7 | Url: https://www.tuxedocomputers.com 8 | Source0: %{name}-%{version}.tar.xz 9 | 10 | Requires: dkms >= 2.1 11 | Recommends: udev-hid-bpf 12 | 13 | BuildArch: noarch 14 | 15 | Provides: tuxedo-cc-wmi = 4.0.0-1 16 | Provides: tuxedo-keyboard = 4.0.0-1 17 | Provides: tuxedo-keyboard-dkms = 4.0.0-1 18 | Provides: tuxedo-keyboard-ite = 4.0.0-1 19 | Provides: tuxedo-touchpad-fix = 4.0.0-1 20 | Provides: tuxedo-wmi-dkms = 4.0.0-1 21 | Provides: tuxedo-xp-xc-airplane-mode-fix = 4.0.0-1 22 | Provides: tuxedo-xp-xc-touchpad-key-fix = 4.0.0-1 23 | Obsoletes: tuxedo-cc-wmi < 4.0.0-1 24 | Obsoletes: tuxedo-keyboard < 4.0.0-1 25 | Obsoletes: tuxedo-keyboard-dkms < 4.0.0-1 26 | Obsoletes: tuxedo-keyboard-ite < 4.0.0-1 27 | Obsoletes: tuxedo-touchpad-fix < 4.0.0-1 28 | Obsoletes: tuxedo-wmi-dkms < 4.0.0-1 29 | Obsoletes: tuxedo-xp-xc-airplane-mode-fix < 4.0.0-1 30 | Obsoletes: tuxedo-xp-xc-touchpad-key-fix < 4.0.0-1 31 | 32 | Group: Hardware/Other 33 | Packager: TUXEDO Computers GmbH 34 | 35 | %description 36 | Drivers for several platform devices for TUXEDO notebooks meant for DKMS. 37 | 38 | %prep 39 | %setup -q 40 | 41 | %install 42 | cp -r %{_builddir}/%{name}-%{version}/usr %{buildroot} 43 | 44 | %files 45 | %{_usrsrc}/%{name}-%{version} 46 | %license LICENSE 47 | /usr/lib/modprobe.d/tuxedo-drivers-backlist-upstream-conflicts.conf 48 | /usr/lib/udev/hwdb.d/61-keyboard-tuxedo.hwdb 49 | /usr/lib/udev/hwdb.d/61-sensor-tuxedo.hwdb 50 | /usr/lib/udev/rules.d/99-tuxedo-fix-huawei-lte-modules-correct-drivers.rules 51 | /usr/lib/udev/rules.d/99-tuxedo-fix-infinity-flex-touchpanel-toggle.rules 52 | /usr/lib/udev/rules.d/99-tuxedo-fix-intel-gen13-sleep-state.rules 53 | /usr/lib/udev/rules.d/99-tuxedo-fix-nb02-touchpad-mouse.rules 54 | /usr/lib/udev/rules.d/99-tuxedo-fix-pulse-gen2-wakeup-through-nvme-controller.rules 55 | /usr/lib/udev/rules.d/99-tuxedo-fix-realtek-rts522a-idle-behaviour.rules 56 | /usr/lib/udev/rules.d/99-tuxedo-fix-systemd-led-bootdelay.rules 57 | 58 | %post 59 | dkms add -m %{name} -v %{version} --rpm_safe_upgrade 60 | dkms build -m %{name} -v %{version} 61 | dkms install -m %{name} -v %{version} 62 | 63 | %preun 64 | dkms remove -m %{name} -v %{version} --all --rpm_safe_upgrade 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2018-2025 TUXEDO Computers GmbH 3 | # 4 | # This file is part of tuxedo-drivers. 5 | # 6 | # tuxedo-drivers is free software; you can redistribute it and/or 7 | # modify it under the terms of the GNU General Public License 8 | # as published by the Free Software Foundation; version 2 9 | # of the License. 10 | # 11 | # This program 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 program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | # 20 | 21 | .PHONY: all install clean package package-deb package-rpm 22 | 23 | KDIR := /lib/modules/$(shell uname -r)/build 24 | 25 | PACKAGE_NAME := $(shell grep -Pom1 '.*(?= \(.*\) .*; urgency=.*)' debian/changelog) 26 | PACKAGE_VERSION := $(shell grep -Pom1 '.* \(\K.*(?=\) .*; urgency=.*)' debian/changelog) 27 | 28 | all: 29 | make -C $(KDIR) M=$(PWD) $(MAKEFLAGS) modules 30 | 31 | install: all 32 | make -C $(KDIR) M=$(PWD) $(MAKEFLAGS) modules_install 33 | cp -r usr / 34 | 35 | clean: 36 | make -C $(KDIR) M=$(PWD) $(MAKEFLAGS) clean 37 | rm -f debian/*.debhelper 38 | rm -f debian/*.debhelper.log 39 | rm -f debian/*.substvars 40 | rm -f debian/debhelper-build-stamp 41 | rm -f debian/files 42 | rm -rf debian/tuxedo-drivers 43 | rm -f src/dkms.conf 44 | rm -f tuxedo-drivers.spec 45 | 46 | package: package-deb package-rpm 47 | 48 | package-deb: 49 | debuild --no-tgz-check --no-sign 50 | 51 | package-rpm: 52 | sed 's/#MODULE_VERSION#/$(PACKAGE_VERSION)/' debian/tuxedo-drivers.dkms > src/dkms.conf 53 | sed 's/#MODULE_VERSION#/$(PACKAGE_VERSION)/' tuxedo-drivers.spec.in > tuxedo-drivers.spec 54 | echo >> tuxedo-drivers.spec 55 | ./debian-changelog-to-rpm-changelog.awk debian/changelog >> tuxedo-drivers.spec 56 | mkdir -p $(shell rpm --eval "%{_sourcedir}") 57 | tar --create --file $(shell rpm --eval "%{_sourcedir}")/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.xz \ 58 | --transform="s/debian\/copyright/$(PACKAGE_NAME)-$(PACKAGE_VERSION)\/LICENSE/" \ 59 | --transform="s/usr/$(PACKAGE_NAME)-$(PACKAGE_VERSION)\/usr/" \ 60 | --transform="s/src/$(PACKAGE_NAME)-$(PACKAGE_VERSION)\/usr\/src\/$(PACKAGE_NAME)-$(PACKAGE_VERSION)/" \ 61 | --exclude=*.cmd \ 62 | --exclude=*.ko \ 63 | --exclude=*.mod \ 64 | --exclude=*.mod.c \ 65 | --exclude=*.o \ 66 | --exclude=*.o.d \ 67 | --exclude=modules.order \ 68 | debian/copyright src usr 69 | rpmbuild -ba tuxedo-drivers.spec 70 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_wmi_ab.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_NB04_WMI_AB_H 22 | #define TUXEDO_NB04_WMI_AB_H 23 | 24 | #define NB04_WMI_AB_GUID "80C9BAA6-AC48-4538-9234-9F81A55E7C85" 25 | MODULE_ALIAS("wmi:" NB04_WMI_AB_GUID); 26 | 27 | #define AB_INPUT_BUFFER_LENGTH_NORMAL 8 28 | #define AB_INPUT_BUFFER_LENGTH_EXTENDED 496 29 | #define AB_OUTPUT_BUFFER_LENGTH 80 30 | #define AB_OUTPUT_BUFFER_LENGTH_REDUCED 10 31 | 32 | enum wmi_return_status { 33 | WMI_RETURN_STATUS_SUCCESS = 0, 34 | WMI_RETURN_STATUS_UNSUPPORTED = 1, 35 | WMI_RETURN_STATUS_INVALID_PARAMETER = 2, 36 | WMI_RETURN_STATUS_UNDEFINED_DEVICE = 3, 37 | WMI_RETURN_STATUS_DEVICE_ERROR = 4, 38 | WMI_RETURN_STATUS_UNEXPECTED_ERROR = 5, 39 | WMI_RETURN_STATUS_TIMEOUT = 6, 40 | WMI_RETURN_STATUS_EC_BUSY = 7, 41 | }; 42 | 43 | enum wmi_device_type_id { 44 | WMI_DEVICE_TYPE_ID_TOUCHPAD = 1, 45 | WMI_DEVICE_TYPE_ID_KEYBOARD = 2, 46 | WMI_DEVICE_TYPE_ID_APPDISPLAYPAGES = 3 47 | }; 48 | 49 | enum wmi_keyboard_type { 50 | WMI_KEYBOARD_TYPE_NORMAL = 0, 51 | WMI_KEYBOARD_TYPE_PERKEY = 1, 52 | WMI_KEYBOARD_TYPE_4ZONE = 2, 53 | WMI_KEYBOARD_TYPE_WHITE = 3, 54 | WMI_KEYBOARD_TYPE_MAX, 55 | }; 56 | 57 | enum wmi_keyboard_matrix { 58 | WMI_KEYBOARD_MATRIX_US = 0, 59 | WMI_KEYBOARD_MATRIX_UK = 1 60 | }; 61 | 62 | enum wmi_color_preset { 63 | WMI_COLOR_PRESET_RED = 1, 64 | WMI_COLOR_PRESET_GREEN = 2, 65 | WMI_COLOR_PRESET_YELLOW = 3, 66 | WMI_COLOR_PRESET_BLUE = 4, 67 | WMI_COLOR_PRESET_PURPLE = 5, 68 | WMI_COLOR_PRESET_INDIGO = 6, 69 | WMI_COLOR_PRESET_WHITE = 7 70 | }; 71 | 72 | struct device_keyboard_status_t { 73 | bool keyboard_state_enabled; 74 | enum wmi_keyboard_type keyboard_type; 75 | bool keyboard_sidebar_support; 76 | enum wmi_keyboard_matrix keyboard_matrix; 77 | }; 78 | 79 | int nb04_wmi_ab_method_buffer(u32 wmi_method_id, u8 *in, u8 *out); 80 | int nb04_wmi_ab_method_buffer_reduced_output(u32 wmi_method_id, u8 *in, u8 *out); 81 | int nb04_wmi_ab_method_extended_input(u32 wmi_method_id, u8 *in, u8 *out); 82 | int nb04_wmi_ab_method_int_out(u32 wmi_method_id, u8 *in, u64 *out); 83 | 84 | int wmi_update_device_status_keyboard(struct device_keyboard_status_t *kbds); 85 | int wmi_set_whole_keyboard(u8 red, u8 green, u8 blue, int brightness); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Content 2 | - Description 3 | - Building and Install 4 | - Troubleshooting 5 | - Regarding upstreaming of tuxedo-drivers 6 | 7 | # Description 8 | Drivers for several platform devices for TUXEDO notebooks meant for DKMS. 9 | 10 | ## Features implemented by this driver package 11 | - Fn-keys 12 | - Keyboard backlight 13 | - Fan control 14 | - Power control 15 | - Other sensors 16 | - Hardware specific userspace quirks 17 | 18 | ## Modules included in this package 19 | - clevo_acpi 20 | - clevo_wmi 21 | - tuxedo_keyboard 22 | - uniwill_wmi 23 | - ite_8291 24 | - ite_8291_lb 25 | - ite_8297 26 | - ite_829x 27 | - tuxedo_io 28 | - tuxedo_compatibility_check 29 | - tuxedo_nb05_keyboard 30 | - tuxedo_nb05_power_profiles 31 | - tuxedo_nb05_ec 32 | - tuxedo_nb05_sensors 33 | - tuxedo_nb04_keyboard 34 | - tuxedo_nb04_wmi_ab 35 | - tuxedo_nb04_wmi_bs 36 | - tuxedo_nb04_sensors 37 | - tuxedo_nb04_power_profiles 38 | - tuxedo_nb04_kbd_backlight 39 | - tuxedo_nb05_kbd_backlight 40 | - tuxedo_nb02_nvidia_power_ctrl 41 | - tuxedo_nb05_fan_control 42 | - tuxi_acpi 43 | - tuxedo_tuxi_fan_control 44 | - stk8321 45 | - gxtp7380 46 | 47 | # Building and Install 48 | 49 | ## Dependencies: 50 | All: 51 | - make 52 | 53 | `make package-deb`: 54 | - devscripts 55 | - debhelper 56 | - dh-dkms 57 | 58 | `make package-rpm`: 59 | - rpm 60 | 61 | # Troubleshooting 62 | 63 | ## The keyboard backlight control and/or touchpad toggle key combinations do not work 64 | For all devices with a touchpad toggle key(-combo) and some devices with keyboard backlight control key-combos the driver does nothing more then to send the corresponding key event to userspace where it is the desktop environments duty to carry out the action. Some smaller desktop environments however don't bind an action to these keys by default so it seems that these keys don't work. 65 | 66 | Please refer to your desktop environments documentation on how to set custom keybindings to fix this. 67 | 68 | For keyboard brightness control you should use the D-Bus interface of UPower as actions for the key presses. 69 | 70 | For touchpad toggle on X11 you can use `xinput` to enable/disable the touchpad, on Wayland the correct way is desktop environment specific. 71 | 72 | # Regarding upstreaming of tuxedo-drivers 73 | The code, while perfectly functional, is currently not in an upstreamable state. That being said we started an upstreaming effort and the first small part, the keyboard backlight control for the Sirius 16 Gen 1 & 2, already got accepted. 74 | 75 | If you want to hack away at this matter yourself please follow the following precautions and guidelines to avoid breakages on both software and hardware level: 76 | - Involve us in the whole process. Nothing is won if at some point tuxedo-control-center or the dkms variant of tuxedo-drivers stops working. Especially when you send something to the LKML, please set us in the cc. 77 | - We mostly can't share documentation, but we can answer questions. 78 | - Code interacting with the EC, which is most of tuxedo-drivers, can brick devices and therefore must be ensured to only run on compatible and tested devices. 79 | - If you use tuxedo-drivers as a reference or code snippets from it, a "Codeveloped-by:\ \" must be included in your upstream commit, with \ and \ depending on the actual part of tuxedo-drivers being used. Please talk to us regarding this. 80 | -------------------------------------------------------------------------------- /debian/tuxedo-drivers.dkms: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME="tuxedo-drivers" 2 | PACKAGE_VERSION="#MODULE_VERSION#" 3 | AUTOINSTALL="yes" 4 | 5 | DEST_MODULE_LOCATION[0]="/kernel/lib/" 6 | BUILT_MODULE_NAME[0]="clevo_acpi" 7 | 8 | DEST_MODULE_LOCATION[1]="/kernel/lib/" 9 | BUILT_MODULE_NAME[1]="clevo_wmi" 10 | 11 | DEST_MODULE_LOCATION[2]="/kernel/lib/" 12 | BUILT_MODULE_NAME[2]="tuxedo_keyboard" 13 | 14 | DEST_MODULE_LOCATION[3]="/kernel/lib/" 15 | BUILT_MODULE_NAME[3]="uniwill_wmi" 16 | 17 | DEST_MODULE_LOCATION[4]="/kernel/lib/" 18 | BUILT_MODULE_NAME[4]="ite_8291" 19 | BUILT_MODULE_LOCATION[4]="ite_8291/" 20 | 21 | DEST_MODULE_LOCATION[5]="/kernel/lib/" 22 | BUILT_MODULE_NAME[5]="ite_8291_lb" 23 | BUILT_MODULE_LOCATION[5]="ite_8291_lb/" 24 | 25 | DEST_MODULE_LOCATION[6]="/kernel/lib/" 26 | BUILT_MODULE_NAME[6]="ite_8297" 27 | BUILT_MODULE_LOCATION[6]="ite_8297/" 28 | 29 | DEST_MODULE_LOCATION[7]="/kernel/lib/" 30 | BUILT_MODULE_NAME[7]="ite_829x" 31 | BUILT_MODULE_LOCATION[7]="ite_829x/" 32 | 33 | DEST_MODULE_LOCATION[8]="/kernel/lib/" 34 | BUILT_MODULE_NAME[8]="tuxedo_io" 35 | BUILT_MODULE_LOCATION[8]="tuxedo_io" 36 | 37 | DEST_MODULE_LOCATION[9]="/kernel/lib/" 38 | BUILT_MODULE_NAME[9]="tuxedo_compatibility_check" 39 | BUILT_MODULE_LOCATION[9]="tuxedo_compatibility_check" 40 | 41 | DEST_MODULE_LOCATION[10]="/kernel/lib/" 42 | BUILT_MODULE_NAME[10]="tuxedo_nb05_keyboard" 43 | BUILT_MODULE_LOCATION[10]="tuxedo_nb05" 44 | 45 | DEST_MODULE_LOCATION[11]="/kernel/lib/" 46 | BUILT_MODULE_NAME[11]="tuxedo_nb05_power_profiles" 47 | BUILT_MODULE_LOCATION[11]="tuxedo_nb05" 48 | 49 | DEST_MODULE_LOCATION[12]="/kernel/lib/" 50 | BUILT_MODULE_NAME[12]="tuxedo_nb05_ec" 51 | BUILT_MODULE_LOCATION[12]="tuxedo_nb05" 52 | 53 | DEST_MODULE_LOCATION[13]="/kernel/lib/" 54 | BUILT_MODULE_NAME[13]="tuxedo_nb05_sensors" 55 | BUILT_MODULE_LOCATION[13]="tuxedo_nb05" 56 | 57 | DEST_MODULE_LOCATION[14]="/kernel/lib/" 58 | BUILT_MODULE_NAME[14]="tuxedo_nb04_keyboard" 59 | BUILT_MODULE_LOCATION[14]="tuxedo_nb04" 60 | 61 | DEST_MODULE_LOCATION[15]="/kernel/lib/" 62 | BUILT_MODULE_NAME[15]="tuxedo_nb04_wmi_ab" 63 | BUILT_MODULE_LOCATION[15]="tuxedo_nb04" 64 | 65 | DEST_MODULE_LOCATION[16]="/kernel/lib/" 66 | BUILT_MODULE_NAME[16]="tuxedo_nb04_wmi_bs" 67 | BUILT_MODULE_LOCATION[16]="tuxedo_nb04" 68 | 69 | DEST_MODULE_LOCATION[17]="/kernel/lib/" 70 | BUILT_MODULE_NAME[17]="tuxedo_nb04_sensors" 71 | BUILT_MODULE_LOCATION[17]="tuxedo_nb04" 72 | 73 | DEST_MODULE_LOCATION[18]="/kernel/lib/" 74 | BUILT_MODULE_NAME[18]="tuxedo_nb04_power_profiles" 75 | BUILT_MODULE_LOCATION[18]="tuxedo_nb04" 76 | 77 | DEST_MODULE_LOCATION[19]="/kernel/lib/" 78 | BUILT_MODULE_NAME[19]="tuxedo_nb04_kbd_backlight" 79 | BUILT_MODULE_LOCATION[19]="tuxedo_nb04" 80 | 81 | DEST_MODULE_LOCATION[20]="/kernel/lib/" 82 | BUILT_MODULE_NAME[20]="tuxedo_nb05_kbd_backlight" 83 | BUILT_MODULE_LOCATION[20]="tuxedo_nb05" 84 | 85 | DEST_MODULE_LOCATION[21]="/kernel/lib/" 86 | BUILT_MODULE_NAME[21]="tuxedo_nb02_nvidia_power_ctrl" 87 | BUILT_MODULE_LOCATION[21]="tuxedo_nb02_nvidia_power_ctrl" 88 | 89 | DEST_MODULE_LOCATION[22]="/kernel/lib/" 90 | BUILT_MODULE_NAME[22]="tuxedo_nb05_fan_control" 91 | BUILT_MODULE_LOCATION[22]="tuxedo_nb05" 92 | 93 | DEST_MODULE_LOCATION[23]="/kernel/lib/" 94 | BUILT_MODULE_NAME[23]="tuxi_acpi" 95 | BUILT_MODULE_LOCATION[23]="tuxedo_tuxi" 96 | 97 | DEST_MODULE_LOCATION[24]="/kernel/lib/" 98 | BUILT_MODULE_NAME[24]="tuxedo_tuxi_fan_control" 99 | BUILT_MODULE_LOCATION[24]="tuxedo_tuxi" 100 | 101 | DEST_MODULE_LOCATION[25]="/kernel/lib/" 102 | BUILT_MODULE_NAME[25]="stk8321" 103 | BUILT_MODULE_LOCATION[25]="stk8321" 104 | 105 | DEST_MODULE_LOCATION[26]="/kernel/lib/" 106 | BUILT_MODULE_NAME[26]="gxtp7380" 107 | BUILT_MODULE_LOCATION[26]="gxtp7380" 108 | -------------------------------------------------------------------------------- /src/tuxedo_keyboard_common.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_KEYBOARD_COMMON_H 22 | #define TUXEDO_KEYBOARD_COMMON_H 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* :::: Module specific Constants and simple Macros :::: */ 33 | #define __TUXEDO_PR(lvl, fmt, ...) do { pr_##lvl(fmt, ##__VA_ARGS__); } while (0) 34 | #define TUXEDO_INFO(fmt, ...) __TUXEDO_PR(info, fmt, ##__VA_ARGS__) 35 | #define TUXEDO_ERROR(fmt, ...) __TUXEDO_PR(err, fmt, ##__VA_ARGS__) 36 | #define TUXEDO_DEBUG(fmt, ...) __TUXEDO_PR(debug, "[%s:%u] " fmt, __func__, __LINE__, ##__VA_ARGS__) 37 | 38 | #ifndef DRIVER_NAME 39 | #define DRIVER_NAME "tuxedo_keyboard" 40 | #endif 41 | 42 | struct tuxedo_keyboard_driver { 43 | // Platform driver provided by driver 44 | struct platform_driver *platform_driver; 45 | // Probe method provided by driver 46 | int (*probe)(struct platform_device *); 47 | // Keymap provided by driver 48 | struct key_entry *key_map; 49 | // Input device reference filled in on module init after probe success 50 | struct input_dev *input_device; 51 | // test if acpi or wmi functions for fn lock are exposed and functional 52 | bool (*fn_lock_available)(void); 53 | // show function for sysfs device fn_lock 54 | ssize_t (*fn_lock_show)(struct device *, struct device_attribute *, char *); 55 | // store function for sysfs device fn_lock 56 | ssize_t (*fn_lock_store)(struct device *, struct device_attribute *, const char *, size_t); 57 | }; 58 | 59 | // Global module devices 60 | static struct platform_device *tuxedo_platform_device = NULL; 61 | static struct input_dev *tuxedo_input_device = NULL; 62 | 63 | // Currently chosen driver 64 | static struct tuxedo_keyboard_driver *current_driver = NULL; 65 | 66 | struct platform_device *tuxedo_keyboard_init_driver(struct tuxedo_keyboard_driver *tk_driver); 67 | void tuxedo_keyboard_remove_driver(struct tuxedo_keyboard_driver *tk_driver); 68 | 69 | /** 70 | * Basically a copy of the existing report event but doesn't report unknown events 71 | */ 72 | static bool sparse_keymap_report_known_event(struct input_dev *dev, unsigned int code, 73 | unsigned int value, bool autorelease) 74 | { 75 | const struct key_entry *ke = 76 | sparse_keymap_entry_from_scancode(dev, code); 77 | 78 | if (ke) { 79 | sparse_keymap_report_entry(dev, ke, value, autorelease); 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | 86 | struct color_t { 87 | u32 code; 88 | char* name; 89 | }; 90 | 91 | struct color_list_t { 92 | uint size; 93 | struct color_t colors[]; 94 | }; 95 | 96 | /** 97 | * Commonly used standard colors 98 | */ 99 | static struct color_list_t color_list = { 100 | .size = 8, 101 | .colors = { 102 | { .name = "BLACK", .code = 0x000000 }, // 0 103 | { .name = "RED", .code = 0xFF0000 }, // 1 104 | { .name = "GREEN", .code = 0x00FF00 }, // 2 105 | { .name = "BLUE", .code = 0x0000FF }, // 3 106 | { .name = "YELLOW", .code = 0xFFFF00 }, // 4 107 | { .name = "MAGENTA", .code = 0xFF00FF }, // 5 108 | { .name = "CYAN", .code = 0x00FFFF }, // 6 109 | { .name = "WHITE", .code = 0xFFFFFF }, // 7 110 | } 111 | }; 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /src/clevo_interfaces.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2020-2021 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef CLEVO_INTERFACES_H 22 | #define CLEVO_INTERFACES_H 23 | 24 | #include 25 | #include 26 | 27 | #define CLEVO_WMI_EVENT_GUID "ABBC0F6B-8EA1-11D1-00A0-C90629100000" 28 | #define CLEVO_WMI_EMAIL_GUID "ABBC0F6C-8EA1-11D1-00A0-C90629100000" 29 | #define CLEVO_WMI_METHOD_GUID "ABBC0F6D-8EA1-11D1-00A0-C90629100000" 30 | 31 | #define CLEVO_ACPI_RESOURCE_HID "CLV0001" 32 | #define CLEVO_ACPI_DSM_UUID "93f224e4-fbdc-4bbf-add6-db71bdc0afad" 33 | 34 | // The clevo get commands expect no parameters 35 | #define CLEVO_CMD_GET_FANINFO1 0x63 36 | #define CLEVO_CMD_GET_FANINFO2 0x64 37 | #define CLEVO_CMD_GET_FANINFO3 0x6e 38 | 39 | #define CLEVO_CMD_GET_WEBCAM_SW 0x06 40 | #define CLEVO_CMD_GET_FLIGHTMODE_SW 0x07 41 | #define CLEVO_CMD_GET_TOUCHPAD_SW 0x09 42 | 43 | #define CLEVO_CMD_GET_EVENT 0x01 44 | 45 | #define CLEVO_CMD_GET_SPECS 0x0D // Returns buffer -> only works with clevo_evaluate_method2 46 | #define CLEVO_CMD_GET_BIOS_FEATURES_1 0x52 47 | #define CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_WHITE_ONLY_KB 0x40000000 48 | #define CLEVO_CMD_GET_BIOS_FEATURES_1_SUB_3_ZONE_RGB_KB 0x00400000 49 | 50 | #define CLEVO_CMD_GET_BIOS_FEATURES_2 0x7A 51 | #define CLEVO_CMD_GET_BIOS_FEATURES_2_SUB_WHITE_ONLY_KB_MAX_5 0x4000 52 | 53 | #define CLEVO_CMD_GET_KB_WHITE_LEDS 0x3D // Get brightness of white only keyboard backlights 54 | 55 | // The clevo set commands expect a parameter 56 | #define CLEVO_CMD_SET_FANSPEED_VALUE 0x68 57 | #define CLEVO_CMD_SET_FANSPEED_AUTO 0x69 58 | 59 | #define CLEVO_CMD_SET_WEBCAM_SW 0x22 60 | #define CLEVO_CMD_SET_FLIGHTMODE_SW 0x20 61 | #define CLEVO_CMD_SET_TOUCHPAD_SW 0x2a 62 | 63 | #define CLEVO_CMD_SET_EVENTS_ENABLED 0x46 64 | 65 | #define CLEVO_CMD_SET_KB_WHITE_LEDS 0x27 // Set brightness of white only keyboard backlights 66 | #define CLEVO_CMD_SET_KB_RGB_LEDS 0x67 // Used to set color, brightness, blinking pattern, etc. 67 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_0 0xF0000000 // 1-zone RGB and 3-zone RGB left 68 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_1 0xF1000000 // 3-zone RGB center 69 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_2 0xF2000000 // 3-Zone RGB right 70 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_ZONE_3 0xF3000000 // Unused on all known Clevo devices 71 | #define CLEVO_CMD_SET_KB_LEDS_SUB_RGB_BRIGHTNESS 0xF4000000 72 | 73 | #define CLEVO_CMD_OPT 0x79 74 | #define CLEVO_CMD_OPT_SUB_SET_PERF_PROF 0x19 75 | 76 | struct clevo_interface_t { 77 | char *string_id; 78 | void (*event_callb)(u32); 79 | int (*method_call)(u8, u32, union acpi_object **); 80 | int (*method_call_pkgbuf)(u8, u8 *, u32, union acpi_object **); 81 | }; 82 | 83 | int clevo_keyboard_add_interface(struct clevo_interface_t *new_interface); 84 | int clevo_keyboard_remove_interface(struct clevo_interface_t *interface); 85 | int clevo_evaluate_method(u8 cmd, u32 arg, u32 *result); 86 | int clevo_evaluate_method2(u8 cmd, u32 arg, union acpi_object **result); 87 | int clevo_get_active_interface_id(char **id_str); 88 | 89 | #define MODULE_ALIAS_CLEVO_WMI() \ 90 | MODULE_ALIAS("wmi:" CLEVO_WMI_EVENT_GUID); \ 91 | MODULE_ALIAS("wmi:" CLEVO_WMI_METHOD_GUID); 92 | 93 | #define CLEVO_INTERFACE_WMI_STRID "clevo_wmi" 94 | #define CLEVO_INTERFACE_ACPI_STRID "clevo_acpi" 95 | 96 | #define MODULE_ALIAS_CLEVO_ACPI() \ 97 | MODULE_ALIAS("acpi*:" CLEVO_ACPI_RESOURCE_HID ":*"); 98 | 99 | #define MODULE_ALIAS_CLEVO_INTERFACES() \ 100 | MODULE_ALIAS_CLEVO_WMI(); \ 101 | MODULE_ALIAS_CLEVO_ACPI(); 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /src/tuxedo_io/tuxedo_io_ioctl.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2019-2022 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef TUXEDO_IO_IOCTL_H 22 | #define TUXEDO_IO_IOCTL_H 23 | 24 | #define IOCTL_MAGIC 0xEC 25 | 26 | #define MAGIC_READ_CL IOCTL_MAGIC + 1 27 | #define MAGIC_WRITE_CL IOCTL_MAGIC + 2 28 | 29 | #define MAGIC_READ_UW IOCTL_MAGIC + 3 30 | #define MAGIC_WRITE_UW IOCTL_MAGIC + 4 31 | 32 | #define MOD_API_MIN_VERSION "0.2.6" // IMPORTANT: Needs to be updated when a new ioctl is added 33 | 34 | // General 35 | #define R_MOD_VERSION _IOR(IOCTL_MAGIC, 0x00, char*) 36 | 37 | #define R_HWCHECK_CL _IOR(IOCTL_MAGIC, 0x05, int32_t*) 38 | #define R_HWCHECK_UW _IOR(IOCTL_MAGIC, 0x06, int32_t*) 39 | 40 | /** 41 | * Clevo interface 42 | */ 43 | 44 | // Read 45 | #define R_CL_HW_IF_STR _IOR(MAGIC_READ_CL, 0x00, char*) 46 | #define R_CL_FANINFO1 _IOR(MAGIC_READ_CL, 0x10, int32_t*) 47 | #define R_CL_FANINFO2 _IOR(MAGIC_READ_CL, 0x11, int32_t*) 48 | #define R_CL_FANINFO3 _IOR(MAGIC_READ_CL, 0x12, int32_t*) 49 | // #define R_FANINFO4 _IOR(MAGIC_READ_CL, 0x04, int32_t*) 50 | 51 | #define R_CL_WEBCAM_SW _IOR(MAGIC_READ_CL, 0x13, int32_t*) 52 | #define R_CL_FLIGHTMODE_SW _IOR(MAGIC_READ_CL, 0x14, int32_t*) 53 | #define R_CL_TOUCHPAD_SW _IOR(MAGIC_READ_CL, 0x15, int32_t*) 54 | 55 | #ifdef DEBUG 56 | #define R_TF_BC _IOW(MAGIC_READ_CL, 0x91, uint32_t*) 57 | #endif 58 | 59 | // Write 60 | #define W_CL_FANSPEED _IOW(MAGIC_WRITE_CL, 0x10, int32_t*) 61 | #define W_CL_FANAUTO _IOW(MAGIC_WRITE_CL, 0x11, int32_t*) 62 | 63 | #define W_CL_WEBCAM_SW _IOW(MAGIC_WRITE_CL, 0x12, int32_t*) 64 | #define W_CL_FLIGHTMODE_SW _IOW(MAGIC_WRITE_CL, 0x13, int32_t*) 65 | #define W_CL_TOUCHPAD_SW _IOW(MAGIC_WRITE_CL, 0x14, int32_t*) 66 | #define W_CL_PERF_PROFILE _IOW(MAGIC_WRITE_CL, 0x15, int32_t*) 67 | 68 | #ifdef DEBUG 69 | #define W_TF_BC _IOW(MAGIC_WRITE_CL, 0x91, uint32_t*) 70 | #endif 71 | 72 | /** 73 | * Uniwill interface 74 | */ 75 | 76 | // Read 77 | #define R_UW_HW_IF_STR _IOR(MAGIC_READ_UW, 0x00, char*) 78 | #define R_UW_MODEL_ID _IOR(MAGIC_READ_UW, 0x01, int32_t*) 79 | #define R_UW_FANSPEED _IOR(MAGIC_READ_UW, 0x10, int32_t*) 80 | #define R_UW_FANSPEED2 _IOR(MAGIC_READ_UW, 0x11, int32_t*) 81 | #define R_UW_FAN_TEMP _IOR(MAGIC_READ_UW, 0x12, int32_t*) 82 | #define R_UW_FAN_TEMP2 _IOR(MAGIC_READ_UW, 0x13, int32_t*) 83 | 84 | #define R_UW_MODE _IOR(MAGIC_READ_UW, 0x14, int32_t*) 85 | #define R_UW_MODE_ENABLE _IOR(MAGIC_READ_UW, 0x15, int32_t*) 86 | #define R_UW_FANS_OFF_AVAILABLE _IOR(MAGIC_READ_UW, 0x16, int32_t*) 87 | #define R_UW_FANS_MIN_SPEED _IOR(MAGIC_READ_UW, 0x17, int32_t*) 88 | 89 | #define R_UW_TDP0 _IOR(MAGIC_READ_UW, 0x18, int32_t*) 90 | #define R_UW_TDP1 _IOR(MAGIC_READ_UW, 0x19, int32_t*) 91 | #define R_UW_TDP2 _IOR(MAGIC_READ_UW, 0x1a, int32_t*) 92 | #define R_UW_TDP0_MIN _IOR(MAGIC_READ_UW, 0x1b, int32_t*) 93 | #define R_UW_TDP1_MIN _IOR(MAGIC_READ_UW, 0x1c, int32_t*) 94 | #define R_UW_TDP2_MIN _IOR(MAGIC_READ_UW, 0x1d, int32_t*) 95 | #define R_UW_TDP0_MAX _IOR(MAGIC_READ_UW, 0x1e, int32_t*) 96 | #define R_UW_TDP1_MAX _IOR(MAGIC_READ_UW, 0x1f, int32_t*) 97 | #define R_UW_TDP2_MAX _IOR(MAGIC_READ_UW, 0x20, int32_t*) 98 | 99 | #define R_UW_PROFS_AVAILABLE _IOR(MAGIC_READ_UW, 0x21, int32_t*) 100 | 101 | // Write 102 | #define W_UW_FANSPEED _IOW(MAGIC_WRITE_UW, 0x10, int32_t*) 103 | #define W_UW_FANSPEED2 _IOW(MAGIC_WRITE_UW, 0x11, int32_t*) 104 | #define W_UW_MODE _IOW(MAGIC_WRITE_UW, 0x12, int32_t*) 105 | #define W_UW_MODE_ENABLE _IOW(MAGIC_WRITE_UW, 0x13, int32_t*) 106 | #define W_UW_FANAUTO _IO(MAGIC_WRITE_UW, 0x14) // undo all previous calls of W_UW_FANSPEED and W_UW_FANSPEED2 107 | 108 | #define W_UW_TDP0 _IOW(MAGIC_WRITE_UW, 0x15, int32_t*) 109 | #define W_UW_TDP1 _IOW(MAGIC_WRITE_UW, 0x16, int32_t*) 110 | #define W_UW_TDP2 _IOW(MAGIC_WRITE_UW, 0x17, int32_t*) 111 | 112 | #define W_UW_PERF_PROF _IOW(MAGIC_WRITE_UW, 0x18, int32_t*) 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_wmi_bs.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 27 | #include "tuxedo_nb04_wmi_bs.h" 28 | 29 | #define BS_INPUT_BUFFER_LENGTH 8 30 | #define BS_OUTPUT_BUFFER_LENGTH 80 31 | 32 | struct driver_data_t {}; 33 | 34 | static DEFINE_MUTEX(nb04_wmi_bs_access_lock); 35 | 36 | static struct wmi_device *__wmi_dev; 37 | 38 | bool nb04_wmi_bs_available(void) 39 | { 40 | if (__wmi_dev) 41 | return true; 42 | else 43 | return false; 44 | } 45 | EXPORT_SYMBOL(nb04_wmi_bs_available); 46 | 47 | static int __nb04_wmi_bs_method(struct wmi_device *wdev, u32 wmi_method_id, 48 | u8 *in, u8 *out) 49 | { 50 | struct acpi_buffer acpi_buffer_in = { (acpi_size)BS_INPUT_BUFFER_LENGTH, in }; 51 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 52 | union acpi_object *acpi_object_out; 53 | acpi_status status; 54 | 55 | mutex_lock(&nb04_wmi_bs_access_lock); 56 | 57 | pr_debug("evaluate: %u\n", wmi_method_id); 58 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 59 | &acpi_buffer_in, &return_buffer); 60 | 61 | mutex_unlock(&nb04_wmi_bs_access_lock); 62 | 63 | if (ACPI_FAILURE(status)) { 64 | pr_err("failed to evaluate wmi method %u\n", wmi_method_id); 65 | return -EIO; 66 | } 67 | 68 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 69 | if (!acpi_object_out) 70 | return -ENODATA; 71 | 72 | if (acpi_object_out->type != ACPI_TYPE_BUFFER) { 73 | // Returns an int 0 when not finding a valid method number 74 | kfree(return_buffer.pointer); 75 | return -EINVAL; 76 | } 77 | 78 | if (acpi_object_out->buffer.length != BS_OUTPUT_BUFFER_LENGTH) { 79 | pr_err("Unexpected buffer length: %u for method (%u) call\n", 80 | acpi_object_out->buffer.length, wmi_method_id); 81 | kfree(return_buffer.pointer); 82 | return -EIO; 83 | } 84 | 85 | memcpy(out, acpi_object_out->buffer.pointer, BS_OUTPUT_BUFFER_LENGTH); 86 | kfree(return_buffer.pointer); 87 | 88 | return 0; 89 | } 90 | 91 | /** 92 | * Method interface 8 bytes in 80 bytes out 93 | */ 94 | int nb04_wmi_bs_method(u32 wmi_method_id, u8 *in, u8 *out) 95 | { 96 | if (__wmi_dev) 97 | return __nb04_wmi_bs_method(__wmi_dev, wmi_method_id, in, out); 98 | else 99 | return -ENODEV; 100 | } 101 | EXPORT_SYMBOL(nb04_wmi_bs_method); 102 | 103 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 104 | static int tuxedo_nb04_wmi_probe(struct wmi_device *wdev) 105 | #else 106 | static int tuxedo_nb04_wmi_probe(struct wmi_device *wdev, const void *dummy_context) 107 | #endif 108 | { 109 | struct driver_data_t *driver_data; 110 | 111 | pr_debug("driver probe\n"); 112 | 113 | if (!tuxedo_is_compatible()) 114 | return -ENODEV; 115 | 116 | if (!wmi_has_guid(NB04_WMI_BS_GUID)) 117 | return -ENODEV; 118 | 119 | __wmi_dev = wdev; 120 | 121 | driver_data = devm_kzalloc(&wdev->dev, sizeof(struct driver_data_t), GFP_KERNEL); 122 | if (!driver_data) 123 | return -ENOMEM; 124 | 125 | dev_set_drvdata(&wdev->dev, driver_data); 126 | 127 | return 0; 128 | } 129 | 130 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 131 | static int tuxedo_nb04_wmi_remove(struct wmi_device *wdev) 132 | #else 133 | static void tuxedo_nb04_wmi_remove(struct wmi_device *wdev) 134 | #endif 135 | { 136 | pr_debug("driver remove\n"); 137 | 138 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 139 | return 0; 140 | #endif 141 | } 142 | 143 | static const struct wmi_device_id tuxedo_nb04_wmi_device_ids[] = { 144 | { .guid_string = NB04_WMI_BS_GUID }, 145 | { } 146 | }; 147 | 148 | static struct wmi_driver tuxedo_nb04_wmi_driver = { 149 | .driver = { 150 | .name = "tuxedo_nb04_wmi", 151 | .owner = THIS_MODULE 152 | }, 153 | .id_table = tuxedo_nb04_wmi_device_ids, 154 | .probe = tuxedo_nb04_wmi_probe, 155 | .remove = tuxedo_nb04_wmi_remove, 156 | }; 157 | 158 | module_wmi_driver(tuxedo_nb04_wmi_driver); 159 | 160 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 161 | MODULE_DESCRIPTION("Driver for NB04 WMI BS methods"); 162 | MODULE_LICENSE("GPL"); 163 | 164 | MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_device_ids); 165 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_kbd_backlight.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "tuxedo_nb05_kbd_backlight.h" 28 | #include "tuxedo_nb05_ec.h" 29 | 30 | #define NB05_KBD_BRIGHTNESS_MAX_WHITE 0x02 31 | #define NB05_KBD_BRIGHTNESS_DEFAULT_WHITE 0x00 32 | 33 | static struct led_classdev *__nb05_kbd_led_cdev = NULL; 34 | 35 | static u8 white_brightness_to_level_map[] = { 36 | 0x00, 37 | 0x5c, 38 | 0xb8, 39 | }; 40 | 41 | struct driver_data_t { 42 | struct led_classdev nb05_kbd_led_cdev; 43 | }; 44 | 45 | static void nb05_leds_set_brightness(struct led_classdev *led_cdev __always_unused, 46 | enum led_brightness brightness) 47 | { 48 | if (brightness < 0 || brightness > NB05_KBD_BRIGHTNESS_MAX_WHITE) 49 | return; 50 | 51 | const struct dmi_system_id *sysid; 52 | sysid = nb05_match_device(); 53 | if (!strcmp(sysid->ident, IFLX14I01)) 54 | nb05_write_ec_ram(0x03e2, white_brightness_to_level_map[brightness]); 55 | else 56 | nb05_write_ec_ram(0x0409, white_brightness_to_level_map[brightness]); 57 | } 58 | 59 | void nb05_leds_notify_brightness_change_extern(u8 step) 60 | { 61 | if (step > NB05_KBD_BRIGHTNESS_MAX_WHITE) 62 | return; 63 | 64 | if (__nb05_kbd_led_cdev) { 65 | __nb05_kbd_led_cdev->brightness = step; 66 | led_classdev_notify_brightness_hw_changed(__nb05_kbd_led_cdev, step); 67 | } 68 | } 69 | EXPORT_SYMBOL(nb05_leds_notify_brightness_change_extern); 70 | 71 | static int init_leds(struct platform_device *pdev) 72 | { 73 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 74 | int retval; 75 | 76 | driver_data->nb05_kbd_led_cdev.name = "white:" LED_FUNCTION_KBD_BACKLIGHT; 77 | driver_data->nb05_kbd_led_cdev.max_brightness = NB05_KBD_BRIGHTNESS_MAX_WHITE; 78 | driver_data->nb05_kbd_led_cdev.brightness_set = &nb05_leds_set_brightness; 79 | driver_data->nb05_kbd_led_cdev.brightness = NB05_KBD_BRIGHTNESS_DEFAULT_WHITE; 80 | driver_data->nb05_kbd_led_cdev.flags = LED_BRIGHT_HW_CHANGED; 81 | 82 | retval = led_classdev_register(&pdev->dev, &driver_data->nb05_kbd_led_cdev); 83 | if (retval) 84 | return retval; 85 | 86 | __nb05_kbd_led_cdev = &driver_data->nb05_kbd_led_cdev; 87 | 88 | return 0; 89 | } 90 | 91 | static int __init tuxedo_nb05_kbd_backlight_probe(struct platform_device *pdev) 92 | { 93 | int result; 94 | struct driver_data_t *driver_data; 95 | 96 | pr_debug("driver probe\n"); 97 | 98 | driver_data = devm_kzalloc(&pdev->dev, sizeof(struct driver_data_t), GFP_KERNEL); 99 | if (!driver_data) 100 | return -ENOMEM; 101 | 102 | dev_set_drvdata(&pdev->dev, driver_data); 103 | 104 | if (result) { 105 | pr_err("Failed init write %d\n", result); 106 | return result; 107 | } 108 | 109 | result = init_leds(pdev); 110 | if (result) 111 | return result; 112 | 113 | return 0; 114 | } 115 | 116 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 117 | static int tuxedo_nb05_kbd_backlight_remove(struct platform_device *pdev) 118 | #else 119 | static void tuxedo_nb05_kbd_backlight_remove(struct platform_device *pdev) 120 | #endif 121 | { 122 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 123 | led_classdev_unregister(&driver_data->nb05_kbd_led_cdev); 124 | __nb05_kbd_led_cdev = NULL; 125 | pr_debug("driver remove\n"); 126 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 127 | return 0; 128 | #endif 129 | } 130 | 131 | static struct platform_device *tuxedo_nb05_kbd_backlight_device; 132 | static struct platform_driver tuxedo_nb05_kbd_backlight_driver = { 133 | .driver.name = "tuxedo_nb05_kbd_backlight", 134 | .remove = tuxedo_nb05_kbd_backlight_remove 135 | }; 136 | 137 | static int __init tuxedo_nb05_kbd_backlight_init(void) 138 | { 139 | tuxedo_nb05_kbd_backlight_device = 140 | platform_create_bundle(&tuxedo_nb05_kbd_backlight_driver, 141 | tuxedo_nb05_kbd_backlight_probe, NULL, 0, NULL, 0); 142 | 143 | if (IS_ERR(tuxedo_nb05_kbd_backlight_device)) 144 | return PTR_ERR(tuxedo_nb05_kbd_backlight_device); 145 | 146 | return 0; 147 | } 148 | 149 | static void __exit tuxedo_nb05_kbd_backlight_exit(void) 150 | { 151 | platform_device_unregister(tuxedo_nb05_kbd_backlight_device); 152 | platform_driver_unregister(&tuxedo_nb05_kbd_backlight_driver); 153 | } 154 | 155 | module_init(tuxedo_nb05_kbd_backlight_init); 156 | module_exit(tuxedo_nb05_kbd_backlight_exit); 157 | 158 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 159 | MODULE_DESCRIPTION("Driver for NB05 keyboard backlight"); 160 | MODULE_LICENSE("GPL"); 161 | -------------------------------------------------------------------------------- /src/clevo_wmi.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2020 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "clevo_interfaces.h" 27 | 28 | static int clevo_wmi_evaluate(u32 wmi_method_id, u32 wmi_arg, union acpi_object **result) 29 | { 30 | struct acpi_buffer acpi_buffer_in = { (acpi_size)sizeof(wmi_arg), 31 | &wmi_arg }; 32 | struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL }; 33 | union acpi_object *acpi_result; 34 | acpi_status status_acpi; 35 | int return_status = 0; 36 | 37 | status_acpi = 38 | wmi_evaluate_method(CLEVO_WMI_METHOD_GUID, 0x00, wmi_method_id, 39 | &acpi_buffer_in, &acpi_buffer_out); 40 | 41 | if (unlikely(ACPI_FAILURE(status_acpi))) { 42 | pr_err("failed to evaluate wmi method\n"); 43 | return -EIO; 44 | } 45 | 46 | acpi_result = (union acpi_object *)acpi_buffer_out.pointer; 47 | if (!acpi_result) { 48 | pr_err("failed to evaluate WMI method\n"); 49 | return_status = -1; 50 | } 51 | else { 52 | if (!IS_ERR_OR_NULL(result)) { 53 | *result = acpi_result; 54 | } 55 | } 56 | 57 | return return_status; 58 | } 59 | 60 | static int clevo_wmi_interface_method_call(u8 cmd, u32 arg, union acpi_object **result_value) 61 | { 62 | return clevo_wmi_evaluate(cmd, arg, result_value); 63 | } 64 | 65 | static int clevo_wmi_interface_method_call_pkgbuf(u8 cmd, u8 *arg, u32 length, union acpi_object **result_value) 66 | { 67 | pr_info("%s: unsupported wmi method call; ignoring cmd 0x%02x; please use acpi interface\n", 68 | __func__, cmd); 69 | return 0; 70 | } 71 | 72 | struct clevo_interface_t clevo_wmi_interface = { 73 | .string_id = CLEVO_INTERFACE_WMI_STRID, 74 | .method_call = clevo_wmi_interface_method_call, 75 | .method_call_pkgbuf = clevo_wmi_interface_method_call_pkgbuf, 76 | }; 77 | 78 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 79 | static int clevo_wmi_probe(struct wmi_device *wdev) 80 | #else 81 | static int clevo_wmi_probe(struct wmi_device *wdev, const void *dummy_context) 82 | #endif 83 | { 84 | int status; 85 | union acpi_object *out_obj; 86 | 87 | pr_debug("clevo_wmi driver probe\n"); 88 | 89 | if (!wmi_has_guid(CLEVO_WMI_EVENT_GUID)) { 90 | pr_debug("probe: Clevo event guid missing\n"); 91 | return -ENODEV; 92 | } 93 | 94 | if (!wmi_has_guid(CLEVO_WMI_METHOD_GUID)) { 95 | pr_debug("probe: Clevo method guid missing\n"); 96 | return -ENODEV; 97 | } 98 | 99 | // Since the WMI GUIDs aren't unique let's (at least) 100 | // check the return of some "known existing general" method 101 | status = clevo_wmi_evaluate(0x52, 0, &out_obj); 102 | if (status < 0) { 103 | pr_debug("probe: Clevo GUIDs present but method call failed\n"); 104 | return -ENODEV; 105 | } 106 | if (out_obj->type != ACPI_TYPE_INTEGER || (out_obj->type == ACPI_TYPE_INTEGER && (u32)out_obj->integer.value == 0xffffffff)) { 107 | pr_debug( 108 | "probe: Clevo GUIDs present but method returned unexpected value\n"); 109 | ACPI_FREE(out_obj); 110 | return -ENODEV; 111 | } 112 | ACPI_FREE(out_obj); 113 | 114 | // Add this interface 115 | clevo_keyboard_add_interface(&clevo_wmi_interface); 116 | 117 | pr_info("interface initialized\n"); 118 | 119 | return 0; 120 | } 121 | 122 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 123 | static int clevo_wmi_remove(struct wmi_device *wdev) 124 | #else 125 | static void clevo_wmi_remove(struct wmi_device *wdev) 126 | #endif 127 | { 128 | pr_debug("clevo_wmi driver remove\n"); 129 | clevo_keyboard_remove_interface(&clevo_wmi_interface); 130 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 131 | return 0; 132 | #endif 133 | } 134 | 135 | static void clevo_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) 136 | { 137 | u32 event_value; 138 | union acpi_object *out_obj; 139 | int status; 140 | 141 | status = clevo_wmi_evaluate(0x01, 0, &out_obj); 142 | if (!status) { 143 | if (out_obj->type == ACPI_TYPE_INTEGER) { 144 | event_value = (u32)out_obj->integer.value; 145 | } else { 146 | pr_err("return type not integer, use clevo_evaluate_method2\n"); 147 | } 148 | ACPI_FREE(out_obj); 149 | } 150 | pr_debug("clevo_wmi notify\n"); 151 | if (!IS_ERR_OR_NULL(clevo_wmi_interface.event_callb)) { 152 | // Execute registered callback 153 | clevo_wmi_interface.event_callb(event_value); 154 | } 155 | } 156 | 157 | static const struct wmi_device_id clevo_wmi_device_ids[] = { 158 | // Listing one should be enough, for a driver that "takes care of all anyways" 159 | // also prevents probe (and handling) per "device" 160 | { .guid_string = CLEVO_WMI_EVENT_GUID }, 161 | { } 162 | }; 163 | 164 | static struct wmi_driver clevo_wmi_driver = { 165 | .driver = { 166 | .name = CLEVO_INTERFACE_WMI_STRID, 167 | .owner = THIS_MODULE 168 | }, 169 | .id_table = clevo_wmi_device_ids, 170 | .probe = clevo_wmi_probe, 171 | .remove = clevo_wmi_remove, 172 | .notify = clevo_wmi_notify, 173 | }; 174 | 175 | module_wmi_driver(clevo_wmi_driver); 176 | 177 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 178 | MODULE_DESCRIPTION("Driver for Clevo WMI interface"); 179 | MODULE_LICENSE("GPL"); 180 | 181 | MODULE_DEVICE_TABLE(wmi, clevo_wmi_device_ids); 182 | MODULE_ALIAS_CLEVO_WMI(); 183 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_ec.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "tuxedo_nb05_ec.h" 31 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 32 | 33 | static struct nb05_ec_data_t ec_data; 34 | 35 | #define EC_PORT_ADDR 0x4e 36 | #define EC_PORT_DATA 0x4f 37 | 38 | #define I2EC_REG_ADDR 0x2e 39 | #define I2EC_REG_DATA 0x2f 40 | 41 | #define I2EC_ADDR_LOW 0x10 42 | #define I2EC_ADDR_HIGH 0x11 43 | #define I2EC_ADDR_DATA 0x12 44 | 45 | static DEFINE_MUTEX(nb05_ec_access_lock); 46 | 47 | static void io_write(u8 reg, u8 data) 48 | { 49 | outb(reg, EC_PORT_ADDR); 50 | outb(data, EC_PORT_DATA); 51 | } 52 | 53 | static u8 io_read(u8 reg) 54 | { 55 | outb(reg, EC_PORT_ADDR); 56 | return inb(EC_PORT_DATA); 57 | } 58 | 59 | void nb05_read_ec_ram(u16 addr, u8 *data) 60 | { 61 | u8 addr_high = (addr >> 8) & 0xff; 62 | u8 addr_low = (addr & 0xff); 63 | 64 | mutex_lock(&nb05_ec_access_lock); 65 | 66 | io_write(I2EC_REG_ADDR, I2EC_ADDR_HIGH); 67 | io_write(I2EC_REG_DATA, addr_high); 68 | 69 | io_write(I2EC_REG_ADDR, I2EC_ADDR_LOW); 70 | io_write(I2EC_REG_DATA, addr_low); 71 | 72 | io_write(I2EC_REG_ADDR, I2EC_ADDR_DATA); 73 | *data = io_read(I2EC_REG_DATA); 74 | 75 | mutex_unlock(&nb05_ec_access_lock); 76 | } 77 | EXPORT_SYMBOL(nb05_read_ec_ram); 78 | 79 | void nb05_write_ec_ram(u16 addr, u8 data) 80 | { 81 | u8 addr_high = (addr >> 8) & 0xff; 82 | u8 addr_low = (addr & 0xff); 83 | 84 | mutex_lock(&nb05_ec_access_lock); 85 | 86 | io_write(I2EC_REG_ADDR, I2EC_ADDR_HIGH); 87 | io_write(I2EC_REG_DATA, addr_high); 88 | 89 | io_write(I2EC_REG_ADDR, I2EC_ADDR_LOW); 90 | io_write(I2EC_REG_DATA, addr_low); 91 | 92 | io_write(I2EC_REG_ADDR, I2EC_ADDR_DATA); 93 | io_write(I2EC_REG_DATA, data); 94 | 95 | mutex_unlock(&nb05_ec_access_lock); 96 | } 97 | EXPORT_SYMBOL(nb05_write_ec_ram); 98 | 99 | void nb05_read_ec_fw_version(u8 *major, u8 *minor) 100 | { 101 | nb05_read_ec_ram(0x0400, major); 102 | nb05_read_ec_ram(0x0401, minor); 103 | } 104 | EXPORT_SYMBOL(nb05_read_ec_fw_version); 105 | 106 | static int tuxedo_nb05_ec_probe(struct platform_device *pdev) 107 | { 108 | u8 minor, major; 109 | 110 | nb05_read_ec_fw_version(&major, &minor); 111 | pr_info("EC I/O driver loaded, firmware version %d.%d\n", major, minor); 112 | 113 | ec_data.ver_major = major; 114 | ec_data.ver_minor = minor; 115 | 116 | return 0; 117 | } 118 | 119 | static struct platform_driver tuxedo_nb05_ec_driver = { 120 | .driver = { 121 | .name = KBUILD_MODNAME, 122 | }, 123 | .probe = tuxedo_nb05_ec_probe, 124 | }; 125 | 126 | static struct platform_device *tuxedo_nb05_ec_device; 127 | 128 | static int dmi_check_callback(const struct dmi_system_id *id) 129 | { 130 | printk(KERN_INFO KBUILD_MODNAME ": found model '%s'\n", id->ident); 131 | ec_data.dev_data = id->driver_data; 132 | return 1; 133 | } 134 | 135 | struct nb05_device_data_t data_pulse = { 136 | .number_fans = 2, 137 | .fanctl_onereg = false, 138 | }; 139 | 140 | struct nb05_device_data_t data_infinityflex = { 141 | .number_fans = 1, 142 | .fanctl_onereg = true, 143 | }; 144 | 145 | static const struct dmi_system_id tuxedo_nb05_id_table[] = { 146 | { 147 | .ident = PULSE1403, 148 | .matches = { 149 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 150 | DMI_MATCH(DMI_BOARD_VENDOR, "NB05"), 151 | DMI_MATCH(DMI_PRODUCT_SKU, "PULSE1403"), 152 | }, 153 | .callback = dmi_check_callback, 154 | .driver_data = &data_pulse, 155 | }, 156 | { 157 | .ident = PULSE1404, 158 | .matches = { 159 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 160 | DMI_MATCH(DMI_BOARD_VENDOR, "NB05"), 161 | DMI_MATCH(DMI_PRODUCT_SKU, "PULSE1404"), 162 | }, 163 | .callback = dmi_check_callback, 164 | .driver_data = &data_pulse, 165 | }, 166 | { 167 | .ident = IFLX14I01, 168 | .matches = { 169 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 170 | DMI_MATCH(DMI_BOARD_VENDOR, "NB05"), 171 | DMI_MATCH(DMI_PRODUCT_SKU, "IFLX14I01"), 172 | }, 173 | .callback = dmi_check_callback, 174 | .driver_data = &data_infinityflex, 175 | }, 176 | { }, 177 | }; 178 | 179 | MODULE_DEVICE_TABLE(dmi, tuxedo_nb05_id_table); 180 | 181 | const struct dmi_system_id *nb05_match_device(void) 182 | { 183 | return dmi_first_match(tuxedo_nb05_id_table); 184 | } 185 | EXPORT_SYMBOL(nb05_match_device); 186 | 187 | void nb05_get_ec_data(struct nb05_ec_data_t **ec_data_pp) 188 | { 189 | *ec_data_pp = &ec_data; 190 | } 191 | EXPORT_SYMBOL(nb05_get_ec_data); 192 | 193 | static int __init tuxedo_nb05_ec_init(void) 194 | { 195 | if (!dmi_check_system(tuxedo_nb05_id_table)) 196 | return -ENODEV; 197 | 198 | if (!tuxedo_is_compatible()) 199 | return -ENODEV; 200 | 201 | tuxedo_nb05_ec_device = 202 | platform_create_bundle(&tuxedo_nb05_ec_driver, 203 | tuxedo_nb05_ec_probe, 204 | NULL, 0, NULL, 0); 205 | 206 | return PTR_ERR_OR_ZERO(tuxedo_nb05_ec_device); 207 | } 208 | 209 | static void __exit tuxedo_nb05_ec_exit(void) 210 | { 211 | platform_device_unregister(tuxedo_nb05_ec_device); 212 | platform_driver_unregister(&tuxedo_nb05_ec_driver); 213 | } 214 | 215 | module_init(tuxedo_nb05_ec_init); 216 | module_exit(tuxedo_nb05_ec_exit); 217 | 218 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 219 | MODULE_DESCRIPTION("Driver for NB05 EC I/O"); 220 | MODULE_LICENSE("GPL"); 221 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_kbd_backlight.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "tuxedo_nb04_wmi_ab.h" 27 | 28 | #define KEYBOARD_MAX_BRIGHTNESS 0x0a 29 | #define KEYBOARD_DEFAULT_BRIGHTNESS 0x00 30 | #define KEYBOARD_DEFAULT_COLOR_RED 0xff 31 | #define KEYBOARD_DEFAULT_COLOR_GREEN 0xff 32 | #define KEYBOARD_DEFAULT_COLOR_BLUE 0xff 33 | 34 | struct driver_data_t { 35 | struct led_classdev_mc mcled_cdev_keyboard; 36 | struct mc_subled mcled_cdev_subleds_keyboard[3]; 37 | struct device_keyboard_status_t device_status; 38 | }; 39 | 40 | static void leds_set_brightness_mc_keyboard(struct led_classdev *led_cdev, enum led_brightness brightness) 41 | { 42 | struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); 43 | u8 red = mcled_cdev->subled_info[0].intensity; 44 | u8 green = mcled_cdev->subled_info[1].intensity; 45 | u8 blue = mcled_cdev->subled_info[2].intensity; 46 | 47 | pr_debug("wmi_set_whole_keyboard(%u, %u, %u, %u)\n", red, green, blue, brightness); 48 | 49 | wmi_set_whole_keyboard(red, green, blue, brightness); 50 | } 51 | 52 | static int init_leds(struct platform_device *pdev) 53 | { 54 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 55 | int retval; 56 | 57 | driver_data->mcled_cdev_keyboard.led_cdev.name = "rgb:" LED_FUNCTION_KBD_BACKLIGHT; 58 | driver_data->mcled_cdev_keyboard.led_cdev.max_brightness = KEYBOARD_MAX_BRIGHTNESS; 59 | driver_data->mcled_cdev_keyboard.led_cdev.brightness_set = &leds_set_brightness_mc_keyboard; 60 | driver_data->mcled_cdev_keyboard.led_cdev.brightness = KEYBOARD_DEFAULT_BRIGHTNESS; 61 | driver_data->mcled_cdev_keyboard.num_colors = 3; 62 | driver_data->mcled_cdev_keyboard.subled_info = driver_data->mcled_cdev_subleds_keyboard; 63 | driver_data->mcled_cdev_keyboard.subled_info[0].color_index = LED_COLOR_ID_RED; 64 | driver_data->mcled_cdev_keyboard.subled_info[0].intensity = KEYBOARD_DEFAULT_COLOR_RED; 65 | driver_data->mcled_cdev_keyboard.subled_info[0].channel = 0; 66 | driver_data->mcled_cdev_keyboard.subled_info[1].color_index = LED_COLOR_ID_GREEN; 67 | driver_data->mcled_cdev_keyboard.subled_info[1].intensity = KEYBOARD_DEFAULT_COLOR_GREEN; 68 | driver_data->mcled_cdev_keyboard.subled_info[1].channel = 0; 69 | driver_data->mcled_cdev_keyboard.subled_info[2].color_index = LED_COLOR_ID_BLUE; 70 | driver_data->mcled_cdev_keyboard.subled_info[2].intensity = KEYBOARD_DEFAULT_COLOR_BLUE; 71 | driver_data->mcled_cdev_keyboard.subled_info[2].channel = 0; 72 | 73 | retval = devm_led_classdev_multicolor_register(&pdev->dev, &driver_data->mcled_cdev_keyboard); 74 | if (retval) 75 | return retval; 76 | 77 | return 0; 78 | } 79 | 80 | static int __init tuxedo_nb04_kbd_backlight_probe(struct platform_device *pdev) 81 | { 82 | int result; 83 | struct driver_data_t *driver_data; 84 | 85 | pr_debug("driver probe\n"); 86 | 87 | driver_data = devm_kzalloc(&pdev->dev, sizeof(struct driver_data_t), GFP_KERNEL); 88 | if (!driver_data) 89 | return -ENOMEM; 90 | 91 | dev_set_drvdata(&pdev->dev, driver_data); 92 | 93 | // Note: Read of keyboard status needed for fw init 94 | // before writing can be done 95 | result = wmi_update_device_status_keyboard(&driver_data->device_status); 96 | 97 | if (result) { 98 | if (result != -ENODEV) 99 | pr_err("Failed initial read %d\n", result); 100 | return result; 101 | } 102 | 103 | result = init_leds(pdev); 104 | if (result) 105 | return result; 106 | 107 | pr_debug("kbd enabled: %d\n", driver_data->device_status.keyboard_state_enabled); 108 | pr_debug("kbd type: %d\n", driver_data->device_status.keyboard_type); 109 | pr_debug("kbd sidebar support: %d\n", driver_data->device_status.keyboard_sidebar_support); 110 | pr_debug("kbd keyboard matrix: %d\n", driver_data->device_status.keyboard_matrix); 111 | 112 | return 0; 113 | } 114 | 115 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 116 | static int tuxedo_nb04_kbd_backlight_remove(struct platform_device *pdev) 117 | #else 118 | static void tuxedo_nb04_kbd_backlight_remove(struct platform_device *pdev) 119 | #endif 120 | { 121 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 122 | devm_led_classdev_multicolor_unregister(&pdev->dev, &driver_data->mcled_cdev_keyboard); 123 | pr_debug("driver remove\n"); 124 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 125 | return 0; 126 | #endif 127 | } 128 | 129 | static struct platform_device *tuxedo_nb04_kbd_backlight_device; 130 | static struct platform_driver tuxedo_nb04_kbd_backlight_driver = { 131 | .driver.name = "tuxedo_nb04_kbd_backlight", 132 | .remove = tuxedo_nb04_kbd_backlight_remove 133 | }; 134 | 135 | static int __init tuxedo_nb04_kbd_backlight_init(void) 136 | { 137 | tuxedo_nb04_kbd_backlight_device = 138 | platform_create_bundle(&tuxedo_nb04_kbd_backlight_driver, 139 | tuxedo_nb04_kbd_backlight_probe, NULL, 0, NULL, 0); 140 | 141 | if (IS_ERR(tuxedo_nb04_kbd_backlight_device)) 142 | return PTR_ERR(tuxedo_nb04_kbd_backlight_device); 143 | 144 | return 0; 145 | } 146 | 147 | static void __exit tuxedo_nb04_kbd_backlight_exit(void) 148 | { 149 | platform_device_unregister(tuxedo_nb04_kbd_backlight_device); 150 | platform_driver_unregister(&tuxedo_nb04_kbd_backlight_driver); 151 | } 152 | 153 | module_init(tuxedo_nb04_kbd_backlight_init); 154 | module_exit(tuxedo_nb04_kbd_backlight_exit); 155 | 156 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 157 | MODULE_DESCRIPTION("Driver for NB04 keyboard backlight"); 158 | MODULE_LICENSE("GPL"); 159 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_sensors.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "tuxedo_nb05_ec.h" 27 | 28 | static int read_cpu_temp(void) 29 | { 30 | u8 cpu_temp; 31 | nb05_read_ec_ram(0x470, &cpu_temp); 32 | return cpu_temp; 33 | } 34 | 35 | static int read_fan1_rpm(void) 36 | { 37 | u8 rpm_low, rpm_high; 38 | nb05_read_ec_ram(0x298, &rpm_high); 39 | nb05_read_ec_ram(0x298, &rpm_low); 40 | return (rpm_high << 8) | rpm_low; 41 | } 42 | 43 | static int read_fan2_rpm(void) 44 | { 45 | u8 rpm_low, rpm_high; 46 | nb05_read_ec_ram(0x218, &rpm_high); 47 | nb05_read_ec_ram(0x219, &rpm_low); 48 | return (rpm_high << 8) | rpm_low; 49 | } 50 | 51 | static const char * const temp_labels[] = { 52 | "cpu0" 53 | }; 54 | 55 | static const char * const fan_labels[] = { 56 | "cpu0", 57 | "cpu1" 58 | }; 59 | 60 | struct driver_data_t { 61 | int fan_cpu_max; 62 | int fan_cpu_min; 63 | int number_fans; 64 | }; 65 | 66 | struct driver_data_t driver_data; 67 | 68 | static umode_t 69 | tuxedo_nb05_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, 70 | u32 attr, int channel) 71 | { 72 | struct driver_data_t *driver_data = (struct driver_data_t *) drvdata; 73 | 74 | switch (type) { 75 | case hwmon_temp: 76 | return 0444; 77 | case hwmon_fan: 78 | if (channel < driver_data->number_fans) 79 | return 0444; 80 | break; 81 | default: 82 | break; 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | static int 89 | tuxedo_nb05_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 90 | u32 attr, int channel, long *val) 91 | { 92 | struct driver_data_t *driver_data = dev_get_drvdata(dev); 93 | 94 | switch (type) { 95 | case hwmon_temp: 96 | *val = read_cpu_temp() * 1000; 97 | return 0; 98 | case hwmon_fan: 99 | switch (attr) { 100 | case hwmon_fan_min: 101 | if (channel == 0) { 102 | *val = driver_data->fan_cpu_min; 103 | return 0; 104 | } else if (channel == 1) { 105 | *val = driver_data->fan_cpu_min; 106 | return 0; 107 | } 108 | break; 109 | case hwmon_fan_max: 110 | if (channel == 0) { 111 | *val = driver_data->fan_cpu_max; 112 | return 0; 113 | } else if (channel == 1) { 114 | *val = driver_data->fan_cpu_max; 115 | return 0; 116 | } 117 | break; 118 | case hwmon_fan_input: 119 | if (channel == 0) { 120 | *val = read_fan1_rpm(); 121 | return 0; 122 | } else if (channel == 1) { 123 | *val = read_fan2_rpm(); 124 | return 0; 125 | } 126 | default: 127 | break; 128 | } 129 | default: 130 | break; 131 | } 132 | 133 | return -EOPNOTSUPP; 134 | } 135 | 136 | static int 137 | tuxedo_nb05_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, 138 | u32 attr, int channel, const char **str) 139 | { 140 | switch (type) { 141 | case hwmon_temp: 142 | *str = temp_labels[channel]; 143 | return 0; 144 | case hwmon_fan: 145 | *str = fan_labels[channel]; 146 | return 0; 147 | default: 148 | break; 149 | } 150 | 151 | return -EOPNOTSUPP; 152 | } 153 | 154 | static const struct hwmon_ops tuxedo_nb05_hwmon_ops = { 155 | .is_visible = tuxedo_nb05_hwmon_is_visible, 156 | .read = tuxedo_nb05_hwmon_read, 157 | .read_string = tuxedo_nb05_hwmon_read_string 158 | }; 159 | 160 | static const struct hwmon_channel_info *const tuxedo_nb05_hwmon_info[] = { 161 | HWMON_CHANNEL_INFO(temp, 162 | HWMON_T_INPUT | HWMON_T_LABEL), 163 | HWMON_CHANNEL_INFO(fan, 164 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX, 165 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX), 166 | NULL 167 | }; 168 | 169 | static const struct hwmon_chip_info tuxedo_nb05_hwmon_chip_info = { 170 | .ops = &tuxedo_nb05_hwmon_ops, 171 | .info = tuxedo_nb05_hwmon_info 172 | }; 173 | 174 | static int __init tuxedo_nb05_sensors_probe(struct platform_device *pdev) { 175 | struct device *hwmon_dev; 176 | const struct dmi_system_id *sysid; 177 | 178 | pr_debug("driver_probe\n"); 179 | 180 | sysid = nb05_match_device(); 181 | if (!sysid) 182 | return -ENODEV; 183 | 184 | driver_data.fan_cpu_min = 0; 185 | 186 | if (!strcmp(sysid->ident, IFLX14I01)) { 187 | driver_data.number_fans = 1; 188 | driver_data.fan_cpu_max = 5600; 189 | } else { 190 | driver_data.number_fans = 2; 191 | driver_data.fan_cpu_max = 5400; 192 | } 193 | 194 | hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 195 | "tuxedo", 196 | &driver_data, 197 | &tuxedo_nb05_hwmon_chip_info, 198 | NULL); 199 | 200 | return PTR_ERR_OR_ZERO(hwmon_dev); 201 | } 202 | 203 | static struct platform_device *tuxedo_nb05_sensors_device; 204 | static struct platform_driver tuxedo_nb05_sensors_driver = { 205 | .driver.name = "tuxedo_nb05_sensors", 206 | }; 207 | 208 | static int __init tuxedo_nb05_sensors_init(void) 209 | { 210 | tuxedo_nb05_sensors_device = 211 | platform_create_bundle(&tuxedo_nb05_sensors_driver, 212 | tuxedo_nb05_sensors_probe, NULL, 0, NULL, 0); 213 | 214 | if (IS_ERR(tuxedo_nb05_sensors_device)) 215 | return PTR_ERR(tuxedo_nb05_sensors_device); 216 | 217 | return 0; 218 | } 219 | 220 | static void __exit tuxedo_nb05_sensors_exit(void) 221 | { 222 | platform_device_unregister(tuxedo_nb05_sensors_device); 223 | platform_driver_unregister(&tuxedo_nb05_sensors_driver); 224 | } 225 | 226 | module_init(tuxedo_nb05_sensors_init); 227 | module_exit(tuxedo_nb05_sensors_exit); 228 | 229 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 230 | MODULE_DESCRIPTION("TUXEDO Computers NB05 sensors driver"); 231 | MODULE_LICENSE("GPL"); 232 | -------------------------------------------------------------------------------- /src/tuxedo_keyboard.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2018-2020 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) "tuxedo_keyboard" ": " fmt 22 | 23 | #include "tuxedo_keyboard_common.h" 24 | #include "clevo_keyboard.h" 25 | #include "uniwill_keyboard.h" 26 | #include "tuxedo_compatibility_check/tuxedo_compatibility_check.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 33 | MODULE_DESCRIPTION("TUXEDO Computers keyboard & keyboard backlight Driver"); 34 | MODULE_LICENSE("GPL"); 35 | 36 | static DEFINE_MUTEX(tuxedo_keyboard_init_driver_lock); 37 | 38 | // sysfs device function 39 | static ssize_t fn_lock_show(struct device *dev, 40 | struct device_attribute *attr, 41 | char *buf) 42 | { 43 | // one sysfs device for clevo or uniwill 44 | return current_driver->fn_lock_show(dev, attr, buf); 45 | } 46 | 47 | // sysfs device function 48 | static ssize_t fn_lock_store(struct device *dev, 49 | struct device_attribute *attr, 50 | const char *buf, size_t size) 51 | { 52 | return current_driver->fn_lock_store(dev, attr, buf, size); 53 | } 54 | 55 | static DEVICE_ATTR_RW(fn_lock); 56 | 57 | // static struct tuxedo_keyboard_driver *driver_list[] = { }; 58 | 59 | static int tuxedo_input_init(const struct key_entry key_map[]) 60 | { 61 | int err; 62 | 63 | tuxedo_input_device = input_allocate_device(); 64 | if (unlikely(!tuxedo_input_device)) { 65 | TUXEDO_ERROR("Error allocating input device\n"); 66 | return -ENOMEM; 67 | } 68 | 69 | tuxedo_input_device->name = "TUXEDO Keyboard"; 70 | tuxedo_input_device->phys = DRIVER_NAME "/input0"; 71 | tuxedo_input_device->id.bustype = BUS_HOST; 72 | tuxedo_input_device->dev.parent = &tuxedo_platform_device->dev; 73 | 74 | if (key_map != NULL) { 75 | err = sparse_keymap_setup(tuxedo_input_device, key_map, NULL); 76 | if (err) { 77 | TUXEDO_ERROR("Failed to setup sparse keymap\n"); 78 | goto err_free_input_device; 79 | } 80 | } 81 | 82 | err = input_register_device(tuxedo_input_device); 83 | if (unlikely(err)) { 84 | TUXEDO_ERROR("Error registering input device\n"); 85 | goto err_free_input_device; 86 | } 87 | 88 | return 0; 89 | 90 | err_free_input_device: 91 | input_free_device(tuxedo_input_device); 92 | 93 | return err; 94 | } 95 | 96 | struct platform_device *tuxedo_keyboard_init_driver(struct tuxedo_keyboard_driver *tk_driver) 97 | { 98 | int err; 99 | struct platform_device *new_platform_device = NULL; 100 | 101 | TUXEDO_DEBUG("init driver start\n"); 102 | 103 | mutex_lock(&tuxedo_keyboard_init_driver_lock); 104 | 105 | if (!IS_ERR_OR_NULL(tuxedo_platform_device)) { 106 | // If already initialized, don't proceed 107 | TUXEDO_DEBUG("platform device already initialized\n"); 108 | goto init_driver_exit; 109 | } else { 110 | // Otherwise, attempt to initialize structures 111 | TUXEDO_DEBUG("create platform bundle\n"); 112 | new_platform_device = platform_create_bundle( 113 | tk_driver->platform_driver, tk_driver->probe, NULL, 0, NULL, 0); 114 | 115 | tuxedo_platform_device = new_platform_device; 116 | 117 | if (IS_ERR_OR_NULL(tuxedo_platform_device)) { 118 | // Normal case probe failed, no init 119 | goto init_driver_exit; 120 | } 121 | 122 | TUXEDO_DEBUG("initialize input device\n"); 123 | if (tk_driver->key_map != NULL) { 124 | err = tuxedo_input_init(tk_driver->key_map); 125 | if (unlikely(err)) { 126 | TUXEDO_ERROR("Could not register input device\n"); 127 | tk_driver->input_device = NULL; 128 | } else { 129 | TUXEDO_DEBUG("input device registered\n"); 130 | tk_driver->input_device = tuxedo_input_device; 131 | } 132 | } 133 | 134 | // set current driver (clevo or uniwill) 135 | current_driver = tk_driver; 136 | 137 | // test for fn lock and create sysfs device 138 | if (current_driver->fn_lock_available()) { 139 | err = device_create_file(&tuxedo_platform_device->dev, &dev_attr_fn_lock); 140 | if(err) 141 | pr_err("device_create_file for fn_lock failed\n"); 142 | } else { 143 | pr_debug("FnLock not available\n"); 144 | } 145 | } 146 | 147 | init_driver_exit: 148 | mutex_unlock(&tuxedo_keyboard_init_driver_lock); 149 | return new_platform_device; 150 | } 151 | EXPORT_SYMBOL(tuxedo_keyboard_init_driver); 152 | 153 | static void __exit tuxedo_input_exit(void) 154 | { 155 | if (unlikely(!tuxedo_input_device)) { 156 | return; 157 | } 158 | 159 | input_unregister_device(tuxedo_input_device); 160 | { 161 | tuxedo_input_device = NULL; 162 | } 163 | 164 | } 165 | 166 | void tuxedo_keyboard_remove_driver(struct tuxedo_keyboard_driver *tk_driver) 167 | { 168 | bool specified_driver_differ_from_used = 169 | tk_driver != NULL && 170 | ( 171 | strcmp( 172 | tk_driver->platform_driver->driver.name, 173 | current_driver->platform_driver->driver.name 174 | ) != 0 175 | ); 176 | 177 | if (specified_driver_differ_from_used) 178 | return; 179 | 180 | device_remove_file(&tuxedo_platform_device->dev, &dev_attr_fn_lock); 181 | 182 | TUXEDO_DEBUG("tuxedo_input_exit()\n"); 183 | tuxedo_input_exit(); 184 | TUXEDO_DEBUG("platform_device_unregister()\n"); 185 | if (!IS_ERR_OR_NULL(tuxedo_platform_device)) { 186 | platform_device_unregister(tuxedo_platform_device); 187 | tuxedo_platform_device = NULL; 188 | } 189 | TUXEDO_DEBUG("platform_driver_unregister()\n"); 190 | if (!IS_ERR_OR_NULL(current_driver)) { 191 | platform_driver_unregister(current_driver->platform_driver); 192 | current_driver = NULL; 193 | } 194 | 195 | } 196 | EXPORT_SYMBOL(tuxedo_keyboard_remove_driver); 197 | 198 | static int __init tuxedo_keyboard_init(void) 199 | { 200 | TUXEDO_INFO("module init\n"); 201 | 202 | if (!tuxedo_is_compatible()) 203 | return -ENODEV; 204 | 205 | return 0; 206 | } 207 | 208 | static void __exit tuxedo_keyboard_exit(void) 209 | { 210 | TUXEDO_INFO("module exit\n"); 211 | 212 | if (tuxedo_platform_device != NULL) 213 | tuxedo_keyboard_remove_driver(NULL); 214 | } 215 | 216 | module_init(tuxedo_keyboard_init); 217 | module_exit(tuxedo_keyboard_exit); 218 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_keyboard.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 31 | 32 | #define NB04_WMI_EVENT_GUID "96A786FA-690C-48FB-9EB3-FA9BC3D92300" 33 | 34 | #define NB04_WMI_EVENT_MODE_BATTERY 0x01 35 | #define NB04_WMI_EVENT_MODE_HUMAN 0x02 36 | #define NB04_WMI_EVENT_MODE_BEAST 0x03 37 | #define NB04_WMI_EVENT_FULL_FAN 0x04 38 | #define NB04_WMI_EVENT_NEXT_POWER_MODE 0x05 39 | #define NB04_WMI_EVENT_TOUCHPAD_TOGGLE 0x06 40 | #define NB04_WMI_EVENT_MIC_MUTE 0x07 41 | #define NB04_WMI_EVENT_KBD_BRT_UP 0x08 42 | #define NB04_WMI_EVENT_KBD_BRT_DOWN 0x09 43 | #define NB04_WMI_EVENT_KBD_EFFECT_ALWAYS 0x0A 44 | #define NB04_WMI_EVENT_KBD_EFFECT_BREATHING 0x0B 45 | #define NB04_WMI_EVENT_KBD_EFFECT_WAVE 0x0C 46 | #define NB04_WMI_EVENT_KBD_EFFECT_TWINKLE 0x0D 47 | #define NB04_WMI_EVENT_KBD_EFFECT_COLOR_CYCLE 0x0E 48 | #define NB04_WMI_EVENT_KBD_EFFECT_REACTIVE 0x0F 49 | #define NB04_WMI_EVENT_KBD_EFFECT_RIPPLE 0x10 50 | #define NB04_WMI_EVENT_KBD_EFFECT_SPIRAL_RAINBOW 0x11 51 | #define NB04_WMI_EVENT_KBD_EFFECT_RAINBOW_RIPPLE 0x12 52 | 53 | struct driver_data_t { 54 | struct input_dev *input_dev; 55 | }; 56 | 57 | static struct key_entry driver_keymap[] = { 58 | { KE_KEY, NB04_WMI_EVENT_MIC_MUTE, { KEY_F20 } }, 59 | { KE_KEY, NB04_WMI_EVENT_TOUCHPAD_TOGGLE, { KEY_F21 } }, 60 | { KE_KEY, NB04_WMI_EVENT_KBD_BRT_DOWN, { KEY_KBDILLUMDOWN } }, 61 | { KE_KEY, NB04_WMI_EVENT_KBD_BRT_UP, { KEY_KBDILLUMUP } }, 62 | { KE_END, 0 } 63 | }; 64 | 65 | /** 66 | * Basically a copy of the existing report event but doesn't report unknown events 67 | */ 68 | static bool sparse_keymap_report_known_event(struct input_dev *dev, 69 | unsigned int code, 70 | unsigned int value, 71 | bool autorelease) 72 | { 73 | const struct key_entry *ke = 74 | sparse_keymap_entry_from_scancode(dev, code); 75 | 76 | if (ke) { 77 | sparse_keymap_report_entry(dev, ke, value, autorelease); 78 | return true; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | static int input_device_init(struct input_dev **input_dev_pp, const struct key_entry key_map[]) 85 | { 86 | struct input_dev *input_dev; 87 | int err; 88 | 89 | input_dev = input_allocate_device(); 90 | if (unlikely(!input_dev)) { 91 | pr_err("Error allocating input device\n"); 92 | return -ENOMEM; 93 | } 94 | 95 | input_dev->name = "TUXEDO Keyboard Events"; 96 | input_dev->phys = "tuxedo-keyboard" "/input0"; 97 | input_dev->id.bustype = BUS_HOST; 98 | input_dev->dev.parent = NULL; 99 | 100 | if (key_map != NULL) { 101 | err = sparse_keymap_setup(input_dev, key_map, NULL); 102 | if (err) { 103 | pr_err("Failed to setup sparse keymap\n"); 104 | goto err_free_input_device; 105 | } 106 | } 107 | 108 | err = input_register_device(input_dev); 109 | if (unlikely(err)) { 110 | pr_err("Error registering input device\n"); 111 | goto err_free_input_device; 112 | } 113 | 114 | *input_dev_pp = input_dev; 115 | 116 | return 0; 117 | 118 | err_free_input_device: 119 | input_free_device(input_dev); 120 | 121 | return err; 122 | } 123 | 124 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 125 | static int tuxedo_nb04_keyboard_probe(struct wmi_device *wdev) 126 | #else 127 | static int tuxedo_nb04_keyboard_probe(struct wmi_device *wdev, const void *dummy_context) 128 | #endif 129 | { 130 | struct driver_data_t *driver_data; 131 | int err; 132 | 133 | pr_debug("driver probe\n"); 134 | 135 | if (!tuxedo_is_compatible()) 136 | return -ENODEV; 137 | 138 | if (!wmi_has_guid(NB04_WMI_EVENT_GUID)) 139 | return -ENODEV; 140 | 141 | driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL); 142 | if (!driver_data) 143 | return -ENOMEM; 144 | 145 | dev_set_drvdata(&wdev->dev, driver_data); 146 | 147 | err = input_device_init(&driver_data->input_dev, driver_keymap); 148 | if (err) 149 | return err; 150 | 151 | return 0; 152 | } 153 | 154 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 155 | static int tuxedo_nb04_keyboard_remove(struct wmi_device *wdev) 156 | #else 157 | static void tuxedo_nb04_keyboard_remove(struct wmi_device *wdev) 158 | #endif 159 | { 160 | pr_debug("driver remove\n"); 161 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 162 | input_unregister_device(driver_data->input_dev); 163 | 164 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 165 | return 0; 166 | #endif 167 | } 168 | 169 | static void tuxedo_nb04_keyboard_notify(struct wmi_device *wdev, union acpi_object *obj) 170 | { 171 | u8 function_number; 172 | u8 event_code; 173 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 174 | 175 | if (obj && 176 | obj->type == ACPI_TYPE_BUFFER && 177 | obj->buffer.length >= 2) 178 | { 179 | function_number = obj->buffer.pointer[0]; 180 | event_code = obj->buffer.pointer[1]; 181 | pr_debug("event value: %d (%0#4x)\n", 182 | event_code, event_code); 183 | sparse_keymap_report_known_event(driver_data->input_dev, 184 | event_code, 185 | 1, 186 | true); 187 | } else { 188 | pr_debug("expected buffer not found\n"); 189 | } 190 | } 191 | 192 | static const struct wmi_device_id tuxedo_nb04_keyboard_device_ids[] = { 193 | { .guid_string = NB04_WMI_EVENT_GUID }, 194 | { } 195 | }; 196 | 197 | static struct wmi_driver tuxedo_nb04_keyboard_driver = { 198 | .driver = { 199 | .name = "tuxedo_nb04_keyboard", 200 | .owner = THIS_MODULE 201 | }, 202 | .id_table = tuxedo_nb04_keyboard_device_ids, 203 | .probe = tuxedo_nb04_keyboard_probe, 204 | .remove = tuxedo_nb04_keyboard_remove, 205 | .notify = tuxedo_nb04_keyboard_notify, 206 | }; 207 | 208 | module_wmi_driver(tuxedo_nb04_keyboard_driver); 209 | 210 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 211 | MODULE_DESCRIPTION("Driver for NB04 WMI (keyboard) events"); 212 | MODULE_LICENSE("GPL"); 213 | 214 | MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_keyboard_device_ids); 215 | MODULE_ALIAS("wmi:" NB04_WMI_EVENT_GUID); 216 | -------------------------------------------------------------------------------- /src/uniwill_interfaces.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0+ */ 2 | /*! 3 | * Copyright (c) 2021-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #ifndef UNIWILL_INTERFACES_H 22 | #define UNIWILL_INTERFACES_H 23 | 24 | #include 25 | 26 | #define UNIWILL_WMI_MGMT_GUID_BA "ABBC0F6D-8EA1-11D1-00A0-C90629100000" 27 | #define UNIWILL_WMI_MGMT_GUID_BB "ABBC0F6E-8EA1-11D1-00A0-C90629100000" 28 | #define UNIWILL_WMI_MGMT_GUID_BC "ABBC0F6F-8EA1-11D1-00A0-C90629100000" 29 | 30 | #define UNIWILL_WMI_EVENT_GUID_0 "ABBC0F70-8EA1-11D1-00A0-C90629100000" 31 | #define UNIWILL_WMI_EVENT_GUID_1 "ABBC0F71-8EA1-11D1-00A0-C90629100000" 32 | #define UNIWILL_WMI_EVENT_GUID_2 "ABBC0F72-8EA1-11D1-00A0-C90629100000" 33 | 34 | #define UNIWILL_WMI_FUNCTION_WRITE 0 35 | #define UNIWILL_WMI_FUNCTION_READ 1 36 | #define UNIWILL_WMI_FUNCTION_FEATURE_TOGGLE 5 37 | 38 | #define UNIWILL_WMI_LOCAL_DIMMING_ON 0x0E 39 | #define UNIWILL_WMI_LOCAL_DIMMING_OFF 0x0D 40 | 41 | #define MODULE_ALIAS_UNIWILL_WMI() \ 42 | MODULE_ALIAS("wmi:" UNIWILL_WMI_EVENT_GUID_2); \ 43 | MODULE_ALIAS("wmi:" UNIWILL_WMI_MGMT_GUID_BC); 44 | 45 | #define UNIWILL_INTERFACE_WMI_STRID "uniwill_wmi" 46 | 47 | typedef int (uniwill_read_ec_ram_t)(u16, u8*); 48 | typedef int (uniwill_read_ec_ram_with_retry_t)(u16, u8*, int); 49 | typedef int (uniwill_write_ec_ram_t)(u16, u8); 50 | typedef int (uniwill_wmi_evaluate_t)(u8 function, u32 arg, u32 *return_buffer); 51 | typedef int (uniwill_write_ec_ram_with_retry_t)(u16, u8, int); 52 | typedef void (uniwill_event_callb_t)(u32); 53 | 54 | // UW_EC_REG_* known relevant EC address exposing some information or function 55 | // UW_EC_REG_*_BIT_* single bit from byte holding information, should be handled with bit-wise operations 56 | // UW_EC_REG_*_VALUE_* discrete value of the whole byte with special meaning 57 | // UW_EC_REG_*_SUBCMD_* writing this discrete value triggers special behaviour 58 | 59 | #define UW_EC_REG_KBD_BL_STATUS 0x078c 60 | #define UW_EC_REG_KBD_BL_STATUS_BIT_WHITE_ONLY_KB 0x01 61 | #define UW_EC_REG_KBD_BL_STATUS_SUBCMD_RESET 0x10 62 | 63 | #define UW_EC_REG_KBD_BL_MAX_BRIGHTNESS_IMMEDIATE 0x1801 64 | #define UW_EC_REG_KBD_BL_WHITE_BRIGHTNESS_IMMEDIATE 0x1802 65 | #define UW_EC_REG_KBD_BL_RGB_RED_BRIGHTNESS_IMMEDIATE 0x1803 66 | #define UW_EC_REG_KBD_BL_RGB_GREEN_BRIGHTNESS_IMMEDIATE 0x1805 67 | #define UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS_IMMEDIATE 0x1808 68 | 69 | #define UW_EC_REG_KBD_BL_RGB_MODE 0x0767 70 | #define UW_EC_REG_KBD_BL_RGB_MODE_BIT_APPLY_COLOR 0x20 71 | #define UW_EC_REG_KBD_BL_RGB_MODE_BIT_RAINBOW 0x80 72 | #define UW_EC_REG_KBD_BL_RGB_RED_BRIGHTNESS 0x0769 73 | #define UW_EC_REG_KBD_BL_RGB_GREEN_BRIGHTNESS 0x076a 74 | #define UW_EC_REG_KBD_BL_RGB_BLUE_BRIGHTNESS 0x076b 75 | 76 | #define UW_EC_REG_KBD_FN_LOCK_STATUS_BIT 0x074e 77 | 78 | #define UW_EC_REG_CUSTOM_PROFILE 0x0727 79 | #define UW_EC_REG_AC_AUTO_BOOT_STATUS 0x0726 80 | #define UW_EC_REG_USB_POWERSHARE_STATUS 0x0767 81 | #define UW_EC_REG_MINI_LED_LOCAL_DIMMING_SUPPORT 0x0D4F 82 | 83 | #define UW_EC_REG_FAN_CTRL_STATUS 0x078e 84 | #define UW_EC_REG_FAN_CTRL_STATUS_BIT_HAS_UW_FAN_CTRL 0x40 85 | 86 | #define UW_EC_REG_CTGP_DB_ENABLE 0x0743 87 | #define UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE 0x01 88 | #define UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE 0x02 89 | #define UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE 0x04 90 | #define UW_EC_REG_CTGP_DB_CTGP_OFFSET 0x0744 91 | #define UW_EC_REG_CTGP_DB_TPP_OFFSET 0x0745 92 | #define UW_EC_REG_CTGP_DB_DB_OFFSET 0x0746 93 | 94 | #define UW_EC_REG_BAREBONE_ID 0x0740 95 | #define UW_EC_REG_BAREBONE_ID_VALUE_PFxxxxx 0x09 96 | #define UW_EC_REG_BAREBONE_ID_VALUE_PFxMxxx 0x0e 97 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TRX1 0x12 98 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TUX1 0x13 99 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4TQx1 0x14 100 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH6TRX1 0x15 101 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH6TQxx 0x16 102 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4Axxx 0x17 103 | #define UW_EC_REG_BAREBONE_ID_VALUE_PH4Pxxx 0x18 104 | 105 | #define UW_EC_REG_FEATURES_0 0x0765 106 | #define UW_EC_REG_FEATURES_1 0x0766 107 | #define UW_EC_REG_FEATURES_1_BIT_1_ZONE_RGB_KB BIT(2) 108 | #define UW_EC_REG_FEATURES_1_BIT_FIXED_COLOR_5_ENABLE BIT(5) 109 | 110 | #define UW_EC_REG_ROMID_START 0x0770 111 | #define UW_EC_REG_ROMID_SPECIAL_1 0x077e 112 | #define UW_EC_REG_ROMID_SPECIAL_2 0x077f 113 | 114 | struct uniwill_interface_t { 115 | char *string_id; 116 | uniwill_event_callb_t *event_callb; 117 | uniwill_read_ec_ram_t *read_ec_ram; 118 | uniwill_write_ec_ram_t *write_ec_ram; 119 | uniwill_wmi_evaluate_t *wmi_evaluate; 120 | }; 121 | 122 | int uniwill_add_interface(struct uniwill_interface_t *new_interface); 123 | int uniwill_remove_interface(struct uniwill_interface_t *interface); 124 | uniwill_read_ec_ram_t uniwill_read_ec_ram; 125 | uniwill_write_ec_ram_t uniwill_write_ec_ram; 126 | uniwill_wmi_evaluate_t uniwill_wmi_evaluate; 127 | uniwill_write_ec_ram_with_retry_t uniwill_write_ec_ram_with_retry; 128 | uniwill_read_ec_ram_with_retry_t uniwill_read_ec_ram_with_retry; 129 | int uniwill_get_active_interface_id(char **id_str); 130 | 131 | #define UW_MODEL_PF5LUXG 0x09 132 | #define UW_MODEL_PH4TUX 0x13 133 | #define UW_MODEL_PH4TRX 0x12 134 | #define UW_MODEL_PH4TQF 0x14 135 | #define UW_MODEL_PH4AQF_ARX 0x17 136 | 137 | struct uniwill_device_features_t { 138 | u8 model; 139 | /** 140 | * Identification for uniwill_power_profile_v1 141 | * 142 | * - Two profiles present in low power devices often called 143 | * "power save" and "balanced". 144 | * - Three profiles present mainly in devices with discrete 145 | * graphics card often called "power save", "balanced" 146 | * and "enthusiast" 147 | */ 148 | bool uniwill_profile_v1; 149 | bool uniwill_profile_v1_two_profs; 150 | bool uniwill_profile_v1_three_profs; 151 | bool uniwill_profile_v1_three_profs_leds_only; 152 | /* 153 | * Identifies devices where mode need to be chosen 154 | * for custom TDP values (and sometimes fan control) to have effect 155 | */ 156 | bool uniwill_custom_profile_mode_needed; 157 | bool uniwill_has_charging_prio; 158 | bool uniwill_has_charging_profile; 159 | bool uniwill_has_universal_ec_fan_control; 160 | bool uniwill_has_double_pl4; 161 | bool uniwill_has_ac_auto_boot; 162 | bool uniwill_has_usb_powershare; 163 | bool uniwill_has_mini_led_local_dimming; 164 | }; 165 | 166 | struct uniwill_device_features_t *uniwill_get_device_features(void); 167 | 168 | union uw_ec_read_return { 169 | u32 dword; 170 | struct { 171 | u8 data_low; 172 | u8 data_high; 173 | } bytes; 174 | }; 175 | 176 | union uw_ec_write_return { 177 | u32 dword; 178 | struct { 179 | u8 addr_low; 180 | u8 addr_high; 181 | u8 data_low; 182 | u8 data_high; 183 | } bytes; 184 | }; 185 | 186 | #endif 187 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_sensors.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include "tuxedo_nb04_wmi_bs.h" 26 | 27 | static int read_cpu_info(u8 *cpu_temp, u8 *cpu_turbo_mode) 28 | { 29 | int err, wmi_return; 30 | u8 in[BS_INPUT_BUFFER_LENGTH]; 31 | u8 out[BS_OUTPUT_BUFFER_LENGTH]; 32 | 33 | err = nb04_wmi_bs_method(0x04, in, out); 34 | if (err) 35 | return err; 36 | 37 | wmi_return = (out[1] << 8) | out[0]; 38 | if (wmi_return != WMI_RETURN_STATUS_SUCCESS) 39 | return -EIO; 40 | 41 | if (cpu_temp) 42 | *cpu_temp = out[2]; 43 | if (cpu_turbo_mode) 44 | *cpu_turbo_mode = out[3]; 45 | 46 | return 0; 47 | } 48 | 49 | static int read_gpu_info(u8 *gpu_temp, u8 *gpu_turbo_mode, u16 *gpu_max_freq) 50 | { 51 | int err, wmi_return; 52 | u8 in[BS_INPUT_BUFFER_LENGTH]; 53 | u8 out[BS_OUTPUT_BUFFER_LENGTH]; 54 | 55 | err = nb04_wmi_bs_method(0x06, in, out); 56 | if (err) 57 | return err; 58 | 59 | wmi_return = (out[1] << 8) | out[0]; 60 | if (wmi_return != WMI_RETURN_STATUS_SUCCESS) 61 | return -EIO; 62 | 63 | if (gpu_temp) 64 | *gpu_temp = out[2]; 65 | if (gpu_turbo_mode) 66 | *gpu_turbo_mode = out[3]; 67 | if (gpu_max_freq) 68 | *gpu_max_freq = (out[5] << 8) | out[4]; 69 | 70 | return 0; 71 | } 72 | 73 | static int read_fan_setting(u16 *fan1_cur_rpm, u16 *fan2_cur_rpm, 74 | u16 *fan1_max_rpm, u16 *fan2_max_rpm, 75 | bool *full_fan_status) 76 | { 77 | int err, wmi_return; 78 | u8 in[BS_INPUT_BUFFER_LENGTH]; 79 | u8 out[BS_OUTPUT_BUFFER_LENGTH]; 80 | 81 | err = nb04_wmi_bs_method(0x02, in, out); 82 | if (err) 83 | return err; 84 | 85 | wmi_return = (out[1] << 8) | out[0]; 86 | if (wmi_return != WMI_RETURN_STATUS_SUCCESS) 87 | return -EIO; 88 | 89 | if (fan1_cur_rpm) 90 | *fan1_cur_rpm = (out[3] << 8) | out[2]; 91 | if (fan2_cur_rpm) 92 | *fan2_cur_rpm = (out[5] << 8) | out[4]; 93 | if (fan1_max_rpm) 94 | *fan1_max_rpm = (out[7] << 8) | out[6]; 95 | if (fan2_max_rpm) 96 | *fan2_max_rpm = (out[9] << 8) | out[8]; 97 | if (full_fan_status) 98 | *full_fan_status = (out[10] == 0x01); 99 | 100 | return 0; 101 | } 102 | 103 | static const char * const temp_labels[] = { 104 | "cpu0", 105 | "gpu0" 106 | }; 107 | 108 | static const char * const fan_labels[] = { 109 | "cpu0", 110 | "gpu0" 111 | }; 112 | 113 | struct driver_data_t { 114 | int fan_cpu_max; 115 | int fan_cpu_min; 116 | int fan_gpu_max; 117 | int fan_gpu_min; 118 | }; 119 | 120 | struct driver_data_t driver_data; 121 | 122 | static umode_t 123 | tuxedo_nb04_sensors_is_visible(const void *drvdata, enum hwmon_sensor_types type, 124 | u32 attr, int channel) 125 | { 126 | return 0444; 127 | } 128 | 129 | static int 130 | tuxedo_nb04_sensors_read(struct device *dev, enum hwmon_sensor_types type, 131 | u32 attr, int channel, long *val) 132 | { 133 | int err; 134 | u8 temp_data; 135 | u16 rpm_data; 136 | struct driver_data_t *driver_data = dev_get_drvdata(dev); 137 | 138 | switch (type) { 139 | case hwmon_temp: 140 | if (channel == 0) { 141 | err = read_cpu_info(&temp_data, NULL); 142 | if (err) 143 | return err; 144 | *val = temp_data * 1000; 145 | return 0; 146 | } else if (channel == 1) { 147 | err = read_gpu_info(&temp_data, NULL, NULL); 148 | if (err) 149 | return err; 150 | *val = temp_data * 1000; 151 | return 0; 152 | } 153 | break; 154 | case hwmon_fan: 155 | switch (attr) { 156 | case hwmon_fan_min: 157 | if (channel == 0) { 158 | *val = driver_data->fan_cpu_min; 159 | return 0; 160 | } else if (channel == 1) { 161 | *val = driver_data->fan_gpu_min; 162 | return 0; 163 | } 164 | break; 165 | case hwmon_fan_max: 166 | if (channel == 0) { 167 | *val = driver_data->fan_cpu_max; 168 | return 0; 169 | } else if (channel == 1) { 170 | *val = driver_data->fan_gpu_max; 171 | return 0; 172 | } 173 | break; 174 | case hwmon_fan_input: 175 | if (channel == 0) { 176 | err = read_fan_setting(&rpm_data, NULL, NULL, NULL, NULL); 177 | if (err) 178 | return err; 179 | *val = rpm_data; 180 | return 0; 181 | } else if (channel == 1) { 182 | err = read_fan_setting(NULL, &rpm_data, NULL, NULL, NULL); 183 | if (err) 184 | return err; 185 | *val = rpm_data; 186 | return 0; 187 | } 188 | break; 189 | default: 190 | break; 191 | } 192 | default: 193 | break; 194 | } 195 | 196 | return -EOPNOTSUPP; 197 | } 198 | 199 | static int 200 | tuxedo_nb04_sensors_read_string(struct device *dev, enum hwmon_sensor_types type, 201 | u32 attr, int channel, const char **str) 202 | { 203 | switch (type) { 204 | case hwmon_temp: 205 | *str = temp_labels[channel]; 206 | return 0; 207 | case hwmon_fan: 208 | *str = fan_labels[channel]; 209 | return 0; 210 | default: 211 | break; 212 | } 213 | 214 | return -EOPNOTSUPP; 215 | } 216 | 217 | static const struct hwmon_ops tuxedo_nb04_sensors_ops = { 218 | .is_visible = tuxedo_nb04_sensors_is_visible, 219 | .read = tuxedo_nb04_sensors_read, 220 | .read_string = tuxedo_nb04_sensors_read_string 221 | }; 222 | 223 | static const struct hwmon_channel_info *const tuxedo_nb04_sensors_info[] = { 224 | HWMON_CHANNEL_INFO(temp, 225 | HWMON_T_INPUT | HWMON_T_LABEL, 226 | HWMON_T_INPUT | HWMON_T_LABEL), 227 | HWMON_CHANNEL_INFO(fan, 228 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX, 229 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX), 230 | NULL 231 | }; 232 | 233 | static const struct hwmon_chip_info tuxedo_nb04_sensors_chip_info = { 234 | .ops = &tuxedo_nb04_sensors_ops, 235 | .info = tuxedo_nb04_sensors_info 236 | }; 237 | 238 | static int __init tuxedo_nb04_sensors_probe(struct platform_device *pdev) 239 | { 240 | struct device *hwmon_dev; 241 | int err; 242 | u16 fan1_max_rpm, fan2_max_rpm; 243 | 244 | err = read_fan_setting(NULL, NULL, &fan1_max_rpm, &fan2_max_rpm, NULL); 245 | if (err) 246 | return err; 247 | 248 | driver_data.fan_cpu_max = fan1_max_rpm; 249 | driver_data.fan_cpu_min = 0; 250 | driver_data.fan_gpu_max = fan2_max_rpm; 251 | driver_data.fan_gpu_min = 0; 252 | 253 | hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 254 | "tuxedo", 255 | &driver_data, 256 | &tuxedo_nb04_sensors_chip_info, 257 | NULL); 258 | 259 | return PTR_ERR_OR_ZERO(hwmon_dev); 260 | } 261 | 262 | static struct platform_device *tuxedo_nb04_sensors_device; 263 | static struct platform_driver tuxedo_nb04_sensors_driver = { 264 | .driver.name = "tuxedo_nb04_sensors", 265 | }; 266 | 267 | static int __init tuxedo_nb04_sensors_init(void) 268 | { 269 | tuxedo_nb04_sensors_device = 270 | platform_create_bundle(&tuxedo_nb04_sensors_driver, 271 | tuxedo_nb04_sensors_probe, NULL, 0, NULL, 0); 272 | 273 | if (IS_ERR(tuxedo_nb04_sensors_device)) 274 | return PTR_ERR(tuxedo_nb04_sensors_device); 275 | 276 | return 0; 277 | } 278 | 279 | static void __exit tuxedo_nb04_sensors_exit(void) 280 | { 281 | platform_device_unregister(tuxedo_nb04_sensors_device); 282 | platform_driver_unregister(&tuxedo_nb04_sensors_driver); 283 | } 284 | 285 | module_init(tuxedo_nb04_sensors_init); 286 | module_exit(tuxedo_nb04_sensors_exit); 287 | 288 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 289 | MODULE_DESCRIPTION("TUXEDO Computers NB04 sensors driver"); 290 | MODULE_LICENSE("GPL"); 291 | -------------------------------------------------------------------------------- /src/clevo_acpi.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2020 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "clevo_interfaces.h" 27 | 28 | #define DRIVER_NAME "clevo_acpi" 29 | 30 | struct clevo_acpi_driver_data_t { 31 | struct acpi_device *adev; 32 | struct clevo_interface_t *clevo_interface; 33 | }; 34 | 35 | static struct clevo_acpi_driver_data_t *active_driver_data = NULL; 36 | 37 | static int clevo_acpi_evaluate(struct acpi_device *device, u8 cmd, u32 arg, union acpi_object **result) 38 | { 39 | int status; 40 | acpi_handle handle; 41 | u64 dsm_rev_dummy = 0x00; // Dummy 0 value since not used 42 | u64 dsm_func = cmd; 43 | union acpi_object *out_obj; 44 | guid_t clevo_acpi_dsm_uuid; 45 | 46 | // Integer package data for argument 47 | union acpi_object dsm_argv4_package_data[] = { 48 | { 49 | .integer.type = ACPI_TYPE_INTEGER, 50 | .integer.value = arg 51 | } 52 | }; 53 | 54 | // Package argument 55 | union acpi_object dsm_argv4 = { 56 | .package.type = ACPI_TYPE_PACKAGE, 57 | .package.count = 1, 58 | .package.elements = dsm_argv4_package_data 59 | }; 60 | 61 | status = guid_parse(CLEVO_ACPI_DSM_UUID, &clevo_acpi_dsm_uuid); 62 | if (status < 0) 63 | return -ENOENT; 64 | 65 | handle = acpi_device_handle(device); 66 | if (handle == NULL) 67 | return -ENODEV; 68 | 69 | out_obj = acpi_evaluate_dsm(handle, &clevo_acpi_dsm_uuid, dsm_rev_dummy, dsm_func, &dsm_argv4); 70 | if (!out_obj) { 71 | pr_err("failed to evaluate _DSM\n"); 72 | status = -1; 73 | } 74 | else { 75 | if (!IS_ERR_OR_NULL(result)) { 76 | *result = out_obj; 77 | } 78 | } 79 | 80 | return status; 81 | } 82 | 83 | static int clevo_acpi_evaluate_pkgbuf(struct acpi_device *device, u8 cmd, u8 *arg, u32 length, union acpi_object **result) 84 | { 85 | int status; 86 | acpi_handle handle; 87 | u64 dsm_rev_dummy = 0x00; // Dummy 0 value since not used 88 | u64 dsm_func = cmd; 89 | 90 | // Use a buffer inside a package 91 | union acpi_object args = { 92 | .buffer.type = ACPI_TYPE_BUFFER, 93 | .buffer.length = length, 94 | .buffer.pointer = arg, 95 | }; 96 | 97 | // Package argument 98 | union acpi_object dsm_argv4 = { 99 | .package.type = ACPI_TYPE_PACKAGE, 100 | .package.count = 1, 101 | .package.elements = &args, 102 | }; 103 | 104 | union acpi_object *out_obj; 105 | 106 | guid_t clevo_acpi_dsm_uuid; 107 | 108 | status = guid_parse(CLEVO_ACPI_DSM_UUID, &clevo_acpi_dsm_uuid); 109 | if (status < 0) 110 | return -ENOENT; 111 | 112 | handle = acpi_device_handle(device); 113 | if (handle == NULL) 114 | return -ENODEV; 115 | 116 | out_obj = acpi_evaluate_dsm(handle, &clevo_acpi_dsm_uuid, dsm_rev_dummy, dsm_func, &dsm_argv4); 117 | if (!out_obj) { 118 | pr_err("failed to evaluate _DSM\n"); 119 | status = -1; 120 | } 121 | else { 122 | if (!IS_ERR_OR_NULL(result)) { 123 | *result = out_obj; 124 | } 125 | } 126 | 127 | return status; 128 | } 129 | 130 | static int clevo_acpi_interface_method_call(u8 cmd, u32 arg, union acpi_object **result_value) 131 | { 132 | int status = 0; 133 | 134 | if (!IS_ERR_OR_NULL(active_driver_data)) { 135 | status = clevo_acpi_evaluate(active_driver_data->adev, cmd, arg, result_value); 136 | } else { 137 | pr_err("acpi method call exec, no driver data found\n"); 138 | pr_err("..for method_call: %0#4x arg: %0#10x\n", cmd, arg); 139 | status = -ENODATA; 140 | } 141 | // 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); 142 | 143 | return status; 144 | } 145 | 146 | static int clevo_acpi_interface_method_call_pkgbuf(u8 cmd, u8 *arg, u32 length, union acpi_object **result_value) 147 | { 148 | int status = 0; 149 | 150 | if (!IS_ERR_OR_NULL(active_driver_data)) { 151 | status = clevo_acpi_evaluate_pkgbuf(active_driver_data->adev, cmd, arg, length, result_value); 152 | } else { 153 | pr_err("acpi method call exec, no driver data found\n"); 154 | pr_err("..for method_call: %0#2x\n", cmd); 155 | status = -ENODATA; 156 | } 157 | 158 | return status; 159 | } 160 | 161 | struct clevo_interface_t clevo_acpi_interface = { 162 | .string_id = CLEVO_INTERFACE_ACPI_STRID, 163 | .method_call = clevo_acpi_interface_method_call, 164 | .method_call_pkgbuf = clevo_acpi_interface_method_call_pkgbuf, 165 | }; 166 | 167 | static int clevo_acpi_add(struct acpi_device *device) 168 | { 169 | struct clevo_acpi_driver_data_t *driver_data; 170 | 171 | driver_data = devm_kzalloc(&device->dev, sizeof(*driver_data), GFP_KERNEL); 172 | if (!driver_data) 173 | return -ENOMEM; 174 | 175 | driver_data->adev = device; 176 | driver_data->clevo_interface = &clevo_acpi_interface; 177 | 178 | active_driver_data = driver_data; 179 | 180 | pr_debug("clevo_acpi driver add\n"); 181 | 182 | // Add this interface 183 | clevo_keyboard_add_interface(&clevo_acpi_interface); 184 | 185 | pr_info("interface initialized\n"); 186 | 187 | return 0; 188 | } 189 | 190 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 191 | static int clevo_acpi_remove(struct acpi_device *device) 192 | #else 193 | static void clevo_acpi_remove(struct acpi_device *device) 194 | #endif 195 | { 196 | pr_debug("clevo_acpi driver remove\n"); 197 | clevo_keyboard_remove_interface(&clevo_acpi_interface); 198 | active_driver_data = NULL; 199 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 200 | return 0; 201 | #endif 202 | } 203 | 204 | static void clevo_acpi_notify(struct acpi_device *device, u32 event) 205 | { 206 | u32 event_value; 207 | union acpi_object *out_obj; 208 | int status; 209 | // struct clevo_acpi_driver_data_t *clevo_acpi_driver_data; 210 | 211 | status = clevo_acpi_evaluate(device, 0x01, 0, &out_obj); 212 | if (!status) { 213 | if (out_obj->type == ACPI_TYPE_INTEGER) { 214 | event_value = (u32)out_obj->integer.value; 215 | } else { 216 | pr_err("return type not integer, use clevo_evaluate_method2\n"); 217 | } 218 | ACPI_FREE(out_obj); 219 | } 220 | pr_debug("clevo_acpi event: %0#6x, clevo event value: %0#6x\n", event, event_value); 221 | 222 | // clevo_acpi_driver_data = container_of(&device, struct clevo_acpi_driver_data_t, adev); 223 | if (!IS_ERR_OR_NULL(clevo_acpi_interface.event_callb)) { 224 | // Execute registered callback 225 | clevo_acpi_interface.event_callb(event); 226 | } 227 | } 228 | 229 | #ifdef CONFIG_PM 230 | static int driver_suspend_callb(struct device *dev) 231 | { 232 | pr_debug("driver suspend\n"); 233 | return 0; 234 | } 235 | 236 | static int driver_resume_callb(struct device *dev) 237 | { 238 | pr_debug("driver resume\n"); 239 | return 0; 240 | } 241 | 242 | static SIMPLE_DEV_PM_OPS(clevo_driver_pm_ops, driver_suspend_callb, driver_resume_callb); 243 | #endif 244 | 245 | static const struct acpi_device_id clevo_acpi_device_ids[] = { 246 | {CLEVO_ACPI_RESOURCE_HID, 0}, 247 | {"", 0} 248 | }; 249 | 250 | static struct acpi_driver clevo_acpi_driver = { 251 | .name = DRIVER_NAME, 252 | .class = DRIVER_NAME, 253 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0) 254 | .owner = THIS_MODULE, 255 | #endif 256 | .ids = clevo_acpi_device_ids, 257 | .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, 258 | .ops = { 259 | .add = clevo_acpi_add, 260 | .remove = clevo_acpi_remove, 261 | .notify = clevo_acpi_notify, 262 | }, 263 | #ifdef CONFIG_PM 264 | .drv.pm = &clevo_driver_pm_ops 265 | #endif 266 | }; 267 | 268 | module_acpi_driver(clevo_acpi_driver); 269 | 270 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 271 | MODULE_DESCRIPTION("Driver for Clevo ACPI interface"); 272 | MODULE_LICENSE("GPL"); 273 | 274 | MODULE_DEVICE_TABLE(acpi, clevo_acpi_device_ids); 275 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_power_profiles.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "tuxedo_nb04_wmi_bs.h" 29 | 30 | #define DEFAULT_PROFILE WMI_SYSTEM_MODE_BEAST 31 | 32 | struct driver_data_t { 33 | struct platform_device *pdev; 34 | u8 current_profile_value; 35 | }; 36 | 37 | static int set_system_mode(u8 mode_input) 38 | { 39 | int err, wmi_return; 40 | u8 in[BS_INPUT_BUFFER_LENGTH] = { 0 }; 41 | u8 out[BS_OUTPUT_BUFFER_LENGTH] = { 0 }; 42 | 43 | if (mode_input >= WMI_SYSTEM_MODE_END) 44 | return -EINVAL; 45 | 46 | in[0] = mode_input; 47 | 48 | err = nb04_wmi_bs_method(0x07, in, out); 49 | if (err) 50 | return err; 51 | 52 | wmi_return = (out[1] << 8) | out[0]; 53 | if (wmi_return != WMI_RETURN_STATUS_SUCCESS) 54 | return -EIO; 55 | 56 | return 0; 57 | } 58 | 59 | static int write_platform_profile_state(struct platform_device *pdev) 60 | { 61 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 62 | return set_system_mode(driver_data->current_profile_value); 63 | } 64 | 65 | static ssize_t platform_profile_choices_show(struct device *dev, 66 | struct device_attribute *attr, 67 | char *buffer); 68 | 69 | static ssize_t platform_profile_show(struct device *dev, 70 | struct device_attribute *attr, char *buffer); 71 | 72 | static ssize_t platform_profile_store(struct device *dev, 73 | struct device_attribute *attr, 74 | const char *buffer, size_t size); 75 | 76 | struct platform_profile_attrs_t { 77 | struct device_attribute platform_profile_choices; 78 | struct device_attribute platform_profile; 79 | }; 80 | 81 | struct platform_profile_attrs_t platform_profile_attrs = { 82 | .platform_profile_choices = __ATTR(platform_profile_choices, 0444, 83 | platform_profile_choices_show, NULL), 84 | .platform_profile = __ATTR(platform_profile, 0644, 85 | platform_profile_show, platform_profile_store) 86 | }; 87 | 88 | static struct attribute *platform_profile_attrs_list[] = { 89 | &platform_profile_attrs.platform_profile_choices.attr, 90 | &platform_profile_attrs.platform_profile.attr, 91 | NULL 92 | }; 93 | 94 | static struct attribute_group platform_profile_attr_group = { 95 | .attrs = platform_profile_attrs_list 96 | }; 97 | 98 | struct char_to_value_t { 99 | char* descriptor; 100 | u64 value; 101 | }; 102 | 103 | static struct char_to_value_t platform_profile_options[] = { 104 | { .descriptor = "low-power", .value = WMI_SYSTEM_MODE_BATTERY }, 105 | { .descriptor = "balanced", .value = WMI_SYSTEM_MODE_HUMAN }, 106 | { .descriptor = "performance", .value = WMI_SYSTEM_MODE_BEAST }, 107 | }; 108 | 109 | static ssize_t platform_profile_choices_show(struct device *dev, 110 | struct device_attribute *attr, 111 | char *buffer) 112 | { 113 | int i, n; 114 | n = ARRAY_SIZE(platform_profile_options); 115 | for (i = 0; i < n; ++i) { 116 | sprintf(buffer + strlen(buffer), "%s", 117 | platform_profile_options[i].descriptor); 118 | if (i < n - 1) 119 | sprintf(buffer + strlen(buffer), " "); 120 | else 121 | sprintf(buffer + strlen(buffer), "\n"); 122 | } 123 | 124 | return strlen(buffer); 125 | } 126 | 127 | static ssize_t platform_profile_show(struct device *dev, 128 | struct device_attribute *attr, char *buffer) 129 | { 130 | u64 platform_profile_value; 131 | int i, err; 132 | struct platform_device *pdev = to_platform_device(dev); 133 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 134 | 135 | err = 0; 136 | platform_profile_value = driver_data->current_profile_value; 137 | 138 | if (err) { 139 | pr_err("Error reading power profile"); 140 | return -EIO; 141 | } 142 | 143 | for (i = 0; i < ARRAY_SIZE(platform_profile_options); ++i) 144 | if (platform_profile_options[i].value == platform_profile_value) { 145 | sprintf(buffer, "%s\n", platform_profile_options[i].descriptor); 146 | return strlen(buffer); 147 | } 148 | 149 | pr_err("Read platform profile value not matched to a descriptor\n"); 150 | 151 | return -EIO; 152 | } 153 | 154 | static ssize_t platform_profile_store(struct device *dev, 155 | struct device_attribute *attr, 156 | const char *buffer, size_t size) 157 | { 158 | struct platform_device *pdev = to_platform_device(dev); 159 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 160 | u64 platform_profile_value; 161 | int i, err; 162 | char *buffer_copy; 163 | char *platform_profile_descriptor; 164 | 165 | buffer_copy = kmalloc(size + 1, GFP_KERNEL); 166 | strcpy(buffer_copy, buffer); 167 | platform_profile_descriptor = strstrip(buffer_copy); 168 | 169 | for (i = 0; i < ARRAY_SIZE(platform_profile_options); ++i) 170 | if (strcmp(platform_profile_options[i].descriptor, platform_profile_descriptor) == 0) { 171 | platform_profile_value = platform_profile_options[i].value; 172 | break; 173 | } 174 | 175 | kfree(buffer_copy); 176 | 177 | if (i < ARRAY_SIZE(platform_profile_options)) { 178 | // Option found try to set 179 | driver_data->current_profile_value = platform_profile_value; 180 | err = write_platform_profile_state(pdev); 181 | if (err) 182 | return err; 183 | return size; 184 | } else { 185 | // Invalid input, not matched to an option 186 | return -EINVAL; 187 | } 188 | } 189 | 190 | static int __init tuxedo_nb04_power_profiles_probe(struct platform_device *pdev) 191 | { 192 | int err; 193 | struct driver_data_t *driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), GFP_KERNEL); 194 | if (!driver_data) 195 | return -ENOMEM; 196 | 197 | pr_debug("driver probe\n"); 198 | 199 | // Sirius uses other platform control interface 200 | if (dmi_match(DMI_SYS_VENDOR, "TUXEDO") && 201 | dmi_match(DMI_PRODUCT_SKU, "SIRIUS1601")) 202 | return -ENODEV; 203 | 204 | dev_set_drvdata(&pdev->dev, driver_data); 205 | 206 | driver_data->pdev = pdev; 207 | driver_data->current_profile_value = DEFAULT_PROFILE; 208 | write_platform_profile_state(pdev); 209 | 210 | err = sysfs_create_group(&driver_data->pdev->dev.kobj, &platform_profile_attr_group); 211 | if (err) { 212 | pr_err("create group failed\n"); 213 | platform_device_unregister(driver_data->pdev); 214 | return err; 215 | } 216 | 217 | return 0; 218 | } 219 | 220 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 221 | static int tuxedo_nb04_power_profiles_remove(struct platform_device *pdev) 222 | #else 223 | static void tuxedo_nb04_power_profiles_remove(struct platform_device *pdev) 224 | #endif 225 | { 226 | pr_debug("driver remove\n"); 227 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 228 | sysfs_remove_group(&driver_data->pdev->dev.kobj, &platform_profile_attr_group); 229 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 230 | return 0; 231 | #endif 232 | } 233 | 234 | static struct platform_device *tuxedo_nb04_power_profiles_device; 235 | static struct platform_driver tuxedo_nb04_power_profiles_driver = { 236 | .driver.name = "tuxedo_platform_profile", 237 | .remove = tuxedo_nb04_power_profiles_remove, 238 | }; 239 | 240 | static int __init tuxedo_nb04_power_profiles_init(void) 241 | { 242 | if (!nb04_wmi_bs_available()) 243 | return -ENODEV; 244 | 245 | tuxedo_nb04_power_profiles_device = 246 | platform_create_bundle(&tuxedo_nb04_power_profiles_driver, 247 | tuxedo_nb04_power_profiles_probe, NULL, 0, NULL, 0); 248 | 249 | if (IS_ERR(tuxedo_nb04_power_profiles_device)) 250 | return PTR_ERR(tuxedo_nb04_power_profiles_device); 251 | 252 | return 0; 253 | } 254 | 255 | static void __exit tuxedo_nb04_power_profiles_exit(void) 256 | { 257 | platform_device_unregister(tuxedo_nb04_power_profiles_device); 258 | platform_driver_unregister(&tuxedo_nb04_power_profiles_driver); 259 | } 260 | 261 | module_init(tuxedo_nb04_power_profiles_init); 262 | module_exit(tuxedo_nb04_power_profiles_exit); 263 | 264 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 265 | MODULE_DESCRIPTION("TUXEDO Computers NB04 platform profile driver"); 266 | MODULE_LICENSE("GPL"); 267 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_keyboard.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "tuxedo_nb05_power_profiles.h" 31 | #include "tuxedo_nb05_kbd_backlight.h" 32 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 33 | 34 | #define NB05_WMI_EVENT_GUID "8FAFC061-22DA-46E2-91DB-1FE3D7E5FF3C" 35 | 36 | #define EVENT_STATUS_COMBO(event, status) (event | (status << 8)) 37 | 38 | #define NB05_WMI_EVENT_TOUCHPAD_TOGGLE 0x02 39 | #define NB05_WMI_EVENT_KBD_BRT_CHANGE 0x03 40 | #define NB05_WMI_EVENT_KBD_BRT_MAX 0x07 41 | #define NB05_WMI_EVENT_KBD_BRT_MIDDLE 0x08 42 | #define NB05_WMI_EVENT_KBD_BRT_OFF 0x09 43 | #define NB05_WMI_EVENT_MODE_POWER_SAVE 0x11 44 | #define NB05_WMI_EVENT_MODE_BALANCE 0x12 45 | #define NB05_WMI_EVENT_MODE_HIGH_PERFORMANCE 0x13 46 | #define NB05_WMI_EVENT_CAMERA_TOGGLE 0x30 47 | #define NB05_WMI_EVENT_FNLOCK_TOGGLE 0x31 48 | 49 | #define NB05_WMI_EVENT_TOUCHPAD_ON EVENT_STATUS_COMBO(NB05_WMI_EVENT_TOUCHPAD_TOGGLE, 0x11) 50 | #define NB05_WMI_EVENT_TOUCHPAD_OFF EVENT_STATUS_COMBO(NB05_WMI_EVENT_TOUCHPAD_TOGGLE, 0x22) 51 | 52 | #define NB05_WMI_EVENT_CAMERA_ON EVENT_STATUS_COMBO(NB05_WMI_EVENT_CAMERA_TOGGLE, 0x11) 53 | #define NB05_WMI_EVENT_CAMERA_OFF EVENT_STATUS_COMBO(NB05_WMI_EVENT_CAMERA_TOGGLE, 0x22) 54 | 55 | #define NB05_WMI_EVENT_FNLOCK_ON EVENT_STATUS_COMBO(NB05_WMI_EVENT_FNLOCK_TOGGLE, 0x11) 56 | #define NB05_WMI_EVENT_FNLOCK_OFF EVENT_STATUS_COMBO(NB05_WMI_EVENT_FNLOCK_TOGGLE, 0x22) 57 | 58 | struct driver_data_t { 59 | struct input_dev *input_dev; 60 | }; 61 | 62 | static struct key_entry driver_keymap[] = { 63 | { KE_KEY, NB05_WMI_EVENT_TOUCHPAD_ON, { KEY_F22 } }, 64 | { KE_KEY, NB05_WMI_EVENT_TOUCHPAD_OFF, { KEY_F23 } }, 65 | // Only used to put ev bits 66 | { KE_KEY, 0xffff, { KEY_F6 } }, 67 | { KE_KEY, 0xffff, { KEY_LEFTALT } }, 68 | { KE_KEY, 0xffff, { KEY_LEFTMETA } }, 69 | { KE_END, 0 } 70 | }; 71 | 72 | static void __attribute__ ((unused)) report_gauge_key_combo(struct input_dev *idev) 73 | { 74 | // Special key combination when mode change key is pressed 75 | input_report_key(idev, KEY_LEFTMETA, 1); 76 | input_report_key(idev, KEY_LEFTALT, 1); 77 | input_report_key(idev, KEY_F6, 1); 78 | input_sync(idev); 79 | input_report_key(idev, KEY_F6, 0); 80 | input_report_key(idev, KEY_LEFTALT, 0); 81 | input_report_key(idev, KEY_LEFTMETA, 0); 82 | input_sync(idev); 83 | } 84 | 85 | /** 86 | * Basically a copy of the existing report event but doesn't report unknown events 87 | */ 88 | static bool sparse_keymap_report_known_event(struct input_dev *dev, 89 | unsigned int code, 90 | unsigned int value, 91 | bool autorelease) 92 | { 93 | const struct key_entry *ke = 94 | sparse_keymap_entry_from_scancode(dev, code); 95 | 96 | if (ke) { 97 | sparse_keymap_report_entry(dev, ke, value, autorelease); 98 | return true; 99 | } 100 | 101 | return false; 102 | } 103 | 104 | static int input_device_init(struct input_dev **input_dev_pp, const struct key_entry key_map[]) 105 | { 106 | struct input_dev *input_dev; 107 | int err; 108 | 109 | input_dev = input_allocate_device(); 110 | if (unlikely(!input_dev)) { 111 | pr_err("Error allocating input device\n"); 112 | return -ENOMEM; 113 | } 114 | 115 | input_dev->name = "TUXEDO Keyboard Events"; 116 | input_dev->phys = "tuxedo-keyboard" "/input0"; 117 | input_dev->id.bustype = BUS_HOST; 118 | input_dev->dev.parent = NULL; 119 | 120 | if (key_map != NULL) { 121 | err = sparse_keymap_setup(input_dev, key_map, NULL); 122 | if (err) { 123 | pr_err("Failed to setup sparse keymap\n"); 124 | goto err_free_input_device; 125 | } 126 | } 127 | 128 | err = input_register_device(input_dev); 129 | if (unlikely(err)) { 130 | pr_err("Error registering input device\n"); 131 | goto err_free_input_device; 132 | } 133 | 134 | *input_dev_pp = input_dev; 135 | 136 | return 0; 137 | 138 | err_free_input_device: 139 | input_free_device(input_dev); 140 | 141 | return err; 142 | } 143 | 144 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 145 | static int tuxedo_nb05_keyboard_probe(struct wmi_device *wdev) 146 | #else 147 | static int tuxedo_nb05_keyboard_probe(struct wmi_device *wdev, const void *dummy_context) 148 | #endif 149 | { 150 | struct driver_data_t *driver_data; 151 | int err; 152 | 153 | pr_debug("driver probe\n"); 154 | 155 | if (!tuxedo_is_compatible()) 156 | return -ENODEV; 157 | 158 | if (!wmi_has_guid(NB05_WMI_EVENT_GUID)) 159 | return -ENODEV; 160 | 161 | driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL); 162 | if (!driver_data) 163 | return -ENOMEM; 164 | 165 | dev_set_drvdata(&wdev->dev, driver_data); 166 | 167 | err = input_device_init(&driver_data->input_dev, driver_keymap); 168 | if (err) 169 | return err; 170 | 171 | return 0; 172 | } 173 | 174 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 175 | static int tuxedo_nb05_keyboard_remove(struct wmi_device *wdev) 176 | #else 177 | static void tuxedo_nb05_keyboard_remove(struct wmi_device *wdev) 178 | #endif 179 | { 180 | pr_debug("driver remove\n"); 181 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 182 | input_unregister_device(driver_data->input_dev); 183 | 184 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 185 | return 0; 186 | #endif 187 | } 188 | 189 | static void tuxedo_nb05_keyboard_notify(struct wmi_device *wdev, union acpi_object *obj) 190 | { 191 | u8 function_number; 192 | u8 event_code; 193 | u16 device_status; 194 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 195 | 196 | if (obj && 197 | obj->type == ACPI_TYPE_BUFFER && 198 | obj->buffer.length >= 4) 199 | { 200 | function_number = obj->buffer.pointer[0]; 201 | event_code = obj->buffer.pointer[1]; 202 | device_status = obj->buffer.pointer[2] | 203 | (obj->buffer.pointer[3] << 8); 204 | pr_debug("event value: %d (%0#4x), device status %d (%0#6x)\n", 205 | event_code, event_code, device_status, device_status); 206 | 207 | switch (event_code) { 208 | case NB05_WMI_EVENT_MODE_POWER_SAVE: 209 | case NB05_WMI_EVENT_MODE_BALANCE: 210 | case NB05_WMI_EVENT_MODE_HIGH_PERFORMANCE: 211 | if (!profile_changed_by_driver()) { 212 | report_gauge_key_combo(driver_data->input_dev); 213 | rewrite_last_profile(); 214 | } 215 | break; 216 | case NB05_WMI_EVENT_KBD_BRT_MAX: 217 | nb05_leds_notify_brightness_change_extern(2); 218 | break; 219 | case NB05_WMI_EVENT_KBD_BRT_MIDDLE: 220 | nb05_leds_notify_brightness_change_extern(1); 221 | break; 222 | case NB05_WMI_EVENT_KBD_BRT_OFF: 223 | nb05_leds_notify_brightness_change_extern(0); 224 | break; 225 | case NB05_WMI_EVENT_KBD_BRT_CHANGE: 226 | nb05_leds_notify_brightness_change_extern(device_status); 227 | break; 228 | default: 229 | break; 230 | } 231 | 232 | // Report event_code 233 | sparse_keymap_report_known_event(driver_data->input_dev, 234 | event_code, 235 | 1, 236 | true); 237 | // Report combined event code and device status 238 | sparse_keymap_report_known_event(driver_data->input_dev, 239 | EVENT_STATUS_COMBO(event_code, device_status), 240 | 1, 241 | true); 242 | } else { 243 | pr_debug("expected buffer not found\n"); 244 | } 245 | } 246 | 247 | static const struct wmi_device_id tuxedo_nb05_keyboard_device_ids[] = { 248 | { .guid_string = NB05_WMI_EVENT_GUID }, 249 | { } 250 | }; 251 | 252 | static struct wmi_driver tuxedo_nb05_keyboard_driver = { 253 | .driver = { 254 | .name = "tuxedo_nb05_keyboard", 255 | .owner = THIS_MODULE 256 | }, 257 | .id_table = tuxedo_nb05_keyboard_device_ids, 258 | .probe = tuxedo_nb05_keyboard_probe, 259 | .remove = tuxedo_nb05_keyboard_remove, 260 | .notify = tuxedo_nb05_keyboard_notify, 261 | }; 262 | 263 | module_wmi_driver(tuxedo_nb05_keyboard_driver); 264 | 265 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 266 | MODULE_DESCRIPTION("Driver for NB05 keyboard events"); 267 | MODULE_LICENSE("GPL"); 268 | 269 | MODULE_DEVICE_TABLE(wmi, tuxedo_nb05_keyboard_device_ids); 270 | MODULE_ALIAS("wmi:" NB05_WMI_EVENT_GUID); 271 | -------------------------------------------------------------------------------- /src/ite_8297/ite_8297.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2020 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | // USB HID feature data write size 28 | #define HID_DATA_SIZE 64 29 | 30 | // led_classdev names, default and max brightness 31 | #define LED_MAX_BRIGHTNESS 0xff 32 | #define ITE_8297_DEFAULT_BRIGHTNESS 0x00 33 | #define LED_NAME_RGB_RED KBUILD_MODNAME ":1" 34 | #define LED_NAME_RGB_GREEN KBUILD_MODNAME ":2" 35 | #define LED_NAME_RGB_BLUE KBUILD_MODNAME ":3" 36 | 37 | struct color_t { 38 | u8 red; 39 | u8 green; 40 | u8 blue; 41 | }; 42 | 43 | struct ite8297_driver_data_t { 44 | struct led_classdev cdev_red; 45 | struct led_classdev cdev_green; 46 | struct led_classdev cdev_blue; 47 | struct hid_device *hid_dev; 48 | struct color_t current_color; 49 | }; 50 | 51 | static int ite8297_write_color(struct hid_device *hdev, u8 red, u8 green, u8 blue) 52 | { 53 | int result = 0; 54 | u8 *buf; 55 | if (hdev == NULL) 56 | return -ENODEV; 57 | 58 | buf = kzalloc(HID_DATA_SIZE, GFP_KERNEL); 59 | buf[0] = 0xcc; 60 | buf[1] = 0xb0; 61 | buf[2] = 0x01; 62 | buf[3] = 0x01; 63 | buf[4] = red; 64 | buf[5] = green; 65 | buf[6] = blue; 66 | 67 | result = hid_hw_raw_request(hdev, buf[0], buf, HID_DATA_SIZE, 68 | HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 69 | kfree(buf); 70 | 71 | return result; 72 | } 73 | 74 | static int ite8297_write_state(struct ite8297_driver_data_t *ite8297_driver_data) 75 | { 76 | return ite8297_write_color(ite8297_driver_data->hid_dev, 77 | ite8297_driver_data->current_color.red, 78 | ite8297_driver_data->current_color.green, 79 | ite8297_driver_data->current_color.blue); 80 | } 81 | 82 | static int lightbar_set_blocking(struct led_classdev *led_cdev, enum led_brightness brightness) 83 | { 84 | bool led_red = strstr(led_cdev->name, LED_NAME_RGB_RED) != NULL; 85 | bool led_green = strstr(led_cdev->name, LED_NAME_RGB_GREEN) != NULL; 86 | bool led_blue = strstr(led_cdev->name, LED_NAME_RGB_BLUE) != NULL; 87 | struct ite8297_driver_data_t *ite8297_driver_data; 88 | 89 | if (led_red) { 90 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_red); 91 | ite8297_driver_data->current_color.red = brightness; 92 | } else if (led_green) { 93 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_green); 94 | ite8297_driver_data->current_color.green = brightness; 95 | } else if (led_blue) { 96 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_blue); 97 | ite8297_driver_data->current_color.blue = brightness; 98 | } 99 | ite8297_write_state(ite8297_driver_data); 100 | 101 | return 0; 102 | } 103 | 104 | static enum led_brightness lightbar_get(struct led_classdev *led_cdev) 105 | { 106 | bool led_red = strstr(led_cdev->name, LED_NAME_RGB_RED) != NULL; 107 | bool led_green = strstr(led_cdev->name, LED_NAME_RGB_GREEN) != NULL; 108 | bool led_blue = strstr(led_cdev->name, LED_NAME_RGB_BLUE) != NULL; 109 | struct ite8297_driver_data_t *ite8297_driver_data; 110 | 111 | if (led_red) { 112 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_red); 113 | return ite8297_driver_data->current_color.red; 114 | } else if (led_green) { 115 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_green); 116 | return ite8297_driver_data->current_color.green; 117 | } else if (led_blue) { 118 | ite8297_driver_data = container_of(led_cdev, struct ite8297_driver_data_t, cdev_blue); 119 | return ite8297_driver_data->current_color.blue; 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | static void stop_hw(struct hid_device *hdev) 126 | { 127 | hid_hw_power(hdev, PM_HINT_NORMAL); 128 | hid_hw_close(hdev); 129 | hid_hw_stop(hdev); 130 | } 131 | 132 | static int start_hw(struct hid_device *hdev) 133 | { 134 | int result; 135 | result = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 136 | if (result) { 137 | pr_err("hid_hw_start failed\n"); 138 | goto err_stop_hw; 139 | } 140 | 141 | hid_hw_power(hdev, PM_HINT_FULLON); 142 | 143 | result = hid_hw_open(hdev); 144 | if (result) { 145 | pr_err("hid_hw_open failed\n"); 146 | goto err_stop_hw; 147 | } 148 | 149 | return 0; 150 | 151 | err_stop_hw: 152 | stop_hw(hdev); 153 | return result; 154 | } 155 | 156 | static int driver_probe_callb(struct hid_device *hdev, const struct hid_device_id *id) 157 | { 158 | int result; 159 | struct ite8297_driver_data_t *ite8297_driver_data; 160 | 161 | result = hid_parse(hdev); 162 | if (result) { 163 | pr_err("hid_parse failed\n"); 164 | stop_hw(hdev); 165 | return result; 166 | } 167 | 168 | result = start_hw(hdev); 169 | if (result != 0) 170 | return result; 171 | 172 | ite8297_driver_data = devm_kzalloc(&hdev->dev, sizeof(*ite8297_driver_data), GFP_KERNEL); 173 | if (!ite8297_driver_data) 174 | return -ENOMEM; 175 | 176 | ite8297_driver_data->cdev_red.name = LED_NAME_RGB_RED; 177 | ite8297_driver_data->cdev_red.max_brightness = LED_MAX_BRIGHTNESS; 178 | ite8297_driver_data->cdev_red.brightness_set_blocking = &lightbar_set_blocking; 179 | ite8297_driver_data->cdev_red.brightness_get = &lightbar_get; 180 | 181 | ite8297_driver_data->cdev_green.name = LED_NAME_RGB_GREEN; 182 | ite8297_driver_data->cdev_green.max_brightness = LED_MAX_BRIGHTNESS; 183 | ite8297_driver_data->cdev_green.brightness_set_blocking = &lightbar_set_blocking; 184 | ite8297_driver_data->cdev_green.brightness_get = &lightbar_get; 185 | 186 | ite8297_driver_data->cdev_blue.name = LED_NAME_RGB_BLUE; 187 | ite8297_driver_data->cdev_blue.max_brightness = LED_MAX_BRIGHTNESS; 188 | ite8297_driver_data->cdev_blue.brightness_set_blocking = &lightbar_set_blocking; 189 | ite8297_driver_data->cdev_blue.brightness_get = &lightbar_get; 190 | 191 | ite8297_driver_data->hid_dev = hdev; 192 | ite8297_driver_data->current_color.red = ITE_8297_DEFAULT_BRIGHTNESS; 193 | ite8297_driver_data->current_color.green = ITE_8297_DEFAULT_BRIGHTNESS; 194 | ite8297_driver_data->current_color.blue = ITE_8297_DEFAULT_BRIGHTNESS; 195 | 196 | led_classdev_register(&hdev->dev, &ite8297_driver_data->cdev_red); 197 | led_classdev_register(&hdev->dev, &ite8297_driver_data->cdev_green); 198 | led_classdev_register(&hdev->dev, &ite8297_driver_data->cdev_blue); 199 | 200 | hid_set_drvdata(hdev, ite8297_driver_data); 201 | 202 | result = ite8297_write_state(ite8297_driver_data); 203 | if (result < 0) 204 | return result; 205 | 206 | return 0; 207 | } 208 | 209 | static void driver_remove_callb(struct hid_device *hdev) 210 | { 211 | struct ite8297_driver_data_t *ite8297_driver_data = hid_get_drvdata(hdev); 212 | if (!IS_ERR_OR_NULL(ite8297_driver_data)) { 213 | led_classdev_unregister(&ite8297_driver_data->cdev_red); 214 | led_classdev_unregister(&ite8297_driver_data->cdev_green); 215 | led_classdev_unregister(&ite8297_driver_data->cdev_blue); 216 | } else { 217 | pr_debug("driver data not found\n"); 218 | } 219 | stop_hw(hdev); 220 | pr_debug("driver remove\n"); 221 | } 222 | 223 | #ifdef CONFIG_PM 224 | static int driver_suspend_callb(struct hid_device *hdev, pm_message_t message) 225 | { 226 | pr_debug("driver suspend\n"); 227 | return 0; 228 | } 229 | 230 | static int driver_resume_callb(struct hid_device *hdev) 231 | { 232 | struct ite8297_driver_data_t *ite8297_driver_data = hid_get_drvdata(hdev); 233 | pr_debug("driver resume\n"); 234 | return ite8297_write_state(ite8297_driver_data); 235 | } 236 | 237 | static int driver_reset_resume_callb(struct hid_device *hdev) 238 | { 239 | struct ite8297_driver_data_t *ite8297_driver_data = hid_get_drvdata(hdev); 240 | pr_debug("driver reset resume\n"); 241 | return ite8297_write_state(ite8297_driver_data); 242 | } 243 | #endif 244 | 245 | static const struct hid_device_id ite8297_device_table[] = { 246 | { HID_USB_DEVICE(0x048d, 0x8297) }, 247 | { } 248 | }; 249 | MODULE_DEVICE_TABLE(hid, ite8297_device_table); 250 | 251 | static struct hid_driver ite8297_driver = { 252 | .name = KBUILD_MODNAME, 253 | .probe = driver_probe_callb, 254 | .remove = driver_remove_callb, 255 | .id_table = ite8297_device_table, 256 | #ifdef CONFIG_PM 257 | .suspend = driver_suspend_callb, 258 | .resume = driver_resume_callb, 259 | .reset_resume = driver_reset_resume_callb 260 | #endif 261 | }; 262 | module_hid_driver(ite8297_driver); 263 | 264 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 265 | MODULE_DESCRIPTION("Driver for IT8297 RGB LED Controller"); 266 | MODULE_LICENSE("GPL"); 267 | -------------------------------------------------------------------------------- /src/tuxedo_tuxi/tuxi_acpi.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "tuxi_acpi.h" 27 | 28 | #define DRIVER_NAME "tuxi_acpi" 29 | 30 | struct tuxi_acpi_driver_data_t { 31 | struct acpi_device *tuxi_adev; 32 | acpi_handle tfan_handle; 33 | }; 34 | 35 | static struct tuxi_acpi_driver_data_t *tuxi_driver_data = NULL; 36 | 37 | static 38 | int evaluate_intparams(acpi_handle handle, 39 | acpi_string pathname, 40 | unsigned long long *int_params, 41 | u32 param_count, 42 | unsigned long long *retval); 43 | 44 | static 45 | int evaluate_intparams(acpi_handle handle, 46 | acpi_string pathname, 47 | unsigned long long *int_params, 48 | u32 param_count, 49 | unsigned long long *retval) 50 | { 51 | struct acpi_object_list input; 52 | union acpi_object *params; 53 | unsigned long long result; 54 | acpi_status status; 55 | int i; 56 | u32 param_buffer_size = sizeof(union acpi_object) * param_count; 57 | 58 | if (!handle) 59 | return -ENODEV; 60 | 61 | if (param_buffer_size > 0) 62 | params = kzalloc(param_buffer_size, GFP_KERNEL); 63 | 64 | for (i = 0; i < param_count; ++i) { 65 | params[i].type = ACPI_TYPE_INTEGER; 66 | params[i].integer.value = int_params[i]; 67 | } 68 | 69 | input.count = param_count; 70 | input.pointer = params; 71 | 72 | if (param_buffer_size > 0) { 73 | status = acpi_evaluate_integer(handle, pathname, &input, &result); 74 | kfree(params); 75 | } else { 76 | status = acpi_evaluate_integer(handle, pathname, NULL, &result); 77 | } 78 | 79 | if (ACPI_FAILURE(status)) 80 | return -EIO; 81 | 82 | if (retval) 83 | *retval = result; 84 | 85 | return 0; 86 | } 87 | 88 | int tuxi_set_fan_speed(u8 fan_index, u8 fan_speed) 89 | { 90 | long long int retval; 91 | long long int args[] = { fan_index, fan_speed }; 92 | int err; 93 | 94 | if (tuxi_driver_data == NULL) 95 | return -ENODEV; 96 | 97 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 98 | "SSPD", 99 | args, ARRAY_SIZE(args), 100 | &retval); 101 | if (err) 102 | return err; 103 | 104 | if (retval) 105 | return -EINVAL; 106 | 107 | return 0; 108 | } 109 | EXPORT_SYMBOL(tuxi_set_fan_speed); 110 | 111 | int tuxi_get_fan_speed(u8 fan_index, u8 *fan_speed) 112 | { 113 | long long int retval; 114 | long long int args[] = { fan_index }; 115 | int err; 116 | 117 | if (tuxi_driver_data == NULL) 118 | return -ENODEV; 119 | 120 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 121 | "GSPD", 122 | args, ARRAY_SIZE(args), 123 | &retval); 124 | if (err) 125 | return err; 126 | 127 | if (retval < 0) 128 | return -EINVAL; 129 | 130 | *fan_speed = (u8) retval; 131 | 132 | return 0; 133 | } 134 | EXPORT_SYMBOL(tuxi_get_fan_speed); 135 | 136 | int tuxi_get_nr_fans(u8 *nr_fans) 137 | { 138 | long long int retval; 139 | int err; 140 | 141 | if (tuxi_driver_data == NULL) 142 | return -ENODEV; 143 | 144 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 145 | "GCNT", 146 | NULL, 0, 147 | &retval); 148 | if (err) 149 | return err; 150 | 151 | *nr_fans = (u8) retval; 152 | 153 | return 0; 154 | } 155 | EXPORT_SYMBOL(tuxi_get_nr_fans); 156 | 157 | int tuxi_set_fan_mode(enum tuxi_fan_mode mode) 158 | { 159 | long long int retval; 160 | long long int args[] = { mode }; 161 | int err; 162 | 163 | if (tuxi_driver_data == NULL) 164 | return -ENODEV; 165 | 166 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 167 | "SMOD", 168 | args, ARRAY_SIZE(args), 169 | &retval); 170 | if (err) 171 | return err; 172 | 173 | if (retval < 0) 174 | return -EINVAL; 175 | 176 | return 0; 177 | } 178 | EXPORT_SYMBOL(tuxi_set_fan_mode); 179 | 180 | int tuxi_get_fan_mode(enum tuxi_fan_mode *mode) 181 | { 182 | long long int retval; 183 | int err; 184 | 185 | if (tuxi_driver_data == NULL) 186 | return -ENODEV; 187 | 188 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 189 | "GMOD", 190 | NULL, 0, 191 | &retval); 192 | if (err) 193 | return err; 194 | 195 | if (retval < 0) 196 | return -EINVAL; 197 | 198 | *mode = (u8) retval; 199 | 200 | return 0; 201 | } 202 | EXPORT_SYMBOL(tuxi_get_fan_mode); 203 | 204 | int tuxi_get_fan_type(u8 fan_index, enum tuxi_fan_type *type) 205 | { 206 | long long int retval; 207 | long long int args[] = { fan_index }; 208 | int err; 209 | 210 | if (tuxi_driver_data == NULL) 211 | return -ENODEV; 212 | 213 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 214 | "GTYP", 215 | args, ARRAY_SIZE(args), 216 | &retval); 217 | if (err) 218 | return err; 219 | 220 | if (retval < 0) 221 | return -EINVAL; 222 | 223 | *type = (u8) retval; 224 | 225 | return 0; 226 | } 227 | EXPORT_SYMBOL(tuxi_get_fan_type); 228 | 229 | int tuxi_get_fan_temp(u8 index, u16 *temp) 230 | { 231 | unsigned long long int retval; 232 | long long int args[] = { index }; 233 | int err; 234 | 235 | if (tuxi_driver_data == NULL) 236 | return -ENODEV; 237 | 238 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 239 | "GTMP", 240 | args, ARRAY_SIZE(args), 241 | &retval); 242 | if (err) 243 | return err; 244 | 245 | if (retval < 0) 246 | return -EINVAL; 247 | 248 | *temp = (u16) retval; 249 | 250 | return 0; 251 | } 252 | EXPORT_SYMBOL(tuxi_get_fan_temp); 253 | 254 | int tuxi_get_fan_rpm(u8 index, u16 *rpm) 255 | { 256 | unsigned long long int retval; 257 | long long int args[] = { index }; 258 | int err; 259 | 260 | if (tuxi_driver_data == NULL) 261 | return -ENODEV; 262 | 263 | err = evaluate_intparams(tuxi_driver_data->tfan_handle, 264 | "GRPM", 265 | args, ARRAY_SIZE(args), 266 | &retval); 267 | if (err) 268 | return err; 269 | 270 | if (retval < 0) 271 | return -EINVAL; 272 | 273 | *rpm = (u16) retval; 274 | 275 | return 0; 276 | } 277 | EXPORT_SYMBOL(tuxi_get_fan_rpm); 278 | 279 | static int get_tfan(struct acpi_device *tuxi_dev, acpi_handle *tfan_handle) 280 | { 281 | acpi_status status; 282 | status = acpi_get_handle(tuxi_dev->handle, "TFAN", tfan_handle); 283 | if (ACPI_FAILURE(status)) 284 | return -ENODEV; 285 | return 0; 286 | } 287 | 288 | static int tuxi_acpi_add(struct acpi_device *device) 289 | { 290 | struct tuxi_acpi_driver_data_t *driver_data; 291 | int err; 292 | 293 | driver_data = devm_kzalloc(&device->dev, sizeof(*driver_data), GFP_KERNEL); 294 | if (!driver_data) 295 | return -ENOMEM; 296 | 297 | driver_data->tuxi_adev = device; 298 | device->driver_data = driver_data; 299 | 300 | // Find subdevices 301 | err = get_tfan(device, &driver_data->tfan_handle); 302 | if (err) 303 | driver_data->tfan_handle = NULL; 304 | 305 | if (!driver_data->tfan_handle) 306 | pr_info("no interface found\n"); 307 | 308 | tuxi_driver_data = driver_data; 309 | 310 | pr_info("interface initialized\n"); 311 | 312 | return 0; 313 | } 314 | 315 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 316 | static int tuxi_acpi_remove(struct acpi_device *device) 317 | #else 318 | static void tuxi_acpi_remove(struct acpi_device *device) 319 | #endif 320 | { 321 | tuxi_driver_data = NULL; 322 | pr_debug("driver remove\n"); 323 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) 324 | return 0; 325 | #endif 326 | } 327 | 328 | static void tuxi_acpi_notify(struct acpi_device *device, u32 event) 329 | { 330 | pr_debug("event: %d\n", event); 331 | } 332 | 333 | #ifdef CONFIG_PM 334 | static int driver_suspend_callb(struct device *dev) 335 | { 336 | pr_debug("driver suspend\n"); 337 | return 0; 338 | } 339 | 340 | static int driver_resume_callb(struct device *dev) 341 | { 342 | pr_debug("driver resume\n"); 343 | return 0; 344 | } 345 | 346 | static SIMPLE_DEV_PM_OPS(tuxi_driver_pm_ops, driver_suspend_callb, driver_resume_callb); 347 | #endif 348 | 349 | static const struct acpi_device_id tuxi_acpi_device_ids[] = { 350 | { TUXI_ACPI_RESOURCE_HID, 0 }, 351 | { "", 0 } 352 | }; 353 | 354 | static struct acpi_driver tuxi_acpi_driver = { 355 | .name = DRIVER_NAME, 356 | .class = DRIVER_NAME, 357 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0) 358 | .owner = THIS_MODULE, 359 | #endif 360 | .ids = tuxi_acpi_device_ids, 361 | .flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS, 362 | .ops = { 363 | .add = tuxi_acpi_add, 364 | .remove = tuxi_acpi_remove, 365 | .notify = tuxi_acpi_notify, 366 | }, 367 | #ifdef CONFIG_PM 368 | .drv.pm = &tuxi_driver_pm_ops 369 | #endif 370 | }; 371 | 372 | module_acpi_driver(tuxi_acpi_driver); 373 | 374 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 375 | MODULE_DESCRIPTION("Driver for TUXEDO ACPI interface"); 376 | MODULE_LICENSE("GPL"); 377 | 378 | MODULE_DEVICE_TABLE(acpi, tuxi_acpi_device_ids); 379 | -------------------------------------------------------------------------------- /src/tuxedo_compatibility_check/tuxedo_compatibility_check.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #include "tuxedo_compatibility_check.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | // Defines that might be missing in older kernel headers 31 | #define INTEL_FAM6_SAPPHIRERAPIDS_X 0x8F 32 | #define INTEL_FAM6_ALDERLAKE 0x97 33 | #define INTEL_FAM6_ALDERLAKE_L 0x9A 34 | #define INTEL_FAM6_ALDERLAKE_N 0xBE 35 | 36 | // This check was not in place before 2023, so we continue to assume 37 | // compatibility for devices <= Intel Core i 12th Gen and <= AMD Ryzen 5th Gen 38 | static const struct x86_cpu_id skip_tuxedo_dmi_string_check_match[] = { 39 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0) 40 | X86_MATCH_INTEL_FAM6_MODEL(CORE_YONAH, NULL), 41 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_MEROM, NULL), 42 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_MEROM_L, NULL), 43 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_PENRYN, NULL), 44 | X86_MATCH_INTEL_FAM6_MODEL(CORE2_DUNNINGTON, NULL), 45 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM, NULL), 46 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_G, NULL), 47 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_EP, NULL), 48 | X86_MATCH_INTEL_FAM6_MODEL(NEHALEM_EX, NULL), 49 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE, NULL), 50 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE_EP, NULL), 51 | X86_MATCH_INTEL_FAM6_MODEL(WESTMERE_EX, NULL), 52 | X86_MATCH_INTEL_FAM6_MODEL(SANDYBRIDGE, NULL), 53 | X86_MATCH_INTEL_FAM6_MODEL(SANDYBRIDGE_X, NULL), 54 | X86_MATCH_INTEL_FAM6_MODEL(IVYBRIDGE, NULL), 55 | X86_MATCH_INTEL_FAM6_MODEL(IVYBRIDGE_X, NULL), 56 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL, NULL), 57 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_X, NULL), 58 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_L, NULL), 59 | X86_MATCH_INTEL_FAM6_MODEL(HASWELL_G, NULL), 60 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL, NULL), 61 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), 62 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), 63 | X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), 64 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, NULL), 65 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, NULL), 66 | X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), 67 | X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, NULL), 68 | X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, NULL), 69 | X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE, NULL), 70 | X86_MATCH_INTEL_FAM6_MODEL(COMETLAKE_L, NULL), 71 | X86_MATCH_INTEL_FAM6_MODEL(CANNONLAKE_L, NULL), 72 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), 73 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), 74 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE, NULL), 75 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_L, NULL), 76 | X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_NNPI, NULL), 77 | X86_MATCH_INTEL_FAM6_MODEL(LAKEFIELD, NULL), 78 | X86_MATCH_INTEL_FAM6_MODEL(ROCKETLAKE, NULL), 79 | X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE_L, NULL), 80 | X86_MATCH_INTEL_FAM6_MODEL(TIGERLAKE, NULL), 81 | X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), 82 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL), 83 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL), 84 | X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_N, NULL), 85 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_BONNELL, NULL), 86 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_BONNELL_MID, NULL), 87 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL, NULL), 88 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_MID, NULL), 89 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_TABLET, NULL), 90 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT, NULL), 91 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_D, NULL), 92 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, NULL), 93 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT, NULL), 94 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT_MID, NULL), 95 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_AIRMONT_NP, NULL), 96 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, NULL), 97 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_D, NULL), 98 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_PLUS, NULL), 99 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_D, NULL), 100 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT, NULL), 101 | X86_MATCH_INTEL_FAM6_MODEL(ATOM_TREMONT_L, NULL), 102 | X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNL, NULL), 103 | X86_MATCH_INTEL_FAM6_MODEL(XEON_PHI_KNM, NULL), 104 | X86_MATCH_VENDOR_FAM_MODEL(INTEL, 5, INTEL_FAM5_QUARK_X1000, NULL), 105 | #else 106 | X86_MATCH_VFM(INTEL_CORE_YONAH, NULL), 107 | X86_MATCH_VFM(INTEL_CORE2_MEROM, NULL), 108 | X86_MATCH_VFM(INTEL_CORE2_MEROM_L, NULL), 109 | X86_MATCH_VFM(INTEL_CORE2_PENRYN, NULL), 110 | X86_MATCH_VFM(INTEL_CORE2_DUNNINGTON, NULL), 111 | X86_MATCH_VFM(INTEL_NEHALEM, NULL), 112 | X86_MATCH_VFM(INTEL_NEHALEM_G, NULL), 113 | X86_MATCH_VFM(INTEL_NEHALEM_EP, NULL), 114 | X86_MATCH_VFM(INTEL_NEHALEM_EX, NULL), 115 | X86_MATCH_VFM(INTEL_WESTMERE, NULL), 116 | X86_MATCH_VFM(INTEL_WESTMERE_EP, NULL), 117 | X86_MATCH_VFM(INTEL_WESTMERE_EX, NULL), 118 | X86_MATCH_VFM(INTEL_SANDYBRIDGE, NULL), 119 | X86_MATCH_VFM(INTEL_SANDYBRIDGE_X, NULL), 120 | X86_MATCH_VFM(INTEL_IVYBRIDGE, NULL), 121 | X86_MATCH_VFM(INTEL_IVYBRIDGE_X, NULL), 122 | X86_MATCH_VFM(INTEL_HASWELL, NULL), 123 | X86_MATCH_VFM(INTEL_HASWELL_X, NULL), 124 | X86_MATCH_VFM(INTEL_HASWELL_L, NULL), 125 | X86_MATCH_VFM(INTEL_HASWELL_G, NULL), 126 | X86_MATCH_VFM(INTEL_BROADWELL, NULL), 127 | X86_MATCH_VFM(INTEL_BROADWELL_G, NULL), 128 | X86_MATCH_VFM(INTEL_BROADWELL_X, NULL), 129 | X86_MATCH_VFM(INTEL_BROADWELL_D, NULL), 130 | X86_MATCH_VFM(INTEL_SKYLAKE_L, NULL), 131 | X86_MATCH_VFM(INTEL_SKYLAKE, NULL), 132 | X86_MATCH_VFM(INTEL_SKYLAKE_X, NULL), 133 | X86_MATCH_VFM(INTEL_KABYLAKE_L, NULL), 134 | X86_MATCH_VFM(INTEL_KABYLAKE, NULL), 135 | X86_MATCH_VFM(INTEL_COMETLAKE, NULL), 136 | X86_MATCH_VFM(INTEL_COMETLAKE_L, NULL), 137 | X86_MATCH_VFM(INTEL_CANNONLAKE_L, NULL), 138 | X86_MATCH_VFM(INTEL_ICELAKE_X, NULL), 139 | X86_MATCH_VFM(INTEL_ICELAKE_D, NULL), 140 | X86_MATCH_VFM(INTEL_ICELAKE, NULL), 141 | X86_MATCH_VFM(INTEL_ICELAKE_L, NULL), 142 | X86_MATCH_VFM(INTEL_ICELAKE_NNPI, NULL), 143 | X86_MATCH_VFM(INTEL_LAKEFIELD, NULL), 144 | X86_MATCH_VFM(INTEL_ROCKETLAKE, NULL), 145 | X86_MATCH_VFM(INTEL_TIGERLAKE_L, NULL), 146 | X86_MATCH_VFM(INTEL_TIGERLAKE, NULL), 147 | X86_MATCH_VFM(INTEL_SAPPHIRERAPIDS_X, NULL), 148 | X86_MATCH_VFM(INTEL_ALDERLAKE, NULL), 149 | X86_MATCH_VFM(INTEL_ALDERLAKE_L, NULL), 150 | X86_MATCH_VFM(INTEL_ATOM_GRACEMONT, NULL), 151 | X86_MATCH_VFM(INTEL_ATOM_BONNELL, NULL), 152 | X86_MATCH_VFM(INTEL_ATOM_BONNELL_MID, NULL), 153 | X86_MATCH_VFM(INTEL_ATOM_SALTWELL, NULL), 154 | X86_MATCH_VFM(INTEL_ATOM_SALTWELL_MID, NULL), 155 | X86_MATCH_VFM(INTEL_ATOM_SALTWELL_TABLET, NULL), 156 | X86_MATCH_VFM(INTEL_ATOM_SILVERMONT, NULL), 157 | X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_D, NULL), 158 | X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID, NULL), 159 | X86_MATCH_VFM(INTEL_ATOM_AIRMONT, NULL), 160 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0) 161 | X86_MATCH_VFM(INTEL_ATOM_AIRMONT_MID, NULL), 162 | #else 163 | X86_MATCH_VFM(INTEL_ATOM_SILVERMONT_MID2, NULL), 164 | #endif 165 | X86_MATCH_VFM(INTEL_ATOM_AIRMONT_NP, NULL), 166 | X86_MATCH_VFM(INTEL_ATOM_GOLDMONT, NULL), 167 | X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_D, NULL), 168 | X86_MATCH_VFM(INTEL_ATOM_GOLDMONT_PLUS, NULL), 169 | X86_MATCH_VFM(INTEL_ATOM_TREMONT_D, NULL), 170 | X86_MATCH_VFM(INTEL_ATOM_TREMONT, NULL), 171 | X86_MATCH_VFM(INTEL_ATOM_TREMONT_L, NULL), 172 | X86_MATCH_VFM(INTEL_XEON_PHI_KNL, NULL), 173 | X86_MATCH_VFM(INTEL_XEON_PHI_KNM, NULL), 174 | X86_MATCH_VFM(INTEL_QUARK_X1000, NULL), 175 | #endif 176 | X86_MATCH_VENDOR_FAM(AMD, 5, NULL), 177 | X86_MATCH_VENDOR_FAM(AMD, 6, NULL), 178 | X86_MATCH_VENDOR_FAM(AMD, 15, NULL), 179 | X86_MATCH_VENDOR_FAM(AMD, 16, NULL), 180 | X86_MATCH_VENDOR_FAM(AMD, 17, NULL), 181 | X86_MATCH_VENDOR_FAM(AMD, 18, NULL), 182 | X86_MATCH_VENDOR_FAM(AMD, 19, NULL), 183 | X86_MATCH_VENDOR_FAM(AMD, 20, NULL), 184 | X86_MATCH_VENDOR_FAM(AMD, 21, NULL), 185 | X86_MATCH_VENDOR_FAM(AMD, 22, NULL), 186 | X86_MATCH_VENDOR_FAM(AMD, 23, NULL), 187 | X86_MATCH_VENDOR_FAM(AMD, 24, NULL), 188 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x01, NULL), 189 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x08, NULL), 190 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x21, NULL), 191 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x40, NULL), 192 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x44, NULL), 193 | X86_MATCH_VENDOR_FAM_MODEL(AMD, 25, 0x50, NULL), 194 | { } 195 | }; 196 | 197 | // Placeholder for potential future exemptions 198 | static const struct x86_cpu_id force_tuxedo_dmi_string_check_match[] = { 199 | { } 200 | }; 201 | 202 | // Going forward we only run the drivers on in house tested devices 203 | static const struct dmi_system_id tuxedo_dmi_string_match[] = { 204 | { 205 | .matches = { 206 | DMI_MATCH(DMI_SYS_VENDOR, "TUXEDO"), 207 | }, 208 | }, 209 | { 210 | .matches = { 211 | DMI_MATCH(DMI_BOARD_VENDOR, "TUXEDO"), 212 | }, 213 | }, 214 | { 215 | .matches = { 216 | DMI_MATCH(DMI_CHASSIS_VENDOR, "TUXEDO"), 217 | }, 218 | }, 219 | { } 220 | }; 221 | 222 | bool tuxedo_is_compatible(void) { 223 | if (dmi_check_system(tuxedo_dmi_string_match) 224 | || (x86_match_cpu(skip_tuxedo_dmi_string_check_match) 225 | && !x86_match_cpu(force_tuxedo_dmi_string_check_match))) { 226 | return true; 227 | } 228 | return false; 229 | } 230 | EXPORT_SYMBOL(tuxedo_is_compatible); 231 | 232 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 233 | MODULE_DESCRIPTION("Provide check for other modules if driver package is known compatible"); 234 | MODULE_LICENSE("GPL"); 235 | -------------------------------------------------------------------------------- /src/tuxedo_nb02_nvidia_power_ctrl/tuxedo_nb02_nvidia_power_ctrl.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "../uniwill_interfaces.h" 27 | 28 | #define __unused __attribute__((unused)) 29 | 30 | static ssize_t ctgp_offset_show(struct device * __unused dev, 31 | struct device_attribute * __unused attr, 32 | char *buf) 33 | { 34 | int result = 0; 35 | u8 data = 0; 36 | 37 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_CTGP_OFFSET, &data); 38 | if (result < 0) 39 | return result; 40 | 41 | return sysfs_emit(buf, "%u\n", data); 42 | } 43 | static ssize_t ctgp_offset_store(struct device * __unused dev, 44 | struct device_attribute * __unused attr, 45 | const char *buf, size_t count) 46 | { 47 | int result = 0; 48 | u8 data = 0; 49 | 50 | result = kstrtou8(buf, 0, &data); 51 | if (result < 0) 52 | return result; 53 | 54 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_CTGP_OFFSET, data); 55 | if (result < 0) 56 | return result; 57 | 58 | return count; 59 | } 60 | DEVICE_ATTR_RW(ctgp_offset); 61 | 62 | #ifdef DEBUG 63 | static ssize_t db_offset_show(struct device * __unused dev, 64 | struct device_attribute * __unused attr, 65 | char *buf) 66 | { 67 | int result = 0; 68 | u8 data = 0; 69 | 70 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_DB_OFFSET, &data); 71 | if (result < 0) 72 | return result; 73 | 74 | return sysfs_emit(buf, "%u\n", data); 75 | } 76 | static ssize_t db_offset_store(struct device * __unused dev, 77 | struct device_attribute * __unused attr, 78 | const char *buf, size_t count) 79 | { 80 | int result = 0; 81 | u8 data = 0; 82 | 83 | result = kstrtou8(buf, 0, &data); 84 | if (result < 0) 85 | return result; 86 | 87 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_DB_OFFSET, data); 88 | if (result < 0) 89 | return result; 90 | 91 | return count; 92 | } 93 | DEVICE_ATTR_RW(db_offset); 94 | 95 | static ssize_t ctgp_enable_show(struct device * __unused dev, 96 | struct device_attribute * __unused attr, 97 | char *buf) 98 | { 99 | int result = 0; 100 | u8 data = 0; 101 | 102 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, &data); 103 | if (result < 0) 104 | return result; 105 | 106 | return sysfs_emit(buf, "%u\n", data & UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE? 1 : 0); 107 | } 108 | static ssize_t ctgp_enable_store(struct device * __unused dev, 109 | struct device_attribute * __unused attr, 110 | const char *buf, size_t count) 111 | { 112 | int result = 0; 113 | u8 data = 0; 114 | bool enable = false; 115 | 116 | result = kstrtobool(buf, &enable); 117 | if (result < 0) 118 | return result; 119 | 120 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, &data); 121 | if (result < 0) 122 | return result; 123 | 124 | if (enable) { 125 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data | 126 | UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE | 127 | UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE); 128 | if (result < 0) 129 | return result; 130 | } 131 | else { 132 | if (data & UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE) { 133 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data & 134 | ~UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE); 135 | if (result < 0) 136 | return result; 137 | } 138 | else { 139 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data & 140 | ~(UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE | 141 | UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE)); 142 | if (result < 0) 143 | return result; 144 | } 145 | } 146 | 147 | return count; 148 | } 149 | DEVICE_ATTR_RW(ctgp_enable); 150 | 151 | static ssize_t db_enable_show(struct device * __unused dev, 152 | struct device_attribute * __unused attr, 153 | char *buf) 154 | { 155 | int result = 0; 156 | u8 data = 0; 157 | 158 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, &data); 159 | if (result < 0) 160 | return result; 161 | 162 | return sysfs_emit(buf, "%u\n", data & UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE? 1 : 0); 163 | } 164 | static ssize_t db_enable_store(struct device * __unused dev, 165 | struct device_attribute * __unused attr, 166 | const char *buf, size_t count) 167 | { 168 | int result = 0; 169 | u8 data = 0; 170 | bool enable = false; 171 | 172 | result = kstrtobool(buf, &enable); 173 | if (result < 0) 174 | return result; 175 | 176 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, &data); 177 | if (result < 0) 178 | return result; 179 | 180 | if (enable) { 181 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data | 182 | UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE | 183 | UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE); 184 | if (result < 0) 185 | return result; 186 | } 187 | else { 188 | if (data & UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE) { 189 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data & 190 | ~UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE); 191 | if (result < 0) 192 | return result; 193 | } 194 | else { 195 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, data & 196 | ~(UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE | 197 | UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE)); 198 | if (result < 0) 199 | return result; 200 | } 201 | } 202 | 203 | return count; 204 | } 205 | DEVICE_ATTR_RW(db_enable); 206 | 207 | static ssize_t tpp_offset_show(struct device * __unused dev, 208 | struct device_attribute * __unused attr, 209 | char *buf) 210 | { 211 | int result = 0; 212 | u8 data = 0; 213 | 214 | result = uniwill_read_ec_ram(UW_EC_REG_CTGP_DB_TPP_OFFSET, &data); 215 | if (result < 0) 216 | return result; 217 | 218 | return sysfs_emit(buf, "%u\n", data); 219 | } 220 | static ssize_t tpp_offset_store(struct device * __unused dev, 221 | struct device_attribute * __unused attr, 222 | const char *buf, size_t count) 223 | { 224 | int result = 0; 225 | u8 data = 0; 226 | 227 | result = kstrtou8(buf, 0, &data); 228 | if (result < 0) 229 | return result; 230 | 231 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_TPP_OFFSET, data); 232 | if (result < 0) 233 | return result; 234 | 235 | return count; 236 | } 237 | DEVICE_ATTR_RW(tpp_offset); 238 | #endif // DEBUG 239 | 240 | static int __init init_db_and_ctgp(void) 241 | { 242 | int result = 0; 243 | 244 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_ENABLE, 245 | UW_EC_REG_CTGP_DB_ENABLE_BIT_GENERAL_ENABLE | 246 | UW_EC_REG_CTGP_DB_ENABLE_BIT_DB_ENABLE | 247 | UW_EC_REG_CTGP_DB_ENABLE_BIT_CTGP_ENABLE); 248 | if (result < 0) 249 | return result; 250 | 251 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_CTGP_OFFSET, 0); 252 | if (result < 0) 253 | return result; 254 | 255 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_TPP_OFFSET, 255); 256 | if (result < 0) 257 | return result; 258 | 259 | result = uniwill_write_ec_ram(UW_EC_REG_CTGP_DB_DB_OFFSET, 25); 260 | if (result < 0) 261 | return result; 262 | 263 | return 0; 264 | } 265 | 266 | static int __init init_sysfs_attrs(struct platform_device *pdev) 267 | { 268 | int result = 0; 269 | 270 | result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_ctgp_offset.attr); 271 | if (result) 272 | return result; 273 | 274 | #ifdef DEBUG 275 | result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_db_offset.attr); 276 | if (result) 277 | return result; 278 | 279 | result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_ctgp_enable.attr); 280 | if (result) 281 | return result; 282 | 283 | result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_db_enable.attr); 284 | if (result) 285 | return result; 286 | 287 | result = sysfs_create_file(&pdev->dev.kobj, &dev_attr_tpp_offset.attr); 288 | if (result) 289 | return result; 290 | #endif // DEBUG 291 | 292 | return 0; 293 | } 294 | 295 | static int interface_probe_retries = 5; 296 | 297 | static int __init tuxedo_nb02_nvidia_power_ctrl_probe(struct platform_device *pdev) { 298 | int result = 0; 299 | char **uniwill_active_interface = NULL; 300 | struct pci_dev *gpu_dev = NULL; 301 | 302 | while (interface_probe_retries) { 303 | result = uniwill_get_active_interface_id(uniwill_active_interface); 304 | if (result == 0) 305 | break; 306 | msleep(20); 307 | --interface_probe_retries; 308 | } 309 | if (result < 0) 310 | return result; 311 | 312 | // Check for NVIDIA 3000 series or higher 313 | result = -ENODEV; 314 | while ((gpu_dev = pci_get_device(0x10de, PCI_ANY_ID, gpu_dev)) != NULL) { 315 | if (gpu_dev->device >= 0x2200) { 316 | result = 0; 317 | pci_dev_put(gpu_dev); 318 | break; 319 | } 320 | } 321 | if (result < 0) 322 | return result; 323 | 324 | result = init_db_and_ctgp(); 325 | if (result < 0) 326 | return result; 327 | 328 | result = init_sysfs_attrs(pdev); 329 | if (result < 0) 330 | return result; 331 | 332 | return 0; 333 | } 334 | 335 | 336 | 337 | // Boilerplate 338 | 339 | static struct platform_device *tuxedo_nb02_nvidia_power_ctrl_device; 340 | static struct platform_driver tuxedo_nb02_nvidia_power_ctrl_driver = { 341 | .driver.name = "tuxedo_nvidia_power_ctrl", 342 | }; 343 | 344 | static int __init tuxedo_nb02_nvidia_power_ctrl_init(void) 345 | { 346 | struct device *dev = NULL; 347 | 348 | dev = bus_find_device_by_name(&platform_bus_type, NULL, 349 | tuxedo_nb02_nvidia_power_ctrl_driver.driver.name); 350 | if (IS_ERR(dev)) 351 | return PTR_ERR(dev); 352 | if (dev != NULL) { 353 | put_device(dev); 354 | return -EEXIST; 355 | } 356 | 357 | tuxedo_nb02_nvidia_power_ctrl_device = 358 | platform_create_bundle(&tuxedo_nb02_nvidia_power_ctrl_driver, 359 | tuxedo_nb02_nvidia_power_ctrl_probe, NULL, 0, NULL, 0); 360 | 361 | if (IS_ERR(tuxedo_nb02_nvidia_power_ctrl_device)) 362 | return PTR_ERR(tuxedo_nb02_nvidia_power_ctrl_device); 363 | 364 | return 0; 365 | } 366 | 367 | static void __exit tuxedo_nb02_nvidia_power_ctrl_exit(void) 368 | { 369 | platform_device_unregister(tuxedo_nb02_nvidia_power_ctrl_device); 370 | platform_driver_unregister(&tuxedo_nb02_nvidia_power_ctrl_driver); 371 | } 372 | 373 | module_init(tuxedo_nb02_nvidia_power_ctrl_init); 374 | module_exit(tuxedo_nb02_nvidia_power_ctrl_exit); 375 | 376 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 377 | MODULE_DESCRIPTION("TUXEDO Computers Dynamic Boost and cTGP control driver for NVIDIA silicon for devices marked with board_vendor NB02"); 378 | MODULE_LICENSE("GPL"); 379 | MODULE_ALIAS("platform:tuxedo_keyboard"); 380 | -------------------------------------------------------------------------------- /src/tuxedo_nb05/tuxedo_nb05_power_profiles.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023-2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "tuxedo_nb05_power_profiles.h" 33 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 34 | 35 | #define dev_to_wdev(__dev) container_of(__dev, struct wmi_device, dev) 36 | 37 | #define NB05_WMI_METHOD_BA_GUID "99D89064-8D50-42BB-BEA9-155B2E5D0FCD" 38 | 39 | static DEFINE_MUTEX(nb05_wmi_aa_access_lock); 40 | 41 | struct driver_data_t { 42 | struct platform_device *pdev; 43 | u64 last_chosen_profile; 44 | }; 45 | 46 | static struct wmi_device *__wmi_dev; 47 | 48 | static int rewrite_last_profile_internal(void); 49 | 50 | static void nb05_rewrite_profile_work_handler(struct work_struct *work) 51 | { 52 | rewrite_last_profile_internal(); 53 | } 54 | 55 | static DECLARE_WORK(nb05_rewrite_profile_work, nb05_rewrite_profile_work_handler); 56 | 57 | static void profile_changed_timeout(struct timer_list *t); 58 | DEFINE_TIMER(profile_changed_timer, profile_changed_timeout); 59 | static volatile bool profile_changed_by_driver_flag = false; 60 | 61 | static void profile_changed_timer_bump(void) 62 | { 63 | profile_changed_by_driver_flag = true; 64 | mod_timer(&profile_changed_timer, jiffies + msecs_to_jiffies(300)); 65 | } 66 | 67 | static void profile_changed_timeout(struct timer_list *t) 68 | { 69 | profile_changed_by_driver_flag = false; 70 | // Again make sure that the last profile is written 71 | rewrite_last_profile(); 72 | } 73 | 74 | /** 75 | * Method interface: int in, int out 76 | */ 77 | static int __nb05_wmi_aa_method(struct wmi_device *wdev, u32 wmi_method_id, 78 | u64 *in, u64 *out) 79 | { 80 | struct acpi_buffer acpi_buffer_in = { (acpi_size) sizeof(*in), in }; 81 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 82 | union acpi_object *acpi_object_out; 83 | acpi_status status; 84 | 85 | mutex_lock(&nb05_wmi_aa_access_lock); 86 | 87 | pr_debug("evaluate: %u\n", wmi_method_id); 88 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 89 | &acpi_buffer_in, &return_buffer); 90 | 91 | mutex_unlock(&nb05_wmi_aa_access_lock); 92 | 93 | if (ACPI_FAILURE(status)) { 94 | pr_err("failed to evaluate wmi method %u\n", wmi_method_id); 95 | return -EIO; 96 | } 97 | 98 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 99 | if (!acpi_object_out) 100 | return -ENODATA; 101 | 102 | if (acpi_object_out->type != ACPI_TYPE_INTEGER) { 103 | pr_err("No integer for method (%u) call\n", wmi_method_id); 104 | kfree(return_buffer.pointer); 105 | return -EIO; 106 | } 107 | 108 | *out = acpi_object_out->integer.value; 109 | kfree(return_buffer.pointer); 110 | 111 | return 0; 112 | } 113 | 114 | static int nb05_wmi_aa_method(u32 wmi_method_id, u64 *in, u64 *out) 115 | { 116 | if (__wmi_dev) 117 | return __nb05_wmi_aa_method(__wmi_dev, wmi_method_id, in, out); 118 | else 119 | return -ENODEV; 120 | } 121 | 122 | static int write_profile(u64 profile) 123 | { 124 | u64 out = 0; 125 | profile_changed_timer_bump(); 126 | int err = nb05_wmi_aa_method(1, &profile, &out); 127 | if (err) 128 | return err; 129 | else if (out) 130 | return -EINVAL; 131 | 132 | return 0; 133 | } 134 | 135 | static int read_profile(u64 *profile) 136 | { 137 | u64 in; 138 | int err = nb05_wmi_aa_method(2, &in, profile); 139 | if (err) 140 | return err; 141 | 142 | return 0; 143 | } 144 | 145 | static ssize_t platform_profile_choices_show(struct device *dev, 146 | struct device_attribute *attr, 147 | char *buffer); 148 | 149 | static ssize_t platform_profile_show(struct device *dev, 150 | struct device_attribute *attr, char *buffer); 151 | 152 | static ssize_t platform_profile_store(struct device *dev, 153 | struct device_attribute *attr, 154 | const char *buffer, size_t size); 155 | 156 | struct platform_profile_attrs_t { 157 | struct device_attribute platform_profile_choices; 158 | struct device_attribute platform_profile; 159 | }; 160 | 161 | struct platform_profile_attrs_t platform_profile_attrs = { 162 | .platform_profile_choices = __ATTR(platform_profile_choices, 0444, 163 | platform_profile_choices_show, NULL), 164 | .platform_profile = __ATTR(platform_profile, 0644, 165 | platform_profile_show, platform_profile_store) 166 | }; 167 | 168 | static struct attribute *platform_profile_attrs_list[] = { 169 | &platform_profile_attrs.platform_profile_choices.attr, 170 | &platform_profile_attrs.platform_profile.attr, 171 | NULL 172 | }; 173 | 174 | static struct attribute_group platform_profile_attr_group = { 175 | .attrs = platform_profile_attrs_list 176 | }; 177 | 178 | struct char_to_value_t { 179 | char* descriptor; 180 | u64 value; 181 | }; 182 | 183 | static struct char_to_value_t platform_profile_options[] = { 184 | { .descriptor = "low-power", .value = 2 }, 185 | { .descriptor = "balanced", .value = 0 }, 186 | { .descriptor = "performance", .value = 1 } 187 | }; 188 | 189 | static ssize_t platform_profile_choices_show(struct device *dev, 190 | struct device_attribute *attr, 191 | char *buffer) 192 | { 193 | int i, n; 194 | n = ARRAY_SIZE(platform_profile_options); 195 | for (i = 0; i < n; ++i) { 196 | sprintf(buffer + strlen(buffer), "%s", 197 | platform_profile_options[i].descriptor); 198 | if (i < n - 1) 199 | sprintf(buffer + strlen(buffer), " "); 200 | else 201 | sprintf(buffer + strlen(buffer), "\n"); 202 | } 203 | 204 | return strlen(buffer); 205 | } 206 | 207 | static ssize_t platform_profile_show(struct device *dev, 208 | struct device_attribute *attr, char *buffer) 209 | { 210 | u64 platform_profile_value; 211 | int i, err; 212 | 213 | err = read_profile(&platform_profile_value); 214 | if (err) { 215 | pr_err("Error reading power profile"); 216 | return -EIO; 217 | } 218 | 219 | for (i = 0; i < ARRAY_SIZE(platform_profile_options); ++i) 220 | if (platform_profile_options[i].value == platform_profile_value) { 221 | sprintf(buffer, "%s\n", platform_profile_options[i].descriptor); 222 | return strlen(buffer); 223 | } 224 | 225 | pr_err("Read platform profile value not matched to a descriptor\n"); 226 | 227 | return -EIO; 228 | } 229 | 230 | static int rewrite_last_profile_internal(void) 231 | { 232 | struct driver_data_t *driver_data = dev_get_drvdata(&__wmi_dev->dev); 233 | u64 current_profile; 234 | int err; 235 | 236 | err = read_profile(¤t_profile); 237 | if (err) 238 | return err; 239 | 240 | if (current_profile != driver_data->last_chosen_profile) { 241 | err = write_profile(driver_data->last_chosen_profile); 242 | if (err) 243 | return err; 244 | } 245 | 246 | return 0; 247 | } 248 | 249 | void rewrite_last_profile(void) 250 | { 251 | schedule_work(&nb05_rewrite_profile_work); 252 | } 253 | EXPORT_SYMBOL(rewrite_last_profile); 254 | 255 | bool profile_changed_by_driver(void) 256 | { 257 | return profile_changed_by_driver_flag; 258 | } 259 | EXPORT_SYMBOL(profile_changed_by_driver); 260 | 261 | static ssize_t platform_profile_store(struct device *dev, 262 | struct device_attribute *attr, 263 | const char *buffer, size_t size) 264 | { 265 | struct platform_device *pdev = to_platform_device(dev); 266 | struct wmi_device *wdev = dev_to_wdev(pdev->dev.parent); 267 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 268 | u64 platform_profile_value; 269 | int i, err; 270 | char *buffer_copy; 271 | char *platform_profile_descriptor; 272 | 273 | buffer_copy = kmalloc(size + 1, GFP_KERNEL); 274 | strcpy(buffer_copy, buffer); 275 | platform_profile_descriptor = strstrip(buffer_copy); 276 | 277 | for (i = 0; i < ARRAY_SIZE(platform_profile_options); ++i) 278 | if (strcmp(platform_profile_options[i].descriptor, platform_profile_descriptor) == 0) { 279 | platform_profile_value = platform_profile_options[i].value; 280 | break; 281 | } 282 | 283 | kfree(buffer_copy); 284 | 285 | if (i < ARRAY_SIZE(platform_profile_options)) { 286 | // Option found try to set 287 | err = write_profile(platform_profile_value); 288 | if (err) 289 | return err; 290 | 291 | driver_data->last_chosen_profile = platform_profile_value; 292 | return size; 293 | } else { 294 | // Invalid input, not matched to an option 295 | return -EINVAL; 296 | } 297 | } 298 | 299 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 300 | static int tuxedo_nb05_power_profiles_probe(struct wmi_device *wdev) 301 | #else 302 | static int tuxedo_nb05_power_profiles_probe(struct wmi_device *wdev, const void *dummy_context) 303 | #endif 304 | { 305 | int err; 306 | struct driver_data_t *driver_data; 307 | 308 | pr_debug("driver probe\n"); 309 | 310 | __wmi_dev = wdev; 311 | 312 | if (!tuxedo_is_compatible()) 313 | return -ENODEV; 314 | 315 | if (!wmi_has_guid(NB05_WMI_METHOD_BA_GUID)) 316 | return -ENODEV; 317 | 318 | driver_data = devm_kzalloc(&wdev->dev, sizeof(*driver_data), GFP_KERNEL); 319 | if (!driver_data) 320 | return -ENOMEM; 321 | 322 | dev_set_drvdata(&wdev->dev, driver_data); 323 | 324 | // Initialize last chosen profile 325 | err = read_profile(&driver_data->last_chosen_profile); 326 | if (err) { 327 | pr_err("Error reading power profile"); 328 | return -EIO; 329 | } 330 | 331 | const struct platform_device_info pinfo = { 332 | .name = "tuxedo_platform_profile", 333 | .id = PLATFORM_DEVID_NONE, 334 | .parent = &wdev->dev 335 | }; 336 | 337 | driver_data->pdev = platform_device_register_full(&pinfo); 338 | if (PTR_ERR_OR_ZERO(driver_data->pdev)) { 339 | pr_err("platform device creation failed\n"); 340 | return -ENOMEM; 341 | } 342 | 343 | err = sysfs_create_group(&driver_data->pdev->dev.kobj, &platform_profile_attr_group); 344 | if (err) { 345 | pr_err("create group failed\n"); 346 | platform_device_unregister(driver_data->pdev); 347 | return err; 348 | } 349 | 350 | return 0; 351 | } 352 | 353 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 354 | static int tuxedo_nb05_power_profiles_remove(struct wmi_device *wdev) 355 | #else 356 | static void tuxedo_nb05_power_profiles_remove(struct wmi_device *wdev) 357 | #endif 358 | { 359 | pr_debug("driver remove\n"); 360 | timer_delete(&profile_changed_timer); 361 | struct driver_data_t *driver_data = dev_get_drvdata(&wdev->dev); 362 | sysfs_remove_group(&driver_data->pdev->dev.kobj, &platform_profile_attr_group); 363 | platform_device_unregister(driver_data->pdev); 364 | 365 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 366 | return 0; 367 | #endif 368 | } 369 | 370 | static const struct wmi_device_id tuxedo_nb05_power_profiles_device_ids[] = { 371 | { .guid_string = NB05_WMI_METHOD_BA_GUID }, 372 | { } 373 | }; 374 | 375 | static struct wmi_driver tuxedo_nb05_power_profiles_driver = { 376 | .driver = { 377 | .name = "tuxedo_nb05_power_profiles", 378 | .owner = THIS_MODULE 379 | }, 380 | .id_table = tuxedo_nb05_power_profiles_device_ids, 381 | .probe = tuxedo_nb05_power_profiles_probe, 382 | .remove = tuxedo_nb05_power_profiles_remove, 383 | }; 384 | 385 | module_wmi_driver(tuxedo_nb05_power_profiles_driver); 386 | 387 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 388 | MODULE_DESCRIPTION("Driver for NB05 power profiles"); 389 | MODULE_LICENSE("GPL"); 390 | 391 | MODULE_DEVICE_TABLE(wmi, tuxedo_nb05_power_profiles_device_ids); 392 | MODULE_ALIAS("wmi:" NB05_WMI_METHOD_BA_GUID); 393 | -------------------------------------------------------------------------------- /src/tuxedo_nb04/tuxedo_nb04_wmi_ab.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2023 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "tuxedo_nb04_wmi_ab.h" 28 | #include "../tuxedo_compatibility_check/tuxedo_compatibility_check.h" 29 | 30 | #define dev_to_wdev(__dev) container_of(__dev, struct wmi_device, dev) 31 | 32 | static DEFINE_MUTEX(nb04_wmi_ab_lock); 33 | 34 | static struct wmi_device *__wmi_dev; 35 | 36 | #define KEYBOARD_MAX_BRIGHTNESS 0x0a 37 | #define KEYBOARD_DEFAULT_BRIGHTNESS 0x00 38 | #define KEYBOARD_DEFAULT_COLOR_RED 0xff 39 | #define KEYBOARD_DEFAULT_COLOR_GREEN 0xff 40 | #define KEYBOARD_DEFAULT_COLOR_BLUE 0xff 41 | 42 | struct driver_data_t {}; 43 | 44 | static int 45 | __nb04_wmi_ab_method_buffer(struct wmi_device *wdev, u32 wmi_method_id, 46 | u8 *in, u8 *out) 47 | { 48 | struct acpi_buffer acpi_buffer_in = { (acpi_size)AB_INPUT_BUFFER_LENGTH_NORMAL, in }; 49 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 50 | union acpi_object *acpi_object_out; 51 | acpi_status status; 52 | 53 | mutex_lock(&nb04_wmi_ab_lock); 54 | pr_debug("evaluate: %u\n", wmi_method_id); 55 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 56 | &acpi_buffer_in, &return_buffer); 57 | mutex_unlock(&nb04_wmi_ab_lock); 58 | 59 | if (ACPI_FAILURE(status)) { 60 | pr_err("failed to evaluate wmi method %u\n", wmi_method_id); 61 | return -EIO; 62 | } 63 | 64 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 65 | if (!acpi_object_out) 66 | return -ENODATA; 67 | 68 | if (acpi_object_out->type != ACPI_TYPE_BUFFER) { 69 | pr_err("No buffer for method (%u) call\n", wmi_method_id); 70 | kfree(return_buffer.pointer); 71 | return -EIO; 72 | } 73 | 74 | if (acpi_object_out->buffer.length != AB_OUTPUT_BUFFER_LENGTH) { 75 | pr_err("Unexpected buffer length: %u for method (%u) call\n", 76 | acpi_object_out->buffer.length, wmi_method_id); 77 | kfree(return_buffer.pointer); 78 | return -EIO; 79 | } 80 | 81 | memcpy(out, acpi_object_out->buffer.pointer, AB_OUTPUT_BUFFER_LENGTH); 82 | kfree(return_buffer.pointer); 83 | 84 | return 0; 85 | } 86 | 87 | /** 88 | * Method interface 8 bytes in 80 bytes out 89 | */ 90 | int nb04_wmi_ab_method_buffer(u32 wmi_method_id, u8 *in, u8 *out) 91 | { 92 | if (__wmi_dev) 93 | return __nb04_wmi_ab_method_buffer(__wmi_dev, wmi_method_id, in, out); 94 | else 95 | return -ENODEV; 96 | } 97 | EXPORT_SYMBOL(nb04_wmi_ab_method_buffer); 98 | 99 | static int 100 | __nb04_wmi_ab_method_buffer_reduced_output(struct wmi_device *wdev, u32 wmi_method_id, 101 | u8 *in, u8 *out) 102 | { 103 | struct acpi_buffer acpi_buffer_in = { (acpi_size)AB_INPUT_BUFFER_LENGTH_NORMAL, in }; 104 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 105 | union acpi_object *acpi_object_out; 106 | acpi_status status; 107 | 108 | mutex_lock(&nb04_wmi_ab_lock); 109 | pr_debug("evaluate: %u\n", wmi_method_id); 110 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 111 | &acpi_buffer_in, &return_buffer); 112 | mutex_unlock(&nb04_wmi_ab_lock); 113 | if (ACPI_FAILURE(status)) { 114 | pr_err("failed to evaluate wmi method %u\n", wmi_method_id); 115 | return -EIO; 116 | } 117 | 118 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 119 | if (!acpi_object_out) 120 | return -ENODATA; 121 | 122 | if (acpi_object_out->type != ACPI_TYPE_BUFFER) { 123 | pr_err("No buffer for method (%u) call\n", wmi_method_id); 124 | kfree(return_buffer.pointer); 125 | return -EIO; 126 | } 127 | 128 | if (acpi_object_out->buffer.length != AB_OUTPUT_BUFFER_LENGTH_REDUCED) { 129 | pr_err("Unexpected buffer length: %u for method (%u) call\n", 130 | acpi_object_out->buffer.length, wmi_method_id); 131 | kfree(return_buffer.pointer); 132 | return -EIO; 133 | } 134 | 135 | memcpy(out, acpi_object_out->buffer.pointer, AB_OUTPUT_BUFFER_LENGTH_REDUCED); 136 | kfree(return_buffer.pointer); 137 | 138 | return 0; 139 | } 140 | 141 | /** 142 | * Method interface 8 bytes in 10 bytes out 143 | */ 144 | int nb04_wmi_ab_method_buffer_reduced_output(u32 wmi_method_id, u8 *in, u8 *out) 145 | { 146 | if (__wmi_dev) 147 | return __nb04_wmi_ab_method_buffer_reduced_output(__wmi_dev, wmi_method_id, in, out); 148 | else 149 | return -ENODEV; 150 | } 151 | EXPORT_SYMBOL(nb04_wmi_ab_method_buffer_reduced_output); 152 | 153 | static int 154 | __nb04_wmi_ab_method_extended_input(struct wmi_device *wdev, u32 wmi_method_id, 155 | u8 *in, u8 *out) 156 | { 157 | struct acpi_buffer acpi_buffer_in = { (acpi_size)AB_INPUT_BUFFER_LENGTH_EXTENDED, in }; 158 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 159 | union acpi_object *acpi_object_out; 160 | acpi_status status; 161 | 162 | mutex_lock(&nb04_wmi_ab_lock); 163 | pr_debug("evaluate: %u\n", wmi_method_id); 164 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 165 | &acpi_buffer_in, &return_buffer); 166 | mutex_unlock(&nb04_wmi_ab_lock); 167 | 168 | if (ACPI_FAILURE(status)) { 169 | pr_err("failed to evaluate wmi method %u\n", wmi_method_id); 170 | return -EIO; 171 | } 172 | 173 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 174 | if (!acpi_object_out) 175 | return -ENODATA; 176 | 177 | if (acpi_object_out->type != ACPI_TYPE_BUFFER) { 178 | pr_err("No buffer for method (%u) call\n", wmi_method_id); 179 | kfree(return_buffer.pointer); 180 | return -EIO; 181 | } 182 | 183 | if (acpi_object_out->buffer.length != AB_OUTPUT_BUFFER_LENGTH) { 184 | pr_err("Unexpected buffer length: %u for method (%u) call\n", 185 | acpi_object_out->buffer.length, wmi_method_id); 186 | kfree(return_buffer.pointer); 187 | return -EIO; 188 | } 189 | 190 | memcpy(out, acpi_object_out->buffer.pointer, AB_OUTPUT_BUFFER_LENGTH); 191 | kfree(return_buffer.pointer); 192 | 193 | return 0; 194 | } 195 | 196 | /** 197 | * Method interface 496 bytes in 80 bytes out 198 | */ 199 | int nb04_wmi_ab_method_extended_input(u32 wmi_method_id, u8 *in, u8 *out) 200 | { 201 | if (__wmi_dev) 202 | return __nb04_wmi_ab_method_extended_input(__wmi_dev, wmi_method_id, in, out); 203 | else 204 | return -ENODEV; 205 | } 206 | EXPORT_SYMBOL(nb04_wmi_ab_method_extended_input); 207 | 208 | static int 209 | __nb04_wmi_ab_method_int_out(struct wmi_device *wdev, u32 wmi_method_id, 210 | u8 *in, u64 *out) 211 | { 212 | struct acpi_buffer acpi_buffer_in = { (acpi_size)AB_INPUT_BUFFER_LENGTH_NORMAL, in }; 213 | struct acpi_buffer return_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 214 | union acpi_object *acpi_object_out; 215 | acpi_status status; 216 | 217 | mutex_lock(&nb04_wmi_ab_lock); 218 | pr_debug("evaluate: %u\n", wmi_method_id); 219 | status = wmidev_evaluate_method(wdev, 0, wmi_method_id, 220 | &acpi_buffer_in, &return_buffer); 221 | mutex_unlock(&nb04_wmi_ab_lock); 222 | 223 | if (ACPI_FAILURE(status)) { 224 | pr_err("failed to evaluate wmi method\n"); 225 | return -EIO; 226 | } 227 | 228 | acpi_object_out = (union acpi_object *) return_buffer.pointer; 229 | if (!acpi_object_out) 230 | return -ENODATA; 231 | 232 | if (acpi_object_out->type != ACPI_TYPE_INTEGER) { 233 | pr_err("No integer\n"); 234 | kfree(return_buffer.pointer); 235 | return -EIO; 236 | } 237 | 238 | *out = acpi_object_out->integer.value; 239 | kfree(return_buffer.pointer); 240 | 241 | return 0; 242 | } 243 | 244 | /** 245 | * Method interface 8 bytes in integer out 246 | */ 247 | int nb04_wmi_ab_method_int_out(u32 wmi_method_id, u8 *in, u64 *out) 248 | { 249 | if (__wmi_dev) 250 | return __nb04_wmi_ab_method_int_out(__wmi_dev, wmi_method_id, in, out); 251 | else 252 | return -ENODEV; 253 | } 254 | EXPORT_SYMBOL(nb04_wmi_ab_method_int_out); 255 | 256 | int wmi_update_device_status_keyboard(struct device_keyboard_status_t *kbds) 257 | { 258 | u8 arg[AB_INPUT_BUFFER_LENGTH_NORMAL] = {0}; 259 | u8 out[AB_OUTPUT_BUFFER_LENGTH] = {0}; 260 | u16 wmi_return; 261 | int result, retry_count = 3; 262 | 263 | arg[0] = WMI_DEVICE_TYPE_ID_KEYBOARD; 264 | 265 | while (retry_count--) { 266 | result = nb04_wmi_ab_method_buffer(2, arg, out); 267 | if (result) 268 | return result; 269 | 270 | wmi_return = (out[1] << 8) | out[0]; 271 | if (wmi_return != WMI_RETURN_STATUS_SUCCESS) 272 | return -EIO; 273 | 274 | if (out[3] < WMI_KEYBOARD_TYPE_MAX) 275 | break; 276 | 277 | // Sometimes initial read has proved to fail without having a fail 278 | // status. However the returned keyboard type in this case is invalid. 279 | pr_debug("Unexpected keyboard type (%d), retry read\n", out[3]); 280 | msleep(50); 281 | } 282 | 283 | // If, despite retries, not getting a valid keyboard type => give up 284 | if (out[3] >= WMI_KEYBOARD_TYPE_MAX) { 285 | pr_err("Failed to identifiy keyboard type\n"); 286 | return -ENODEV; 287 | } 288 | 289 | kbds->keyboard_state_enabled = out[2] == 1; 290 | kbds->keyboard_type = out[3]; 291 | kbds->keyboard_sidebar_support = out[4] == 1; 292 | kbds->keyboard_matrix = out[5]; 293 | 294 | return 0; 295 | } 296 | EXPORT_SYMBOL(wmi_update_device_status_keyboard); 297 | 298 | int wmi_set_whole_keyboard(u8 red, u8 green, u8 blue, int brightness) 299 | { 300 | u8 arg[AB_INPUT_BUFFER_LENGTH_NORMAL] = {0}; 301 | u64 out; 302 | u8 brightness_byte = 0xfe, enable_byte = 0x01; 303 | int result; 304 | 305 | if (brightness == 0) 306 | enable_byte = 0x00; 307 | else if (brightness > 0 && brightness <= 10) 308 | brightness_byte = brightness; 309 | 310 | arg[0] = 0x00; 311 | arg[1] = red; 312 | arg[2] = green; 313 | arg[3] = blue; 314 | arg[4] = brightness_byte; 315 | arg[5] = 0xfe; 316 | arg[6] = 0x00; 317 | arg[7] = enable_byte; 318 | 319 | result = nb04_wmi_ab_method_int_out(3, arg, &out); 320 | if (result) 321 | return result; 322 | 323 | if (out != 0) 324 | return -EIO; 325 | 326 | return 0; 327 | } 328 | EXPORT_SYMBOL(wmi_set_whole_keyboard); 329 | 330 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 0) 331 | static int tuxedo_nb04_wmi_ab_probe(struct wmi_device *wdev) 332 | #else 333 | static int tuxedo_nb04_wmi_ab_probe(struct wmi_device *wdev, const void *dummy_context) 334 | #endif 335 | { 336 | struct driver_data_t *driver_data; 337 | 338 | pr_debug("driver probe\n"); 339 | 340 | if (!tuxedo_is_compatible()) 341 | return -ENODEV; 342 | 343 | if (!wmi_has_guid(NB04_WMI_AB_GUID)) 344 | return -ENODEV; 345 | 346 | driver_data = devm_kzalloc(&wdev->dev, sizeof(struct driver_data_t), GFP_KERNEL); 347 | if (!driver_data) 348 | return -ENOMEM; 349 | 350 | dev_set_drvdata(&wdev->dev, driver_data); 351 | 352 | __wmi_dev = wdev; 353 | 354 | return 0; 355 | } 356 | 357 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 358 | static int tuxedo_nb04_wmi_ab_remove(struct wmi_device *wdev) 359 | #else 360 | static void tuxedo_nb04_wmi_ab_remove(struct wmi_device *wdev) 361 | #endif 362 | { 363 | __wmi_dev = NULL; 364 | pr_debug("driver remove\n"); 365 | 366 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) 367 | return 0; 368 | #endif 369 | } 370 | 371 | static const struct wmi_device_id tuxedo_nb04_wmi_ab_device_ids[] = { 372 | { .guid_string = NB04_WMI_AB_GUID }, 373 | { } 374 | }; 375 | 376 | static struct wmi_driver tuxedo_nb04_wmi_ab_driver = { 377 | .driver = { 378 | .name = "tuxedo_nb04_wmi_ab", 379 | .owner = THIS_MODULE 380 | }, 381 | .id_table = tuxedo_nb04_wmi_ab_device_ids, 382 | .probe = tuxedo_nb04_wmi_ab_probe, 383 | .remove = tuxedo_nb04_wmi_ab_remove, 384 | }; 385 | 386 | module_wmi_driver(tuxedo_nb04_wmi_ab_driver); 387 | 388 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 389 | MODULE_DESCRIPTION("Driver for NB04 WMI AB methods"); 390 | MODULE_LICENSE("GPL"); 391 | 392 | MODULE_DEVICE_TABLE(wmi, tuxedo_nb04_wmi_ab_device_ids); 393 | MODULE_ALIAS("wmi:" NB04_WMI_AB_GUID); 394 | -------------------------------------------------------------------------------- /src/tuxedo_tuxi/tuxedo_tuxi_fan_control.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0+ 2 | /*! 3 | * Copyright (c) 2024 TUXEDO Computers GmbH 4 | * 5 | * This file is part of tuxedo-drivers. 6 | * 7 | * This program is free software; you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation; either version 2 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License along 18 | * with this program; if not, see . 19 | */ 20 | 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "tuxi_acpi.h" 30 | 31 | #define FAN_SET_DUTY_MAX 255 32 | #define FAN_ON_MIN_SPEED_PERCENT 25 33 | 34 | struct driver_data_t { 35 | struct platform_device *pdev; 36 | }; 37 | 38 | static ssize_t fan1_pwm_show(struct device *dev, 39 | struct device_attribute *attr, char *buffer); 40 | 41 | static ssize_t fan1_pwm_store(struct device *dev, 42 | struct device_attribute *attr, 43 | const char *buffer, size_t size); 44 | 45 | static ssize_t fan1_pwm_enable_show(struct device *dev, 46 | struct device_attribute *attr, char *buffer); 47 | 48 | static ssize_t fan1_pwm_enable_store(struct device *dev, 49 | struct device_attribute *attr, 50 | const char *buffer, size_t size); 51 | 52 | static ssize_t fan2_pwm_show(struct device *dev, 53 | struct device_attribute *attr, char *buffer); 54 | 55 | static ssize_t fan2_pwm_store(struct device *dev, 56 | struct device_attribute *attr, 57 | const char *buffer, size_t size); 58 | 59 | static ssize_t fan2_pwm_enable_show(struct device *dev, 60 | struct device_attribute *attr, char *buffer); 61 | 62 | static ssize_t fan2_pwm_enable_store(struct device *dev, 63 | struct device_attribute *attr, 64 | const char *buffer, size_t size); 65 | 66 | struct fan_control_attrs_t { 67 | struct device_attribute fan1_pwm; 68 | struct device_attribute fan1_pwm_enable; 69 | struct device_attribute fan2_pwm; 70 | struct device_attribute fan2_pwm_enable; 71 | }; 72 | 73 | struct fan_control_attrs_t fan_control_attrs = { 74 | .fan1_pwm = __ATTR(fan1_pwm, 0644, fan1_pwm_show, fan1_pwm_store), 75 | .fan1_pwm_enable = __ATTR(fan1_pwm_enable, 0644, fan1_pwm_enable_show, fan1_pwm_enable_store), 76 | .fan2_pwm = __ATTR(fan2_pwm, 0644, fan2_pwm_show, fan2_pwm_store), 77 | .fan2_pwm_enable = __ATTR(fan2_pwm_enable, 0644, fan2_pwm_enable_show, fan2_pwm_enable_store), 78 | }; 79 | 80 | static struct attribute *fan_control_attrs_list[] = { 81 | &fan_control_attrs.fan1_pwm.attr, 82 | &fan_control_attrs.fan1_pwm_enable.attr, 83 | &fan_control_attrs.fan2_pwm.attr, 84 | &fan_control_attrs.fan2_pwm_enable.attr, 85 | NULL 86 | }; 87 | 88 | static struct attribute_group fan_control_attr_group = { 89 | .attrs = fan_control_attrs_list 90 | }; 91 | 92 | static ssize_t fan1_pwm_show(struct device *dev, 93 | struct device_attribute *attr, char *buffer) 94 | { 95 | u8 pwm_data, duty_data; 96 | duty_data = tuxi_get_fan_speed(0, &duty_data); 97 | pwm_data = (duty_data * 0xff) / FAN_SET_DUTY_MAX; 98 | sysfs_emit(buffer, "%d\n", pwm_data); 99 | return strlen(buffer); 100 | } 101 | 102 | static ssize_t fan1_pwm_store(struct device *dev, 103 | struct device_attribute *attr, 104 | const char *buffer, size_t size) 105 | { 106 | u8 pwm_data, duty_data; 107 | int err; 108 | 109 | if (kstrtou8(buffer, 0, &pwm_data)) 110 | return -EINVAL; 111 | 112 | duty_data = (pwm_data * FAN_SET_DUTY_MAX) / 0xff; 113 | 114 | // Don't allow vallues between fan-off and minimum fan-on-speed 115 | if (duty_data <= FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 2 / 100) 116 | duty_data = 0; 117 | else if (duty_data < FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 100) 118 | duty_data = FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 100; 119 | 120 | err = tuxi_set_fan_speed(0, duty_data); 121 | if (err) 122 | return err; 123 | 124 | return size; 125 | } 126 | 127 | static ssize_t fan1_pwm_enable_show(struct device *dev, 128 | struct device_attribute *attr, char *buffer) 129 | { 130 | enum tuxi_fan_mode mode; 131 | u8 enable_hwmon; 132 | int err; 133 | 134 | err = tuxi_get_fan_mode(&mode); 135 | if (mode == MANUAL) { 136 | enable_hwmon = 1; 137 | } else { 138 | enable_hwmon = 2; 139 | } 140 | sysfs_emit(buffer, "%d\n", enable_hwmon); 141 | 142 | return strlen(buffer); 143 | } 144 | 145 | static ssize_t fan1_pwm_enable_store(struct device *dev, 146 | struct device_attribute *attr, 147 | const char *buffer, size_t size) 148 | { 149 | enum tuxi_fan_mode mode; 150 | u8 enable_hwmon; 151 | int err; 152 | 153 | if (kstrtou8(buffer, 0, &enable_hwmon)) 154 | return -EINVAL; 155 | 156 | if (enable_hwmon == 1) 157 | mode = MANUAL; 158 | else 159 | mode = AUTO; 160 | 161 | err = tuxi_set_fan_mode(mode); 162 | if (err) 163 | return err; 164 | 165 | return size; 166 | } 167 | 168 | static ssize_t fan2_pwm_show(struct device *dev, 169 | struct device_attribute *attr, char *buffer) 170 | { 171 | u8 pwm_data, duty_data; 172 | duty_data = tuxi_get_fan_speed(1, &duty_data); 173 | pwm_data = (duty_data * 0xff) / FAN_SET_DUTY_MAX; 174 | sysfs_emit(buffer, "%d\n", pwm_data); 175 | return strlen(buffer); 176 | } 177 | 178 | static ssize_t fan2_pwm_store(struct device *dev, 179 | struct device_attribute *attr, 180 | const char *buffer, size_t size) 181 | { 182 | u8 pwm_data, duty_data; 183 | int err; 184 | 185 | if (kstrtou8(buffer, 0, &pwm_data)) 186 | return -EINVAL; 187 | 188 | duty_data = (pwm_data * FAN_SET_DUTY_MAX) / 0xff; 189 | 190 | // Don't allow vallues between fan-off and minimum fan-on-speed 191 | if (duty_data <= FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 2 / 100) 192 | duty_data = 0; 193 | else if (duty_data < FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 100) 194 | duty_data = FAN_ON_MIN_SPEED_PERCENT * FAN_SET_DUTY_MAX / 100; 195 | 196 | err = tuxi_set_fan_speed(1, duty_data); 197 | if (err) 198 | return err; 199 | 200 | return size; 201 | } 202 | 203 | static ssize_t fan2_pwm_enable_show(struct device *dev, 204 | struct device_attribute *attr, char *buffer) 205 | { 206 | enum tuxi_fan_mode mode; 207 | u8 enable_hwmon; 208 | int err; 209 | 210 | err = tuxi_get_fan_mode(&mode); 211 | if (mode == MANUAL) { 212 | enable_hwmon = 1; 213 | } else { 214 | enable_hwmon = 2; 215 | } 216 | sysfs_emit(buffer, "%d\n", enable_hwmon); 217 | 218 | return strlen(buffer); 219 | } 220 | 221 | static ssize_t fan2_pwm_enable_store(struct device *dev, 222 | struct device_attribute *attr, 223 | const char *buffer, size_t size) 224 | { 225 | enum tuxi_fan_mode mode; 226 | u8 enable_hwmon; 227 | int err; 228 | 229 | if (kstrtou8(buffer, 0, &enable_hwmon)) 230 | return -EINVAL; 231 | 232 | if (enable_hwmon == 1) 233 | mode = MANUAL; 234 | else 235 | mode = AUTO; 236 | 237 | err = tuxi_set_fan_mode(mode); 238 | if (err) 239 | return err; 240 | 241 | return size; 242 | } 243 | 244 | static umode_t 245 | hwm_is_visible(const void __always_unused *drvdata, 246 | enum hwmon_sensor_types __always_unused type, 247 | u32 __always_unused attr, int __always_unused channel) 248 | { 249 | return 0444; 250 | } 251 | 252 | static int 253 | hwm_read(struct device __always_unused *dev, enum hwmon_sensor_types type, 254 | u32 __always_unused attr, int channel, long *val) 255 | { 256 | int err; 257 | u16 temp; 258 | u16 rpm; 259 | 260 | switch (type) { 261 | case hwmon_temp: 262 | err = tuxi_get_fan_temp(channel, &temp); 263 | if (err) 264 | return err; 265 | *val = (temp - 2730) * 100; // temp is in tenth Kelvin, hovever 266 | // the last digit is always 0, so 267 | // the conversion is also rounded to 268 | // whole °C. 269 | return 0; 270 | case hwmon_fan: 271 | switch (attr) { 272 | case hwmon_fan_min: 273 | *val = 0; 274 | return 0; 275 | case hwmon_fan_max: 276 | *val = 6000; // FIXME Return value read from firmware. 277 | return 0; 278 | case hwmon_fan_input: 279 | err = tuxi_get_fan_rpm(channel, &rpm); 280 | if (err) 281 | return err; 282 | *val = rpm; 283 | return 0; 284 | default: 285 | break; 286 | } 287 | default: 288 | break; 289 | } 290 | 291 | return -EOPNOTSUPP; 292 | } 293 | 294 | static const char * const hwm_temp_labels[] = { 295 | "cpu0", 296 | "gpu0" 297 | }; 298 | 299 | static const char * const hwm_fan_labels[] = { 300 | "cpu0", 301 | "gpu0" 302 | }; 303 | 304 | static int 305 | hwm_read_string(struct device __always_unused *dev, 306 | enum hwmon_sensor_types type, u32 __always_unused attr, 307 | int channel, const char **str) 308 | { 309 | switch (type) { 310 | case hwmon_temp: 311 | *str = hwm_temp_labels[channel]; 312 | return 0; 313 | case hwmon_fan: 314 | *str = hwm_fan_labels[channel]; 315 | return 0; 316 | default: 317 | break; 318 | } 319 | 320 | return -EOPNOTSUPP; 321 | } 322 | 323 | static const struct hwmon_ops hwmops = { 324 | .is_visible = hwm_is_visible, 325 | .read = hwm_read, 326 | .read_string = hwm_read_string 327 | }; 328 | 329 | static const struct hwmon_channel_info *const hwmcinfo[] = { 330 | HWMON_CHANNEL_INFO(temp, 331 | HWMON_T_INPUT | HWMON_T_LABEL, 332 | HWMON_T_INPUT | HWMON_T_LABEL), 333 | HWMON_CHANNEL_INFO(fan, 334 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX, 335 | HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX), 336 | NULL 337 | }; 338 | 339 | static const struct hwmon_chip_info hwminfo = { 340 | .ops = &hwmops, 341 | .info = hwmcinfo 342 | }; 343 | 344 | static int __init tuxedo_tuxi_fan_control_probe(struct platform_device *pdev) 345 | { 346 | int err; 347 | u16 temp, rpm; 348 | struct device *hwmdev; 349 | struct driver_data_t *driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), GFP_KERNEL); 350 | if (!driver_data) 351 | return -ENOMEM; 352 | 353 | pr_debug("driver probe\n"); 354 | 355 | dev_set_drvdata(&pdev->dev, driver_data); 356 | 357 | driver_data->pdev = pdev; 358 | 359 | err = sysfs_create_group(&driver_data->pdev->dev.kobj, &fan_control_attr_group); 360 | if (err) { 361 | pr_err("create group failed\n"); 362 | platform_device_unregister(driver_data->pdev); 363 | return err; 364 | } 365 | 366 | if(tuxi_get_fan_temp(0, &temp) == 0 && tuxi_get_fan_rpm(0, &rpm) == 0) { 367 | hwmdev = devm_hwmon_device_register_with_info(&pdev->dev, 368 | "tuxedo_tuxi_sensors", 369 | NULL, 370 | &hwminfo, 371 | NULL); 372 | return PTR_ERR_OR_ZERO(hwmdev); 373 | } 374 | pr_debug("Old tuxi interface with missing temp and rpm functions detected.\n"); 375 | pr_debug("Skipping hwmon creation.\n"); 376 | 377 | return 0; 378 | } 379 | 380 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 381 | static int tuxedo_tuxi_fan_control_remove(struct platform_device *pdev) 382 | #else 383 | static void tuxedo_tuxi_fan_control_remove(struct platform_device *pdev) 384 | #endif 385 | { 386 | pr_debug("driver remove\n"); 387 | struct driver_data_t *driver_data = dev_get_drvdata(&pdev->dev); 388 | sysfs_remove_group(&driver_data->pdev->dev.kobj, &fan_control_attr_group); 389 | 390 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) 391 | return 0; 392 | #endif 393 | } 394 | 395 | static struct platform_device *tuxedo_tuxi_fan_control_device; 396 | static struct platform_driver tuxedo_tuxi_fan_control_driver = { 397 | .driver.name = "tuxedo_fan_control", 398 | .remove = tuxedo_tuxi_fan_control_remove, 399 | }; 400 | 401 | static int __init tuxedo_tuxi_fan_control_init(void) 402 | { 403 | int err; 404 | u8 dummy; 405 | 406 | // Check interface presence 407 | err = tuxi_get_nr_fans(&dummy); 408 | if (err == -ENODEV) 409 | return err; 410 | 411 | tuxedo_tuxi_fan_control_device = 412 | platform_create_bundle(&tuxedo_tuxi_fan_control_driver, 413 | tuxedo_tuxi_fan_control_probe, NULL, 0, NULL, 0); 414 | 415 | if (IS_ERR(tuxedo_tuxi_fan_control_device)) 416 | return PTR_ERR(tuxedo_tuxi_fan_control_device); 417 | 418 | return 0; 419 | } 420 | 421 | static void __exit tuxedo_tuxi_fan_control_exit(void) 422 | { 423 | platform_device_unregister(tuxedo_tuxi_fan_control_device); 424 | platform_driver_unregister(&tuxedo_tuxi_fan_control_driver); 425 | } 426 | 427 | module_init(tuxedo_tuxi_fan_control_init); 428 | module_exit(tuxedo_tuxi_fan_control_exit); 429 | 430 | MODULE_AUTHOR("TUXEDO Computers GmbH "); 431 | MODULE_DESCRIPTION("TUXEDO Computers TUXI fan control driver"); 432 | MODULE_LICENSE("GPL"); 433 | --------------------------------------------------------------------------------