├── .editorconfig ├── .github ├── Bug_Reporting_Howto.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .perlcriticrc ├── .shellcheckrc ├── 00-template.conf ├── AUTHORS ├── COPYING ├── LICENSE ├── Makefile ├── README.d ├── README.rst ├── VERSION ├── bat.d ├── 05-thinkpad ├── 10-thinkpad-legacy ├── 15-lenovo ├── 20-huawei ├── 25-msi ├── 30-samsung ├── 35-lg ├── 36-lg-legacy ├── 40-sony ├── 45-system76 ├── 50-toshiba ├── 55-cros-ec ├── 56-framework ├── 60-macbook ├── 65-dell ├── 89-asus ├── 90-generic └── TEMPLATE ├── changelog ├── completion ├── bash │ ├── tlp-rdw.bash_completion │ └── tlp.bash_completion ├── fish │ ├── tlp-rdw.fish │ ├── tlp-stat.fish │ └── tlp.fish └── zsh │ ├── _tlp │ ├── _tlp-radio-device │ ├── _tlp-rdw │ ├── _tlp-run-on │ └── _tlp-stat ├── de.linrunner.tlp.metainfo.xml ├── defaults.conf ├── deprecated.conf ├── func.d ├── 05-tlp-func-pm ├── 10-tlp-func-cpu ├── 15-tlp-func-disk ├── 20-tlp-func-usb ├── 25-tlp-func-rf ├── 30-tlp-func-rf-sw ├── 35-tlp-func-batt ├── 40-tlp-func-bay ├── 45-tlp-func-gpu └── tlp-func-stat ├── man-rdw └── tlp-rdw.8 ├── man ├── bluetooth.1 ├── nfc.1 ├── run-on-ac.1 ├── run-on-bat.1 ├── tlp-stat.8 ├── tlp.8 ├── tlp.service.8 ├── wifi.1 └── wwan.1 ├── rename.conf ├── tlp-func-base.in ├── tlp-pcilist ├── tlp-rdw-nm.in ├── tlp-rdw-udev.in ├── tlp-rdw.in ├── tlp-rdw.rules.in ├── tlp-readconfs.in ├── tlp-rf.in ├── tlp-run-on.in ├── tlp-sleep ├── tlp-sleep.elogind ├── tlp-stat.in ├── tlp-usb-udev.in ├── tlp-usblist ├── tlp.conf.in ├── tlp.in ├── tlp.init ├── tlp.rules.in ├── tlp.service.in ├── tlp.upstart.in └── unit-tests ├── charge-thresholds_cros-ec-v2 ├── charge-thresholds_cros-ec-v3 ├── charge-thresholds_dell ├── charge-thresholds_macbook ├── charge-thresholds_msi ├── charge-thresholds_simulate1 ├── charge-thresholds_simulate2 ├── charge-thresholds_system76 ├── charge-thresholds_thinkpad ├── charge-thresholds_thinkpad-BAT1 ├── charge-thresholds_thinkpad-legacy ├── charge-thresholds_toshiba ├── kmod-helper ├── services-check ├── test-bc_all-simulate.sh ├── test-bc_all.sh ├── test-bc_cros-ec-all-simulate.sh ├── test-bc_cros-ec-v2.sh ├── test-bc_cros-ec-v3.sh ├── test-bc_dell-simulate.sh ├── test-bc_thinkpad.sh ├── test-cpufreq.sh ├── test-func ├── test-gpufreq.sh ├── test_all.sh └── unit-tests.rst /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [Makefile] 11 | indent_style = tab -------------------------------------------------------------------------------- /.github/Bug_Reporting_Howto.md: -------------------------------------------------------------------------------- 1 | ## How to submit a bug report 2 | 3 | ### Before you report a bug 4 | Make sure you have: 5 | 6 | * Followed the appropiate [Installation instructions](https://linrunner.de/tlp/installation) 7 | * Read the [Settings guide](https://linrunner.de/tlp/settings) 8 | * Carefully checked the [FAQ](https://linrunner.de/tlp/faq) 9 | * Checked [existing bug reports](https://github.com/linrunner/TLP/issues) 10 | * Tried to isolate the cause as described in [Troubleshooting](https://linrunner.de/tlp/support/troubleshooting.html) 11 | 12 | ### What not to report 13 | * **Outdated, missing or broken TLP packages. These are *not* provided by the project, 14 | but by the Linux distributions (exception: the Ubuntu PPA).** 15 | * Asking for help about installation, configuration and usage 16 | * Questions about your laptop's power consumption and how to optimize it 17 | * Deviations from powertop's recommendations 18 | * Hardware issues e.g. worn out or malfunctioning batteries 19 | 20 | Please use adequate Linux forums for help and support questions. 21 | 22 | ### Reporting a bug 23 | This project uses [GitHub issues](https://github.com/linrunner/TLP/issues) for bug reports and feature requests. 24 | 25 | When opening an issue, provide **all the information requested by the 26 | [template](https://github.com/linrunner/TLP/blob/master/.github/ISSUE_TEMPLATE/bug_report.md)**. 27 | Bug reports not providing the necessary information get flagged *incomplete* and 28 | may be closed without further notice. 29 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How can I contribute to TLP? 2 | 3 | Contributing is not only about coding and pull requests. Volunteers helping 4 | with support and testing are always welcome! 5 | 6 | Please read [Contribute](https://linrunner.de/tlp/contribute) for details. 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | [x] I've read and accepted the [Bug Reporting Howto](https://github.com/linrunner/TLP/blob/master/.github/Bug_Reporting_Howto.md) 11 | [x] I've provided all required `tlp-stat` outputs via [Gist](https://gist.github.com/) (see below) 12 | 13 | **Describe the bug** 14 | 15 | A clear and concise description of what the bug is. 16 | 17 | **Expected behavior** 18 | 19 | A clear and concise description of what you expected to happen. 20 | *"Works fine" is not enough to analyze the problem!* 21 | 22 | **To Reproduce** 23 | 24 | Steps to reproduce the unexpected behavior: 25 | 26 | 1. Does the problem occur on battery or AC or both? 27 | 2. Actions to reproduce the behaviour 28 | 3. Shell commands entered and their output *including error message(s)* 29 | 4. **Full output of `tlp-stat` via https://gist.github.com/ for *all* 30 | matching cases of 1** (not as file attachment, no screenshots) 31 | 32 | **Additional context** 33 | 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I repeatedly ran into [...] 13 | 14 | **Describe the solution you'd like** 15 | 16 | A clear and concise description of: 17 | 18 | * Your use case(s) 19 | * What you want to happen 20 | 21 | **Describe alternatives you've considered** 22 | 23 | A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | **Additional context** 26 | 27 | Add any other context and references about the feature request here: 28 | 29 | * New kernel interfaces: please provide links to the description and usage examples 30 | * Sample shell code 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debian 2 | *.geany 3 | .idea 4 | *.log 5 | PKGBUILD* 6 | pkg 7 | src 8 | tlp.install 9 | worktree* 10 | .zed 11 | *.zst 12 | -------------------------------------------------------------------------------- /.perlcriticrc: -------------------------------------------------------------------------------- 1 | [ValuesAndExpressions::ProhibitConstantPragma] 2 | severity = 1 3 | 4 | [InputOutput::RequireBriefOpen] 5 | severity = 1 6 | 7 | [Subroutines::RequireArgUnpacking] 8 | severity = 1 9 | -------------------------------------------------------------------------------- /.shellcheckrc: -------------------------------------------------------------------------------- 1 | # shellcheck config for TLP 2 | shell=dash 3 | disable=SC3043 4 | -------------------------------------------------------------------------------- /00-template.conf: -------------------------------------------------------------------------------- 1 | # 00-template.conf - Template for TLP drop-in customizations 2 | # See full explanation: https://linrunner.de/tlp/settings 3 | # 4 | # PARAMETER="value" 5 | # PARAMETER+="add value" 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Main author: 2 | Thomas Koch - 3 | 4 | Contributors: 5 | André Erdmann 6 | Pali Rohár 7 | https://github.com/linrunner/TLP/graphs/contributors 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Main Author: 2 | 3 | Thomas Koch 4 | 5 | Copyright: 6 | 7 | Copyright (c) 2025 Thomas Koch, André Erdmann, Pali Rohár 8 | 9 | See https://github.com/linrunner/TLP/ for additional contributors 10 | 11 | Some code and descriptions were adapted from: 12 | - laptop-mode-tools 13 | Copyright (c) 2004 by Bart Samwel, Kiko Piris, Micha Feigin, 14 | Andrew Morton, Herve Eychenne, Dax Kelson, Jan Topinski 15 | - https://thinkwiki.org 16 | 17 | Thinkpad ACPI Battery Control (tpacpi-bat): 18 | Copyright (c) 2011-2016 Elliot Wolk 19 | 20 | License: 21 | 22 | This software is licensed under the GPL v2 or later, 23 | see '/usr/share/common-licenses/GPL-2' 24 | 25 | tpacpi-bat is licensed under the GPL v3 or later, 26 | see '/usr/share/common-licenses/GPL-3' 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for TLP 2 | # Copyright (c) 2025 Thomas Koch and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | TLPVER := $(shell read _ver _dummy < ./VERSION; printf '%s' "$${_ver:-undef}") 5 | 6 | # Evaluate parameters 7 | TLP_SBIN ?= /usr/sbin 8 | TLP_BIN ?= /usr/bin 9 | TLP_TLIB ?= /usr/share/tlp 10 | TLP_FLIB ?= /usr/share/tlp/func.d 11 | TLP_ULIB ?= /usr/lib/udev 12 | TLP_BATD ?= /usr/share/tlp/bat.d 13 | TLP_NMDSP ?= /usr/lib/NetworkManager/dispatcher.d 14 | TLP_CONFUSR ?= /etc/tlp.conf 15 | TLP_CONFDIR ?= /etc/tlp.d 16 | TLP_CONFDEF ?= /usr/share/tlp/defaults.conf 17 | TLP_CONFREN ?= /usr/share/tlp/rename.conf 18 | TLP_CONFDPR ?= /usr/share/tlp/deprecated.conf 19 | TLP_CONF ?= /etc/default/tlp 20 | TLP_SYSD ?= /usr/lib/systemd/system 21 | TLP_SDSL ?= /usr/lib/systemd/system-sleep 22 | TLP_SYSV ?= /etc/init.d 23 | TLP_ELOD ?= /usr/lib/elogind/system-sleep 24 | TLP_SHCPL ?= /usr/share/bash-completion/completions 25 | TLP_ZSHCPL ?= /usr/share/zsh/site-functions 26 | TLP_FISHCPL ?= /usr/share/fish/vendor_completions.d 27 | TLP_MAN ?= /usr/share/man 28 | TLP_META ?= /usr/share/metainfo 29 | TLP_RUN ?= /run/tlp 30 | TLP_VAR ?= /var/lib/tlp 31 | 32 | # Catenate DESTDIR to paths 33 | _SBIN = $(DESTDIR)$(TLP_SBIN) 34 | _BIN = $(DESTDIR)$(TLP_BIN) 35 | _TLIB = $(DESTDIR)$(TLP_TLIB) 36 | _FLIB = $(DESTDIR)$(TLP_FLIB) 37 | _ULIB = $(DESTDIR)$(TLP_ULIB) 38 | _BATD = $(DESTDIR)$(TLP_BATD) 39 | _NMDSP = $(DESTDIR)$(TLP_NMDSP) 40 | _CONFUSR = $(DESTDIR)$(TLP_CONFUSR) 41 | _CONFDIR = $(DESTDIR)$(TLP_CONFDIR) 42 | _CONFDEF = $(DESTDIR)$(TLP_CONFDEF) 43 | _CONFREN = $(DESTDIR)$(TLP_CONFREN) 44 | _CONFDPR = $(DESTDIR)$(TLP_CONFDPR) 45 | _CONF = $(DESTDIR)$(TLP_CONF) 46 | _SYSD = $(DESTDIR)$(TLP_SYSD) 47 | _SDSL = $(DESTDIR)$(TLP_SDSL) 48 | _SYSV = $(DESTDIR)$(TLP_SYSV) 49 | _ELOD = $(DESTDIR)$(TLP_ELOD) 50 | _SHCPL = $(DESTDIR)$(TLP_SHCPL) 51 | _ZSHCPL = $(DESTDIR)$(TLP_ZSHCPL) 52 | _FISHCPL = $(DESTDIR)$(TLP_FISHCPL) 53 | _MAN = $(DESTDIR)$(TLP_MAN) 54 | _META = $(DESTDIR)$(TLP_META) 55 | _RUN = $(DESTDIR)$(TLP_RUN) 56 | _VAR = $(DESTDIR)$(TLP_VAR) 57 | 58 | SED = sed \ 59 | -e "s|@TLPVER@|$(TLPVER)|g" \ 60 | -e "s|@TLP_SBIN@|$(TLP_SBIN)|g" \ 61 | -e "s|@TLP_TLIB@|$(TLP_TLIB)|g" \ 62 | -e "s|@TLP_FLIB@|$(TLP_FLIB)|g" \ 63 | -e "s|@TLP_ULIB@|$(TLP_ULIB)|g" \ 64 | -e "s|@TLP_BATD@|$(TLP_BATD)|g" \ 65 | -e "s|@TLP_CONFUSR@|$(TLP_CONFUSR)|g" \ 66 | -e "s|@TLP_CONFDIR@|$(TLP_CONFDIR)|g" \ 67 | -e "s|@TLP_CONFDEF@|$(TLP_CONFDEF)|g" \ 68 | -e "s|@TLP_CONFREN@|$(TLP_CONFREN)|g" \ 69 | -e "s|@TLP_CONFDPR@|$(TLP_CONFDPR)|g" \ 70 | -e "s|@TLP_CONF@|$(TLP_CONF)|g" \ 71 | -e "s|@TLP_RUN@|$(TLP_RUN)|g" \ 72 | -e "s|@TLP_VAR@|$(TLP_VAR)|g" 73 | 74 | INFILES = \ 75 | tlp \ 76 | tlp.conf \ 77 | tlp-func-base \ 78 | tlp-rdw-nm \ 79 | tlp-rdw.rules \ 80 | tlp-rdw-udev \ 81 | tlp-rdw \ 82 | tlp-rf \ 83 | tlp.rules \ 84 | tlp-readconfs \ 85 | tlp-run-on \ 86 | tlp.service \ 87 | tlp-stat \ 88 | tlp.upstart \ 89 | tlp-usb-udev 90 | 91 | MANFILES1 = \ 92 | bluetooth.1 \ 93 | nfc.1 \ 94 | run-on-ac.1 \ 95 | run-on-bat.1 \ 96 | wifi.1 \ 97 | wwan.1 98 | 99 | MANFILES8 = \ 100 | tlp.8 \ 101 | tlp-stat.8 \ 102 | tlp.service.8 103 | 104 | MANFILESRDW8 = \ 105 | tlp-rdw.8 106 | 107 | SHFILES = \ 108 | tlp.in \ 109 | tlp-func-base.in \ 110 | func.d/* \ 111 | bat.d/* \ 112 | tlp-rdw.in \ 113 | tlp-rdw-nm.in \ 114 | tlp-rdw-udev.in \ 115 | tlp-rf.in \ 116 | tlp-run-on.in \ 117 | tlp-sleep \ 118 | tlp-sleep.elogind \ 119 | tlp-stat.in \ 120 | tlp-usb-udev.in \ 121 | 122 | UTSHFILES = \ 123 | unit-tests/test-func \ 124 | unit-tests/test-*.sh 125 | 126 | PLFILES = \ 127 | tlp-pcilist \ 128 | tlp-readconfs.in \ 129 | tlp-usblist 130 | 131 | BATDRVFILES = $(foreach drv,$(wildcard bat.d/[0-9][0-9]-[a-z]*),$(drv)~) 132 | 133 | # Make targets 134 | all: $(INFILES) 135 | 136 | $(INFILES): %: %.in 137 | $(SED) $< > $@ 138 | 139 | clean: 140 | rm -f $(INFILES) 141 | rm -f bat.d/*~ 142 | 143 | install-tlp: all 144 | # Package tlp 145 | install -D -m 755 tlp $(_SBIN)/tlp 146 | install -D -m 755 tlp-rf $(_BIN)/bluetooth 147 | ln -sf bluetooth $(_BIN)/nfc 148 | ln -sf bluetooth $(_BIN)/wifi 149 | ln -sf bluetooth $(_BIN)/wwan 150 | install -m 755 tlp-run-on $(_BIN)/run-on-ac 151 | ln -sf run-on-ac $(_BIN)/run-on-bat 152 | install -m 755 tlp-stat $(_BIN)/ 153 | install -D -m 755 -t $(_TLIB)/func.d func.d/* 154 | install -m 755 tlp-func-base $(_TLIB)/ 155 | install -D -m 755 -t $(_TLIB)/bat.d bat.d/* 156 | install -m 755 tlp-pcilist $(_TLIB)/ 157 | install -m 755 tlp-readconfs $(_TLIB)/ 158 | install -m 755 tlp-usblist $(_TLIB)/ 159 | install -D -m 755 tlp-usb-udev $(_ULIB)/tlp-usb-udev 160 | install -D -m 644 tlp.rules $(_ULIB)/rules.d/85-tlp.rules 161 | [ -f $(_CONFUSR) ] || install -D -m 644 tlp.conf $(_CONFUSR) 162 | install -d $(_CONFDIR) 163 | install -D -m 644 README.d $(_CONFDIR)/README 164 | install -D -m 644 00-template.conf $(_CONFDIR)/00-template.conf 165 | install -D -m 644 defaults.conf $(_CONFDEF) 166 | install -D -m 644 rename.conf $(_CONFREN) 167 | install -D -m 644 deprecated.conf $(_CONFDPR) 168 | ifneq ($(TLP_NO_INIT),1) 169 | install -D -m 755 tlp.init $(_SYSV)/tlp 170 | endif 171 | ifneq ($(TLP_WITH_SYSTEMD),0) 172 | install -D -m 644 tlp.service $(_SYSD)/tlp.service 173 | install -D -m 755 tlp-sleep $(_SDSL)/tlp 174 | endif 175 | ifneq ($(TLP_WITH_ELOGIND),0) 176 | install -D -m 755 tlp-sleep.elogind $(_ELOD)/49-tlp-sleep 177 | endif 178 | ifneq ($(TLP_NO_BASHCOMP),1) 179 | install -D -m 644 completion/bash/tlp.bash_completion $(_SHCPL)/tlp 180 | ln -sf tlp $(_SHCPL)/tlp-stat 181 | ln -sf tlp $(_SHCPL)/bluetooth 182 | ln -sf tlp $(_SHCPL)/nfc 183 | ln -sf tlp $(_SHCPL)/wifi 184 | ln -sf tlp $(_SHCPL)/wwan 185 | ln -sf tlp $(_SHCPL)/run-on-ac 186 | ln -sf tlp $(_SHCPL)/run-on-bat 187 | endif 188 | ifneq ($(TLP_NO_ZSHCOMP),1) 189 | install -D -m 644 completion/zsh/_tlp $(_ZSHCPL)/_tlp 190 | install -D -m 644 completion/zsh/_tlp-radio-device $(_ZSHCPL)/_tlp-radio-device 191 | install -D -m 644 completion/zsh/_tlp-run-on $(_ZSHCPL)/_tlp-run-on 192 | install -D -m 644 completion/zsh/_tlp-stat $(_ZSHCPL)/_tlp-stat 193 | endif 194 | ifneq ($(TLP_NO_FISHCOMP),1) 195 | install -D -m 644 completion/fish/tlp.fish $(_FISHCPL)/tlp.fish 196 | install -D -m 644 completion/fish/tlp-stat.fish $(_FISHCPL)/tlp-stat.fish 197 | ln -sf tlp.fish $(_FISHCPL)/bluetooth.fish 198 | ln -sf tlp.fish $(_FISHCPL)/nfc.fish 199 | ln -sf tlp.fish $(_FISHCPL)/wifi.fish 200 | ln -sf tlp.fish $(_FISHCPL)/wwan.fish 201 | ln -sf tlp.fish $(_FISHCPL)/run-on-ac.fish 202 | ln -sf tlp.fish $(_FISHCPL)/run-on-bat.fish 203 | endif 204 | install -D -m 644 de.linrunner.tlp.metainfo.xml $(_META)/de.linrunner.tlp.metainfo.xml 205 | install -d -m 755 $(_VAR) 206 | 207 | install-rdw: all 208 | # Package tlp-rdw 209 | install -D -m 755 tlp-rdw $(_BIN)/tlp-rdw 210 | install -D -m 644 tlp-rdw.rules $(_ULIB)/rules.d/85-tlp-rdw.rules 211 | install -D -m 755 tlp-rdw-udev $(_ULIB)/tlp-rdw-udev 212 | install -D -m 755 tlp-rdw-nm $(_NMDSP)/99tlp-rdw-nm 213 | ifneq ($(TLP_NO_BASHCOMP),1) 214 | install -D -m 644 completion/bash/tlp-rdw.bash_completion $(_SHCPL)/tlp-rdw 215 | endif 216 | ifneq ($(TLP_NO_ZSHCOMP),1) 217 | install -D -m 644 completion/zsh/_tlp-rdw $(_ZSHCPL)/_tlp-rdw 218 | endif 219 | ifneq ($(TLP_NO_FISHCOMP),1) 220 | install -D -m 644 completion/fish/tlp-rdw.fish $(_FISHCPL)/tlp-rdw.fish 221 | endif 222 | 223 | install-man-tlp: 224 | # manpages 225 | install -d -m 755 $(_MAN)/man1 226 | cd man && install -m 644 $(MANFILES1) $(_MAN)/man1/ 227 | install -d -m 755 $(_MAN)/man8 228 | cd man && install -m 644 $(MANFILES8) $(_MAN)/man8/ 229 | 230 | install-man-rdw: 231 | # manpages 232 | install -d -m 755 $(_MAN)/man8 233 | cd man-rdw && install -m 644 $(MANFILESRDW8) $(_MAN)/man8/ 234 | 235 | install: install-tlp install-rdw 236 | 237 | install-man: install-man-tlp install-man-rdw 238 | 239 | uninstall-tlp: 240 | # Package tlp 241 | rm $(_SBIN)/tlp 242 | rm $(_BIN)/bluetooth 243 | rm $(_BIN)/nfc 244 | rm $(_BIN)/wifi 245 | rm $(_BIN)/wwan 246 | rm $(_BIN)/run-on-ac 247 | rm $(_BIN)/run-on-bat 248 | rm $(_BIN)/tlp-stat 249 | rm $(_CONFDIR)/README 250 | rm $(_CONFDIR)/00-template.conf 251 | rm -r $(_TLIB) 252 | rm $(_ULIB)/tlp-usb-udev 253 | rm $(_ULIB)/rules.d/85-tlp.rules 254 | rm -f $(_SYSV)/tlp 255 | rm -f $(_SYSD)/tlp.service 256 | rm -f $(_SDSL)/tlp-sleep 257 | rm -f $(_ELOD)/49-tlp-sleep 258 | rm -f $(_SHCPL)/tlp 259 | rm -f $(_SHCPL)/tlp-stat 260 | rm -f $(_SHCPL)/bluetooth 261 | rm -f $(_SHCPL)/nfc 262 | rm -f $(_SHCPL)/wifi 263 | rm -f $(_SHCPL)/wwan 264 | rm -f $(_SHCPL)/run-on-ac 265 | rm -f $(_SHCPL)/run-on-bat 266 | rm -f $(_ZSHCPL)/_tlp 267 | rm -f $(_ZSHCPL)/_tlp-radio-device 268 | rm -f $(_ZSHCPL)/_tlp-run-on 269 | rm -f $(_ZSHCPL)/_tlp-stat 270 | rm -f $(_FISHCPL)/tlp.fish 271 | rm -f $(_FISHCPL)/tlp-stat.fish 272 | rm -f $(_FISHCPL)/bluetooth.fish 273 | rm -f $(_FISHCPL)/nfc.fish 274 | rm -f $(_FISHCPL)/wifi.fish 275 | rm -f $(_FISHCPL)/wwan.fish 276 | rm -f $(_FISHCPL)/run-on-ac.fish 277 | rm -f $(_FISHCPL)/run-on-bat.fish 278 | rm -f $(_META)/de.linrunner.tlp.metainfo.xml 279 | rm -r $(_VAR) 280 | 281 | uninstall-rdw: 282 | # Package tlp-rdw 283 | rm $(_BIN)/tlp-rdw 284 | rm $(_ULIB)/rules.d/85-tlp-rdw.rules 285 | rm $(_ULIB)/tlp-rdw-udev 286 | rm $(_NMDSP)/99tlp-rdw-nm 287 | rm -f $(_SHCPL)/tlp-rdw 288 | rm -f $(_ZSHCPL)/_tlp-rdw 289 | rm -f $(_FISHCPL)/tlp-rdw.fish 290 | 291 | uninstall-man-tlp: 292 | # manpages 293 | cd $(_MAN)/man1 && rm -f $(MANFILES1) 294 | cd $(_MAN)/man8 && rm -f $(MANFILES8) 295 | 296 | uninstall-man-rdw: 297 | # manpages 298 | cd $(_MAN)/man8 && rm -f $(MANFILESRDW8) 299 | 300 | uninstall: uninstall-tlp uninstall-rdw 301 | 302 | uninstall-man: uninstall-man-tlp uninstall-man-rdw 303 | 304 | checkall: checkbashisms shellcheck perlcritic checkdupconst checkbatdrv checkwip 305 | 306 | checkbashisms: 307 | @echo "*** checkbashisms ***************************************************************************" 308 | @{ checkbashisms $(SHFILES) 2>&1 | sed -e '/test with unary -a (should be -e)/{N;d;}'; } || true 309 | 310 | shellcheck: 311 | @echo "*** shellcheck ******************************************************************************" 312 | @shellcheck -s dash $(SHFILES) $(UTSHFILES) || true 313 | 314 | perlcritic: 315 | @echo "*** perlcritic ******************************************************************************" 316 | @perlcritic --severity 4 --verbose "%F: [%p] %m at line %l, column %c. (Severity: %s)\n" $(PLFILES) || true 317 | 318 | checkdupconst: 319 | @echo "*** checkdupconst ***************************************************************************" 320 | @{ sed -n -r -e 's,^.*readonly\s+([A-Za-z_][A-Za-z_0-9]*)=.*$$,\1,p' $(SHFILES) | sort | uniq -d; } || true 321 | 322 | checkwip: 323 | @echo "*** checkwip ********************************************************************************" 324 | @grep -E -n "### (DEBUG|DEVEL|TODO|WIP)" $(SHFILES) $(UTSHFILES) $(PLFILES) || true 325 | 326 | bat.d/TEMPLATE~: bat.d/TEMPLATE 327 | @awk '/^batdrv_[a-z_]+ ()/ { print $$1; }' $< | grep -v 'batdrv_is' | sort > $@ 328 | 329 | bat.d/%~: bat.d/% 330 | @printf "*** checkbatdrv %-25s ***********************************************\n" "$<" 331 | @awk '/^batdrv_[a-z_]+ ()/ { print $$1; }' $< | grep -v -E 'batdrv_(is|has)' | sort > $@ 332 | @diff -U 1 -s bat.d/TEMPLATE~ $@ || true 333 | 334 | checkbatdrv: bat.d/TEMPLATE~ $(BATDRVFILES) 335 | rm -f bat.d/*~ 336 | -------------------------------------------------------------------------------- /README.d: -------------------------------------------------------------------------------- 1 | This directory is intended to contain drop-in customizations for TLP. 2 | See full explanation: https://linrunner.de/tlp/settings 3 | 4 | The naming scheme is 00-name.conf, the files are read in lexical (aphabetical) 5 | order. 6 | 7 | You may also use /etc/tlp.conf directly, which will override any settings in 8 | this directory. 9 | 10 | After making changes, run 'tlp start' to activate them without reboot. 11 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TLP - Optimize Linux Laptop Battery Life 2 | ======================================== 3 | TLP is a feature-rich command line utility for Linux, saving laptop battery power 4 | without the need to delve deeper into technical details. 5 | 6 | TLP’s default settings are already optimized for battery life, so you may just 7 | install and forget it. 8 | 9 | Nevertheless TLP is highly customizable to meet your specific requirements. 10 | 11 | Documentation 12 | ------------- 13 | Read the full documentation at the website ``_. 14 | 15 | For a summary of how TLP works and its features see 16 | `Introduction `_. 17 | 18 | Installation 19 | ------------ 20 | TLP packages are available for all major Linux distributions: 21 | `Installation `_. 22 | 23 | Settings 24 | -------- 25 | Settings are organized into two profiles, enabling you to adjust between savings 26 | and performance independently for battery (BAT) and AC operation. 27 | 28 | Refer to `Settings `_ to learn 29 | how to customize the configuration if desired. 30 | 31 | Support 32 | ------- 33 | Please visit your favorite Linux community for help and support questions. 34 | Make shure to check `Support `_ first. 35 | 36 | Bug reports 37 | ----------- 38 | Refer to the 39 | `Bug Reporting Howto `_. 40 | 41 | Contribute 42 | ---------- 43 | Contributing is not only about coding. Volunteers helping with support, testing 44 | and documentation are always welcome! 45 | 46 | See `Contributing `_. 47 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.9.0-alpha.0 2 | -------------------------------------------------------------------------------- /bat.d/90-generic: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 90-generic - Battery plugin catchall for laptops that either not provide 3 | # a kernel interface for battery care or TLP doesn't support it yet. 4 | # 5 | # Copyright (c) 2025 Thomas Koch and others. 6 | # SPDX-License-Identifier: GPL-2.0-or-later 7 | 8 | # Needs: tlp-func-base, 35-tlp-func-batt, tlp-func-stat 9 | 10 | batdrv_init () { 11 | # detect hardware and initialize driver 12 | # rc: 0 (catchall) 13 | # retval: $_batdrv_plugin 14 | # 15 | # 1. determine present batteries 16 | # list of batteries (space separated) --> retval $_batteries; 17 | # 18 | # 2. designate battery care as unsupported 19 | # reading battery data --> retval $_bm_read = "none", 20 | # reading/writing charging thresholds --> retval $_bm_thresh = "none", 21 | # reading/writing force discharge --> retval $_bm_dischg = "none": 22 | 23 | _batdrv_plugin="generic" 24 | 25 | # iterate batteries 26 | local bs bd 27 | _batteries="" 28 | for bd in "$ACPIBATDIR"/*; do 29 | if [ "$(read_sysf "$bd/type")" = "Battery" ] \ 30 | && [ "$(read_sysf "$bd/present")" = "1" ]; then 31 | bs=${bd##/*/} 32 | # ignore atypical power supplies and batteries 33 | printf '%s\n' "$bs" | grep -E -q "$RE_PS_IGNORE" && continue 34 | # record detected batteries and directories 35 | if [ -n "$_batteries" ]; then 36 | _batteries="$_batteries $bs" 37 | else 38 | _batteries="$bs" 39 | fi 40 | fi 41 | done 42 | 43 | # shellcheck disable=SC2034 44 | _bm_read="none" 45 | # shellcheck disable=SC2034 46 | _bm_thresh="none" 47 | # shellcheck disable=SC2034 48 | _bm_dischg="none" 49 | 50 | # shellcheck disable=SC2034 51 | _batdrv_selected=$_batdrv_plugin 52 | echo_debug "bat" "batdrv_init.${_batdrv_plugin}: batteries=$_batteries" 53 | # catchall: always return 0 54 | return 0 55 | } 56 | 57 | batdrv_select_battery () { 58 | # determine battery acpidir 59 | # $1: battery 60 | # retval: $_bd_read: directory with battery data sysfiles 61 | 62 | local bat="$1" 63 | 64 | # convert battery param to uppercase 65 | bat="$(printf '%s' "$1" | tr "[:lower:]" "[:upper:]")" 66 | 67 | _bd_read="$ACPIBATDIR/$bat" 68 | return 0 69 | } 70 | 71 | batdrv_read_threshold () { 72 | # function not implemented for generic hardware 73 | echo_debug "bat" "batdrv.${_batdrv_plugin}.read_treshold.not_implemented" 74 | return 255 75 | } 76 | 77 | batdrv_write_thresholds () { 78 | # function not implemented for generic hardware 79 | echo_debug "bat" "batdrv.${_batdrv_plugin}.write_tresholds.not_implemented" 80 | return 255 81 | } 82 | 83 | batdrv_calc_soc () { 84 | # function not implemented as not required 85 | return 255 86 | } 87 | 88 | batdrv_chargeonce () { 89 | # function not implemented for generic hardware 90 | echo_debug "bat" "batdrv.${_batdrv_plugin}.chargeonce.not_implemented" 91 | return 255 92 | } 93 | 94 | batdrv_apply_configured_thresholds () { 95 | # function not implemented for generic hardware 96 | echo_debug "bat" "batdrv.${_batdrv_plugin}.apply_configured_thresholds.not_implemented" 97 | return 255 98 | } 99 | 100 | batdrv_read_force_discharge () { 101 | # function not implemented for generic hardware 102 | echo_debug "bat" "batdrv.${_batdrv_plugin}.read_force_discharge.not_implemented" 103 | return 255 104 | } 105 | 106 | batdrv_write_force_discharge () { 107 | # function not implemented for generic hardware 108 | echo_debug "bat" "batdrv.${_batdrv_plugin}.write_force_discharge.not_implemented" 109 | return 255 110 | } 111 | 112 | batdrv_cancel_force_discharge () { 113 | # function not implemented for generic hardware 114 | echo_debug "bat" "batdrv.${_batdrv_plugin}.cancel_force_discharge.not_implemented" 115 | return 255 116 | } 117 | 118 | batdrv_force_discharge_active () { 119 | # function not implemented for generic hardware 120 | echo_debug "bat" "batdrv.${_batdrv_plugin}.force_discharge_active.not_implemented" 121 | return 255 122 | } 123 | 124 | batdrv_discharge_safetylock () { 125 | # check safety lock - force-discharge not implemented for generic hardware 126 | # $1: discharge/recalibrate 127 | # rc: 0=engaged/1=disengaged 128 | 129 | return 1 130 | } 131 | 132 | batdrv_discharge () { 133 | # function not implemented for generic hardware 134 | 135 | # Important: release lock from caller 136 | unlock_tlp tlp_discharge 137 | 138 | echo_debug "bat" "batdrv.${_batdrv_plugin}.discharge.not_implemented" 139 | return 255 140 | } 141 | 142 | batdrv_show_battery_data () { # output battery data 143 | # $1: 1=verbose 144 | # global param: $_batteries 145 | # rc: 0=ok/1=no batteries specified 146 | local verbose=${1:-0} 147 | 148 | printf "+++ Battery Care\n" 149 | printf "Plugin: %s\n" "$_batdrv_plugin" 150 | cprintf "warning" "Supported features: none available\n" 151 | printf "\n" 152 | 153 | local bat lf 154 | local bcnt=0 155 | local ed ef en 156 | local efsum=0 157 | local ensum=0 158 | 159 | if [ -z "$_batteries" ]; then 160 | printf "+++ Battery Status\n" 161 | cprintf "warning" "No batteries detected.\n" 162 | printf "\n" 163 | return 1 164 | fi 165 | 166 | for bat in $_batteries; do # iterate batteries 167 | batdrv_select_battery "$bat" 168 | 169 | printf "+++ Battery Status: %s\n" "$bat" 170 | 171 | printparm "%-59s = ##%s##" "$_bd_read/manufacturer" 172 | printparm "%-59s = ##%s##" "$_bd_read/model_name" 173 | 174 | print_battery_cycle_count "$_bd_read/cycle_count" "$(read_sysf "$_bd_read/cycle_count")" 175 | 176 | if [ -f "$_bd_read/energy_full" ]; then 177 | printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_full_design" "" 000 178 | printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_full" "" 000 179 | printparm "%-59s = ##%6d## [mWh]" "$_bd_read/energy_now" "" 000 180 | printparm "%-59s = ##%6d## [mW]" "$_bd_read/power_now" "" 000 181 | 182 | # store values for charge / capacity calculation below 183 | ed=$(read_sysval "$_bd_read/energy_full_design") 184 | ef=$(read_sysval "$_bd_read/energy_full") 185 | en=$(read_sysval "$_bd_read/energy_now") 186 | efsum=$((efsum + ef)) 187 | ensum=$((ensum + en)) 188 | 189 | elif [ -f "$_bd_read/charge_full" ]; then 190 | printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_full_design" "" 000 191 | printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_full" "" 000 192 | printparm "%-59s = ##%6d## [mAh]" "$_bd_read/charge_now" "" 000 193 | printparm "%-59s = ##%6d## [mA]" "$_bd_read/current_now" "" 000 194 | 195 | # store values for charge / capacity calculation below 196 | ed=$(read_sysval "$_bd_read/charge_full_design") 197 | ef=$(read_sysval "$_bd_read/charge_full") 198 | en=$(read_sysval "$_bd_read/charge_now") 199 | efsum=$((efsum + ef)) 200 | ensum=$((ensum + en)) 201 | 202 | else 203 | ed=0 204 | ef=0 205 | en=0 206 | fi 207 | 208 | print_batstate "$_bd_read/status" 209 | printf "\n" 210 | 211 | if [ "$verbose" -eq 1 ]; then 212 | printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_min_design" "" 000 213 | printparm "%-59s = ##%6s## [mV]" "$_bd_read/voltage_now" "" 000 214 | printf "\n" 215 | fi 216 | 217 | printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_start_threshold" "not available" 218 | printparm "%-59s = ##%6d## [%%]" "$_bd_read/charge_control_end_threshold" "not available" 219 | printparm "%-59s = ##%6s## [mV]" "$_bd_read/charge_behaviour" "not available" 220 | printf "\n" 221 | 222 | # charge + capacity 223 | lf=0 224 | if [ "$ef" -ne 0 ]; then 225 | perl -e 'printf ("%-59s = %6.1f [%%]\n", "Charge", 100.0 * '"$en"' / '"$ef"');' 226 | lf=1 227 | fi 228 | if [ "$ed" -ne 0 ]; then 229 | perl -e 'printf ("%-59s = %6.1f [%%]\n", "Capacity", 100.0 * '"$ef"' / '"$ed"');' 230 | lf=1 231 | fi 232 | [ "$lf" -gt 0 ] && printf "\n" 233 | bcnt=$((bcnt+1)) 234 | 235 | done 236 | 237 | if [ $bcnt -gt 1 ] && [ $efsum -ne 0 ]; then 238 | # more than one battery detected --> show charge total 239 | perl -e 'printf ("%-59s = %6.1f [%%]\n", "+++ Charge total", 100.0 * '"$ensum"' / '"$efsum"');' 240 | printf "\n" 241 | fi 242 | 243 | return 0 244 | } 245 | 246 | batdrv_check_soc_gt_stop () { 247 | # function not implemented for generic hardware 248 | 249 | return 1 250 | } 251 | 252 | batdrv_recommendations () { 253 | # no recommendations for generic hardware 254 | 255 | return 0 256 | } 257 | -------------------------------------------------------------------------------- /completion/bash/tlp-rdw.bash_completion: -------------------------------------------------------------------------------- 1 | # bash completion for TLP-RDW 2 | # Copyright (c) 2025 Thomas Koch and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | _tlp_rdw() { 6 | local cur prev words cword opts 7 | _init_completion || return 8 | 9 | opts="enable disable --version" 10 | 11 | if [ $cword -eq 1 ]; then 12 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 13 | return 0 14 | fi 15 | } && 16 | complete -F _tlp_rdw tlp-rdw 17 | -------------------------------------------------------------------------------- /completion/bash/tlp.bash_completion: -------------------------------------------------------------------------------- 1 | # bash completion for TLP 2 | # Copyright (c) 2025 Thomas Koch and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | _batteries() { 6 | # show list of batteries 7 | local bats b 8 | 9 | bats=$( { 10 | for b in /sys/class/power_supply/*; do 11 | if echo "$b" | grep -E -v -q "hid" \ 12 | && [ "$(cat $b/present 2> /dev/null)" = "1" ] \ 13 | && [ "$(cat $b/type)" = "Battery" ]; then 14 | echo "${b##/*/} " 15 | fi 16 | done } ) 17 | 18 | if [ -n "$bats" ]; then 19 | COMPREPLY=( $(compgen -W "${bats}" -- ${cur}) ) 20 | fi 21 | } 22 | 23 | _target_level() { 24 | local thresh 25 | 26 | if thresh="$(cat /sys/class/power_supply/${COMP_WORDS[2]}/charge_control_end_threshold 2> /dev/null)" \ 27 | || thresh="$(cat /sys/devices/platform/smapi/${COMP_WORDS[2]}/stop_charge_thresh 2> /dev/null)"; then 28 | COMPREPLY=( $(compgen -W "$thresh" -- ${cur}) ) 29 | fi 30 | } 31 | 32 | _tlp() { 33 | local cur prev words cword opts bats 34 | _init_completion || return 35 | 36 | opts="start ac bat usb bayoff discharge setcharge fullcharge chargeonce recalibrate diskid --version" 37 | 38 | case $cword in 39 | 1) 40 | # subcmds only 41 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 42 | return 0 43 | ;; 44 | 45 | 2) 46 | case "${prev}" in 47 | fullcharge|chargeonce|recalibrate) 48 | _batteries 49 | return 0 50 | ;; 51 | 52 | discharge) 53 | _batteries 54 | return 0 55 | ;; 56 | esac 57 | ;; 58 | 59 | 3) 60 | if [ "${COMP_WORDS[1]}" = "discharge" ]; then 61 | _target_level 62 | return 0 63 | fi 64 | ;; 65 | 66 | 4) 67 | if [ "${COMP_WORDS[1]}" = "setcharge" ]; then 68 | _batteries 69 | return 0 70 | fi 71 | ;; 72 | esac 73 | } && 74 | complete -F _tlp tlp 75 | 76 | _tlp_rf() { 77 | local cur prev words cword opts 78 | _init_completion || return 79 | 80 | opts="on off toggle --version" 81 | 82 | if [ $cword -eq 1 ]; then 83 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 84 | return 0 85 | fi 86 | } && 87 | complete -F _tlp_rf bluetooth nfc wwan wifi 88 | 89 | _tlp_stat() { 90 | local cur prev words cword opts 91 | _init_completion || return 92 | 93 | opts="--battery --cdiff --config --disk --graphics --mode --pcie --pev 94 | --processor --psup --quiet --rfkill --system --temp --trace --udev 95 | --usb --verbose --warn" 96 | 97 | if [ $cword -eq 1 ]; then 98 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 99 | return 0 100 | fi 101 | } && 102 | complete -F _tlp_stat tlp-stat 103 | 104 | complete -F _command run-on-ac run-on-bat 105 | -------------------------------------------------------------------------------- /completion/fish/tlp-rdw.fish: -------------------------------------------------------------------------------- 1 | # Fish shell completion for tlp-rdw 2 | 3 | set -l tlp_rdw_commands enable disable 4 | 5 | complete -c tlp-rdw -f 6 | complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -a enable -d 'Enable RDW actions' 7 | complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -a disable -d 'Disable RDW actions' 8 | complete -c tlp-rdw -n "not __fish_seen_subcommand_from $tlp_rdw_commands" -l version -d 'Print TLP version' 9 | -------------------------------------------------------------------------------- /completion/fish/tlp-stat.fish: -------------------------------------------------------------------------------- 1 | # Fish shell completion for tlp-stat 2 | 3 | complete -c tlp-stat -f 4 | complete -c tlp-stat -s b -l battery -d 'View battery data' 5 | complete -c tlp-stat -s c -l config -d 'View active configuration' 6 | complete -c tlp-stat -s d -l disk -d 'View disk device information' 7 | complete -c tlp-stat -s e -l pcie -d 'View PCIe device information' 8 | complete -c tlp-stat -s g -l graphics -d 'View graphics card information' 9 | complete -c tlp-stat -s m -l mode -d 'View tlp status information' 10 | complete -c tlp-stat -s p -l processor -d 'View processor information' 11 | complete -c tlp-stat -s q -l quiet -d 'Show less information' 12 | complete -c tlp-stat -s r -l rfkill -d 'View radio device states' 13 | complete -c tlp-stat -s s -l system -d 'View system information and TLP status' 14 | complete -c tlp-stat -s t -l temp -d 'View temperatures and fan speed' 15 | complete -c tlp-stat -s u -l usb -d 'View USB device information' 16 | complete -c tlp-stat -s w -l warn -d 'View warnings about SATA disks' 17 | complete -c tlp-stat -s v -l verbose -d 'Show more information' 18 | complete -c tlp-stat -s P -l psup -d 'View power supply diagnostics' 19 | complete -c tlp-stat -s T -l trace -d 'View trace output' 20 | complete -c tlp-stat -l cdiff -d 'View difference between defaults and user configuration' 21 | complete -c tlp-stat -l pev -d 'Monitor power supply udev events' 22 | complete -c tlp-stat -l udev -d 'Check if udev rules for power source changes and connecting USB devices are active' 23 | complete -c tlp-stat -l version -d 'Print TLP version' 24 | -------------------------------------------------------------------------------- /completion/fish/tlp.fish: -------------------------------------------------------------------------------- 1 | # Fish shell completion for tlp, radio device command: bluetooth nfc wifi wwan, and run-on command: run-on-ac run-on-bat 2 | 3 | set -l tlp_commands start bat ac usb bayoff setcharge fullcharge discharge recalibrate chargeonce diskid 4 | set -l tlp_rf_devices bluetooth nfc wifi wwan 5 | set -l tlp_rf_devices_commands on off toggle 6 | set -l runon_commands run-on-ac run-on-bat 7 | 8 | set -l current_command (status basename | path change-extension '') 9 | 10 | if test $current_command = "tlp" 11 | set -l bats 12 | 13 | for b in /sys/class/power_supply/* 14 | if not string match -q -r hid $b; and test -f $b/present; and test (cat $b/present) = "1"; and test (cat $b/type) = "Battery" 15 | set -a bats (path basename $b) 16 | end 17 | end 18 | 19 | complete -c tlp -f 20 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a start -d "Start tlp and apply power saving profile for the actual power source" 21 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a bat -d "Apply battery profile and enter manual mode" 22 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a ac -d "Apply AC profile and enter manual mode" 23 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a usb -d "Enable autosuspend for all USB devices except excluded" 24 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a bayoff -d "Turn off optical drive in UltraBay/MediaBay" 25 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a setcharge -d "Change charge thresholds temporarily" 26 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a fullcharge -d "Charge battery to full capacity" 27 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a chargeonce -d "Charge battery to the stop charge threshold once (ThinkPads only)" 28 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a discharge -d "Force a complete discharge of the battery (ThinkPads only)" 29 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a recalibrate -d "Perform a battery recalibration (ThinkPads only)" 30 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -a diskid -d "Print disk ids for configured drives" 31 | complete -c tlp -n "not __fish_seen_subcommand_from $tlp_commands" -l version -d 'Print TLP version' 32 | complete -c tlp -n "__fish_seen_subcommand_from $tlp_commands[6..10] && not __fish_seen_subcommand_from $bats" -a "$bats" 33 | end 34 | 35 | if contains $current_command $tlp_rf_devices 36 | complete -c $current_command -f 37 | complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a on -d 'Switch device on' 38 | complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a off -d 'Switch device off' 39 | complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -a toggle -d 'Toggle device state' 40 | complete -c $current_command -n "not __fish_seen_subcommand_from $tlp_rf_devices_commands" -l version -d 'Print TLP version' 41 | end 42 | 43 | if contains $current_command $runon_commands 44 | complete -c $current_command -xa "(__fish_complete_subcommand)" 45 | end 46 | -------------------------------------------------------------------------------- /completion/zsh/_tlp: -------------------------------------------------------------------------------- 1 | #compdef tlp 2 | # Copyright (c) 2022 Arvid Norlander and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | # Helper to find installed batteries. Avoid a generic name since this is 6 | # going into the global scope. 7 | _tlp_batteries() { 8 | local -a bats 9 | for b in /sys/class/power_supply/*; do 10 | if [[ ! $b =~ hid \ 11 | && -f $b/present && "$(< $b/present)" == "1" \ 12 | && "$(< $b/type)" = "Battery" ]]; then 13 | bats+=("${b##/*/}[Battery ${b##/*/}]") 14 | fi 15 | done 16 | if [[ -n "$bats" ]]; then 17 | _values "battery" $bats 18 | else 19 | _message "battery (none found)" 20 | fi 21 | } 22 | 23 | # Extra helper hoop required when using _regex_words below 24 | local -a subcmd_batteries 25 | subcmd_batteries=(/$'[^\0]##\0'/ ':battery:battery:_tlp_batteries') 26 | 27 | local -a subcmd_setcharge 28 | subcmd_setcharge=( 29 | /$'[0-9]##\0'/ ':number:start charge threshold: ' 30 | /$'[0-9]##\0'/ ':number:stop charge threshold: ' 31 | /$'[^\0]##\0'/ ':battery:battery:_tlp_batteries' 32 | ) 33 | 34 | local -a reply 35 | 36 | local -a args 37 | args=( 38 | # Command word. Don't care what that is. 39 | /$'[^\0]#\0'/ 40 | ) 41 | 42 | _regex_words commands 'tlp command' \ 43 | 'start:Start TLP and apply power saving profile for the actual power source' \ 44 | 'bat:Apply battery profile and enter manual mode' \ 45 | 'ac:Apply AC profile and enter manual mode' \ 46 | 'usb:Enable autosuspend for all USB devices except excluded' \ 47 | 'bayoff:Turn off optical drive in UltraBay/MediaBay' \ 48 | 'chargeonce:Charge battery to the stop charge threshold once (ThinkPads only):$subcmd_batteries' \ 49 | 'discharge:Force a complete discharge of the battery (ThinkPads only):$subcmd_batteries' \ 50 | 'setcharge:Change charge thresholds temporarily:$subcmd_setcharge' \ 51 | 'fullcharge:Charge battery to full capacity:$subcmd_batteries' \ 52 | 'recalibrate:Perform a battery recalibration (ThinkPads only):$subcmd_batteries' \ 53 | 'diskid:Print disk ids for configured drives' \ 54 | '--version:Print TLP version' 55 | args+=("$reply[@]") 56 | 57 | _regex_arguments _tlp "$args[@]" 58 | _tlp "$@" 59 | -------------------------------------------------------------------------------- /completion/zsh/_tlp-radio-device: -------------------------------------------------------------------------------- 1 | #compdef bluetooth nfc wifi wwan 2 | # Copyright (c) 2022 Arvid Norlander and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | local -a args reply 6 | args=( 7 | # Command word. Don't care what that is. 8 | /$'[^\0]#\0'/ 9 | ) 10 | 11 | _regex_words commands "$service command" \ 12 | 'on:Switch device on' \ 13 | 'off:Switch device off' \ 14 | 'toggle:Toggle device state' \ 15 | '--version:Print TLP version' 16 | args+=("$reply[@]") 17 | 18 | _regex_arguments _tlp-radio-device "$args[@]" 19 | _tlp-radio-device "$@" 20 | -------------------------------------------------------------------------------- /completion/zsh/_tlp-rdw: -------------------------------------------------------------------------------- 1 | #compdef tlp-rdw 2 | # Copyright (c) 2022 Arvid Norlander and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | local -a args reply 6 | args=( 7 | # Command word. Don't care what that is. 8 | /$'[^\0]#\0'/ 9 | ) 10 | 11 | _regex_words commands 'tlp-rdw command' \ 12 | 'disable:Disable RDW actions' \ 13 | 'enable:Enable RDW actions' \ 14 | '--version:Print TLP version' 15 | args+=("$reply[@]") 16 | 17 | _regex_arguments _tlp-rdw "$args[@]" 18 | _tlp-rdw "$@" 19 | -------------------------------------------------------------------------------- /completion/zsh/_tlp-run-on: -------------------------------------------------------------------------------- 1 | #compdef run-on-ac run-on-bat 2 | # Copyright (c) 2022 Arvid Norlander and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | # Command line is an arbitrary command, so just forward completion to _precommand 6 | _precommand 7 | -------------------------------------------------------------------------------- /completion/zsh/_tlp-stat: -------------------------------------------------------------------------------- 1 | #compdef tlp-stat 2 | # Copyright (c) 2022 Arvid Norlander and others. 3 | # SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | local -a args 6 | args=( 7 | '(-b --battery)'{--battery,-b}'[View battery data]' 8 | '(-c --config)'{--config,-c}'[View active configuration]' 9 | '--cdiff[View difference between defaults and user configuration]' 10 | '(-d --disk)'{--disk,-d}'[View disk device information]' 11 | '(-e --pcie)'{--pcie,-e}'[View PCIe device information]' 12 | '(-g --graphics)'{--graphics,-g}'[View graphics card information]' 13 | '(-m --mode)'{--mode,-m}'[Print current power mode]' 14 | '(-p --processor)'{--processor,-p}'[View processor information]' 15 | '(-q --quiet)'{--quiet,-q}'[Show less information]' 16 | '(-r --rfkill)'{--rfkill,-r}'[View radio device states]' 17 | '(-s --system)'{--system,-s}'[View system information and TLP status]' 18 | '(-t --temp)'{--temp,-t}'[View temperatures and fan speed]' 19 | '(-u --usb)'{--usb,-u}'[View USB device information]' 20 | '(-v --verbose)'{--verbose,-v}'[Show more information]' 21 | '--version[Print TLP version]' 22 | '(-P --pev)'{--pev,-P}'[Monitor power supply udev events]' 23 | '--psup[View power supply diagnostics]' 24 | '(-T --trace)'{--trace,-T}'[View trace output]' 25 | '--udev[Check if udev rules for power source changes and connecting USB devices are active]' 26 | '(-w --warn)'{--warn,-w}'[View warnings about SATA disks]' 27 | ) 28 | 29 | _arguments $args 30 | -------------------------------------------------------------------------------- /de.linrunner.tlp.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | de.linrunner.tlp 6 | MIT 7 | TLP 8 | 9 | Save battery power on laptops 10 | 11 |

12 | TLP is an advanced power management tool for Linux. It comes with a 13 | default configuration already optimized for battery life. At the same 14 | time it is highly customizable to fulfil specific user requirements. 15 |

16 |

17 | TLP supplies separate settings profiles for AC and battery power and can 18 | enable or disable bluetooth, WiFi and WWAN radio devices upon system 19 | startup. 20 |

21 |

22 | For ThinkPads it provides a unified way to configure charging thresholds 23 | and recalibrate the battery for all models which support it (via tp-smapi 24 | or acpi-call). 25 |

26 |

27 | TLP is a pure command line tool with automated background tasks, it does 28 | not contain a GUI. 29 |

30 |
31 | 32 | 33 | System 34 | 35 | 36 | 37 | 38 | 39 | GPL-2.0+ 40 | Thomas Koch 41 | https://linrunner.de/tlp 42 | 43 | 44 | 45 | dmi:*:ct8:* 46 | 47 | dmi:*:ct9:* 48 | 49 | dmi:*:ct10:* 50 | 51 | acpi:PNP0C0A:* 52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /defaults.conf: -------------------------------------------------------------------------------- 1 | # /usr/share/tlp/defaults.conf - TLP intrinsic defaults 2 | # IMPORTANT: do not edit this file, put your settings in /etc/tlp.conf or 3 | # /etc/tlp.d/*.conf instead! 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | TLP_ENABLE=1 7 | TLP_WARN_LEVEL=3 8 | TLP_MSG_COLORS="91 93 1 92" 9 | TLP_PERSISTENT_DEFAULT=0 10 | DISK_IDLE_SECS_ON_AC=0 11 | DISK_IDLE_SECS_ON_BAT=2 12 | MAX_LOST_WORK_SECS_ON_AC=15 13 | MAX_LOST_WORK_SECS_ON_BAT=60 14 | CPU_ENERGY_PERF_POLICY_ON_AC=balance_performance 15 | CPU_ENERGY_PERF_POLICY_ON_BAT=balance_power 16 | NMI_WATCHDOG=0 17 | DISK_DEVICES="nvme0n1 sda" 18 | DISK_APM_LEVEL_ON_AC="254 254" 19 | DISK_APM_LEVEL_ON_BAT="128 128" 20 | DISK_APM_CLASS_DENYLIST="usb ieee1394" 21 | DISK_IOSCHED="keep keep" 22 | SATA_LINKPWR_ON_AC="med_power_with_dipm" 23 | SATA_LINKPWR_ON_BAT="med_power_with_dipm" 24 | AHCI_RUNTIME_PM_ON_AC=on 25 | AHCI_RUNTIME_PM_ON_BAT=auto 26 | AHCI_RUNTIME_PM_TIMEOUT=15 27 | PCIE_ASPM_ON_AC=default 28 | PCIE_ASPM_ON_BAT=default 29 | RADEON_DPM_PERF_LEVEL_ON_AC=auto 30 | RADEON_DPM_PERF_LEVEL_ON_BAT=auto 31 | AMDGPU_ABM_LEVEL_ON_AC=0 32 | AMDGPU_ABM_LEVEL_ON_BAT=1 33 | WIFI_PWR_ON_AC=off 34 | WIFI_PWR_ON_BAT=on 35 | WOL_DISABLE=Y 36 | SOUND_POWER_SAVE_ON_AC=1 37 | SOUND_POWER_SAVE_ON_BAT=1 38 | SOUND_POWER_SAVE_CONTROLLER=Y 39 | BAY_POWEROFF_ON_AC=0 40 | BAY_POWEROFF_ON_BAT=0 41 | BAY_DEVICE="sr0" 42 | RUNTIME_PM_ON_AC=on 43 | RUNTIME_PM_ON_BAT=auto 44 | RUNTIME_PM_DRIVER_DENYLIST="mei_me nouveau radeon xhci_hcd" 45 | USB_AUTOSUSPEND=1 46 | USB_EXCLUDE_AUDIO=1 47 | USB_EXCLUDE_BTUSB=0 48 | USB_EXCLUDE_PHONE=0 49 | USB_EXCLUDE_PRINTER=1 50 | USB_EXCLUDE_WWAN=0 51 | RESTORE_DEVICE_STATE_ON_STARTUP=0 52 | RESTORE_THRESHOLDS_ON_BAT=0 53 | NATACPI_ENABLE=1 54 | TPSMAPI_ENABLE=1 55 | -------------------------------------------------------------------------------- /deprecated.conf: -------------------------------------------------------------------------------- 1 | DEVICES_TO_DISABLE_ON_SHUTDOWN # Parameter was removed 2 | DEVICES_TO_ENABLE_ON_SHUTDOWN # Parameter was removed 3 | RADEON_POWER_PROFILE_ON_AC # Parameter was removed 4 | RADEON_POWER_PROFILE_ON_BAT # Parameter was removed 5 | SCHED_POWERSAVE_ON_AC # Parameter was removed: obsolete since kernel 3.5 6 | SCHED_POWERSAVE_ON_BAT # Parameter was removed: obsolete since kernel 3.5 7 | USB_AUTOSUSPEND_DISABLE_ON_SHUTDOWN # Parameter was removed 8 | -------------------------------------------------------------------------------- /func.d/05-tlp-func-pm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-func-pm - Device Power Management Functions 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # Needs: tlp-func-base 8 | 9 | # ---------------------------------------------------------------------------- 10 | # Constants 11 | 12 | readonly ETHTOOL=ethtool 13 | 14 | readonly PCID=/sys/bus/pci/devices 15 | readonly PCIDRV=/sys/bus/pci/drivers 16 | readonly SLEEPMODE=/sys/power/mem_sleep 17 | 18 | # ---------------------------------------------------------------------------- 19 | # Functions 20 | 21 | # --- PCI(e) Devices 22 | 23 | set_runtime_pm () { # set runtime power management 24 | # $1: 0=ac mode, 1=battery mode 25 | 26 | local address class control device driver drv_bl type 27 | local pci_bl_adr="" 28 | local pci_bl_drv="" 29 | local pci_enable_adr pci_disable_adr 30 | 31 | if [ "$1" = "1" ]; then 32 | control=${RUNTIME_PM_ON_BAT:-} 33 | else 34 | control=${RUNTIME_PM_ON_AC:-} 35 | fi 36 | 37 | # permanent addresses 38 | pci_enable_adr=${RUNTIME_PM_ENABLE:-} 39 | pci_disable_adr=${RUNTIME_PM_DISABLE:-} 40 | 41 | if [ -z "$control" ] && [ -z "$pci_enable_adr" ] && [ -z "$pci_disable_adr" ] ; then 42 | # quit if completely unconfigured 43 | echo_debug "pm" "set_runtime_pm($1).not_configured" 44 | return 0 45 | fi 46 | 47 | case "$control" in 48 | auto|on|"") # valid control value or no operation ("") 49 | ;; 50 | 51 | *) # invalid control value 52 | echo_debug "pm" "set_runtime_pm($1).invalid: $control" 53 | return 0 54 | ;; 55 | esac 56 | 57 | if [ -n "$control" ]; then 58 | # RUNTIME_PM_ON_AC/BAT is configured --> prepare denylists 59 | # driver specific denylist: 60 | # - undefined = use intrinsic default from /usr/share/tlp/defaults.conf 61 | # - empty = disable feature 62 | drv_bl="$RUNTIME_PM_DRIVER_DENYLIST" 63 | 64 | # pci address denylisting 65 | pci_bl_adr=${RUNTIME_PM_DENYLIST:-} 66 | 67 | # pci driver denylisting: corresponding pci addresses 68 | pci_bl_drv="" 69 | 70 | # cumulate pci addresses for devices with denylisted drivers 71 | for driver in $drv_bl; do # iterate list 72 | if [ -n "$driver" ] && [ -d "$PCIDRV/$driver" ]; then 73 | # driver is active --> iterate over assigned devices 74 | for device in "$PCIDRV/$driver/0000:"*; do 75 | # get short device address 76 | address=${device##/*/0000:} 77 | 78 | # add to list when not already contained 79 | if ! wordinlist "$address" "$pci_bl_drv"; then 80 | pci_bl_drv="$pci_bl_drv $address" 81 | fi 82 | done # for device 83 | fi # if driver 84 | done # for driver 85 | fi 86 | 87 | # iterate pci(e) devices 88 | for type in $PCID; do 89 | for device in "$type"/*; do 90 | if [ -f "$device/power/control" ]; then 91 | # get short device address, class 92 | address=${device##/*/0000:} 93 | class=$(read_sysf "$device/class") 94 | 95 | if wordinlist "$address" "$pci_enable_adr"; then 96 | # device should be permanently 'auto' (enabled) 97 | write_sysf "auto" "$device/power/control" 98 | echo_debug "pm" "set_runtime_pm($1).perm_auto: $device [$class]; rc=$?" 99 | elif wordinlist "$address" "$pci_disable_adr"; then 100 | # device should be permanently 'on' (disabled) 101 | write_sysf "on" "$device/power/control" 102 | echo_debug "pm" "set_runtime_pm($1).perm_on: $device [$class]; rc=$?" 103 | elif wordinlist "$address" "$pci_bl_adr"; then 104 | # device is in address denylist 105 | echo_debug "pm" "set_runtime_pm($1).deny_address: $device [$class]" 106 | elif wordinlist "$address" "$pci_bl_drv"; then 107 | # device is in driver denylist 108 | echo_debug "pm" "set_runtime_pm($1).deny_driver: $device [$class]" 109 | else 110 | case $control in 111 | auto|on) 112 | write_sysf "$control" "$device/power/control" 113 | echo_debug "pm" "set_runtime_pm($1).$control: $device [$class]; rc=$?" 114 | ;; 115 | 116 | "") # no operation i.e. apply RUNTIME_PM_ENABLE/DISABLE only 117 | echo_debug "pm" "set_runtime_pm($1).nop: $device [$class]" 118 | ;; 119 | esac 120 | fi # if denylist 121 | fi # if power/control 122 | done # for device 123 | done # for type 124 | 125 | return 0 126 | } 127 | 128 | set_pcie_aspm () { # set pcie active state power management 129 | # $1: 0=ac mode, 1=battery mode 130 | 131 | local pwr="" 132 | 133 | case "$1" in 134 | 0) pwr="${PCIE_ASPM_ON_AC:-}" ;; 135 | 1) pwr="${PCIE_ASPM_ON_BAT:-}" ;; 136 | 2) # reset on suspend only when configured 137 | if [ -n "${PCIE_ASPM_ON_AC:-}${PCIE_ASPM_ON_BAT:-}" ]; then 138 | pwr="default" 139 | fi 140 | ;; 141 | esac 142 | 143 | if [ -z "$pwr" ]; then 144 | # do nothing if unconfigured 145 | echo_debug "pm" "set_pcie_aspm($1).not_configured" 146 | return 0 147 | fi 148 | 149 | if [ -f /sys/module/pcie_aspm/parameters/policy ]; then 150 | if write_sysf "$pwr" /sys/module/pcie_aspm/parameters/policy; then 151 | echo_debug "pm" "set_pcie_aspm($1): $pwr" 152 | else 153 | echo_debug "pm" "set_pcie_aspm($1).disabled_by_kernel" 154 | fi 155 | else 156 | echo_debug "pm" "set_pcie_aspm($1).not_available" 157 | fi 158 | 159 | return 0 160 | } 161 | 162 | # -- Audio Devices 163 | 164 | set_sound_power_mode () { # set sound chip power modes 165 | # $1: 0=ac mode, 1=battery mode 166 | 167 | local pwr cpwr 168 | 169 | # new config param 170 | if [ "$1" = "1" ]; then 171 | pwr=${SOUND_POWER_SAVE_ON_BAT:-} 172 | else 173 | pwr=${SOUND_POWER_SAVE_ON_AC:-} 174 | fi 175 | 176 | # when unconfigured consider legacy config param 177 | [ -z "$pwr" ] && pwr=${SOUND_POWER_SAVE:-} 178 | 179 | if [ -z "$pwr" ]; then 180 | # do nothing if unconfigured 181 | echo_debug "pm" "set_sound_power_mode($1).not_configured" 182 | return 0 183 | fi 184 | 185 | cpwr="$SOUND_POWER_SAVE_CONTROLLER" 186 | 187 | if [ -d /sys/module/snd_hda_intel ]; then 188 | write_sysf "$pwr" /sys/module/snd_hda_intel/parameters/power_save 189 | echo_debug "pm" "set_sound_power_mode($1).hda: $pwr; rc=$?" 190 | 191 | if [ "$pwr" = "0" ]; then 192 | write_sysf "N" /sys/module/snd_hda_intel/parameters/power_save_controller 193 | echo_debug "pm" "set_sound_power_mode($1).hda_controller: N controller=$cpwr; rc=$?" 194 | else 195 | write_sysf "$cpwr" /sys/module/snd_hda_intel/parameters/power_save_controller 196 | echo_debug "pm" "set_sound_power_mode($1).hda_controller: $cpwr; rc=$?" 197 | fi 198 | fi 199 | 200 | if [ -d /sys/module/snd_ac97_codec ]; then 201 | write_sysf "$pwr" /sys/module/snd_ac97_codec/parameters/power_save 202 | echo_debug "pm" "set_sound_power_mode($1).ac97: $pwr; rc=$?" 203 | fi 204 | 205 | return 0 206 | } 207 | 208 | # --- LAN Devices 209 | 210 | get_ethifaces () { # get all eth devices -- retval: $_ethifaces 211 | local ei eic 212 | _ethifaces="" 213 | 214 | for eic in "$NETD"/*/device/class; do 215 | if [ "$(read_sysf "$eic")" = "0x020000" ] \ 216 | && [ ! -d "${eic%/class}/ieee80211" ]; then 217 | 218 | ei=${eic%/device/class}; ei=${ei##*/} 219 | _ethifaces="$_ethifaces $ei" 220 | fi 221 | done 222 | 223 | _ethifaces="${_ethifaces# }" 224 | return 0 225 | } 226 | 227 | disable_wake_on_lan () { # disable WOL 228 | local ei 229 | 230 | if [ "$WOL_DISABLE" = "Y" ]; then 231 | get_ethifaces 232 | for ei in $_ethifaces; do 233 | $ETHTOOL -s "$ei" wol d > /dev/null 2>&1 234 | echo_debug "pm" "disable_wake_on_lan: $ei; rc=$?" 235 | done 236 | else 237 | echo_debug "pm" "disable_wake_on_lan.not_configured" 238 | fi 239 | 240 | return 0 241 | } 242 | 243 | # --- Set suspend method 244 | 245 | set_mem_sleep () { 246 | # $1: 0=ac mode, 1=battery mode 247 | 248 | local susp 249 | 250 | if [ "$1" = "1" ]; then 251 | susp=${MEM_SLEEP_ON_BAT:-} 252 | else 253 | susp=${MEM_SLEEP_ON_AC:-} 254 | fi 255 | 256 | if [ -z "$susp" ]; then 257 | # do nothing if unconfigured 258 | echo_debug "pm" "set_mem_sleep($1).not_configured" 259 | return 0 260 | fi 261 | 262 | if [ -f $SLEEPMODE ]; then 263 | if write_sysf "$susp" $SLEEPMODE; then 264 | echo_debug "pm" "set_mem_sleep($1): $susp" 265 | else 266 | echo_debug "pm" "set_mem_sleep($1).rejected_by_kernel" 267 | fi 268 | else 269 | echo_debug "pm" "set_mem_sleep($1).not_available" 270 | fi 271 | 272 | return 0 273 | } 274 | -------------------------------------------------------------------------------- /func.d/25-tlp-func-rf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-func-rf - Radio Device Checks and PM Functions 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # Needs: tlp-func-base 8 | 9 | # ---------------------------------------------------------------------------- 10 | # Constants 11 | 12 | readonly IW=iw 13 | 14 | readonly BLUETOOTHD=/sys/class/bluetooth 15 | 16 | # ---------------------------------------------------------------------------- 17 | # Functions 18 | 19 | # --- Wifi Device Checks 20 | 21 | get_wifi_ifaces () { # get all wifi devices -- retval: $_wifaces 22 | local wi wiu 23 | _wifaces="" 24 | 25 | for wiu in "$NETD"/*/uevent; do 26 | if grep -q -s 'DEVTYPE=wlan' "$wiu" ; then 27 | wi=${wiu%/uevent}; wi=${wi##*/} 28 | _wifaces="$_wifaces $wi" 29 | fi 30 | done 31 | 32 | _wifaces="${_wifaces# }" 33 | return 0 34 | } 35 | 36 | get_wifi_driver () { # get driver associated with interface 37 | # $1: iface; retval: $_wifidrv 38 | local drvl 39 | 40 | _wifidrv="" 41 | if [ -d "$NETD/$1" ]; then 42 | drvl=$(readlink "$NETD/$1/device/driver") 43 | # shellcheck disable=SC2034 44 | [ -n "$drvl" ] && _wifidrv=${drvl##*/} 45 | fi 46 | 47 | return 0 48 | } 49 | 50 | wireless_in_use () { # check if wifi or wwan device is in use -- $1: iface 51 | if [ -f "$NETD/$1/carrier" ]; then 52 | if [ "$(read_sysf "$NETD/$1/carrier")" = "1" ]; then 53 | return 0 54 | fi 55 | fi 56 | return 1 57 | } 58 | 59 | any_wifi_in_use () { # check if any wifi device is in use 60 | local iface 61 | 62 | get_wifi_ifaces 63 | for iface in $_wifaces; do 64 | wireless_in_use "$iface" && return 0 65 | done 66 | 67 | return 1 68 | } 69 | 70 | # --- Wifi Power Management 71 | 72 | set_wifi_power_mode () { # set wifi power save mode -- $1: 0=ac mode, 1=battery mode 73 | local pwr iface 74 | 75 | if [ "$1" = "1" ]; then 76 | pwr=${WIFI_PWR_ON_BAT:-} 77 | else 78 | pwr=${WIFI_PWR_ON_AC:-} 79 | fi 80 | 81 | # check values, translate obsolete syntax 82 | case "$pwr" in 83 | off|on) ;; 84 | 0|1|N) pwr="off" ;; 85 | 2|3|4|5|6|Y) pwr="on" ;; 86 | *) pwr="" ;; # invalid input --> unconfigured 87 | esac 88 | 89 | if [ -z "$pwr" ]; then 90 | # do nothing if unconfigured 91 | echo_debug "pm" "set_wifi_power_mode($1).not_configured" 92 | return 0 93 | fi 94 | 95 | get_wifi_ifaces 96 | if [ -z "$_wifaces" ]; then 97 | echo_debug "pm" "set_wifi_power_mode($1).no_ifaces" 98 | return 0 99 | fi 100 | 101 | for iface in $_wifaces; do 102 | if [ -n "$iface" ]; then 103 | if cmd_exists $IW; then 104 | $IW dev "$iface" set power_save "$pwr" > /dev/null 2>&1 105 | echo_debug "pm" "set_wifi_power_mode($1, $iface).iw: $pwr; rc=$?" 106 | else 107 | # iw not iwconfig installed 108 | echo_debug "pm" "set_wifi_power_mode($1, $iface).no_iw" 109 | return 1 110 | fi 111 | fi 112 | done 113 | 114 | return 0 115 | } 116 | 117 | # --- WWAN Device Checks 118 | 119 | get_wwan_ifaces () { # get all wwan devices -- retval: $_wanifaces 120 | local wi wiu 121 | _wanifaces="" 122 | 123 | for wiu in "$NETD"/*/uevent; do 124 | if grep -q -s 'DEVTYPE=wwan' "$wiu" ; then 125 | wi=${wiu%/uevent}; wi=${wi##*/} 126 | _wanifaces="$_wanifaces $wi" 127 | fi 128 | done 129 | 130 | _wanifaces="${_wanifaces# }" 131 | return 0 132 | } 133 | 134 | any_wwan_in_use () { # check if any wwan device is in use 135 | local iface 136 | 137 | get_wwan_ifaces 138 | for iface in $_wanifaces; do 139 | wireless_in_use "$iface" && return 0 140 | done 141 | 142 | return 1 143 | } 144 | 145 | get_wwan_driver () { # get driver associated with interface 146 | # $1: iface; retval: $_wwandrv 147 | local drvl 148 | 149 | _wwandrv="" 150 | if [ -d "$NETD/$1" ]; then 151 | drvl=$(readlink "$NETD/$1/device/driver") 152 | # shellcheck disable=SC2034 153 | [ -n "$drvl" ] && _wwandrv=${drvl##*/} 154 | fi 155 | 156 | return 0 157 | } 158 | 159 | # --- Bluetooth Device Checks 160 | 161 | get_bluetooth_ifaces () { # get all bluetooth interfaces -- retval: $_bifaces 162 | # enumerate symlinks only 163 | _bifaces="$(for i in "$BLUETOOTHD"/*; do [ -h "$i" ] && echo "${i##/*/}"; done | grep -v ':')" 164 | return 0 165 | } 166 | 167 | get_bluetooth_driver () { # get driver associated with interface -- $1: iface; retval: $_btdrv 168 | local drvl 169 | 170 | # shellcheck disable=SC2034 171 | _btdrv="" 172 | if [ -d "$BLUETOOTHD/$1" ]; then 173 | drvl=$(readlink "$BLUETOOTHD/$1/device/driver") 174 | # shellcheck disable=SC2034 175 | [ -n "$drvl" ] && _btdrv=${drvl##*/} 176 | fi 177 | 178 | return 0 179 | } 180 | 181 | bluetooth_in_use () { # check if bluetooth interface is in use -- $1: iface 182 | local uev 183 | 184 | # when devices are connected to an interface its sysdir is populated with 185 | # subdevices like : where the uevent file contains a line 186 | # "DEVTYPE=link" 187 | for uev in "$BLUETOOTHD/$1/$1":*/uevent; do 188 | grep -q -s 'DEVTYPE=link' "$uev" && return 0 189 | done 190 | 191 | return 1 192 | } 193 | 194 | any_bluetooth_in_use () { # check if any bluetooth interface is in use 195 | local i 196 | 197 | get_bluetooth_ifaces 198 | for i in $_bifaces; do 199 | bluetooth_in_use "$i" && return 0 200 | done 201 | 202 | return 1 203 | } 204 | -------------------------------------------------------------------------------- /func.d/35-tlp-func-batt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-func-batt - Battery Feature Functions 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # Needs: tlp-func-base, 34-tlp-func-platform 8 | 9 | # ---------------------------------------------------------------------------- 10 | # Constants 11 | 12 | # shellcheck disable=SC2034 13 | readonly ACPIBATDIR=/sys/class/power_supply 14 | 15 | # ---------------------------------------------------------------------------- 16 | # Functions 17 | 18 | init_batteries_thresholds () { 19 | # apply thresholds from configuration to all batteries 20 | # optional depending on active plugin when specified in $1 21 | # - called from bg tasks tlp init [re]start/auto and tlp start 22 | # $1: plugin list (space separated) 23 | # rc: 0=ok/ 24 | # 1=battery not present/ 25 | # 2=threshold(s) out of range or non-numeric/ 26 | # 3=minimum start stop diff violated/ 27 | # 4=read error/ 28 | # 5=write error/ 29 | # 6=threshold write discarded by kernel or firmware/ 30 | # 255=no thresh api 31 | 32 | local rc 33 | 34 | # select battery feature driver 35 | select_batdrv 36 | # shellcheck disable=SC2154 37 | if [ "$_bm_thresh" = "none" ]; then 38 | # thresholds not available --> quit 39 | echo_debug "bat" "set_charge_thresholds.no_method" 40 | return 255 41 | fi 42 | 43 | # apply thresholds 44 | # shellcheck disable=SC2154 45 | if [ -z "$1" ]; then 46 | batdrv_apply_configured_thresholds; rc=$? 47 | elif wordinlist "$_batdrv_plugin" "$1"; then 48 | batdrv_apply_configured_thresholds; rc=$? 49 | fi 50 | 51 | return $rc 52 | } 53 | 54 | setcharge_battery () { 55 | # apply charge thresholds for a single battery 56 | # - called from cmdline tlp setcharge/fullcharge/recalibrate 57 | # $1: start charge threshold || battery 58 | # $2: stop charge threshold 59 | # $3: battery 60 | # rc: 0=ok/ 61 | # 1=battery not present/ 62 | # 2=threshold(s) out of range or non-numeric/ 63 | # 3=minimum start stop diff violated/ 64 | # 4=read error/ 65 | # 5=write error/ 66 | # 6=threshold write discarded by kernel or firmware/ 67 | # 255=no thresh api 68 | 69 | local bat rc start_thresh stop_thresh 70 | local use_cfg=0 71 | 72 | # select battery feature driver 73 | select_batdrv 74 | # shellcheck disable=SC2154 75 | if [ "$_bm_thresh" = "none" ]; then 76 | # thresholds not available --> quit 77 | cecho "Error: there is no hardware driver support for charge thresholds." 1>&2 78 | echo_debug "bat" "setcharge_battery.no_method" 79 | return 255 80 | fi 81 | 82 | # check params 83 | case $# in 84 | 0) # no args 85 | bat=DEF # use default(1st) battery 86 | use_cfg=1 # use configured values 87 | ;; 88 | 89 | 1) # assume $1 is battery 90 | bat=$1 91 | use_cfg=1 # use configured values 92 | ;; 93 | 94 | 2) # assume $1,$2 are thresholds 95 | start_thresh=$1 96 | stop_thresh=$2 97 | bat=DEF # use default(1st) battery 98 | ;; 99 | 100 | 3|4) # assume $1,$2 are thresholds, $3 is battery 101 | start_thresh=$1 102 | stop_thresh=$2 103 | bat=${3:-DEF} 104 | ;; 105 | esac 106 | 107 | # check bat presence and/or get default(1st) battery 108 | if batdrv_select_battery "$bat"; then 109 | # battery present -> get configured values if requested 110 | if [ $use_cfg -eq 1 ]; then 111 | # shellcheck disable=SC2154 112 | eval start_thresh="\$START_CHARGE_THRESH_${_bt_cfg_bat}" 113 | # shellcheck disable=SC2154 114 | eval stop_thresh="\$STOP_CHARGE_THRESH_${_bt_cfg_bat}" 115 | fi 116 | else 117 | # battery not present 118 | cecho "Error: battery $bat not present." 1>&2 119 | echo_debug "bat" "setcharge_battery.not_present($bat)" 120 | return 1 121 | fi 122 | 123 | # apply thresholds 124 | if [ $use_cfg -eq 1 ]; then 125 | # from configuration 126 | batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2 1; rc=$? 127 | else 128 | # from command line 129 | batdrv_write_thresholds "$start_thresh" "$stop_thresh" 2; rc=$? 130 | fi 131 | return $rc 132 | } 133 | 134 | chargeonce_battery () { 135 | # charge battery to upper threshold once 136 | # $1: battery 137 | # rc: 0=ok/1=battery not present/255=no api 138 | 139 | local bat rc 140 | 141 | # select battery feature driver 142 | select_batdrv 143 | if [ "$_bm_thresh" = "none" ]; then 144 | # thresholds not available --> quit 145 | cecho "Error: there is no hardware driver support for charge thresholds." 1>&2 146 | echo_debug "bat" "chargeonce_battery.no_method" 147 | return 255 148 | fi 149 | 150 | # check params 151 | if [ -n "$1" ]; then 152 | # parameter(s) given, check $1 153 | bat="${1:-DEF}" 154 | bat="$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]")" 155 | else 156 | # no parameters given, use default(1st) battery 157 | bat=DEF 158 | fi 159 | 160 | # check bat presence and/or get default(1st) battery 161 | if ! batdrv_select_battery "$bat"; then 162 | # battery not present 163 | cecho "Error: battery $bat not present." 1>&2 164 | # shellcheck disable=SC2154 165 | echo_debug "bat" "chargeonce_battery.not_present($_bat_str)" 166 | return 1 167 | fi 168 | 169 | # apply temporary start threshold 170 | batdrv_chargeonce; rc=$? 171 | if [ $rc -eq 255 ]; then 172 | cecho "Error: chargeonce not available for your hardware." 1>&2 173 | echo_debug "bat" "chargeonce_battery.not_supported" 174 | return 255 175 | fi 176 | 177 | return $rc 178 | } 179 | 180 | discharge_battery () { 181 | # discharge battery 182 | # $1: discharge/recalibrate 183 | # $2: battery 184 | # $2 or $3: target soc 0(default)..99 185 | # rc: 0=ok/6=target soc out of bounds/7=target > actual soc/8=target soc reached/10=battery not present/11=fullcharge malfunction/12=no ac power/15=concurrent op running/16=safety lock/255=no api 186 | 187 | local bat mode rc target_soc 188 | 189 | # get params 190 | mode="${1:-discharge}" 191 | shift 192 | 193 | if ! check_ac_power "$mode"; then 194 | return 12 195 | fi 196 | check_root 197 | 198 | # select battery care plugin 199 | select_batdrv 200 | # shellcheck disable=SC2154 201 | if [ "$_bm_dischg" = "none" ]; then 202 | # no method available --> quit 203 | cecho "Error: there is no hardware driver support for battery $mode." 1>&2 204 | echo_debug "bat" "discharge_battery.no_method" 205 | return 255 206 | fi 207 | 208 | if batdrv_discharge_safetylock "$mode"; then 209 | return 16 210 | fi 211 | 212 | if ! lock_tlp_nb tlp_discharge; then 213 | cecho "Error: another $mode operation is pending." 1>&2 214 | echo_debug "bat" "discharge_battery.concurrent_op_running" 215 | return 15 216 | fi 217 | 218 | # check params $1, $2 (after shift) 219 | case "$mode" in 220 | recalibrate) 221 | # $1 is battery (if existent) 222 | bat="${1:-DEF}" 223 | target_soc=0 224 | ;; 225 | 226 | discharge) 227 | if [ -z "$1" ] && [ -z "$2" ]; then 228 | bat=DEF 229 | target_soc=0 230 | elif [ -n "$1" ] && [ -z "$2" ]; then 231 | # $1 is target soc value or battery 232 | if is_uint "$1"; then 233 | bat=DEF 234 | target_soc="$1" 235 | else 236 | bat="$1" 237 | target_soc=0 238 | fi 239 | else 240 | # $1 is battery, $2 is target soc value 241 | bat="$1" 242 | target_soc="$2" 243 | fi 244 | ;; 245 | esac 246 | bat=$(printf '%s' "$bat" | tr "[:lower:]" "[:upper:]") 247 | 248 | # check bat presence and/or get default(1st) battery 249 | if ! batdrv_select_battery "$bat"; then 250 | # battery not present 251 | cecho "Error: battery $bat not present." 1>&2 252 | echo_debug "bat" "discharge_battery.not_present($bat)" 253 | unlock_tlp tlp_discharge 254 | return 10 255 | fi 256 | 257 | # enable fullcharge 258 | if [ "$mode" = "recalibrate" ] && ! batdrv_write_thresholds DEF DEF 2 ""; then 259 | echo_debug "bat" "discharge_battery.fullcharge_malfunction" 260 | unlock_tlp tlp_discharge 261 | return 11 262 | fi 263 | 264 | # execute discharge 265 | batdrv_discharge "$target_soc"; rc=$? 266 | if [ $rc -eq 0 ] && [ "$mode" = "recalibrate" ]; then 267 | cecho "Charging starts now. For a complete recalibration" "notice" 1>&2 268 | cecho "keep AC connected until the battery is fully charged." "notice" 1>&2 269 | fi 270 | 271 | unlock_tlp tlp_discharge 272 | 273 | return $rc 274 | } 275 | 276 | soc_gt_stop_notice () { 277 | # output notice to discharge on battery power if SOC is above stop threshold 278 | # global params: $_batteries, $_bm_thresh, $_bd_read, $_bat_str 279 | # prerequisite: batdrv_init(), batdrv_select_battery() 280 | 281 | # disable SOC check in unit-tests 282 | [ "$X_SOC_CHECK" = "0" ] && return 0 283 | 284 | # shellcheck disable=SC2154 285 | if batdrv_check_soc_gt_stop; then 286 | echo_message "Notice: $_bat_str charge level is above the stop threshold. Use your laptop"` 287 | `" on battery power until the battery is discharged to the stop threshold." 288 | fi 289 | 290 | return 0 291 | } 292 | 293 | soc_gt_stop_recommendation () { 294 | # output recommendation to discharge on battery power if SOC is above stop threshold 295 | # global params: $_batteries, $_bm_thresh, $_bd_read 296 | # prerequisite: batdrv_init() 297 | 298 | local bat 299 | 300 | # disable SOC check in unit-tests 301 | [ "$X_SOC_CHECK" = "0" ] && return 0 302 | 303 | # shellcheck disable=SC2154 304 | for bat in $_batteries; do # iterate detected batteries 305 | batdrv_select_battery "$bat" 306 | if batdrv_check_soc_gt_stop; then 307 | printf "%s charge level is above the stop threshold. Use your laptop on battery power"` 308 | `" until the battery is discharged to the stop threshold.\n" "$bat" 309 | fi 310 | done 311 | 312 | return 0 313 | } 314 | -------------------------------------------------------------------------------- /func.d/40-tlp-func-bay: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-func-bay - Bay Functions 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # This software is licensed under the GPL v2 or later. 6 | 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | 9 | # ---------------------------------------------------------------------------- 10 | # Constants 11 | 12 | readonly DOCK_GLOB="/sys/devices/platform/dock.?" 13 | 14 | readonly BAYSTATEFILE=$RUNDIR/bay_saved 15 | 16 | # ---------------------------------------------------------------------------- 17 | # Functions 18 | 19 | # --- Drive Bay 20 | 21 | get_drivebay_device () { # Find generic dock interface for drive bay 22 | # rc: 0; retval: $dock 23 | 24 | # shellcheck disable=SC2086 25 | dock=$(grep -l 'ata_bay' $DOCK_GLOB/type 2> /dev/null) 26 | dock=${dock%%/type} 27 | if [ ! -d "$dock" ]; then 28 | dock="" 29 | fi 30 | 31 | return 0 32 | } 33 | 34 | check_is_docked() { # check if $dock is docked; 35 | # rc: 0 if docked, else 1 36 | 37 | local dock_status dock_info_file 38 | 39 | # return 0 if any sysfs file indicates "docked" 40 | for dock_info_file in docked firmware_node/status; do 41 | if [ -f "$dock/$dock_info_file" ] && \ 42 | read -r dock_status < "$dock/$dock_info_file" 2>/dev/null; then 43 | # catch empty $dock_status (safety check, unlikely case) 44 | [ "${dock_status:-0}" != "0" ] && return 0 45 | fi 46 | done 47 | 48 | # otherwise assume "not docked" 49 | return 1 50 | } 51 | 52 | poweroff_drivebay () { # power off optical drive in drive bay 53 | # $1: 0=ac mode, 1=battery mode 54 | # $2: 0=conditional+quiet mode, 1=force+verbose mode 55 | # Some code adapted from https://www.thinkwiki.org/wiki/How_to_hotswap_UltraBay_devices 56 | 57 | local pwr optdrv syspath 58 | 59 | if [ "$1" = "1" ]; then 60 | pwr="$BAY_POWEROFF_ON_BAT" 61 | else 62 | pwr="$BAY_POWEROFF_ON_AC" 63 | fi 64 | 65 | # Run only if forced or enabled 66 | if [ "$2" != "1" ]; then 67 | case "$pwr" in 68 | 1) # enabled --> proceed 69 | ;; 70 | 71 | 0) # disabled 72 | echo_debug "pm" "poweroff_drivebay($1).disabled" 73 | return 0 74 | ;; 75 | 76 | *) # not configured or invalid parameter 77 | echo_debug "pm" "poweroff_drivebay($1).not_configured" 78 | return 0 79 | ;; 80 | esac 81 | fi 82 | 83 | get_drivebay_device 84 | if [ -z "$dock" ] || [ ! -d "$dock" ]; then 85 | echo_debug "pm" "poweroff_drivebay($1).no_bay_device" 86 | [ "$2" = "1" ] && cecho "Error: cannot locate bay device." 1>&2 87 | return 1 88 | fi 89 | echo_debug "pm" "poweroff_drivebay($1): dock=$dock" 90 | 91 | # Check if bay is occupied 92 | if ! check_is_docked; then 93 | echo_debug "pm" "poweroff_drivebay($1).drive_already_off" 94 | [ "$2" = "1" ] && echo "No drive in bay (or power already off)." 95 | else 96 | # Check for optical drive 97 | optdrv="$BAY_DEVICE" 98 | if [ -z "$optdrv" ]; then 99 | echo_debug "pm" "poweroff_drivebay($1).opt_drive_not_configured" 100 | [ "$2" = "1" ] && cecho "Error: no optical drive configured (BAY_DEVICE=\"\")." 1>&2 101 | return 1 102 | elif [ ! -b "/dev/$optdrv" ]; then 103 | echo_debug "pm" "poweroff_drivebay($1).no_opt_drive: /dev/$optdrv" 104 | [ "$2" = "1" ] && echo "No optical drive in bay (/dev/$optdrv)." 105 | return 0 106 | else 107 | echo_debug "pm" "poweroff_drivebay($1): optdrv=$optdrv" 108 | [ "$2" = "1" ] && echo -n "Powering off drive bay..." 109 | 110 | # Unmount media 111 | umount -l "$optdrv" > /dev/null 2>&1 112 | 113 | # Sync drive 114 | sync 115 | sleep 1 116 | 117 | # Power off drive 118 | $HDPARM -Y "$optdrv" > /dev/null 2>&1 119 | sleep 5 120 | 121 | # Unregister scsi device 122 | if syspath="$($UDEVADM info --query=path --name="$optdrv" 2> /dev/null)"; then 123 | syspath="/sys${syspath%/block/*}" 124 | 125 | if [ "$syspath" != "/sys" ]; then 126 | write_sysf "1" "$syspath/delete" 127 | echo_debug "pm" "poweroff_drivebay($1): syspath=$syspath; rc=$?" 128 | else 129 | echo_debug "pm" "poweroff_drivebay($1): got empty/invalid syspath for $optdrv" 130 | fi 131 | else 132 | echo_debug "pm" "poweroff_drivebay($1): failed to get syspath (udevadm returned $?)" 133 | fi 134 | 135 | # Turn power off 136 | write_sysf "1" "$dock/undock" 137 | echo_debug "pm" "poweroff_drivebay($1).bay_powered_off: rc=$?" 138 | [ "$2" = "1" ] && echo "done." 139 | fi 140 | fi 141 | 142 | return 0 143 | } 144 | 145 | suspend_drivebay () { # Save power state of drive bay before suspend 146 | # $1: 0=ac mode, 1=battery mode 147 | 148 | if [ "$1" = "1" ] && [ "$BAY_POWEROFF_ON_BAT" = "1" ] || \ 149 | [ "$1" = "0" ] && [ "$BAY_POWEROFF_ON_AC" = "1" ]; then 150 | # setting corresponding to mode is active -> save state 151 | get_drivebay_device 152 | 153 | if [ -n "$dock" ]; then 154 | create_rundir 155 | if ! check_is_docked; then 156 | write_sysf "off" "$BAYSTATEFILE" 157 | echo_debug "pm" "suspend_drivebay($1): bay=off; rc=$?" 158 | else 159 | write_sysf "on" "$BAYSTATEFILE" 160 | echo_debug "pm" "suspend_drivebay($1): bay=on; rc=$?" 161 | fi 162 | fi 163 | else 164 | # setting not active -> remove state file 165 | rm -f "$BAYSTATEFILE" 2> /dev/null 166 | fi 167 | 168 | return 0 169 | } 170 | 171 | resume_drivebay () { # 172 | # $1: 0=ac mode, 1=battery mode 173 | local cnt rc 174 | 175 | if [ "$(read_sysf "$BAYSTATEFILE")" = "off" ]; then 176 | # saved state = off 177 | get_drivebay_device 178 | 179 | if [ -n "$dock" ]; then 180 | if check_is_docked; then 181 | # device active -> deactivate 182 | if [ -e "$dock/undock" ]; then 183 | cnt=5 184 | rc=1 185 | until [ $rc = 0 ] || [ $cnt = 0 ]; do 186 | cnt=$((cnt - 1)) 187 | { printf '%s\n' "1" > "$dock/undock"; } 2> /dev/null 188 | rc=$? 189 | [ $rc = 0 ] || sleep 0.5 190 | done 191 | echo_debug "pm" "resume_drivebay.bay_off: rc=$rc" 192 | fi 193 | else 194 | echo_debug "pm" "resume_drivebay.already_off" 195 | fi 196 | fi 197 | else 198 | # No saved state or state != off --> apply settings 199 | poweroff_drivebay "$1" 0 200 | fi 201 | 202 | rm -f "$BAYSTATEFILE" 2> /dev/null 203 | 204 | return 0 205 | } 206 | -------------------------------------------------------------------------------- /man-rdw/tlp-rdw.8: -------------------------------------------------------------------------------- 1 | .TH tlp-rdw 8 2024-04-01 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | tlp-rdw - disable Radio Device Wizard temporarily (until reboot). 5 | . 6 | .SH SYNOPSIS 7 | .B tlp-rdw \fR[\fIcommand\fR] 8 | . 9 | .SH COMMANDS 10 | . 11 | .TP 12 | .B disable 13 | Disable RDW actions. 14 | . 15 | .TP 16 | .B enable 17 | Enable RDW actions. 18 | . 19 | .TP 20 | 21 | Show RDW state. 22 | . 23 | .TP 24 | .B --version 25 | Print TLP version. 26 | . 27 | .SH SEE ALSO 28 | .BR tlp (8). 29 | . 30 | .SH AUTHOR 31 | (c) 2024 Thomas Koch 32 | -------------------------------------------------------------------------------- /man/bluetooth.1: -------------------------------------------------------------------------------- 1 | .TH bluetooth 1 2024-04-01 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | bluetooth - enable/disable internal bluetooth device 5 | . 6 | .SH SYNOPSIS 7 | .B bluetooth \fR[\fIcommand\fR] 8 | . 9 | .SH COMMANDS 10 | . 11 | .TP 12 | .B on 13 | Switch device on. 14 | . 15 | .TP 16 | .B off 17 | Switch device off. 18 | . 19 | .TP 20 | .B toggle 21 | Toggle device state. 22 | . 23 | .TP 24 | 25 | Show device state. 26 | . 27 | .TP 28 | .B --version 29 | Print TLP version. 30 | . 31 | .SH SEE ALSO 32 | .BR tlp (8). 33 | . 34 | .SH AUTHOR 35 | (c) 2025 Thomas Koch 36 | -------------------------------------------------------------------------------- /man/nfc.1: -------------------------------------------------------------------------------- 1 | .TH nfc 1 2024-04-01 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | nfc - enable/disable internal NFC device 5 | . 6 | .SH SYNOPSIS 7 | .B nfc \fR[\fIcommand\fR] 8 | . 9 | .SH COMMANDS 10 | . 11 | .TP 12 | .B on 13 | Switch device on. 14 | . 15 | .TP 16 | .B off 17 | Switch device off. 18 | . 19 | .TP 20 | .B toggle 21 | Toggle device state. 22 | . 23 | .TP 24 | 25 | Show device state. 26 | . 27 | .TP 28 | .B --version 29 | Print TLP version. 30 | . 31 | .SH SEE ALSO 32 | .BR bluetooth (1), 33 | .BR wifi (1), 34 | .BR wwan (1), 35 | .BR tlp (8). 36 | . 37 | .SH AUTHOR 38 | (c) 2025 Thomas Koch 39 | -------------------------------------------------------------------------------- /man/run-on-ac.1: -------------------------------------------------------------------------------- 1 | .TH run-on-ac 1 2020-01-31 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | run-on-ac - run command when on ac power 5 | . 6 | .SH SYNOPSIS 7 | .B run-on-ac \fR\fIcommand\fR \fR[\fIarg(s)]\fR 8 | . 9 | .SH SEE ALSO 10 | .BR run-on-bat (1), 11 | .BR tlp (8). 12 | . 13 | .SH AUTHOR 14 | (c) 2025 Thomas Koch 15 | -------------------------------------------------------------------------------- /man/run-on-bat.1: -------------------------------------------------------------------------------- 1 | .TH run-on-bat 1 2020-01-31 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | run-on-bat - run command when on battery power 5 | . 6 | .SH SYNOPSIS 7 | .B run-on-bat \fR\fIcommand\fR \fR[\fIarg(s)]\fR 8 | . 9 | .SH SEE ALSO 10 | .BR run-on-ac (1), 11 | .BR tlp (8). 12 | . 13 | .SH AUTHOR 14 | (c) 2025 Thomas Koch 15 | -------------------------------------------------------------------------------- /man/tlp-stat.8: -------------------------------------------------------------------------------- 1 | .TH tlp-stat 8 2025-03-22 "TLP 1.9.0" "Power Management" 2 | . 3 | .SH NAME 4 | tlp-stat - view power saving status 5 | . 6 | .SH SYNOPSIS 7 | .B tlp-stat \fB[\fIoptions\fR] [\fB--\fR \fICONFIG_PARAM\fR\fB=\fIvalue\fR "..."] 8 | . 9 | .SH DESCRIPTION 10 | View configuration, system information, kernel power saving tunables and battery 11 | data. Invocation without options shows all information categories. 12 | . 13 | .SH OPTIONS 14 | . 15 | .TP 16 | .B -b, --battery 17 | View battery data. Add \fB-v\fR to see battery voltages (if available). 18 | . 19 | .TP 20 | .B -c, --config 21 | View active configuration. 22 | . 23 | .TP 24 | .B --cdiff 25 | View the difference between defaults and user configuration. 26 | . 27 | .TP 28 | .B -d, --disk 29 | View disk device information. 30 | . 31 | .TP 32 | .B -e, --pcie 33 | View PCIe device information. Add \fB-v\fR to see device runtime status. 34 | . 35 | .TP 36 | .B -g, --graphics 37 | View graphics card information. 38 | . 39 | .TP 40 | .B -m, --mode 41 | Print current power mode. 42 | . 43 | .TP 44 | .B -p, --processor 45 | View processor information. For clarity the standard output shows only cpu0. 46 | Add \fB-v\fR to see all cpus. 47 | Add \fB-q\fR to see cpu driver state only. 48 | . 49 | .TP 50 | .B -q, --quiet 51 | Omit version header and show less information in the processor category. 52 | . 53 | .TP 54 | .B -r, --rfkill 55 | View radio device states. 56 | . 57 | .TP 58 | .B -s, --system 59 | View system information and TLP status. 60 | . 61 | .TP 62 | .B -t, --temp 63 | View temperatures and fan speed. 64 | Add \fB-v\fR to see all individual sensors. 65 | . 66 | .TP 67 | .B -u, --usb 68 | View USB device information. Add \fB-v\fR to see device runtime status. 69 | . 70 | .TP 71 | .B -v, --verbose 72 | Show more information in the battery, PCIe, processor, temperature and USB categories. 73 | . 74 | .TP 75 | .B --version 76 | Print TLP version. 77 | . 78 | .PP 79 | Diagnostics and debugging: 80 | . 81 | .TP 82 | .B -P, --pev 83 | Monitor power supply udev events. 84 | . 85 | .TP 86 | .B --psup 87 | View power supply diagnostics. 88 | . 89 | .TP 90 | .B -T, --trace 91 | View trace output. 92 | . 93 | .TP 94 | .B --udev 95 | Check if udev rules for power source changes and connecting USB devices 96 | are active. 97 | . 98 | .TP 99 | .B -w, --warn 100 | View warnings about SATA disks. 101 | . 102 | .TP 103 | .B -- \fR\fICONFIG_PARAM\fR\fB=\fIvalue\fR "..." 104 | Append configuration parameters to a command. These temporarily override 105 | the system configuration during execution of that command only and are not 106 | kept afterwards. 107 | Disclaimer: this feature exists for the sole purpose of test automation 108 | during TLP's development. It is provided as is and there is no support 109 | whatsoever. 110 | . 111 | .SH FILES 112 | .I /etc/tlp.conf 113 | .RS 114 | System-wide user configuration file, uncomment parameters here to override 115 | default settings and customization files below. 116 | .PP 117 | .RE 118 | .I /etc/tlp.d/*.conf 119 | .RS 120 | System-wide drop-in customization files, overriding defaults below. 121 | .PP 122 | .RE 123 | .I /usr/share/tlp/defaults.conf 124 | .RS 125 | Intrinsic default settings. DO NOT EDIT this file, instead use one of the above 126 | alternatives. 127 | .PP 128 | .RE 129 | .I /run/tlp/run.conf 130 | .RS 131 | Effective settings consolidated from all above files. DO NOT CHANGE this file, 132 | it is for reference only and regenerated on every invocation of TLP. 133 | .PP 134 | .RE 135 | .I /etc/default/tlp 136 | .RS 137 | Obsolete system-wide configuration file. DO NOT USE this file, it is 138 | evaluated as fallback only when /etc/tlp.conf is non-existent. 139 | . 140 | .SH SEE ALSO 141 | .BR tlp (8). 142 | . 143 | .SH AUTHOR 144 | (c) 2025 Thomas Koch 145 | -------------------------------------------------------------------------------- /man/tlp.8: -------------------------------------------------------------------------------- 1 | .TH tlp 8 2025-02-04 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | tlp - apply laptop power saving settings 5 | . 6 | .SH SYNOPSIS 7 | .B tlp \fIcommand\fR [\fIparameters\fR] [\fB--\fR \fICONFIG_PARAM\fR\fB=\fIvalue\fR "..."] 8 | . 9 | .SH DESCRIPTION 10 | \fBtlp\fR applies power saving settings manually and controls battery care 11 | features. 12 | . 13 | .SH COMMANDS 14 | . 15 | .TP 16 | .B start 17 | Start \fBtlp\fR and apply power saving profile for the actual power source. 18 | Also use to apply a changed configuration or to leave manual mode. 19 | . 20 | .TP 21 | .B bat 22 | Apply battery profile and enter manual mode. 23 | Manual mode means that changes to the power source will be ignored until the 24 | next reboot or \fBtlp start\fR is issued to resume automatic mode. 25 | . 26 | .TP 27 | .B true 28 | Alias for \fBbat\fR. 29 | . 30 | .TP 31 | .B ac 32 | Apply AC profile and enter manual mode. 33 | Manual mode means that changes to the power source will be ignored until the 34 | next reboot or \fBtlp start\fR is issued to resume automatic mode. 35 | . 36 | .TP 37 | .B false 38 | Alias for \fBac\fR. 39 | . 40 | .TP 41 | .B usb 42 | Enable autosuspend for all USB devices except those excluded by default or 43 | via configuration. 44 | . 45 | .TP 46 | .B bayoff 47 | Turn off optical drive in UltraBay/MediaBay. 48 | The drive may be re-enabled by pulling the eject lever or pushing the media 49 | eject button on newer models. 50 | . 51 | .TP 52 | .B setcharge\fR [\fIstart_threshold stop_threshold\fR] [\fIbattery\fR] 53 | Change battery charge thresholds temporarily. 54 | If your hardware supports only a stop threshold, set the start value to 0. 55 | Configured charge thresholds will be restored at the next boot or by using 56 | \fBtlp setcharge\fR again but without the threshold arguments. 57 | . 58 | .TP 59 | .B fullcharge\fR [\fIbattery\fR] 60 | Charge battery to full capacity. 61 | This is done by applying vendor presets to the charge thresholds temporarily. 62 | Configured charge thresholds will be restored at the next boot or by using 63 | \fBtlp setcharge\fR without the threshold arguments. 64 | . 65 | .TP 66 | .B chargeonce\fR [\fIbattery\fR] 67 | Charge battery to the stop charge threshold once. 68 | This is done by temporarily lifting the start charge threshold. 69 | The configured start charge threshold will be restored at the next boot or by 70 | using \fBtlp setcharge\fR without the threshold arguments. 71 | . 72 | .TP 73 | .B discharge\fR [\fIbattery\fR] [\fItarget_charge_level\fR] 74 | Force a complete or partial discharge of the battery while on AC power. 75 | . 76 | .TP 77 | .B recalibrate\fR [\fIbattery\fR] 78 | Perform a battery recalibration while on AC power: completely discharge the 79 | battery and recharge to 100%. The latter is done by temporarily applying vendor 80 | presets to the thresholds. Configured thresholds will be restored at the next 81 | boot or by using \fBtlp setcharge\fR. 82 | . 83 | .TP 84 | .B diskid 85 | Print disk ids for configured drives. 86 | . 87 | .TP 88 | .B --version 89 | Print TLP version. 90 | . 91 | .TP 92 | .B --\fR \fICONFIG_PARAM\fR\fB=\fIvalue\fR "..." 93 | Append configuration parameters to a command. These temporarily override 94 | the system configuration during execution of that command only and are not 95 | kept afterwards. 96 | Disclaimer: this feature exists for the sole purpose of test automation 97 | during TLP's development. It is provided as is and there is no support 98 | whatsoever. 99 | . 100 | .SH NOTES 101 | .PP 102 | Availability of the above \fBbattery care\fR commands and the possible charge 103 | threshold values always depend on laptop vendor or brand, Linux kernel version 104 | and TLP version. Check for actual availability, threshold ranges and battery 105 | names with \fBtlp-stat -b\fR. Follow the link in the \fBSEE ALSO\fR section for 106 | details. 107 | 108 | For laptops with two batteries, the secondary battery must be specified 109 | as a command parameter in order to select it. In many cases the main battery 110 | will be \fBBAT0\fR, the secondary battery \fBBAT1\fR. When in doubt, the output of 111 | \fBtlp-stat -b\fR, which lists all batteries, can help. 112 | . 113 | .SH EXAMPLES 114 | Change thresholds of the main battery to 70 / 90% temporarily: 115 | .IP 116 | tlp setcharge 70 90 117 | .PP 118 | Charge the secondary battery to full capacity: 119 | .IP 120 | tlp fullcharge BAT1 121 | .PP 122 | Recalibrate the main battery: 123 | .IP 124 | tlp recalibrate 125 | . 126 | .SH FILES 127 | .I /etc/tlp.conf 128 | .RS 129 | System-wide user configuration file, uncomment parameters here to override 130 | default settings and customization files below. 131 | .PP 132 | .RE 133 | .I /etc/tlp.d/*.conf 134 | .RS 135 | System-wide drop-in customization files, overriding defaults below. 136 | .PP 137 | .RE 138 | .I /usr/share/tlp/defaults.conf 139 | .RS 140 | Intrinsic default settings. DO NOT EDIT this file, instead use one of the above 141 | alternatives. 142 | .PP 143 | .RE 144 | .I /run/tlp/run.conf 145 | .RS 146 | Effective settings consolidated from all above files. DO NOT CHANGE this file, 147 | it is for reference only and regenerated on every invocation of TLP. 148 | .PP 149 | .RE 150 | .I /etc/default/tlp 151 | .RS 152 | Obsolete system-wide configuration file. DO NOT USE this file, it is 153 | evaluated only when /etc/tlp.conf is non-existent. 154 | . 155 | .SH EXIT STATUS 156 | On success, 0 is returned, a non-zero failure code otherwise. 157 | . 158 | .SH SEE ALSO 159 | .BR tlp-stat (8), 160 | .BR bluetooth (1), 161 | .BR nfc (1), 162 | .BR wifi (1), 163 | .BR wwan (1). 164 | . 165 | .PP 166 | .mso www.tmac 167 | .URL "https://linrunner.de/tlp" "Project hoempage: " 168 | .PP 169 | .URL "https://linrunner.de/tlp/settings/bc-vendors.html" \ 170 | "Battery care: " 171 | . 172 | .SH AUTHOR 173 | (c) 2025 Thomas Koch 174 | -------------------------------------------------------------------------------- /man/tlp.service.8: -------------------------------------------------------------------------------- 1 | .TH tlp.service 8 2021-12-18 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | . 5 | tlp.service - Initialize power saving at boot and cleanup upon shutdown 6 | . 7 | .SH SYNOPSIS 8 | .B tlp\&.service 9 | . 10 | .SH DESCRIPTION 11 | tlp.service executes the following tasks: 12 | .IP " 1." 4 13 | System boot-up: switch or restore radio states, apply power saving settings 14 | and charge thresholds. 15 | .IP " 2." 4 16 | System shutdown: save radio states and cleanup. 17 | . 18 | .SH FILES 19 | .I /lib/systemd/system-sleep/tlp 20 | .RS 21 | Applies power saving settings upon system suspend and resume. 22 | .SH SEE ALSO 23 | .BR tlp (8). 24 | . 25 | .SH NOTES 26 | Do \fInot\fR employ tlp.service to refresh power saving settings after a 27 | configuration change. 28 | Use \fBtlp start\fR instead. 29 | . 30 | .SH SEE ALSO 31 | .BR tlp (8). 32 | . 33 | .SH AUTHOR 34 | (c) 2025 Thomas Koch 35 | -------------------------------------------------------------------------------- /man/wifi.1: -------------------------------------------------------------------------------- 1 | .TH wifi 1 2024-04-01 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | wifi - enable/disable internal Wi-Fi device 5 | . 6 | .SH SYNOPSIS 7 | .B wifi \fR[\fIcommand\fR] 8 | . 9 | .SH COMMANDS 10 | . 11 | .TP 12 | .B on 13 | Switch device on. 14 | . 15 | .TP 16 | .B off 17 | Switch device off. 18 | . 19 | .TP 20 | .B toggle 21 | Toggle device state. 22 | . 23 | .TP 24 | 25 | Show device state. 26 | . 27 | .TP 28 | .B --version 29 | Print TLP version. 30 | . 31 | .SH SEE ALSO 32 | .BR bluetooth (1), 33 | .BR nfc (1), 34 | .BR wwan (1), 35 | .BR tlp (8). 36 | . 37 | .SH AUTHOR 38 | (c) 2025 Thomas Koch 39 | -------------------------------------------------------------------------------- /man/wwan.1: -------------------------------------------------------------------------------- 1 | .TH wwan 1 2024-04-01 "TLP 1.8.0" "Power Management" 2 | . 3 | .SH NAME 4 | wwan - enable/disable internal WWAN (3G/4G/5G) device 5 | . 6 | .SH SYNOPSIS 7 | .B wwan \fR[\fIcommand\fR] 8 | . 9 | .SH COMMANDS 10 | . 11 | .TP 12 | .B on 13 | Switch device on. 14 | . 15 | .TP 16 | .B off 17 | Switch device off. 18 | . 19 | .TP 20 | .B toggle 21 | Toggle device state. 22 | . 23 | .TP 24 | 25 | Show device state. 26 | . 27 | .TP 28 | .B --version 29 | Print TLP version. 30 | . 31 | SH SEE ALSO 32 | .BR bluetooth (1), 33 | .BR nfc (1), 34 | .BR wifi (1), 35 | .BR tlp (8). 36 | . 37 | .SH AUTHOR 38 | (c) 2025 Thomas Koch 39 | -------------------------------------------------------------------------------- /rename.conf: -------------------------------------------------------------------------------- 1 | CPU_HWP_ON_AC CPU_ENERGY_PERF_POLICY_ON_AC 2 | CPU_HWP_ON_BAT CPU_ENERGY_PERF_POLICY_ON_BAT 3 | SATA_LINKPWR_BLACKLIST SATA_LINKPWR_DENYLIST 4 | RUNTIME_PM_BLACKLIST RUNTIME_PM_DENYLIST 5 | RUNTIME_PM_DRIVER_BLACKLIST RUNTIME_PM_DRIVER_DENYLIST 6 | USB_BLACKLIST USB_DENYLIST 7 | USB_BLACKLIST_BTUSB USB_EXCLUDE_BTUSB 8 | USB_BLACKLIST_PHONE USB_EXCLUDE_PHONE 9 | USB_BLACKLIST_PRINTER USB_EXCLUDE_PRINTER 10 | USB_BLACKLIST_WWAN USB_EXCLUDE_WWAN 11 | USB_WHITELIST USB_ALLOWLIST 12 | -------------------------------------------------------------------------------- /tlp-pcilist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # tlp-pcilist - list pci devices with runtime pm mode and device class 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # Cmdline options 8 | # --verbose: show Runtime PM device status 9 | 10 | package tlp_pcilist; 11 | use strict; 12 | use warnings; 13 | 14 | # --- Modules 15 | use Getopt::Long; 16 | 17 | # --- Global vars 18 | my $verbose = 0; 19 | 20 | # --- Subroutines 21 | 22 | # Read content from a sysfile 23 | # $_[0]: input file 24 | # return: content / empty string if nonexistent or not readable 25 | sub catsysf { 26 | my $fname = "$_[0]"; 27 | my $sysval = ""; 28 | if (open my $sysf, "<", $fname) { 29 | chomp ($sysval = <$sysf>); 30 | close $sysf; 31 | } 32 | return $sysval; 33 | } 34 | 35 | # Read device driver from DEVICE/uevent 36 | # $_[0]: (sub)device base path 37 | # return: driver / empty string if uevent nonexistent or not readable 38 | sub getdriver { 39 | my $dpath = "$_[0]"; 40 | my $driver = ""; 41 | if ( open (my $sysf, "<", $dpath . "/uevent") ) { 42 | # read file line by line 43 | while (<$sysf>) { 44 | # match line content and return DRIVER= value 45 | if ( s/^DRIVER=(.*)/$1/ ) { 46 | chomp ($driver = $_); 47 | last; # break loop 48 | } 49 | } 50 | close ($sysf); 51 | } 52 | return $driver 53 | } 54 | 55 | # --- MAIN 56 | # parse arguments 57 | GetOptions ('verbose' => \$verbose); 58 | 59 | # Output device list with Runtime PM mode, status and device class 60 | foreach (`lspci -m`) { 61 | # parse lspci output: get short PCI(e) id and long description of device 62 | my ($dev, $classdesc) = /(\S+) \"(.+?)\"/; 63 | # join device path 64 | my $devp = "/sys/bus/pci/devices/0000:$dev"; 65 | # control file for Runtime PM 66 | my $devc = "$devp/power/control"; 67 | # status file for Runtime PM 68 | my $devs = "$devp/power/runtime_status"; 69 | # get device class 70 | my $class = catsysf ("$devp/class"); 71 | # get device driver 72 | my $driver = getdriver ("$devp") || "no driver"; 73 | 74 | if (-f $devc) { # control file exists 75 | # get device mode 76 | my $pmode = catsysf ("$devc"); 77 | if ( $verbose ) { 78 | # get device status 79 | my $pstatus = catsysf ("$devs"); 80 | # output device mode, status and data 81 | printf "%s/power/control = %-4s, runtime_status = %-9s (%s, %s, %s)\n", $devp, $pmode, $pstatus, $class, $classdesc, $driver; 82 | } else { 83 | # output device mode and data 84 | printf "%s/power/control = %-4s (%s, %s, %s)\n", $devp, $pmode, $class, $classdesc, $driver; 85 | } 86 | } else { # control file missing --> output device data only 87 | printf "%s/power/control = (not available) (%s, %s, %s)\n", $devp, $class, $classdesc, $driver; 88 | } 89 | } 90 | 91 | exit 0; 92 | -------------------------------------------------------------------------------- /tlp-rdw-nm.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-rdw - network manager dispatcher hook: 3 | # enable/disable radios on ifup/ifdown 4 | # 5 | # Copyright (c) 2025 Thomas Koch and others. 6 | # SPDX-License-Identifier: GPL-2.0-or-later 7 | 8 | # --- Source libraries 9 | 10 | for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do 11 | # shellcheck disable=SC1090 12 | . "$lib" || exit 70 13 | done 14 | 15 | # --- Functions 16 | 17 | check_switch_lock() { # switch listed radio devices 18 | # and time-lock them afterwards if actually switched 19 | # $1: device type where the event originated -- do nothing if its time-locked 20 | # $2: list of device types to switch 21 | # $3: on/off 22 | local sw_rc type 23 | 24 | # quit if the originating *radio* device is time-locked (not LAN) 25 | [ "$1" != "LAN" ] && check_timed_lock "${RDW_NM_LOCK}_$1" && return 1 26 | 27 | for type in $2; do 28 | if [ -n "$type" ] && [ "$type" != "$1" ]; then 29 | # device type is valid and not the originating one 30 | # --> do switch with state change lock 31 | device_switch "$type" "$3" "${RDW_NM_LOCK}_${type}" "$RDW_NM_LOCKTIME"; sw_rc=$? 32 | 33 | if [ "$sw_rc" = "4" ]; then 34 | # switch failed, NetworkManager may be "asleep" -> schedule repeat: 35 | # open a detached subshell, wait 2 secs and respawn ourselves, 36 | # no more than two repetitions. 37 | case "$repeats" in 38 | "") repeats="2" ;; 39 | 2) repeats="1" ;; 40 | *) repeats="" ;; 41 | esac 42 | if [ -n "$repeats" ]; then 43 | echo_debug "nm" "+++ tlp_rdw_nm(${iface}).${action}.nm_seems_asleep: repeats=$repeats" 44 | ( sleep 2; $0 "$iface" "$action" "$repeats" < /dev/null > /dev/null ) & 45 | do_exit 0 46 | fi 47 | fi 48 | fi 49 | done 50 | 51 | return 0 52 | } 53 | 54 | save_iface_type () { # save interface type -- $1: interface; $2: type 55 | # rc: 0=saved/1=error 56 | [ -d "$NETD/$1" ] && { printf '%s\n' "$2" > "$RUNDIR/${1}.itype"; } 2> /dev/null 57 | return $? 58 | } 59 | 60 | get_iface_type () { # get saved interface type -- $1: interface 61 | # rc: 0=saved state found/1=not found 62 | # retval: $itype 63 | local rc 64 | 65 | itype=$(read_sysf "$RUNDIR/${1}.itype"); rc=$? 66 | rm -f "$RUNDIR/${1}.itype" 67 | return $rc 68 | } 69 | 70 | echo_env () { 71 | # record environment 72 | if [ "$X_USB_ENV_TRACE" = "1" ]; then 73 | echo_debug "nm" "tlp_rdw_nm.env: $(printenv)" 74 | fi 75 | } 76 | 77 | # --- MAIN 78 | # shellcheck disable=SC2034 79 | _bgtask=1 80 | 81 | # read configuration: quit on error, trace allowed 82 | read_config 1 0 83 | 84 | # quit if TLP disabled 85 | check_tlp_enabled || do_exit 0 86 | 87 | # quit if RDW disabled 88 | check_run_flag "$RDW_KILL" && do_exit 0 89 | add_sbin2path 90 | 91 | # get args 92 | iface="$1" 93 | action="$2" 94 | repeats="$3" 95 | itype="" 96 | 97 | case "$action" in 98 | up|down) # interface up/down 99 | # quit for invalid interfaces 100 | if [ -z "$iface" ] || [ "$iface" = "none" ]; then 101 | echo_debug "nm" "tlp_rdw_nm($iface).${action}.no_interface" 102 | echo_env 103 | do_exit 0 104 | fi 105 | # quit for virtual interfaces (up action) 106 | if [ "$action" = "up" ] && readlink "$NETD/${iface}" | grep -q '/virtual/'; then 107 | # save type for down action where $NETD/$iface won't be there anymore 108 | save_iface_type "$iface" virtual 109 | echo_debug "nm" "tlp_rdw_nm($iface).${action}.ignore_virtual" 110 | echo_env 111 | do_exit 0 112 | fi 113 | 114 | # get saved interface type (down action) 115 | if [ "$action" = "down" ]; then 116 | get_iface_type "$iface" 117 | 118 | # quit for virtual interfaces 119 | if [ "$itype" = "virtual" ]; then 120 | echo_debug "nm" "tlp_rdw_nm($iface).${action}.ignore_virtual" 121 | do_exit 0 122 | fi 123 | fi 124 | 125 | echo_debug "nm" "+++ tlp_rdw_nm($iface).$action: repeats=$repeats" 126 | echo_env 127 | # shellcheck disable=SC2154 128 | if [ -n "$_addpath" ]; then 129 | # shellcheck disable=SC2154 130 | echo_debug "path" "PATH=${_oldpath}[${_addpath}]" 131 | else 132 | # shellcheck disable=SC2154 133 | echo_debug "path" "PATH=${_oldpath}" 134 | fi 135 | 136 | # determine interface type 137 | if [ -n "$itype" ]; then 138 | # saved type available (down action) 139 | echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [saved]" 140 | 141 | elif cmd_exists "$NMCLI"; then 142 | # no saved type but nmcli is available 143 | # --> check if nmcli dev output matches interface 144 | itype="$($NMCLI dev | awk '$1 ~ /^'"$iface"'$/ { print $2; }')" 145 | 146 | if [ -z "$itype" ]; then 147 | # iface is not found in nmcli dev output: many WWAN devices have 148 | # different devices for control and the actual network connection 149 | # --> check if interface matches a WWAN device 150 | get_wwan_ifaces 151 | # shellcheck disable=SC2154 152 | if wordinlist "$iface" "$_wanifaces"; then 153 | itype="wwan" 154 | else 155 | # fallback: 156 | # if interface type detection with nmcli failed, then try to 157 | # deduct it using interface name: it can happen if e.g. 158 | # usb network card is unplugged 159 | case "$iface" in 160 | en* | eth*) 161 | itype="ethernet" 162 | ;; 163 | 164 | wl*) 165 | itype="wifi" 166 | ;; 167 | 168 | ww*) 169 | itype="wwan" 170 | ;; 171 | 172 | *) 173 | itype="unknown" 174 | ;; 175 | esac 176 | fi 177 | fi 178 | 179 | # save interface type (up action) 180 | [ "$action" = "up" ] && save_iface_type "$iface" "$itype" 181 | 182 | echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [nmcli]" 183 | 184 | else 185 | # nmcli is not available 186 | itype="unknown" 187 | echo_debug "nm" "tlp_rdw_nm($iface).${action}: type=$itype [none]" 188 | fi 189 | 190 | case "$action" in 191 | up) # interface up, disable configured interfaces 192 | 193 | case $itype in 194 | *ethernet) 195 | check_switch_lock LAN "$DEVICES_TO_DISABLE_ON_LAN_CONNECT" off 196 | ;; 197 | 198 | *wireless|wifi) 199 | check_switch_lock wifi "$DEVICES_TO_DISABLE_ON_WIFI_CONNECT" off 200 | ;; 201 | 202 | gsm|wwan) 203 | check_switch_lock wwan "$DEVICES_TO_DISABLE_ON_WWAN_CONNECT" off 204 | ;; 205 | esac 206 | ;; # up 207 | 208 | down) # interface down, enable configured interfaces 209 | case $itype in 210 | *ethernet) 211 | check_switch_lock LAN "$DEVICES_TO_ENABLE_ON_LAN_DISCONNECT" on 212 | ;; 213 | 214 | *wireless|wifi) 215 | check_switch_lock wifi "$DEVICES_TO_ENABLE_ON_WIFI_DISCONNECT" on 216 | ;; 217 | 218 | gsm|wwan) 219 | check_switch_lock wwan "$DEVICES_TO_ENABLE_ON_WWAN_DISCONNECT" on 220 | ;; 221 | esac 222 | ;; # down 223 | 224 | esac 225 | ;; # up/down 226 | 227 | *) 228 | # other calls: do nothing 229 | ;; 230 | 231 | esac # action 232 | 233 | do_exit 0 234 | -------------------------------------------------------------------------------- /tlp-rdw-udev.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-rdw - handle dock/undock events 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # --- Source libraries 8 | 9 | for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do 10 | # shellcheck disable=SC1090 11 | . "$lib" || exit 70 12 | done 13 | 14 | # --- MAIN 15 | 16 | # read configuration: quit on error, trace allowed 17 | read_config 1 0 18 | 19 | check_tlp_enabled || do_exit 0 20 | check_run_flag "$RDW_KILL" && do_exit 0 21 | add_sbin2path 22 | 23 | # get power source 24 | get_sys_power_supply 25 | 26 | # get device/type 27 | ddev=/sys$1 28 | devtype=$2 29 | 30 | case $devtype in 31 | dock) 32 | # check if type is "dock_station", quit if not 33 | type=$(read_sysf "$ddev/type") 34 | [ "$type" = "dock_station" ] || do_exit 0 35 | 36 | docked=$(read_sysf "$ddev/docked") 37 | action=$EVENT 38 | 39 | # shellcheck disable=SC2154 40 | echo_debug "udev" "+++ rdw_udev($devtype).$action dev=$ddev type=$type docked=$docked syspwr=$_syspwr" 41 | ;; 42 | 43 | usb_dock) 44 | # shellcheck disable=SC2153 45 | case $ACTION in 46 | add) action="dock" ;; 47 | remove) action="undock" ;; 48 | esac 49 | 50 | echo_debug "udev" "+++ rdw_udev($devtype).$action dev=$ddev syspwr=$_syspwr" 51 | ;; 52 | 53 | *) do_exit 0 ;; # unknown device type 54 | esac 55 | 56 | # quit if timed lock in progress 57 | if check_timed_lock "$RDW_DOCK_LOCK" ; then 58 | echo_debug "udev" "rdw_udev.locked" 59 | do_exit 0 60 | fi 61 | 62 | case $action in 63 | dock) # laptop was docked 64 | 65 | # lock for 2 seconds in case dock has multiple devices 66 | set_timed_lock "$RDW_DOCK_LOCK" "$RDW_NM_LOCKTIME" 67 | 68 | # enable configured radios (obey rdw nm locks too) 69 | for dev in $DEVICES_TO_ENABLE_ON_DOCK; do 70 | [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ 71 | && device_switch "$dev" on "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" 72 | done 73 | 74 | # disable configured radios (obey rdw nm locks too) 75 | for dev in $DEVICES_TO_DISABLE_ON_DOCK; do 76 | [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ 77 | && device_switch "$dev" off "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" 78 | done 79 | ;; 80 | 81 | undock) # laptop was undocked 82 | 83 | # lock for 2 seconds in case dock has multiple devices 84 | set_timed_lock "$RDW_DOCK_LOCK" "$RDW_NM_LOCKTIME" 85 | 86 | # enable configured radios (obey rdw nm locks too) 87 | for dev in $DEVICES_TO_ENABLE_ON_UNDOCK; do 88 | [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ 89 | && device_switch "$dev" on "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" 90 | done 91 | 92 | # disable configured radios (obey rdw nm locks too) 93 | for dev in $DEVICES_TO_DISABLE_ON_UNDOCK; do 94 | [ -n "$dev" ] && ! check_timed_lock "${RDW_NM_LOCK}_${dev}" \ 95 | && device_switch "$dev" off "${RDW_NM_LOCK}_${dev}" "$RDW_NM_LOCKTIME" 96 | done 97 | ;; 98 | 99 | *) ;; # unknown action -> do nothing 100 | esac 101 | 102 | do_exit 0 103 | -------------------------------------------------------------------------------- /tlp-rdw.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp-rdw - enable/disable RDW 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # --- Source libraries 8 | 9 | for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do 10 | # shellcheck disable=SC1090 11 | . "$lib" || exit 70 12 | done 13 | 14 | # --- MAIN 15 | # shellcheck disable=SC2034 16 | _bgtask=1 17 | 18 | carg1="$1" 19 | if [ "$carg1" = "--version" ]; then 20 | print_version 21 | exit 0 22 | fi 23 | 24 | # read configuration: quit on error, trace allowed 25 | read_config 1 0 26 | parse_args4config "$@" 27 | cprintf_init 28 | 29 | case $carg1 in 30 | "") 31 | if check_run_flag "$RDW_KILL"; then 32 | echo "tlp-rdw: disabled." 33 | else 34 | echo "tlp-rdw: enabled." 35 | fi 36 | ;; 37 | 38 | enable) 39 | check_root 40 | reset_run_flag "$RDW_KILL" 41 | echo "tlp-rdw: enabled." 42 | ;; 43 | 44 | disable) 45 | check_root 46 | set_run_flag "$RDW_KILL" 47 | echo "tlp-rdw: disabled." 48 | ;; 49 | 50 | *) 51 | echo "Usage: tlp-rdw [ enable | disable ]" 52 | do_exit 3 53 | 54 | esac 55 | 56 | do_exit 0 57 | -------------------------------------------------------------------------------- /tlp-rdw.rules.in: -------------------------------------------------------------------------------- 1 | # tlp-rdw - udev rules 2 | # 3 | # Copyright (c) 2025 Thomas Koch and others. 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | # --- Dock/undock events 7 | 8 | # ThinkPad Advanced Mini Dock (and all older models), ThinkPad UltraBase 9 | ACTION=="change", SUBSYSTEM=="platform", KERNEL=="dock.*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p dock" 10 | 11 | # ThinkPad Mini Dock (Plus) Series 3 12 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/100a/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 13 | 14 | # ThinkPad Pro Dock [P/N 40A1] 15 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/1012/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 16 | 17 | # ThinkPad Ultra Dock [P/N 40A2] 18 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/1010/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 19 | 20 | # ThinkPad OneLink Pro Dock (USB3 Gigabit LAN interface) [P/N 40X1E] 21 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/304b/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 22 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/304f/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 23 | 24 | # ThinkPad OneLink Dock [P/N 40X1A9] 25 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/3049/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 26 | 27 | # ThinkPad OneLink Dock Plus [P/N 40A4] 28 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/3054/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 29 | 30 | # ThinkPad Pro Dock "CS18" [P/N 40AH] 31 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/306f/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 32 | 33 | # ThinkPad USB-C Dock Gen 2 [P/N 40AS] 34 | ACTION=="add|remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="17ef/a396/*", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 35 | 36 | # Thunderbolt docks 37 | ACTION=="add|remove", SUBSYSTEM=="thunderbolt", ENV{DEVTYPE}=="thunderbolt_device", ENV{USB4_TYPE}=="hub", RUN+="@TLP_ULIB@/tlp-rdw-udev %p usb_dock" 38 | -------------------------------------------------------------------------------- /tlp-readconfs.in: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # tlp-readconfs - read all of TLP's config files 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # Cmdline options 8 | # --outfile : filepath to contain merged configuration 9 | # --notrace: disable trace 10 | # --cdiff: only show differences to the default 11 | # 12 | # Return codes 13 | # 0: ok 14 | # 5: tlp.conf missing 15 | # 6: defaults.conf missing 16 | 17 | package tlp_readconfs; 18 | use strict; 19 | use warnings; 20 | 21 | # --- Modules 22 | use File::Basename; 23 | use Getopt::Long; 24 | 25 | # --- Constants 26 | use constant CONF_USR => "@TLP_CONFUSR@"; 27 | use constant CONF_DIR => "@TLP_CONFDIR@"; 28 | use constant CONF_DEF => "@TLP_CONFDEF@"; 29 | use constant CONF_REN => "@TLP_CONFREN@"; 30 | use constant CONF_DPR => "@TLP_CONFDPR@"; 31 | use constant CONF_OLD => "@TLP_CONF@"; 32 | 33 | # Exit codes 34 | use constant EXIT_TLPCONF => 5; 35 | use constant EXIT_DEFCONF => 6; 36 | 37 | # --- Global vars 38 | my @config_val = (); # 2-dim array: parameter name, value, source, default-value 39 | my %config_idx = (); # hash: parameter name => index into the name-value array 40 | 41 | my %rename = (); # hash: OLD_PARAMETER => NEW_PARAMETER 42 | my $renrex; # compiled regex for renaming parameters 43 | my $do_rename = 0; # enable renaming (when $renrex not empty) 44 | my %dprmsg = (); # hash: PARAMETER => deprecated message 45 | 46 | my $notrace = 0; 47 | my $debug = 0; 48 | my $cdiff = 0; 49 | 50 | my $outfile; 51 | 52 | my $defsrc = basename (CONF_DEF); 53 | 54 | # --- Subroutines 55 | 56 | # Format and write debug message 57 | # @_: printf arguments including format string 58 | sub printf_debug { 59 | if ( ! $notrace && $debug ) { 60 | open (my $logpipe, "|-", "logger -p debug -t \"tlp\" --id=\$\$ --") || return 1; 61 | printf {$logpipe} @_; 62 | close ($logpipe); 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | # Store parameter name, value, source in array/hash 69 | # $_[0]: parameter name (non-null string) 70 | # $_[1]: parameter value (maybe null string) 71 | # $_[2]: 0=replace/1=append parameter value 72 | # $_[3]: parameter source e.g. filepath + line no. 73 | # $_[4]: 0=user config/1=default 74 | # return: 0=new name/1=known name 75 | sub store_name_value_source { 76 | my $name = $_[0]; 77 | my $value = $_[1]; 78 | my $append = $_[2]; 79 | my $source = $_[3]; 80 | my $is_def = $_[4]; 81 | 82 | $debug = 1 if ( $name eq "TLP_DEBUG" && $value =~ /\bcfg\b/ ); 83 | 84 | if ( defined $config_idx{$name} ) { 85 | # existing name 86 | if ( $append ) { 87 | # append value, source 88 | $config_val[$config_idx{$name}][1] .= " $value"; 89 | $config_val[$config_idx{$name}][2] .= " & $source"; 90 | } else { 91 | # replace value, source 92 | $config_val[$config_idx{$name}][1] = $value; 93 | $config_val[$config_idx{$name}][2] = $source; 94 | } 95 | 96 | printf_debug ("tlp-readconfs.replace [%s]: %s=\"%s\" %s\n", $config_idx{$name}, $name, $value, $source); 97 | } else { 98 | # new name --> store name, value, source and hash name 99 | if ( $is_def ) { 100 | #save value as default 101 | push(@config_val, [$name, $value, $source, $value]); 102 | } else { 103 | # save value as user config 104 | push(@config_val, [$name, $value, $source, ""]); 105 | } 106 | $config_idx{$name} = $#config_val; 107 | 108 | printf_debug ("tlp-readconfs.insert [%s]: %s=\"%s\" %s\n", $#config_val, $name, $value, $source); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | # Parse whole config file and store parameters 115 | # $_[0]: filepath 116 | # $_[1]: 0=no change/1=rename parameters 117 | # return: 0=ok/1=file non-existent 118 | sub parse_configfile { 119 | my $fname = $_[0]; 120 | my $do_ren = $_[1]; 121 | my $source; 122 | my $is_def; 123 | if ( $fname eq CONF_DEF ) { 124 | $source = $defsrc; 125 | $is_def = 1; 126 | } else { 127 | $source = $fname; 128 | $is_def = 0; 129 | } 130 | 131 | open (my $cf, "<", $fname) || return 1; 132 | 133 | my $ln = 0; 134 | while ( my $line = <$cf> ) { 135 | # strip newline 136 | chomp $line; 137 | $ln += 1; 138 | # strip comments: everything after '#' but not when '#' is quoted, i.e. followed by a closing quote ('"') 139 | # note: opening quote is handled by the regex below 140 | $line =~ s/#(?=[^"]*$).*$//; 141 | # strip trailing spaces 142 | $line =~ s/\s+$//; 143 | # select lines with format 'PARAMETER=value' or 'PARAMETER="value"' 144 | if ( $line =~ /^(?[A-Z_]+[0-9]*)(?(=|\+=))(?:(?[-0-9a-zA-Z _.:]*)|"(?[-0-9a-zA-Z _.:]*)")\s*$/ ) { 145 | my $name = $+{name}; 146 | if ( $do_ren ) { 147 | # rename PARAMETER 148 | $name =~ s/$renrex/$rename{$1}/; 149 | } 150 | my $value = $+{val_dquoted} // $+{val_bare}; 151 | my $append = $+{op} eq "+="; 152 | store_name_value_source ($name, $value, $append, $source . " L" . sprintf ("%04d", $ln), $is_def ); 153 | } 154 | } 155 | close ($cf); 156 | 157 | return 0; 158 | } 159 | 160 | # Output all stored parameter name, value to a file 161 | # or parameter name, value, source to stdout 162 | # $_[0]: filepath (without argument the output will be written to stdout) 163 | # return: 0=ok/1=file open error 164 | sub write_runconf { 165 | my $fname = $_[0]; 166 | 167 | my $runconf; 168 | if ( ! $fname ) { 169 | $runconf = *STDOUT; 170 | } else { 171 | open ($runconf, ">", $fname) || return 1; 172 | } 173 | 174 | foreach ( @config_val ) { 175 | my ($name, $value, $source, $default) = @$_; 176 | if ( $runconf eq *STDOUT ) { 177 | my $msg = ""; 178 | # stdout: check for deprecated message 179 | if ( defined $dprmsg{$name} ) { 180 | $msg = " #! $dprmsg{$name}"; 181 | } 182 | # --cdiff: do not show user config lines matching the default 183 | if ( ! $cdiff || $value ne $default ) { 184 | printf {$runconf} "%s: %s=\"%s\"%s\n", $source, $name, $value, $msg; 185 | } 186 | } else { 187 | printf {$runconf} "%s=\"%s\"\n", $name, $value; 188 | } 189 | } 190 | close ($runconf); 191 | 192 | return 0 193 | } 194 | 195 | # Parse parameter renaming rules from file 196 | # $_[0]: rules file 197 | # return: 0=ok/1=file non-existent 198 | sub parse_renfile { 199 | my $fname = $_[0]; 200 | 201 | open (my $rf, "<", $fname) || return 1; 202 | 203 | # accumulate renaming 204 | while ( my $line = <$rf> ) { 205 | chomp $line; 206 | # select lines with format 'OLD_PARAMETERNEW_PARAMETER' 207 | if ( $line =~ /^(?[A-Z_]+[0-9]*)\s+(?[A-Z_]+[0-9]*)\s*$/ ) { 208 | my $old_name = $+{old_name}; 209 | my $new_name = $+{new_name}; 210 | $rename{$old_name} = $new_name; 211 | } 212 | } 213 | close ($rf); 214 | 215 | if ( keys %rename > 0 ) { 216 | # renaming hash not empty --> compile OLD_PARAMETER keys to match regex 217 | $renrex = qr/^(@{[join '|', map { quotemeta($_) } keys %rename]})$/; 218 | # enable renaming 219 | $do_rename = 1; 220 | } 221 | 222 | return 0; 223 | } 224 | 225 | # Parse deprecated parameters and messages from file 226 | # $_[0]: parameters file 227 | # return: 0=ok/1=file non-existent 228 | sub parse_dprfile { 229 | my $fname = $_[0]; 230 | 231 | open (my $df, "<", $fname) || return 1; 232 | 233 | # accumulate deprecated params and mesgs 234 | while ( my $line = <$df> ) { 235 | chomp $line; 236 | # select lines with format 'PARAMETER# message' 237 | if ( $line =~ /^(?[A-Z_]+[0-9]*)\s+#\s+(?.*)$/ ) { 238 | my $param_name = $+{param_name}; 239 | my $param_msg = $+{param_msg}; 240 | $dprmsg{$param_name} = $param_msg; 241 | } 242 | } 243 | close ($df); 244 | 245 | return 0; 246 | } 247 | 248 | # --- MAIN 249 | # parse arguments 250 | GetOptions ('outfile=s' => \$outfile, 'notrace' => \$notrace, 'cdiff' => \$cdiff); 251 | 252 | # read parameter renaming rules 253 | parse_renfile (CONF_REN); 254 | 255 | # read deprecated parameter messages 256 | parse_dprfile (CONF_DPR); 257 | 258 | # 1. read intrinsic defaults (no renaming) 259 | parse_configfile (CONF_DEF, 0) == 0 || exit EXIT_DEFCONF; 260 | 261 | # 2. read customization (with renaming) 262 | foreach my $conffile ( grep { -f } glob CONF_DIR . "/*.conf" ) { 263 | parse_configfile ($conffile, $do_rename); 264 | } 265 | 266 | # 3. read user settings (with renaming) 267 | parse_configfile (CONF_USR, $do_rename) == 0 268 | || parse_configfile (CONF_OLD, $do_rename) == 0 || exit EXIT_TLPCONF; 269 | 270 | # save result 271 | write_runconf ($outfile); 272 | 273 | exit 0; 274 | -------------------------------------------------------------------------------- /tlp-rf.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp - switch bluetooth/nfc/wifi/wwan on/off 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # --- Source libraries 8 | 9 | for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/25-tlp-func-rf @TLP_FLIB@/30-tlp-func-rf-sw; do 10 | # shellcheck disable=SC1090 11 | . "$lib" 12 | done 13 | 14 | # --- MAIN 15 | carg1="$1" 16 | if [ "$carg1" = "--version" ]; then 17 | print_version 18 | exit 0 19 | fi 20 | 21 | # read configuration: quit on error, trace allowed 22 | read_config 1 0 23 | parse_args4config "$@" 24 | cprintf_init 25 | 26 | add_sbin2path 27 | self=${0##*/} 28 | 29 | case $self in 30 | bluetooth|nfc|wifi|wwan) 31 | case $carg1 in 32 | on) 33 | device_switch "$self" on 34 | # shellcheck disable=SC2154 35 | echo_device_state "$self" "$_devs" 36 | ;; 37 | 38 | off) 39 | device_switch "$self" off 40 | echo_device_state "$self" "$_devs" 41 | ;; 42 | 43 | toggle) 44 | device_switch "$self" toggle 45 | echo_device_state "$self" "$_devs" 46 | ;; 47 | 48 | *) 49 | device_state "$self" 50 | echo_device_state "$self" "$_devs" 51 | ;; 52 | esac 53 | ;; 54 | 55 | *) 56 | cecho "Error: unknown device type \"$self\"." 1>&2 57 | do_exit 1 58 | ;; 59 | esac 60 | 61 | do_exit 0 62 | -------------------------------------------------------------------------------- /tlp-run-on.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp - run commands depending on power source 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # --- Source libraries 8 | 9 | # shellcheck disable=SC2043 10 | for lib in @TLP_TLIB@/tlp-func-base; do 11 | # shellcheck disable=SC1090 12 | . "$lib" 13 | done 14 | 15 | # --- MAIN 16 | self=${0##*/} 17 | 18 | cmd=$1 19 | if [ -z "$cmd" ]; then 20 | cecho "Usage: $self command [arg(s)]" 1>&2 21 | exit 1 22 | fi 23 | if ! cmd_exists "$cmd"; then 24 | cecho "Error: \"$cmd\" not found." 1>&2 25 | exit 2 26 | fi 27 | shift 28 | 29 | case $self in 30 | run-on-ac) 31 | if get_power_mode; then 32 | $cmd "$@" 33 | fi 34 | ;; 35 | 36 | run-on-bat) 37 | if ! get_power_mode; then 38 | $cmd "$@" 39 | fi 40 | ;; 41 | 42 | *) 43 | cecho "Error: unknown mode $self." 1>&2 44 | exit 1 45 | ;; 46 | esac 47 | -------------------------------------------------------------------------------- /tlp-sleep: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # tlp - systemd suspend/resume hook 4 | # 5 | # Copyright (c) 2025 Thomas Koch and others. 6 | # This software is licensed under the GPL v2 or later. 7 | 8 | case $1 in 9 | pre) tlp suspend ;; 10 | post) tlp resume ;; 11 | esac 12 | -------------------------------------------------------------------------------- /tlp-sleep.elogind: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | case "${1-}" in 3 | 'pre') 4 | exec tlp suspend 5 | ;; 6 | 7 | 'post') 8 | exec tlp resume 9 | ;; 10 | 11 | *) 12 | exit 64 13 | ;; 14 | esac 15 | -------------------------------------------------------------------------------- /tlp-usb-udev.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # tlp - handle added usb devices 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | # Remark: the calling udev rule is triggered for "base" devices only, 8 | # not for the corresponding subdevices. 9 | 10 | # --- Source libraries 11 | 12 | for lib in @TLP_TLIB@/tlp-func-base @TLP_FLIB@/15-tlp-func-disk @TLP_FLIB@/20-tlp-func-usb; do 13 | # shellcheck disable=SC1090 14 | . "$lib" || exit 70 15 | done 16 | 17 | # --- MAIN 18 | # shellcheck disable=SC2034 19 | _bgtask=1 20 | 21 | # read configuration: quit on error, trace allowed 22 | read_config 1 0 23 | 24 | # quit if TLP disabled 25 | check_tlp_enabled || do_exit 0 26 | 27 | if [ "$X_USB_ENV_TRACE" = "1" ]; then 28 | echo_debug "usb" "tlp_usb_udev.env = $(printenv)" 29 | fi 30 | 31 | case "$1" in 32 | usb) # usb devices in general 33 | [ "$USB_AUTOSUSPEND" = "1" ] || do_exit 0 34 | # quit if usb autosuspend disabled 35 | 36 | # USB autosuspend has two principal operation modes: 37 | # 38 | # Mode 1 (optional): 39 | # - System startup is handled by tlp-functions:set_usb_suspend() 40 | # - Startup completion is signaled by "flag file" $USB_DONE 41 | # - Newly added devices are handled by this udev script 42 | # - Mode 1 is enabled by the private config variable X_TLP_USB_MODE=1 43 | # 44 | # Mode 2 (default): 45 | # - Everything - including system startup, but not shutdown - is handled by this udev script 46 | 47 | # quit if mode 1 and no startup completion flag 48 | [ "$X_TLP_USB_MODE" = "1" ] && ! check_run_flag "$USB_DONE" && do_exit 0 49 | 50 | 51 | # handle device 52 | usb_suspend_device "/sys$2" "udev" 53 | ;; 54 | 55 | disk) # (s)ata disks attached via usb 56 | get_power_mode; pwrmode=$? 57 | dev="${2##*/block/}" 58 | set_ahci_disk_runtime_pm $pwrmode "$dev" 59 | set_disk_apm_level $pwrmode "$dev" 60 | set_disk_spindown_timeout $pwrmode "$dev" 61 | set_disk_iosched "$dev" 62 | ;; 63 | esac 64 | 65 | do_exit 0 66 | -------------------------------------------------------------------------------- /tlp-usblist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # tlp-usblist - list usb device info with autosuspend attributes 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | package tlp_usblist; 8 | use strict; 9 | use warnings; 10 | 11 | # --- Constants 12 | use constant USBD => "/sys/bus/usb/devices"; 13 | 14 | # --- Modules 15 | use Getopt::Long; 16 | 17 | # --- Global vars 18 | my %usbdevices; 19 | 20 | my $verbose = 0; 21 | 22 | # --- Subroutines 23 | 24 | # Read content from a sysfile 25 | # $_[0]: input file 26 | # return: content / empty string if nonexistent or not readable 27 | sub catsysf { 28 | my $fname = "$_[0]"; 29 | my $sysval = ""; 30 | if ( open (my $sysf, "<", $fname) ) { 31 | chomp ($sysval = <$sysf>); 32 | close ($sysf); 33 | } 34 | return $sysval; 35 | } 36 | 37 | # Read device driver from DEVICE/uevent 38 | # $_[0]: (sub)device base path 39 | # return: driver / empty string if uevent nonexistent or not readable 40 | sub getdriver { 41 | my $dpath = "$_[0]"; 42 | my $driver = ""; 43 | if ( open (my $sysf, "<", $dpath . "/uevent") ) { 44 | # read file line by line 45 | while (<$sysf>) { 46 | # match line content and return DRIVER= value 47 | if ( s/^DRIVER=(.*)/$1/ ) { 48 | chomp ($driver = $_); 49 | last; # break loop 50 | } 51 | } 52 | close ($sysf); 53 | } 54 | return $driver 55 | } 56 | 57 | # Get drivers associated with USB device by iterating subdevices 58 | # $_[0]: device base path 59 | # return: driver list / "no driver" if none found 60 | sub usbdriverlist { 61 | my $dpath = "$_[0]"; 62 | my $driverlist = ""; 63 | # iterate subdevices 64 | foreach my $subdev (glob $dpath . "/*:*") { 65 | # get subdevice driver 66 | my $driver = getdriver ("$subdev"); 67 | if ( $driver ) { 68 | if (index ($driverlist, $driver) == -1) { 69 | if ($driverlist) { $driverlist = $driverlist . ", " . $driver; } 70 | else { $driverlist = $driver; } 71 | } # if index 72 | } # if $driver 73 | } # foreach $subdev 74 | 75 | if (! $driverlist) { $driverlist = "no driver"; } 76 | return $driverlist 77 | } 78 | 79 | # --- MAIN 80 | # parse arguments 81 | GetOptions ('verbose' => \$verbose); 82 | 83 | # Read USB device tree attributes as arrays into %usbdevices hash, indexed by Bus_Device 84 | foreach my $udev (grep { ! /:/ } glob USBD . "/*") { 85 | my $usbv = "(autosuspend not available)"; 86 | 87 | # get device id 88 | my $usbk = sprintf ("%03d_%03d", catsysf ("$udev/busnum"), catsysf ("$udev/devnum") ); 89 | 90 | # get device mode and timeout 91 | if ( length (my $ptimeout = catsysf ("$udev/power/autosuspend_delay_ms")) 92 | && length (my $pmode = catsysf ("$udev/power/control")) ) { 93 | if ( $verbose ) { 94 | # get device status 95 | my $pstatus = catsysf ("$udev/power/runtime_status"); 96 | # format: device mode, timeout, status 97 | $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d, runtime_status = %-9s", $pmode . ",", $ptimeout, $pstatus); 98 | } else { 99 | # format: device mode, timeout 100 | $usbv = sprintf ("control = %-5s autosuspend_delay_ms = %4d", $pmode . ",", $ptimeout); 101 | } 102 | } 103 | 104 | # store formatted result in hash 105 | @{$usbdevices{$usbk}} = ($udev, $usbv); 106 | } 107 | 108 | # Output device list with attributes and drivers 109 | foreach (`lsusb 2> /dev/null`) { 110 | my ($bus, $dev, $usbid, $desc) = /Bus (\S+) Device (\S+): ID (\S+)[ ]+(.*)/; 111 | if (length ($bus) and length ($dev) and length ($usbid) ) { 112 | my $usbk = $bus . "_" . $dev; 113 | $desc =~ s/\s+$//; 114 | $desc ||= ""; 115 | print "Bus $bus Device $dev ID $usbid $usbdevices{$usbk}[1] -- $desc (" 116 | . usbdriverlist($usbdevices{$usbk}[0]) . ")\n"; 117 | } 118 | } 119 | 120 | exit 0; 121 | -------------------------------------------------------------------------------- /tlp.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # tlp - system startup/shutdown 4 | # 5 | # Copyright (c) 2025 Thomas Koch and others. 6 | # This software is licensed under the GPL v2 or later. 7 | # 8 | # chkconfig: 2345 98 01 9 | 10 | ### BEGIN INIT INFO 11 | # Provides: tlp 12 | # Required-Start: $remote_fs 13 | # Required-Stop: $remote_fs 14 | # Default-Start: 2 3 4 5 15 | # Default-Stop: 0 1 6 16 | # Short-Description: tlp start/stop script 17 | # Description: Initialize tlp 18 | ### END INIT INFO 19 | 20 | [ -r /lib/lsb/init-functions ] && . /lib/lsb/init-functions 21 | 22 | TLP=/usr/sbin/tlp 23 | [ -x $TLP ] || exit 0 24 | 25 | case "$1" in 26 | status) 27 | tlp-stat -s 28 | ;; 29 | 30 | start|\ 31 | stop|\ 32 | restart|\ 33 | force-reload) 34 | $TLP init $1 35 | ;; 36 | 37 | *) 38 | echo "Usage: $0 start|stop|restart|force-reload|status" 1>&2 39 | exit 3 40 | ;; 41 | esac 42 | 43 | exit 0 44 | -------------------------------------------------------------------------------- /tlp.rules.in: -------------------------------------------------------------------------------- 1 | # tlp - udev rules 2 | # 3 | # Copyright (c) 2025 Thomas Koch and others. 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | # handle change of power source ac/bat, ignore input device batteries 7 | ACTION=="change", SUBSYSTEM=="power_supply", KERNEL!="hidpp_battery*", RUN+="@TLP_SBIN@/tlp auto" 8 | 9 | # handle added usb devices (exclude subdevices via DRIVER=="USB") 10 | ACTION=="add", SUBSYSTEM=="usb", DRIVER=="usb", ENV{DEVTYPE}=="usb_device", RUN+="@TLP_ULIB@/tlp-usb-udev usb %p" 11 | 12 | # handle added usb disk devices (exclude partitions via ENV{DEVTYPE}=="disk") 13 | ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_BUS}=="usb", RUN+="@TLP_ULIB@/tlp-usb-udev disk %p" 14 | ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_USB_TYPE}=="disk", RUN+="@TLP_ULIB@/tlp-usb-udev disk %p" 15 | -------------------------------------------------------------------------------- /tlp.service.in: -------------------------------------------------------------------------------- 1 | # tlp - systemd startup/shutdown service 2 | # 3 | # Copyright (c) 2025 Thomas Koch and others. 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | [Unit] 7 | Description=TLP system startup/shutdown 8 | After=multi-user.target NetworkManager.service 9 | Before=shutdown.target 10 | Documentation=https://linrunner.de/tlp 11 | 12 | [Service] 13 | Type=oneshot 14 | RemainAfterExit=yes 15 | ExecStart=@TLP_SBIN@/tlp init start 16 | ExecReload=@TLP_SBIN@/tlp start 17 | ExecStop=@TLP_SBIN@/tlp init stop 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /tlp.upstart.in: -------------------------------------------------------------------------------- 1 | # tlp - system startup/shutdown 2 | # 3 | # Copyright (c) 2025 Thomas Koch and others. 4 | # This software is licensed under the GPL v2 or later. 5 | 6 | description "tlp" 7 | 8 | start on ( virtual-filesystems and runlevel [2345] ) 9 | stop on runlevel [!2345] 10 | 11 | env TLP=@TLP_SBIN@/tlp 12 | 13 | pre-start script 14 | [ -x $TLP ] || exit 4 15 | $TLP init start 16 | 17 | end script 18 | 19 | post-stop script 20 | [ -x $TLP ] || exit 4 21 | $TLP init stop 22 | 23 | end script 24 | 25 | 26 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_cros-ec-v2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for laptops with ChromeOS EC (cmd v2) 3 | # Requirements: 4 | # * Hardware: 5 | # 1. Chromebook 6 | # 2. Framework Laptop 13/16 Intel/AMD 7 | # * Software: 8 | # 1.,2.: cros_charge-control (Linux 6.12.8/6.13+) 9 | # 2.: out-of-tree kernel module framework_laptop 10 | # Copyright (c) 2025 Thomas Koch . 11 | # SPDX-License-Identifier: GPL-2.0-or-later 12 | # 13 | $ # +++ ChromeOS EC (cmd v2) laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 14 | $ # 15 | $ # --- tlp start 16 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata} STOP_CHARGE_THRESH_${bata} START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 19 | TLP started in AC mode (auto). 20 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="0" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/" 21 | Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Battery skipped. 22 | TLP started in AC mode (auto). 23 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/" 24 | Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Battery skipped. 25 | TLP started in AC mode (auto). 26 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="86" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 27 | TLP started in AC mode (auto). 28 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 29 | TLP started in AC mode (auto). 30 | $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 31 | TLP started in AC mode (auto). 32 | $ # 33 | $ # --- tlp setcharge w/o arguments 34 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" 35 | Setting temporary charge threshold(s) for battery BATA: 36 | stop = 100 (no change) 37 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="0" 2>&1 | sed -r "s/${bata}/BATA/" 38 | Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Aborted. 39 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" 2>&1| sed -r "s/${bata}/BATA/" 40 | Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Aborted. 41 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="ABCDE" STOP_CHARGE_THRESH_${bata}="XYZZY" 2>&1 | sed -r "s/${bata}/BATA/" 42 | Error in configuration at STOP_CHARGE_THRESH_BATA="XYZZY": not specified, invalid or out of range (1..100). Aborted. 43 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" 44 | Setting temporary charge threshold(s) for battery BATA: 45 | stop = 100 (no change) 46 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="86" 2>&1 | sed -r "s/${bata}/BATA/" 47 | Setting temporary charge threshold(s) for battery BATA: 48 | stop = 86 49 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="80" 2>&1 | sed -r "s/${bata}/BATA/" 50 | Setting temporary charge threshold(s) for battery BATA: 51 | stop = 80 52 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="na" STOP_CHARGE_THRESH_${bata}="80" 2>&1 | sed -r "s/${bata}/BATA/" 53 | Setting temporary charge threshold(s) for battery BATA: 54 | stop = 80 (no change) 55 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" 56 | Setting temporary charge threshold(s) for battery BATA: 57 | stop = 100 58 | $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" 59 | Error: there is no hardware driver support for charge thresholds. 60 | $ # 61 | $ # --- tlp setcharge w/ arguments 62 | $ sudo tlp setcharge 60 100 ${bata} -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 63 | Setting temporary charge threshold(s) for battery BATA: 64 | stop = 100 (no change) 65 | $ sudo tlp setcharge 0 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 66 | Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. 67 | $ sudo tlp setcharge 0 101 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 68 | Error: stop charge threshold (101) for BATA is not specified, invalid or out of range (1..100). Aborted. 69 | $ sudo tlp setcharge ABCDE 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 70 | Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. 71 | $ sudo tlp setcharge 0 XYZZY -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 72 | Error: stop charge threshold (XYZZY) for BATA is not specified, invalid or out of range (1..100). Aborted. 73 | $ sudo tlp setcharge 97 100 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 74 | Setting temporary charge threshold(s) for battery BATA: 75 | stop = 100 (no change) 76 | $ sudo tlp setcharge 0 66 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 77 | Setting temporary charge threshold(s) for battery BATA: 78 | stop = 66 79 | $ sudo tlp setcharge 0 60 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 80 | Setting temporary charge threshold(s) for battery BATA: 81 | stop = 60 82 | $ sudo tlp setcharge 0 60 -- ${xinc} X_THRESH_SIMULATE_STOP="100" 2>&1 | sed -r "s/${bata}/BATA/" 83 | Setting temporary charge threshold(s) for battery BATA: 84 | stop = 60 85 | $ sudo tlp setcharge 0 60 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 86 | Setting temporary charge threshold(s) for battery BATA: 87 | stop = 60 (no change) 88 | $ sudo tlp setcharge DEF DEF -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 89 | Setting temporary charge threshold(s) for battery BATA: 90 | stop = 100 91 | $ sudo tlp setcharge ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 92 | Error: battery BATB not present. 93 | $ sudo tlp setcharge 0 3 ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 94 | Error: battery BATB not present. 95 | $ sudo tlp setcharge XYZZY ABCDE ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 96 | Error: battery BATB not present. 97 | $ # 98 | $ # --- tlp-stat 99 | $ sudo tlp-stat -b -- ${xinc} | grep "${bata}/charge_control_end_threshold" | sed -r "s/${bata}/BATA/" 100 | /sys/class/power_supply/BATA/charge_control_end_threshold = 100 [%] 101 | $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep "${bata}/charge_control_end_threshold" | sed -r "s/${bata}/BATA/" 102 | /sys/class/power_supply/BATA/charge_control_end_threshold = (not available) [%] 103 | $ # 104 | $ # --- Reset test machine to configured thresholds 105 | $ sudo tlp setcharge ${bata} -- ${xinc} > /dev/null 2>&1 106 | $ # 107 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_cros-ec-v3: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for laptops with ChromeOS EC (cmd v3) 3 | # Requirements: 4 | # * Hardware: yet unknown, possibly Chromebooks from July 2021 5 | # * Software: kernel module cros_charge-control (Linux 6.12.8/6.13+) 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # +++ ChromeOS EC (cmd v3) laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | $ # 11 | $ # --- tlp start 12 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}= STOP_CHARGE_THRESH_${bata}= START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 13 | TLP started in AC mode (auto). 14 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/" 17 | Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or out of range (0..99). Battery skipped. 18 | TLP started in AC mode (auto). 19 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="0" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/" 20 | Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Battery skipped. 21 | TLP started in AC mode (auto). 22 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/" 23 | Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Battery skipped. 24 | TLP started in AC mode (auto). 25 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="97" STOP_CHARGE_THRESH_${bata}="97" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 2>&1 | sed -r "s/${bata}/BATA/g" 26 | Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BATA. Battery skipped. 27 | TLP started in AC mode (auto). 28 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 29 | TLP started in AC mode (auto). 30 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" START_CHARGE_THRESH_${batb}= STOP_CHARGE_THRESH_${batb}= 31 | TLP started in AC mode (auto). 32 | $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 33 | TLP started in AC mode (auto). 34 | $ # 35 | $ # --- tlp setcharge w/o arguments 36 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="60" STOP_CHARGE_THRESH_${bata}="100" X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" 37 | Setting temporary charge threshold(s) for battery BATA: 38 | start = 60 39 | stop = 100 (no change) 40 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="100" STOP_CHARGE_THRESH_${bata}="100" 2>&1 | sed -r "s/${bata}/BATA/" 41 | Error in configuration at START_CHARGE_THRESH_BATA="100": not specified, invalid or out of range (0..99). Aborted. 42 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="0" 2>&1 | sed -r "s/${bata}/BATA/" 43 | Error in configuration at STOP_CHARGE_THRESH_BATA="0": not specified, invalid or out of range (1..100). Aborted. 44 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="0" STOP_CHARGE_THRESH_${bata}="101" 2>&1 | sed -r "s/${bata}/BATA/" 45 | Error in configuration at STOP_CHARGE_THRESH_BATA="101": not specified, invalid or out of range (1..100). Aborted. 46 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="97" STOP_CHARGE_THRESH_${bata}="97" 2>&1 | sed -r "s/${bata}/BATA/g" 47 | Error in configuration: START_CHARGE_THRESH_BATA >= STOP_CHARGE_THRESH_BATA. Aborted. 48 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" 2>&1 | sed -r "s/${bata}/BATA/" 49 | Setting temporary charge threshold(s) for battery BATA: 50 | start = 95 51 | stop = 96 52 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="95" STOP_CHARGE_THRESH_${bata}="96" 2>&1 | sed -r "s/${bata}/BATA/" 53 | Setting temporary charge threshold(s) for battery BATA: 54 | start = 95 (no change) 55 | stop = 96 (no change) 56 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 2>&1 | sed -r "s/${bata}/BATA/" 57 | Setting temporary charge threshold(s) for battery BATA: 58 | start = 0 59 | stop = 100 60 | $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_${bata}="DEF" STOP_CHARGE_THRESH_${bata}="DEF" 61 | Error: there is no hardware driver support for charge thresholds. 62 | $ # 63 | $ # --- tlp setcharge w/ arguments 64 | $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 2>&1 | sed -r "s/${bata}/BATA/" 65 | Setting temporary charge threshold(s) for battery BATA: 66 | start = 60 67 | stop = 100 (no change) 68 | $ sudo tlp setcharge 100 100 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 69 | Error: start charge threshold (100) for BATA is not specified, invalid or out of range (0..99). Aborted. 70 | $ sudo tlp setcharge 0 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 71 | Error: stop charge threshold (0) for BATA is not specified, invalid or out of range (1..100). Aborted. 72 | $ sudo tlp setcharge 0 101 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 73 | Error: stop charge threshold (101) for BATA is not specified, invalid or out of range (1..100). Aborted. 74 | $ sudo tlp setcharge XYZZY 0 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 75 | Error: start charge threshold (XYZZY) for BATA is not specified, invalid or out of range (0..99). Aborted. 76 | $ sudo tlp setcharge 0 XYZZY -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 77 | Error: stop charge threshold (XYZZY) for BATA is not specified, invalid or out of range (1..100). Aborted. 78 | $ sudo tlp setcharge 97 97 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 79 | Error: start threshold >= stop threshold for BATA. Aborted. 80 | $ sudo tlp setcharge 95 96 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 81 | Setting temporary charge threshold(s) for battery BATA: 82 | start = 95 83 | stop = 96 84 | $ sudo tlp setcharge 95 96 -- ${xinc} X_THRESH_SIMULATE_READERR="1" 2>&1 | sed -r "s/${bata}/BATA/" 85 | Error: could not read current charge threshold(s) for BATA. Aborted. 86 | $ sudo tlp setcharge 95 96 -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 87 | Setting temporary charge threshold(s) for battery BATA: 88 | start = 95 (no change) 89 | stop = 96 (no change) 90 | $ sudo tlp setcharge DEF DEF -- ${xinc} 2>&1 | sed -r "s/${bata}/BATA/" 91 | Setting temporary charge threshold(s) for battery BATA: 92 | start = 0 93 | stop = 100 94 | $ sudo tlp setcharge ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 95 | Error: battery BATB not present. 96 | $ sudo tlp setcharge 0 3 ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 97 | Error: battery BATB not present. 98 | $ sudo tlp setcharge XYZZY ABCDE ${batb} -- ${xinc} 2>&1 | sed -r "s/${batb}/BATB/" 99 | Error: battery BATB not present. 100 | $ # 101 | $ # --- tlp-stat 102 | $ sudo tlp-stat -b -- ${xinc}| grep -E "${bata}/charge_(control|behaviour)" | sed -r "s/${bata}/BATA/" 103 | /sys/class/power_supply/BATA/charge_control_start_threshold = 0 [%] 104 | /sys/class/power_supply/BATA/charge_control_end_threshold = 100 [%] 105 | /sys/class/power_supply/BATA/charge_behaviour = [auto] inhibit-charge force-discharge 106 | $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E "${bata}/charge_(control|behaviour)" | sed -r "s/${bata}/BATA/" 107 | /sys/class/power_supply/BATA/charge_control_start_threshold = (not available) [%] 108 | /sys/class/power_supply/BATA/charge_control_end_threshold = (not available) [%] 109 | /sys/class/power_supply/BATA/charge_behaviour = [auto] inhibit-charge force-discharge 110 | $ # 111 | $ # --- Reset test machine to configured thresholds 112 | $ sudo tlp setcharge -- ${xinc} > /dev/null 2>&1 113 | $ # 114 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_dell: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for Dell laptops 3 | # Requirements: 4 | # * Hardware: Dell Laptop 5 | # * Software: kernel module dell_laptop (Linux 6.12+) 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # +++ Dell laptops +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | $ # 11 | $ # --- tlp start 12 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 13 | TLP started in AC mode (auto). 14 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- ${xinc} X_THRESH_SIMULATE_LOCKEDBIOS=1 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 17 | Error: failed to set 'Custom' charge type for battery BAT0. Battery skipped. 18 | Remove the BIOS Admin password to allow the thresholds to be set. 19 | TLP started in AC mode (auto). 20 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 21 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Battery skipped. 22 | TLP started in AC mode (auto). 23 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 24 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Battery skipped. 25 | TLP started in AC mode (auto). 26 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 27 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Battery skipped. 28 | TLP started in AC mode (auto). 29 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 30 | Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Battery skipped. 31 | TLP started in AC mode (auto). 32 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 33 | TLP started in AC mode (auto). 34 | $ sudo tlp start -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 35 | TLP started in AC mode (auto). 36 | $ sudo tlp start -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 37 | TLP started in AC mode (auto). 38 | $ # 39 | $ # --- tlp setcharge w/o arguments 40 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 41 | Setting temporary charge thresholds for battery BAT0: 42 | start = 60 43 | stop = 100 (no change) 44 | $ sudo tlp setcharge -- ${xinc} X_THRESH_SIMULATE_LOCKEDBIOS=1 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 45 | Setting temporary charge thresholds for battery BAT0: 46 | Error: failed to set 'Custom' charge type for battery BAT0. Aborted. 47 | Remove the BIOS Admin password to allow the thresholds to be set. 48 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" 49 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (50..95). Aborted. 50 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="0" 51 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (55..100). Aborted. 52 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="50" STOP_CHARGE_THRESH_BAT0="101" 53 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (55..100). Aborted. 54 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="91" 55 | Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 5. Aborted. 56 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" 57 | Setting temporary charge thresholds for battery BAT0: 58 | start = 90 59 | stop = 95 60 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="90" STOP_CHARGE_THRESH_BAT0="95" 61 | Setting temporary charge thresholds for battery BAT0: 62 | start = 90 (no change) 63 | stop = 95 (no change) 64 | $ sudo tlp setcharge -- ${xinc} START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 65 | Setting temporary charge thresholds for battery BAT0: 66 | stop = 100 67 | start = 95 68 | $ sudo tlp setcharge -- ${xinc} NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 69 | Error: there is no hardware driver support for charge thresholds. 70 | $ # 71 | $ # --- tlp setcharge w/ arguments 72 | $ sudo tlp setcharge 60 100 -- ${xinc} X_SOC_CHECK=0 73 | Setting temporary charge thresholds for battery BAT0: 74 | start = 60 75 | stop = 100 (no change) 76 | $ sudo tlp setcharge 61 99 -- ${xinc} X_THRESH_SIMULATE_WRITEERR=1 X_SOC_CHECK=0 77 | Setting temporary charge thresholds for battery BAT0: 78 | start = 61 (Error: write failed) 79 | stop = 99 (Error: write failed) 80 | Remove the BIOS Admin password to allow the thresholds to be set. 81 | $ sudo tlp setcharge 100 100 -- ${xinc} 82 | Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (50..95). Aborted. 83 | $ sudo tlp setcharge 50 0 -- ${xinc} 84 | Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. 85 | $ sudo tlp setcharge 50 101 -- ${xinc} 86 | Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. 87 | $ sudo tlp setcharge XYZZY 0 -- ${xinc} 88 | Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (50..95). Aborted. 89 | $ sudo tlp setcharge 50 XYZZY -- ${xinc} 90 | Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (55..100). Aborted. 91 | $ sudo tlp setcharge 90 91 -- ${xinc} 92 | Error: start threshold > stop threshold - 5 for battery BAT0. Aborted. 93 | $ sudo tlp setcharge 90 95 -- ${xinc} 94 | Setting temporary charge thresholds for battery BAT0: 95 | start = 90 96 | stop = 95 97 | $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_READERR="1" 98 | Error: could not read current charge threshold(s) for battery BAT0. Aborted. 99 | $ sudo tlp setcharge 90 95 -- ${xinc} X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 100 | Setting temporary charge thresholds for battery BAT0: 101 | start = 90 102 | stop = 95 103 | $ sudo tlp setcharge 90 95 -- ${xinc} 104 | Setting temporary charge thresholds for battery BAT0: 105 | start = 90 (no change) 106 | stop = 95 (no change) 107 | $ sudo tlp setcharge DEF DEF -- ${xinc} 108 | Setting temporary charge thresholds for battery BAT0: 109 | stop = 100 110 | start = 95 111 | $ sudo tlp setcharge BAT2 -- ${xinc} 112 | Error: battery BAT2 not present. 113 | $ sudo tlp setcharge 0 3 BAT2 -- ${xinc} 114 | Error: battery BAT2 not present. 115 | $ sudo tlp setcharge XYZZY ABCDE BAT2 -- ${xinc} 116 | Error: battery BAT2 not present. 117 | $ # 118 | $ # --- tlp-stat 119 | $ sudo tlp-stat -b -- ${xinc} | grep -E 'BAT0/charge_(control|behaviour)' 120 | /sys/class/power_supply/BAT0/charge_control_start_threshold = 95 [%] 121 | /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] 122 | $ sudo tlp-stat -b -- ${xinc} X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT0/charge_(control|behaviour)' 123 | /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] 124 | /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] 125 | $ # 126 | $ # --- Reset test machine to configured thresholds 127 | $ sudo tlp setcharge BAT0 - ${xinc} > /dev/null 2>&1 128 | $ # 129 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_macbook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for Macbooks 3 | # Requirements: 4 | # * Hardware: Apple Silcon Macbook w/ MacOS firmware 13.0+ 5 | # * Software: Asahi Linux kernel 6.3+ 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # +++ Apple Silicon Macbooks ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | $ # 11 | $ # --- tlp start 12 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 13 | TLP started in AC mode (auto). 14 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0=95 STOP_CHARGE_THRESH_BAT0=100 START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="42" STOP_CHARGE_THRESH_BAT0="80" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="9" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 19 | Error in configuration at STOP_CHARGE_THRESH_BAT0="9": not specified or invalid (must be 80 or 100). Battery skipped. 20 | TLP started in AC mode (auto). 21 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 22 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Battery skipped. 23 | TLP started in AC mode (auto). 24 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 25 | TLP started in AC mode (auto). 26 | $ # 27 | $ # --- tlp setcharge w/o arguments 28 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="77" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 29 | Setting temporary charge thresholds for battery macsmc-battery: 30 | stop = 80 31 | start = 75 (due to hardware constraint) 32 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="66" STOP_CHARGE_THRESH_BAT0="80" X_SOC_CHECK=0 33 | Setting temporary charge thresholds for battery macsmc-battery: 34 | stop = 80 (no change) 35 | start = 75 (no change) 36 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" 37 | Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified or invalid (must be 80 or 100). Aborted. 38 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="42" 39 | Error in configuration at STOP_CHARGE_THRESH_BAT0="42": not specified or invalid (must be 80 or 100). Aborted. 40 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" 41 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified or invalid (must be 80 or 100). Aborted. 42 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 43 | Setting temporary charge thresholds for battery macsmc-battery: 44 | stop = 100 45 | start = 100 (due to hardware constraint) 46 | $ sudo tlp setcharge -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 47 | Error: battery charge thresholds not available. 48 | $ # 49 | $ # --- tlp setcharge w/ arguments 50 | $ sudo tlp setcharge 77 80 -- X_SOC_CHECK=0 51 | Setting temporary charge thresholds for battery macsmc-battery: 52 | stop = 80 53 | start = 75 (due to hardware constraint) 54 | $ sudo tlp setcharge 66 80 -- X_SOC_CHECK=0 55 | Setting temporary charge thresholds for battery macsmc-battery: 56 | stop = 80 (no change) 57 | start = 75 (no change) 58 | $ sudo tlp setcharge 0 XYZZY 59 | Error: stop charge threshold (XYZZY) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. 60 | $ sudo tlp setcharge 0 42 61 | Error: stop charge threshold (42) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. 62 | $ sudo tlp setcharge 0 101 63 | Error: stop charge threshold (101) for battery macsmc-battery is not specified or invalid (must be 80 or 100). Aborted. 64 | $ sudo tlp setcharge DEF DEF -- X_THRESH_SIMULATE_READERR="1" 65 | Error: could not read current stop charge threshold for battery macsmc-battery. Aborted. 66 | $ sudo tlp setcharge BAT1 67 | Error: battery BAT1 not present. 68 | $ sudo tlp setcharge 0 3 BAT1 69 | Error: battery BAT1 not present. 70 | $ sudo tlp setcharge XYZZY ABCDE BAT1 71 | Error: battery BAT1 not present. 72 | $ # --- Reset to hardware defaults 73 | $ sudo tlp setcharge DEF DEF 74 | Setting temporary charge thresholds for battery macsmc-battery: 75 | stop = 100 76 | start = 100 (due to hardware constraint) 77 | $ # 78 | $ # --- tlp-stat 79 | $ sudo tlp-stat -b -- | grep -E 'charge_control' 80 | /sys/class/power_supply/macsmc-battery/charge_control_start_threshold = 100 [%] 81 | /sys/class/power_supply/macsmc-battery/charge_control_end_threshold = 100 [%] 82 | $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_control' 83 | /sys/class/power_supply/macsmc-battery/charge_control_start_threshold = (not available) [%] 84 | /sys/class/power_supply/macsmc-battery/charge_control_end_threshold = (not available) [%] 85 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_msi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for MSI laptops 3 | # Requirements: 4 | # * Hardware: MSI laptop supported by msi_ec driver 5 | # * Software: kernel 6.3+ 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # +++ MSI ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | $ # 11 | $ # --- tlp start 12 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 13 | TLP started in AC mode (auto). 14 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="9" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 17 | Error in configuration at STOP_CHARGE_THRESH_BAT1="9": not specified, invalid or out of range (10..100). Battery skipped. 18 | TLP started in AC mode (auto). 19 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 20 | Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (10..100). Battery skipped. 21 | TLP started in AC mode (auto). 22 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 23 | TLP started in AC mode (auto). 24 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 25 | TLP started in AC mode (auto). 26 | $ sudo tlp start -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= 27 | TLP started in AC mode (auto). 28 | $ # 29 | $ # --- tlp setcharge w/o arguments 30 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" X_SOC_CHECK=0 31 | Setting temporary charge thresholds for battery BAT1: 32 | stop = 90 33 | start = 80 (due to hardware constraint) 34 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="70" STOP_CHARGE_THRESH_BAT1="90" X_SOC_CHECK=0 35 | Setting temporary charge thresholds for battery BAT1: 36 | stop = 90 (no change) 37 | start = 80 (no change) 38 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="XYZZY" 39 | Error in configuration at STOP_CHARGE_THRESH_BAT1="XYZZY": not specified, invalid or out of range (10..100). Aborted. 40 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="9" 41 | Error in configuration at STOP_CHARGE_THRESH_BAT1="9": not specified, invalid or out of range (10..100). Aborted. 42 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" 43 | Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (10..100). Aborted. 44 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" 45 | Setting temporary charge thresholds for battery BAT1: 46 | stop = 97 47 | start = 87 (due to hardware constraint) 48 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 49 | Setting temporary charge thresholds for battery BAT1: 50 | stop = 96 51 | start = 86 (due to hardware constraint) 52 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 53 | Setting temporary charge thresholds for battery BAT1: 54 | stop = 96 (no change) 55 | start = 86 (no change) 56 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 57 | Setting temporary charge thresholds for battery BAT1: 58 | stop = 100 59 | start = 90 (due to hardware constraint) 60 | $ sudo tlp setcharge -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 61 | Error: battery charge thresholds not available. 62 | $ # 63 | $ # --- tlp setcharge w/ arguments 64 | $ sudo tlp setcharge 70 90 BAT1 -- X_SOC_CHECK=0 65 | Setting temporary charge thresholds for battery BAT1: 66 | stop = 90 67 | start = 80 (due to hardware constraint) 68 | $ sudo tlp setcharge 70 90 -- X_SOC_CHECK=0 69 | Setting temporary charge thresholds for battery BAT1: 70 | stop = 90 (no change) 71 | start = 80 (no change) 72 | $ sudo tlp setcharge 0 XYZZY 73 | Error: stop charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. 74 | $ sudo tlp setcharge 0 9 75 | Error: stop charge threshold (9) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. 76 | $ sudo tlp setcharge 0 101 77 | Error: stop charge threshold (101) for battery BAT1 is not specified, invalid or out of range (10..100). Aborted. 78 | $ sudo tlp setcharge 97 97 79 | Setting temporary charge thresholds for battery BAT1: 80 | stop = 97 81 | start = 87 (due to hardware constraint) 82 | $ sudo tlp setcharge 95 96 83 | Setting temporary charge thresholds for battery BAT1: 84 | stop = 96 85 | start = 86 (due to hardware constraint) 86 | $ sudo tlp setcharge 95 96 87 | Setting temporary charge thresholds for battery BAT1: 88 | stop = 96 (no change) 89 | start = 86 (no change) 90 | $ sudo tlp setcharge BAT0 91 | Error: battery BAT0 not present. 92 | $ sudo tlp setcharge 0 3 BAT0 93 | Error: battery BAT0 not present. 94 | $ sudo tlp setcharge XYZZY ABCDE BAT0 95 | Error: battery BAT0 not present. 96 | $ # 97 | $ # --- Reset to hardware defaults 98 | $ sudo tlp setcharge DEF DEF 99 | Setting temporary charge thresholds for battery BAT1: 100 | stop = 100 101 | start = 90 (due to hardware constraint) 102 | $ # --- tlp-stat 103 | $ sudo tlp-stat -b | grep -E 'charge_control' 104 | /sys/class/power_supply/BAT1/charge_control_start_threshold = 90 [%] 105 | /sys/class/power_supply/BAT1/charge_control_end_threshold = 100 [%] 106 | $ # 107 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_system76: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for System76 laptops 3 | # Requirements: 4 | # * Hardware: System76 laptop w/ open source EC firmware 5 | # * Software: kernel 5.16+ 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # +++ System76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | $ # 11 | $ # --- tlp start 12 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 13 | TLP started in AC mode (auto). 14 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 17 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Battery skipped. 18 | TLP started in AC mode (auto). 19 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 20 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. 21 | TLP started in AC mode (auto). 22 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 23 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. 24 | TLP started in AC mode (auto). 25 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 26 | Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Battery skipped. 27 | TLP started in AC mode (auto). 28 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 29 | TLP started in AC mode (auto). 30 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 31 | TLP started in AC mode (auto). 32 | $ # 33 | $ # --- tlp setcharge w/o arguments 34 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 35 | Setting temporary charge thresholds for battery BAT0: 36 | start = 60 37 | stop = 100 (no change) 38 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" 39 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Aborted. 40 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="ABCDE" STOP_CHARGE_THRESH_BAT0="100" 41 | Error in configuration at START_CHARGE_THRESH_BAT0="ABCDE": not specified, invalid or out of range (0..99). Aborted. 42 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" 43 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. 44 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" 45 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. 46 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="XYZZY" 47 | Error in configuration at STOP_CHARGE_THRESH_BAT0="XYZZY": not specified, invalid or out of range (1..100). Aborted. 48 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" 49 | Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Aborted. 50 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" 51 | Setting temporary charge thresholds for battery BAT0: 52 | start = 95 53 | stop = 96 54 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" 55 | Setting temporary charge thresholds for battery BAT0: 56 | start = 95 (no change) 57 | stop = 96 (no change) 58 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 59 | Setting temporary charge thresholds for battery BAT0: 60 | start = 0 61 | stop = 100 62 | $ sudo tlp setcharge -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 63 | Error: battery charge thresholds not available. 64 | $ # 65 | $ # --- tlp setcharge w/ arguments 66 | $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 67 | Setting temporary charge thresholds for battery BAT0: 68 | start = 60 69 | stop = 100 (no change) 70 | $ sudo tlp setcharge 100 100 71 | Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. 72 | $ sudo tlp setcharge 0 0 73 | Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 74 | $ sudo tlp setcharge 0 101 75 | Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 76 | $ sudo tlp setcharge ABCDE 0 77 | Error: start charge threshold (ABCDE) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. 78 | $ sudo tlp setcharge 0 XYZZY 79 | Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 80 | $ sudo tlp setcharge 97 97 81 | Error: start threshold >= stop threshold for battery BAT0. Aborted. 82 | $ sudo tlp setcharge 95 96 83 | Setting temporary charge thresholds for battery BAT0: 84 | start = 95 85 | stop = 96 86 | $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_READERR="1" 87 | Error: could not read current charge threshold(s) for battery BAT0. Aborted. 88 | $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 89 | Setting temporary charge thresholds for battery BAT0: 90 | start = 95 91 | stop = 96 92 | $ sudo tlp setcharge 95 96 93 | Setting temporary charge thresholds for battery BAT0: 94 | start = 95 (no change) 95 | stop = 96 (no change) 96 | $ sudo tlp setcharge BAT1 97 | Error: battery BAT1 not present. 98 | $ sudo tlp setcharge 0 3 BAT1 99 | Error: battery BAT1 not present. 100 | $ sudo tlp setcharge XYZZY ABCDE BAT1 101 | Error: battery BAT1 not present. 102 | $ # 103 | $ # --- Reset to hardware defaults 104 | $ sudo tlp setcharge DEF DEF 105 | Setting temporary charge thresholds for battery BAT0: 106 | start = 90 107 | stop = 100 108 | $ # 109 | $ # --- tlp-stat 110 | $ sudo tlp-stat -b -- | grep -E 'charge_control' 111 | /sys/class/power_supply/BAT0/charge_control_start_threshold = 90 [%] 112 | /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] 113 | $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_control' 114 | /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] 115 | /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] 116 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_thinkpad: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for ThinkPads 3 | # Requirements: 4 | # * Hardware: non-legacy ThinkPad 5 | # * Kernel: >= 5.17 6 | # * Batteries: BAT0 only 7 | # * Power source AC 8 | # Copyright (c) 2025 Thomas Koch . 9 | # SPDX-License-Identifier: GPL-2.0-or-later 10 | # 11 | $ # +++ ThinkPad (BAT0) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 12 | $ # 13 | $ # --- tlp start 14 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 19 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Battery skipped. 20 | TLP started in AC mode (auto). 21 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 22 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Battery skipped. 23 | TLP started in AC mode (auto). 24 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 25 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Battery skipped. 26 | TLP started in AC mode (auto). 27 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 28 | Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Battery skipped. 29 | TLP started in AC mode (auto). 30 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 31 | TLP started in AC mode (auto). 32 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 33 | TLP started in AC mode (auto). 34 | $ sudo tlp start -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 35 | TLP started in AC mode (auto). 36 | $ # 37 | $ # --- tlp setcharge w/o arguments 38 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" 39 | Setting temporary charge thresholds for battery BAT0: 40 | start = 60 41 | stop = 100 (no change) 42 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" 43 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (0..99). Aborted. 44 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="0" 45 | Error in configuration at STOP_CHARGE_THRESH_BAT0="0": not specified, invalid or out of range (1..100). Aborted. 46 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="0" STOP_CHARGE_THRESH_BAT0="101" 47 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (1..100). Aborted. 48 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="97" STOP_CHARGE_THRESH_BAT0="97" 49 | Error in configuration: START_CHARGE_THRESH_BAT0 >= STOP_CHARGE_THRESH_BAT0. Aborted. 50 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" 51 | Setting temporary charge thresholds for battery BAT0: 52 | start = 95 53 | stop = 96 54 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="96" 55 | Setting temporary charge thresholds for battery BAT0: 56 | start = 95 (no change) 57 | stop = 96 (no change) 58 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 59 | Setting temporary charge thresholds for battery BAT0: 60 | stop = 100 61 | start = 96 62 | $ sudo tlp setcharge -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 63 | Error: there is no hardware driver support for charge thresholds. 64 | $ # 65 | $ # --- tlp setcharge w/ arguments 66 | $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 67 | Setting temporary charge thresholds for battery BAT0: 68 | start = 60 69 | stop = 100 (no change) 70 | $ sudo tlp setcharge 100 100 71 | Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. 72 | $ sudo tlp setcharge 0 0 73 | Error: stop charge threshold (0) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 74 | $ sudo tlp setcharge 0 101 75 | Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 76 | $ sudo tlp setcharge XYZZY 0 77 | Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (0..99). Aborted. 78 | $ sudo tlp setcharge 0 XYZZY 79 | Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (1..100). Aborted. 80 | $ sudo tlp setcharge 97 97 81 | Error: start threshold >= stop threshold for battery BAT0. Aborted. 82 | $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 83 | Setting temporary charge thresholds for battery BAT0: 84 | start = 95 85 | stop = 96 86 | $ sudo tlp setcharge 95 96 -- X_THRESH_SIMULATE_READERR="1" 87 | Error: could not read current charge threshold(s) for battery BAT0. Aborted. 88 | $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 89 | Setting temporary charge thresholds for battery BAT0: 90 | start = 95 91 | stop = 96 92 | $ sudo tlp setcharge 95 96 -- X_SOC_CHECK=0 93 | Setting temporary charge thresholds for battery BAT0: 94 | start = 95 (no change) 95 | stop = 96 (no change) 96 | $ sudo tlp setcharge DEF DEF 97 | Setting temporary charge thresholds for battery BAT0: 98 | stop = 100 99 | start = 96 100 | $ sudo tlp setcharge BAT2 101 | Error: battery BAT2 not present. 102 | $ sudo tlp setcharge 0 3 BAT2 103 | Error: battery BAT2 not present. 104 | $ sudo tlp setcharge XYZZY ABCDE BAT2 105 | Error: battery BAT2 not present. 106 | $ # 107 | $ # --- tlp discharge 108 | $ sudo tlp discharge 100 109 | Error: target charge level (100) for battery BAT0 is out of range (0..99). 110 | $ sudo tlp discharge BAT0 100 111 | Error: target charge level (100) for battery BAT0 is out of range (0..99). 112 | $ sudo tlp discharge BAT2 113 | Error: battery BAT2 not present. 114 | $ sudo tlp discharge BAT2 100 115 | Error: battery BAT2 not present. 116 | $ # 117 | $ # --- tlp-stat 118 | $ # steps require a kernel >= 5.17 -- with 'charge_behaviour' 119 | $ sudo tlp-stat -b | grep -E 'charge_(control|behaviour)' 120 | /sys/class/power_supply/BAT0/charge_control_start_threshold = 96 [%] 121 | /sys/class/power_supply/BAT0/charge_control_end_threshold = 100 [%] 122 | /sys/class/power_supply/BAT0/charge_behaviour = [auto] inhibit-charge force-discharge 123 | $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_(control|behaviour)' 124 | /sys/class/power_supply/BAT0/charge_control_start_threshold = (not available) [%] 125 | /sys/class/power_supply/BAT0/charge_control_end_threshold = (not available) [%] 126 | /sys/class/power_supply/BAT0/charge_behaviour = [auto] inhibit-charge force-discharge 127 | $ # 128 | $ # --- Reset test machine to configured thresholds 129 | $ sudo tlp setcharge BAT0 > /dev/null 2>&1 130 | $ # 131 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_thinkpad-BAT1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for ThinkPads 3 | # Requirements: 4 | # * Hardware: non-legacy ThinkPad 5 | # * Kernel: >= 5.17 6 | # * Batteries: BAT1 + (optional) BAT0 7 | # * Power source AC 8 | # Copyright (c) 2025 Thomas Koch . 9 | # SPDX-License-Identifier: GPL-2.0-or-later 10 | # 11 | $ # +++ ThinkPad (BAT1) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 12 | $ # 13 | $ # --- tlp start 14 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 15 | TLP started in AC mode (auto). 16 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="61" STOP_CHARGE_THRESH_BAT1="100" 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="100" STOP_CHARGE_THRESH_BAT1="100" 19 | Error in configuration at START_CHARGE_THRESH_BAT1="100": not specified, invalid or out of range (0..99). Battery skipped. 20 | TLP started in AC mode (auto). 21 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="0" 22 | Error in configuration at STOP_CHARGE_THRESH_BAT1="0": not specified, invalid or out of range (1..100). Battery skipped. 23 | TLP started in AC mode (auto). 24 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" 25 | Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (1..100). Battery skipped. 26 | TLP started in AC mode (auto). 27 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" 28 | Error in configuration: START_CHARGE_THRESH_BAT1 >= STOP_CHARGE_THRESH_BAT1. Battery skipped. 29 | TLP started in AC mode (auto). 30 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 31 | TLP started in AC mode (auto). 32 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 33 | TLP started in AC mode (auto). 34 | $ # 35 | $ # --- tlp setcharge w/o arguments 36 | $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="60" STOP_CHARGE_THRESH_BAT1="100" X_SOC_CHECK=0 37 | Setting temporary charge thresholds for battery BAT1: 38 | start = 60 39 | stop = 100 (no change) 40 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="100" STOP_CHARGE_THRESH_BAT1="100" 41 | Error in configuration at START_CHARGE_THRESH_BAT1="100": not specified, invalid or out of range (0..99). Aborted. 42 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="0" 43 | Error in configuration at STOP_CHARGE_THRESH_BAT1="0": not specified, invalid or out of range (1..100). Aborted. 44 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="0" STOP_CHARGE_THRESH_BAT1="101" 45 | Error in configuration at STOP_CHARGE_THRESH_BAT1="101": not specified, invalid or out of range (1..100). Aborted. 46 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="97" STOP_CHARGE_THRESH_BAT1="97" 47 | Error in configuration: START_CHARGE_THRESH_BAT1 >= STOP_CHARGE_THRESH_BAT1. Aborted. 48 | $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 49 | Setting temporary charge thresholds for battery BAT1: 50 | start = 95 51 | stop = 96 52 | $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="95" STOP_CHARGE_THRESH_BAT1="96" 53 | Setting temporary charge thresholds for battery BAT1: 54 | start = 95 (no change) 55 | stop = 96 (no change) 56 | $ sudo tlp setcharge BAT1 -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 57 | Setting temporary charge thresholds for battery BAT1: 58 | stop = 100 59 | start = 96 60 | $ sudo tlp setcharge BAT1 -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 61 | Error: there is no hardware driver support for charge thresholds. 62 | $ # 63 | $ # --- tlp setcharge w/ arguments 64 | $ sudo tlp setcharge 60 100 BAT1 -- X_SOC_CHECK=0 65 | Setting temporary charge thresholds for battery BAT1: 66 | start = 60 67 | stop = 100 (no change) 68 | $ sudo tlp setcharge 100 100 BAT1 69 | Error: start charge threshold (100) for battery BAT1 is not specified, invalid or out of range (0..99). Aborted. 70 | $ sudo tlp setcharge 0 0 BAT1 71 | Error: stop charge threshold (0) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. 72 | $ sudo tlp setcharge 0 101 BAT1 73 | Error: stop charge threshold (101) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. 74 | $ sudo tlp setcharge XYZZY 0 BAT1 75 | Error: start charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (0..99). Aborted. 76 | $ sudo tlp setcharge 0 XYZZY BAT1 77 | Error: stop charge threshold (XYZZY) for battery BAT1 is not specified, invalid or out of range (1..100). Aborted. 78 | $ sudo tlp setcharge 97 97 BAT1 79 | Error: start threshold >= stop threshold for battery BAT1. Aborted. 80 | $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 81 | Setting temporary charge thresholds for battery BAT1: 82 | start = 95 83 | stop = 96 84 | $ sudo tlp setcharge 95 96 BAT1 -- X_THRESH_SIMULATE_READERR="1" 85 | Error: could not read current charge threshold(s) for battery BAT1. Aborted. 86 | $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 87 | Setting temporary charge thresholds for battery BAT1: 88 | start = 95 89 | stop = 96 90 | $ sudo tlp setcharge 95 96 BAT1 -- X_SOC_CHECK=0 91 | Setting temporary charge thresholds for battery BAT1: 92 | start = 95 (no change) 93 | stop = 96 (no change) 94 | $ sudo tlp setcharge DEF DEF BAT1 95 | Setting temporary charge thresholds for battery BAT1: 96 | stop = 100 97 | start = 96 98 | $ sudo tlp setcharge 61 91 BAT1 -- X_SOC_CHECK=0 99 | Setting temporary charge thresholds for battery BAT1: 100 | start = 61 101 | stop = 91 102 | $ # 103 | $ # --- tlp discharge 104 | $ sudo tlp discharge BAT1 100 105 | Error: target charge level (100) for battery BAT1 is out of range (0..99). 106 | $ # 107 | $ # --- tlp-stat 108 | $ # steps require a kernel >= 5.17 -- with 'charge_behaviour' 109 | $ sudo tlp-stat -b | grep -E 'BAT1/charge_(control|behaviour)' 110 | /sys/class/power_supply/BAT1/charge_control_start_threshold = 61 [%] 111 | /sys/class/power_supply/BAT1/charge_control_end_threshold = 91 [%] 112 | /sys/class/power_supply/BAT1/charge_behaviour = [auto] inhibit-charge force-discharge 113 | $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'BAT1/charge_(control|behaviour)' 114 | /sys/class/power_supply/BAT1/charge_control_start_threshold = (not available) [%] 115 | /sys/class/power_supply/BAT1/charge_control_end_threshold = (not available) [%] 116 | /sys/class/power_supply/BAT1/charge_behaviour = [auto] inhibit-charge force-discharge 117 | $ # 118 | $ # --- Reset test machine to configured thresholds 119 | $ sudo tlp setcharge BAT0 > /dev/null 2>&1 120 | $ sudo tlp setcharge BAT1 > /dev/null 2>&1 121 | $ # 122 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_thinkpad-legacy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for battery Legacy ThinkPads 3 | # Requirements: 4 | # * Hardware: Legacy ThinkPad (<= X201/T410) 5 | # Copyright (c) 2025 Thomas Koch . 6 | # SPDX-License-Identifier: GPL-2.0-or-later 7 | # 8 | $ # +++ Legacy ThinkPad +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 | $ # 10 | $ # --- tlp start 11 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 12 | TLP started in AC mode (auto). 13 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 14 | TLP started in AC mode (auto). 15 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 16 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (2..96). Battery skipped. 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="2" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 19 | Error in configuration at STOP_CHARGE_THRESH_BAT0="2": not specified, invalid or out of range (6..100). Battery skipped. 20 | TLP started in AC mode (auto). 21 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="101" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 22 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (6..100). Battery skipped. 23 | TLP started in AC mode (auto). 24 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="96" STOP_CHARGE_THRESH_BAT0="99" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 25 | Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 4. Battery skipped. 26 | TLP started in AC mode (auto). 27 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 28 | TLP started in AC mode (auto). 29 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 30 | TLP started in AC mode (auto). 31 | $ # 32 | $ # --- tlp setcharge w/o arguments 33 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="60" STOP_CHARGE_THRESH_BAT0="100" X_SOC_CHECK=0 34 | Setting temporary charge thresholds for battery BAT0: 35 | start = 60 36 | stop = 100 (no change) 37 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="100" STOP_CHARGE_THRESH_BAT0="100" 38 | Error in configuration at START_CHARGE_THRESH_BAT0="100": not specified, invalid or out of range (2..96). Aborted. 39 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="2" 40 | Error in configuration at STOP_CHARGE_THRESH_BAT0="2": not specified, invalid or out of range (6..100). Aborted. 41 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="2" STOP_CHARGE_THRESH_BAT0="101" 42 | Error in configuration at STOP_CHARGE_THRESH_BAT0="101": not specified, invalid or out of range (6..100). Aborted. 43 | $ sudo tlp setcharge -- START_CHARGE_THRESH_BAT0="96" STOP_CHARGE_THRESH_BAT0="99" 44 | Error in configuration: START_CHARGE_THRESH_BAT0 > STOP_CHARGE_THRESH_BAT0 - 4. Aborted. 45 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" 46 | Setting temporary charge thresholds for battery BAT0: 47 | start = 95 48 | stop = 99 49 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="95" STOP_CHARGE_THRESH_BAT0="99" 50 | Setting temporary charge thresholds for battery BAT0: 51 | start = 95 (no change) 52 | stop = 99 (no change) 53 | $ sudo tlp setcharge -- X_SOC_CHECK=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 54 | Setting temporary charge thresholds for battery BAT0: 55 | start = 96 56 | stop = 100 57 | $ sudo tlp setcharge -- TPSMAPI_ENABLE=0 START_CHARGE_THRESH_BAT0="DEF" STOP_CHARGE_THRESH_BAT0="DEF" 58 | Error: there is no hardware driver support for charge thresholds. 59 | $ # 60 | $ # --- tlp setcharge w/ arguments 61 | $ sudo tlp setcharge 60 100 -- X_SOC_CHECK=0 62 | Setting temporary charge thresholds for battery BAT0: 63 | start = 60 64 | stop = 100 (no change) 65 | $ sudo tlp setcharge 100 100 66 | Error: start charge threshold (100) for battery BAT0 is not specified, invalid or out of range (2..96). Aborted. 67 | $ sudo tlp setcharge 2 2 68 | Error: stop charge threshold (2) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. 69 | $ sudo tlp setcharge 2 101 70 | Error: stop charge threshold (101) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. 71 | $ sudo tlp setcharge XYZZY 0 72 | Error: start charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (2..96). Aborted. 73 | $ sudo tlp setcharge 2 XYZZY 74 | Error: stop charge threshold (XYZZY) for battery BAT0 is not specified, invalid or out of range (6..100). Aborted. 75 | $ sudo tlp setcharge 96 99 76 | Error: start threshold > stop threshold - 4 for battery BAT0. Aborted. 77 | $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 78 | Setting temporary charge thresholds for battery BAT0: 79 | start = 95 80 | stop = 99 81 | $ sudo tlp setcharge 95 99 -- X_THRESH_SIMULATE_READERR="1" 82 | Error: could not read current charge threshold(s) for BAT0. Aborted. 83 | $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 X_THRESH_SIMULATE_START="60" X_THRESH_SIMULATE_STOP="100" 84 | Setting temporary charge thresholds for battery BAT0: 85 | start = 95 86 | stop = 99 87 | $ sudo tlp setcharge 95 99 -- X_SOC_CHECK=0 88 | Setting temporary charge thresholds for battery BAT0: 89 | start = 95 (no change) 90 | stop = 99 (no change) 91 | $ sudo tlp setcharge DEF DEF 92 | Setting temporary charge thresholds for battery BAT0: 93 | start = 96 94 | stop = 100 95 | $ sudo tlp setcharge BAT1 96 | Error: battery BAT1 not present. 97 | $ sudo tlp setcharge 0 3 BAT1 98 | Error: battery BAT1 not present. 99 | $ sudo tlp setcharge XYZZY ABCDE BAT1 100 | Error: battery BAT1 not present. 101 | $ # 102 | $ # --- tlp discharge 103 | $ sudo tlp discharge 100 104 | Error: target charge level (100) for battery BAT0 is out of range (0..99). 105 | $ sudo tlp discharge BAT0 100 106 | Error: target charge level (100) for battery BAT0 is out of range (0..99). 107 | $ sudo tlp discharge BAT1 108 | Error: battery BAT1 not present. 109 | $ sudo tlp discharge BAT1 100 110 | Error: battery BAT1 not present. 111 | $ # 112 | $ # --- tlp-stat 113 | $ sudo tlp-stat -b | grep -E 'charge_thresh|force_discharge' 114 | /sys/devices/platform/smapi/BAT0/start_charge_thresh = 96 [%] 115 | /sys/devices/platform/smapi/BAT0/stop_charge_thresh = 100 [%] 116 | /sys/devices/platform/smapi/BAT0/force_discharge = 0 117 | $ sudo tlp-stat -b -- X_THRESH_SIMULATE_READERR=1 | grep -E 'charge_thresh|force_discharge' 118 | /sys/devices/platform/smapi/BAT0/start_charge_thresh = (not available) [%] 119 | /sys/devices/platform/smapi/BAT0/stop_charge_thresh = (not available) [%] 120 | /sys/devices/platform/smapi/BAT0/force_discharge = 0 121 | $ # 122 | $ # --- Feature Detection Edge Cases and Kernel Module Recommendations 123 | $ sudo ./kmod-helper tp_smapi restore 124 | $ sudo ./kmod-helper tp_smapi enable 125 | $ sudo tlp-stat -b | head -7 | tail -4 126 | Plugin: thinkpad-legacy 127 | Supported features: charge thresholds, chargeonce, discharge, recalibrate 128 | Driver usage: 129 | * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) 130 | $ sudo tlp-stat -b -- NATACPI_ENABLE=0 | head -7 | tail -4 131 | Plugin: thinkpad-legacy 132 | Supported features: charge thresholds, chargeonce, discharge, recalibrate 133 | Driver usage: 134 | * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) 135 | $ sudo tlp-stat -b -- NATACPI_ENABLE=0 TPACPI_ENABLE=0 | head -7 | tail -4 136 | Plugin: thinkpad-legacy 137 | Supported features: charge thresholds, chargeonce, discharge, recalibrate 138 | Driver usage: 139 | * tp-smapi (tp_smapi) = active (status, charge thresholds, force-discharge) 140 | $ sudo ./kmod-helper tp_smapi disable 141 | $ sudo tlp-stat -b | head -7 | tail -4 142 | Plugin: thinkpad-legacy 143 | Supported features: none available 144 | Driver usage: 145 | * tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' load error) 146 | $ sudo ./kmod-helper tp_smapi enable 147 | $ sudo ./kmod-helper tp_smapi remove 148 | $ sudo tlp-stat -b | head -7 | tail -4 149 | Plugin: thinkpad-legacy 150 | Supported features: none available 151 | Driver usage: 152 | * tp-smapi (tp_smapi) = inactive (kernel module 'tp_smapi' not installed) 153 | $ sudo tlp-stat -b | grep -A1 '+++ Recommendations' 154 | +++ Recommendations 155 | * Install tp-smapi kernel modules for ThinkPad battery thresholds and recalibration 156 | $ sudo ./kmod-helper tp_smapi restore 157 | $ # 158 | $ # --- Reset test machine to configured thresholds 159 | $ sudo tlp setcharge BAT0 > /dev/null 2>&1 160 | $ # 161 | -------------------------------------------------------------------------------- /unit-tests/charge-thresholds_toshiba: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test charge thresholds for Toshiba/Dynabook laptops 3 | # Requirements: 4 | # * Hardware: Toshiba/Dynabook laptop w/ toshiba_acpi driver 5 | # * Battery: BAT1 6 | # * Software: kernel 6.0+ 7 | # Copyright (c) 2025 Thomas Koch . 8 | # SPDX-License-Identifier: GPL-2.0-or-later 9 | # 10 | $ # +++ Toshiba/Dynabook ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 11 | $ # 12 | $ # --- tlp start 13 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1= STOP_CHARGE_THRESH_BAT1= 14 | TLP started in AC mode (auto). 15 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="24" 16 | Error in configuration at STOP_CHARGE_THRESH_BAT1="24": not specified or invalid (must be 80 or 100). Battery skipped. 17 | TLP started in AC mode (auto). 18 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="80" 19 | TLP started in AC mode (auto). 20 | $ sudo tlp start -- START_CHARGE_THRESH_BAT0= STOP_CHARGE_THRESH_BAT0= START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 21 | TLP started in AC mode (auto). 22 | $ sudo tlp start -- NATACPI_ENABLE=0 START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 23 | TLP started in AC mode (auto). 24 | $ # 25 | $ # --- tlp setcharge w/o threshold arguments 26 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="42" STOP_CHARGE_THRESH_BAT1="24" 27 | Error in configuration at STOP_CHARGE_THRESH_BAT1="24": not specified or invalid (must be 80 or 100). Aborted. 28 | $ sudo tlp setcharge BAT1 -- START_CHARGE_THRESH_BAT1="DEF" STOP_CHARGE_THRESH_BAT1="DEF" 29 | Setting temporary charge threshold for BAT1: 30 | stop = 100 (no change) 31 | $ # 32 | $ # --- tlp setcharge w/ threshold arguments 33 | $ sudo tlp setcharge 42 24 BAT1 34 | Error: stop charge threshold (24) for BAT1 is not specified or invalid (must be 80 or 100). Aborted. 35 | $ sudo tlp setcharge 42 100 BAT1 36 | Setting temporary charge threshold for BAT1: 37 | stop = 100 (no change) 38 | $ sudo tlp setcharge 42 80 BAT1 -- X_SOC_CHECK=0 39 | Setting temporary charge threshold for BAT1: 40 | stop = 80 41 | $ sudo tlp setcharge 42 80 42 | Error: battery BAT0 not present. 43 | $ sudo tlp setcharge 2 44 | Error: battery 2 not present. 45 | $ # 46 | $ # --- Reset to hardware defaults 47 | $ sudo tlp setcharge DEF DEF BAT1 48 | Setting temporary charge thresholds for BAT1: 49 | stop = 100 50 | $ # 51 | $ # --- tlp-stat 52 | $ sudo tlp-stat -b | sudo tlp-stat -b | grep -E 'charge_control 53 | /sys/class/power_supply/BAT1/charge_control_end_threshold = 100 [%] 54 | $ # 55 | -------------------------------------------------------------------------------- /unit-tests/kmod-helper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Kernel module helper for battery care testing: disable/remove DKMS modules 3 | # $1: module name 4 | # $2: disable/enable/remove/restore 5 | 6 | # Constants 7 | KILLFILE=/etc/modprobe.d/kmod-helper.conf 8 | 9 | # Functions 10 | unload_kmod () { # $1: module name 11 | modprobe -r "$1" 12 | } 13 | 14 | # MAIN 15 | 16 | if [ "$1" != "acpi_call" ] && [ "$1" != "tp_smapi" ]; then 17 | echo "Error: unknown kernel module \"$1\"." 1>&2 18 | exit 1 19 | fi 20 | 21 | kernel=$(uname -r) 22 | module=$1 23 | if ! modfile="$(modinfo -F filename "$module" 2> /dev/null)"; then 24 | echo "Error: could not locate kernel module \"$module\"." 1>&2 25 | exit 1 26 | fi 27 | modsave="${modfile}-save" 28 | 29 | case "$2" in 30 | disable) 31 | unload_kmod "$module" 32 | echo "install ${module} killmod" > $KILLFILE 33 | ;; 34 | 35 | enable) 36 | rm -f "$KILLFILE" 37 | ;; 38 | 39 | remove) 40 | unload_kmod "$module" 41 | if [ -f "$modfile" ]; then 42 | mv "$modfile" "$modsave" 43 | else 44 | exit 2 45 | fi 46 | ;; 47 | 48 | restore) 49 | if [ -f "$modsave" ]; then 50 | mv "$modsave" "$modfile" 51 | else 52 | exit 2 53 | fi 54 | ;; 55 | 56 | *) 57 | echo "Error: unknown action \"$2\"." 1>&2 58 | exit 1 59 | ;; 60 | esac 61 | 62 | exit 0 63 | -------------------------------------------------------------------------------- /unit-tests/services-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env clitest 2 | # Test services check 3 | # Requirements: 4 | # * installed: systemd, power-profiles-daemon 5 | # * RESTORE_DEVICE_STATE_ON_STARTUP, DEVICES_TO_DISABLE_ON_STARTUP, DEVICES_TO_ENABLE_ON_STARTUP not configured 6 | # Copyright (c) 2025 Thomas Koch . 7 | # SPDX-License-Identifier: GPL-2.0-or-later 8 | # 9 | $ # --- tlp.service 10 | $ # prepare 11 | $ sudo systemctl -q disable tlp.service 12 | $ # test 13 | $ sudo tlp start > /dev/null 14 | Error: TLP's power saving will not apply on boot because tlp.service is not enabled --> Invoke 'systemctl enable tlp.service' to ensure the full functionality of TLP. 15 | 16 | $ sudo tlp-stat -s > /dev/null 17 | Error: TLP's power saving will not apply on boot because tlp.service is not enabled --> Invoke 'systemctl enable tlp.service' to ensure the full functionality of TLP. 18 | 19 | $ # restore 20 | $ sudo systemctl -q enable tlp.service 21 | $ # 22 | $ # --- systemd-rfkill.service/.socket 23 | $ # prepare 24 | $ sudo systemctl -q unmask systemd-rfkill.service 25 | $ sudo systemctl -q unmask systemd-rfkill.socket 26 | $ # test 27 | $ sudo tlp start -- RESTORE_DEVICE_STATE_ON_STARTUP=1 > /dev/null 28 | Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.service is not masked --> Invoke 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. 29 | 30 | Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.socket is not masked --> Invoke 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. 31 | 32 | $ sudo tlp-stat -s -- RESTORE_DEVICE_STATE_ON_STARTUP=1 > /dev/null 33 | Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.service is not masked --> Invoke 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. 34 | 35 | Warning: TLP's radio device switching on boot may not work as expected because RESTORE_DEVICE_STATE_ON_STARTUP=1 is configured and systemd-rfkill.socket is not masked --> Invoke 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. 36 | 37 | $ sudo tlp-stat -s -- DEVICES_TO_ENABLE_ON_STARTUP="dummy" > /dev/null 38 | Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.service is not masked --> Invoke 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. 39 | 40 | Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.socket is not masked --> Invoke 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. 41 | 42 | $ sudo tlp-stat -s -- DEVICES_TO_DISABLE_ON_STARTUP="dummy" > /dev/null 43 | Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.service is not masked --> Invoke 'systemctl mask systemd-rfkill.service' to ensure the full functionality of TLP. 44 | 45 | Warning: TLP's radio device switching on boot may not work as expected because DEVICES_TO_DISABLE_ON_STARTUP or DEVICES_TO_ENABLE_ON_STARTUP is configured and systemd-rfkill.socket is not masked --> Invoke 'systemctl mask systemd-rfkill.socket' to ensure the full functionality of TLP. 46 | 47 | $ # restore 48 | $ sudo systemctl -q mask systemd-rfkill.service 49 | $ sudo systemctl -q mask systemd-rfkill.socket 50 | $ # 51 | $ # --- power-profiles-daemon.service 52 | $ # prepare 53 | $ sudo systemctl -q unmask power-profiles-daemon.service 54 | $ sudo systemctl -q start power-profiles-daemon.service 55 | $ # test -- note: may fail partially depending on hardware 56 | $ sudo tlp start > /dev/null 57 | Warning: PLATFORM_PROFILE_ON_AC/BAT is not set because power-profiles-daemon is running. 58 | Warning: CPU_BOOST_ON_BAT/BAT is not set because power-profiles-daemon is running. 59 | Warning: CPU_ENERGY_PERF_POLICY_ON_AC/BAT is not set because power-profiles-daemon is running. 60 | Warning: AMDGPU_ABM_LEVEL_ON_AC/BAT is not set because power-profiles-daemon is running. 61 | $ sudo tlp-stat -s > /dev/null 62 | Warning: TLP's power saving will not apply on boot because the conflicting power-profiles-daemon.service is active. 63 | $ # restore 64 | $ sudo systemctl -q mask --now power-profiles-daemon.service 65 | $ # 66 | -------------------------------------------------------------------------------- /unit-tests/test-bc_all-simulate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | run_clitest charge-thresholds_simulate1 13 | run_clitest charge-thresholds_simulate2 14 | 15 | ./test-bc_cros-ec-all-simulate.sh 16 | ./test-bc_dell-simulate.sh 17 | 18 | print_report 19 | -------------------------------------------------------------------------------- /unit-tests/test-bc_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | ./test-bc_thinkpad.sh 13 | ./test-bc_all-simulate.sh 14 | 15 | print_report 16 | -------------------------------------------------------------------------------- /unit-tests/test-bc_cros-ec-all-simulate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | if bat_present BAT0; then 13 | export bata=BAT0 14 | export batb=BAT2 15 | elif bat_present BAT1; then 16 | export bata=BAT1 17 | export batb=BAT2 18 | else 19 | # shellcheck disable=SC2059 20 | printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" 21 | report_test "${0##*/}" 22 | report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" 23 | print_report 24 | exit 1 25 | fi 26 | 27 | export xinc="X_BAT_PLUGIN_SIMULATE=cros-ec X_BAT_CROSCC_SIMULATE_ECVER=2" 28 | sudo tlp setcharge ${bata} 35 100 > /dev/null 2>&1 # preset start threshold for simulation 29 | ./test-bc_cros-ec-v2.sh "(cros_charge-control)" 30 | 31 | export xinc="X_BAT_PLUGIN_SIMULATE=framework" 32 | sudo tlp setcharge ${bata} 35 100 > /dev/null 2>&1 # preset start threshold for simulation 33 | ./test-bc_cros-ec-v2.sh "(framework)" 34 | 35 | export xinc="X_BAT_PLUGIN_SIMULATE=cros-ec" 36 | ./test-bc_cros-ec-v3.sh 37 | 38 | sudo tlp setcharge ${bata} > /dev/null 2>&1 # reset test machine to configured thresholds 39 | 40 | print_report 41 | -------------------------------------------------------------------------------- /unit-tests/test-bc_cros-ec-v2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | if bat_present BAT0; then 13 | export bata=BAT0 14 | export batb=BAT2 15 | elif bat_present BAT1; then 16 | export bata=BAT1 17 | export batb=BAT2 18 | else 19 | # shellcheck disable=SC2059 20 | printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" 21 | report_test "${0##*/}" 22 | report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" 23 | print_report 24 | exit 1 25 | fi 26 | 27 | # shellcheck disable=SC2154 28 | echo " # bata=${bata} batb=${batb} xinc=${xinc}" 29 | run_clitest charge-thresholds_cros-ec-v2 "$1" 30 | 31 | print_report 32 | -------------------------------------------------------------------------------- /unit-tests/test-bc_cros-ec-v3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | if bat_present BAT0; then 13 | export bata=BAT0 14 | export batb=BAT2 15 | elif bat_present BAT1; then 16 | export bata=BAT1 17 | export batb=BAT2 18 | else 19 | # shellcheck disable=SC2059 20 | printf "${ANSI_RED}Error:${ANSI_BLACK} neither BAT0 nor BAT1 exists.\n\n" 21 | report_test "${0##*/}" 22 | report_line "${ANSI_RED}FAIL:${ANSI_BLACK} not run - neither BAT0 nor BAT1 exists" 23 | print_report 24 | exit 1 25 | fi 26 | 27 | # shellcheck disable=SC2154 28 | echo " # bata=${bata} batb=${batb} xinc=${xinc}" 29 | run_clitest charge-thresholds_cros-ec-v3 30 | 31 | print_report 32 | -------------------------------------------------------------------------------- /unit-tests/test-bc_dell-simulate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | export xinc="X_BAT_PLUGIN_SIMULATE=dell" 13 | echo " # xinc=${xinc}" 1>&2 14 | run_clitest charge-thresholds_dell "" "$1" 15 | sudo tlp setcharge BAT0 > /dev/null 2>&1 # reset test machine to configured thresholds 16 | 17 | print_report 18 | -------------------------------------------------------------------------------- /unit-tests/test-bc_thinkpad.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | start_report 10 | 11 | if cat /sys/class/power_supply/BAT1/charge_control_end_threshold > /dev/null 2>&1; then 12 | run_clitest charge-thresholds_thinkpad-BAT1 13 | elif cat /sys/class/power_supply/BAT0/charge_control_end_threshold > /dev/null 2>&1; then 14 | run_clitest charge-thresholds_thinkpad 15 | elif cat /sys/devices/platform/smapi/BAT0/stop_charge_thresh > /dev/null 2>&1; then 16 | run_clitest charge-thresholds_thinkpad-legacy 17 | fi 18 | 19 | print_report 20 | -------------------------------------------------------------------------------- /unit-tests/test-func: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # func-test - Unit Test Helper Functions 3 | # 4 | # Copyright (c) 2025 Thomas Koch and others. 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | 7 | # ---------------------------------------------------------------------------- 8 | # Constants 9 | 10 | readonly ANSI_RED="\033[31m" 11 | readonly ANSI_GREEN="\033[32m" 12 | readonly ANSI_BLACK="\033[m" 13 | 14 | # ---------------------------------------------------------------------------- 15 | # Functions 16 | 17 | printf_msg () { 18 | # print message to stderr and logfile 19 | # $1: format string 20 | # $2..$n: message string(s) 21 | local fmt="$1" 22 | shift 23 | # shellcheck disable=SC2154,SC2059 24 | printf "$fmt" "$@" | tee "${_logfile:-/dev/null}" 1>&2 25 | } 26 | 27 | test_root () { 28 | # test root privilege -- rc: 0=root, 1=not root 29 | [ "$(id -u)" = "0" ] 30 | } 31 | 32 | cache_root_cred () { 33 | # cache user credentials before actual testing 34 | sudo true 35 | } 36 | 37 | check_tlp () { 38 | # test if tlp installed 39 | if [ ! -f /usr/sbin/tlp ]; then 40 | printf_msg "Error: %s not installed." "$TLP" 1>&2 41 | exit 254 42 | fi 43 | } 44 | 45 | read_sysf () { 46 | # read and print contents of a sysfile 47 | # return 1 and print default if read fails 48 | # $1: sysfile 49 | # $2: default 50 | # rc: 0=ok/1=error 51 | if cat "$1" 2> /dev/null; then 52 | return 0 53 | else 54 | printf "%s" "$2" 55 | return 1 56 | fi 57 | } 58 | 59 | write_sysf () { # write string to a sysfile 60 | # $1: string 61 | # $2: sysfile 62 | # rc: 0=ok/1=error 63 | { printf '%s\n' "$1" > "$2"; } 2> /dev/null 64 | } 65 | 66 | compare_sysf () { 67 | # Compare a string to the contents of a sysfile 68 | # expression 69 | # $1: string 70 | # $2: file 71 | 72 | local cmp_str="$1" 73 | local sys_str 74 | 75 | if [ -f "$2" ] && sys_str=$(read_sysf "$2"); then 76 | if [ "$sys_str" != "$cmp_str" ]; then 77 | printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$2" "$sys_str" "$cmp_str" 78 | return 1 79 | fi 80 | else 81 | printf_msg "\n*** Deviation for %s: sysfile does not exist.\n" "$2" 82 | return 2 83 | fi 84 | 85 | return 0 86 | } 87 | 88 | glob_compare_sysf () { 89 | # Compare a string to the contents of sysfiles selected by a glob 90 | # expression 91 | # $1: string 92 | # $2..$n: file, ... 93 | 94 | local cmp_str="$1" 95 | local file_pat="$*" 96 | file_pat="${file_pat#* }" 97 | local sys_str 98 | local cnt=0 99 | 100 | while shift && [ $# -gt 0 ]; do 101 | if [ -f "$1" ] && sys_str=$(read_sysf "$1"); then 102 | cnt=$((cnt + 1)) 103 | if [ "$sys_str" != "$cmp_str" ]; then 104 | printf_msg "\n*** Deviation at %s: %s (act) != %s (exp)\n" "$1" "$sys_str" "$cmp_str" 105 | return 1 106 | fi 107 | fi 108 | done 109 | 110 | if [ "$cnt" -eq 0 ]; then 111 | printf_msg "\n*** Deviation for %s: no matching sysfile(s) exist(s).\n" "$file_pat" 112 | return 2 113 | fi 114 | 115 | return 0 116 | } 117 | 118 | print_nth_arg () { 119 | # Get n-th argument 120 | # $1: n 121 | # $2..$m: arguments 122 | local n="$1" 123 | [ "$1" -gt 0 ] || return 124 | 125 | until [ "$n" -eq 0 ] || [ $# -eq 0 ]; do 126 | shift 127 | n=$((n - 1)) 128 | done 129 | printf "%s" "$1" 130 | } 131 | 132 | on_ac () { 133 | # Detect AC power 134 | # rc: 0=AC/1=BAT 135 | # Note: compared to get_sys_power_supply() this is primitive. but it will do. 136 | upower -i /org/freedesktop/UPower/devices/line_power_AC 2> /dev/null | grep -qE 'online:\s+yes' 137 | } 138 | 139 | bat_present () { 140 | # Check for battery 141 | # $1: battery name 142 | # rc: 0=present/1=absent 143 | 144 | [ "$(read_sysf "/sys/class/power_supply/$1/present")" = "1" ] 145 | } 146 | 147 | run_clitest () { 148 | # Run clitest script and record result line to file 149 | # $1: script filepath 150 | # $2: suffix 151 | # global param: $_report_file 152 | 153 | if [ -f "$_report_file" ]; then 154 | printf "%-50s --> " "${1##*/} $2" >> "$_report_file" 155 | clitest --color always "$1" | tee /dev/fd/2 | grep -E '(OK|FAIL):' >> "$_report_file" 156 | else 157 | clitest --color always "$1" 1>&2 158 | fi 159 | printf "\n" 1>&2 160 | } 161 | 162 | is_uint () { # check for unsigned integer -- $1: string 163 | printf "%s" "$1" | grep -E -q "^[0-9]+$" 2> /dev/null 164 | } 165 | 166 | start_report () { 167 | # Create report file 168 | # retval: $_report_file, $_nest_level 169 | 170 | if [ -z "$_report_file" ]; then 171 | # first call -> create report temp file 172 | if ! _report_file="$(mktemp --tmpdir "tlp-test-report.XXX")"; then 173 | printf "Error: failed to create report file.\n" 1>&2 174 | fi 175 | export _report_file 176 | export _nest_level=0 177 | else 178 | # increment level 179 | if is_uint "$_nest_level"; then 180 | export _nest_level="$((_nest_level + 1))" 181 | else 182 | export _nest_level=1 183 | fi 184 | fi 185 | } 186 | 187 | report_test () { 188 | # Write test name to report 189 | if [ -f "$_report_file" ]; then 190 | printf "%-50s --> " "$1" >> "$_report_file" 191 | fi 192 | } 193 | 194 | report_line () { 195 | # Write text line to report 196 | # $1: text 197 | if [ -f "$_report_file" ]; then 198 | # note: use output string in format for proper ansi esc sequence interpolation 199 | # shellcheck disable=SC2059 200 | printf "$1\n" >> "$_report_file" 201 | fi 202 | } 203 | 204 | report_result () { 205 | # Write test result to terminal and report 206 | # $1: # of tests 207 | # $2: # of errors 208 | if [ "$2" -eq 0 ]; then 209 | printf_msg "${ANSI_GREEN}OK:${ANSI_BLACK} %s of %s tests passed\n\n" "$1" "$1" 210 | report_line "${ANSI_GREEN}OK:${ANSI_BLACK} $1 of $1 tests passed" 211 | else 212 | printf_msg "${ANSI_RED}FAIL:${ANSI_BLACK} %s of %s tests failed\n\n" "$2" "$1" 213 | report_line "${ANSI_RED}FAIL:${ANSI_BLACK} $2 of $1 tests failed" 214 | fi 215 | } 216 | 217 | print_report () { 218 | [ "$_nest_level" -gt 0 ] && return 219 | 220 | if [ -f "$_report_file" ]; then 221 | printf "+++ Test Summary ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" 222 | cat "$_report_file" 223 | printf "\n" 224 | rm -f "$_report_file" 225 | else 226 | printf "Error: missing report file ''%s''.\n" "$_report_file" 1>&2 227 | fi 228 | } 229 | -------------------------------------------------------------------------------- /unit-tests/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | readonly TESTLIB="./test-func" 3 | # shellcheck disable=SC1090 4 | . $TESTLIB || { 5 | printf "Error: missing library %s\n" "${TESTLIB}" 1>&2 6 | exit 70 7 | } 8 | 9 | cache_root_cred 10 | start_report 11 | 12 | ./test-cpufreq.sh 13 | ./test-gpufreq.sh 14 | ./test-bc_all.sh 15 | 16 | print_report 17 | -------------------------------------------------------------------------------- /unit-tests/unit-tests.rst: -------------------------------------------------------------------------------- 1 | Unit Tests for TLP 2 | ================== 3 | Tests cover only part of the functionality of TLP. 4 | For objectives see the individual files. 5 | 6 | Prequisites 7 | ----------- 8 | Software 9 | ^^^^^^^^ 10 | Tests are executed with the tool 11 | `clitest `_. 12 | 13 | Hardware 14 | ^^^^^^^^ 15 | Unit tests must be run on real hardware, not in a virtual machine or container. 16 | See the individual files for hardware details. 17 | --------------------------------------------------------------------------------