├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .packit.yaml ├── CHANGES ├── Makefile ├── README ├── TODO ├── dkms.conf ├── hdaps.c ├── thinkpad_ec.c ├── thinkpad_ec.h ├── tp_smapi.c └── tp_smapi.spec /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '**' 9 | pull_request: 10 | 11 | concurrency: 12 | group: ${{ github.ref_name }}-${{ github.workflow }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.host }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | linux: 22 | - v6.1 23 | - master 24 | host: 25 | - ubuntu-22.04 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | path: tp_smapi 30 | - uses: actions/checkout@v4 31 | with: 32 | repository: torvalds/linux 33 | ref: ${{ matrix.linux }} 34 | path: linux 35 | - run: sudo apt-get install -y libelf-dev ccache 36 | - uses: hendrikmuhs/ccache-action@v1.2 37 | with: 38 | key: ${{ matrix.linux }} 39 | - name: Configure kernel 40 | run: | 41 | make -C linux tinyconfig 42 | echo -e "CONFIG_64BIT=y\nCONFIG_X86_64=y\nCONFIG_MODULES=y\nCONFIG_INPUT=y" > linux/kernel/configs/ci.config 43 | make -C linux ci.config 44 | - name: Run make -C linux 45 | run: | 46 | export PATH="/usr/lib/ccache:$PATH" 47 | make -C linux -j $(nproc) 48 | - run: make -C tp_smapi modules KBUILD=../linux HDAPS=1 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.cmd 2 | *.ko 3 | *.mod.c 4 | *.o 5 | .tmp_versions 6 | Module.symvers 7 | modules.order 8 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | # See the documentation for more information: 2 | # https://packit.dev/docs/configuration/ 3 | 4 | specfile_path: tp_smapi.spec 5 | 6 | # add or remove files that should be synced 7 | files_to_sync: 8 | - tp_smapi.spec 9 | - .packit.yaml 10 | 11 | # name in upstream package repository/registry (e.g. in PyPI) 12 | upstream_package_name: tp_smapi 13 | # downstream (Fedora) RPM package name 14 | downstream_package_name: tp_smapi 15 | 16 | upstream_tag_template: tp-smapi/{version} 17 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Change history for the tp_smapi package: 2 | 3 | 0.44 2023-07-31 4 | --------------------- 5 | - Support for 6.4 kernels 6 | 7 | 0.43 2018-02-24 8 | --------------------- 9 | - Support for 4.15 kernels 10 | 11 | 0.42 2016-04-23 12 | --------------------- 13 | - fixes for newer kernels 14 | - kbuild support 15 | - dkms support 16 | - quirks update 17 | - RPM support 18 | 19 | 0.41 2011-07-29 20 | --------------------- 21 | - new maintainer 22 | - fixes for newer kernels 23 | - fixes for newer ThinkPads (esp. SandyBridge ones) 24 | 25 | 0.40 2008-12-16 26 | --------------------- 27 | - thinkpad_ec: Added a "force_io=1" module parameter, to let it load on 28 | recent ThinkPad models (e.g., T400 and T500). This is a kludge to work 29 | around an ACPI DSDT which claims the ports we need. 30 | - tp_smapi: Don't restore battery charging thresholds when resuming from 31 | suspend-to-RAM. 32 | - When building, use /lib/modules/*/build instead of /lib/modules/*/src 33 | and give a clearer error message if that's missing. 34 | 35 | 0.39 2008-09-27 36 | --------------------- 37 | - Fixed compilation on kernels <2.6.26 38 | (thanks to Evgeni Golov and Whoopie!) 39 | 40 | 0.38 2008-09-26 41 | --------------------- 42 | - Fixed compilation on kernel 2.6.27-rc7. 43 | - tp_smapi: Added a new battery attribute 44 | /sys/devices/platform/smapi/BAT?/remaining_percent_error 45 | This is the error margin for the remaing_percent attribute. 46 | Unfortunately, it doesn't seem to be reset by battery calibration. 47 | - hdaps: Removed the fake_data_mode attribute. 48 | 49 | 0.37 2008-03-28 50 | --------------------- 51 | - Fix compilation on kernel 2.6.25 52 | - hdaps whitelist: changed default orientation of ThinkPad R60, R61, X40, X41. 53 | - tp_smapi: Added 3 new read-only battery attributes 54 | /sys/devices/platform/smapi/BAT?/remaining_running_time_now 55 | (time remaining according to instantenous power instead of average) 56 | /sys/devices/platform/smapi/BAT?/charging_max_current 57 | (maximum current allowed during charging) 58 | /sys/devices/platform/smapi/BAT?/charging_max_voltage 59 | (maximum voltage allowed during charging) 60 | - tp_smapi and README: improved documentation of battery info attributes. 61 | 62 | 0.36 2008-01-27 63 | --------------------- 64 | - tp_smapi: Added a 4th cell group voltage attribute: 65 | /sys/devices/platform/smapi/BAT0/group3_voltage 66 | - tp_smapi: Reversed order of the group{0,1,2}_voltage attributes. 67 | - hdaps whitelist: changed default orientation of ThinkPad X61. 68 | - README: Improve attributes documentation. 69 | - Sourcecode cleanups. 70 | 71 | 0.35-test1 2008-01-21 72 | --------------------- 73 | - Allow loading on new T61 firmware that caused an "hdaps_check_ec failed 74 | error. Correct operation with this firmware is not yet fully tested. 75 | - tp_smapi: Added 3 new sysfs attributes: 76 | /sys/devices/platform/smapi/BAT0/group0_voltage 77 | /sys/devices/platform/smapi/BAT0/group1_voltage 78 | /sys/devices/platform/smapi/BAT0/group2_voltage 79 | ThinkPad batteries (at least on the 600, X3x, T4x and R5x models) contains 80 | 3 cell groups in series, where each group consisting of 2 or 3 cells 81 | connected in parallel. The voltage of each group is given by these attributes. 82 | (The effective performance of the battery is determined by the weakest 83 | group, i.e., the one those voltage changes most rapidly during dis/charging.) 84 | - hdaps whitelist: changed default orientation of ThinkPad X60 Tablet, 85 | X61 Tablet and ThinkPad X60s and ThinkPd T60. 86 | - hdaps: when hdaps_ec_check fails, report the values received from the EC. 87 | 88 | 0.34 2008-01-06 89 | --------------------- 90 | - hdaps: Fixed handling of "invert" module parameter (make it a uint, 91 | and don't let whitelist override module parameter). 92 | Contributed by Ostap Cherkashin. 93 | - hdaps whitelist: change ThinkPad X61s and T61 default orientation. 94 | X60 Tablet and X61 Tablet remain wrong until someone reports their DMI ID. 95 | 96 | 0.33 2007-12-22 97 | --------------------- 98 | - Ported to kernel 2.6.24-rc6. 99 | Building on older kernels will cause harmless warnings about pointer types. 100 | (Makefile patch contributed by Tim Niemeyer and Whoopie.) 101 | - README: Document the additional hdaps attributes and the reduced timer 102 | interrupts introduced on 0.32. 103 | - hdaps: Extends the "invert" parameter to cover all possible axis 104 | orientations. 105 | The possible values are as follows. 106 | Let X,Y denote the hardware readouts. 107 | Let R denote the laptop's roll (tilt left/right). 108 | Let P denote the laptop's pitch (tilt forward/backward). 109 | invert=0: R= X P= Y (same as mainline) 110 | invert=1: R=-X P=-Y (same as mainline) 111 | invert=2: R=-X P= Y (new) 112 | invert=3: R= X P=-Y (new) 113 | invert=4: R= Y P= X (new) 114 | invert=5: R=-Y P=-X (new) 115 | invert=6: R=-Y P= X (new) 116 | invert=7: R= Y P=-X (new) 117 | It's probably easiest to just try all 8 possibilities and see which yields 118 | correct results (e.g., in the hdaps-gl visualisation). 119 | - hdaps: Adds a whitelist which automatically sets the correct axis orientation 120 | for some models. If the value for your model is wrong or missing, you can 121 | override it using the "invert" parameter. Please also update the tables at 122 | http://www.thinkwiki.org/wiki/tp_smapi and 123 | http://www.thinkwiki.org/wiki/List_of_DMI_IDs 124 | and submit a patch for the whitelist in hdaps.c. 125 | - tp_smapi: Report EOPNOTSUPP instead of ENOSYS for unimplemented SMAPI 126 | functions. 127 | 128 | 0.32 2007-07-28 129 | --------------------- 130 | - hdaps: Added a second input device which publishes the raw sensor position, 131 | without joystick fuzz. In conjunction with a new hdapsd, this lets us 132 | avoid the frequent redundant interrupts caused by hdapsd. 133 | - hdaps: Stop polling the hardware when no one is listening. This prevents 134 | unnecessary interrupts on tickless kernel. (Contributed by Michael Riepe.) 135 | - In the patch generated by "make patch", thinkpad_ec and tp_smapi now reside 136 | under drivers/misc instead of drivers/platform. 137 | - "make patch" fixed for kernel 2.6.22. 138 | - Removed kludge for kernels lacking the DMI OEM String decode patch. 139 | - Now requires kernel >= 2.6.19 (due to above). 140 | - Removed the useless "enable_pci_power_saving_on_boot" sysfs attribute. 141 | 142 | 0.31 2007-03-07 143 | --------------------- 144 | Visible changes: 145 | - Fix #includes to allow compilation on 64-bit platforms 146 | (patch by Emil Renner Berthing). 147 | - thinkpad_ec: Avoid spurious "bad end STR3: (...)->0x80" warnings 148 | by relaxing status check (still OK according to H8 EC docs). 149 | - Invert axes on all T60, not just T60p. 150 | 151 | Internal changes: 152 | - Add __init to find_smapi_port() and thinkpad_ec_test() 153 | - tp_smapi.c: Fix "initialization from incompatible pointer type" warnings 154 | on 64-bit platforms 155 | 156 | 0.30 2006-09-03 157 | --------------------- 158 | Visible changes: 159 | - Makefile: now supports kernels built with separate source and build 160 | directories (thanks to Lenz Grimmer). 161 | To work against against some kernel installed under /lib/modules/ 162 | add an appropriate KVER= parameter: 163 | # make patch KVER=2.6.16-rc2 164 | If it's not installed you'll need to set KSRC and KBUILD too: 165 | # make patch KVER=2.6.16-rc2 KSRC=$HOME/2.6.16-rc2 KBUILD=$HOME/2.6.16-rc2 166 | - Changed the format of /sys/devices/platform/smapi/smapi_request 167 | (no more register names). 168 | 169 | Internal changes: 170 | - tp_smapi: the 'dump' sysfs attribute now outputs only rows 0x00 through 0x0a, 171 | since 0x0b causes an EC hang on some firmware (e.g., all T42 and old T43). 172 | Thanks to Henrique and Sukant for tracking this down! 173 | - thinkpad_ec: Log a warning if 0x161F returns 0x80. 174 | - thinkpad_ec: Increase TPC_REQUEST_RETRIES 175 | - thinkpad_ec: Report row args upon data read error. 176 | - hdaps: Apply hwmon-hdaps-handle-errors-from-input-register-device.patch 177 | from -mm. 178 | 179 | 0.29 2006-08-17 180 | --------------------- 181 | Visible changes: 182 | - Added new sysfs attributes to tp_smapi: 183 | /sys/devices/platform/smapi/BAT0/remaining_percent 184 | /sys/devices/platform/smapi/BAT0/remaining_running_time 185 | /sys/devices/platform/smapi/BAT0/remaining_charging_time 186 | /sys/devices/platform/smapi/BAT0/temperature # milli-Celsius 187 | All of these are read directly from the embedded controller, so they are 188 | more accurate than doing your own computation. 189 | 190 | Internal changes: 191 | - tp_smapi: Extended and commented battery attribute show functions. 192 | 193 | 0.28 2006-08-16 194 | --------------------- 195 | Visible changes: 196 | - thinkpad_ec: Removed 'debug' module parameter, it no longer affected much. 197 | - tp_smapi: Remove optical drive speed control (formerly #ifdefed out). 198 | 199 | Internal changes: 200 | - hdaps: Removed __init from hdaps_check_ec(). This fixes crashes on resume 201 | (Thanks, Igor!). 202 | - hdaps: Moved axis transformation to dedicated function transform_axes(). 203 | - tp_smapi: Cleaned up printk() output, changed macros. 204 | - tp_smapi: Added "E" to SMAPI parameter names (BX->EBX) - they're 32-bit. 205 | - tp_Smapi: Removed verbose printks()s on bad sysfs args. 206 | - A lot of small cleanups. 207 | 208 | 0.27 2006-08-05 209 | --------------------- 210 | Visible changes: 211 | - Renamed tp_base.* to thinkpad_ec.*. 212 | NOTE: If you have manual modprobe commands or configuration mentioning 213 | tp_base, you'll need to change them. 214 | - hdaps: All HDAPS-equippedThinkPads should be now be automatically 215 | recognized. Whitelisting is needed only for changing the axis 216 | configuration. 217 | - hdaps: Removed 'force' module parameter, it's no longer needed. 218 | 219 | Internal changes: 220 | - Renamed all tp_controller_* functions to thinkpad_ec_*. 221 | - Small change to invalidation in hdaps_device_init(). 222 | - Separate Kconfig changes into separate patches. 223 | - Clean up generated patch format a bit. 224 | 225 | 0.26 2006-08-04 226 | --------------------- 227 | - The modified hdaps.c is now included as a full file (based on linux git) 228 | instead of a patch, to solve compilation and versioning problems. 229 | - Makefile: Fixed a problem with "make patch" on new kernels. 230 | - tp_smapi.c: renamed internal attribute functions. 231 | 232 | 0.25 2006-08-03 233 | --------------------- 234 | - Added new attribute: 235 | /sys/devices/platform/smapi/enable_pci_power_saving_on_boot 236 | This controls the "PCI bus power saving" option in the BIOS (takes 237 | effect at the next boot). 238 | - Added new attribute: 239 | /sys/devices/platform/smapi/smapi_request 240 | This performs raw SMAPI calls. It uses a bad interface that cannot handle 241 | multiple simultaneous accesses. Don't touch it, it's for development only. 242 | - "make patch" works again. If needed, the resulting patch will include 243 | the dmi-decode-and-save-oem-string-information patch. 244 | - "make load" now gives an informative error if ran as non-root. 245 | - Makefile: Give nicer error message on kernel <= 2.6.16. 246 | - Makefile: Verify that /usr/sbin/dmidecode works. 247 | 248 | 0.24-test3 2006-08-01 249 | --------------------- 250 | - tp_base: fix compilation error on unpatched kernels. 251 | 252 | 0.24-test1 2006-07-31 253 | --------------------- 254 | This is an experimental version with a few quirks necessary for testing new 255 | code. It's OK to run it yourself to try the new features, but it's not 256 | suitable for packaging in distributions. The stable version remains 0.22. 257 | 258 | Changes to loading, whitelisting and building: 259 | - Detection of the embedded controller (needed for all our modules) is now 260 | done according to OEM Strings in the DMI information. If the EC is not 261 | mentioned there (you can check it in 'dmidecode' too) you'll get a 262 | "tp_base: no ThinkPad embedded controller!" message in dmesg and the 263 | modules won't load. If tp_smapi used to work and was broken by this, 264 | please report. A few known exceptions are already 265 | - The above wants the following kernel patch (soon to be included in -mm): 266 | diff/dmi-decode-and-save-oem-string-information.patch 267 | If this patch is not already applied to the current kernel, the Makefile 268 | and tp_base.c default to an ugly kludge where the DMI information is 269 | hardcoded into your driver instead of being read in runtime. You'll get 270 | a warning about it, but it will work nicely. Just don't run the compiled 271 | modules on a different machine. 272 | Sorry for the mess, but we need to exercise the DMI check code. 273 | - "make patch" is temporarily broken, due to the above. 274 | - hdaps: Ignore the "initial mode latch" during init, we're not using it and 275 | Lenovo keeps adding new ones. 276 | 277 | Changes to hdaps functionality: 278 | - hdaps: New attribute /sys/devices/platform/hdaps/sampling_rate. 279 | This determines how frequently of accelerometer state refresh, independently 280 | of userspace polling (i.e., reading the position attribute more frequently 281 | than this will yield duplicate readouts). Default=50. 282 | - Multiple hdaps applications can now run without stealing each other's data. 283 | Set /sys/devices/platform/hdaps/sampling_rate to the highest rate required 284 | by any app. 285 | - hdaps: New attribute /sys/devices/platform/hdaps/oversampling_ratio. 286 | When set to X, the embedded controller is told to do physical accelerometer 287 | measurements at a rate that is X times higher than the rate at which 288 | the driver reads those measurements (i.e., X*sampling_rate). This 289 | reduces sample phase difference is, and useful for the running average 290 | filter (see next). Default=5. 291 | - hdaps: New attribute /sys/devices/platform/hdaps/running_avg_filter_order. 292 | When set to X, reported readouts will be the average of the last X physical 293 | accelerometer measurements. Current firmware allows 1<=X<=8. Setting to a 294 | high value decreases readout fluctuations. The averaging is handled 295 | by the embedded controller, so no CPU resources are used. 296 | Default=2 (this implicit in previous versions). 297 | - hdaps: New attribute /sys/devices/platform/hdaps/fake_data_mode (write only). 298 | If set to 1, enables a test mode where the physical accelerometer readouts 299 | are replaced with an incrementing counter. This is useful for checking the 300 | regularity of the sampling interval and driver<->userspace communication. 301 | 302 | Changes to tp_smapi functionality: 303 | - tp_smapi: Removed the units ("mV", "mWh" etc.) from all /sys readouts, to 304 | follow kernel convention. 305 | - tp_smapi: /sys/devices/platform/smapi/BAT?/dump now contains additional 306 | data lines (but we don't know what they mean). Default=0. 307 | - tp_smapi: /sys/devices/platform/smapi/BAT?/dump contains some unused values; 308 | they're now omitted (replaced by "--") instead of showing semi-random junk. 309 | 310 | Other changes: 311 | - tp_smapi: fixed signness bug in current_now and current_avg when 312 | discharging (thanks to Pavel Machek!). 313 | - hdaps: Turn off power and EC polling upon suspend or module unload. 314 | - hdaps: Check more EC function return values. 315 | - hdaps: Refactored some functions. 316 | - tp_base: Test EC on module load and refuse loading if EC not found. 317 | - tp_base, hdaps, tp_smapi: changed args passing and made it more concise. 318 | - tp_smapi: cleaned up attribute macros. 319 | - tp_smapi: cleaned up smapi retry loop. 320 | - tp_smapi: removed smapi_write and dropped outAX param from smapi_request. 321 | - tp_smapi: improved comments. 322 | - tp_base: moved comments from tp_base.h to tp_base.c. 323 | 324 | Many of the above changes were suggested or contributed by 325 | Henrique de Moraes Holschuh. 326 | 327 | 0.22 2006-07-20 328 | ---------------- 329 | - hdaps: Added support for X60s (but X axis is still inverted). 330 | - tp_base: Wait until EC starts replying before considering a request 331 | successful (this improves stability and eliminates abnormal events.) 332 | - hdaps: Removed /sys/devices/platform/hdaps/{variance,temp2}. They actually 333 | show an older pending readout (if there is one), so it never makes sense 334 | to read them via the existing interface. 335 | - tp_base: Added many additional status and consistency checks on EC, 336 | to give early warning on any strange behavior. 337 | - tp_base: New constants and comments greatly clarify access to the EC. 338 | - hdaps: Convert all init code to use tp_base transactions. Mo more direct 339 | IO port access. 340 | - hdaps: Move some init code to new functions hdaps_set_power() and 341 | hdaps_set_ec_config(). These will be exposed as attributes in the future. 342 | - hdaps: If init failed, tell at which step it happened. 343 | - tp_base: Allow additional inputs and optional outputs to EC requests. 344 | - tp_base: Decrease timeout for reply to request. 345 | - Kconfig: Make TP_BASE an invisible tristate option 346 | - tp_base: Rename tp_controller_trylock() to tp_controller_try_lock(). 347 | - tp_base: make tp_controller_lock() return an int; have it checked in 348 | tp_smapi and hdaps. 349 | - tp_base: Rewrote all printk()s using macros, and made debug=1 quieter. 350 | - Makefile: work even if $SHELL is not bash. 351 | - Makefile: Autopatch hdaps patch to work with 2.6.18-rc1 352 | 353 | Many of the above changes were suggested or contributed by 354 | Henrique de Moraes Holschuh. 355 | 356 | 0.21 2006-06-21 357 | ---------------- 358 | - Compatibility with kernel 2.6.17. 359 | - hdaps: Fixed a locking bug. This further reduces (completely solves?) the 360 | EC hangs problem. 361 | (Thanks to Rasto, Andrew and Whoopie for helping track this down!) 362 | - hdaps: Support for ThinkPad T60: added 0x04 as valid initial latch value 363 | - hdaps: Mousedev poll timer was not restarted after resume from suspend. 364 | - tp_base: Added verbose debug output, enabled by the "debug=1" module 365 | parameter (and by "make load DEBUG=1") 366 | - tp_base: Cosmetic fix to tp_base DMI vendor reporting (patch by Whoopie). 367 | - Cleaned up some printk()s. 368 | - Makefile: "make load" now enables dmesg debug output only if given DEBUG=1. 369 | - Makefile: will not delete hdaps.c if edited manually after generation. 370 | 371 | 0.20 2006-04-30 372 | ---------------- 373 | - Makefile: fix for "linux/tp_base.h: No such file or directory" problem. 374 | - tp_base: whitelist "LENOVO" vendor string too. 375 | - hdaps: add "force=1" parameter to force loading on models that are 376 | not whitelisted (patch by Whoopie). 377 | - hdaps: fix init on several models by using tp_controller_read_row() 378 | instead of direct port IO. This apparently also makes the initial latch 379 | value is meaningful even when the device is already initialized. 380 | - Now requires kernel 2.6.15 or newer (compatibility cruft removed). 381 | 382 | 0.19 2006-04-08 383 | ---------------- 384 | - Added new battery status attributes: 385 | /sys/devices/platform/smapi/BAT?/first_use_date 386 | /sys/devices/platform/smapi/BAT?/manufacture_date 387 | - Bugfix in tp_base embedded controller readout 388 | (missing prefetch invalidation). This apparently solves remnants of the 389 | the embedded-controller-lockup problem. 390 | - "make load" now demands "HDAPS=1" if the hdaps module is loaded. 391 | - hdaps driver patch: 392 | - Remember keyboard/mouse activity for 0.1sec, so that all applications 393 | get a chance to read it (the hardware resets its activity flag 394 | when it's read). 395 | 396 | 0.18 2006-04-01 397 | ---------------- 398 | - Solved embedded controller lockups with HDAPS=1 (see hdaps comments below). 399 | - Added new function tp_controller_try_read_row() to tp_base. 400 | - Added extra status checks in tp_base, to catch abnormal conditions earlier. 401 | - Restructured several functions in tp_base for clarity and reusability. 402 | - Restructured tp_smapi attribute registration code using fancy macros, to 403 | remove redundancy. 404 | - Reduced prefetch delay and maximum retries in tp_base. 405 | - Now locks tp_controller when making SMAPI call (just in case). 406 | - Makefile change to fix compilation in Debian. 407 | - Minor cleanups and added comments in tp_smapi.c. 408 | - Major changes to hdaps patch: 409 | - Whenever we read data from the controller, parse all of it and 410 | remember the values in global vars. 411 | - Simplified the device model *_show functions, via the above change. 412 | - If the mousedev poll timer handler experiences a transient fault, use 413 | use last saved data from the global vars. 414 | - Handle delayed calibration at first opportunity, not just mousedev polls. 415 | - Disable mousedev poll timer when suspending, to prevent readouts before 416 | the resume code re-initializes the sensor. 417 | - Don't accept initial status 0x00, it was probably an artifact of 418 | the premature mousepoll invocation. 419 | - When doing a sensor readout in mousedev poll, avoid time-consuming 420 | retries and fetches, to minimize time in softirq. This effectively 421 | exorcises an embedded controller lockup Heisenbug. There may still 422 | a nonzero probability of lockup, but now it's not worse than the 423 | vanilla hdaps (since they do essentially the same). 424 | 425 | 0.17 2006-02-10 426 | ---------------- 427 | - Fixed off-by-one bugs in charge threshold handling + cleanup 428 | - Enforce bounds on threshold the same way Battery Maximizer does 429 | - Recognize more battry status values 430 | - Fixes in error reporting 431 | - Fixed a warning in tp_smapi.c 432 | - hdaps driver patches: 433 | - Informatively report init errors 434 | - Recognize 0x00 as a valid latch value 435 | - Increase init timeouts 436 | 437 | 0.16 2006-01-11 438 | ---------------- 439 | - Renamed smapi/BAT?/force_discharge1 to smapi/BAT?/force_discharge 440 | - Removed smapi/BAT?/force_discharge2 (doesn't work on any model) 441 | - HDAPS patch changes: 442 | - Check ready status (instead of occasionally returning junk) also for 443 | variance, not just for position. 444 | - Calibrate unsynchronously (reduces load and resume time by 1-2 seconds) 445 | - Fix position readouts - read word, not byte. 446 | - Fixed driver lockup when writing to hdaps/variance. 447 | - Changes in diff handling. 448 | 449 | 0.15 2006-01-10 450 | ---------------- 451 | - Bugfix: 0.14 had broken SM BIOS call code. 452 | 453 | 0.14 2006-01-09 454 | ---------------- 455 | This version has no user-visible functionality changes, but improves 456 | the reliability of coordinating hardware access with the hdaps driver. 457 | - Moved controller access code to tp_base. 458 | - Changes in HDAPS driver patch: 459 | - Use tp_base for controller access instead of direct port IO. Using 460 | the "row read" and "prefetch" abstraction of tp_base makes the hdaps 461 | code shorter, clearer and safer. 462 | - Added checking of the STATUS port and automatic retries if device is not 463 | ready (previously it just returned junk values). 464 | - Added missing lock in hdaps_invert_store. 465 | - A few local code simplifications 466 | - Redue the retry delay (200ms was excessive). 467 | - Changed SMAPI calls to use 32-bit args. 468 | - Made "make patch" produce an LKML-compliant patches 469 | (include a diffstat, remove CD_SPEED, remove <2.6.15 #ifdefs). 470 | - Changes in Makefile and directory structure. 471 | 472 | 0.13 2005-12-21 473 | ---------------- 474 | - First step toward resolving conflict with the hdaps module: 475 | - Added a tp_base module, which handles coordination of access to the 476 | ThinkPad controller (thanks to Alan Cox and Rovert Love). 477 | - Changed tp_smapi to require and use tp_base. 478 | - "make load HDAPS=1" and "make install HDAPS=1" will copy and patch hdaps.c 479 | for compatibility with tp_smapi and tp_base, and then load or install it. 480 | - Added a "make patch" target, which creates a stand-alone patch against the 481 | current kernel tree (thanks, Spiney!). 482 | - Future kernel compatibility: avoids platform_device_register_simple. 483 | 484 | 0.12 2005-12-16 485 | ---------------- 486 | - Added smapi/BAT?/force_discharge1 and 487 | smapi/BAT?/force_discharge2. 488 | When set to 1, they stop forces discharging of battery even if on AC power. 489 | The two files have the same functionaliy but use different SMAPI calls to 490 | achieve it. On ThinkPad T43 only force_discharge1 works; the other one is 491 | completely untested. 492 | - Removed smapi/cd_speed (unless you set "#define PROVIDE_CD_SPEED"), since 493 | its function is provided in a safer way by the combination of "hdparm -E" 494 | (for CD) and speedcontrol (for DVD, http://safari.iki.fi/speedcontrol.c). 495 | 496 | 0.10 2005-12-13 497 | ---------------- 498 | - Added smapi/BAT?/state (idle/charging/discharing). 499 | - Added smapi/BAT?/{power_now,power_avg}. 500 | - Renamed smapi/BAT?/{current1,current2} to {current_now,current_avg}. 501 | - If stop_charge_thresh is unsupported, when trying to set it don't affect 502 | start_charge_thresh. 503 | - smapi/BAT?/{design_capacity,last_full_capacity} were reversed. 504 | - smapi/cdrom_speed renamed to cd_speed, and safety mechanism added: you must 505 | use "echo 1 yes_crash_my_computer > /sys/devices/platform/smapi/cd_speed". 506 | - Added smapi/ac_connected added (actually in 0.09). 507 | 508 | 0.09 2005-12-12 509 | ---------------- 510 | - Dual-battery support: moved all battery-related sysfs files to 511 | /sys/devices/platform/smapi/BAT0/* 512 | and made the 2nd battery accessible via 513 | /sys/devices/platform/smapi/BAT1/* 514 | - Added numerous read-only battery status files: 515 | /sys/devices/platform/smapi/BAT?/{installed,cycle_count,current1,current2, 516 | last_full_capacity,remaining_capacity,design_capacity,voltage, 517 | design_voltage,manufacturer,model,serial,barcoding,chemistry} 518 | These are incompatible with HDAPS - see README. 519 | - /sys/devices/platform/smapi/BAT?/dump gives the raw status dump. 520 | - Added "debug" module parameter, default (debug=0) reduces printk verbosity. 521 | For bug reports, please use "modprobe debug=1" (or just use "make load"). 522 | - Now requires kernel >= 2.6.13 (stick with v0.08 for earlier kernels). 523 | - Suspend+resume now correctly handles default thresholds (start==stop==0). 524 | - Extended whitelist to cover all ThinkPads. 525 | - Cleanup of init/probing code. 526 | - Reduced kernel logging level 527 | - Fix in set_inhibit_charge 528 | 529 | 0.08 2005-12-09 530 | ---------------- 531 | - Fixes in README and dmesg outputs. 532 | 533 | 0.07 2005-12-07 534 | ---------------- 535 | - Added /sys/devices/platform/smapi/cdrom_speed to set/get the CD drive speed 536 | level (0=slow, 1=medium, 2=fast). 537 | WARNING: 538 | Writing to this file when the CD is being accessed will hang your computer. 539 | - Fixed some dmesg outputs 540 | 541 | 0.06 2005-12-06 542 | ---------------- 543 | - /sys/devices/platform/smapi/inhibit_charge renamed to 544 | /sys/devices/platform/smapi/inhibit_charge_minutes and now accepts 545 | the number of minutes to inhibit charging. 546 | - Compatibility with kernel 2.6.12 (thanks to Guenther Starnberger) 547 | 548 | 0.05 2005-12-05 549 | ---------------- 550 | - Kernel 2.6.15 compatibility (thanks to Volker Gropp) 551 | - Improved error reporting 552 | - Cleared confusing dmesg output 553 | 554 | 0.04 2005-12-05 555 | ---------------- 556 | - Made start_charge_thresh work even with stop_start_thresh is 557 | not available. 558 | 559 | 0.03 2005-12-05 560 | ---------------- 561 | - Added /sys/devices/platform/smapi/inhibit_charge 562 | - Fixed #includes 563 | - Kernel 2.6.15 compatibility 564 | - Added versionless IBM machines to DMI whitelist 565 | - Added retries to SMAPI request code 566 | - Added list of supported models to README 567 | 568 | 0.02 2005-12-05 569 | ---------------- 570 | - improved SMAPI request code and error reporting 571 | 572 | 0.01 2005-12-04 573 | ---------------- 574 | - initial release 575 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef TP_MODULES 2 | # This part runs as a normal, top-level Makefile: 3 | X:=$(shell false) 4 | KVER ?= $(shell uname -r) 5 | KBASE ?= /lib/modules/$(KVER) 6 | KSRC ?= $(KBASE)/source 7 | KBUILD ?= $(KBASE)/build 8 | MOD_DIR ?= $(KBASE)/kernel 9 | PWD := $(shell pwd) 10 | IDIR := include/linux 11 | TP_DIR := drivers/platform/x86 12 | TP_MODULES := thinkpad_ec.o tp_smapi.o 13 | SHELL := /bin/bash 14 | 15 | ifeq ($(HDAPS),1) 16 | TP_MODULES += hdaps.o 17 | LOAD_HDAPS := insmod ./hdaps.ko 18 | else 19 | LOAD_HDAPS := : 20 | endif 21 | 22 | ifeq ($(FORCE_IO),1) 23 | THINKPAD_EC_PARAM := force_io=1 24 | else 25 | THINKPAD_EC_PARAM := 26 | endif 27 | 28 | ifneq ($(KERNELRELEASE),) 29 | obj-m := $(TP_MODULES) 30 | else 31 | endif 32 | 33 | DEBUG := 0 34 | 35 | .PHONY: default clean modules load unload install check_hdaps \ 36 | check-ver set-version create-tgz create-rpm 37 | export TP_MODULES 38 | 39 | ##################################################################### 40 | # Main targets 41 | 42 | default: modules 43 | 44 | # Build the modules thinkpad_ec.ko, tp_smapi.ko and (if HDAPS=1) hdaps.ko 45 | modules: $(KBUILD) $(patsubst %.o,%.c,$(TP_MODULES)) 46 | $(MAKE) -C $(KBUILD) M=$(PWD) O=$(KBUILD) modules 47 | 48 | clean: 49 | rm -f tp_smapi.mod.* tp_smapi.o tp_smapi.ko .tp_smapi.*.cmd 50 | rm -f thinkpad_ec.mod.* thinkpad_ec.o thinkpad_ec.ko .thinkpad_ec.*.cmd 51 | rm -f hdaps.mod.* hdaps.o hdaps.ko .hdaps.*.cmd 52 | rm -f *~ *.orig *.rej 53 | rm -fr .tmp_versions Modules.symvers 54 | 55 | load: check_hdaps unload modules 56 | @( [ `id -u` == 0 ] || { echo "Must be root to load modules"; exit 1; } ) 57 | { insmod ./thinkpad_ec.ko $(THINKPAD_EC_PARAM) && insmod ./tp_smapi.ko debug=$(DEBUG) && $(LOAD_HDAPS); }; : 58 | @echo -e '\nRecent dmesg output:' ; dmesg | tail -10 59 | 60 | unload: 61 | @( [ `id -u` == 0 ] || { echo "Must be root to unload modules"; exit 1; } ) 62 | if lsmod | grep -q '^hdaps '; then rmmod hdaps; fi 63 | if lsmod | grep -q '^tp_smapi '; then rmmod tp_smapi; fi 64 | if lsmod | grep -q '^thinkpad_ec '; then rmmod thinkpad_ec; fi 65 | if lsmod | grep -q '^tp_base '; then rmmod tp_base; fi # old thinkpad_ec 66 | 67 | check_hdaps: 68 | ifneq ($(HDAPS),1) 69 | @if lsmod | grep -q '^hdaps '; then \ 70 | echo 'The hdaps driver is loaded. Use "make HDAPS=1 ..." to'\ 71 | 'patch hdaps for compatibility with tp_smapi.'\ 72 | 'This requires a kernel source tree.'; exit 1; fi 73 | endif 74 | 75 | install: modules 76 | @( [ `id -u` == 0 ] || { echo "Must be root to install modules"; exit 1; } ) 77 | rm -f $(MOD_DIR)/$(TP_DIR)/{thinkpad_ec,tp_smapi,tp_base}.ko 78 | rm -f $(MOD_DIR)/drivers/firmware/{thinkpad_ec,tp_smapi,tp_base}.ko 79 | rm -f $(MOD_DIR)/extra/{thinkpad_ec,tp_smapi,tp_base}.ko 80 | ifeq ($(HDAPS),1) 81 | rm -f $(MOD_DIR)/drivers/platform/x86/hdaps.ko 82 | rm -f $(MOD_DIR)/extra/hdaps.ko 83 | endif 84 | $(MAKE) -C $(KBUILD) M=$(PWD) O=$(KBUILD) modules_install 85 | depmod $(KVER) 86 | 87 | 88 | ##################################################################### 89 | # Tools for preparing a release. Ignore these. 90 | 91 | TGZ=../tp_smapi-$(VER).tgz 92 | 93 | check-ver: 94 | @if [ -z "$(VER)" ]; then \ 95 | echo "VER is unset"; \ 96 | echo "run: $(MAKE) $(MAKECMDGOALS) VER="; \ 97 | exit 1 ;\ 98 | fi 99 | 100 | set-version: check-ver 101 | perl -i -pe 's/^(tp_smapi version ).*/$${1}$(VER)/' README 102 | perl -i -pe 's/^(#define TP_VERSION ").*/$${1}$(VER)"/' thinkpad_ec.c tp_smapi.c 103 | perl -i -pe 's/^(TP_VER := ).*/$${1}$(VER)/' Makefile 104 | perl -i -pe 's/^(PACKAGE_VERSION=").*/$${1}$(VER)"/' dkms.conf 105 | perl -i -pe 's/^(Version:\s+).*/$${1}$(VER)/' tp_smapi.spec 106 | 107 | create-tgz: check-ver 108 | git archive --format=tar --prefix=tp_smapi-$(VER)/ HEAD | gzip -c > $(TGZ) 109 | tar tzvf $(TGZ) 110 | echo "Ready: $(TGZ)" 111 | 112 | create-rpm: create-tgz 113 | mkdir -p rpmbuild 114 | rpmbuild -tb --define "_topdir $$PWD/rpmbuild" $(TGZ) 115 | 116 | 117 | else 118 | ##################################################################### 119 | # This part runs as a submake in kernel Makefile context: 120 | 121 | EXTRA_CFLAGS := $(CFLAGS) -I$(M)/include 122 | obj-m := $(TP_MODULES) 123 | 124 | endif 125 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | tp_smapi version 0.44 2 | IBM ThinkPad hardware functions driver 3 | 4 | Author: Shem Multinymous 5 | Project: http://sourceforge.net/projects/tpctl 6 | Wiki: http://thinkwiki.org/wiki/tp_smapi 7 | List: linux-thinkpad@linux-thinkpad.org 8 | (http://mailman.linux-thinkpad.org/mailman/listinfo/linux-thinkpad) 9 | 10 | Description 11 | ----------- 12 | 13 | ThinkPad laptops include a proprietary interface called SMAPI BIOS 14 | (System Management Application Program Interface) which provides some 15 | hardware control functionality that is not accessible by other means. 16 | 17 | This driver exposes some features of the SMAPI BIOS through a sysfs 18 | interface. It is suitable for newer models, on which SMAPI is invoked 19 | through IO port writes. Older models use a different SMAPI interface; 20 | for those, try the "thinkpad" module from the "tpctl" package. 21 | 22 | WARNING: 23 | This driver uses undocumented features and direct hardware access. 24 | It thus cannot be guaranteed to work, and may cause arbitrary damage 25 | (especially on models it wasn't tested on). 26 | 27 | 28 | Installation 29 | ------------ 30 | 31 | For testing, you can simply compile and load the driver within the current 32 | working directory: 33 | # make load 34 | 35 | To compile and install into the kernel's module path: 36 | # make install 37 | 38 | If you use the HDAPS driver, use these instead to replace the hdaps module 39 | with one patched for compatibility with tp_smapi: 40 | # make load HDAPS=1 41 | or 42 | # make install HDAPS=1 43 | 44 | With some recent ThinkPad models, the "thinkpad_ec" will refuse to load due to 45 | reserved ports. You can force loading by passing the "force_io=1" parameter 46 | to the "thinkpad_ec" module using /etc/modules.conf (or your distribution's 47 | equivalent). To test this prior to installation, use: 48 | # make load HDAPS=1 FORCE_IO=1 49 | 50 | To prepare a stand-alone patch against the current kernel tree (including 51 | the hdaps replacement and Kconfig entries), assuming you have its sourcecode 52 | under /lib/modules/`uname -r`/source: 53 | # make patch 54 | 55 | To work against a kernel (other than the current one) that's properly 56 | installed under /lib/modules/*, add an appropriate KVER= parameter 57 | # make patch KVER=2.6.16 58 | To work against another kernel, you'll need to set KSRC and KBUILD too: 59 | # make patch KVER=2.6.16 KSRC=$HOME/2.6.16 KBUILD=$HOME/2.6.16 60 | 61 | To delete all autogenerated files: 62 | # make clean 63 | 64 | Append "DEBUG=1" to "make load" to load tp_smapi with debug=1. 65 | 66 | The original kernel tree is never modified by any these commands. 67 | The /lib/modules directory is modified only by "make install". 68 | 69 | 70 | Module parameters 71 | ----------------- 72 | 73 | thinkpad_ec module: 74 | force_io=1 lets thinkpad_ec load on some recent ThinkPad models 75 | (e.g., T400 and T500) whose BIOS's ACPI DSDT reserves the ports we need. 76 | tp_smapi module: 77 | debug=1 enables verbose dmesg output. 78 | 79 | 80 | Usage 81 | ----- 82 | 83 | Control of battery charging thresholds (in percents of current full charge 84 | capacity): 85 | 86 | # echo 40 > /sys/devices/platform/smapi/BAT0/start_charge_thresh 87 | # echo 70 > /sys/devices/platform/smapi/BAT0/stop_charge_thresh 88 | # cat /sys/devices/platform/smapi/BAT0/*_charge_thresh 89 | 90 | (This is useful since Li-Ion batteries wear out much faster at very 91 | high or low charge levels. The driver will also keeps the thresholds 92 | across suspend-to-disk with AC disconnected; this isn't done 93 | automatically by the hardware.) 94 | 95 | Inhibiting battery charging for 17 minutes (overrides thresholds): 96 | 97 | # echo 17 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes 98 | # echo 0 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes # stop 99 | # cat /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes 100 | 101 | (This can be used to control which battery is charged when using an 102 | Ultrabay battery.) 103 | 104 | Forcing battery discharging even if AC power available: 105 | 106 | # echo 1 > /sys/devices/platform/smapi/BAT0/force_discharge # start discharge 107 | # echo 0 > /sys/devices/platform/smapi/BAT0/force_discharge # stop discharge 108 | # cat /sys/devices/platform/smapi/BAT0/force_discharge 109 | 110 | (When AC is connected, forced discharging will automatically stop 111 | when battery is fully depleted -- this is useful for calibration. 112 | Also, this attribute can be used to control which battery is discharged 113 | when both a system battery and an Ultrabay battery are connected.) 114 | 115 | Misc read-only battery status attributes (see note about HDAPS below): 116 | 117 | /sys/devices/platform/smapi/BAT0/installed # 0 or 1 118 | /sys/devices/platform/smapi/BAT0/state # idle/charging/discharging 119 | /sys/devices/platform/smapi/BAT0/cycle_count # integer counter 120 | /sys/devices/platform/smapi/BAT0/current_now # instantaneous current 121 | /sys/devices/platform/smapi/BAT0/current_avg # last minute average 122 | /sys/devices/platform/smapi/BAT0/power_now # instantaneous power 123 | /sys/devices/platform/smapi/BAT0/power_avg # last minute average 124 | /sys/devices/platform/smapi/BAT0/last_full_capacity # in mWh 125 | /sys/devices/platform/smapi/BAT0/remaining_percent # remaining percent of energy (set by calibration) 126 | /sys/devices/platform/smapi/BAT0/remaining_percent_error # error range of remaing_percent (not reset by calibration) 127 | /sys/devices/platform/smapi/BAT0/remaining_running_time # in minutes, by last minute average power 128 | /sys/devices/platform/smapi/BAT0/remaining_running_time_now # in minutes, by instantenous power 129 | /sys/devices/platform/smapi/BAT0/remaining_charging_time # in minutes 130 | /sys/devices/platform/smapi/BAT0/remaining_capacity # in mWh 131 | /sys/devices/platform/smapi/BAT0/design_capacity # in mWh 132 | /sys/devices/platform/smapi/BAT0/voltage # in mV 133 | /sys/devices/platform/smapi/BAT0/design_voltage # in mV 134 | /sys/devices/platform/smapi/BAT0/charging_max_current # max charging current 135 | /sys/devices/platform/smapi/BAT0/charging_max_voltage # max charging voltage 136 | /sys/devices/platform/smapi/BAT0/group{0,1,2,3}_voltage # see below 137 | /sys/devices/platform/smapi/BAT0/manufacturer # string 138 | /sys/devices/platform/smapi/BAT0/model # string 139 | /sys/devices/platform/smapi/BAT0/barcoding # string 140 | /sys/devices/platform/smapi/BAT0/chemistry # string 141 | /sys/devices/platform/smapi/BAT0/serial # integer 142 | /sys/devices/platform/smapi/BAT0/manufacture_date # YYYY-MM-DD 143 | /sys/devices/platform/smapi/BAT0/first_use_date # YYYY-MM-DD 144 | /sys/devices/platform/smapi/BAT0/temperature # in milli-Celsius 145 | /sys/devices/platform/smapi/BAT0/dump # see below 146 | /sys/devices/platform/smapi/ac_connected # 0 or 1 147 | 148 | The BAT0/group{0,1,2,3}_voltage attribute refers to the separate cell groups 149 | in each battery. For example, on the ThinkPad 600, X3x, T4x and R5x models, 150 | the battery contains 3 cell groups in series, where each group consisting of 2 151 | or 3 cells connected in parallel. The voltage of each group is given by these 152 | attributes, and their sum (roughly) equals the "voltage" attribute. 153 | (The effective performance of the battery is determined by the weakest group, 154 | i.e., the one those voltage changes most rapidly during dis/charging.) 155 | 156 | The "BAT0/dump" attribute gives a a hex dump of the raw status data, which 157 | contains additional data now in the above (if you can figure it out). Some 158 | unused values are autodetected and replaced by "--": 159 | 160 | In all of the above, replace BAT0 with BAT1 to address the 2nd battery (e.g. 161 | in the UltraBay). 162 | 163 | 164 | Raw SMAPI calls: 165 | 166 | /sys/devices/platform/smapi/smapi_request 167 | This performs raw SMAPI calls. It uses a bad interface that cannot handle 168 | multiple simultaneous access. Don't touch it, it's for development only. 169 | If you did touch it, you would so something like 170 | # echo '211a 100 0 0' > /sys/devices/platform/smapi/smapi_request 171 | # cat /sys/devices/platform/smapi/smapi_request 172 | and notice that in the output "211a 34b b2 0 0 0 'OK'", the "4b" in the 2nd 173 | value, converted to decimal is 75: the current charge stop threshold. 174 | 175 | 176 | Model-specific status 177 | --------------------- 178 | 179 | Works (at least partially) on the following ThinkPad model: 180 | * A30 181 | * G41 182 | * R40, R50p, R51, R52 183 | * T23, T40, T40p, T41, T41p, T42, T42p, T43, T43p, T60, T61, T400, T410, T420 (partially) 184 | * X24, X31, X32, X40, X41, X60, X61, X200, X201, X220 (partially) 185 | * Z60t, Z61m 186 | 187 | Does not work on: 188 | * X230 and newer 189 | * T430 and newer 190 | * Any ThinkPad Edge 191 | * Any ThinkPad Yoga 192 | * Any ThinkPad L series 193 | * Any ThinkPad P series 194 | 195 | Not all functions are available on all models; for detailed status, see: 196 | http://thinkwiki.org/wiki/tp_smapi 197 | 198 | Please report success/failure by e-mail or on the Wiki. 199 | If you get a "not implemented" or "not supported" message, your laptop 200 | probably just can't do that (at least not via the SMAPI BIOS). 201 | For negative reports, follow the bug reporting guidelines below. 202 | If you send me the necessary technical data (i.e., SMAPI function 203 | interfaces), I will support additional models. 204 | 205 | 206 | Conflict with HDAPS 207 | ------------------- 208 | 209 | The extended battery status function conflicts with the "hdaps" kernel module 210 | (they use the same IO ports). 211 | 212 | You can use HDAPS=1 (see Installation) to get a patched version of hdaps which 213 | is compatible with tp_smapi. 214 | 215 | Otherwise: 216 | 217 | If you load "hdaps" first, tp_smapi will disable these functions (and log a 218 | message in the kernel log). If you load "tp_smapi" first, "hdaps" will refuse 219 | to load. To switch between the two, "rmmod" both and then load one you need. 220 | 221 | Some of the battery status is also visible through ACPI (/proc/acpi/battery/). 222 | 223 | The charging control files (*_charge_thresh, inhibit_charge_minutes and 224 | force_discrage*) don't have this problem. 225 | 226 | 227 | Additional HDAPS features 228 | ------------------------- 229 | 230 | The modified hdaps driver has several improvements on the one in mainline 231 | (beyond resolving the conflict with thinkpad_ec and tp_smapi): 232 | 233 | - Fixes reliability and improves support for recent ThinkPad models 234 | (especially *60 and newer). Unlike the mainline driver, the modified hdaps 235 | correctly follows the Embedded Controller communication protocol. 236 | 237 | - Extends the "invert" parameter to cover all possible axis orientations. 238 | The possible values are as follows. 239 | Let X,Y denote the hardware readouts. 240 | Let R denote the laptop's roll (tilt left/right). 241 | Let P denote the laptop's pitch (tilt forward/backward). 242 | invert=0: R= X P= Y (same as mainline) 243 | invert=1: R=-X P=-Y (same as mainline) 244 | invert=2: R=-X P= Y (new) 245 | invert=3: R= X P=-Y (new) 246 | invert=4: R= Y P= X (new) 247 | invert=5: R=-Y P=-X (new) 248 | invert=6: R=-Y P= X (new) 249 | invert=7: R= Y P=-X (new) 250 | It's probably easiest to just try all 8 possibilities and see which yields 251 | correct results (e.g., in the hdaps-gl visualisation). 252 | 253 | - Adds a whitelist which automatically sets the correct axis orientation for 254 | some models. If the value for your model is wrong or missing, you can override 255 | it using the "invert" parameter. Please also update the tables at 256 | http://www.thinkwiki.org/wiki/tp_smapi and 257 | http://www.thinkwiki.org/wiki/List_of_DMI_IDs 258 | and submit a patch for the whitelist in hdaps.c. 259 | 260 | - Provides new attributes: 261 | /sys/devices/platform/hdaps/sampling_rate: 262 | This determines the frequency at which the host queries the embedded 263 | controller for accelerometer data (and informs the hdaps input devices). 264 | Default=50. 265 | /sys/devices/platform/hdaps/oversampling_ratio: 266 | When set to X, the embedded controller is told to do physical accelerometer 267 | measurements at a rate that is X times higher than the rate at which 268 | the driver reads those measurements (i.e., X*sampling_rate). This 269 | makes the readouts from the embedded controller more fresh, and is also 270 | useful for the running average filter (see next). Default=5 271 | /sys/devices/platform/hdaps/running_avg_filter_order: 272 | When set to X, reported readouts will be the average of the last X physical 273 | accelerometer measurements. Current firmware allows 1<=X<=8. Setting to a 274 | high value decreases readout fluctuations. The averaging is handled by the 275 | embedded controller, so no CPU resources are used. Higher values make the 276 | readouts smoother, since it averages out both sensor noise (good) and abrupt 277 | changes (bad). Default=2. 278 | 279 | - Provides a second input device, which publishes the raw accelerometer 280 | measurements (without the fuzzing needed for joystick emulation). This input 281 | device can be matched by a udev rule such as the following (all on one line): 282 | KERNEL=="event[0-9]*", ATTRS{phys}=="hdaps/input1", 283 | ATTRS{modalias}=="input:b0019v1014p5054e4801-*", 284 | SYMLINK+="input/hdaps/accelerometer-event 285 | 286 | A new version of the hdapsd userspace daemon, which uses the input device 287 | interface instead of polling sysfs, is available seprately. Using this reduces 288 | the total interrupts per second generated by hdaps+hdapsd (on tickless kernels) 289 | to 50, down from a value that fluctuates between 50 and 100. Set the 290 | sampling_rate sysfs attribute to a lower value to further reduce interrupts, 291 | at the expense of response latency. 292 | 293 | Licensing note: all my changes to the HDAPS driver are licensed under the 294 | GPL version 2 or, at your option and to the extent allowed by derivation from 295 | prior works, any later version. My version of hdaps is derived work from the 296 | mainline version, which at the time of writing is available only under 297 | GPL version 2. 298 | 299 | Bug reporting 300 | ------------- 301 | 302 | Mail . Please include: 303 | * Details about your model, 304 | * Relevant "dmesg" output. Make sure thinkpad_ec and tp_smapi are loaded with 305 | the "debug=1" parameter (e.g., use "make load HDAPS=1 DEBUG=1"). 306 | * Output of "dmidecode | grep -C5 Product" 307 | * Does the failed functionality works under Windows? 308 | 309 | 310 | Files in this package 311 | --------------------- 312 | 313 | README 314 | This file. 315 | CHANGES 316 | Change log. 317 | TODO 318 | Pending improvements. 319 | Makefile 320 | Makefile (see "Installation" above). 321 | tp_smapi.c 322 | tp_smapi driver module (main code). 323 | thinkpad_ec.* 324 | thinkpad_ec driver module (coordinates hardware access between tp_smapi and 325 | hdaps) 326 | hdaps.c 327 | Modified version of hdaps.c driver from mainline kernel, patched to use 328 | thinkpad_ec and several other improvements. 329 | 330 | 331 | More about SMAPI 332 | ---------------- 333 | 334 | For hints about what may be possible via the SMAPI BIOS and how, see: 335 | 336 | * IBM Technical Reference Manual for the ThinkPad 770 337 | (http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD) 338 | * Exported symbols in PWRMGRIF.DLL or TPPWRW32.DLL (e.g., use "objdump -x"). 339 | * drivers/char/mwave/smapi.c in the Linux kernel tree.* 340 | * The "thinkpad" SMAPI module (http://tpctl.sourceforge.net). 341 | * The SMAPI_* constants in tp_smapi.c. 342 | 343 | Note that in the above Technical Reference and in the "thinkpad" module, 344 | SMAPI is invoked through a function call to some physical address. However, 345 | the interface used by tp_smapi and the above mwave drive, and apparently 346 | required by newer ThinkPad, is different: you set the parameters up in the 347 | CPU's registers and write to ports 0xB2 (the APM control port) and 0x4F; this 348 | triggers an SMI (System Management Interrupt), causing the CPU to enter 349 | SMM (System Management Mode) and run the BIOS firmware; the results are 350 | returned in the CPU's registers. It is not clear what is the relation between 351 | the two variants of SMAPI, though the assignment of error codes seems to be 352 | similar. 353 | 354 | In addition, the embedded controller on ThinkPad laptops has a non-standard 355 | interface at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip). 356 | The interface provides various system management services (currently known: 357 | battery information and accelerometer readouts). For more information see the 358 | thinkpad_ec module and the H8S hardware documentation: 359 | http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf 360 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Ideas for improvement 2 | --------------------- 3 | (The best way to get these done is to send a patch.) 4 | 5 | Don't create /sys files for unsupported functions, and don't access those 6 | functions on suspend+resume (requires probing on module load or a huge 7 | white/blacklist). 8 | 9 | Make inhibit_charge_minutes return the time left, not the time originally 10 | set (as returned by the SMAPI BIOS). Requires remembering when 11 | inhibit_charge_minutes was set and comparing to current time. 12 | 13 | Save and and restore inhibit_charge_minutes across suspend-to-disk, as done 14 | for charge thresholds (requires the above time calculations too). 15 | 16 | Use the new Linux battery model introduced in kernel 2.6.23 (see the kernel's 17 | Documentation/power/power_supply_class.txt). 18 | -------------------------------------------------------------------------------- /dkms.conf: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME="tp_smapi" 2 | PACKAGE_VERSION="0.44" 3 | MAKE[0]="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build HDAPS=1" 4 | CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" 5 | BUILT_MODULE_NAME[0]="thinkpad_ec" 6 | BUILT_MODULE_NAME[1]="tp_smapi" 7 | BUILT_MODULE_NAME[2]="hdaps" 8 | DEST_MODULE_LOCATION[0]="/extra" 9 | DEST_MODULE_LOCATION[1]="/extra" 10 | DEST_MODULE_LOCATION[2]="/updates" 11 | AUTOINSTALL="yes" 12 | -------------------------------------------------------------------------------- /hdaps.c: -------------------------------------------------------------------------------- 1 | /* 2 | * drivers/platform/x86/hdaps.c - driver for IBM's Hard Drive Active Protection System 3 | * 4 | * Copyright (C) 2005 Robert Love 5 | * Copyright (C) 2005 Jesper Juhl 6 | * 7 | * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads 8 | * starting with the R40, T41, and X40. It provides a basic two-axis 9 | * accelerometer and other data, such as the device's temperature. 10 | * 11 | * This driver is based on the document by Mark A. Smith available at 12 | * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial 13 | * and error. 14 | * 15 | * This program is free software; you can redistribute it and/or modify it 16 | * under the terms of the GNU General Public License v2 as published by the 17 | * Free Software Foundation. 18 | * 19 | * This program is distributed in the hope that it will be useful, but WITHOUT 20 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 21 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 22 | * more details. 23 | * 24 | * You should have received a copy of the GNU General Public License along with 25 | * this program; if not, write to the Free Software Foundation, Inc., 26 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "thinkpad_ec.h" 38 | #include 39 | #include 40 | 41 | /* Embedded controller accelerometer read command and its result: */ 42 | static const struct thinkpad_ec_row ec_accel_args = 43 | { .mask = 0x0001, .val = {0x11} }; 44 | #define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */ 45 | /* First readout, if READOUTS>=1: */ 46 | #define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */ 47 | #define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */ 48 | #define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */ 49 | /* Second readout, if READOUTS>=2: */ 50 | #define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */ 51 | #define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis position word */ 52 | #define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */ 53 | #define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */ 54 | #define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */ 55 | #define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */ 56 | 57 | #define KEYBD_MASK 0x20 /* set if keyboard activity */ 58 | #define MOUSE_MASK 0x40 /* set if mouse activity */ 59 | 60 | #define READ_TIMEOUT_MSECS 100 /* wait this long for device read */ 61 | #define RETRY_MSECS 3 /* retry delay */ 62 | 63 | #define HDAPS_INPUT_FUZZ 4 /* input event threshold */ 64 | #define HDAPS_INPUT_FLAT 4 65 | #define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistence */ 66 | 67 | /* Input IDs */ 68 | #define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM 69 | #define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */ 70 | #define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */ 71 | #define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */ 72 | 73 | /* Axis orientation. */ 74 | /* The unnatural bit-representation of inversions is for backward 75 | * compatibility with the"invert=1" module parameter. */ 76 | #define HDAPS_ORIENT_INVERT_XY 0x01 /* Invert both X and Y axes. */ 77 | #define HDAPS_ORIENT_INVERT_X 0x02 /* Invert the X axis (uninvert if 78 | * already inverted by INVERT_XY). */ 79 | #define HDAPS_ORIENT_SWAP 0x04 /* Swap the axes. The swap occurs 80 | * before inverting X or Y. */ 81 | #define HDAPS_ORIENT_MAX 0x07 82 | #define HDAPS_ORIENT_UNDEFINED 0xFF /* Placeholder during initialization */ 83 | #define HDAPS_ORIENT_INVERT_Y (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X) 84 | 85 | static struct timer_list hdaps_timer; 86 | static struct platform_device *pdev; 87 | static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */ 88 | static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */ 89 | static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED; 90 | static int needs_calibration; 91 | 92 | /* Configuration: */ 93 | static int sampling_rate = 50; /* Sampling rate */ 94 | static int oversampling_ratio = 5; /* Ratio between our sampling rate and 95 | * EC accelerometer sampling rate */ 96 | static int running_avg_filter_order = 2; /* EC running average filter order */ 97 | 98 | /* Latest state readout: */ 99 | static int pos_x, pos_y; /* position */ 100 | static int temperature; /* temperature */ 101 | static int stale_readout = 1; /* last read invalid */ 102 | static int rest_x, rest_y; /* calibrated rest position */ 103 | 104 | /* Last time we saw keyboard and mouse activity: */ 105 | static u64 last_keyboard_jiffies = INITIAL_JIFFIES; 106 | static u64 last_mouse_jiffies = INITIAL_JIFFIES; 107 | static u64 last_update_jiffies = INITIAL_JIFFIES; 108 | 109 | /* input device use count */ 110 | static int hdaps_users; 111 | static DEFINE_MUTEX(hdaps_users_mtx); 112 | 113 | /* Some models require an axis transformation to the standard representation */ 114 | static void transform_axes(int *x, int *y) 115 | { 116 | if (hdaps_invert & HDAPS_ORIENT_SWAP) { 117 | int z; 118 | z = *x; 119 | *x = *y; 120 | *y = z; 121 | } 122 | if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) { 123 | *x = -*x; 124 | *y = -*y; 125 | } 126 | if (hdaps_invert & HDAPS_ORIENT_INVERT_X) 127 | *x = -*x; 128 | } 129 | 130 | /** 131 | * __hdaps_update - query current state, with locks already acquired 132 | * @fast: if nonzero, do one quick attempt without retries. 133 | * 134 | * Query current accelerometer state and update global state variables. 135 | * Also prefetches the next query. Caller must hold controller lock. 136 | */ 137 | static int __hdaps_update(int fast) 138 | { 139 | /* Read data: */ 140 | struct thinkpad_ec_row data; 141 | int ret; 142 | 143 | data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) | 144 | (3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) | 145 | (1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL); 146 | if (fast) 147 | ret = thinkpad_ec_try_read_row(&ec_accel_args, &data); 148 | else 149 | ret = thinkpad_ec_read_row(&ec_accel_args, &data); 150 | thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */ 151 | if (ret) 152 | return ret; 153 | 154 | /* Check status: */ 155 | if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) { 156 | printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n", 157 | data.val[EC_ACCEL_IDX_RETVAL]); 158 | return -EIO; 159 | } 160 | 161 | if (data.val[EC_ACCEL_IDX_READOUTS] < 1) 162 | return -EBUSY; /* no pending readout, try again later */ 163 | 164 | /* Parse position data: */ 165 | pos_x = *(s16 *)(data.val+EC_ACCEL_IDX_XPOS1); 166 | pos_y = *(s16 *)(data.val+EC_ACCEL_IDX_YPOS1); 167 | transform_axes(&pos_x, &pos_y); 168 | 169 | /* Keyboard and mouse activity status is cleared as soon as it's read, 170 | * so applications will eat each other's events. Thus we remember any 171 | * event for KMACT_REMEMBER_PERIOD jiffies. 172 | */ 173 | if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK) 174 | last_keyboard_jiffies = get_jiffies_64(); 175 | if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK) 176 | last_mouse_jiffies = get_jiffies_64(); 177 | 178 | temperature = data.val[EC_ACCEL_IDX_TEMP1]; 179 | 180 | last_update_jiffies = get_jiffies_64(); 181 | stale_readout = 0; 182 | if (needs_calibration) { 183 | rest_x = pos_x; 184 | rest_y = pos_y; 185 | needs_calibration = 0; 186 | } 187 | 188 | return 0; 189 | } 190 | 191 | /** 192 | * hdaps_update - acquire locks and query current state 193 | * 194 | * Query current accelerometer state and update global state variables. 195 | * Also prefetches the next query. 196 | * Retries until timeout if the accelerometer is not in ready status (common). 197 | * Does its own locking. 198 | */ 199 | static int hdaps_update(void) 200 | { 201 | u64 age = get_jiffies_64() - last_update_jiffies; 202 | int total, ret; 203 | 204 | if (!stale_readout && age < (9*HZ)/(10*sampling_rate)) 205 | return 0; /* already updated recently */ 206 | for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) { 207 | ret = thinkpad_ec_lock(); 208 | if (ret) 209 | return ret; 210 | ret = __hdaps_update(0); 211 | thinkpad_ec_unlock(); 212 | 213 | if (!ret) 214 | return 0; 215 | if (ret != -EBUSY) 216 | break; 217 | msleep(RETRY_MSECS); 218 | } 219 | return ret; 220 | } 221 | 222 | /** 223 | * hdaps_set_power - enable or disable power to the accelerometer. 224 | * Returns zero on success and negative error code on failure. Can sleep. 225 | */ 226 | static int hdaps_set_power(int on) 227 | { 228 | struct thinkpad_ec_row args = 229 | { .mask = 0x0003, .val = {0x14, on?0x01:0x00} }; 230 | struct thinkpad_ec_row data = { .mask = 0x8000 }; 231 | int ret = thinkpad_ec_read_row(&args, &data); 232 | if (ret) 233 | return ret; 234 | if (data.val[0xF] != 0x00) 235 | return -EIO; 236 | return 0; 237 | } 238 | 239 | /** 240 | * hdaps_set_ec_config - set accelerometer parameters. 241 | * @ec_rate: embedded controller sampling rate 242 | * @order: embedded controller running average filter order 243 | * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.) 244 | * Returns zero on success and negative error code on failure. Can sleep. 245 | */ 246 | static int hdaps_set_ec_config(int ec_rate, int order) 247 | { 248 | struct thinkpad_ec_row args = { .mask = 0x000F, 249 | .val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} }; 250 | struct thinkpad_ec_row data = { .mask = 0x8000 }; 251 | int ret = thinkpad_ec_read_row(&args, &data); 252 | printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n", 253 | ec_rate, order); 254 | if (ret) 255 | return ret; 256 | if (data.val[0xF] == 0x03) { 257 | printk(KERN_WARNING "hdaps: config param out of range\n"); 258 | return -EINVAL; 259 | } 260 | if (data.val[0xF] == 0x06) { 261 | printk(KERN_WARNING "hdaps: config change already pending\n"); 262 | return -EBUSY; 263 | } 264 | if (data.val[0xF] != 0x00) { 265 | printk(KERN_WARNING "hdaps: config change error, ret=%d\n", 266 | data.val[0xF]); 267 | return -EIO; 268 | } 269 | return 0; 270 | } 271 | 272 | /** 273 | * hdaps_get_ec_config - get accelerometer parameters. 274 | * @ec_rate: embedded controller sampling rate 275 | * @order: embedded controller running average filter order 276 | * Returns zero on success and negative error code on failure. Can sleep. 277 | */ 278 | static int hdaps_get_ec_config(int *ec_rate, int *order) 279 | { 280 | const struct thinkpad_ec_row args = 281 | { .mask = 0x0003, .val = {0x17, 0x82} }; 282 | struct thinkpad_ec_row data = { .mask = 0x801F }; 283 | int ret = thinkpad_ec_read_row(&args, &data); 284 | if (ret) 285 | return ret; 286 | if (data.val[0xF] != 0x00) 287 | return -EIO; 288 | if (!(data.val[0x1] & 0x01)) 289 | return -ENXIO; /* accelerometer polling not enabled */ 290 | if (data.val[0x1] & 0x02) 291 | return -EBUSY; /* config change in progress, retry later */ 292 | *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8); 293 | *order = data.val[0x4]; 294 | return 0; 295 | } 296 | 297 | /** 298 | * hdaps_get_ec_mode - get EC accelerometer mode 299 | * Returns zero on success and negative error code on failure. Can sleep. 300 | */ 301 | static int hdaps_get_ec_mode(u8 *mode) 302 | { 303 | const struct thinkpad_ec_row args = 304 | { .mask = 0x0001, .val = {0x13} }; 305 | struct thinkpad_ec_row data = { .mask = 0x8002 }; 306 | int ret = thinkpad_ec_read_row(&args, &data); 307 | if (ret) 308 | return ret; 309 | if (data.val[0xF] != 0x00) { 310 | printk(KERN_WARNING 311 | "accelerometer not implemented (0x%02x)\n", 312 | data.val[0xF]); 313 | return -EIO; 314 | } 315 | *mode = data.val[0x1]; 316 | return 0; 317 | } 318 | 319 | /** 320 | * hdaps_check_ec - checks something about the EC. 321 | * Follows the clean-room spec for HDAPS; we don't know what it means. 322 | * Returns zero on success and negative error code on failure. Can sleep. 323 | */ 324 | static int hdaps_check_ec(void) 325 | { 326 | const struct thinkpad_ec_row args = 327 | { .mask = 0x0003, .val = {0x17, 0x81} }; 328 | struct thinkpad_ec_row data = { .mask = 0x800E }; 329 | int ret = thinkpad_ec_read_row(&args, &data); 330 | if (ret) 331 | return ret; 332 | if (!((data.val[0x1] == 0x00 && data.val[0x2] == 0x60) || /* cleanroom spec */ 333 | (data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */ 334 | data.val[0x3] != 0x00 || data.val[0xF] != 0x00) { 335 | printk(KERN_WARNING 336 | "hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n", 337 | data.val[0x1], data.val[0x2], 338 | data.val[0x3], data.val[0xF]); 339 | return -EIO; 340 | } 341 | return 0; 342 | } 343 | 344 | /** 345 | * hdaps_device_init - initialize the accelerometer. 346 | * 347 | * Call several embedded controller functions to test and initialize the 348 | * accelerometer. 349 | * Returns zero on success and negative error code on failure. Can sleep. 350 | */ 351 | #define FAILED_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg) 352 | static int hdaps_device_init(void) 353 | { 354 | int ret; 355 | u8 mode; 356 | 357 | ret = thinkpad_ec_lock(); 358 | if (ret) 359 | return ret; 360 | 361 | if (hdaps_get_ec_mode(&mode)) 362 | { FAILED_INIT("hdaps_get_ec_mode failed"); goto bad; } 363 | 364 | printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode); 365 | if (mode == 0x00) 366 | { FAILED_INIT("accelerometer not available"); goto bad; } 367 | 368 | if (hdaps_check_ec()) 369 | { FAILED_INIT("hdaps_check_ec failed"); goto bad; } 370 | 371 | if (hdaps_set_power(1)) 372 | { FAILED_INIT("hdaps_set_power failed"); goto bad; } 373 | 374 | if (hdaps_set_ec_config(sampling_rate*oversampling_ratio, 375 | running_avg_filter_order)) 376 | { FAILED_INIT("hdaps_set_ec_config failed"); goto bad; } 377 | 378 | thinkpad_ec_invalidate(); 379 | udelay(200); 380 | 381 | /* Just prefetch instead of reading, to avoid ~1sec delay on load */ 382 | ret = thinkpad_ec_prefetch_row(&ec_accel_args); 383 | if (ret) 384 | { FAILED_INIT("initial prefetch failed"); goto bad; } 385 | goto good; 386 | bad: 387 | thinkpad_ec_invalidate(); 388 | ret = -ENXIO; 389 | good: 390 | stale_readout = 1; 391 | thinkpad_ec_unlock(); 392 | return ret; 393 | } 394 | 395 | /** 396 | * hdaps_device_shutdown - power off the accelerometer 397 | * Returns nonzero on failure. Can sleep. 398 | */ 399 | static int hdaps_device_shutdown(void) 400 | { 401 | int ret; 402 | ret = hdaps_set_power(0); 403 | if (ret) { 404 | printk(KERN_WARNING "hdaps: cannot power off\n"); 405 | return ret; 406 | } 407 | ret = hdaps_set_ec_config(0, 1); 408 | if (ret) 409 | printk(KERN_WARNING "hdaps: cannot stop EC sampling\n"); 410 | return ret; 411 | } 412 | 413 | /* Device model stuff */ 414 | 415 | static int hdaps_probe(struct platform_device *dev) 416 | { 417 | int ret; 418 | 419 | ret = hdaps_device_init(); 420 | if (ret) 421 | return ret; 422 | 423 | printk(KERN_INFO "hdaps: device successfully initialized.\n"); 424 | return 0; 425 | } 426 | 427 | static int hdaps_suspend(struct platform_device *dev, pm_message_t state) 428 | { 429 | /* Don't do hdaps polls until resume re-initializes the sensor. */ 430 | #if LINUX_VERSION_CODE < KERNEL_VERSION(6,15,0) 431 | del_timer_sync(&hdaps_timer); 432 | #else 433 | timer_delete_sync(&hdaps_timer); 434 | #endif 435 | hdaps_device_shutdown(); /* ignore errors, effect is negligible */ 436 | return 0; 437 | } 438 | 439 | static int hdaps_resume(struct platform_device *dev) 440 | { 441 | int ret = hdaps_device_init(); 442 | if (ret) 443 | return ret; 444 | 445 | mutex_lock(&hdaps_users_mtx); 446 | if (hdaps_users) 447 | mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); 448 | mutex_unlock(&hdaps_users_mtx); 449 | return 0; 450 | } 451 | 452 | static struct platform_driver hdaps_driver = { 453 | .probe = hdaps_probe, 454 | .suspend = hdaps_suspend, 455 | .resume = hdaps_resume, 456 | .driver = { 457 | .name = "hdaps", 458 | .owner = THIS_MODULE, 459 | }, 460 | }; 461 | 462 | /** 463 | * hdaps_calibrate - set our "resting" values. 464 | * Does its own locking. 465 | */ 466 | static void hdaps_calibrate(void) 467 | { 468 | needs_calibration = 1; 469 | hdaps_update(); 470 | /* If that fails, the mousedev poll will take care of things later. */ 471 | } 472 | 473 | /* Timer handler for updating the input device. Runs in softirq context, 474 | * so avoid lenghty or blocking operations. 475 | */ 476 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) 477 | static void hdaps_mousedev_poll(unsigned long unused) 478 | #else 479 | static void hdaps_mousedev_poll(struct timer_list *unused) 480 | #endif 481 | { 482 | int ret; 483 | 484 | stale_readout = 1; 485 | 486 | /* Cannot sleep. Try nonblockingly. If we fail, try again later. */ 487 | if (thinkpad_ec_try_lock()) 488 | goto keep_active; 489 | 490 | ret = __hdaps_update(1); /* fast update, we're in softirq context */ 491 | thinkpad_ec_unlock(); 492 | /* Any of "successful", "not yet ready" and "not prefetched"? */ 493 | if (ret != 0 && ret != -EBUSY && ret != -ENODATA) { 494 | printk(KERN_ERR 495 | "hdaps: poll failed, disabling updates\n"); 496 | return; 497 | } 498 | 499 | keep_active: 500 | /* Even if we failed now, pos_x,y may have been updated earlier: */ 501 | input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x); 502 | input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y); 503 | input_sync(hdaps_idev); 504 | input_report_abs(hdaps_idev_raw, ABS_X, pos_x); 505 | input_report_abs(hdaps_idev_raw, ABS_Y, pos_y); 506 | input_sync(hdaps_idev_raw); 507 | mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); 508 | } 509 | 510 | 511 | /* Sysfs Files */ 512 | 513 | static ssize_t hdaps_position_show(struct device *dev, 514 | struct device_attribute *attr, char *buf) 515 | { 516 | int ret = hdaps_update(); 517 | if (ret) 518 | return ret; 519 | return sprintf(buf, "(%d,%d)\n", pos_x, pos_y); 520 | } 521 | 522 | static ssize_t hdaps_temp1_show(struct device *dev, 523 | struct device_attribute *attr, char *buf) 524 | { 525 | int ret = hdaps_update(); 526 | if (ret) 527 | return ret; 528 | return sprintf(buf, "%d\n", temperature); 529 | } 530 | 531 | static ssize_t hdaps_keyboard_activity_show(struct device *dev, 532 | struct device_attribute *attr, 533 | char *buf) 534 | { 535 | int ret = hdaps_update(); 536 | if (ret) 537 | return ret; 538 | return sprintf(buf, "%u\n", 539 | get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD); 540 | } 541 | 542 | static ssize_t hdaps_mouse_activity_show(struct device *dev, 543 | struct device_attribute *attr, 544 | char *buf) 545 | { 546 | int ret = hdaps_update(); 547 | if (ret) 548 | return ret; 549 | return sprintf(buf, "%u\n", 550 | get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD); 551 | } 552 | 553 | static ssize_t hdaps_calibrate_show(struct device *dev, 554 | struct device_attribute *attr, char *buf) 555 | { 556 | return sprintf(buf, "(%d,%d)\n", rest_x, rest_y); 557 | } 558 | 559 | static ssize_t hdaps_calibrate_store(struct device *dev, 560 | struct device_attribute *attr, 561 | const char *buf, size_t count) 562 | { 563 | hdaps_calibrate(); 564 | return count; 565 | } 566 | 567 | static ssize_t hdaps_invert_show(struct device *dev, 568 | struct device_attribute *attr, char *buf) 569 | { 570 | return sprintf(buf, "%u\n", hdaps_invert); 571 | } 572 | 573 | static ssize_t hdaps_invert_store(struct device *dev, 574 | struct device_attribute *attr, 575 | const char *buf, size_t count) 576 | { 577 | int invert; 578 | 579 | if (sscanf(buf, "%d", &invert) != 1 || 580 | invert < 0 || invert > HDAPS_ORIENT_MAX) 581 | return -EINVAL; 582 | 583 | hdaps_invert = invert; 584 | hdaps_calibrate(); 585 | 586 | return count; 587 | } 588 | 589 | static ssize_t hdaps_sampling_rate_show( 590 | struct device *dev, struct device_attribute *attr, char *buf) 591 | { 592 | return sprintf(buf, "%d\n", sampling_rate); 593 | } 594 | 595 | static ssize_t hdaps_sampling_rate_store( 596 | struct device *dev, struct device_attribute *attr, 597 | const char *buf, size_t count) 598 | { 599 | int rate, ret; 600 | if (sscanf(buf, "%d", &rate) != 1 || rate > HZ || rate <= 0) { 601 | printk(KERN_WARNING 602 | "must have 0driver_data; 730 | hdaps_invert = orient; 731 | printk(KERN_INFO "hdaps: %s detected, setting orientation %u\n", 732 | id->ident, orient); 733 | return 1; /* stop enumeration */ 734 | } 735 | 736 | #define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \ 737 | .ident = vendor " " model, \ 738 | .callback = hdaps_dmi_match_invert, \ 739 | .driver_data = (void *)(orient), \ 740 | .matches = { \ 741 | DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ 742 | DMI_MATCH(DMI_PRODUCT_VERSION, model) \ 743 | } \ 744 | } 745 | 746 | /* List of models with abnormal axis configuration. 747 | Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match 748 | "ThinkPad T42p", and enumeration stops after first match, 749 | so the order of the entries matters. */ 750 | struct dmi_system_id __initconst hdaps_whitelist[] = { 751 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY), 752 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), 753 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY), 754 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY), 755 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y), 756 | HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y), 757 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), 758 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY), 759 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R400", HDAPS_ORIENT_INVERT_XY), 760 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R500", HDAPS_ORIENT_INVERT_XY), 761 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY), 762 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY), 763 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y), 764 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y), 765 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), 766 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), 767 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X), 768 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY), 769 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410s", HDAPS_ORIENT_SWAP), 770 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY), 771 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY), 772 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T510", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y), 773 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W510", HDAPS_ORIENT_MAX), 774 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W520", HDAPS_ORIENT_MAX), 775 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), 776 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y), 777 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), 778 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), 779 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), 780 | HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP), 781 | { .ident = NULL } 782 | }; 783 | 784 | static int __init hdaps_init(void) 785 | { 786 | int ret; 787 | 788 | /* Determine axis orientation orientation */ 789 | if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */ 790 | if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */ 791 | hdaps_invert = 0; /* default */ 792 | 793 | /* Init timer before platform_driver_register, in case of suspend */ 794 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) 795 | init_timer(&hdaps_timer); 796 | hdaps_timer.function = hdaps_mousedev_poll; 797 | #else 798 | timer_setup(&hdaps_timer, hdaps_mousedev_poll, 0); 799 | #endif 800 | ret = platform_driver_register(&hdaps_driver); 801 | if (ret) 802 | goto out; 803 | 804 | pdev = platform_device_register_simple("hdaps", -1, NULL, 0); 805 | if (IS_ERR(pdev)) { 806 | ret = PTR_ERR(pdev); 807 | goto out_driver; 808 | } 809 | 810 | ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group); 811 | if (ret) 812 | goto out_device; 813 | 814 | hdaps_idev = input_allocate_device(); 815 | if (!hdaps_idev) { 816 | ret = -ENOMEM; 817 | goto out_group; 818 | } 819 | 820 | hdaps_idev_raw = input_allocate_device(); 821 | if (!hdaps_idev_raw) { 822 | ret = -ENOMEM; 823 | goto out_idev_first; 824 | } 825 | 826 | /* calibration for the input device (deferred to avoid delay) */ 827 | needs_calibration = 1; 828 | 829 | /* initialize the joystick-like fuzzed input device */ 830 | hdaps_idev->name = "ThinkPad HDAPS joystick emulation"; 831 | hdaps_idev->phys = "hdaps/input0"; 832 | hdaps_idev->id.bustype = BUS_HOST; 833 | hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR; 834 | hdaps_idev->id.product = HDAPS_INPUT_PRODUCT; 835 | hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION; 836 | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) 837 | hdaps_idev->cdev.dev = &pdev->dev; 838 | #endif 839 | hdaps_idev->evbit[0] = BIT(EV_ABS); 840 | hdaps_idev->open = hdaps_mousedev_open; 841 | hdaps_idev->close = hdaps_mousedev_close; 842 | input_set_abs_params(hdaps_idev, ABS_X, 843 | -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); 844 | input_set_abs_params(hdaps_idev, ABS_Y, 845 | -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); 846 | 847 | ret = input_register_device(hdaps_idev); 848 | if (ret) 849 | goto out_idev; 850 | 851 | /* initialize the raw data input device */ 852 | hdaps_idev_raw->name = "ThinkPad HDAPS accelerometer data"; 853 | hdaps_idev_raw->phys = "hdaps/input1"; 854 | hdaps_idev_raw->id.bustype = BUS_HOST; 855 | hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR; 856 | hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT; 857 | hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION; 858 | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) 859 | hdaps_idev_raw->cdev.dev = &pdev->dev; 860 | #endif 861 | hdaps_idev_raw->evbit[0] = BIT(EV_ABS); 862 | hdaps_idev_raw->open = hdaps_mousedev_open; 863 | hdaps_idev_raw->close = hdaps_mousedev_close; 864 | input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0); 865 | input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0); 866 | 867 | ret = input_register_device(hdaps_idev_raw); 868 | if (ret) 869 | goto out_idev_reg_first; 870 | 871 | printk(KERN_INFO "hdaps: driver successfully loaded.\n"); 872 | return 0; 873 | 874 | out_idev_reg_first: 875 | input_unregister_device(hdaps_idev); 876 | out_idev: 877 | input_free_device(hdaps_idev_raw); 878 | out_idev_first: 879 | input_free_device(hdaps_idev); 880 | out_group: 881 | sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); 882 | out_device: 883 | platform_device_unregister(pdev); 884 | out_driver: 885 | platform_driver_unregister(&hdaps_driver); 886 | hdaps_device_shutdown(); 887 | out: 888 | printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret); 889 | return ret; 890 | } 891 | 892 | static void __exit hdaps_exit(void) 893 | { 894 | input_unregister_device(hdaps_idev_raw); 895 | input_unregister_device(hdaps_idev); 896 | hdaps_device_shutdown(); /* ignore errors, effect is negligible */ 897 | sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); 898 | platform_device_unregister(pdev); 899 | platform_driver_unregister(&hdaps_driver); 900 | 901 | printk(KERN_INFO "hdaps: driver unloaded.\n"); 902 | } 903 | 904 | module_init(hdaps_init); 905 | module_exit(hdaps_exit); 906 | 907 | module_param_named(invert, hdaps_invert, uint, 0); 908 | MODULE_PARM_DESC(invert, "axis orientation code"); 909 | 910 | MODULE_AUTHOR("Robert Love"); 911 | MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver"); 912 | MODULE_LICENSE("GPL v2"); 913 | -------------------------------------------------------------------------------- /thinkpad_ec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * thinkpad_ec.c - ThinkPad embedded controller LPC3 functions 3 | * 4 | * The embedded controller on ThinkPad laptops has a non-standard interface, 5 | * where LPC channel 3 of the H8S EC chip is hooked up to IO ports 6 | * 0x1600-0x161F and implements (a special case of) the H8S LPC protocol. 7 | * The EC LPC interface provides various system management services (currently 8 | * known: battery information and accelerometer readouts). This driver 9 | * provides access and mutual exclusion for the EC interface. 10 | * 11 | * The LPC protocol and terminology are documented here: 12 | * "H8S/2104B Group Hardware Manual", 13 | * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf 14 | * 15 | * Copyright (C) 2006-2007 Shem Multinymous 16 | * 17 | * This program is free software; you can redistribute it and/or modify 18 | * it under the terms of the GNU General Public License as published by 19 | * the Free Software Foundation; either version 2 of the License, or 20 | * (at your option) any later version. 21 | * 22 | * This program is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | * GNU General Public License for more details. 26 | * 27 | * You should have received a copy of the GNU General Public License 28 | * along with this program; if not, write to the Free Software 29 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "thinkpad_ec.h" 38 | #include 39 | #include 40 | 41 | #include 42 | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) 43 | #include 44 | #else 45 | #include 46 | #endif 47 | 48 | #define TP_VERSION "0.44" 49 | 50 | MODULE_AUTHOR("Shem Multinymous"); 51 | MODULE_DESCRIPTION("ThinkPad embedded controller hardware access"); 52 | MODULE_VERSION(TP_VERSION); 53 | MODULE_LICENSE("GPL"); 54 | 55 | /* IO ports used by embedded controller LPC channel 3: */ 56 | #define TPC_BASE_PORT 0x1600 57 | #define TPC_NUM_PORTS 0x20 58 | #define TPC_STR3_PORT 0x1604 /* Reads H8S EC register STR3 */ 59 | #define TPC_TWR0_PORT 0x1610 /* Mapped to H8S EC register TWR0MW/SW */ 60 | #define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */ 61 | /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 00x%02x", \ 82 | msg, args->val[0x0], args->val[0xF], code) 83 | 84 | /* State of request prefetching: */ 85 | static u8 prefetch_arg0, prefetch_argF; /* Args of last prefetch */ 86 | static u64 prefetch_jiffies; /* time of prefetch, or: */ 87 | #define TPC_PREFETCH_NONE INITIAL_JIFFIES /* No prefetch */ 88 | #define TPC_PREFETCH_JUNK (INITIAL_JIFFIES+1) /* Ignore prefetch */ 89 | 90 | /* Locking: */ 91 | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) 92 | static DECLARE_MUTEX(thinkpad_ec_mutex); 93 | #elif LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0) 94 | static DEFINE_SEMAPHORE(thinkpad_ec_mutex); 95 | #else 96 | static DEFINE_SEMAPHORE(thinkpad_ec_mutex, 1); 97 | #endif 98 | 99 | /* Kludge in case the ACPI DSDT reserves the ports we need. */ 100 | static bool force_io; /* Willing to do IO to ports we couldn't reserve? */ 101 | static int reserved_io; /* Successfully reserved the ports? */ 102 | module_param_named(force_io, force_io, bool, 0600); 103 | MODULE_PARM_DESC(force_io, "Force IO even if region already reserved (0=off, 1=on)"); 104 | 105 | /** 106 | * thinkpad_ec_lock - get lock on the ThinkPad EC 107 | * 108 | * Get exclusive lock for accesing the ThinkPad embedded controller LPC3 109 | * interface. Returns 0 iff lock acquired. 110 | */ 111 | int thinkpad_ec_lock(void) 112 | { 113 | int ret; 114 | ret = down_interruptible(&thinkpad_ec_mutex); 115 | return ret; 116 | } 117 | EXPORT_SYMBOL_GPL(thinkpad_ec_lock); 118 | 119 | /** 120 | * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC 121 | * 122 | * Try getting an exclusive lock for accesing the ThinkPad embedded 123 | * controller LPC3. Returns immediately if lock is not available; neither 124 | * blocks nor sleeps. Returns 0 iff lock acquired . 125 | */ 126 | int thinkpad_ec_try_lock(void) 127 | { 128 | return down_trylock(&thinkpad_ec_mutex); 129 | } 130 | EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock); 131 | 132 | /** 133 | * thinkpad_ec_unlock - release lock on ThinkPad EC 134 | * 135 | * Release a previously acquired exclusive lock on the ThinkPad ebmedded 136 | * controller LPC3 interface. 137 | */ 138 | void thinkpad_ec_unlock(void) 139 | { 140 | up(&thinkpad_ec_mutex); 141 | } 142 | EXPORT_SYMBOL_GPL(thinkpad_ec_unlock); 143 | 144 | /** 145 | * thinkpad_ec_request_row - tell embedded controller to prepare a row 146 | * @args Input register arguments 147 | * 148 | * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or 149 | * a subset thereof) following the protocol prescribed by the "H8S/2104B Group 150 | * Hardware Manual". Does sanity checks via status register STR3. 151 | */ 152 | static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args) 153 | { 154 | u8 str3; 155 | int i; 156 | 157 | /* EC protocol requires write to TWR0 (function code): */ 158 | if (!(args->mask & 0x0001)) { 159 | printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask)); 160 | return -EINVAL; 161 | } 162 | 163 | /* Check initial STR3 status: */ 164 | str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; 165 | if (str3 & H8S_STR3_OBF3B) { /* data already pending */ 166 | inb(TPC_TWR15_PORT); /* marks end of previous transaction */ 167 | if (prefetch_jiffies == TPC_PREFETCH_NONE) 168 | printk(KERN_WARNING REQ_FMT( 169 | "EC has result from unrequested transaction", 170 | str3)); 171 | return -EBUSY; /* EC will be ready in a few usecs */ 172 | } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */ 173 | if (prefetch_jiffies == TPC_PREFETCH_NONE) 174 | printk(KERN_WARNING REQ_FMT( 175 | "EC is busy with unrequested transaction", 176 | str3)); 177 | return -EBUSY; /* data will be pending in a few usecs */ 178 | } else if (str3 != 0x00) { /* unexpected status? */ 179 | printk(KERN_WARNING REQ_FMT("unexpected initial STR3", str3)); 180 | return -EIO; 181 | } 182 | 183 | /* Send TWR0MW: */ 184 | outb(args->val[0], TPC_TWR0_PORT); 185 | str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; 186 | if (str3 != H8S_STR3_MWMF) { /* not accepted? */ 187 | printk(KERN_WARNING REQ_FMT("arg0 rejected", str3)); 188 | return -EIO; 189 | } 190 | 191 | /* Send TWR1 through TWR14: */ 192 | for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) 193 | if ((args->mask>>i)&1) 194 | outb(args->val[i], TPC_TWR0_PORT+i); 195 | 196 | /* Send TWR15 (default to 0x01). This marks end of command. */ 197 | outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT); 198 | 199 | /* Wait until EC starts writing its reply (~60ns on average). 200 | * Releasing locks before this happens may cause an EC hang 201 | * due to firmware bug! 202 | */ 203 | for (i = 0; i < TPC_REQUEST_RETRIES; i++) { 204 | str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; 205 | if (str3 & H8S_STR3_SWMF) /* EC started replying */ 206 | return 0; 207 | else if (!(str3 & ~(H8S_STR3_IBF3B|H8S_STR3_MWMF))) 208 | /* Normal progress (the EC hasn't seen the request 209 | * yet, or is processing it). Wait it out. */ 210 | ndelay(TPC_REQUEST_NDELAY); 211 | else { /* weird EC status */ 212 | printk(KERN_WARNING 213 | REQ_FMT("bad end STR3", str3)); 214 | return -EIO; 215 | } 216 | } 217 | printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3)); 218 | return -EIO; 219 | } 220 | 221 | /** 222 | * thinkpad_ec_read_data - read pre-requested row-data from EC 223 | * @args Input register arguments of pre-requested rows 224 | * @data Output register values 225 | * 226 | * Reads current row data from the controller, assuming it's already 227 | * requested. Follows the H8S spec for register access and status checks. 228 | */ 229 | static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args, 230 | struct thinkpad_ec_row *data) 231 | { 232 | int i; 233 | u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; 234 | /* Once we make a request, STR3 assumes the sequence of values listed 235 | * in the following 'if' as it reads the request and writes its data. 236 | * It takes about a few dozen nanosecs total, with very high variance. 237 | */ 238 | if (str3 == (H8S_STR3_IBF3B|H8S_STR3_MWMF) || 239 | str3 == 0x00 || /* the 0x00 is indistinguishable from idle EC! */ 240 | str3 == H8S_STR3_SWMF) 241 | return -EBUSY; /* not ready yet */ 242 | /* Finally, the EC signals output buffer full: */ 243 | if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) { 244 | printk(KERN_WARNING 245 | REQ_FMT("bad initial STR3", str3)); 246 | return -EIO; 247 | } 248 | 249 | /* Read first byte (signals start of read transactions): */ 250 | data->val[0] = inb(TPC_TWR0_PORT); 251 | /* Optionally read 14 more bytes: */ 252 | for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) 253 | if ((data->mask >> i)&1) 254 | data->val[i] = inb(TPC_TWR0_PORT+i); 255 | /* Read last byte from 0x161F (signals end of read transaction): */ 256 | data->val[0xF] = inb(TPC_TWR15_PORT); 257 | 258 | /* Readout still pending? */ 259 | str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; 260 | if (str3 & H8S_STR3_OBF3B) 261 | printk(KERN_WARNING 262 | REQ_FMT("OBF3B=1 after read", str3)); 263 | /* If port 0x161F returns 0x80 too often, the EC may lock up. Warn: */ 264 | if (data->val[0xF] == 0x80) 265 | printk(KERN_WARNING 266 | REQ_FMT("0x161F reports error", data->val[0xF])); 267 | return 0; 268 | } 269 | 270 | /** 271 | * thinkpad_ec_is_row_fetched - is the given row currently prefetched? 272 | * 273 | * To keep things simple we compare only the first and last args; 274 | * this suffices for all known cases. 275 | */ 276 | static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args) 277 | { 278 | return (prefetch_jiffies != TPC_PREFETCH_NONE) && 279 | (prefetch_jiffies != TPC_PREFETCH_JUNK) && 280 | (prefetch_arg0 == args->val[0]) && 281 | (prefetch_argF == args->val[0xF]) && 282 | (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT); 283 | } 284 | 285 | /** 286 | * thinkpad_ec_read_row - request and read data from ThinkPad EC 287 | * @args Input register arguments 288 | * @data Output register values 289 | * 290 | * Read a data row from the ThinkPad embedded controller LPC3 interface. 291 | * Does fetching and retrying if needed. The row is specified by an 292 | * array of 16 bytes, some of which may be undefined (but the first is 293 | * mandatory). These bytes are given in @args->val[], where @args->val[i] is 294 | * used iff (@args->mask>>i)&1). The resulting row data is stored in 295 | * @data->val[], but is only guaranteed to be valid for indices corresponding 296 | * to set bit in @data->mask. That is, if @data->mask&(1<val[i] is undefined. 298 | * 299 | * Returns -EBUSY on transient error and -EIO on abnormal condition. 300 | * Caller must hold controller lock. 301 | */ 302 | int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, 303 | struct thinkpad_ec_row *data) 304 | { 305 | int retries, ret; 306 | 307 | if (thinkpad_ec_is_row_fetched(args)) 308 | goto read_row; /* already requested */ 309 | 310 | /* Request the row */ 311 | for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { 312 | ret = thinkpad_ec_request_row(args); 313 | if (!ret) 314 | goto read_row; 315 | if (ret != -EBUSY) 316 | break; 317 | ndelay(TPC_READ_NDELAY); 318 | } 319 | printk(KERN_ERR REQ_FMT("failed requesting row", ret)); 320 | goto out; 321 | 322 | read_row: 323 | /* Read the row's data */ 324 | for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { 325 | ret = thinkpad_ec_read_data(args, data); 326 | if (!ret) 327 | goto out; 328 | if (ret != -EBUSY) 329 | break; 330 | ndelay(TPC_READ_NDELAY); 331 | } 332 | 333 | printk(KERN_ERR REQ_FMT("failed waiting for data", ret)); 334 | 335 | out: 336 | prefetch_jiffies = TPC_PREFETCH_JUNK; 337 | return ret; 338 | } 339 | EXPORT_SYMBOL_GPL(thinkpad_ec_read_row); 340 | 341 | /** 342 | * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC 343 | * @args Input register arguments 344 | * @data Output register values 345 | * 346 | * Try reading a data row from the ThinkPad embedded controller LPC3 347 | * interface, if this raw was recently prefetched using 348 | * thinkpad_ec_prefetch_row(). Does not fetch, retry or block. 349 | * The parameters have the same meaning as in thinkpad_ec_read_row(). 350 | * 351 | * Returns -EBUSY is data not ready and -ENODATA if row not prefetched. 352 | * Caller must hold controller lock. 353 | */ 354 | int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, 355 | struct thinkpad_ec_row *data) 356 | { 357 | int ret; 358 | if (!thinkpad_ec_is_row_fetched(args)) { 359 | ret = -ENODATA; 360 | } else { 361 | ret = thinkpad_ec_read_data(args, data); 362 | if (!ret) 363 | prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */ 364 | } 365 | return ret; 366 | } 367 | EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row); 368 | 369 | /** 370 | * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC 371 | * @args Input register arguments 372 | * 373 | * Prefetch a data row from the ThinkPad embedded controller LCP3 374 | * interface. A subsequent call to thinkpad_ec_read_row() with the 375 | * same arguments will be faster, and a subsequent call to 376 | * thinkpad_ec_try_read_row() stands a good chance of succeeding if 377 | * done neither too soon nor too late. See 378 | * thinkpad_ec_read_row() for the meaning of @args. 379 | * 380 | * Returns -EBUSY on transient error and -EIO on abnormal condition. 381 | * Caller must hold controller lock. 382 | */ 383 | int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args) 384 | { 385 | int ret; 386 | ret = thinkpad_ec_request_row(args); 387 | if (ret) { 388 | prefetch_jiffies = TPC_PREFETCH_JUNK; 389 | } else { 390 | prefetch_jiffies = get_jiffies_64(); 391 | prefetch_arg0 = args->val[0x0]; 392 | prefetch_argF = args->val[0xF]; 393 | } 394 | return ret; 395 | } 396 | EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row); 397 | 398 | /** 399 | * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data 400 | * 401 | * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the 402 | * ThinkPad embedded controller LPC3 interface. 403 | * Must be called before unlocking by any code that accesses the controller 404 | * ports directly. 405 | */ 406 | void thinkpad_ec_invalidate(void) 407 | { 408 | prefetch_jiffies = TPC_PREFETCH_JUNK; 409 | } 410 | EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate); 411 | 412 | 413 | /*** Checking for EC hardware ***/ 414 | 415 | /** 416 | * thinkpad_ec_test - verify the EC is present and follows protocol 417 | * 418 | * Ensure the EC LPC3 channel really works on this machine by making 419 | * an EC request and seeing if the EC follows the documented H8S protocol. 420 | * The requested row just reads battery status, so it should be harmless to 421 | * access it (on a correct EC). 422 | * This test writes to IO ports, so execute only after checking DMI. 423 | */ 424 | static int __init thinkpad_ec_test(void) 425 | { 426 | int ret; 427 | const struct thinkpad_ec_row args = /* battery 0 basic status */ 428 | { .mask = 0x8001, .val = {0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} }; 429 | struct thinkpad_ec_row data = { .mask = 0x0000 }; 430 | ret = thinkpad_ec_lock(); 431 | if (ret) 432 | return ret; 433 | ret = thinkpad_ec_read_row(&args, &data); 434 | thinkpad_ec_unlock(); 435 | return ret; 436 | } 437 | 438 | /* Search all DMI device names of a given type for a substring */ 439 | static int __init dmi_find_substring(int type, const char *substr) 440 | { 441 | const struct dmi_device *dev = NULL; 442 | while ((dev = dmi_find_device(type, NULL, dev))) { 443 | if (strstr(dev->name, substr)) 444 | return 1; 445 | } 446 | return 0; 447 | } 448 | 449 | #define TP_DMI_MATCH(vendor,model) { \ 450 | .ident = vendor " " model, \ 451 | .matches = { \ 452 | DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ 453 | DMI_MATCH(DMI_PRODUCT_VERSION, model) \ 454 | } \ 455 | } 456 | 457 | /* Check DMI for existence of ThinkPad embedded controller */ 458 | static int __init check_dmi_for_ec(void) 459 | { 460 | /* A few old models that have a good EC but don't report it in DMI */ 461 | struct dmi_system_id tp_whitelist[] = { 462 | TP_DMI_MATCH("IBM", "ThinkPad A30"), 463 | TP_DMI_MATCH("IBM", "ThinkPad T23"), 464 | TP_DMI_MATCH("IBM", "ThinkPad X24"), 465 | TP_DMI_MATCH("LENOVO", "ThinkPad"), 466 | { .ident = NULL } 467 | }; 468 | return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING, 469 | "IBM ThinkPad Embedded Controller") || 470 | dmi_check_system(tp_whitelist); 471 | } 472 | 473 | /*** Init and cleanup ***/ 474 | 475 | static int __init thinkpad_ec_init(void) 476 | { 477 | if (!check_dmi_for_ec()) { 478 | printk(KERN_WARNING 479 | "thinkpad_ec: no ThinkPad embedded controller!\n"); 480 | return -ENODEV; 481 | } 482 | 483 | if (request_region(TPC_BASE_PORT, TPC_NUM_PORTS, "thinkpad_ec")) { 484 | reserved_io = 1; 485 | } else { 486 | printk(KERN_ERR "thinkpad_ec: cannot claim IO ports %#x-%#x... ", 487 | TPC_BASE_PORT, 488 | TPC_BASE_PORT + TPC_NUM_PORTS - 1); 489 | if (force_io) { 490 | printk("forcing use of unreserved IO ports.\n"); 491 | } else { 492 | printk("consider using force_io=1.\n"); 493 | return -ENXIO; 494 | } 495 | } 496 | prefetch_jiffies = TPC_PREFETCH_JUNK; 497 | if (thinkpad_ec_test()) { 498 | printk(KERN_ERR "thinkpad_ec: initial ec test failed\n"); 499 | if (reserved_io) 500 | release_region(TPC_BASE_PORT, TPC_NUM_PORTS); 501 | return -ENXIO; 502 | } 503 | printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n"); 504 | return 0; 505 | } 506 | 507 | static void __exit thinkpad_ec_exit(void) 508 | { 509 | if (reserved_io) 510 | release_region(TPC_BASE_PORT, TPC_NUM_PORTS); 511 | printk(KERN_INFO "thinkpad_ec: unloaded.\n"); 512 | } 513 | 514 | module_init(thinkpad_ec_init); 515 | module_exit(thinkpad_ec_exit); 516 | -------------------------------------------------------------------------------- /thinkpad_ec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions 3 | * 4 | * Copyright (C) 2005 Shem Multinymous 5 | * 6 | * This program is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | */ 20 | 21 | #ifndef _THINKPAD_EC_H 22 | #define _THINKPAD_EC_H 23 | 24 | #ifdef __KERNEL__ 25 | 26 | #define TP_CONTROLLER_ROW_LEN 16 27 | 28 | /* EC transactions input and output (possibly partial) vectors of 16 bytes. */ 29 | struct thinkpad_ec_row { 30 | u16 mask; /* bitmap of which entries of val[] are meaningful */ 31 | u8 val[TP_CONTROLLER_ROW_LEN]; 32 | }; 33 | 34 | extern int __must_check thinkpad_ec_lock(void); 35 | extern int __must_check thinkpad_ec_try_lock(void); 36 | extern void thinkpad_ec_unlock(void); 37 | 38 | extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, 39 | struct thinkpad_ec_row *data); 40 | extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, 41 | struct thinkpad_ec_row *mask); 42 | extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args); 43 | extern void thinkpad_ec_invalidate(void); 44 | 45 | 46 | #endif /* __KERNEL */ 47 | #endif /* _THINKPAD_EC_H */ 48 | -------------------------------------------------------------------------------- /tp_smapi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tp_smapi.c - ThinkPad SMAPI support 3 | * 4 | * This driver exposes some features of the System Management Application 5 | * Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on 6 | * models in which the SMAPI BIOS runs in SMM and is invoked by writing 7 | * to the APM control port 0xB2. 8 | * It also exposes battery status information, obtained from the ThinkPad 9 | * embedded controller (via the thinkpad_ec module). 10 | * Ancient ThinkPad models use a different interface, supported by the 11 | * "thinkpad" module from "tpctl". 12 | * 13 | * Many of the battery status values obtained from the EC simply mirror 14 | * values provided by the battery's Smart Battery System (SBS) interface, so 15 | * their meaning is defined by the Smart Battery Data Specification (see 16 | * http://sbs-forum.org/specs/sbdat110.pdf). References to this SBS spec 17 | * are given in the code where relevant. 18 | * 19 | * Copyright (C) 2006 Shem Multinymous . 20 | * SMAPI access code based on the mwave driver by Mike Sullivan. 21 | * 22 | * This program is free software; you can redistribute it and/or modify 23 | * it under the terms of the GNU General Public License as published by 24 | * the Free Software Foundation; either version 2 of the License, or 25 | * (at your option) any later version. 26 | * 27 | * This program is distributed in the hope that it will be useful, 28 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | * GNU General Public License for more details. 31 | * 32 | * You should have received a copy of the GNU General Public License 33 | * along with this program; if not, write to the Free Software 34 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include /* CMOS defines */ 43 | #include 44 | #include 45 | #include "thinkpad_ec.h" 46 | #include 47 | #include 48 | #include 49 | 50 | #define TP_VERSION "0.44" 51 | #define TP_DESC "ThinkPad SMAPI Support" 52 | #define TP_DIR "smapi" 53 | 54 | MODULE_AUTHOR("Shem Multinymous"); 55 | MODULE_DESCRIPTION(TP_DESC); 56 | MODULE_VERSION(TP_VERSION); 57 | MODULE_LICENSE("GPL"); 58 | 59 | static struct platform_device *pdev; 60 | 61 | static int tp_debug; 62 | module_param_named(debug, tp_debug, int, 0600); 63 | MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)"); 64 | 65 | /* A few macros for printk()ing: */ 66 | #define TPRINTK(level, fmt, args...) \ 67 | dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args) 68 | #define DPRINTK(fmt, args...) \ 69 | do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while (0) 70 | 71 | /********************************************************************* 72 | * SMAPI interface 73 | */ 74 | 75 | /* SMAPI functions (register BX when making the SMM call). */ 76 | #define SMAPI_GET_INHIBIT_CHARGE 0x2114 77 | #define SMAPI_SET_INHIBIT_CHARGE 0x2115 78 | #define SMAPI_GET_THRESH_START 0x2116 79 | #define SMAPI_SET_THRESH_START 0x2117 80 | #define SMAPI_GET_FORCE_DISCHARGE 0x2118 81 | #define SMAPI_SET_FORCE_DISCHARGE 0x2119 82 | #define SMAPI_GET_THRESH_STOP 0x211a 83 | #define SMAPI_SET_THRESH_STOP 0x211b 84 | 85 | /* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at 86 | http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */ 87 | #define SMAPI_RETCODE_EOF 0xff 88 | static struct { u8 rc; char *msg; int ret; } smapi_retcode[] = 89 | { 90 | {0x00, "OK", 0}, 91 | {0x53, "SMAPI function is not available", -ENXIO}, 92 | {0x81, "Invalid parameter", -EINVAL}, 93 | {0x86, "Function is not supported by SMAPI BIOS", -EOPNOTSUPP}, 94 | {0x90, "System error", -EIO}, 95 | {0x91, "System is invalid", -EIO}, 96 | {0x92, "System is busy, -EBUSY"}, 97 | {0xa0, "Device error (disk read error)", -EIO}, 98 | {0xa1, "Device is busy", -EBUSY}, 99 | {0xa2, "Device is not attached", -ENXIO}, 100 | {0xa3, "Device is disbled", -EIO}, 101 | {0xa4, "Request parameter is out of range", -EINVAL}, 102 | {0xa5, "Request parameter is not accepted", -EINVAL}, 103 | {0xa6, "Transient error", -EBUSY}, /* ? */ 104 | {SMAPI_RETCODE_EOF, "Unknown error code", -EIO} 105 | }; 106 | 107 | 108 | #define SMAPI_MAX_RETRIES 10 109 | #define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */ 110 | static unsigned short smapi_port; /* APM control port, normally 0xB2 */ 111 | 112 | #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) 113 | static DECLARE_MUTEX(smapi_mutex); 114 | #elif LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0) 115 | static DEFINE_SEMAPHORE(smapi_mutex); 116 | #else 117 | static DEFINE_SEMAPHORE(smapi_mutex, 1); 118 | #endif 119 | 120 | /** 121 | * find_smapi_port - read SMAPI port from NVRAM 122 | */ 123 | static int __init find_smapi_port(void) 124 | { 125 | u16 smapi_id = 0; 126 | unsigned short port = 0; 127 | unsigned long flags; 128 | 129 | spin_lock_irqsave(&rtc_lock, flags); 130 | smapi_id = CMOS_READ(0x7C); 131 | smapi_id |= (CMOS_READ(0x7D) << 8); 132 | spin_unlock_irqrestore(&rtc_lock, flags); 133 | 134 | if (smapi_id != 0x5349) { 135 | printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id); 136 | return -ENXIO; 137 | } 138 | spin_lock_irqsave(&rtc_lock, flags); 139 | port = CMOS_READ(0x7E); 140 | port |= (CMOS_READ(0x7F) << 8); 141 | spin_unlock_irqrestore(&rtc_lock, flags); 142 | if (port == 0) { 143 | printk(KERN_ERR "unable to read SMAPI port number\n"); 144 | return -ENXIO; 145 | } 146 | return port; 147 | } 148 | 149 | /** 150 | * smapi_request - make a SMAPI call 151 | * @inEBX, @inECX, @inEDI, @inESI: input registers 152 | * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers 153 | * @msg: textual error message 154 | * Invokes the SMAPI SMBIOS with the given input and outpu args. 155 | * All outputs are optional (can be %NULL). 156 | * Returns 0 when successful, and a negative errno constant 157 | * (see smapi_retcode above) upon failure. 158 | */ 159 | static int smapi_request(u32 inEBX, u32 inECX, 160 | u32 inEDI, u32 inESI, 161 | u32 *outEBX, u32 *outECX, u32 *outEDX, 162 | u32 *outEDI, u32 *outESI, const char **msg) 163 | { 164 | int ret = 0; 165 | int i; 166 | int retries; 167 | u8 rc; 168 | /* Must use local vars for output regs, due to reg pressure. */ 169 | u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI; 170 | 171 | for (retries = 0; retries < SMAPI_MAX_RETRIES; ++retries) { 172 | DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x", 173 | inEBX, inECX, inEDI, inESI); 174 | 175 | /* SMAPI's SMBIOS call and thinkpad_ec end up using use 176 | * different interfaces to the same chip, so play it safe. */ 177 | ret = thinkpad_ec_lock(); 178 | if (ret) 179 | return ret; 180 | 181 | __asm__ __volatile__( 182 | "movl $0x00005380,%%eax\n\t" 183 | "movl %6,%%ebx\n\t" 184 | "movl %7,%%ecx\n\t" 185 | "movl %8,%%edi\n\t" 186 | "movl %9,%%esi\n\t" 187 | "xorl %%edx,%%edx\n\t" 188 | "movw %10,%%dx\n\t" 189 | "out %%al,%%dx\n\t" /* trigger SMI to SMBIOS */ 190 | "out %%al,$0x4F\n\t" 191 | "movl %%eax,%0\n\t" 192 | "movl %%ebx,%1\n\t" 193 | "movl %%ecx,%2\n\t" 194 | "movl %%edx,%3\n\t" 195 | "movl %%edi,%4\n\t" 196 | "movl %%esi,%5\n\t" 197 | :"=m"(tmpEAX), 198 | "=m"(tmpEBX), 199 | "=m"(tmpECX), 200 | "=m"(tmpEDX), 201 | "=m"(tmpEDI), 202 | "=m"(tmpESI) 203 | :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI), 204 | "m"((u16)smapi_port) 205 | :"%eax", "%ebx", "%ecx", "%edx", "%edi", 206 | "%esi"); 207 | 208 | thinkpad_ec_invalidate(); 209 | thinkpad_ec_unlock(); 210 | 211 | /* Don't let the next SMAPI access happen too quickly, 212 | * may case problems. (We're hold smapi_mutex). */ 213 | msleep(50); 214 | 215 | if (outEBX) *outEBX = tmpEBX; 216 | if (outECX) *outECX = tmpECX; 217 | if (outEDX) *outEDX = tmpEDX; 218 | if (outESI) *outESI = tmpESI; 219 | if (outEDI) *outEDI = tmpEDI; 220 | 221 | /* Look up error code */ 222 | rc = (tmpEAX>>8)&0xFF; 223 | for (i = 0; smapi_retcode[i].rc != SMAPI_RETCODE_EOF && 224 | smapi_retcode[i].rc != rc; ++i) {} 225 | ret = smapi_retcode[i].ret; 226 | if (msg) 227 | *msg = smapi_retcode[i].msg; 228 | 229 | DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d", 230 | tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret); 231 | if (ret) 232 | TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)", 233 | smapi_retcode[i].msg, inEBX); 234 | 235 | if (ret != -EBUSY) 236 | return ret; 237 | } 238 | return ret; 239 | } 240 | 241 | /* Convenience wrapper: discard output arguments */ 242 | static int smapi_write(u32 inEBX, u32 inECX, 243 | u32 inEDI, u32 inESI, const char **msg) 244 | { 245 | return smapi_request(inEBX, inECX, inEDI, inESI, 246 | NULL, NULL, NULL, NULL, NULL, msg); 247 | } 248 | 249 | 250 | /********************************************************************* 251 | * Specific SMAPI services 252 | * All of these functions return 0 upon success, and a negative errno 253 | * constant (see smapi_retcode) on failure. 254 | */ 255 | 256 | enum thresh_type { 257 | THRESH_STOP = 0, /* the code assumes this is 0 for brevity */ 258 | THRESH_START 259 | }; 260 | #define THRESH_NAME(which) ((which == THRESH_START) ? "start" : "stop") 261 | 262 | /** 263 | * __get_real_thresh - read battery charge start/stop threshold from SMAPI 264 | * @bat: battery number (0 or 1) 265 | * @which: THRESH_START or THRESH_STOP 266 | * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI) 267 | * @outEDI: some additional state that needs to be preserved, meaning unknown 268 | * @outESI: some additional state that needs to be preserved, meaning unknown 269 | */ 270 | static int __get_real_thresh(int bat, enum thresh_type which, int *thresh, 271 | u32 *outEDI, u32 *outESI) 272 | { 273 | u32 ebx = (which == THRESH_START) ? SMAPI_GET_THRESH_START 274 | : SMAPI_GET_THRESH_STOP; 275 | u32 ecx = (bat+1)<<8; 276 | const char *msg; 277 | int ret = smapi_request(ebx, ecx, 0, 0, NULL, 278 | &ecx, NULL, outEDI, outESI, &msg); 279 | if (ret) { 280 | TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s", 281 | THRESH_NAME(which), bat, msg); 282 | return ret; 283 | } 284 | if (!(ecx&0x00000100)) { 285 | TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x", 286 | THRESH_NAME(which), bat, ecx); 287 | return -EIO; 288 | } 289 | if (thresh) 290 | *thresh = ecx&0xFF; 291 | return 0; 292 | } 293 | 294 | /** 295 | * get_real_thresh - read battery charge start/stop threshold from SMAPI 296 | * @bat: battery number (0 or 1) 297 | * @which: THRESH_START or THRESH_STOP 298 | * @thresh: 1..99, 0=default (passes as-is to SMAPI) 299 | */ 300 | static int get_real_thresh(int bat, enum thresh_type which, int *thresh) 301 | { 302 | return __get_real_thresh(bat, which, thresh, NULL, NULL); 303 | } 304 | 305 | /** 306 | * set_real_thresh - write battery start/top charge threshold to SMAPI 307 | * @bat: battery number (0 or 1) 308 | * @which: THRESH_START or THRESH_STOP 309 | * @thresh: 1..99, 0=default (passes as-is to SMAPI) 310 | */ 311 | static int set_real_thresh(int bat, enum thresh_type which, int thresh) 312 | { 313 | u32 ebx = (which == THRESH_START) ? SMAPI_SET_THRESH_START 314 | : SMAPI_SET_THRESH_STOP; 315 | u32 ecx = ((bat+1)<<8) + thresh; 316 | u32 getDI, getSI; 317 | const char *msg; 318 | int ret; 319 | 320 | /* verify read before writing */ 321 | ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI); 322 | if (ret) 323 | return ret; 324 | 325 | ret = smapi_write(ebx, ecx, getDI, getSI, &msg); 326 | if (ret) 327 | TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s", 328 | THRESH_NAME(which), thresh, bat, msg); 329 | else 330 | TPRINTK(KERN_INFO, "set %s to %d for bat=%d", 331 | THRESH_NAME(which), thresh, bat); 332 | return ret; 333 | } 334 | 335 | /** 336 | * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI 337 | * @bat: battery number (0 or 1) 338 | * @minutes: period in minutes (1..65535 minutes, 0=disabled) 339 | * @outECX: some additional state that needs to be preserved, meaning unknown 340 | * Note that @minutes is the originally set value, it does not count down. 341 | */ 342 | static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX) 343 | { 344 | u32 ecx = (bat+1)<<8; 345 | u32 esi; 346 | const char *msg; 347 | int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0, 348 | NULL, &ecx, NULL, NULL, &esi, &msg); 349 | if (ret) { 350 | TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); 351 | return ret; 352 | } 353 | if (!(ecx&0x0100)) { 354 | TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat); 355 | return -EIO; 356 | } 357 | if (minutes) 358 | *minutes = (ecx&0x0001)?esi:0; 359 | if (outECX) 360 | *outECX = ecx; 361 | return 0; 362 | } 363 | 364 | /** 365 | * get_inhibit_charge_minutes - get inhibit charge period from SMAPI 366 | * @bat: battery number (0 or 1) 367 | * @minutes: period in minutes (1..65535 minutes, 0=disabled) 368 | * Note that @minutes is the originally set value, it does not count down. 369 | */ 370 | static int get_inhibit_charge_minutes(int bat, int *minutes) 371 | { 372 | return __get_inhibit_charge_minutes(bat, minutes, NULL); 373 | } 374 | 375 | /** 376 | * set_inhibit_charge_minutes - write inhibit charge period to SMAPI 377 | * @bat: battery number (0 or 1) 378 | * @minutes: period in minutes (1..65535 minutes, 0=disabled) 379 | */ 380 | static int set_inhibit_charge_minutes(int bat, int minutes) 381 | { 382 | u32 ecx; 383 | const char *msg; 384 | int ret; 385 | 386 | /* verify read before writing */ 387 | ret = __get_inhibit_charge_minutes(bat, NULL, &ecx); 388 | if (ret) 389 | return ret; 390 | 391 | ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes > 0 ? 0x0001 : 0x0000); 392 | if (minutes > 0xFFFF) 393 | minutes = 0xFFFF; 394 | ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg); 395 | if (ret) 396 | TPRINTK(KERN_NOTICE, 397 | "set to %d failed for bat=%d: %s", minutes, bat, msg); 398 | else 399 | TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat); 400 | return ret; 401 | } 402 | 403 | 404 | /** 405 | * get_force_discharge - get status of forced discharging from SMAPI 406 | * @bat: battery number (0 or 1) 407 | * @enabled: 1 if forced discharged is enabled, 0 if not 408 | */ 409 | static int get_force_discharge(int bat, int *enabled) 410 | { 411 | u32 ecx = (bat+1)<<8; 412 | const char *msg; 413 | int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, 414 | NULL, &ecx, NULL, NULL, NULL, &msg); 415 | if (ret) { 416 | TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); 417 | return ret; 418 | } 419 | *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0; 420 | return 0; 421 | } 422 | 423 | /** 424 | * set_force_discharge - write status of forced discharging to SMAPI 425 | * @bat: battery number (0 or 1) 426 | * @enabled: 1 if forced discharged is enabled, 0 if not 427 | */ 428 | static int set_force_discharge(int bat, int enabled) 429 | { 430 | u32 ecx = (bat+1)<<8; 431 | const char *msg; 432 | int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, 433 | NULL, &ecx, NULL, NULL, NULL, &msg); 434 | if (ret) { 435 | TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg); 436 | return ret; 437 | } 438 | if (ecx&0x00000100) { 439 | TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat); 440 | return -EIO; 441 | } 442 | 443 | ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0); 444 | ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg); 445 | if (ret) 446 | TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s", 447 | enabled, bat, msg); 448 | else 449 | TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat); 450 | return ret; 451 | } 452 | 453 | 454 | /********************************************************************* 455 | * Wrappers to threshold-related SMAPI functions, which handle default 456 | * thresholds and related quirks. 457 | */ 458 | 459 | /* Minimum, default and minimum difference for battery charging thresholds: */ 460 | #define MIN_THRESH_DELTA 4 /* Min delta between start and stop thresh */ 461 | #define MIN_THRESH_START 2 462 | #define MAX_THRESH_START (100-MIN_THRESH_DELTA) 463 | #define MIN_THRESH_STOP (MIN_THRESH_START + MIN_THRESH_DELTA) 464 | #define MAX_THRESH_STOP 100 465 | #define DEFAULT_THRESH_START MAX_THRESH_START 466 | #define DEFAULT_THRESH_STOP MAX_THRESH_STOP 467 | 468 | /* The GUI of IBM's Battery Maximizer seems to show a start threshold that 469 | * is 1 more than the value we set/get via SMAPI. Since the threshold is 470 | * maintained across reboot, this can be confusing. So we kludge our 471 | * interface for interoperability: */ 472 | #define BATMAX_FIX 1 473 | 474 | /* Get charge start/stop threshold (1..100), 475 | * substituting default values if needed and applying BATMAT_FIX. */ 476 | static int get_thresh(int bat, enum thresh_type which, int *thresh) 477 | { 478 | int ret = get_real_thresh(bat, which, thresh); 479 | if (ret) 480 | return ret; 481 | if (*thresh == 0) 482 | *thresh = (which == THRESH_START) ? DEFAULT_THRESH_START 483 | : DEFAULT_THRESH_STOP; 484 | else if (which == THRESH_START) 485 | *thresh += BATMAX_FIX; 486 | return 0; 487 | } 488 | 489 | 490 | /* Set charge start/stop threshold (1..100), 491 | * substituting default values if needed and applying BATMAT_FIX. */ 492 | static int set_thresh(int bat, enum thresh_type which, int thresh) 493 | { 494 | if (which == THRESH_STOP && thresh == DEFAULT_THRESH_STOP) 495 | thresh = 0; /* 100 is out of range, but default means 100 */ 496 | if (which == THRESH_START) 497 | thresh -= BATMAX_FIX; 498 | return set_real_thresh(bat, which, thresh); 499 | } 500 | 501 | /********************************************************************* 502 | * ThinkPad embedded controller readout and basic functions 503 | */ 504 | 505 | /** 506 | * read_tp_ec_row - read data row from the ThinkPad embedded controller 507 | * @arg0: EC command code 508 | * @bat: battery number, 0 or 1 509 | * @j: the byte value to be used for "junk" (unused) input/outputs 510 | * @dataval: result vector 511 | */ 512 | static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8 *dataval) 513 | { 514 | int ret; 515 | const struct thinkpad_ec_row args = { .mask = 0xFFFF, 516 | .val = {arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} }; 517 | struct thinkpad_ec_row data = { .mask = 0xFFFF }; 518 | 519 | ret = thinkpad_ec_lock(); 520 | if (ret) 521 | return ret; 522 | ret = thinkpad_ec_read_row(&args, &data); 523 | thinkpad_ec_unlock(); 524 | memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN); 525 | return ret; 526 | } 527 | 528 | /** 529 | * power_device_present - check for presence of battery or AC power 530 | * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power 531 | * Returns 1 if present, 0 if not present, negative if error. 532 | */ 533 | static int power_device_present(int bat) 534 | { 535 | u8 row[TP_CONTROLLER_ROW_LEN]; 536 | u8 test; 537 | int ret = read_tp_ec_row(1, bat, 0, row); 538 | if (ret) 539 | return ret; 540 | switch (bat) { 541 | case 0: test = 0x40; break; /* battery 0 */ 542 | case 1: test = 0x20; break; /* battery 1 */ 543 | default: test = 0x80; /* AC power */ 544 | } 545 | return (row[0] & test) ? 1 : 0; 546 | } 547 | 548 | /** 549 | * bat_has_status - check if battery can report detailed status 550 | * @bat: 0 for battery 0, 1 for battery 1 551 | * Returns 1 if yes, 0 if no, negative if error. 552 | */ 553 | static int bat_has_status(int bat) 554 | { 555 | u8 row[TP_CONTROLLER_ROW_LEN]; 556 | int ret = read_tp_ec_row(1, bat, 0, row); 557 | if (ret) 558 | return ret; 559 | if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */ 560 | return 0; 561 | if ((row[1] & (0x60)) == 0) /* no status */ 562 | return 0; 563 | return 1; 564 | } 565 | 566 | /** 567 | * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data 568 | * @arg0: first argument to EC 569 | * @off: offset in row returned from EC 570 | * @bat: battery (0 or 1) 571 | * @val: the 16-bit value obtained 572 | * Returns nonzero on error. 573 | */ 574 | static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val) 575 | { 576 | u8 row[TP_CONTROLLER_ROW_LEN]; 577 | int ret; 578 | if (bat_has_status(bat) != 1) 579 | return -ENXIO; 580 | ret = read_tp_ec_row(arg0, bat, 0, row); 581 | if (ret) 582 | return ret; 583 | *val = *(u16 *)(row+offset); 584 | return 0; 585 | } 586 | 587 | /********************************************************************* 588 | * sysfs attributes for batteries - 589 | * definitions and helper functions 590 | */ 591 | 592 | /* A custom device attribute struct which holds a battery number */ 593 | struct bat_device_attribute { 594 | struct device_attribute dev_attr; 595 | int bat; 596 | }; 597 | 598 | /** 599 | * attr_get_bat - get the battery to which the attribute belongs 600 | */ 601 | static int attr_get_bat(struct device_attribute *attr) 602 | { 603 | return container_of(attr, struct bat_device_attribute, dev_attr)->bat; 604 | } 605 | 606 | /** 607 | * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute 608 | * @arg0: specified 1st argument of EC raw to read 609 | * @offset: byte offset in EC raw data 610 | * @mul: correction factor to multiply by 611 | * @na_msg: string to output is value not available (0xFFFFFFFF) 612 | * @attr: battery attribute 613 | * @buf: output buffer 614 | * The 16-bit value is read from the EC, treated as unsigned, 615 | * transformed as x->mul*x, and printed to the buffer. 616 | * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead. 617 | */ 618 | static ssize_t show_tp_ec_bat_u16(u8 arg0, int offset, int mul, 619 | const char *na_msg, 620 | struct device_attribute *attr, char *buf) 621 | { 622 | u16 val; 623 | int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); 624 | if (ret) 625 | return ret; 626 | if (na_msg && val == 0xFFFF) 627 | return sprintf(buf, "%s\n", na_msg); 628 | else 629 | return sprintf(buf, "%u\n", mul*(unsigned int)val); 630 | } 631 | 632 | /** 633 | * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute 634 | * @arg0: specified 1st argument of EC raw to read 635 | * @offset: byte offset in EC raw data 636 | * @mul: correction factor to multiply by 637 | * @add: correction term to add after multiplication 638 | * @attr: battery attribute 639 | * @buf: output buffer 640 | * The 16-bit value is read from the EC, treated as signed, 641 | * transformed as x->mul*x+add, and printed to the buffer. 642 | */ 643 | static ssize_t show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add, 644 | struct device_attribute *attr, char *buf) 645 | { 646 | u16 val; 647 | int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); 648 | if (ret) 649 | return ret; 650 | return sprintf(buf, "%d\n", mul*(s16)val+add); 651 | } 652 | 653 | /** 654 | * show_tp_ec_bat_str - show a string from EC battery status data 655 | * @arg0: specified 1st argument of EC raw to read 656 | * @offset: byte offset in EC raw data 657 | * @maxlen: maximum string length 658 | * @attr: battery attribute 659 | * @buf: output buffer 660 | */ 661 | static ssize_t show_tp_ec_bat_str(u8 arg0, int offset, int maxlen, 662 | struct device_attribute *attr, char *buf) 663 | { 664 | int bat = attr_get_bat(attr); 665 | u8 row[TP_CONTROLLER_ROW_LEN]; 666 | int ret; 667 | if (bat_has_status(bat) != 1) 668 | return -ENXIO; 669 | ret = read_tp_ec_row(arg0, bat, 0, row); 670 | if (ret) 671 | return ret; 672 | strncpy(buf, (char *)row+offset, maxlen); 673 | buf[maxlen] = 0; 674 | strcat(buf, "\n"); 675 | return strlen(buf); 676 | } 677 | 678 | /** 679 | * show_tp_ec_bat_power - show a power readout from EC battery status data 680 | * @arg0: specified 1st argument of EC raw to read 681 | * @offV: byte offset of voltage in EC raw data 682 | * @offI: byte offset of current in EC raw data 683 | * @attr: battery attribute 684 | * @buf: output buffer 685 | * Computes the power as current*voltage from the two given readout offsets. 686 | */ 687 | static ssize_t show_tp_ec_bat_power(u8 arg0, int offV, int offI, 688 | struct device_attribute *attr, char *buf) 689 | { 690 | u8 row[TP_CONTROLLER_ROW_LEN]; 691 | int milliamp, millivolt, ret; 692 | int bat = attr_get_bat(attr); 693 | if (bat_has_status(bat) != 1) 694 | return -ENXIO; 695 | ret = read_tp_ec_row(1, bat, 0, row); 696 | if (ret) 697 | return ret; 698 | millivolt = *(u16 *)(row+offV); 699 | milliamp = *(s16 *)(row+offI); 700 | return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* units: mW */ 701 | } 702 | 703 | /** 704 | * show_tp_ec_bat_date - decode and show a date from EC battery status data 705 | * @arg0: specified 1st argument of EC raw to read 706 | * @offset: byte offset in EC raw data 707 | * @attr: battery attribute 708 | * @buf: output buffer 709 | */ 710 | static ssize_t show_tp_ec_bat_date(u8 arg0, int offset, 711 | struct device_attribute *attr, char *buf) 712 | { 713 | u8 row[TP_CONTROLLER_ROW_LEN]; 714 | u16 v; 715 | int ret; 716 | int day, month, year; 717 | int bat = attr_get_bat(attr); 718 | if (bat_has_status(bat) != 1) 719 | return -ENXIO; 720 | ret = read_tp_ec_row(arg0, bat, 0, row); 721 | if (ret) 722 | return ret; 723 | 724 | /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */ 725 | v = *(u16 *)(row+offset); 726 | day = v & 0x1F; 727 | month = (v >> 5) & 0xF; 728 | year = (v >> 9) + 1980; 729 | 730 | return sprintf(buf, "%04d-%02d-%02d\n", year, month, day); 731 | } 732 | 733 | 734 | /********************************************************************* 735 | * sysfs attribute I/O for batteries - 736 | * the actual attribute show/store functions 737 | */ 738 | 739 | static ssize_t show_battery_start_charge_thresh(struct device *dev, 740 | struct device_attribute *attr, char *buf) 741 | { 742 | int thresh; 743 | int bat = attr_get_bat(attr); 744 | int ret = get_thresh(bat, THRESH_START, &thresh); 745 | if (ret) 746 | return ret; 747 | return sprintf(buf, "%d\n", thresh); /* units: percent */ 748 | } 749 | 750 | static ssize_t show_battery_stop_charge_thresh(struct device *dev, 751 | struct device_attribute *attr, char *buf) 752 | { 753 | int thresh; 754 | int bat = attr_get_bat(attr); 755 | int ret = get_thresh(bat, THRESH_STOP, &thresh); 756 | if (ret) 757 | return ret; 758 | return sprintf(buf, "%d\n", thresh); /* units: percent */ 759 | } 760 | 761 | /** 762 | * store_battery_start_charge_thresh - store battery_start_charge_thresh attr 763 | * Since this is a kernel<->user interface, we ensure a valid state for 764 | * the hardware. We do this by clamping the requested threshold to the 765 | * valid range and, if necessary, moving the other threshold so that 766 | * it's MIN_THRESH_DELTA away from this one. 767 | */ 768 | static ssize_t store_battery_start_charge_thresh(struct device *dev, 769 | struct device_attribute *attr, const char *buf, size_t count) 770 | { 771 | int thresh, other_thresh, ret; 772 | int bat = attr_get_bat(attr); 773 | 774 | if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) 775 | return -EINVAL; 776 | 777 | if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */ 778 | thresh = MIN_THRESH_START; 779 | if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */ 780 | thresh = MAX_THRESH_START; 781 | 782 | down(&smapi_mutex); 783 | ret = get_thresh(bat, THRESH_STOP, &other_thresh); 784 | if (ret != -EOPNOTSUPP && ret != -ENXIO) { 785 | if (ret) /* other threshold is set? */ 786 | goto out; 787 | ret = get_real_thresh(bat, THRESH_START, NULL); 788 | if (ret) /* this threshold is set? */ 789 | goto out; 790 | if (other_thresh < thresh+MIN_THRESH_DELTA) { 791 | /* move other thresh to keep it above this one */ 792 | ret = set_thresh(bat, THRESH_STOP, 793 | thresh+MIN_THRESH_DELTA); 794 | if (ret) 795 | goto out; 796 | } 797 | } 798 | ret = set_thresh(bat, THRESH_START, thresh); 799 | out: 800 | up(&smapi_mutex); 801 | return count; 802 | 803 | } 804 | 805 | /** 806 | * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr 807 | * Since this is a kernel<->user interface, we ensure a valid state for 808 | * the hardware. We do this by clamping the requested threshold to the 809 | * valid range and, if necessary, moving the other threshold so that 810 | * it's MIN_THRESH_DELTA away from this one. 811 | */ 812 | static ssize_t store_battery_stop_charge_thresh(struct device *dev, 813 | struct device_attribute *attr, const char *buf, size_t count) 814 | { 815 | int thresh, other_thresh, ret; 816 | int bat = attr_get_bat(attr); 817 | 818 | if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) 819 | return -EINVAL; 820 | 821 | if (thresh < MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */ 822 | thresh = MIN_THRESH_STOP; 823 | 824 | down(&smapi_mutex); 825 | ret = get_thresh(bat, THRESH_START, &other_thresh); 826 | if (ret != -EOPNOTSUPP && ret != -ENXIO) { /* other threshold exists? */ 827 | if (ret) 828 | goto out; 829 | /* this threshold exists? */ 830 | ret = get_real_thresh(bat, THRESH_STOP, NULL); 831 | if (ret) 832 | goto out; 833 | if (other_thresh >= thresh-MIN_THRESH_DELTA) { 834 | /* move other thresh to be below this one */ 835 | ret = set_thresh(bat, THRESH_START, 836 | thresh-MIN_THRESH_DELTA); 837 | if (ret) 838 | goto out; 839 | } 840 | } 841 | ret = set_thresh(bat, THRESH_STOP, thresh); 842 | out: 843 | up(&smapi_mutex); 844 | return count; 845 | } 846 | 847 | static ssize_t show_battery_inhibit_charge_minutes(struct device *dev, 848 | struct device_attribute *attr, char *buf) 849 | { 850 | int minutes; 851 | int bat = attr_get_bat(attr); 852 | int ret = get_inhibit_charge_minutes(bat, &minutes); 853 | if (ret) 854 | return ret; 855 | return sprintf(buf, "%d\n", minutes); /* units: minutes */ 856 | } 857 | 858 | static ssize_t store_battery_inhibit_charge_minutes(struct device *dev, 859 | struct device_attribute *attr, 860 | const char *buf, size_t count) 861 | { 862 | int ret; 863 | int minutes; 864 | int bat = attr_get_bat(attr); 865 | if (sscanf(buf, "%d", &minutes) != 1 || minutes < 0) { 866 | TPRINTK(KERN_ERR, "inhibit_charge_minutes: " 867 | "must be a non-negative integer"); 868 | return -EINVAL; 869 | } 870 | ret = set_inhibit_charge_minutes(bat, minutes); 871 | if (ret) 872 | return ret; 873 | return count; 874 | } 875 | 876 | static ssize_t show_battery_force_discharge(struct device *dev, 877 | struct device_attribute *attr, char *buf) 878 | { 879 | int enabled; 880 | int bat = attr_get_bat(attr); 881 | int ret = get_force_discharge(bat, &enabled); 882 | if (ret) 883 | return ret; 884 | return sprintf(buf, "%d\n", enabled); /* type: boolean */ 885 | } 886 | 887 | static ssize_t store_battery_force_discharge(struct device *dev, 888 | struct device_attribute *attr, const char *buf, size_t count) 889 | { 890 | int ret; 891 | int enabled; 892 | int bat = attr_get_bat(attr); 893 | if (sscanf(buf, "%d", &enabled) != 1 || enabled < 0 || enabled > 1) 894 | return -EINVAL; 895 | ret = set_force_discharge(bat, enabled); 896 | if (ret) 897 | return ret; 898 | return count; 899 | } 900 | 901 | static ssize_t show_battery_installed( 902 | struct device *dev, struct device_attribute *attr, char *buf) 903 | { 904 | int bat = attr_get_bat(attr); 905 | int ret = power_device_present(bat); 906 | if (ret < 0) 907 | return ret; 908 | return sprintf(buf, "%d\n", ret); /* type: boolean */ 909 | } 910 | 911 | static ssize_t show_battery_state( 912 | struct device *dev, struct device_attribute *attr, char *buf) 913 | { 914 | u8 row[TP_CONTROLLER_ROW_LEN]; 915 | const char *txt; 916 | int ret; 917 | int bat = attr_get_bat(attr); 918 | if (bat_has_status(bat) != 1) 919 | return sprintf(buf, "none\n"); 920 | ret = read_tp_ec_row(1, bat, 0, row); 921 | if (ret) 922 | return ret; 923 | switch (row[1] & 0xf0) { 924 | case 0xc0: txt = "idle"; break; 925 | case 0xd0: txt = "discharging"; break; 926 | case 0xe0: txt = "charging"; break; 927 | default: return sprintf(buf, "unknown (0x%x)\n", row[1]); 928 | } 929 | return sprintf(buf, "%s\n", txt); /* type: string from fixed set */ 930 | } 931 | 932 | static ssize_t show_battery_manufacturer( 933 | struct device *dev, struct device_attribute *attr, char *buf) 934 | { 935 | /* type: string. SBS spec v1.1 p34: ManufacturerName() */ 936 | return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); 937 | } 938 | 939 | static ssize_t show_battery_model( 940 | struct device *dev, struct device_attribute *attr, char *buf) 941 | { 942 | /* type: string. SBS spec v1.1 p34: DeviceName() */ 943 | return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); 944 | } 945 | 946 | static ssize_t show_battery_barcoding( 947 | struct device *dev, struct device_attribute *attr, char *buf) 948 | { 949 | /* type: string */ 950 | return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); 951 | } 952 | 953 | static ssize_t show_battery_chemistry( 954 | struct device *dev, struct device_attribute *attr, char *buf) 955 | { 956 | /* type: string. SBS spec v1.1 p34-35: DeviceChemistry() */ 957 | return show_tp_ec_bat_str(6, 2, 5, attr, buf); 958 | } 959 | 960 | static ssize_t show_battery_voltage( 961 | struct device *dev, struct device_attribute *attr, char *buf) 962 | { 963 | /* units: mV. SBS spec v1.1 p24: Voltage() */ 964 | return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf); 965 | } 966 | 967 | static ssize_t show_battery_design_voltage( 968 | struct device *dev, struct device_attribute *attr, char *buf) 969 | { 970 | /* units: mV. SBS spec v1.1 p32: DesignVoltage() */ 971 | return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf); 972 | } 973 | 974 | static ssize_t show_battery_charging_max_voltage( 975 | struct device *dev, struct device_attribute *attr, char *buf) 976 | { 977 | /* units: mV. SBS spec v1.1 p37,39: ChargingVoltage() */ 978 | return show_tp_ec_bat_u16(9, 8, 1, NULL, attr, buf); 979 | } 980 | 981 | static ssize_t show_battery_group0_voltage( 982 | struct device *dev, struct device_attribute *attr, char *buf) 983 | { 984 | /* units: mV */ 985 | return show_tp_ec_bat_u16(0xA, 12, 1, NULL, attr, buf); 986 | } 987 | 988 | static ssize_t show_battery_group1_voltage( 989 | struct device *dev, struct device_attribute *attr, char *buf) 990 | { 991 | /* units: mV */ 992 | return show_tp_ec_bat_u16(0xA, 10, 1, NULL, attr, buf); 993 | } 994 | 995 | static ssize_t show_battery_group2_voltage( 996 | struct device *dev, struct device_attribute *attr, char *buf) 997 | { 998 | /* units: mV */ 999 | return show_tp_ec_bat_u16(0xA, 8, 1, NULL, attr, buf); 1000 | } 1001 | 1002 | static ssize_t show_battery_group3_voltage( 1003 | struct device *dev, struct device_attribute *attr, char *buf) 1004 | { 1005 | /* units: mV */ 1006 | return show_tp_ec_bat_u16(0xA, 6, 1, NULL, attr, buf); 1007 | } 1008 | 1009 | static ssize_t show_battery_current_now( 1010 | struct device *dev, struct device_attribute *attr, char *buf) 1011 | { 1012 | /* units: mA. SBS spec v1.1 p24: Current() */ 1013 | return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf); 1014 | } 1015 | 1016 | static ssize_t show_battery_current_avg( 1017 | struct device *dev, struct device_attribute *attr, char *buf) 1018 | { 1019 | /* units: mA. SBS spec v1.1 p24: AverageCurrent() */ 1020 | return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf); 1021 | } 1022 | 1023 | static ssize_t show_battery_charging_max_current( 1024 | struct device *dev, struct device_attribute *attr, char *buf) 1025 | { 1026 | /* units: mA. SBS spec v1.1 p36,38: ChargingCurrent() */ 1027 | return show_tp_ec_bat_s16(9, 6, 1, 0, attr, buf); 1028 | } 1029 | 1030 | static ssize_t show_battery_power_now( 1031 | struct device *dev, struct device_attribute *attr, char *buf) 1032 | { 1033 | /* units: mW. SBS spec v1.1: Voltage()*Current() */ 1034 | return show_tp_ec_bat_power(1, 6, 8, attr, buf); 1035 | } 1036 | 1037 | static ssize_t show_battery_power_avg( 1038 | struct device *dev, struct device_attribute *attr, char *buf) 1039 | { 1040 | /* units: mW. SBS spec v1.1: Voltage()*AverageCurrent() */ 1041 | return show_tp_ec_bat_power(1, 6, 10, attr, buf); 1042 | } 1043 | 1044 | static ssize_t show_battery_remaining_percent( 1045 | struct device *dev, struct device_attribute *attr, char *buf) 1046 | { 1047 | /* units: percent. SBS spec v1.1 p25: RelativeStateOfCharge() */ 1048 | return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf); 1049 | } 1050 | 1051 | static ssize_t show_battery_remaining_percent_error( 1052 | struct device *dev, struct device_attribute *attr, char *buf) 1053 | { 1054 | /* units: percent. SBS spec v1.1 p25: MaxError() */ 1055 | return show_tp_ec_bat_u16(9, 4, 1, NULL, attr, buf); 1056 | } 1057 | 1058 | static ssize_t show_battery_remaining_charging_time( 1059 | struct device *dev, struct device_attribute *attr, char *buf) 1060 | { 1061 | /* units: minutes. SBS spec v1.1 p27: AverageTimeToFull() */ 1062 | return show_tp_ec_bat_u16(2, 8, 1, "not_charging", attr, buf); 1063 | } 1064 | 1065 | static ssize_t show_battery_remaining_running_time( 1066 | struct device *dev, struct device_attribute *attr, char *buf) 1067 | { 1068 | /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ 1069 | return show_tp_ec_bat_u16(2, 6, 1, "not_discharging", attr, buf); 1070 | } 1071 | 1072 | static ssize_t show_battery_remaining_running_time_now( 1073 | struct device *dev, struct device_attribute *attr, char *buf) 1074 | { 1075 | /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ 1076 | return show_tp_ec_bat_u16(2, 4, 1, "not_discharging", attr, buf); 1077 | } 1078 | 1079 | static ssize_t show_battery_remaining_capacity( 1080 | struct device *dev, struct device_attribute *attr, char *buf) 1081 | { 1082 | /* units: mWh. SBS spec v1.1 p26. */ 1083 | return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf); 1084 | } 1085 | 1086 | static ssize_t show_battery_last_full_capacity( 1087 | struct device *dev, struct device_attribute *attr, char *buf) 1088 | { 1089 | /* units: mWh. SBS spec v1.1 p26: FullChargeCapacity() */ 1090 | return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf); 1091 | } 1092 | 1093 | static ssize_t show_battery_design_capacity( 1094 | struct device *dev, struct device_attribute *attr, char *buf) 1095 | { 1096 | /* units: mWh. SBS spec v1.1 p32: DesignCapacity() */ 1097 | return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf); 1098 | } 1099 | 1100 | static ssize_t show_battery_cycle_count( 1101 | struct device *dev, struct device_attribute *attr, char *buf) 1102 | { 1103 | /* units: ordinal. SBS spec v1.1 p32: CycleCount() */ 1104 | return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); 1105 | } 1106 | 1107 | static ssize_t show_battery_temperature( 1108 | struct device *dev, struct device_attribute *attr, char *buf) 1109 | { 1110 | /* units: millicelsius. SBS spec v1.1: Temperature()*10 */ 1111 | return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf); 1112 | } 1113 | 1114 | static ssize_t show_battery_serial( 1115 | struct device *dev, struct device_attribute *attr, char *buf) 1116 | { 1117 | /* type: int. SBS spec v1.1 p34: SerialNumber() */ 1118 | return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf); 1119 | } 1120 | 1121 | static ssize_t show_battery_manufacture_date( 1122 | struct device *dev, struct device_attribute *attr, char *buf) 1123 | { 1124 | /* type: YYYY-MM-DD. SBS spec v1.1 p34: ManufactureDate() */ 1125 | return show_tp_ec_bat_date(3, 8, attr, buf); 1126 | } 1127 | 1128 | static ssize_t show_battery_first_use_date( 1129 | struct device *dev, struct device_attribute *attr, char *buf) 1130 | { 1131 | /* type: YYYY-MM-DD */ 1132 | return show_tp_ec_bat_date(8, 2, attr, buf); 1133 | } 1134 | 1135 | /** 1136 | * show_battery_dump - show the battery's dump attribute 1137 | * The dump attribute gives a hex dump of all EC readouts related to a 1138 | * battery. Some of the enumerated values don't really exist (i.e., the 1139 | * EC function just leaves them untouched); we use a kludge to detect and 1140 | * denote these. 1141 | */ 1142 | #define MIN_DUMP_ARG0 0x00 1143 | #define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */ 1144 | static ssize_t show_battery_dump( 1145 | struct device *dev, struct device_attribute *attr, char *buf) 1146 | { 1147 | int i; 1148 | char *p = buf; 1149 | int bat = attr_get_bat(attr); 1150 | u8 arg0; /* first argument to EC */ 1151 | u8 rowa[TP_CONTROLLER_ROW_LEN], 1152 | rowb[TP_CONTROLLER_ROW_LEN]; 1153 | const u8 junka = 0xAA, 1154 | junkb = 0x55; /* junk values for testing changes */ 1155 | int ret; 1156 | 1157 | for (arg0 = MIN_DUMP_ARG0; arg0 <= MAX_DUMP_ARG0; ++arg0) { 1158 | if ((p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5) 1159 | return -ENOMEM; /* don't overflow sysfs buf */ 1160 | /* Read raw twice with different junk values, 1161 | * to detect unused output bytes which are left unchaged: */ 1162 | ret = read_tp_ec_row(arg0, bat, junka, rowa); 1163 | if (ret) 1164 | return ret; 1165 | ret = read_tp_ec_row(arg0, bat, junkb, rowb); 1166 | if (ret) 1167 | return ret; 1168 | for (i = 0; i < TP_CONTROLLER_ROW_LEN; i++) { 1169 | if (rowa[i] == junka && rowb[i] == junkb) 1170 | p += sprintf(p, "-- "); /* unused by EC */ 1171 | else 1172 | p += sprintf(p, "%02x ", rowa[i]); 1173 | } 1174 | p += sprintf(p, "\n"); 1175 | } 1176 | return p-buf; 1177 | } 1178 | 1179 | 1180 | /********************************************************************* 1181 | * sysfs attribute I/O, other than batteries 1182 | */ 1183 | 1184 | static ssize_t show_ac_connected( 1185 | struct device *dev, struct device_attribute *attr, char *buf) 1186 | { 1187 | int ret = power_device_present(0xFF); 1188 | if (ret < 0) 1189 | return ret; 1190 | return sprintf(buf, "%d\n", ret); /* type: boolean */ 1191 | } 1192 | 1193 | /********************************************************************* 1194 | * The the "smapi_request" sysfs attribute executes a raw SMAPI call. 1195 | * You write to make a request and read to get the result. The state 1196 | * is saved globally rather than per fd (sysfs limitation), so 1197 | * simultaenous requests may get each other's results! So this is for 1198 | * development and debugging only. 1199 | */ 1200 | #define MAX_SMAPI_ATTR_ANSWER_LEN 128 1201 | static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = ""; 1202 | 1203 | static ssize_t show_smapi_request(struct device *dev, 1204 | struct device_attribute *attr, char *buf) 1205 | { 1206 | int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer); 1207 | smapi_attr_answer[0] = '\0'; 1208 | return ret; 1209 | } 1210 | 1211 | static ssize_t store_smapi_request(struct device *dev, 1212 | struct device_attribute *attr, 1213 | const char *buf, size_t count) 1214 | { 1215 | unsigned int inEBX, inECX, inEDI, inESI; 1216 | u32 outEBX, outECX, outEDX, outEDI, outESI; 1217 | const char *msg; 1218 | int ret; 1219 | if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) { 1220 | smapi_attr_answer[0] = '\0'; 1221 | return -EINVAL; 1222 | } 1223 | ret = smapi_request( 1224 | inEBX, inECX, inEDI, inESI, 1225 | &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg); 1226 | snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN, 1227 | "%x %x %x %x %x %d '%s'\n", 1228 | (unsigned int)outEBX, (unsigned int)outECX, 1229 | (unsigned int)outEDX, (unsigned int)outEDI, 1230 | (unsigned int)outESI, ret, msg); 1231 | if (ret) 1232 | return ret; 1233 | else 1234 | return count; 1235 | } 1236 | 1237 | /********************************************************************* 1238 | * Power management: the embedded controller forgets the battery 1239 | * thresholds when the system is suspended to disk and unplugged from 1240 | * AC and battery, so we restore it upon resume. 1241 | */ 1242 | 1243 | static int saved_threshs[4] = {-1, -1, -1, -1}; /* -1 = don't know */ 1244 | 1245 | static int tp_suspend(struct platform_device *dev, pm_message_t state) 1246 | { 1247 | int restore = (state.event == PM_EVENT_HIBERNATE || 1248 | state.event == PM_EVENT_FREEZE); 1249 | if (!restore || get_real_thresh(0, THRESH_STOP , &saved_threshs[0])) 1250 | saved_threshs[0] = -1; 1251 | if (!restore || get_real_thresh(0, THRESH_START, &saved_threshs[1])) 1252 | saved_threshs[1] = -1; 1253 | if (!restore || get_real_thresh(1, THRESH_STOP , &saved_threshs[2])) 1254 | saved_threshs[2] = -1; 1255 | if (!restore || get_real_thresh(1, THRESH_START, &saved_threshs[3])) 1256 | saved_threshs[3] = -1; 1257 | DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0], 1258 | saved_threshs[1], saved_threshs[2], saved_threshs[3]); 1259 | return 0; 1260 | } 1261 | 1262 | static int tp_resume(struct platform_device *dev) 1263 | { 1264 | DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0], 1265 | saved_threshs[1], saved_threshs[2], saved_threshs[3]); 1266 | if (saved_threshs[0] >= 0) 1267 | set_real_thresh(0, THRESH_STOP , saved_threshs[0]); 1268 | if (saved_threshs[1] >= 0) 1269 | set_real_thresh(0, THRESH_START, saved_threshs[1]); 1270 | if (saved_threshs[2] >= 0) 1271 | set_real_thresh(1, THRESH_STOP , saved_threshs[2]); 1272 | if (saved_threshs[3] >= 0) 1273 | set_real_thresh(1, THRESH_START, saved_threshs[3]); 1274 | return 0; 1275 | } 1276 | 1277 | 1278 | /********************************************************************* 1279 | * Driver model 1280 | */ 1281 | 1282 | static struct platform_driver tp_driver = { 1283 | .suspend = tp_suspend, 1284 | .resume = tp_resume, 1285 | .driver = { 1286 | .name = "smapi", 1287 | .owner = THIS_MODULE 1288 | }, 1289 | }; 1290 | 1291 | 1292 | /********************************************************************* 1293 | * Sysfs device model 1294 | */ 1295 | 1296 | /* Attributes in /sys/devices/platform/smapi/ */ 1297 | 1298 | static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL); 1299 | static DEVICE_ATTR(smapi_request, 0600, show_smapi_request, 1300 | store_smapi_request); 1301 | 1302 | static struct attribute *tp_root_attributes[] = { 1303 | &dev_attr_ac_connected.attr, 1304 | &dev_attr_smapi_request.attr, 1305 | NULL 1306 | }; 1307 | static struct attribute_group tp_root_attribute_group = { 1308 | .attrs = tp_root_attributes 1309 | }; 1310 | 1311 | /* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ : 1312 | * Every attribute needs to be defined (i.e., statically allocated) for 1313 | * each battery, and then referenced in the attribute list of each battery. 1314 | * We use preprocessor voodoo to avoid duplicating the list of attributes 4 1315 | * times. The preprocessor output is just normal sysfs attributes code. 1316 | */ 1317 | 1318 | /** 1319 | * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes 1320 | * @_BAT: battery number (0 or 1) 1321 | * @_ATTR_RW: macro to invoke for each read/write attribute 1322 | * @_ATTR_R: macro to invoke for each read-only attribute 1323 | */ 1324 | #define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \ 1325 | _ATTR_RW(_BAT, start_charge_thresh) \ 1326 | _ATTR_RW(_BAT, stop_charge_thresh) \ 1327 | _ATTR_RW(_BAT, inhibit_charge_minutes) \ 1328 | _ATTR_RW(_BAT, force_discharge) \ 1329 | _ATTR_R(_BAT, installed) \ 1330 | _ATTR_R(_BAT, state) \ 1331 | _ATTR_R(_BAT, manufacturer) \ 1332 | _ATTR_R(_BAT, model) \ 1333 | _ATTR_R(_BAT, barcoding) \ 1334 | _ATTR_R(_BAT, chemistry) \ 1335 | _ATTR_R(_BAT, voltage) \ 1336 | _ATTR_R(_BAT, group0_voltage) \ 1337 | _ATTR_R(_BAT, group1_voltage) \ 1338 | _ATTR_R(_BAT, group2_voltage) \ 1339 | _ATTR_R(_BAT, group3_voltage) \ 1340 | _ATTR_R(_BAT, current_now) \ 1341 | _ATTR_R(_BAT, current_avg) \ 1342 | _ATTR_R(_BAT, charging_max_current) \ 1343 | _ATTR_R(_BAT, power_now) \ 1344 | _ATTR_R(_BAT, power_avg) \ 1345 | _ATTR_R(_BAT, remaining_percent) \ 1346 | _ATTR_R(_BAT, remaining_percent_error) \ 1347 | _ATTR_R(_BAT, remaining_charging_time) \ 1348 | _ATTR_R(_BAT, remaining_running_time) \ 1349 | _ATTR_R(_BAT, remaining_running_time_now) \ 1350 | _ATTR_R(_BAT, remaining_capacity) \ 1351 | _ATTR_R(_BAT, last_full_capacity) \ 1352 | _ATTR_R(_BAT, design_voltage) \ 1353 | _ATTR_R(_BAT, charging_max_voltage) \ 1354 | _ATTR_R(_BAT, design_capacity) \ 1355 | _ATTR_R(_BAT, cycle_count) \ 1356 | _ATTR_R(_BAT, temperature) \ 1357 | _ATTR_R(_BAT, serial) \ 1358 | _ATTR_R(_BAT, manufacture_date) \ 1359 | _ATTR_R(_BAT, first_use_date) \ 1360 | _ATTR_R(_BAT, dump) 1361 | 1362 | /* Define several macros we will feed into FOREACH_BAT_ATTR: */ 1363 | 1364 | #define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \ 1365 | static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ 1366 | .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, \ 1367 | store_battery_##_NAME), \ 1368 | .bat = _BAT \ 1369 | }; 1370 | 1371 | #define DEFINE_BAT_ATTR_R(_BAT,_NAME) \ 1372 | static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ 1373 | .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \ 1374 | .bat = _BAT \ 1375 | }; 1376 | 1377 | #define REF_BAT_ATTR(_BAT,_NAME) \ 1378 | &dev_attr_##_NAME##_##_BAT.dev_attr.attr, 1379 | 1380 | /* This provide all attributes for one battery: */ 1381 | 1382 | #define PROVIDE_BAT_ATTRS(_BAT) \ 1383 | FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \ 1384 | static struct attribute *tp_bat##_BAT##_attributes[] = { \ 1385 | FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \ 1386 | NULL \ 1387 | }; \ 1388 | static struct attribute_group tp_bat##_BAT##_attribute_group = { \ 1389 | .name = "BAT" #_BAT, \ 1390 | .attrs = tp_bat##_BAT##_attributes \ 1391 | }; 1392 | 1393 | /* Finally genereate the attributes: */ 1394 | 1395 | PROVIDE_BAT_ATTRS(0) 1396 | PROVIDE_BAT_ATTRS(1) 1397 | 1398 | /* List of attribute groups */ 1399 | 1400 | static struct attribute_group *attr_groups[] = { 1401 | &tp_root_attribute_group, 1402 | &tp_bat0_attribute_group, 1403 | &tp_bat1_attribute_group, 1404 | NULL 1405 | }; 1406 | 1407 | 1408 | /********************************************************************* 1409 | * Init and cleanup 1410 | */ 1411 | 1412 | static struct attribute_group **next_attr_group; /* next to register */ 1413 | 1414 | static int __init tp_init(void) 1415 | { 1416 | int ret; 1417 | printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n"); 1418 | 1419 | ret = find_smapi_port(); 1420 | if (ret < 0) 1421 | goto err; 1422 | else 1423 | smapi_port = ret; 1424 | 1425 | if (!request_region(smapi_port, 1, "smapi")) { 1426 | printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", 1427 | smapi_port); 1428 | ret = -ENXIO; 1429 | goto err; 1430 | } 1431 | 1432 | if (!request_region(SMAPI_PORT2, 1, "smapi")) { 1433 | printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", 1434 | SMAPI_PORT2); 1435 | ret = -ENXIO; 1436 | goto err_port1; 1437 | } 1438 | 1439 | ret = platform_driver_register(&tp_driver); 1440 | if (ret) 1441 | goto err_port2; 1442 | 1443 | pdev = platform_device_alloc("smapi", -1); 1444 | if (!pdev) { 1445 | ret = -ENOMEM; 1446 | goto err_driver; 1447 | } 1448 | 1449 | ret = platform_device_add(pdev); 1450 | if (ret) 1451 | goto err_device_free; 1452 | 1453 | for (next_attr_group = attr_groups; *next_attr_group; 1454 | ++next_attr_group) { 1455 | ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group); 1456 | if (ret) 1457 | goto err_attr; 1458 | } 1459 | 1460 | printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n", 1461 | smapi_port); 1462 | return 0; 1463 | 1464 | err_attr: 1465 | while (--next_attr_group >= attr_groups) 1466 | sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); 1467 | platform_device_unregister(pdev); 1468 | err_device_free: 1469 | platform_device_put(pdev); 1470 | err_driver: 1471 | platform_driver_unregister(&tp_driver); 1472 | err_port2: 1473 | release_region(SMAPI_PORT2, 1); 1474 | err_port1: 1475 | release_region(smapi_port, 1); 1476 | err: 1477 | printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret); 1478 | return ret; 1479 | } 1480 | 1481 | static void __exit tp_exit(void) 1482 | { 1483 | while (next_attr_group && --next_attr_group >= attr_groups) 1484 | sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); 1485 | platform_device_unregister(pdev); 1486 | platform_driver_unregister(&tp_driver); 1487 | release_region(SMAPI_PORT2, 1); 1488 | if (smapi_port) 1489 | release_region(smapi_port, 1); 1490 | 1491 | printk(KERN_INFO "tp_smapi unloaded.\n"); 1492 | } 1493 | 1494 | module_init(tp_init); 1495 | module_exit(tp_exit); 1496 | -------------------------------------------------------------------------------- /tp_smapi.spec: -------------------------------------------------------------------------------- 1 | %define module tp_smapi 2 | 3 | Name: %{module} 4 | Version: 0.44 5 | Release: 1%{?dist} 6 | Summary: IBM ThinkPad hardware functions driver - DKMS version 7 | License: GPLv2 8 | Source0: %{module}-%{version}.tgz 9 | 10 | Requires: dkms >= 1.00 11 | 12 | BuildArch: noarch 13 | 14 | 15 | %description 16 | The package contains kernel driver for ThinkPad SMAPI (System 17 | Management Application Program Interface). The driver is built using 18 | DKMS. 19 | 20 | %prep 21 | %setup -q 22 | 23 | 24 | %install 25 | mkdir -p %{buildroot}%{_usrsrc}/%{module}-%{version}/ 26 | cp -rf * %{buildroot}%{_usrsrc}/%{module}-%{version} 27 | 28 | %files 29 | %doc README TODO 30 | %{_usrsrc}/%{module}-%{version}/ 31 | 32 | %post 33 | dkms add -m %{module} -v %{version} --rpm_safe_upgrade 34 | dkms build -m %{module} -v %{version} 35 | dkms install -m %{module} -v %{version} 36 | 37 | %preun 38 | dkms remove -m %{module} -v %{version} --all --rpm_safe_upgrade 39 | 40 | %changelog 41 | --------------------------------------------------------------------------------