mirror of
https://github.com/ps1x/onexplayer-superxcontrol.git
synced 2026-09-10 09:18:14 +00:00
Add Super X tablet and power integration
This commit is contained in:
parent
e75643f2c4
commit
f5eae5c97e
17 changed files with 657 additions and 3 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -8,6 +8,8 @@ Module.symvers
|
|||
modules.order
|
||||
.*.cmd
|
||||
.tmp/
|
||||
/contrib/upstream-linux/test-module/
|
||||
/contrib/upstream-linux/outgoing-*/
|
||||
|
||||
# Python cache
|
||||
__pycache__/
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -7,6 +7,8 @@ Linux control stack for **OneXPlayer Super X** with:
|
|||
- GNOME Shell extension
|
||||
- RGB control
|
||||
- TDP control
|
||||
- automatic tablet-mode reporting and display rotation calibration
|
||||
- selectable CPU power policies
|
||||
- battery charge-limit backend
|
||||
|
||||
If you are searching for **OneXPlayer Super X Linux fan control**, **OneXPlayer
|
||||
|
|
@ -43,6 +45,10 @@ under `local/` and are excluded from Git.
|
|||
- GNOME Shell top-bar plugin for daily use
|
||||
- RGB preset and color control helpers
|
||||
- TDP presets through `ryzenadj`
|
||||
- Turbo-button TDP synchronization
|
||||
- Adaptive and Ultra Saver CPU power modes
|
||||
- detachable-keyboard tablet-mode reporting
|
||||
- accelerometer mounting calibration for automatic rotation
|
||||
- battery status and charge-limit backend used by the plugin
|
||||
|
||||
## Compatibility
|
||||
|
|
@ -66,6 +72,8 @@ This project is based on the original `oxp-sensors` work:
|
|||
- `gnome-extension/`: GNOME Shell top-bar plugin
|
||||
- `oxp-rgb`, `oxp-rgb-hid.py`: RGB control helpers
|
||||
- `oxp-tdp`: TDP helper based on `ryzenadj`
|
||||
- `contrib/power/`: CPU policy, Turbo-button TDP, and idle-power helpers
|
||||
- `contrib/sensors/`: Super X accelerometer hardware database entry
|
||||
- `oxp-battery-probe.py`, `oxp-battery-ec-probe.c`: battery status / charge-limit backend for the plugin
|
||||
|
||||
## Build DKMS Module
|
||||
|
|
@ -90,6 +98,9 @@ Install the client tools, fan-control service, and GNOME extension:
|
|||
./install.sh
|
||||
```
|
||||
|
||||
The TDP helpers require `ryzenadj` to be installed and available in `PATH`.
|
||||
The CPU power menu requires `power-profiles-daemon` and `powerprofilesctl`.
|
||||
|
||||
## Uninstall
|
||||
|
||||
Remove the installed userland components with:
|
||||
|
|
@ -127,6 +138,7 @@ sudo /usr/local/bin/oxp-fan-profile set balanced
|
|||
The GNOME Shell extension adds a top-bar menu for:
|
||||
|
||||
- fan profile switching
|
||||
- CPU power modes
|
||||
- RGB presets and custom color
|
||||
- TDP presets
|
||||
- battery charge-limit / bypass controls when the backend is available
|
||||
|
|
|
|||
2
contrib/power/10-wifi-powersave.conf
Normal file
2
contrib/power/10-wifi-powersave.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[connection]
|
||||
wifi.powersave=3
|
||||
1
contrib/power/80-oxp-disable-fingerprint.rules
Normal file
1
contrib/power/80-oxp-disable-fingerprint.rules
Normal file
|
|
@ -0,0 +1 @@
|
|||
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="2808", ATTR{idProduct}=="5952", RUN+="/usr/local/sbin/oxp-disable-fingerprint %p"
|
||||
33
contrib/power/README.md
Normal file
33
contrib/power/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# OneXPlayer idle-power tuning
|
||||
|
||||
This host policy combines aggressive runtime idle power management with full
|
||||
AMD CPU boost availability. The three locally identified USB input devices are
|
||||
left alone so the built-in controller, keyboard, and receiver wake instantly.
|
||||
|
||||
The system service applies PCI runtime PM, a 625 MHz CPU floor, the full 5.19
|
||||
GHz boost ceiling, `balance_power` EPP, and less frequent dirty-page wakeups.
|
||||
The resume hook reapplies those settings after suspend.
|
||||
|
||||
Power Profiles reports `performance` because that firmware profile is required
|
||||
to expose the full boost ceiling. The service then replaces its CPU floor and
|
||||
EPP values, so the effective policy is the custom low-idle configuration above.
|
||||
|
||||
The NetworkManager drop-in enables Wi-Fi power saving by default. Installation
|
||||
reloads NetworkManager configuration; reconnect Wi-Fi to apply it to an
|
||||
already-active connection.
|
||||
|
||||
`oxp-cpu-profile` exposes the effective custom mode as `adaptive` and is used
|
||||
by the OXP Control GNOME extension's CPU Power menu. Selecting Adaptive asks
|
||||
for administrator authorization and reapplies `oxp-idle-power.service`.
|
||||
|
||||
Ultra Saver keeps the 625 MHz floor, caps the CPU at its 2.0 GHz lowest
|
||||
non-linear (most efficient) frequency, and selects the `power` EPP hint. The
|
||||
other modes restore the CPU's full hardware frequency range before applying
|
||||
their profile, so leaving Ultra Saver cannot retain its 2.0 GHz ceiling.
|
||||
The selected OXP mode is stored in `/var/lib/oxp-power-mode` and restored at boot
|
||||
and after resume.
|
||||
|
||||
The internal unsupported FocalTech `2808:5952` fingerprint reader is disabled
|
||||
at its dedicated USB port. A device-specific udev rule reapplies the setting
|
||||
after USB controller re-enumeration; the power service also reapplies it after
|
||||
resume.
|
||||
48
contrib/power/oxp-cpu-profile
Executable file
48
contrib/power/oxp-cpu-profile
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 status | set {power-saver|balanced|performance|adaptive|ultra}" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
effective_profile() {
|
||||
policy=/sys/devices/system/cpu/cpufreq/policy0
|
||||
min=$(cat "$policy/scaling_min_freq")
|
||||
max=$(cat "$policy/scaling_max_freq")
|
||||
epp=$(cat "$policy/energy_performance_preference")
|
||||
expected_min=$(cat "$policy/cpuinfo_min_freq")
|
||||
ultra_max=$(cat "$policy/amd_pstate_lowest_nonlinear_freq")
|
||||
if [ -r "$policy/amd_pstate_max_freq" ]; then
|
||||
expected_max=$(cat "$policy/amd_pstate_max_freq")
|
||||
else
|
||||
expected_max=$(cat "$policy/cpuinfo_max_freq")
|
||||
fi
|
||||
|
||||
if [ "$min" = "$expected_min" ] && [ "$max" = "$expected_max" ] && [ "$epp" = balance_power ]; then
|
||||
echo adaptive
|
||||
elif [ "$min" = "$expected_min" ] && [ "$max" = "$ultra_max" ] && [ "$epp" = power ]; then
|
||||
echo ultra
|
||||
else
|
||||
powerprofilesctl get
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
status)
|
||||
effective_profile
|
||||
;;
|
||||
set)
|
||||
case "${2:-}" in
|
||||
power-saver|balanced|performance|adaptive|ultra)
|
||||
/usr/local/sbin/oxp-idle-power "$2"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
21
contrib/power/oxp-disable-fingerprint
Executable file
21
contrib/power/oxp-disable-fingerprint
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
# Disable the internal FocalTech fingerprint reader at its USB port.
|
||||
set -eu
|
||||
|
||||
[ "$#" -eq 1 ] || exit 2
|
||||
device="/sys$1"
|
||||
[ -r "$device/idVendor" ] || exit 0
|
||||
[ "$(cat "$device/idVendor")" = 2808 ] || exit 0
|
||||
[ "$(cat "$device/idProduct")" = 5952 ] || exit 0
|
||||
|
||||
usb_node=$(basename "$device")
|
||||
bus=${usb_node%%-*}
|
||||
port=${usb_node#*-}
|
||||
case "$port" in
|
||||
''|*[!0-9]*) exit 1 ;;
|
||||
esac
|
||||
|
||||
usb_root=$(dirname "$device")
|
||||
disable="$usb_root/$bus-0:1.0/usb$bus-port$port/disable"
|
||||
[ -w "$disable" ] || exit 1
|
||||
printf '%s\n' 1 > "$disable"
|
||||
7
contrib/power/oxp-idle-power-resume
Executable file
7
contrib/power/oxp-idle-power-resume
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
case "$1" in
|
||||
post)
|
||||
systemctl restart oxp-idle-power.service
|
||||
;;
|
||||
esac
|
||||
11
contrib/power/oxp-idle-power.service
Normal file
11
contrib/power/oxp-idle-power.service
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=OneXPlayer low-idle-power tuning with full CPU boost
|
||||
After=power-profiles-daemon.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/oxp-idle-power
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
71
contrib/power/oxp-idle-power.sh
Executable file
71
contrib/power/oxp-idle-power.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/sh
|
||||
# Apply the selected OXP CPU policy and common idle-power savings.
|
||||
set -eu
|
||||
|
||||
state_file=/var/lib/oxp-power-mode
|
||||
mode=${1:-}
|
||||
if [ -n "$mode" ]; then
|
||||
case "$mode" in
|
||||
power-saver|balanced|performance|adaptive|ultra) ;;
|
||||
*) echo "Unknown OXP power mode: $mode" >&2; exit 2 ;;
|
||||
esac
|
||||
printf '%s\n' "$mode" > "$state_file"
|
||||
elif [ -r "$state_file" ]; then
|
||||
mode=$(cat "$state_file")
|
||||
else
|
||||
mode=adaptive
|
||||
fi
|
||||
|
||||
# Performance is the only firmware profile that exposes the full CPPC boost
|
||||
# range needed by Adaptive. Ultra starts from the standard saver profile.
|
||||
case "$mode" in
|
||||
adaptive) powerprofilesctl set performance ;;
|
||||
ultra) powerprofilesctl set power-saver ;;
|
||||
*) powerprofilesctl set "$mode" ;;
|
||||
esac
|
||||
|
||||
# The unsupported internal FocalTech fingerprint reader otherwise remains on
|
||||
# the USB bus. Disable its dedicated port; udev applies the same rule at boot.
|
||||
for fingerprint in /sys/bus/usb/devices/*; do
|
||||
[ -r "$fingerprint/idVendor" ] || continue
|
||||
[ "$(cat "$fingerprint/idVendor")" = 2808 ] || continue
|
||||
[ "$(cat "$fingerprint/idProduct")" = 5952 ] || continue
|
||||
/usr/local/sbin/oxp-disable-fingerprint "${fingerprint#/sys}"
|
||||
done
|
||||
|
||||
# Let every PCI driver runtime-suspend its device when the driver considers it
|
||||
# idle. Active devices remain awake and resume automatically on demand.
|
||||
for control in /sys/bus/pci/devices/*/power/control; do
|
||||
[ -w "$control" ] && printf '%s\n' auto > "$control"
|
||||
done
|
||||
|
||||
for policy in /sys/devices/system/cpu/cpufreq/policy*; do
|
||||
[ -d "$policy" ] || continue
|
||||
|
||||
# power-profiles-daemon does not reset cpufreq limits previously written by
|
||||
# Ultra Saver. Restore the hardware range on every profile transition before
|
||||
# applying any custom ceiling, otherwise Performance can remain stuck at
|
||||
# 2 GHz (or lower) indefinitely.
|
||||
if [ -r "$policy/amd_pstate_max_freq" ]; then
|
||||
cat "$policy/amd_pstate_max_freq" > "$policy/scaling_max_freq"
|
||||
else
|
||||
cat "$policy/cpuinfo_max_freq" > "$policy/scaling_max_freq"
|
||||
fi
|
||||
cat "$policy/cpuinfo_min_freq" > "$policy/scaling_min_freq"
|
||||
|
||||
if [ "$mode" = adaptive ] || [ "$mode" = ultra ]; then
|
||||
# amd-pstate's powersave governor is dynamic, not a fixed low frequency.
|
||||
printf '%s\n' powersave > "$policy/scaling_governor"
|
||||
if [ "$mode" = adaptive ]; then
|
||||
printf '%s\n' balance_power > "$policy/energy_performance_preference"
|
||||
else
|
||||
# This is the efficient ceiling; lower clocks can increase total
|
||||
# energy by making work take disproportionately longer.
|
||||
cat "$policy/amd_pstate_lowest_nonlinear_freq" > "$policy/scaling_max_freq"
|
||||
printf '%s\n' power > "$policy/energy_performance_preference"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Batch periodic dirty-page writeback to reduce otherwise needless wakeups.
|
||||
sysctl -q -w vm.dirty_writeback_centisecs=1500
|
||||
136
contrib/power/oxp-turbo-tdp.py
Executable file
136
contrib/power/oxp-turbo-tdp.py
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Set a TDP preset when the OneXPlayer Super X Turbo key is pressed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
INPUT_NAME = "OneXPlayer Super X Turbo Button"
|
||||
FIRMWARE_TDP = Path("/sys/devices/platform/oxp-platform/firmware_tdp")
|
||||
EV_KEY = 0x01
|
||||
KEY_PROG1 = 148
|
||||
INPUT_EVENT = struct.Struct("llHHI")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--watts", type=int, default=80)
|
||||
parser.add_argument("--normal-stapm", type=int, default=10)
|
||||
parser.add_argument("--normal-fast", type=int, default=15)
|
||||
parser.add_argument("--normal-slow", type=int, default=10)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def wait_for(path: Path, timeout: float = 15.0) -> None:
|
||||
deadline = time.monotonic() + timeout
|
||||
while not path.exists():
|
||||
if time.monotonic() >= deadline:
|
||||
raise TimeoutError(f"timed out waiting for {path}")
|
||||
time.sleep(0.25)
|
||||
|
||||
|
||||
def find_input_device() -> Path:
|
||||
for event in Path("/sys/class/input").glob("event*"):
|
||||
try:
|
||||
name = (event / "device/name").read_text(encoding="utf-8").strip()
|
||||
except OSError:
|
||||
continue
|
||||
if name == INPUT_NAME:
|
||||
return Path("/dev/input") / event.name
|
||||
raise FileNotFoundError(f"input device {INPUT_NAME!r} was not found")
|
||||
|
||||
|
||||
def read_firmware_tdp() -> tuple[int, int, int]:
|
||||
values = tuple(
|
||||
int(value) for value in FIRMWARE_TDP.read_text(encoding="ascii").split()
|
||||
)
|
||||
if len(values) != 3:
|
||||
raise RuntimeError(f"invalid firmware TDP tuple: {values}")
|
||||
return values
|
||||
|
||||
|
||||
def apply_limits(stapm: int, fast: int, slow: int) -> None:
|
||||
ryzenadj = shutil.which("ryzenadj")
|
||||
if not ryzenadj:
|
||||
raise FileNotFoundError("ryzenadj was not found in PATH")
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
ryzenadj,
|
||||
"--stapm-limit", str(stapm * 1000),
|
||||
"--fast-limit", str(fast * 1000),
|
||||
"--slow-limit", str(slow * 1000),
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
limits = (args.watts, args.normal_stapm, args.normal_fast, args.normal_slow)
|
||||
if any(not 3 <= watts <= 80 for watts in limits):
|
||||
raise ValueError("TDP limits must be between 3 and 80 W")
|
||||
|
||||
running = True
|
||||
|
||||
def stop(_signum: int, _frame: object) -> None:
|
||||
nonlocal running
|
||||
running = False
|
||||
|
||||
signal.signal(signal.SIGINT, stop)
|
||||
signal.signal(signal.SIGTERM, stop)
|
||||
|
||||
wait_for(FIRMWARE_TDP)
|
||||
input_device = find_input_device()
|
||||
|
||||
def sync_from_firmware() -> None:
|
||||
firmware = read_firmware_tdp()
|
||||
turbo_enabled = firmware[0] == firmware[1] == firmware[2] and firmware[0] >= 45
|
||||
if turbo_enabled:
|
||||
applied = (args.watts, args.watts, args.watts)
|
||||
mode = "turbo"
|
||||
else:
|
||||
applied = (args.normal_stapm, args.normal_fast, args.normal_slow)
|
||||
mode = "normal"
|
||||
apply_limits(*applied)
|
||||
print(
|
||||
f"Firmware {firmware[0]}/{firmware[1]}/{firmware[2]} W: "
|
||||
f"applied {applied[0]}/{applied[1]}/{applied[2]} W ({mode})",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
sync_from_firmware()
|
||||
|
||||
fd = os.open(input_device, os.O_RDONLY | os.O_NONBLOCK)
|
||||
poller = select.poll()
|
||||
poller.register(fd, select.POLLIN)
|
||||
|
||||
try:
|
||||
while running:
|
||||
for _fd, event_mask in poller.poll(1000):
|
||||
if event_mask & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
|
||||
raise OSError(f"input device poll failed: mask={event_mask:#x}")
|
||||
|
||||
data = os.read(fd, INPUT_EVENT.size * 64)
|
||||
for offset in range(0, len(data), INPUT_EVENT.size):
|
||||
_sec, _usec, event_type, code, value = \
|
||||
INPUT_EVENT.unpack_from(data, offset)
|
||||
if event_type == EV_KEY and code == KEY_PROG1 and value == 1:
|
||||
sync_from_firmware()
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
contrib/power/oxp-turbo-tdp.service
Normal file
15
contrib/power/oxp-turbo-tdp.service
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=OneXPlayer Super X Turbo button TDP preset
|
||||
After=systemd-modules-load.service
|
||||
StartLimitIntervalSec=60
|
||||
StartLimitBurst=3
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/usr/sbin/modprobe oxp-sensors
|
||||
ExecStart=/usr/local/bin/oxp-turbo-tdp --watts 80
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
5
contrib/sensors/61-sensor-onexplayer-super-x.hwdb
Normal file
5
contrib/sensors/61-sensor-onexplayer-super-x.hwdb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# One-Netbook OneXPlayer Super X
|
||||
# The BMI260 ACPI node uses the BMI0160 ID and is mounted with its X and Y
|
||||
# axes exchanged relative to the built-in display.
|
||||
sensor:modalias:acpi:BMI0160:*:dmi:*:svnONE-NETBOOK:pnONEXPLAYERSUPERX:*
|
||||
ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
|
||||
|
|
@ -10,6 +10,7 @@ import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
|||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
const PROFILE_HELPER = '/usr/local/bin/oxp-fan-profile';
|
||||
const CPU_PROFILE_HELPER = '/usr/local/bin/oxp-cpu-profile';
|
||||
const RGB_HELPER = '/usr/local/bin/oxp-rgb';
|
||||
const RGB_HID_HELPER = '/usr/local/bin/oxp-rgb-hid';
|
||||
const TDP_HELPER = '/usr/local/bin/oxp-tdp';
|
||||
|
|
@ -196,6 +197,9 @@ class OXPFanProfilesButton extends PanelMenu.Button {
|
|||
this._profilesMenu = new PopupMenu.PopupSubMenuMenuItem('Fan Profiles');
|
||||
this.menu.addMenuItem(this._profilesMenu);
|
||||
|
||||
this._cpuProfilesMenu = new PopupMenu.PopupSubMenuMenuItem('CPU Power');
|
||||
this.menu.addMenuItem(this._cpuProfilesMenu);
|
||||
|
||||
this._tdpMenu = new PopupMenu.PopupSubMenuMenuItem('TDP');
|
||||
this.menu.addMenuItem(this._tdpMenu);
|
||||
|
||||
|
|
@ -570,6 +574,38 @@ class OXPFanProfilesButton extends PanelMenu.Button {
|
|||
});
|
||||
}
|
||||
|
||||
_getCpuPowerMode() {
|
||||
const result = this._runChecked([CPU_PROFILE_HELPER, 'status']);
|
||||
return result.ok ? result.stdout : null;
|
||||
}
|
||||
|
||||
_setCpuPowerMode(mode, label) {
|
||||
const command = ['pkexec', CPU_PROFILE_HELPER, 'set', mode];
|
||||
this._spawn(command, `CPU power: ${label}…`);
|
||||
this._runLater(1200, () => this._refresh());
|
||||
}
|
||||
|
||||
_buildCpuProfilesMenu() {
|
||||
const current = this._getCpuPowerMode();
|
||||
const profiles = [
|
||||
['power-saver', 'Power Saver'],
|
||||
['balanced', 'Balanced'],
|
||||
['performance', 'Performance'],
|
||||
['adaptive', 'Adaptive'],
|
||||
['ultra', 'Ultra Saver'],
|
||||
];
|
||||
|
||||
this._cpuProfilesMenu.menu.removeAll();
|
||||
for (const [mode, label] of profiles) {
|
||||
const item = new PopupMenu.PopupMenuItem(label);
|
||||
if (mode === current) {
|
||||
item.setOrnament(PopupMenu.Ornament.DOT);
|
||||
}
|
||||
item.connect('activate', () => this._setCpuPowerMode(mode, label));
|
||||
this._cpuProfilesMenu.menu.addMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
_setTdp(watts) {
|
||||
const power = this._readPowerSourceInfo();
|
||||
if (power.onBattery && watts > MAX_BATTERY_TDP_WATTS) {
|
||||
|
|
@ -897,6 +933,7 @@ class OXPFanProfilesButton extends PanelMenu.Button {
|
|||
const status = this._runStatus();
|
||||
this._enforceBatteryTdpSafety();
|
||||
this._profilesMenu.menu.removeAll();
|
||||
this._buildCpuProfilesMenu();
|
||||
this._buildTdpMenu();
|
||||
this._refreshBatteryInfo(false);
|
||||
|
||||
|
|
|
|||
20
install.sh
20
install.sh
|
|
@ -13,14 +13,26 @@ sudo install -Dm755 "$REPO_DIR/oxp-fan-profile.py" /usr/local/bin/oxp-fan-profil
|
|||
sudo install -Dm755 "$REPO_DIR/oxp-rgb-hid.py" /usr/local/bin/oxp-rgb-hid
|
||||
sudo install -Dm755 "$REPO_DIR/oxp-rgb" /usr/local/bin/oxp-rgb
|
||||
sudo install -Dm755 "$REPO_DIR/oxp-tdp" /usr/local/bin/oxp-tdp
|
||||
sudo install -Dm755 "$REPO_DIR/contrib/power/oxp-turbo-tdp.py" /usr/local/bin/oxp-turbo-tdp
|
||||
sudo install -Dm755 "$REPO_DIR/contrib/power/oxp-cpu-profile" /usr/local/bin/oxp-cpu-profile
|
||||
sudo install -Dm755 "$REPO_DIR/contrib/power/oxp-idle-power.sh" /usr/local/sbin/oxp-idle-power
|
||||
sudo install -Dm755 "$REPO_DIR/contrib/power/oxp-disable-fingerprint" /usr/local/sbin/oxp-disable-fingerprint
|
||||
sudo install -Dm755 "$REPO_DIR/oxp-battery-probe.py" /usr/local/bin/oxp-battery-probe
|
||||
gcc -O2 -Wall -Wextra -o /tmp/oxp-battery-ec-probe "$REPO_DIR/oxp-battery-ec-probe.c"
|
||||
sudo install -Dm755 /tmp/oxp-battery-ec-probe /usr/local/bin/oxp-battery-ec-probe
|
||||
sudo chmod 4755 /usr/local/bin/oxp-battery-ec-probe
|
||||
sudo install -Dm644 "$REPO_DIR/oxp-fan-control.service" /etc/systemd/system/oxp-fan-control.service
|
||||
sudo install -Dm644 "$REPO_DIR/contrib/power/oxp-turbo-tdp.service" /etc/systemd/system/oxp-turbo-tdp.service
|
||||
sudo install -Dm644 "$REPO_DIR/contrib/power/oxp-idle-power.service" /etc/systemd/system/oxp-idle-power.service
|
||||
sudo install -Dm755 "$REPO_DIR/contrib/power/oxp-idle-power-resume" /usr/lib/systemd/system-sleep/oxp-idle-power
|
||||
sudo install -Dm644 "$REPO_DIR/oxp-sensors.conf" /etc/modules-load.d/oxp-sensors.conf
|
||||
sudo install -Dm644 "$REPO_DIR/oxp-fan-profile.rules" /etc/polkit-1/rules.d/49-oxp-fan-profile.rules
|
||||
sudo install -Dm644 "$REPO_DIR/oxp-rgb-hid.rules" /etc/udev/rules.d/99-oxp-rgb-hid.rules
|
||||
sudo install -Dm644 "$REPO_DIR/contrib/power/80-oxp-disable-fingerprint.rules" /etc/udev/rules.d/80-oxp-disable-fingerprint.rules
|
||||
sudo install -Dm644 "$REPO_DIR/contrib/power/10-wifi-powersave.conf" /etc/NetworkManager/conf.d/10-wifi-powersave.conf
|
||||
sudo install -Dm644 \
|
||||
"$REPO_DIR/contrib/sensors/61-sensor-onexplayer-super-x.hwdb" \
|
||||
/etc/udev/hwdb.d/61-sensor-onexplayer-super-x.hwdb
|
||||
|
||||
if ! sudo test -f /etc/oxp-fan-control.conf; then
|
||||
echo "Installing fresh profile config..."
|
||||
|
|
@ -40,10 +52,18 @@ sudo install -m644 -o "$TARGET_USER" -g "$TARGET_GROUP" \
|
|||
"$EXTENSION_DIR/extension.js"
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemd-hwdb update
|
||||
if command -v nmcli >/dev/null 2>&1; then
|
||||
sudo nmcli general reload conf || true
|
||||
fi
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --subsystem-match=iio --action=change || true
|
||||
sudo udevadm trigger --attr-match=idVendor=1a2c --attr-match=idProduct=b001 || true
|
||||
sudo modprobe oxp-sensors || true
|
||||
sudo systemctl try-restart iio-sensor-proxy.service
|
||||
sudo systemctl enable --now oxp-fan-control.service
|
||||
sudo systemctl enable --now oxp-turbo-tdp.service
|
||||
sudo systemctl enable --now oxp-idle-power.service
|
||||
|
||||
if command -v gnome-extensions >/dev/null 2>&1; then
|
||||
sudo -u "$TARGET_USER" gnome-extensions enable "$EXTENSION_UUID" 2>/dev/null || true
|
||||
|
|
|
|||
215
oxp-sensors.c
215
oxp-sensors.c
|
|
@ -8,11 +8,22 @@
|
|||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/processor.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
struct acpi_ec;
|
||||
typedef int (*acpi_ec_query_func)(void *data);
|
||||
|
||||
extern struct acpi_ec *first_ec;
|
||||
extern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
|
||||
acpi_handle handle,
|
||||
acpi_ec_query_func func, void *data);
|
||||
extern void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);
|
||||
|
||||
/* Handle ACPI lock mechanism */
|
||||
static u32 oxp_mutex;
|
||||
|
|
@ -46,6 +57,12 @@ static bool unlock_global_acpi_lock(void)
|
|||
#define OXP_TURBO_TAKE_VAL 0x40
|
||||
#define OXP_TURBO_RETURN_VAL 0x00
|
||||
|
||||
#define OXP_TURBO_QUERY 0x49
|
||||
#define OXP_TDP_STAPM_REG 0x31
|
||||
#define OXP_TDP_FAST_REG 0x32
|
||||
#define OXP_TDP_SLOW_REG 0x33
|
||||
#define OXP_TURBO_QUERY_METHOD "\\_SB.PCI0.SBRG.EC0._Q49"
|
||||
|
||||
#define OXP_LED_ENABLE_VAL 0x01
|
||||
#define OXP_LED_DISABLE_VAL 0x00
|
||||
|
||||
|
|
@ -75,6 +92,156 @@ MODULE_PARM_DESC(led_blue_reg, "EC register for LED blue channel");
|
|||
|
||||
static bool led_regs_valid;
|
||||
|
||||
#define OXP_SUPER_X_KEYBOARD_VID 0x1a86
|
||||
#define OXP_SUPER_X_KEYBOARD_PID 0x1305
|
||||
|
||||
static struct input_dev *oxp_tablet_mode_input;
|
||||
static acpi_handle oxp_turbo_query_method;
|
||||
|
||||
static bool oxp_is_detachable_keyboard(const struct usb_device *udev)
|
||||
{
|
||||
return le16_to_cpu(udev->descriptor.idVendor) == OXP_SUPER_X_KEYBOARD_VID &&
|
||||
le16_to_cpu(udev->descriptor.idProduct) == OXP_SUPER_X_KEYBOARD_PID;
|
||||
}
|
||||
|
||||
static void oxp_report_keyboard_attached(bool attached)
|
||||
{
|
||||
if (!oxp_tablet_mode_input)
|
||||
return;
|
||||
|
||||
/* SW_TABLET_MODE is set when the detachable keyboard is absent. */
|
||||
input_report_switch(oxp_tablet_mode_input, SW_TABLET_MODE, !attached);
|
||||
input_sync(oxp_tablet_mode_input);
|
||||
}
|
||||
|
||||
static int oxp_find_detachable_keyboard(struct usb_device *udev, void *data)
|
||||
{
|
||||
bool *attached = data;
|
||||
|
||||
if (oxp_is_detachable_keyboard(udev))
|
||||
*attached = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int oxp_usb_notify(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct usb_device *udev = data;
|
||||
|
||||
if (!oxp_is_detachable_keyboard(udev))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
switch (action) {
|
||||
case USB_DEVICE_ADD:
|
||||
oxp_report_keyboard_attached(true);
|
||||
break;
|
||||
case USB_DEVICE_REMOVE:
|
||||
oxp_report_keyboard_attached(false);
|
||||
break;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block oxp_usb_notifier = {
|
||||
.notifier_call = oxp_usb_notify,
|
||||
};
|
||||
|
||||
static void oxp_unregister_usb_notifier(void *unused)
|
||||
{
|
||||
usb_unregister_notify(&oxp_usb_notifier);
|
||||
oxp_tablet_mode_input = NULL;
|
||||
}
|
||||
|
||||
static int oxp_register_tablet_mode_switch(struct device *dev)
|
||||
{
|
||||
struct input_dev *input;
|
||||
bool keyboard_attached = false;
|
||||
int ret;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input->name = "OneXPlayer Super X Tablet Mode Switch";
|
||||
input->phys = "oxp-platform/input0";
|
||||
input->id.bustype = BUS_HOST;
|
||||
input_set_capability(input, EV_SW, SW_TABLET_MODE);
|
||||
|
||||
ret = input_register_device(input);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
oxp_tablet_mode_input = input;
|
||||
usb_register_notify(&oxp_usb_notifier);
|
||||
ret = devm_add_action_or_reset(dev, oxp_unregister_usb_notifier, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
usb_for_each_dev(&keyboard_attached, oxp_find_detachable_keyboard);
|
||||
oxp_report_keyboard_attached(keyboard_attached);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int oxp_turbo_query_handler(void *data)
|
||||
{
|
||||
struct input_dev *input = data;
|
||||
|
||||
/* Preserve the firmware _Q49 power-limit notification. */
|
||||
acpi_evaluate_object(oxp_turbo_query_method, NULL, NULL, NULL);
|
||||
|
||||
input_report_key(input, KEY_PROG1, 1);
|
||||
input_sync(input);
|
||||
input_report_key(input, KEY_PROG1, 0);
|
||||
input_sync(input);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void oxp_unregister_turbo_query(void *unused)
|
||||
{
|
||||
acpi_ec_remove_query_handler(first_ec, OXP_TURBO_QUERY);
|
||||
}
|
||||
|
||||
static int oxp_register_turbo_query(struct device *dev)
|
||||
{
|
||||
struct input_dev *input;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (!first_ec)
|
||||
return -ENODEV;
|
||||
|
||||
status = acpi_get_handle(NULL, OXP_TURBO_QUERY_METHOD,
|
||||
&oxp_turbo_query_method);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -ENODEV;
|
||||
|
||||
input = devm_input_allocate_device(dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input->name = "OneXPlayer Super X Turbo Button";
|
||||
input->phys = "oxp-platform/input1";
|
||||
input->id.bustype = BUS_HOST;
|
||||
input_set_capability(input, EV_KEY, KEY_PROG1);
|
||||
|
||||
ret = input_register_device(input);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = acpi_ec_add_query_handler(first_ec, OXP_TURBO_QUERY, NULL,
|
||||
oxp_turbo_query_handler, input);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return devm_add_action_or_reset(dev, oxp_unregister_turbo_query, NULL);
|
||||
}
|
||||
|
||||
static const struct dmi_system_id dmi_table[] = {
|
||||
{
|
||||
.matches = {
|
||||
|
|
@ -184,6 +351,37 @@ static ssize_t tt_toggle_show(struct device *dev,
|
|||
|
||||
static DEVICE_ATTR_RW(tt_toggle);
|
||||
|
||||
static ssize_t firmware_tdp_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
long stapm, fast, slow;
|
||||
int ret;
|
||||
|
||||
ret = read_from_ec(OXP_TDP_STAPM_REG, 1, &stapm);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = read_from_ec(OXP_TDP_FAST_REG, 1, &fast);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = read_from_ec(OXP_TDP_SLOW_REG, 1, &slow);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return sysfs_emit(buf, "%ld %ld %ld\n", stapm, fast, slow);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(firmware_tdp);
|
||||
|
||||
static struct attribute *oxp_tt_attrs[] = {
|
||||
&dev_attr_tt_toggle.attr,
|
||||
&dev_attr_firmware_tdp.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group oxp_tt_group = {
|
||||
.attrs = oxp_tt_attrs,
|
||||
};
|
||||
|
||||
/* LED control functions */
|
||||
static int led_enable(void)
|
||||
{
|
||||
|
|
@ -473,7 +671,6 @@ static const struct hwmon_channel_info * const oxp_platform_sensors[] = {
|
|||
};
|
||||
|
||||
static struct attribute *oxp_ec_attrs[] = {
|
||||
&dev_attr_tt_toggle.attr,
|
||||
&dev_attr_led_enable.attr,
|
||||
&dev_attr_led_mode.attr,
|
||||
&dev_attr_led_brightness.attr,
|
||||
|
|
@ -481,7 +678,9 @@ static struct attribute *oxp_ec_attrs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
ATTRIBUTE_GROUPS(oxp_ec);
|
||||
static const struct attribute_group oxp_ec_group = {
|
||||
.attrs = oxp_ec_attrs,
|
||||
};
|
||||
|
||||
static const struct hwmon_ops oxp_ec_hwmon_ops = {
|
||||
.is_visible = oxp_ec_hwmon_is_visible,
|
||||
|
|
@ -506,6 +705,18 @@ static int oxp_platform_probe(struct platform_device *pdev)
|
|||
|
||||
led_regs_valid = led_map_complete();
|
||||
|
||||
ret = oxp_register_tablet_mode_switch(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = oxp_register_turbo_query(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_device_add_group(dev, &oxp_tt_group);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (led_regs_valid) {
|
||||
ret = devm_device_add_group(dev, &oxp_ec_group);
|
||||
if (ret)
|
||||
|
|
|
|||
24
uninstall.sh
24
uninstall.sh
|
|
@ -17,6 +17,11 @@ fi
|
|||
|
||||
echo "Stopping OXP fan service..."
|
||||
sudo systemctl disable --now oxp-fan-control.service 2>/dev/null || true
|
||||
sudo systemctl disable --now oxp-turbo-tdp.service 2>/dev/null || true
|
||||
sudo systemctl disable --now oxp-idle-power.service 2>/dev/null || true
|
||||
if [[ -x /usr/local/bin/oxp-cpu-profile ]]; then
|
||||
sudo /usr/local/bin/oxp-cpu-profile set balanced 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "Removing installed binaries..."
|
||||
for path in \
|
||||
|
|
@ -25,6 +30,10 @@ for path in \
|
|||
/usr/local/bin/oxp-rgb-hid \
|
||||
/usr/local/bin/oxp-rgb \
|
||||
/usr/local/bin/oxp-tdp \
|
||||
/usr/local/bin/oxp-turbo-tdp \
|
||||
/usr/local/bin/oxp-cpu-profile \
|
||||
/usr/local/sbin/oxp-idle-power \
|
||||
/usr/local/sbin/oxp-disable-fingerprint \
|
||||
/usr/local/bin/oxp-battery-probe \
|
||||
/usr/local/bin/oxp-battery-ec-probe
|
||||
do
|
||||
|
|
@ -34,9 +43,15 @@ done
|
|||
echo "Removing service and rules..."
|
||||
for path in \
|
||||
/etc/systemd/system/oxp-fan-control.service \
|
||||
/etc/systemd/system/oxp-turbo-tdp.service \
|
||||
/etc/systemd/system/oxp-idle-power.service \
|
||||
/usr/lib/systemd/system-sleep/oxp-idle-power \
|
||||
/etc/modules-load.d/oxp-sensors.conf \
|
||||
/etc/polkit-1/rules.d/49-oxp-fan-profile.rules \
|
||||
/etc/udev/rules.d/99-oxp-rgb-hid.rules
|
||||
/etc/udev/rules.d/99-oxp-rgb-hid.rules \
|
||||
/etc/udev/rules.d/80-oxp-disable-fingerprint.rules \
|
||||
/etc/NetworkManager/conf.d/10-wifi-powersave.conf \
|
||||
/etc/udev/hwdb.d/61-sensor-onexplayer-super-x.hwdb
|
||||
do
|
||||
sudo rm -f "$path"
|
||||
done
|
||||
|
|
@ -50,11 +65,18 @@ if [[ $PURGE -eq 1 ]]; then
|
|||
echo "Purging local configuration..."
|
||||
sudo rm -f /etc/oxp-fan-control.conf
|
||||
sudo rm -f "$TARGET_HOME/.config/oxp-control.json"
|
||||
sudo rm -f /var/lib/oxp-power-mode
|
||||
fi
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemd-hwdb update
|
||||
if command -v nmcli >/dev/null 2>&1; then
|
||||
sudo nmcli general reload conf || true
|
||||
fi
|
||||
sudo udevadm control --reload-rules || true
|
||||
sudo udevadm trigger --subsystem-match=iio --action=change || true
|
||||
sudo udevadm trigger --attr-match=idVendor=1a2c --attr-match=idProduct=b001 || true
|
||||
sudo systemctl try-restart iio-sensor-proxy.service
|
||||
sudo modprobe -r oxp-sensors 2>/dev/null || true
|
||||
|
||||
if command -v gnome-extensions >/dev/null 2>&1; then
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue