Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Smar:thinkpad
tp_smapi-kmp
_service:obs_scm:tp_smapi-0.43.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:tp_smapi-0.43.obscpio of Package tp_smapi-kmp
07070100000000000081A40000000200000002000000015A918BF700006360000000000000000000000000000000000000001600000000tp_smapi-0.43/CHANGESChange history for the tp_smapi package: 0.42 2016-04-23 --------------------- - fixes for newer kernels - kbuild support - dkms support - quirks update - RPM support 0.41 2011-07-29 --------------------- - new maintainer - fixes for newer kernels - fixes for newer ThinkPads (esp. SandyBridge ones) 0.40 2008-12-16 --------------------- - thinkpad_ec: Added a "force_io=1" module parameter, to let it load on recent ThinkPad models (e.g., T400 and T500). This is a kludge to work around an ACPI DSDT which claims the ports we need. - tp_smapi: Don't restore battery charging thresholds when resuming from suspend-to-RAM. - When building, use /lib/modules/*/build instead of /lib/modules/*/src and give a clearer error message if that's missing. 0.39 2008-09-27 --------------------- - Fixed compilation on kernels <2.6.26 (thanks to Evgeni Golov and Whoopie!) 0.38 2008-09-26 --------------------- - Fixed compilation on kernel 2.6.27-rc7. - tp_smapi: Added a new battery attribute /sys/devices/platform/smapi/BAT?/remaining_percent_error This is the error margin for the remaing_percent attribute. Unfortunately, it doesn't seem to be reset by battery calibration. - hdaps: Removed the fake_data_mode attribute. 0.37 2008-03-28 --------------------- - Fix compilation on kernel 2.6.25 - hdaps whitelist: changed default orientation of ThinkPad R60, R61, X40, X41. - tp_smapi: Added 3 new read-only battery attributes /sys/devices/platform/smapi/BAT?/remaining_running_time_now (time remaining according to instantenous power instead of average) /sys/devices/platform/smapi/BAT?/charging_max_current (maximum current allowed during charging) /sys/devices/platform/smapi/BAT?/charging_max_voltage (maximum voltage allowed during charging) - tp_smapi and README: improved documentation of battery info attributes. 0.36 2008-01-27 --------------------- - tp_smapi: Added a 4th cell group voltage attribute: /sys/devices/platform/smapi/BAT0/group3_voltage - tp_smapi: Reversed order of the group{0,1,2}_voltage attributes. - hdaps whitelist: changed default orientation of ThinkPad X61. - README: Improve attributes documentation. - Sourcecode cleanups. 0.35-test1 2008-01-21 --------------------- - Allow loading on new T61 firmware that caused an "hdaps_check_ec failed error. Correct operation with this firmware is not yet fully tested. - tp_smapi: Added 3 new sysfs attributes: /sys/devices/platform/smapi/BAT0/group0_voltage /sys/devices/platform/smapi/BAT0/group1_voltage /sys/devices/platform/smapi/BAT0/group2_voltage ThinkPad batteries (at least on the 600, X3x, T4x and R5x models) contains 3 cell groups in series, where each group consisting of 2 or 3 cells connected in parallel. The voltage of each group is given by these attributes. (The effective performance of the battery is determined by the weakest group, i.e., the one those voltage changes most rapidly during dis/charging.) - hdaps whitelist: changed default orientation of ThinkPad X60 Tablet, X61 Tablet and ThinkPad X60s and ThinkPd T60. - hdaps: when hdaps_ec_check fails, report the values received from the EC. 0.34 2008-01-06 --------------------- - hdaps: Fixed handling of "invert" module parameter (make it a uint, and don't let whitelist override module parameter). Contributed by Ostap Cherkashin. - hdaps whitelist: change ThinkPad X61s and T61 default orientation. X60 Tablet and X61 Tablet remain wrong until someone reports their DMI ID. 0.33 2007-12-22 --------------------- - Ported to kernel 2.6.24-rc6. Building on older kernels will cause harmless warnings about pointer types. (Makefile patch contributed by Tim Niemeyer and Whoopie.) - README: Document the additional hdaps attributes and the reduced timer interrupts introduced on 0.32. - hdaps: Extends the "invert" parameter to cover all possible axis orientations. The possible values are as follows. Let X,Y denote the hardware readouts. Let R denote the laptop's roll (tilt left/right). Let P denote the laptop's pitch (tilt forward/backward). invert=0: R= X P= Y (same as mainline) invert=1: R=-X P=-Y (same as mainline) invert=2: R=-X P= Y (new) invert=3: R= X P=-Y (new) invert=4: R= Y P= X (new) invert=5: R=-Y P=-X (new) invert=6: R=-Y P= X (new) invert=7: R= Y P=-X (new) It's probably easiest to just try all 8 possibilities and see which yields correct results (e.g., in the hdaps-gl visualisation). - hdaps: Adds a whitelist which automatically sets the correct axis orientation for some models. If the value for your model is wrong or missing, you can override it using the "invert" parameter. Please also update the tables at http://www.thinkwiki.org/wiki/tp_smapi and http://www.thinkwiki.org/wiki/List_of_DMI_IDs and submit a patch for the whitelist in hdaps.c. - tp_smapi: Report EOPNOTSUPP instead of ENOSYS for unimplemented SMAPI functions. 0.32 2007-07-28 --------------------- - hdaps: Added a second input device which publishes the raw sensor position, without joystick fuzz. In conjunction with a new hdapsd, this lets us avoid the frequent redundant interrupts caused by hdapsd. - hdaps: Stop polling the hardware when no one is listening. This prevents unnecessary interrupts on tickless kernel. (Contributed by Michael Riepe.) - In the patch generated by "make patch", thinkpad_ec and tp_smapi now reside under drivers/misc instead of drivers/platform. - "make patch" fixed for kernel 2.6.22. - Removed kludge for kernels lacking the DMI OEM String decode patch. - Now requires kernel >= 2.6.19 (due to above). - Removed the useless "enable_pci_power_saving_on_boot" sysfs attribute. 0.31 2007-03-07 --------------------- Visible changes: - Fix #includes to allow compilation on 64-bit platforms (patch by Emil Renner Berthing). - thinkpad_ec: Avoid spurious "bad end STR3: (...)->0x80" warnings by relaxing status check (still OK according to H8 EC docs). - Invert axes on all T60, not just T60p. Internal changes: - Add __init to find_smapi_port() and thinkpad_ec_test() - tp_smapi.c: Fix "initialization from incompatible pointer type" warnings on 64-bit platforms 0.30 2006-09-03 --------------------- Visible changes: - Makefile: now supports kernels built with separate source and build directories (thanks to Lenz Grimmer). To work against against some kernel installed under /lib/modules/ add an appropriate KVER= parameter: # make patch KVER=2.6.16-rc2 If it's not installed you'll need to set KSRC and KBUILD too: # make patch KVER=2.6.16-rc2 KSRC=$HOME/2.6.16-rc2 KBUILD=$HOME/2.6.16-rc2 - Changed the format of /sys/devices/platform/smapi/smapi_request (no more register names). Internal changes: - tp_smapi: the 'dump' sysfs attribute now outputs only rows 0x00 through 0x0a, since 0x0b causes an EC hang on some firmware (e.g., all T42 and old T43). Thanks to Henrique and Sukant for tracking this down! - thinkpad_ec: Log a warning if 0x161F returns 0x80. - thinkpad_ec: Increase TPC_REQUEST_RETRIES - thinkpad_ec: Report row args upon data read error. - hdaps: Apply hwmon-hdaps-handle-errors-from-input-register-device.patch from -mm. 0.29 2006-08-17 --------------------- Visible changes: - Added new sysfs attributes to tp_smapi: /sys/devices/platform/smapi/BAT0/remaining_percent /sys/devices/platform/smapi/BAT0/remaining_running_time /sys/devices/platform/smapi/BAT0/remaining_charging_time /sys/devices/platform/smapi/BAT0/temperature # milli-Celsius All of these are read directly from the embedded controller, so they are more accurate than doing your own computation. Internal changes: - tp_smapi: Extended and commented battery attribute show functions. 0.28 2006-08-16 --------------------- Visible changes: - thinkpad_ec: Removed 'debug' module parameter, it no longer affected much. - tp_smapi: Remove optical drive speed control (formerly #ifdefed out). Internal changes: - hdaps: Removed __init from hdaps_check_ec(). This fixes crashes on resume (Thanks, Igor!). - hdaps: Moved axis transformation to dedicated function transform_axes(). - tp_smapi: Cleaned up printk() output, changed macros. - tp_smapi: Added "E" to SMAPI parameter names (BX->EBX) - they're 32-bit. - tp_Smapi: Removed verbose printks()s on bad sysfs args. - A lot of small cleanups. 0.27 2006-08-05 --------------------- Visible changes: - Renamed tp_base.* to thinkpad_ec.*. NOTE: If you have manual modprobe commands or configuration mentioning tp_base, you'll need to change them. - hdaps: All HDAPS-equippedThinkPads should be now be automatically recognized. Whitelisting is needed only for changing the axis configuration. - hdaps: Removed 'force' module parameter, it's no longer needed. Internal changes: - Renamed all tp_controller_* functions to thinkpad_ec_*. - Small change to invalidation in hdaps_device_init(). - Separate Kconfig changes into separate patches. - Clean up generated patch format a bit. 0.26 2006-08-04 --------------------- - The modified hdaps.c is now included as a full file (based on linux git) instead of a patch, to solve compilation and versioning problems. - Makefile: Fixed a problem with "make patch" on new kernels. - tp_smapi.c: renamed internal attribute functions. 0.25 2006-08-03 --------------------- - Added new attribute: /sys/devices/platform/smapi/enable_pci_power_saving_on_boot This controls the "PCI bus power saving" option in the BIOS (takes effect at the next boot). - Added new attribute: /sys/devices/platform/smapi/smapi_request This performs raw SMAPI calls. It uses a bad interface that cannot handle multiple simultaneous accesses. Don't touch it, it's for development only. - "make patch" works again. If needed, the resulting patch will include the dmi-decode-and-save-oem-string-information patch. - "make load" now gives an informative error if ran as non-root. - Makefile: Give nicer error message on kernel <= 2.6.16. - Makefile: Verify that /usr/sbin/dmidecode works. 0.24-test3 2006-08-01 --------------------- - tp_base: fix compilation error on unpatched kernels. 0.24-test1 2006-07-31 --------------------- This is an experimental version with a few quirks necessary for testing new code. It's OK to run it yourself to try the new features, but it's not suitable for packaging in distributions. The stable version remains 0.22. Changes to loading, whitelisting and building: - Detection of the embedded controller (needed for all our modules) is now done according to OEM Strings in the DMI information. If the EC is not mentioned there (you can check it in 'dmidecode' too) you'll get a "tp_base: no ThinkPad embedded controller!" message in dmesg and the modules won't load. If tp_smapi used to work and was broken by this, please report. A few known exceptions are already - The above wants the following kernel patch (soon to be included in -mm): diff/dmi-decode-and-save-oem-string-information.patch If this patch is not already applied to the current kernel, the Makefile and tp_base.c default to an ugly kludge where the DMI information is hardcoded into your driver instead of being read in runtime. You'll get a warning about it, but it will work nicely. Just don't run the compiled modules on a different machine. Sorry for the mess, but we need to exercise the DMI check code. - "make patch" is temporarily broken, due to the above. - hdaps: Ignore the "initial mode latch" during init, we're not using it and Lenovo keeps adding new ones. Changes to hdaps functionality: - hdaps: New attribute /sys/devices/platform/hdaps/sampling_rate. This determines how frequently of accelerometer state refresh, independently of userspace polling (i.e., reading the position attribute more frequently than this will yield duplicate readouts). Default=50. - Multiple hdaps applications can now run without stealing each other's data. Set /sys/devices/platform/hdaps/sampling_rate to the highest rate required by any app. - hdaps: New attribute /sys/devices/platform/hdaps/oversampling_ratio. When set to X, the embedded controller is told to do physical accelerometer measurements at a rate that is X times higher than the rate at which the driver reads those measurements (i.e., X*sampling_rate). This reduces sample phase difference is, and useful for the running average filter (see next). Default=5. - hdaps: New attribute /sys/devices/platform/hdaps/running_avg_filter_order. When set to X, reported readouts will be the average of the last X physical accelerometer measurements. Current firmware allows 1<=X<=8. Setting to a high value decreases readout fluctuations. The averaging is handled by the embedded controller, so no CPU resources are used. Default=2 (this implicit in previous versions). - hdaps: New attribute /sys/devices/platform/hdaps/fake_data_mode (write only). If set to 1, enables a test mode where the physical accelerometer readouts are replaced with an incrementing counter. This is useful for checking the regularity of the sampling interval and driver<->userspace communication. Changes to tp_smapi functionality: - tp_smapi: Removed the units ("mV", "mWh" etc.) from all /sys readouts, to follow kernel convention. - tp_smapi: /sys/devices/platform/smapi/BAT?/dump now contains additional data lines (but we don't know what they mean). Default=0. - tp_smapi: /sys/devices/platform/smapi/BAT?/dump contains some unused values; they're now omitted (replaced by "--") instead of showing semi-random junk. Other changes: - tp_smapi: fixed signness bug in current_now and current_avg when discharging (thanks to Pavel Machek!). - hdaps: Turn off power and EC polling upon suspend or module unload. - hdaps: Check more EC function return values. - hdaps: Refactored some functions. - tp_base: Test EC on module load and refuse loading if EC not found. - tp_base, hdaps, tp_smapi: changed args passing and made it more concise. - tp_smapi: cleaned up attribute macros. - tp_smapi: cleaned up smapi retry loop. - tp_smapi: removed smapi_write and dropped outAX param from smapi_request. - tp_smapi: improved comments. - tp_base: moved comments from tp_base.h to tp_base.c. Many of the above changes were suggested or contributed by Henrique de Moraes Holschuh. 0.22 2006-07-20 ---------------- - hdaps: Added support for X60s (but X axis is still inverted). - tp_base: Wait until EC starts replying before considering a request successful (this improves stability and eliminates abnormal events.) - hdaps: Removed /sys/devices/platform/hdaps/{variance,temp2}. They actually show an older pending readout (if there is one), so it never makes sense to read them via the existing interface. - tp_base: Added many additional status and consistency checks on EC, to give early warning on any strange behavior. - tp_base: New constants and comments greatly clarify access to the EC. - hdaps: Convert all init code to use tp_base transactions. Mo more direct IO port access. - hdaps: Move some init code to new functions hdaps_set_power() and hdaps_set_ec_config(). These will be exposed as attributes in the future. - hdaps: If init failed, tell at which step it happened. - tp_base: Allow additional inputs and optional outputs to EC requests. - tp_base: Decrease timeout for reply to request. - Kconfig: Make TP_BASE an invisible tristate option - tp_base: Rename tp_controller_trylock() to tp_controller_try_lock(). - tp_base: make tp_controller_lock() return an int; have it checked in tp_smapi and hdaps. - tp_base: Rewrote all printk()s using macros, and made debug=1 quieter. - Makefile: work even if $SHELL is not bash. - Makefile: Autopatch hdaps patch to work with 2.6.18-rc1 Many of the above changes were suggested or contributed by Henrique de Moraes Holschuh. 0.21 2006-06-21 ---------------- - Compatibility with kernel 2.6.17. - hdaps: Fixed a locking bug. This further reduces (completely solves?) the EC hangs problem. (Thanks to Rasto, Andrew and Whoopie for helping track this down!) - hdaps: Support for ThinkPad T60: added 0x04 as valid initial latch value - hdaps: Mousedev poll timer was not restarted after resume from suspend. - tp_base: Added verbose debug output, enabled by the "debug=1" module parameter (and by "make load DEBUG=1") - tp_base: Cosmetic fix to tp_base DMI vendor reporting (patch by Whoopie). - Cleaned up some printk()s. - Makefile: "make load" now enables dmesg debug output only if given DEBUG=1. - Makefile: will not delete hdaps.c if edited manually after generation. 0.20 2006-04-30 ---------------- - Makefile: fix for "linux/tp_base.h: No such file or directory" problem. - tp_base: whitelist "LENOVO" vendor string too. - hdaps: add "force=1" parameter to force loading on models that are not whitelisted (patch by Whoopie). - hdaps: fix init on several models by using tp_controller_read_row() instead of direct port IO. This apparently also makes the initial latch value is meaningful even when the device is already initialized. - Now requires kernel 2.6.15 or newer (compatibility cruft removed). 0.19 2006-04-08 ---------------- - Added new battery status attributes: /sys/devices/platform/smapi/BAT?/first_use_date /sys/devices/platform/smapi/BAT?/manufacture_date - Bugfix in tp_base embedded controller readout (missing prefetch invalidation). This apparently solves remnants of the the embedded-controller-lockup problem. - "make load" now demands "HDAPS=1" if the hdaps module is loaded. - hdaps driver patch: - Remember keyboard/mouse activity for 0.1sec, so that all applications get a chance to read it (the hardware resets its activity flag when it's read). 0.18 2006-04-01 ---------------- - Solved embedded controller lockups with HDAPS=1 (see hdaps comments below). - Added new function tp_controller_try_read_row() to tp_base. - Added extra status checks in tp_base, to catch abnormal conditions earlier. - Restructured several functions in tp_base for clarity and reusability. - Restructured tp_smapi attribute registration code using fancy macros, to remove redundancy. - Reduced prefetch delay and maximum retries in tp_base. - Now locks tp_controller when making SMAPI call (just in case). - Makefile change to fix compilation in Debian. - Minor cleanups and added comments in tp_smapi.c. - Major changes to hdaps patch: - Whenever we read data from the controller, parse all of it and remember the values in global vars. - Simplified the device model *_show functions, via the above change. - If the mousedev poll timer handler experiences a transient fault, use use last saved data from the global vars. - Handle delayed calibration at first opportunity, not just mousedev polls. - Disable mousedev poll timer when suspending, to prevent readouts before the resume code re-initializes the sensor. - Don't accept initial status 0x00, it was probably an artifact of the premature mousepoll invocation. - When doing a sensor readout in mousedev poll, avoid time-consuming retries and fetches, to minimize time in softirq. This effectively exorcises an embedded controller lockup Heisenbug. There may still a nonzero probability of lockup, but now it's not worse than the vanilla hdaps (since they do essentially the same). 0.17 2006-02-10 ---------------- - Fixed off-by-one bugs in charge threshold handling + cleanup - Enforce bounds on threshold the same way Battery Maximizer does - Recognize more battry status values - Fixes in error reporting - Fixed a warning in tp_smapi.c - hdaps driver patches: - Informatively report init errors - Recognize 0x00 as a valid latch value - Increase init timeouts 0.16 2006-01-11 ---------------- - Renamed smapi/BAT?/force_discharge1 to smapi/BAT?/force_discharge - Removed smapi/BAT?/force_discharge2 (doesn't work on any model) - HDAPS patch changes: - Check ready status (instead of occasionally returning junk) also for variance, not just for position. - Calibrate unsynchronously (reduces load and resume time by 1-2 seconds) - Fix position readouts - read word, not byte. - Fixed driver lockup when writing to hdaps/variance. - Changes in diff handling. 0.15 2006-01-10 ---------------- - Bugfix: 0.14 had broken SM BIOS call code. 0.14 2006-01-09 ---------------- This version has no user-visible functionality changes, but improves the reliability of coordinating hardware access with the hdaps driver. - Moved controller access code to tp_base. - Changes in HDAPS driver patch: - Use tp_base for controller access instead of direct port IO. Using the "row read" and "prefetch" abstraction of tp_base makes the hdaps code shorter, clearer and safer. - Added checking of the STATUS port and automatic retries if device is not ready (previously it just returned junk values). - Added missing lock in hdaps_invert_store. - A few local code simplifications - Redue the retry delay (200ms was excessive). - Changed SMAPI calls to use 32-bit args. - Made "make patch" produce an LKML-compliant patches (include a diffstat, remove CD_SPEED, remove <2.6.15 #ifdefs). - Changes in Makefile and directory structure. 0.13 2005-12-21 ---------------- - First step toward resolving conflict with the hdaps module: - Added a tp_base module, which handles coordination of access to the ThinkPad controller (thanks to Alan Cox and Rovert Love). - Changed tp_smapi to require and use tp_base. - "make load HDAPS=1" and "make install HDAPS=1" will copy and patch hdaps.c for compatibility with tp_smapi and tp_base, and then load or install it. - Added a "make patch" target, which creates a stand-alone patch against the current kernel tree (thanks, Spiney!). - Future kernel compatibility: avoids platform_device_register_simple. 0.12 2005-12-16 ---------------- - Added smapi/BAT?/force_discharge1 and smapi/BAT?/force_discharge2. When set to 1, they stop forces discharging of battery even if on AC power. The two files have the same functionaliy but use different SMAPI calls to achieve it. On ThinkPad T43 only force_discharge1 works; the other one is completely untested. - Removed smapi/cd_speed (unless you set "#define PROVIDE_CD_SPEED"), since its function is provided in a safer way by the combination of "hdparm -E" (for CD) and speedcontrol (for DVD, http://safari.iki.fi/speedcontrol.c). 0.10 2005-12-13 ---------------- - Added smapi/BAT?/state (idle/charging/discharing). - Added smapi/BAT?/{power_now,power_avg}. - Renamed smapi/BAT?/{current1,current2} to {current_now,current_avg}. - If stop_charge_thresh is unsupported, when trying to set it don't affect start_charge_thresh. - smapi/BAT?/{design_capacity,last_full_capacity} were reversed. - smapi/cdrom_speed renamed to cd_speed, and safety mechanism added: you must use "echo 1 yes_crash_my_computer > /sys/devices/platform/smapi/cd_speed". - Added smapi/ac_connected added (actually in 0.09). 0.09 2005-12-12 ---------------- - Dual-battery support: moved all battery-related sysfs files to /sys/devices/platform/smapi/BAT0/* and made the 2nd battery accessible via /sys/devices/platform/smapi/BAT1/* - Added numerous read-only battery status files: /sys/devices/platform/smapi/BAT?/{installed,cycle_count,current1,current2, last_full_capacity,remaining_capacity,design_capacity,voltage, design_voltage,manufacturer,model,serial,barcoding,chemistry} These are incompatible with HDAPS - see README. - /sys/devices/platform/smapi/BAT?/dump gives the raw status dump. - Added "debug" module parameter, default (debug=0) reduces printk verbosity. For bug reports, please use "modprobe debug=1" (or just use "make load"). - Now requires kernel >= 2.6.13 (stick with v0.08 for earlier kernels). - Suspend+resume now correctly handles default thresholds (start==stop==0). - Extended whitelist to cover all ThinkPads. - Cleanup of init/probing code. - Reduced kernel logging level - Fix in set_inhibit_charge 0.08 2005-12-09 ---------------- - Fixes in README and dmesg outputs. 0.07 2005-12-07 ---------------- - Added /sys/devices/platform/smapi/cdrom_speed to set/get the CD drive speed level (0=slow, 1=medium, 2=fast). WARNING: Writing to this file when the CD is being accessed will hang your computer. - Fixed some dmesg outputs 0.06 2005-12-06 ---------------- - /sys/devices/platform/smapi/inhibit_charge renamed to /sys/devices/platform/smapi/inhibit_charge_minutes and now accepts the number of minutes to inhibit charging. - Compatibility with kernel 2.6.12 (thanks to Guenther Starnberger) 0.05 2005-12-05 ---------------- - Kernel 2.6.15 compatibility (thanks to Volker Gropp) - Improved error reporting - Cleared confusing dmesg output 0.04 2005-12-05 ---------------- - Made start_charge_thresh work even with stop_start_thresh is not available. 0.03 2005-12-05 ---------------- - Added /sys/devices/platform/smapi/inhibit_charge - Fixed #includes - Kernel 2.6.15 compatibility - Added versionless IBM machines to DMI whitelist - Added retries to SMAPI request code - Added list of supported models to README 0.02 2005-12-05 ---------------- - improved SMAPI request code and error reporting 0.01 2005-12-04 ---------------- - initial release 07070100000001000081A40000000200000002000000015A918BF7000017D0000000000000000000000000000000000000001700000000tp_smapi-0.43/Makefileifndef TP_MODULES # This part runs as a normal, top-level Makefile: X:=$(shell false) KVER ?= $(shell uname -r) KBASE ?= /lib/modules/$(KVER) KSRC ?= $(KBASE)/source KBUILD ?= $(KBASE)/build MOD_DIR ?= $(KBASE)/kernel PWD := $(shell pwd) IDIR := include/linux TP_DIR := drivers/platform/x86 TP_MODULES := thinkpad_ec.o tp_smapi.o SHELL := /bin/bash ifeq ($(HDAPS),1) TP_MODULES += hdaps.o LOAD_HDAPS := insmod ./hdaps.ko else LOAD_HDAPS := : endif ifeq ($(FORCE_IO),1) THINKPAD_EC_PARAM := force_io=1 else THINKPAD_EC_PARAM := endif ifneq ($(KERNELRELEASE),) obj-m := $(TP_MODULES) else endif DEBUG := 0 .PHONY: default clean modules load unload install patch check_hdaps mk-hdaps.diff \ check-ver set-version create-tgz create-rpm export TP_MODULES ##################################################################### # Main targets default: modules # Build the modules thinkpad_ec.ko, tp_smapi.ko and (if HDAPS=1) hdaps.ko modules: $(KBUILD) $(patsubst %.o,%.c,$(TP_MODULES)) $(MAKE) -C $(KBUILD) M=$(PWD) O=$(KBUILD) modules clean: rm -f tp_smapi.mod.* tp_smapi.o tp_smapi.ko .tp_smapi.*.cmd rm -f thinkpad_ec.mod.* thinkpad_ec.o thinkpad_ec.ko .thinkpad_ec.*.cmd rm -f hdaps.mod.* hdaps.o hdaps.ko .hdaps.*.cmd rm -f *~ diff/*~ *.orig diff/*.orig *.rej diff/*.rej rm -f tp_smapi-*-for-*.patch rm -fr .tmp_versions Modules.symvers diff/hdaps.diff.tmp load: check_hdaps unload modules @( [ `id -u` == 0 ] || { echo "Must be root to load modules"; exit 1; } ) { insmod ./thinkpad_ec.ko $(THINKPAD_EC_PARAM) && insmod ./tp_smapi.ko debug=$(DEBUG) && $(LOAD_HDAPS); }; : @echo -e '\nRecent dmesg output:' ; dmesg | tail -10 unload: @( [ `id -u` == 0 ] || { echo "Must be root to unload modules"; exit 1; } ) if lsmod | grep -q '^hdaps '; then rmmod hdaps; fi if lsmod | grep -q '^tp_smapi '; then rmmod tp_smapi; fi if lsmod | grep -q '^thinkpad_ec '; then rmmod thinkpad_ec; fi if lsmod | grep -q '^tp_base '; then rmmod tp_base; fi # old thinkpad_ec check_hdaps: ifneq ($(HDAPS),1) @if lsmod | grep -q '^hdaps '; then \ echo 'The hdaps driver is loaded. Use "make HDAPS=1 ..." to'\ 'patch hdaps for compatibility with tp_smapi.'\ 'This requires a kernel source tree.'; exit 1; fi endif install: modules @( [ `id -u` == 0 ] || { echo "Must be root to install modules"; exit 1; } ) rm -f $(MOD_DIR)/$(TP_DIR)/{thinkpad_ec,tp_smapi,tp_base}.ko rm -f $(MOD_DIR)/drivers/firmware/{thinkpad_ec,tp_smapi,tp_base}.ko rm -f $(MOD_DIR)/extra/{thinkpad_ec,tp_smapi,tp_base}.ko ifeq ($(HDAPS),1) rm -f $(MOD_DIR)/drivers/platform/x86/hdaps.ko rm -f $(MOD_DIR)/extra/hdaps.ko endif $(MAKE) -C $(KBUILD) M=$(PWD) O=$(KBUILD) modules_install depmod $(KVER) ##################################################################### # Generate a stand-alone kernel patch TP_VER := 0.43 ORG := a NEW := b PATCH := tp_smapi-$(TP_VER)-for-$(KVER).patch BASE_IN_PATCH := 1 SMAPI_IN_PATCH := 1 HDAPS_IN_PATCH := 1 patch: $(KSRC) @TMPDIR=`mktemp -d /tmp/tp_smapi-patch.XXXXXX` &&\ echo "Working directory: $$TMPDIR" &&\ cd $$TMPDIR &&\ mkdir -p $(ORG)/$(TP_DIR) &&\ mkdir -p $(ORG)/$(IDIR) &&\ mkdir -p $(ORG)/drivers/platform/x86 &&\ cp $(KSRC)/$(TP_DIR)/{Kconfig,Makefile} $(ORG)/$(TP_DIR) &&\ cp $(KSRC)/drivers/platform/x86/{Kconfig,hdaps.c} $(ORG)/drivers/platform/x86/ &&\ cp -r $(ORG) $(NEW) &&\ \ if [ "$(BASE_IN_PATCH)" == 1 ]; then \ cp $(PWD)/thinkpad_ec.c $(NEW)/$(TP_DIR)/thinkpad_ec.c &&\ cp $(PWD)/thinkpad_ec.h $(NEW)/$(TP_DIR)/thinkpad_ec.h &&\ perl -i -pe 'print `cat $(PWD)/diff/Kconfig-thinkpad_ec.add` if m/^(endmenu|endif # X86_PLATFORM_DEVICES)$$/' $(NEW)/$(TP_DIR)/Kconfig &&\ sed -i -e '$$aobj-$$(CONFIG_THINKPAD_EC) += thinkpad_ec.o' $(NEW)/$(TP_DIR)/Makefile \ ; fi &&\ \ if [ "$(HDAPS_IN_PATCH)" == 1 ]; then \ cp $(PWD)/hdaps.c $(NEW)/drivers/platform/x86/ &&\ perl -i -0777 -pe 's/(config SENSORS_HDAPS\n\ttristate [^\n]+\n\tdepends [^\n]+\n)/$$1\tselect THINKPAD_EC\n/' $(NEW)/drivers/platform/x86/Kconfig \ ; fi &&\ \ if [ "$(SMAPI_IN_PATCH)" == 1 ]; then \ sed -i -e '$$aobj-$$(CONFIG_TP_SMAPI) += tp_smapi.o' $(NEW)/$(TP_DIR)/Makefile &&\ perl -i -pe 'print `cat $(PWD)/diff/Kconfig-tp_smapi.add` if m/^(endmenu|endif # X86_PLATFORM_DEVICES)$$/' $(NEW)/$(TP_DIR)/Kconfig &&\ cp $(PWD)/tp_smapi.c $(NEW)/$(TP_DIR)/tp_smapi.c &&\ mkdir -p $(NEW)/Documentation &&\ perl -0777 -pe 's/\n(Installation\n---+|Conflict with HDAPS\n---+|Files in this package\n---+|Setting and getting CD-ROM speed:\n).*?\n(?=[^\n]*\n-----)/\n/gs' $(PWD)/README > $(NEW)/Documentation/tp_smapi.txt \ ; fi &&\ \ { diff -dNurp $(ORG) $(NEW) > patch \ || [ $$? -lt 2 ]; } &&\ { echo "Generated for $(KVER) in $(KSRC)"; echo; diffstat patch; echo; echo; cat patch; } \ > $(PWD)/${PATCH} &&\ rm -r $$TMPDIR @echo @diffstat ${PATCH} @echo -e "\nPatch file created:\n ${PATCH}" @echo -e "To apply, use:\n patch -p1 -d ${KSRC} < ${PATCH}" ##################################################################### # Tools for preparing a release. Ignore these. TGZ=../tp_smapi-$(VER).tgz check-ver: @if [ -z "$(VER)" ]; then \ echo "VER is unset"; \ echo "run: $(MAKE) $(MAKECMDGOALS) VER=<release version>"; \ exit 1 ;\ fi set-version: check-ver perl -i -pe 's/^(tp_smapi version ).*/$${1}$(VER)/' README perl -i -pe 's/^(#define TP_VERSION ").*/$${1}$(VER)"/' thinkpad_ec.c tp_smapi.c perl -i -pe 's/^(TP_VER := ).*/$${1}$(VER)/' Makefile perl -i -pe 's/^(PACKAGE_VERSION=").*/$${1}$(VER)"/' dkms.conf perl -i -pe 's/^(%define version ).*/$${1}$(VER)/' tp_smapi.spec create-tgz: check-ver git archive --format=tar --prefix=tp_smapi-$(VER)/ HEAD | gzip -c > $(TGZ) tar tzvf $(TGZ) echo "Ready: $(TGZ)" create-rpm: create-tgz mkdir -p rpmbuild rpmbuild -tb --define "_topdir $$PWD/rpmbuild" $(TGZ) else ##################################################################### # This part runs as a submake in kernel Makefile context: EXTRA_CFLAGS := $(CFLAGS) -I$(M)/include obj-m := $(TP_MODULES) endif 07070100000002000081A40000000200000002000000015A918BF700003CCA000000000000000000000000000000000000001500000000tp_smapi-0.43/READMEtp_smapi version 0.43 IBM ThinkPad hardware functions driver Author: Shem Multinymous <multinymous@gmail.com> Project: http://sourceforge.net/projects/tpctl Wiki: http://thinkwiki.org/wiki/tp_smapi List: linux-thinkpad@linux-thinkpad.org (http://mailman.linux-thinkpad.org/mailman/listinfo/linux-thinkpad) Description ----------- ThinkPad laptops include a proprietary interface called SMAPI BIOS (System Management Application Program Interface) which provides some hardware control functionality that is not accessible by other means. This driver exposes some features of the SMAPI BIOS through a sysfs interface. It is suitable for newer models, on which SMAPI is invoked through IO port writes. Older models use a different SMAPI interface; for those, try the "thinkpad" module from the "tpctl" package. WARNING: This driver uses undocumented features and direct hardware access. It thus cannot be guaranteed to work, and may cause arbitrary damage (especially on models it wasn't tested on). Installation ------------ For testing, you can simply compile and load the driver within the current working directory: # make load To compile and install into the kernel's module path: # make install If you use the HDAPS driver, use these instead to replace the hdaps module with one patched for compatibility with tp_smapi: # make load HDAPS=1 or # make install HDAPS=1 With some recent ThinkPad models, the "thinkpad_ec" will refuse to load due to reserved ports. You can force loading by passing the "force_io=1" parameter to the "thinkpad_ec" module using /etc/modules.conf (or your distribution's equivalent). To test this prior to installation, use: # make load HDAPS=1 FORCE_IO=1 To prepare a stand-alone patch against the current kernel tree (including the hdaps replacement and Kconfig entries), assuming you have its sourcecode under /lib/modules/`uname -r`/source: # make patch To work against a kernel (other than the current one) that's properly installed under /lib/modules/*, add an appropriate KVER= parameter # make patch KVER=2.6.16 To work against another kernel, you'll need to set KSRC and KBUILD too: # make patch KVER=2.6.16 KSRC=$HOME/2.6.16 KBUILD=$HOME/2.6.16 To delete all autogenerated files: # make clean Append "DEBUG=1" to "make load" to load tp_smapi with debug=1. The original kernel tree is never modified by any these commands. The /lib/modules directory is modified only by "make install". Module parameters ----------------- thinkpad_ec module: force_io=1 lets thinkpad_ec load on some recent ThinkPad models (e.g., T400 and T500) whose BIOS's ACPI DSDT reserves the ports we need. tp_smapi module: debug=1 enables verbose dmesg output. Usage ----- Control of battery charging thresholds (in percents of current full charge capacity): # echo 40 > /sys/devices/platform/smapi/BAT0/start_charge_thresh # echo 70 > /sys/devices/platform/smapi/BAT0/stop_charge_thresh # cat /sys/devices/platform/smapi/BAT0/*_charge_thresh (This is useful since Li-Ion batteries wear out much faster at very high or low charge levels. The driver will also keeps the thresholds across suspend-to-disk with AC disconnected; this isn't done automatically by the hardware.) Inhibiting battery charging for 17 minutes (overrides thresholds): # echo 17 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes # echo 0 > /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes # stop # cat /sys/devices/platform/smapi/BAT0/inhibit_charge_minutes (This can be used to control which battery is charged when using an Ultrabay battery.) Forcing battery discharging even if AC power available: # echo 1 > /sys/devices/platform/smapi/BAT0/force_discharge # start discharge # echo 0 > /sys/devices/platform/smapi/BAT0/force_discharge # stop discharge # cat /sys/devices/platform/smapi/BAT0/force_discharge (When AC is connected, forced discharging will automatically stop when battery is fully depleted -- this is useful for calibration. Also, this attribute can be used to control which battery is discharged when both a system battery and an Ultrabay battery are connected.) Misc read-only battery status attributes (see note about HDAPS below): /sys/devices/platform/smapi/BAT0/installed # 0 or 1 /sys/devices/platform/smapi/BAT0/state # idle/charging/discharging /sys/devices/platform/smapi/BAT0/cycle_count # integer counter /sys/devices/platform/smapi/BAT0/current_now # instantaneous current /sys/devices/platform/smapi/BAT0/current_avg # last minute average /sys/devices/platform/smapi/BAT0/power_now # instantaneous power /sys/devices/platform/smapi/BAT0/power_avg # last minute average /sys/devices/platform/smapi/BAT0/last_full_capacity # in mWh /sys/devices/platform/smapi/BAT0/remaining_percent # remaining percent of energy (set by calibration) /sys/devices/platform/smapi/BAT0/remaining_percent_error # error range of remaing_percent (not reset by calibration) /sys/devices/platform/smapi/BAT0/remaining_running_time # in minutes, by last minute average power /sys/devices/platform/smapi/BAT0/remaining_running_time_now # in minutes, by instantenous power /sys/devices/platform/smapi/BAT0/remaining_charging_time # in minutes /sys/devices/platform/smapi/BAT0/remaining_capacity # in mWh /sys/devices/platform/smapi/BAT0/design_capacity # in mWh /sys/devices/platform/smapi/BAT0/voltage # in mV /sys/devices/platform/smapi/BAT0/design_voltage # in mV /sys/devices/platform/smapi/BAT0/charging_max_current # max charging current /sys/devices/platform/smapi/BAT0/charging_max_voltage # max charging voltage /sys/devices/platform/smapi/BAT0/group{0,1,2,3}_voltage # see below /sys/devices/platform/smapi/BAT0/manufacturer # string /sys/devices/platform/smapi/BAT0/model # string /sys/devices/platform/smapi/BAT0/barcoding # string /sys/devices/platform/smapi/BAT0/chemistry # string /sys/devices/platform/smapi/BAT0/serial # integer /sys/devices/platform/smapi/BAT0/manufacture_date # YYYY-MM-DD /sys/devices/platform/smapi/BAT0/first_use_date # YYYY-MM-DD /sys/devices/platform/smapi/BAT0/temperature # in milli-Celsius /sys/devices/platform/smapi/BAT0/dump # see below /sys/devices/platform/smapi/ac_connected # 0 or 1 The BAT0/group{0,1,2,3}_voltage attribute refers to the separate cell groups in each battery. For example, on the ThinkPad 600, X3x, T4x and R5x models, the battery contains 3 cell groups in series, where each group consisting of 2 or 3 cells connected in parallel. The voltage of each group is given by these attributes, and their sum (roughly) equals the "voltage" attribute. (The effective performance of the battery is determined by the weakest group, i.e., the one those voltage changes most rapidly during dis/charging.) The "BAT0/dump" attribute gives a a hex dump of the raw status data, which contains additional data now in the above (if you can figure it out). Some unused values are autodetected and replaced by "--": In all of the above, replace BAT0 with BAT1 to address the 2nd battery (e.g. in the UltraBay). Raw SMAPI calls: /sys/devices/platform/smapi/smapi_request This performs raw SMAPI calls. It uses a bad interface that cannot handle multiple simultaneous access. Don't touch it, it's for development only. If you did touch it, you would so something like # echo '211a 100 0 0' > /sys/devices/platform/smapi/smapi_request # cat /sys/devices/platform/smapi/smapi_request and notice that in the output "211a 34b b2 0 0 0 'OK'", the "4b" in the 2nd value, converted to decimal is 75: the current charge stop threshold. Model-specific status --------------------- Works (at least partially) on the following ThinkPad model: * A30 * G41 * R40, R50p, R51, R52 * T23, T40, T40p, T41, T41p, T42, T42p, T43, T43p, T60, T61, T400, T410, T420 (partially) * X24, X31, X32, X40, X41, X60, X61, X200, X201, X220 (partially) * Z60t, Z61m Does not work on: * X230 and newer * T430 and newer * Any ThinkPad Edge * Any ThinkPad Yoga * Any ThinkPad L series * Any ThinkPad P series Not all functions are available on all models; for detailed status, see: http://thinkwiki.org/wiki/tp_smapi Please report success/failure by e-mail or on the Wiki. If you get a "not implemented" or "not supported" message, your laptop probably just can't do that (at least not via the SMAPI BIOS). For negative reports, follow the bug reporting guidelines below. If you send me the necessary technical data (i.e., SMAPI function interfaces), I will support additional models. Conflict with HDAPS ------------------- The extended battery status function conflicts with the "hdaps" kernel module (they use the same IO ports). You can use HDAPS=1 (see Installation) to get a patched version of hdaps which is compatible with tp_smapi. Otherwise: If you load "hdaps" first, tp_smapi will disable these functions (and log a message in the kernel log). If you load "tp_smapi" first, "hdaps" will refuse to load. To switch between the two, "rmmod" both and then load one you need. Some of the battery status is also visible through ACPI (/proc/acpi/battery/). The charging control files (*_charge_thresh, inhibit_charge_minutes and force_discrage*) don't have this problem. Additional HDAPS features ------------------------- The modified hdaps driver has several improvements on the one in mainline (beyond resolving the conflict with thinkpad_ec and tp_smapi): - Fixes reliability and improves support for recent ThinkPad models (especially *60 and newer). Unlike the mainline driver, the modified hdaps correctly follows the Embedded Controller communication protocol. - Extends the "invert" parameter to cover all possible axis orientations. The possible values are as follows. Let X,Y denote the hardware readouts. Let R denote the laptop's roll (tilt left/right). Let P denote the laptop's pitch (tilt forward/backward). invert=0: R= X P= Y (same as mainline) invert=1: R=-X P=-Y (same as mainline) invert=2: R=-X P= Y (new) invert=3: R= X P=-Y (new) invert=4: R= Y P= X (new) invert=5: R=-Y P=-X (new) invert=6: R=-Y P= X (new) invert=7: R= Y P=-X (new) It's probably easiest to just try all 8 possibilities and see which yields correct results (e.g., in the hdaps-gl visualisation). - Adds a whitelist which automatically sets the correct axis orientation for some models. If the value for your model is wrong or missing, you can override it using the "invert" parameter. Please also update the tables at http://www.thinkwiki.org/wiki/tp_smapi and http://www.thinkwiki.org/wiki/List_of_DMI_IDs and submit a patch for the whitelist in hdaps.c. - Provides new attributes: /sys/devices/platform/hdaps/sampling_rate: This determines the frequency at which the host queries the embedded controller for accelerometer data (and informs the hdaps input devices). Default=50. /sys/devices/platform/hdaps/oversampling_ratio: When set to X, the embedded controller is told to do physical accelerometer measurements at a rate that is X times higher than the rate at which the driver reads those measurements (i.e., X*sampling_rate). This makes the readouts from the embedded controller more fresh, and is also useful for the running average filter (see next). Default=5 /sys/devices/platform/hdaps/running_avg_filter_order: When set to X, reported readouts will be the average of the last X physical accelerometer measurements. Current firmware allows 1<=X<=8. Setting to a high value decreases readout fluctuations. The averaging is handled by the embedded controller, so no CPU resources are used. Higher values make the readouts smoother, since it averages out both sensor noise (good) and abrupt changes (bad). Default=2. - Provides a second input device, which publishes the raw accelerometer measurements (without the fuzzing needed for joystick emulation). This input device can be matched by a udev rule such as the following (all on one line): KERNEL=="event[0-9]*", ATTRS{phys}=="hdaps/input1", ATTRS{modalias}=="input:b0019v1014p5054e4801-*", SYMLINK+="input/hdaps/accelerometer-event A new version of the hdapsd userspace daemon, which uses the input device interface instead of polling sysfs, is available seprately. Using this reduces the total interrupts per second generated by hdaps+hdapsd (on tickless kernels) to 50, down from a value that fluctuates between 50 and 100. Set the sampling_rate sysfs attribute to a lower value to further reduce interrupts, at the expense of response latency. Licensing note: all my changes to the HDAPS driver are licensed under the GPL version 2 or, at your option and to the extent allowed by derivation from prior works, any later version. My version of hdaps is derived work from the mainline version, which at the time of writing is available only under GPL version 2. Bug reporting ------------- Mail <multinymous@gmail.com>. Please include: * Details about your model, * Relevant "dmesg" output. Make sure thinkpad_ec and tp_smapi are loaded with the "debug=1" parameter (e.g., use "make load HDAPS=1 DEBUG=1"). * Output of "dmidecode | grep -C5 Product" * Does the failed functionality works under Windows? Files in this package --------------------- README This file. CHANGES Change log. TODO Pending improvements. Makefile Makefile (see "Installation" above). tp_smapi.c tp_smapi driver module (main code). thinkpad_ec.* thinkpad_ec driver module (coordinates hardware access between tp_smapi and hdaps) hdaps.c Modified version of hdaps.c driver from mainline kernel, patched to use thinkpad_ec and several other improvements. diff/* (excluding above) Used by "make patch" to create a clean stand-alone patch. More about SMAPI ---------------- For hints about what may be possible via the SMAPI BIOS and how, see: * IBM Technical Reference Manual for the ThinkPad 770 (http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD) * Exported symbols in PWRMGRIF.DLL or TPPWRW32.DLL (e.g., use "objdump -x"). * drivers/char/mwave/smapi.c in the Linux kernel tree.* * The "thinkpad" SMAPI module (http://tpctl.sourceforge.net). * The SMAPI_* constants in tp_smapi.c. Note that in the above Technical Reference and in the "thinkpad" module, SMAPI is invoked through a function call to some physical address. However, the interface used by tp_smapi and the above mwave drive, and apparently required by newer ThinkPad, is different: you set the parameters up in the CPU's registers and write to ports 0xB2 (the APM control port) and 0x4F; this triggers an SMI (System Management Interrupt), causing the CPU to enter SMM (System Management Mode) and run the BIOS firmware; the results are returned in the CPU's registers. It is not clear what is the relation between the two variants of SMAPI, though the assignment of error codes seems to be similar. In addition, the embedded controller on ThinkPad laptops has a non-standard interface at IO ports 0x1600-0x161F (mapped to LCP channel 3 of the H8S chip). The interface provides various system management services (currently known: battery information and accelerometer readouts). For more information see the thinkpad_ec module and the H8S hardware documentation: http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf 07070100000003000081A40000000200000002000000015A918BF7000002D8000000000000000000000000000000000000001300000000tp_smapi-0.43/TODOIdeas for improvement --------------------- (The best way to get these done is to send a patch.) Don't create /sys files for unsupported functions, and don't access those functions on suspend+resume (requires probing on module load or a huge white/blacklist). Make inhibit_charge_minutes return the time left, not the time originally set (as returned by the SMAPI BIOS). Requires remembering when inhibit_charge_minutes was set and comparing to current time. Save and and restore inhibit_charge_minutes across suspend-to-disk, as done for charge thresholds (requires the above time calculations too). Use the new Linux battery model introduced in kernel 2.6.23 (see the kernel's Documentation/power/power_supply_class.txt). 07070100000004000041ED0000000200000002000000025A918BF700000000000000000000000000000000000000000000001300000000tp_smapi-0.43/diff07070100000005000081A40000000200000002000000015A918BF7000000E6000000000000000000000000000000000000002B00000000tp_smapi-0.43/diff/Kconfig-thinkpad_ec.addconfig THINKPAD_EC tristate depends on X86 ---help--- This is a low-level driver for accessing the ThinkPad H8S embedded controller over the LPC bus (not to be confused with the ACPI Embedded Controller interface). 07070100000006000081A40000000200000002000000015A918BF700000166000000000000000000000000000000000000002800000000tp_smapi-0.43/diff/Kconfig-tp_smapi.addconfig TP_SMAPI tristate "ThinkPad SMAPI Support" depends on X86 select THINKPAD_EC default n help This adds SMAPI support on Lenovo/IBM ThinkPads, for features such as battery charging control. For more information about this driver see <http://www.thinkwiki.org/wiki/tp_smapi>. If you have a Lenovo/IBM ThinkPad laptop, say Y or M here. 07070100000007000081A40000000200000002000000015A918BF7000001D0000000000000000000000000000000000000001800000000tp_smapi-0.43/dkms.confPACKAGE_NAME="tp_smapi" PACKAGE_VERSION="0.43" MAKE[0]="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build HDAPS=1" CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" BUILT_MODULE_NAME[0]="thinkpad_ec" BUILT_MODULE_NAME[1]="tp_smapi" BUILT_MODULE_NAME[2]="hdaps" DEST_MODULE_LOCATION[0]="/extra" DEST_MODULE_LOCATION[1]="/extra" DEST_MODULE_LOCATION[2]="/updates" AUTOINSTALL="yes" 07070100000008000081A40000000200000002000000015A918BF700006E65000000000000000000000000000000000000001600000000tp_smapi-0.43/hdaps.c/* * drivers/platform/x86/hdaps.c - driver for IBM's Hard Drive Active Protection System * * Copyright (C) 2005 Robert Love <rml@novell.com> * Copyright (C) 2005 Jesper Juhl <jesper.juhl@gmail.com> * * The HardDisk Active Protection System (hdaps) is present in IBM ThinkPads * starting with the R40, T41, and X40. It provides a basic two-axis * accelerometer and other data, such as the device's temperature. * * This driver is based on the document by Mark A. Smith available at * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial * and error. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License v2 as published by the * Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/timer.h> #include <linux/dmi.h> #include <linux/jiffies.h> #include "thinkpad_ec.h" #include <linux/pci_ids.h> #include <linux/version.h> /* Embedded controller accelerometer read command and its result: */ static const struct thinkpad_ec_row ec_accel_args = { .mask = 0x0001, .val = {0x11} }; #define EC_ACCEL_IDX_READOUTS 0x1 /* readouts included in this read */ /* First readout, if READOUTS>=1: */ #define EC_ACCEL_IDX_YPOS1 0x2 /* y-axis position word */ #define EC_ACCEL_IDX_XPOS1 0x4 /* x-axis position word */ #define EC_ACCEL_IDX_TEMP1 0x6 /* device temperature in Celsius */ /* Second readout, if READOUTS>=2: */ #define EC_ACCEL_IDX_XPOS2 0x7 /* y-axis position word */ #define EC_ACCEL_IDX_YPOS2 0x9 /* x-axis position word */ #define EC_ACCEL_IDX_TEMP2 0xb /* device temperature in Celsius */ #define EC_ACCEL_IDX_QUEUED 0xc /* Number of queued readouts left */ #define EC_ACCEL_IDX_KMACT 0xd /* keyboard or mouse activity */ #define EC_ACCEL_IDX_RETVAL 0xf /* command return value, good=0x00 */ #define KEYBD_MASK 0x20 /* set if keyboard activity */ #define MOUSE_MASK 0x40 /* set if mouse activity */ #define READ_TIMEOUT_MSECS 100 /* wait this long for device read */ #define RETRY_MSECS 3 /* retry delay */ #define HDAPS_INPUT_FUZZ 4 /* input event threshold */ #define HDAPS_INPUT_FLAT 4 #define KMACT_REMEMBER_PERIOD (HZ/10) /* keyboard/mouse persistence */ /* Input IDs */ #define HDAPS_INPUT_VENDOR PCI_VENDOR_ID_IBM #define HDAPS_INPUT_PRODUCT 0x5054 /* "TP", shared with thinkpad_acpi */ #define HDAPS_INPUT_JS_VERSION 0x6801 /* Joystick emulation input device */ #define HDAPS_INPUT_RAW_VERSION 0x4801 /* Raw accelerometer input device */ /* Axis orientation. */ /* The unnatural bit-representation of inversions is for backward * compatibility with the"invert=1" module parameter. */ #define HDAPS_ORIENT_INVERT_XY 0x01 /* Invert both X and Y axes. */ #define HDAPS_ORIENT_INVERT_X 0x02 /* Invert the X axis (uninvert if * already inverted by INVERT_XY). */ #define HDAPS_ORIENT_SWAP 0x04 /* Swap the axes. The swap occurs * before inverting X or Y. */ #define HDAPS_ORIENT_MAX 0x07 #define HDAPS_ORIENT_UNDEFINED 0xFF /* Placeholder during initialization */ #define HDAPS_ORIENT_INVERT_Y (HDAPS_ORIENT_INVERT_XY | HDAPS_ORIENT_INVERT_X) static struct timer_list hdaps_timer; static struct platform_device *pdev; static struct input_dev *hdaps_idev; /* joystick-like device with fuzz */ static struct input_dev *hdaps_idev_raw; /* raw hdaps sensor readouts */ static unsigned int hdaps_invert = HDAPS_ORIENT_UNDEFINED; static int needs_calibration; /* Configuration: */ static int sampling_rate = 50; /* Sampling rate */ static int oversampling_ratio = 5; /* Ratio between our sampling rate and * EC accelerometer sampling rate */ static int running_avg_filter_order = 2; /* EC running average filter order */ /* Latest state readout: */ static int pos_x, pos_y; /* position */ static int temperature; /* temperature */ static int stale_readout = 1; /* last read invalid */ static int rest_x, rest_y; /* calibrated rest position */ /* Last time we saw keyboard and mouse activity: */ static u64 last_keyboard_jiffies = INITIAL_JIFFIES; static u64 last_mouse_jiffies = INITIAL_JIFFIES; static u64 last_update_jiffies = INITIAL_JIFFIES; /* input device use count */ static int hdaps_users; static DEFINE_MUTEX(hdaps_users_mtx); /* Some models require an axis transformation to the standard representation */ static void transform_axes(int *x, int *y) { if (hdaps_invert & HDAPS_ORIENT_SWAP) { int z; z = *x; *x = *y; *y = z; } if (hdaps_invert & HDAPS_ORIENT_INVERT_XY) { *x = -*x; *y = -*y; } if (hdaps_invert & HDAPS_ORIENT_INVERT_X) *x = -*x; } /** * __hdaps_update - query current state, with locks already acquired * @fast: if nonzero, do one quick attempt without retries. * * Query current accelerometer state and update global state variables. * Also prefetches the next query. Caller must hold controller lock. */ static int __hdaps_update(int fast) { /* Read data: */ struct thinkpad_ec_row data; int ret; data.mask = (1 << EC_ACCEL_IDX_READOUTS) | (1 << EC_ACCEL_IDX_KMACT) | (3 << EC_ACCEL_IDX_YPOS1) | (3 << EC_ACCEL_IDX_XPOS1) | (1 << EC_ACCEL_IDX_TEMP1) | (1 << EC_ACCEL_IDX_RETVAL); if (fast) ret = thinkpad_ec_try_read_row(&ec_accel_args, &data); else ret = thinkpad_ec_read_row(&ec_accel_args, &data); thinkpad_ec_prefetch_row(&ec_accel_args); /* Prefetch even if error */ if (ret) return ret; /* Check status: */ if (data.val[EC_ACCEL_IDX_RETVAL] != 0x00) { printk(KERN_WARNING "hdaps: read RETVAL=0x%02x\n", data.val[EC_ACCEL_IDX_RETVAL]); return -EIO; } if (data.val[EC_ACCEL_IDX_READOUTS] < 1) return -EBUSY; /* no pending readout, try again later */ /* Parse position data: */ pos_x = *(s16 *)(data.val+EC_ACCEL_IDX_XPOS1); pos_y = *(s16 *)(data.val+EC_ACCEL_IDX_YPOS1); transform_axes(&pos_x, &pos_y); /* Keyboard and mouse activity status is cleared as soon as it's read, * so applications will eat each other's events. Thus we remember any * event for KMACT_REMEMBER_PERIOD jiffies. */ if (data.val[EC_ACCEL_IDX_KMACT] & KEYBD_MASK) last_keyboard_jiffies = get_jiffies_64(); if (data.val[EC_ACCEL_IDX_KMACT] & MOUSE_MASK) last_mouse_jiffies = get_jiffies_64(); temperature = data.val[EC_ACCEL_IDX_TEMP1]; last_update_jiffies = get_jiffies_64(); stale_readout = 0; if (needs_calibration) { rest_x = pos_x; rest_y = pos_y; needs_calibration = 0; } return 0; } /** * hdaps_update - acquire locks and query current state * * Query current accelerometer state and update global state variables. * Also prefetches the next query. * Retries until timeout if the accelerometer is not in ready status (common). * Does its own locking. */ static int hdaps_update(void) { u64 age = get_jiffies_64() - last_update_jiffies; int total, ret; if (!stale_readout && age < (9*HZ)/(10*sampling_rate)) return 0; /* already updated recently */ for (total = 0; total < READ_TIMEOUT_MSECS; total += RETRY_MSECS) { ret = thinkpad_ec_lock(); if (ret) return ret; ret = __hdaps_update(0); thinkpad_ec_unlock(); if (!ret) return 0; if (ret != -EBUSY) break; msleep(RETRY_MSECS); } return ret; } /** * hdaps_set_power - enable or disable power to the accelerometer. * Returns zero on success and negative error code on failure. Can sleep. */ static int hdaps_set_power(int on) { struct thinkpad_ec_row args = { .mask = 0x0003, .val = {0x14, on?0x01:0x00} }; struct thinkpad_ec_row data = { .mask = 0x8000 }; int ret = thinkpad_ec_read_row(&args, &data); if (ret) return ret; if (data.val[0xF] != 0x00) return -EIO; return 0; } /** * hdaps_set_ec_config - set accelerometer parameters. * @ec_rate: embedded controller sampling rate * @order: embedded controller running average filter order * (Normally we have @ec_rate = sampling_rate * oversampling_ratio.) * Returns zero on success and negative error code on failure. Can sleep. */ static int hdaps_set_ec_config(int ec_rate, int order) { struct thinkpad_ec_row args = { .mask = 0x000F, .val = {0x10, (u8)ec_rate, (u8)(ec_rate>>8), order} }; struct thinkpad_ec_row data = { .mask = 0x8000 }; int ret = thinkpad_ec_read_row(&args, &data); printk(KERN_DEBUG "hdaps: setting ec_rate=%d, filter_order=%d\n", ec_rate, order); if (ret) return ret; if (data.val[0xF] == 0x03) { printk(KERN_WARNING "hdaps: config param out of range\n"); return -EINVAL; } if (data.val[0xF] == 0x06) { printk(KERN_WARNING "hdaps: config change already pending\n"); return -EBUSY; } if (data.val[0xF] != 0x00) { printk(KERN_WARNING "hdaps: config change error, ret=%d\n", data.val[0xF]); return -EIO; } return 0; } /** * hdaps_get_ec_config - get accelerometer parameters. * @ec_rate: embedded controller sampling rate * @order: embedded controller running average filter order * Returns zero on success and negative error code on failure. Can sleep. */ static int hdaps_get_ec_config(int *ec_rate, int *order) { const struct thinkpad_ec_row args = { .mask = 0x0003, .val = {0x17, 0x82} }; struct thinkpad_ec_row data = { .mask = 0x801F }; int ret = thinkpad_ec_read_row(&args, &data); if (ret) return ret; if (data.val[0xF] != 0x00) return -EIO; if (!(data.val[0x1] & 0x01)) return -ENXIO; /* accelerometer polling not enabled */ if (data.val[0x1] & 0x02) return -EBUSY; /* config change in progress, retry later */ *ec_rate = data.val[0x2] | ((int)(data.val[0x3]) << 8); *order = data.val[0x4]; return 0; } /** * hdaps_get_ec_mode - get EC accelerometer mode * Returns zero on success and negative error code on failure. Can sleep. */ static int hdaps_get_ec_mode(u8 *mode) { const struct thinkpad_ec_row args = { .mask = 0x0001, .val = {0x13} }; struct thinkpad_ec_row data = { .mask = 0x8002 }; int ret = thinkpad_ec_read_row(&args, &data); if (ret) return ret; if (data.val[0xF] != 0x00) { printk(KERN_WARNING "accelerometer not implemented (0x%02x)\n", data.val[0xF]); return -EIO; } *mode = data.val[0x1]; return 0; } /** * hdaps_check_ec - checks something about the EC. * Follows the clean-room spec for HDAPS; we don't know what it means. * Returns zero on success and negative error code on failure. Can sleep. */ static int hdaps_check_ec(void) { const struct thinkpad_ec_row args = { .mask = 0x0003, .val = {0x17, 0x81} }; struct thinkpad_ec_row data = { .mask = 0x800E }; int ret = thinkpad_ec_read_row(&args, &data); if (ret) return ret; if (!((data.val[0x1] == 0x00 && data.val[0x2] == 0x60) || /* cleanroom spec */ (data.val[0x1] == 0x01 && data.val[0x2] == 0x00)) || /* seen on T61 */ data.val[0x3] != 0x00 || data.val[0xF] != 0x00) { printk(KERN_WARNING "hdaps_check_ec: bad response (0x%x,0x%x,0x%x,0x%x)\n", data.val[0x1], data.val[0x2], data.val[0x3], data.val[0xF]); return -EIO; } return 0; } /** * hdaps_device_init - initialize the accelerometer. * * Call several embedded controller functions to test and initialize the * accelerometer. * Returns zero on success and negative error code on failure. Can sleep. */ #define FAILED_INIT(msg) printk(KERN_ERR "hdaps init failed at: %s\n", msg) static int hdaps_device_init(void) { int ret; u8 mode; ret = thinkpad_ec_lock(); if (ret) return ret; if (hdaps_get_ec_mode(&mode)) { FAILED_INIT("hdaps_get_ec_mode failed"); goto bad; } printk(KERN_DEBUG "hdaps: initial mode latch is 0x%02x\n", mode); if (mode == 0x00) { FAILED_INIT("accelerometer not available"); goto bad; } if (hdaps_check_ec()) { FAILED_INIT("hdaps_check_ec failed"); goto bad; } if (hdaps_set_power(1)) { FAILED_INIT("hdaps_set_power failed"); goto bad; } if (hdaps_set_ec_config(sampling_rate*oversampling_ratio, running_avg_filter_order)) { FAILED_INIT("hdaps_set_ec_config failed"); goto bad; } thinkpad_ec_invalidate(); udelay(200); /* Just prefetch instead of reading, to avoid ~1sec delay on load */ ret = thinkpad_ec_prefetch_row(&ec_accel_args); if (ret) { FAILED_INIT("initial prefetch failed"); goto bad; } goto good; bad: thinkpad_ec_invalidate(); ret = -ENXIO; good: stale_readout = 1; thinkpad_ec_unlock(); return ret; } /** * hdaps_device_shutdown - power off the accelerometer * Returns nonzero on failure. Can sleep. */ static int hdaps_device_shutdown(void) { int ret; ret = hdaps_set_power(0); if (ret) { printk(KERN_WARNING "hdaps: cannot power off\n"); return ret; } ret = hdaps_set_ec_config(0, 1); if (ret) printk(KERN_WARNING "hdaps: cannot stop EC sampling\n"); return ret; } /* Device model stuff */ static int hdaps_probe(struct platform_device *dev) { int ret; ret = hdaps_device_init(); if (ret) return ret; printk(KERN_INFO "hdaps: device successfully initialized.\n"); return 0; } static int hdaps_suspend(struct platform_device *dev, pm_message_t state) { /* Don't do hdaps polls until resume re-initializes the sensor. */ del_timer_sync(&hdaps_timer); hdaps_device_shutdown(); /* ignore errors, effect is negligible */ return 0; } static int hdaps_resume(struct platform_device *dev) { int ret = hdaps_device_init(); if (ret) return ret; mutex_lock(&hdaps_users_mtx); if (hdaps_users) mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); mutex_unlock(&hdaps_users_mtx); return 0; } static struct platform_driver hdaps_driver = { .probe = hdaps_probe, .suspend = hdaps_suspend, .resume = hdaps_resume, .driver = { .name = "hdaps", .owner = THIS_MODULE, }, }; /** * hdaps_calibrate - set our "resting" values. * Does its own locking. */ static void hdaps_calibrate(void) { needs_calibration = 1; hdaps_update(); /* If that fails, the mousedev poll will take care of things later. */ } /* Timer handler for updating the input device. Runs in softirq context, * so avoid lenghty or blocking operations. */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) static void hdaps_mousedev_poll(unsigned long unused) #else static void hdaps_mousedev_poll(struct timer_list *unused) #endif { int ret; stale_readout = 1; /* Cannot sleep. Try nonblockingly. If we fail, try again later. */ if (thinkpad_ec_try_lock()) goto keep_active; ret = __hdaps_update(1); /* fast update, we're in softirq context */ thinkpad_ec_unlock(); /* Any of "successful", "not yet ready" and "not prefetched"? */ if (ret != 0 && ret != -EBUSY && ret != -ENODATA) { printk(KERN_ERR "hdaps: poll failed, disabling updates\n"); return; } keep_active: /* Even if we failed now, pos_x,y may have been updated earlier: */ input_report_abs(hdaps_idev, ABS_X, pos_x - rest_x); input_report_abs(hdaps_idev, ABS_Y, pos_y - rest_y); input_sync(hdaps_idev); input_report_abs(hdaps_idev_raw, ABS_X, pos_x); input_report_abs(hdaps_idev_raw, ABS_Y, pos_y); input_sync(hdaps_idev_raw); mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); } /* Sysfs Files */ static ssize_t hdaps_position_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = hdaps_update(); if (ret) return ret; return sprintf(buf, "(%d,%d)\n", pos_x, pos_y); } static ssize_t hdaps_temp1_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = hdaps_update(); if (ret) return ret; return sprintf(buf, "%d\n", temperature); } static ssize_t hdaps_keyboard_activity_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = hdaps_update(); if (ret) return ret; return sprintf(buf, "%u\n", get_jiffies_64() < last_keyboard_jiffies + KMACT_REMEMBER_PERIOD); } static ssize_t hdaps_mouse_activity_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret = hdaps_update(); if (ret) return ret; return sprintf(buf, "%u\n", get_jiffies_64() < last_mouse_jiffies + KMACT_REMEMBER_PERIOD); } static ssize_t hdaps_calibrate_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "(%d,%d)\n", rest_x, rest_y); } static ssize_t hdaps_calibrate_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { hdaps_calibrate(); return count; } static ssize_t hdaps_invert_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%u\n", hdaps_invert); } static ssize_t hdaps_invert_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int invert; if (sscanf(buf, "%d", &invert) != 1 || invert < 0 || invert > HDAPS_ORIENT_MAX) return -EINVAL; hdaps_invert = invert; hdaps_calibrate(); return count; } static ssize_t hdaps_sampling_rate_show( struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", sampling_rate); } static ssize_t hdaps_sampling_rate_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int rate, ret; if (sscanf(buf, "%d", &rate) != 1 || rate > HZ || rate <= 0) { printk(KERN_WARNING "must have 0<input_sampling_rate<=HZ=%d\n", HZ); return -EINVAL; } ret = hdaps_set_ec_config(rate*oversampling_ratio, running_avg_filter_order); if (ret) return ret; sampling_rate = rate; return count; } static ssize_t hdaps_oversampling_ratio_show( struct device *dev, struct device_attribute *attr, char *buf) { int ec_rate, order; int ret = hdaps_get_ec_config(&ec_rate, &order); if (ret) return ret; return sprintf(buf, "%u\n", ec_rate / sampling_rate); } static ssize_t hdaps_oversampling_ratio_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ratio, ret; if (sscanf(buf, "%d", &ratio) != 1 || ratio < 1) return -EINVAL; ret = hdaps_set_ec_config(sampling_rate*ratio, running_avg_filter_order); if (ret) return ret; oversampling_ratio = ratio; return count; } static ssize_t hdaps_running_avg_filter_order_show( struct device *dev, struct device_attribute *attr, char *buf) { int rate, order; int ret = hdaps_get_ec_config(&rate, &order); if (ret) return ret; return sprintf(buf, "%u\n", order); } static ssize_t hdaps_running_avg_filter_order_store( struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int order, ret; if (sscanf(buf, "%d", &order) != 1) return -EINVAL; ret = hdaps_set_ec_config(sampling_rate*oversampling_ratio, order); if (ret) return ret; running_avg_filter_order = order; return count; } static int hdaps_mousedev_open(struct input_dev *dev) { if (!try_module_get(THIS_MODULE)) return -ENODEV; mutex_lock(&hdaps_users_mtx); if (hdaps_users++ == 0) /* first input user */ mod_timer(&hdaps_timer, jiffies + HZ/sampling_rate); mutex_unlock(&hdaps_users_mtx); return 0; } static void hdaps_mousedev_close(struct input_dev *dev) { mutex_lock(&hdaps_users_mtx); if (--hdaps_users == 0) /* no input users left */ del_timer_sync(&hdaps_timer); mutex_unlock(&hdaps_users_mtx); module_put(THIS_MODULE); } static DEVICE_ATTR(position, 0444, hdaps_position_show, NULL); static DEVICE_ATTR(temp1, 0444, hdaps_temp1_show, NULL); /* "temp1" instead of "temperature" is hwmon convention */ static DEVICE_ATTR(keyboard_activity, 0444, hdaps_keyboard_activity_show, NULL); static DEVICE_ATTR(mouse_activity, 0444, hdaps_mouse_activity_show, NULL); static DEVICE_ATTR(calibrate, 0644, hdaps_calibrate_show, hdaps_calibrate_store); static DEVICE_ATTR(invert, 0644, hdaps_invert_show, hdaps_invert_store); static DEVICE_ATTR(sampling_rate, 0644, hdaps_sampling_rate_show, hdaps_sampling_rate_store); static DEVICE_ATTR(oversampling_ratio, 0644, hdaps_oversampling_ratio_show, hdaps_oversampling_ratio_store); static DEVICE_ATTR(running_avg_filter_order, 0644, hdaps_running_avg_filter_order_show, hdaps_running_avg_filter_order_store); static struct attribute *hdaps_attributes[] = { &dev_attr_position.attr, &dev_attr_temp1.attr, &dev_attr_keyboard_activity.attr, &dev_attr_mouse_activity.attr, &dev_attr_calibrate.attr, &dev_attr_invert.attr, &dev_attr_sampling_rate.attr, &dev_attr_oversampling_ratio.attr, &dev_attr_running_avg_filter_order.attr, NULL, }; static struct attribute_group hdaps_attribute_group = { .attrs = hdaps_attributes, }; /* Module stuff */ /* hdaps_dmi_match_invert - found an inverted match. */ static int __init hdaps_dmi_match_invert(const struct dmi_system_id *id) { unsigned int orient = (kernel_ulong_t) id->driver_data; hdaps_invert = orient; printk(KERN_INFO "hdaps: %s detected, setting orientation %u\n", id->ident, orient); return 1; /* stop enumeration */ } #define HDAPS_DMI_MATCH_INVERT(vendor, model, orient) { \ .ident = vendor " " model, \ .callback = hdaps_dmi_match_invert, \ .driver_data = (void *)(orient), \ .matches = { \ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \ } \ } /* List of models with abnormal axis configuration. Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match "ThinkPad T42p", and enumeration stops after first match, so the order of the entries matters. */ struct dmi_system_id __initconst hdaps_whitelist[] = { HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X40", HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad X41", HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R60", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R61", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R400", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad R500", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60 Tablet", HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60s", HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X61", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400s", HDAPS_ORIENT_INVERT_X), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410s", HDAPS_ORIENT_SWAP), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T410", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T500", HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T510", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W510", HDAPS_ORIENT_MAX), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad W520", HDAPS_ORIENT_MAX), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X200", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X | HDAPS_ORIENT_INVERT_Y), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201 Tablet", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201s", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_XY), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X201", HDAPS_ORIENT_SWAP | HDAPS_ORIENT_INVERT_X), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X220", HDAPS_ORIENT_SWAP), { .ident = NULL } }; static int __init hdaps_init(void) { int ret; /* Determine axis orientation orientation */ if (hdaps_invert == HDAPS_ORIENT_UNDEFINED) /* set by module param? */ if (dmi_check_system(hdaps_whitelist) < 1) /* in whitelist? */ hdaps_invert = 0; /* default */ /* Init timer before platform_driver_register, in case of suspend */ #if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) init_timer(&hdaps_timer); hdaps_timer.function = hdaps_mousedev_poll; #else timer_setup(&hdaps_timer, hdaps_mousedev_poll, 0); #endif ret = platform_driver_register(&hdaps_driver); if (ret) goto out; pdev = platform_device_register_simple("hdaps", -1, NULL, 0); if (IS_ERR(pdev)) { ret = PTR_ERR(pdev); goto out_driver; } ret = sysfs_create_group(&pdev->dev.kobj, &hdaps_attribute_group); if (ret) goto out_device; hdaps_idev = input_allocate_device(); if (!hdaps_idev) { ret = -ENOMEM; goto out_group; } hdaps_idev_raw = input_allocate_device(); if (!hdaps_idev_raw) { ret = -ENOMEM; goto out_idev_first; } /* calibration for the input device (deferred to avoid delay) */ needs_calibration = 1; /* initialize the joystick-like fuzzed input device */ hdaps_idev->name = "ThinkPad HDAPS joystick emulation"; hdaps_idev->phys = "hdaps/input0"; hdaps_idev->id.bustype = BUS_HOST; hdaps_idev->id.vendor = HDAPS_INPUT_VENDOR; hdaps_idev->id.product = HDAPS_INPUT_PRODUCT; hdaps_idev->id.version = HDAPS_INPUT_JS_VERSION; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) hdaps_idev->cdev.dev = &pdev->dev; #endif hdaps_idev->evbit[0] = BIT(EV_ABS); hdaps_idev->open = hdaps_mousedev_open; hdaps_idev->close = hdaps_mousedev_close; input_set_abs_params(hdaps_idev, ABS_X, -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); input_set_abs_params(hdaps_idev, ABS_Y, -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); ret = input_register_device(hdaps_idev); if (ret) goto out_idev; /* initialize the raw data input device */ hdaps_idev_raw->name = "ThinkPad HDAPS accelerometer data"; hdaps_idev_raw->phys = "hdaps/input1"; hdaps_idev_raw->id.bustype = BUS_HOST; hdaps_idev_raw->id.vendor = HDAPS_INPUT_VENDOR; hdaps_idev_raw->id.product = HDAPS_INPUT_PRODUCT; hdaps_idev_raw->id.version = HDAPS_INPUT_RAW_VERSION; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) hdaps_idev_raw->cdev.dev = &pdev->dev; #endif hdaps_idev_raw->evbit[0] = BIT(EV_ABS); hdaps_idev_raw->open = hdaps_mousedev_open; hdaps_idev_raw->close = hdaps_mousedev_close; input_set_abs_params(hdaps_idev_raw, ABS_X, -32768, 32767, 0, 0); input_set_abs_params(hdaps_idev_raw, ABS_Y, -32768, 32767, 0, 0); ret = input_register_device(hdaps_idev_raw); if (ret) goto out_idev_reg_first; printk(KERN_INFO "hdaps: driver successfully loaded.\n"); return 0; out_idev_reg_first: input_unregister_device(hdaps_idev); out_idev: input_free_device(hdaps_idev_raw); out_idev_first: input_free_device(hdaps_idev); out_group: sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); out_device: platform_device_unregister(pdev); out_driver: platform_driver_unregister(&hdaps_driver); hdaps_device_shutdown(); out: printk(KERN_WARNING "hdaps: driver init failed (ret=%d)!\n", ret); return ret; } static void __exit hdaps_exit(void) { input_unregister_device(hdaps_idev_raw); input_unregister_device(hdaps_idev); hdaps_device_shutdown(); /* ignore errors, effect is negligible */ sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); platform_device_unregister(pdev); platform_driver_unregister(&hdaps_driver); printk(KERN_INFO "hdaps: driver unloaded.\n"); } module_init(hdaps_init); module_exit(hdaps_exit); module_param_named(invert, hdaps_invert, uint, 0); MODULE_PARM_DESC(invert, "axis orientation code"); MODULE_AUTHOR("Robert Love"); MODULE_DESCRIPTION("IBM Hard Drive Active Protection System (HDAPS) driver"); MODULE_LICENSE("GPL v2"); 07070100000009000081A40000000200000002000000015A918BF7000041C2000000000000000000000000000000000000001C00000000tp_smapi-0.43/thinkpad_ec.c/* * thinkpad_ec.c - ThinkPad embedded controller LPC3 functions * * The embedded controller on ThinkPad laptops has a non-standard interface, * where LPC channel 3 of the H8S EC chip is hooked up to IO ports * 0x1600-0x161F and implements (a special case of) the H8S LPC protocol. * The EC LPC interface provides various system management services (currently * known: battery information and accelerometer readouts). This driver * provides access and mutual exclusion for the EC interface. * * The LPC protocol and terminology are documented here: * "H8S/2104B Group Hardware Manual", * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf * * Copyright (C) 2006-2007 Shem Multinymous <multinymous@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/dmi.h> #include <linux/ioport.h> #include <linux/delay.h> #include "thinkpad_ec.h" #include <linux/jiffies.h> #include <asm/io.h> #include <linux/version.h> #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) #include <asm/semaphore.h> #else #include <linux/semaphore.h> #endif #define TP_VERSION "0.43" MODULE_AUTHOR("Shem Multinymous"); MODULE_DESCRIPTION("ThinkPad embedded controller hardware access"); MODULE_VERSION(TP_VERSION); MODULE_LICENSE("GPL"); /* IO ports used by embedded controller LPC channel 3: */ #define TPC_BASE_PORT 0x1600 #define TPC_NUM_PORTS 0x20 #define TPC_STR3_PORT 0x1604 /* Reads H8S EC register STR3 */ #define TPC_TWR0_PORT 0x1610 /* Mapped to H8S EC register TWR0MW/SW */ #define TPC_TWR15_PORT 0x161F /* Mapped to H8S EC register TWR15. */ /* (and port TPC_TWR0_PORT+i is mapped to H8S reg TWRi for 0<i<16) */ /* H8S STR3 status flags (see "H8S/2104B Group Hardware Manual" p.549) */ #define H8S_STR3_IBF3B 0x80 /* Bidi. Data Register Input Buffer Full */ #define H8S_STR3_OBF3B 0x40 /* Bidi. Data Register Output Buffer Full */ #define H8S_STR3_MWMF 0x20 /* Master Write Mode Flag */ #define H8S_STR3_SWMF 0x10 /* Slave Write Mode Flag */ #define H8S_STR3_MASK 0xF0 /* All bits we care about in STR3 */ /* Timeouts and retries */ #define TPC_READ_RETRIES 150 #define TPC_READ_NDELAY 500 #define TPC_REQUEST_RETRIES 1000 #define TPC_REQUEST_NDELAY 10 #define TPC_PREFETCH_TIMEOUT (HZ/10) /* invalidate prefetch after 0.1sec */ /* A few macros for printk()ing: */ #define MSG_FMT(fmt, args...) \ "thinkpad_ec: %s: " fmt "\n", __func__, ## args #define REQ_FMT(msg, code) \ MSG_FMT("%s: (0x%02x:0x%02x)->0x%02x", \ msg, args->val[0x0], args->val[0xF], code) /* State of request prefetching: */ static u8 prefetch_arg0, prefetch_argF; /* Args of last prefetch */ static u64 prefetch_jiffies; /* time of prefetch, or: */ #define TPC_PREFETCH_NONE INITIAL_JIFFIES /* No prefetch */ #define TPC_PREFETCH_JUNK (INITIAL_JIFFIES+1) /* Ignore prefetch */ /* Locking: */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) static DECLARE_MUTEX(thinkpad_ec_mutex); #else static DEFINE_SEMAPHORE(thinkpad_ec_mutex); #endif /* Kludge in case the ACPI DSDT reserves the ports we need. */ static bool force_io; /* Willing to do IO to ports we couldn't reserve? */ static int reserved_io; /* Successfully reserved the ports? */ module_param_named(force_io, force_io, bool, 0600); MODULE_PARM_DESC(force_io, "Force IO even if region already reserved (0=off, 1=on)"); /** * thinkpad_ec_lock - get lock on the ThinkPad EC * * Get exclusive lock for accesing the ThinkPad embedded controller LPC3 * interface. Returns 0 iff lock acquired. */ int thinkpad_ec_lock(void) { int ret; ret = down_interruptible(&thinkpad_ec_mutex); return ret; } EXPORT_SYMBOL_GPL(thinkpad_ec_lock); /** * thinkpad_ec_try_lock - try getting lock on the ThinkPad EC * * Try getting an exclusive lock for accesing the ThinkPad embedded * controller LPC3. Returns immediately if lock is not available; neither * blocks nor sleeps. Returns 0 iff lock acquired . */ int thinkpad_ec_try_lock(void) { return down_trylock(&thinkpad_ec_mutex); } EXPORT_SYMBOL_GPL(thinkpad_ec_try_lock); /** * thinkpad_ec_unlock - release lock on ThinkPad EC * * Release a previously acquired exclusive lock on the ThinkPad ebmedded * controller LPC3 interface. */ void thinkpad_ec_unlock(void) { up(&thinkpad_ec_mutex); } EXPORT_SYMBOL_GPL(thinkpad_ec_unlock); /** * thinkpad_ec_request_row - tell embedded controller to prepare a row * @args Input register arguments * * Requests a data row by writing to H8S LPC registers TRW0 through TWR15 (or * a subset thereof) following the protocol prescribed by the "H8S/2104B Group * Hardware Manual". Does sanity checks via status register STR3. */ static int thinkpad_ec_request_row(const struct thinkpad_ec_row *args) { u8 str3; int i; /* EC protocol requires write to TWR0 (function code): */ if (!(args->mask & 0x0001)) { printk(KERN_ERR MSG_FMT("bad args->mask=0x%02x", args->mask)); return -EINVAL; } /* Check initial STR3 status: */ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; if (str3 & H8S_STR3_OBF3B) { /* data already pending */ inb(TPC_TWR15_PORT); /* marks end of previous transaction */ if (prefetch_jiffies == TPC_PREFETCH_NONE) printk(KERN_WARNING REQ_FMT( "EC has result from unrequested transaction", str3)); return -EBUSY; /* EC will be ready in a few usecs */ } else if (str3 == H8S_STR3_SWMF) { /* busy with previous request */ if (prefetch_jiffies == TPC_PREFETCH_NONE) printk(KERN_WARNING REQ_FMT( "EC is busy with unrequested transaction", str3)); return -EBUSY; /* data will be pending in a few usecs */ } else if (str3 != 0x00) { /* unexpected status? */ printk(KERN_WARNING REQ_FMT("unexpected initial STR3", str3)); return -EIO; } /* Send TWR0MW: */ outb(args->val[0], TPC_TWR0_PORT); str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; if (str3 != H8S_STR3_MWMF) { /* not accepted? */ printk(KERN_WARNING REQ_FMT("arg0 rejected", str3)); return -EIO; } /* Send TWR1 through TWR14: */ for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) if ((args->mask>>i)&1) outb(args->val[i], TPC_TWR0_PORT+i); /* Send TWR15 (default to 0x01). This marks end of command. */ outb((args->mask & 0x8000) ? args->val[0xF] : 0x01, TPC_TWR15_PORT); /* Wait until EC starts writing its reply (~60ns on average). * Releasing locks before this happens may cause an EC hang * due to firmware bug! */ for (i = 0; i < TPC_REQUEST_RETRIES; i++) { str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; if (str3 & H8S_STR3_SWMF) /* EC started replying */ return 0; else if (!(str3 & ~(H8S_STR3_IBF3B|H8S_STR3_MWMF))) /* Normal progress (the EC hasn't seen the request * yet, or is processing it). Wait it out. */ ndelay(TPC_REQUEST_NDELAY); else { /* weird EC status */ printk(KERN_WARNING REQ_FMT("bad end STR3", str3)); return -EIO; } } printk(KERN_WARNING REQ_FMT("EC is mysteriously silent", str3)); return -EIO; } /** * thinkpad_ec_read_data - read pre-requested row-data from EC * @args Input register arguments of pre-requested rows * @data Output register values * * Reads current row data from the controller, assuming it's already * requested. Follows the H8S spec for register access and status checks. */ static int thinkpad_ec_read_data(const struct thinkpad_ec_row *args, struct thinkpad_ec_row *data) { int i; u8 str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; /* Once we make a request, STR3 assumes the sequence of values listed * in the following 'if' as it reads the request and writes its data. * It takes about a few dozen nanosecs total, with very high variance. */ if (str3 == (H8S_STR3_IBF3B|H8S_STR3_MWMF) || str3 == 0x00 || /* the 0x00 is indistinguishable from idle EC! */ str3 == H8S_STR3_SWMF) return -EBUSY; /* not ready yet */ /* Finally, the EC signals output buffer full: */ if (str3 != (H8S_STR3_OBF3B|H8S_STR3_SWMF)) { printk(KERN_WARNING REQ_FMT("bad initial STR3", str3)); return -EIO; } /* Read first byte (signals start of read transactions): */ data->val[0] = inb(TPC_TWR0_PORT); /* Optionally read 14 more bytes: */ for (i = 1; i < TP_CONTROLLER_ROW_LEN-1; i++) if ((data->mask >> i)&1) data->val[i] = inb(TPC_TWR0_PORT+i); /* Read last byte from 0x161F (signals end of read transaction): */ data->val[0xF] = inb(TPC_TWR15_PORT); /* Readout still pending? */ str3 = inb(TPC_STR3_PORT) & H8S_STR3_MASK; if (str3 & H8S_STR3_OBF3B) printk(KERN_WARNING REQ_FMT("OBF3B=1 after read", str3)); /* If port 0x161F returns 0x80 too often, the EC may lock up. Warn: */ if (data->val[0xF] == 0x80) printk(KERN_WARNING REQ_FMT("0x161F reports error", data->val[0xF])); return 0; } /** * thinkpad_ec_is_row_fetched - is the given row currently prefetched? * * To keep things simple we compare only the first and last args; * this suffices for all known cases. */ static int thinkpad_ec_is_row_fetched(const struct thinkpad_ec_row *args) { return (prefetch_jiffies != TPC_PREFETCH_NONE) && (prefetch_jiffies != TPC_PREFETCH_JUNK) && (prefetch_arg0 == args->val[0]) && (prefetch_argF == args->val[0xF]) && (get_jiffies_64() < prefetch_jiffies + TPC_PREFETCH_TIMEOUT); } /** * thinkpad_ec_read_row - request and read data from ThinkPad EC * @args Input register arguments * @data Output register values * * Read a data row from the ThinkPad embedded controller LPC3 interface. * Does fetching and retrying if needed. The row is specified by an * array of 16 bytes, some of which may be undefined (but the first is * mandatory). These bytes are given in @args->val[], where @args->val[i] is * used iff (@args->mask>>i)&1). The resulting row data is stored in * @data->val[], but is only guaranteed to be valid for indices corresponding * to set bit in @data->mask. That is, if @data->mask&(1<<i)==0 then * @data->val[i] is undefined. * * Returns -EBUSY on transient error and -EIO on abnormal condition. * Caller must hold controller lock. */ int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, struct thinkpad_ec_row *data) { int retries, ret; if (thinkpad_ec_is_row_fetched(args)) goto read_row; /* already requested */ /* Request the row */ for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { ret = thinkpad_ec_request_row(args); if (!ret) goto read_row; if (ret != -EBUSY) break; ndelay(TPC_READ_NDELAY); } printk(KERN_ERR REQ_FMT("failed requesting row", ret)); goto out; read_row: /* Read the row's data */ for (retries = 0; retries < TPC_READ_RETRIES; ++retries) { ret = thinkpad_ec_read_data(args, data); if (!ret) goto out; if (ret != -EBUSY) break; ndelay(TPC_READ_NDELAY); } printk(KERN_ERR REQ_FMT("failed waiting for data", ret)); out: prefetch_jiffies = TPC_PREFETCH_JUNK; return ret; } EXPORT_SYMBOL_GPL(thinkpad_ec_read_row); /** * thinkpad_ec_try_read_row - try reading prefetched data from ThinkPad EC * @args Input register arguments * @data Output register values * * Try reading a data row from the ThinkPad embedded controller LPC3 * interface, if this raw was recently prefetched using * thinkpad_ec_prefetch_row(). Does not fetch, retry or block. * The parameters have the same meaning as in thinkpad_ec_read_row(). * * Returns -EBUSY is data not ready and -ENODATA if row not prefetched. * Caller must hold controller lock. */ int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, struct thinkpad_ec_row *data) { int ret; if (!thinkpad_ec_is_row_fetched(args)) { ret = -ENODATA; } else { ret = thinkpad_ec_read_data(args, data); if (!ret) prefetch_jiffies = TPC_PREFETCH_NONE; /* eaten up */ } return ret; } EXPORT_SYMBOL_GPL(thinkpad_ec_try_read_row); /** * thinkpad_ec_prefetch_row - prefetch data from ThinkPad EC * @args Input register arguments * * Prefetch a data row from the ThinkPad embedded controller LCP3 * interface. A subsequent call to thinkpad_ec_read_row() with the * same arguments will be faster, and a subsequent call to * thinkpad_ec_try_read_row() stands a good chance of succeeding if * done neither too soon nor too late. See * thinkpad_ec_read_row() for the meaning of @args. * * Returns -EBUSY on transient error and -EIO on abnormal condition. * Caller must hold controller lock. */ int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args) { int ret; ret = thinkpad_ec_request_row(args); if (ret) { prefetch_jiffies = TPC_PREFETCH_JUNK; } else { prefetch_jiffies = get_jiffies_64(); prefetch_arg0 = args->val[0x0]; prefetch_argF = args->val[0xF]; } return ret; } EXPORT_SYMBOL_GPL(thinkpad_ec_prefetch_row); /** * thinkpad_ec_invalidate - invalidate prefetched ThinkPad EC data * * Invalidate the data prefetched via thinkpad_ec_prefetch_row() from the * ThinkPad embedded controller LPC3 interface. * Must be called before unlocking by any code that accesses the controller * ports directly. */ void thinkpad_ec_invalidate(void) { prefetch_jiffies = TPC_PREFETCH_JUNK; } EXPORT_SYMBOL_GPL(thinkpad_ec_invalidate); /*** Checking for EC hardware ***/ /** * thinkpad_ec_test - verify the EC is present and follows protocol * * Ensure the EC LPC3 channel really works on this machine by making * an EC request and seeing if the EC follows the documented H8S protocol. * The requested row just reads battery status, so it should be harmless to * access it (on a correct EC). * This test writes to IO ports, so execute only after checking DMI. */ static int __init thinkpad_ec_test(void) { int ret; const struct thinkpad_ec_row args = /* battery 0 basic status */ { .mask = 0x8001, .val = {0x01,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x00} }; struct thinkpad_ec_row data = { .mask = 0x0000 }; ret = thinkpad_ec_lock(); if (ret) return ret; ret = thinkpad_ec_read_row(&args, &data); thinkpad_ec_unlock(); return ret; } /* Search all DMI device names of a given type for a substring */ static int __init dmi_find_substring(int type, const char *substr) { const struct dmi_device *dev = NULL; while ((dev = dmi_find_device(type, NULL, dev))) { if (strstr(dev->name, substr)) return 1; } return 0; } #define TP_DMI_MATCH(vendor,model) { \ .ident = vendor " " model, \ .matches = { \ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \ } \ } /* Check DMI for existence of ThinkPad embedded controller */ static int __init check_dmi_for_ec(void) { /* A few old models that have a good EC but don't report it in DMI */ struct dmi_system_id tp_whitelist[] = { TP_DMI_MATCH("IBM", "ThinkPad A30"), TP_DMI_MATCH("IBM", "ThinkPad T23"), TP_DMI_MATCH("IBM", "ThinkPad X24"), TP_DMI_MATCH("LENOVO", "ThinkPad"), { .ident = NULL } }; return dmi_find_substring(DMI_DEV_TYPE_OEM_STRING, "IBM ThinkPad Embedded Controller") || dmi_check_system(tp_whitelist); } /*** Init and cleanup ***/ static int __init thinkpad_ec_init(void) { if (!check_dmi_for_ec()) { printk(KERN_WARNING "thinkpad_ec: no ThinkPad embedded controller!\n"); return -ENODEV; } if (request_region(TPC_BASE_PORT, TPC_NUM_PORTS, "thinkpad_ec")) { reserved_io = 1; } else { printk(KERN_ERR "thinkpad_ec: cannot claim IO ports %#x-%#x... ", TPC_BASE_PORT, TPC_BASE_PORT + TPC_NUM_PORTS - 1); if (force_io) { printk("forcing use of unreserved IO ports.\n"); } else { printk("consider using force_io=1.\n"); return -ENXIO; } } prefetch_jiffies = TPC_PREFETCH_JUNK; if (thinkpad_ec_test()) { printk(KERN_ERR "thinkpad_ec: initial ec test failed\n"); if (reserved_io) release_region(TPC_BASE_PORT, TPC_NUM_PORTS); return -ENXIO; } printk(KERN_INFO "thinkpad_ec: thinkpad_ec " TP_VERSION " loaded.\n"); return 0; } static void __exit thinkpad_ec_exit(void) { if (reserved_io) release_region(TPC_BASE_PORT, TPC_NUM_PORTS); printk(KERN_INFO "thinkpad_ec: unloaded.\n"); } module_init(thinkpad_ec_init); module_exit(thinkpad_ec_exit); 0707010000000A000081A40000000200000002000000015A918BF7000006A8000000000000000000000000000000000000001C00000000tp_smapi-0.43/thinkpad_ec.h/* * thinkpad_ec.h - interface to ThinkPad embedded controller LPC3 functions * * Copyright (C) 2005 Shem Multinymous <multinymous@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _THINKPAD_EC_H #define _THINKPAD_EC_H #ifdef __KERNEL__ #define TP_CONTROLLER_ROW_LEN 16 /* EC transactions input and output (possibly partial) vectors of 16 bytes. */ struct thinkpad_ec_row { u16 mask; /* bitmap of which entries of val[] are meaningful */ u8 val[TP_CONTROLLER_ROW_LEN]; }; extern int __must_check thinkpad_ec_lock(void); extern int __must_check thinkpad_ec_try_lock(void); extern void thinkpad_ec_unlock(void); extern int thinkpad_ec_read_row(const struct thinkpad_ec_row *args, struct thinkpad_ec_row *data); extern int thinkpad_ec_try_read_row(const struct thinkpad_ec_row *args, struct thinkpad_ec_row *mask); extern int thinkpad_ec_prefetch_row(const struct thinkpad_ec_row *args); extern void thinkpad_ec_invalidate(void); #endif /* __KERNEL */ #endif /* _THINKPAD_EC_H */ 0707010000000B000081A40000000200000002000000015A918BF70000AF5A000000000000000000000000000000000000001900000000tp_smapi-0.43/tp_smapi.c/* * tp_smapi.c - ThinkPad SMAPI support * * This driver exposes some features of the System Management Application * Program Interface (SMAPI) BIOS found on ThinkPad laptops. It works on * models in which the SMAPI BIOS runs in SMM and is invoked by writing * to the APM control port 0xB2. * It also exposes battery status information, obtained from the ThinkPad * embedded controller (via the thinkpad_ec module). * Ancient ThinkPad models use a different interface, supported by the * "thinkpad" module from "tpctl". * * Many of the battery status values obtained from the EC simply mirror * values provided by the battery's Smart Battery System (SBS) interface, so * their meaning is defined by the Smart Battery Data Specification (see * http://sbs-forum.org/specs/sbdat110.pdf). References to this SBS spec * are given in the code where relevant. * * Copyright (C) 2006 Shem Multinymous <multinymous@gmail.com>. * SMAPI access code based on the mwave driver by Mike Sullivan. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/proc_fs.h> #include <linux/mc146818rtc.h> /* CMOS defines */ #include <linux/delay.h> #include <linux/version.h> #include "thinkpad_ec.h" #include <linux/platform_device.h> #include <asm/uaccess.h> #include <asm/io.h> #define TP_VERSION "0.43" #define TP_DESC "ThinkPad SMAPI Support" #define TP_DIR "smapi" MODULE_AUTHOR("Shem Multinymous"); MODULE_DESCRIPTION(TP_DESC); MODULE_VERSION(TP_VERSION); MODULE_LICENSE("GPL"); static struct platform_device *pdev; static int tp_debug; module_param_named(debug, tp_debug, int, 0600); MODULE_PARM_DESC(debug, "Debug level (0=off, 1=on)"); /* A few macros for printk()ing: */ #define TPRINTK(level, fmt, args...) \ dev_printk(level, &(pdev->dev), "%s: " fmt "\n", __func__, ## args) #define DPRINTK(fmt, args...) \ do { if (tp_debug) TPRINTK(KERN_DEBUG, fmt, ## args); } while (0) /********************************************************************* * SMAPI interface */ /* SMAPI functions (register BX when making the SMM call). */ #define SMAPI_GET_INHIBIT_CHARGE 0x2114 #define SMAPI_SET_INHIBIT_CHARGE 0x2115 #define SMAPI_GET_THRESH_START 0x2116 #define SMAPI_SET_THRESH_START 0x2117 #define SMAPI_GET_FORCE_DISCHARGE 0x2118 #define SMAPI_SET_FORCE_DISCHARGE 0x2119 #define SMAPI_GET_THRESH_STOP 0x211a #define SMAPI_SET_THRESH_STOP 0x211b /* SMAPI error codes (see ThinkPad 770 Technical Reference Manual p.83 at http://www-307.ibm.com/pc/support/site.wss/document.do?lndocid=PFAN-3TUQQD */ #define SMAPI_RETCODE_EOF 0xff static struct { u8 rc; char *msg; int ret; } smapi_retcode[] = { {0x00, "OK", 0}, {0x53, "SMAPI function is not available", -ENXIO}, {0x81, "Invalid parameter", -EINVAL}, {0x86, "Function is not supported by SMAPI BIOS", -EOPNOTSUPP}, {0x90, "System error", -EIO}, {0x91, "System is invalid", -EIO}, {0x92, "System is busy, -EBUSY"}, {0xa0, "Device error (disk read error)", -EIO}, {0xa1, "Device is busy", -EBUSY}, {0xa2, "Device is not attached", -ENXIO}, {0xa3, "Device is disbled", -EIO}, {0xa4, "Request parameter is out of range", -EINVAL}, {0xa5, "Request parameter is not accepted", -EINVAL}, {0xa6, "Transient error", -EBUSY}, /* ? */ {SMAPI_RETCODE_EOF, "Unknown error code", -EIO} }; #define SMAPI_MAX_RETRIES 10 #define SMAPI_PORT2 0x4F /* fixed port, meaning unclear */ static unsigned short smapi_port; /* APM control port, normally 0xB2 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,37) static DECLARE_MUTEX(smapi_mutex); #else static DEFINE_SEMAPHORE(smapi_mutex); #endif /** * find_smapi_port - read SMAPI port from NVRAM */ static int __init find_smapi_port(void) { u16 smapi_id = 0; unsigned short port = 0; unsigned long flags; spin_lock_irqsave(&rtc_lock, flags); smapi_id = CMOS_READ(0x7C); smapi_id |= (CMOS_READ(0x7D) << 8); spin_unlock_irqrestore(&rtc_lock, flags); if (smapi_id != 0x5349) { printk(KERN_ERR "SMAPI not supported (ID=0x%x)\n", smapi_id); return -ENXIO; } spin_lock_irqsave(&rtc_lock, flags); port = CMOS_READ(0x7E); port |= (CMOS_READ(0x7F) << 8); spin_unlock_irqrestore(&rtc_lock, flags); if (port == 0) { printk(KERN_ERR "unable to read SMAPI port number\n"); return -ENXIO; } return port; } /** * smapi_request - make a SMAPI call * @inEBX, @inECX, @inEDI, @inESI: input registers * @outEBX, @outECX, @outEDX, @outEDI, @outESI: outputs registers * @msg: textual error message * Invokes the SMAPI SMBIOS with the given input and outpu args. * All outputs are optional (can be %NULL). * Returns 0 when successful, and a negative errno constant * (see smapi_retcode above) upon failure. */ static int smapi_request(u32 inEBX, u32 inECX, u32 inEDI, u32 inESI, u32 *outEBX, u32 *outECX, u32 *outEDX, u32 *outEDI, u32 *outESI, const char **msg) { int ret = 0; int i; int retries; u8 rc; /* Must use local vars for output regs, due to reg pressure. */ u32 tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI; for (retries = 0; retries < SMAPI_MAX_RETRIES; ++retries) { DPRINTK("req_in: BX=%x CX=%x DI=%x SI=%x", inEBX, inECX, inEDI, inESI); /* SMAPI's SMBIOS call and thinkpad_ec end up using use * different interfaces to the same chip, so play it safe. */ ret = thinkpad_ec_lock(); if (ret) return ret; __asm__ __volatile__( "movl $0x00005380,%%eax\n\t" "movl %6,%%ebx\n\t" "movl %7,%%ecx\n\t" "movl %8,%%edi\n\t" "movl %9,%%esi\n\t" "xorl %%edx,%%edx\n\t" "movw %10,%%dx\n\t" "out %%al,%%dx\n\t" /* trigger SMI to SMBIOS */ "out %%al,$0x4F\n\t" "movl %%eax,%0\n\t" "movl %%ebx,%1\n\t" "movl %%ecx,%2\n\t" "movl %%edx,%3\n\t" "movl %%edi,%4\n\t" "movl %%esi,%5\n\t" :"=m"(tmpEAX), "=m"(tmpEBX), "=m"(tmpECX), "=m"(tmpEDX), "=m"(tmpEDI), "=m"(tmpESI) :"m"(inEBX), "m"(inECX), "m"(inEDI), "m"(inESI), "m"((u16)smapi_port) :"%eax", "%ebx", "%ecx", "%edx", "%edi", "%esi"); thinkpad_ec_invalidate(); thinkpad_ec_unlock(); /* Don't let the next SMAPI access happen too quickly, * may case problems. (We're hold smapi_mutex). */ msleep(50); if (outEBX) *outEBX = tmpEBX; if (outECX) *outECX = tmpECX; if (outEDX) *outEDX = tmpEDX; if (outESI) *outESI = tmpESI; if (outEDI) *outEDI = tmpEDI; /* Look up error code */ rc = (tmpEAX>>8)&0xFF; for (i = 0; smapi_retcode[i].rc != SMAPI_RETCODE_EOF && smapi_retcode[i].rc != rc; ++i) {} ret = smapi_retcode[i].ret; if (msg) *msg = smapi_retcode[i].msg; DPRINTK("req_out: AX=%x BX=%x CX=%x DX=%x DI=%x SI=%x r=%d", tmpEAX, tmpEBX, tmpECX, tmpEDX, tmpEDI, tmpESI, ret); if (ret) TPRINTK(KERN_NOTICE, "SMAPI error: %s (func=%x)", smapi_retcode[i].msg, inEBX); if (ret != -EBUSY) return ret; } return ret; } /* Convenience wrapper: discard output arguments */ static int smapi_write(u32 inEBX, u32 inECX, u32 inEDI, u32 inESI, const char **msg) { return smapi_request(inEBX, inECX, inEDI, inESI, NULL, NULL, NULL, NULL, NULL, msg); } /********************************************************************* * Specific SMAPI services * All of these functions return 0 upon success, and a negative errno * constant (see smapi_retcode) on failure. */ enum thresh_type { THRESH_STOP = 0, /* the code assumes this is 0 for brevity */ THRESH_START }; #define THRESH_NAME(which) ((which == THRESH_START) ? "start" : "stop") /** * __get_real_thresh - read battery charge start/stop threshold from SMAPI * @bat: battery number (0 or 1) * @which: THRESH_START or THRESH_STOP * @thresh: 1..99, 0=default 1..99, 0=default (pass this as-is to SMAPI) * @outEDI: some additional state that needs to be preserved, meaning unknown * @outESI: some additional state that needs to be preserved, meaning unknown */ static int __get_real_thresh(int bat, enum thresh_type which, int *thresh, u32 *outEDI, u32 *outESI) { u32 ebx = (which == THRESH_START) ? SMAPI_GET_THRESH_START : SMAPI_GET_THRESH_STOP; u32 ecx = (bat+1)<<8; const char *msg; int ret = smapi_request(ebx, ecx, 0, 0, NULL, &ecx, NULL, outEDI, outESI, &msg); if (ret) { TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: %s", THRESH_NAME(which), bat, msg); return ret; } if (!(ecx&0x00000100)) { TPRINTK(KERN_NOTICE, "cannot get %s_thresh of bat=%d: ecx=0%x", THRESH_NAME(which), bat, ecx); return -EIO; } if (thresh) *thresh = ecx&0xFF; return 0; } /** * get_real_thresh - read battery charge start/stop threshold from SMAPI * @bat: battery number (0 or 1) * @which: THRESH_START or THRESH_STOP * @thresh: 1..99, 0=default (passes as-is to SMAPI) */ static int get_real_thresh(int bat, enum thresh_type which, int *thresh) { return __get_real_thresh(bat, which, thresh, NULL, NULL); } /** * set_real_thresh - write battery start/top charge threshold to SMAPI * @bat: battery number (0 or 1) * @which: THRESH_START or THRESH_STOP * @thresh: 1..99, 0=default (passes as-is to SMAPI) */ static int set_real_thresh(int bat, enum thresh_type which, int thresh) { u32 ebx = (which == THRESH_START) ? SMAPI_SET_THRESH_START : SMAPI_SET_THRESH_STOP; u32 ecx = ((bat+1)<<8) + thresh; u32 getDI, getSI; const char *msg; int ret; /* verify read before writing */ ret = __get_real_thresh(bat, which, NULL, &getDI, &getSI); if (ret) return ret; ret = smapi_write(ebx, ecx, getDI, getSI, &msg); if (ret) TPRINTK(KERN_NOTICE, "set %s to %d for bat=%d failed: %s", THRESH_NAME(which), thresh, bat, msg); else TPRINTK(KERN_INFO, "set %s to %d for bat=%d", THRESH_NAME(which), thresh, bat); return ret; } /** * __get_inhibit_charge_minutes - get inhibit charge period from SMAPI * @bat: battery number (0 or 1) * @minutes: period in minutes (1..65535 minutes, 0=disabled) * @outECX: some additional state that needs to be preserved, meaning unknown * Note that @minutes is the originally set value, it does not count down. */ static int __get_inhibit_charge_minutes(int bat, int *minutes, u32 *outECX) { u32 ecx = (bat+1)<<8; u32 esi; const char *msg; int ret = smapi_request(SMAPI_GET_INHIBIT_CHARGE, ecx, 0, 0, NULL, &ecx, NULL, NULL, &esi, &msg); if (ret) { TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); return ret; } if (!(ecx&0x0100)) { TPRINTK(KERN_NOTICE, "bad ecx=0x%x for bat=%d", ecx, bat); return -EIO; } if (minutes) *minutes = (ecx&0x0001)?esi:0; if (outECX) *outECX = ecx; return 0; } /** * get_inhibit_charge_minutes - get inhibit charge period from SMAPI * @bat: battery number (0 or 1) * @minutes: period in minutes (1..65535 minutes, 0=disabled) * Note that @minutes is the originally set value, it does not count down. */ static int get_inhibit_charge_minutes(int bat, int *minutes) { return __get_inhibit_charge_minutes(bat, minutes, NULL); } /** * set_inhibit_charge_minutes - write inhibit charge period to SMAPI * @bat: battery number (0 or 1) * @minutes: period in minutes (1..65535 minutes, 0=disabled) */ static int set_inhibit_charge_minutes(int bat, int minutes) { u32 ecx; const char *msg; int ret; /* verify read before writing */ ret = __get_inhibit_charge_minutes(bat, NULL, &ecx); if (ret) return ret; ecx = ((bat+1)<<8) | (ecx&0x00FE) | (minutes > 0 ? 0x0001 : 0x0000); if (minutes > 0xFFFF) minutes = 0xFFFF; ret = smapi_write(SMAPI_SET_INHIBIT_CHARGE, ecx, 0, minutes, &msg); if (ret) TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s", minutes, bat, msg); else TPRINTK(KERN_INFO, "set to %d for bat=%d\n", minutes, bat); return ret; } /** * get_force_discharge - get status of forced discharging from SMAPI * @bat: battery number (0 or 1) * @enabled: 1 if forced discharged is enabled, 0 if not */ static int get_force_discharge(int bat, int *enabled) { u32 ecx = (bat+1)<<8; const char *msg; int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, NULL, &ecx, NULL, NULL, NULL, &msg); if (ret) { TPRINTK(KERN_NOTICE, "failed for bat=%d: %s", bat, msg); return ret; } *enabled = (!(ecx&0x00000100) && (ecx&0x00000001))?1:0; return 0; } /** * set_force_discharge - write status of forced discharging to SMAPI * @bat: battery number (0 or 1) * @enabled: 1 if forced discharged is enabled, 0 if not */ static int set_force_discharge(int bat, int enabled) { u32 ecx = (bat+1)<<8; const char *msg; int ret = smapi_request(SMAPI_GET_FORCE_DISCHARGE, ecx, 0, 0, NULL, &ecx, NULL, NULL, NULL, &msg); if (ret) { TPRINTK(KERN_NOTICE, "get failed for bat=%d: %s", bat, msg); return ret; } if (ecx&0x00000100) { TPRINTK(KERN_NOTICE, "cannot force discharge bat=%d", bat); return -EIO; } ecx = ((bat+1)<<8) | (ecx&0x000000FA) | (enabled?0x00000001:0); ret = smapi_write(SMAPI_SET_FORCE_DISCHARGE, ecx, 0, 0, &msg); if (ret) TPRINTK(KERN_NOTICE, "set to %d failed for bat=%d: %s", enabled, bat, msg); else TPRINTK(KERN_INFO, "set to %d for bat=%d", enabled, bat); return ret; } /********************************************************************* * Wrappers to threshold-related SMAPI functions, which handle default * thresholds and related quirks. */ /* Minimum, default and minimum difference for battery charging thresholds: */ #define MIN_THRESH_DELTA 4 /* Min delta between start and stop thresh */ #define MIN_THRESH_START 2 #define MAX_THRESH_START (100-MIN_THRESH_DELTA) #define MIN_THRESH_STOP (MIN_THRESH_START + MIN_THRESH_DELTA) #define MAX_THRESH_STOP 100 #define DEFAULT_THRESH_START MAX_THRESH_START #define DEFAULT_THRESH_STOP MAX_THRESH_STOP /* The GUI of IBM's Battery Maximizer seems to show a start threshold that * is 1 more than the value we set/get via SMAPI. Since the threshold is * maintained across reboot, this can be confusing. So we kludge our * interface for interoperability: */ #define BATMAX_FIX 1 /* Get charge start/stop threshold (1..100), * substituting default values if needed and applying BATMAT_FIX. */ static int get_thresh(int bat, enum thresh_type which, int *thresh) { int ret = get_real_thresh(bat, which, thresh); if (ret) return ret; if (*thresh == 0) *thresh = (which == THRESH_START) ? DEFAULT_THRESH_START : DEFAULT_THRESH_STOP; else if (which == THRESH_START) *thresh += BATMAX_FIX; return 0; } /* Set charge start/stop threshold (1..100), * substituting default values if needed and applying BATMAT_FIX. */ static int set_thresh(int bat, enum thresh_type which, int thresh) { if (which == THRESH_STOP && thresh == DEFAULT_THRESH_STOP) thresh = 0; /* 100 is out of range, but default means 100 */ if (which == THRESH_START) thresh -= BATMAX_FIX; return set_real_thresh(bat, which, thresh); } /********************************************************************* * ThinkPad embedded controller readout and basic functions */ /** * read_tp_ec_row - read data row from the ThinkPad embedded controller * @arg0: EC command code * @bat: battery number, 0 or 1 * @j: the byte value to be used for "junk" (unused) input/outputs * @dataval: result vector */ static int read_tp_ec_row(u8 arg0, int bat, u8 j, u8 *dataval) { int ret; const struct thinkpad_ec_row args = { .mask = 0xFFFF, .val = {arg0, j,j,j,j,j,j,j,j,j,j,j,j,j,j, (u8)bat} }; struct thinkpad_ec_row data = { .mask = 0xFFFF }; ret = thinkpad_ec_lock(); if (ret) return ret; ret = thinkpad_ec_read_row(&args, &data); thinkpad_ec_unlock(); memcpy(dataval, &data.val, TP_CONTROLLER_ROW_LEN); return ret; } /** * power_device_present - check for presence of battery or AC power * @bat: 0 for battery 0, 1 for battery 1, otherwise AC power * Returns 1 if present, 0 if not present, negative if error. */ static int power_device_present(int bat) { u8 row[TP_CONTROLLER_ROW_LEN]; u8 test; int ret = read_tp_ec_row(1, bat, 0, row); if (ret) return ret; switch (bat) { case 0: test = 0x40; break; /* battery 0 */ case 1: test = 0x20; break; /* battery 1 */ default: test = 0x80; /* AC power */ } return (row[0] & test) ? 1 : 0; } /** * bat_has_status - check if battery can report detailed status * @bat: 0 for battery 0, 1 for battery 1 * Returns 1 if yes, 0 if no, negative if error. */ static int bat_has_status(int bat) { u8 row[TP_CONTROLLER_ROW_LEN]; int ret = read_tp_ec_row(1, bat, 0, row); if (ret) return ret; if ((row[0] & (bat?0x20:0x40)) == 0) /* no battery */ return 0; if ((row[1] & (0x60)) == 0) /* no status */ return 0; return 1; } /** * get_tp_ec_bat_16 - read a 16-bit value from EC battery status data * @arg0: first argument to EC * @off: offset in row returned from EC * @bat: battery (0 or 1) * @val: the 16-bit value obtained * Returns nonzero on error. */ static int get_tp_ec_bat_16(u8 arg0, int offset, int bat, u16 *val) { u8 row[TP_CONTROLLER_ROW_LEN]; int ret; if (bat_has_status(bat) != 1) return -ENXIO; ret = read_tp_ec_row(arg0, bat, 0, row); if (ret) return ret; *val = *(u16 *)(row+offset); return 0; } /********************************************************************* * sysfs attributes for batteries - * definitions and helper functions */ /* A custom device attribute struct which holds a battery number */ struct bat_device_attribute { struct device_attribute dev_attr; int bat; }; /** * attr_get_bat - get the battery to which the attribute belongs */ static int attr_get_bat(struct device_attribute *attr) { return container_of(attr, struct bat_device_attribute, dev_attr)->bat; } /** * show_tp_ec_bat_u16 - show an unsigned 16-bit battery attribute * @arg0: specified 1st argument of EC raw to read * @offset: byte offset in EC raw data * @mul: correction factor to multiply by * @na_msg: string to output is value not available (0xFFFFFFFF) * @attr: battery attribute * @buf: output buffer * The 16-bit value is read from the EC, treated as unsigned, * transformed as x->mul*x, and printed to the buffer. * If the value is 0xFFFFFFFF and na_msg!=%NULL, na_msg is printed instead. */ static ssize_t show_tp_ec_bat_u16(u8 arg0, int offset, int mul, const char *na_msg, struct device_attribute *attr, char *buf) { u16 val; int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); if (ret) return ret; if (na_msg && val == 0xFFFF) return sprintf(buf, "%s\n", na_msg); else return sprintf(buf, "%u\n", mul*(unsigned int)val); } /** * show_tp_ec_bat_s16 - show an signed 16-bit battery attribute * @arg0: specified 1st argument of EC raw to read * @offset: byte offset in EC raw data * @mul: correction factor to multiply by * @add: correction term to add after multiplication * @attr: battery attribute * @buf: output buffer * The 16-bit value is read from the EC, treated as signed, * transformed as x->mul*x+add, and printed to the buffer. */ static ssize_t show_tp_ec_bat_s16(u8 arg0, int offset, int mul, int add, struct device_attribute *attr, char *buf) { u16 val; int ret = get_tp_ec_bat_16(arg0, offset, attr_get_bat(attr), &val); if (ret) return ret; return sprintf(buf, "%d\n", mul*(s16)val+add); } /** * show_tp_ec_bat_str - show a string from EC battery status data * @arg0: specified 1st argument of EC raw to read * @offset: byte offset in EC raw data * @maxlen: maximum string length * @attr: battery attribute * @buf: output buffer */ static ssize_t show_tp_ec_bat_str(u8 arg0, int offset, int maxlen, struct device_attribute *attr, char *buf) { int bat = attr_get_bat(attr); u8 row[TP_CONTROLLER_ROW_LEN]; int ret; if (bat_has_status(bat) != 1) return -ENXIO; ret = read_tp_ec_row(arg0, bat, 0, row); if (ret) return ret; strncpy(buf, (char *)row+offset, maxlen); buf[maxlen] = 0; strcat(buf, "\n"); return strlen(buf); } /** * show_tp_ec_bat_power - show a power readout from EC battery status data * @arg0: specified 1st argument of EC raw to read * @offV: byte offset of voltage in EC raw data * @offI: byte offset of current in EC raw data * @attr: battery attribute * @buf: output buffer * Computes the power as current*voltage from the two given readout offsets. */ static ssize_t show_tp_ec_bat_power(u8 arg0, int offV, int offI, struct device_attribute *attr, char *buf) { u8 row[TP_CONTROLLER_ROW_LEN]; int milliamp, millivolt, ret; int bat = attr_get_bat(attr); if (bat_has_status(bat) != 1) return -ENXIO; ret = read_tp_ec_row(1, bat, 0, row); if (ret) return ret; millivolt = *(u16 *)(row+offV); milliamp = *(s16 *)(row+offI); return sprintf(buf, "%d\n", milliamp*millivolt/1000); /* units: mW */ } /** * show_tp_ec_bat_date - decode and show a date from EC battery status data * @arg0: specified 1st argument of EC raw to read * @offset: byte offset in EC raw data * @attr: battery attribute * @buf: output buffer */ static ssize_t show_tp_ec_bat_date(u8 arg0, int offset, struct device_attribute *attr, char *buf) { u8 row[TP_CONTROLLER_ROW_LEN]; u16 v; int ret; int day, month, year; int bat = attr_get_bat(attr); if (bat_has_status(bat) != 1) return -ENXIO; ret = read_tp_ec_row(arg0, bat, 0, row); if (ret) return ret; /* Decode bit-packed: v = day | (month<<5) | ((year-1980)<<9) */ v = *(u16 *)(row+offset); day = v & 0x1F; month = (v >> 5) & 0xF; year = (v >> 9) + 1980; return sprintf(buf, "%04d-%02d-%02d\n", year, month, day); } /********************************************************************* * sysfs attribute I/O for batteries - * the actual attribute show/store functions */ static ssize_t show_battery_start_charge_thresh(struct device *dev, struct device_attribute *attr, char *buf) { int thresh; int bat = attr_get_bat(attr); int ret = get_thresh(bat, THRESH_START, &thresh); if (ret) return ret; return sprintf(buf, "%d\n", thresh); /* units: percent */ } static ssize_t show_battery_stop_charge_thresh(struct device *dev, struct device_attribute *attr, char *buf) { int thresh; int bat = attr_get_bat(attr); int ret = get_thresh(bat, THRESH_STOP, &thresh); if (ret) return ret; return sprintf(buf, "%d\n", thresh); /* units: percent */ } /** * store_battery_start_charge_thresh - store battery_start_charge_thresh attr * Since this is a kernel<->user interface, we ensure a valid state for * the hardware. We do this by clamping the requested threshold to the * valid range and, if necessary, moving the other threshold so that * it's MIN_THRESH_DELTA away from this one. */ static ssize_t store_battery_start_charge_thresh(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int thresh, other_thresh, ret; int bat = attr_get_bat(attr); if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) return -EINVAL; if (thresh < MIN_THRESH_START) /* clamp up to MIN_THRESH_START */ thresh = MIN_THRESH_START; if (thresh > MAX_THRESH_START) /* clamp down to MAX_THRESH_START */ thresh = MAX_THRESH_START; down(&smapi_mutex); ret = get_thresh(bat, THRESH_STOP, &other_thresh); if (ret != -EOPNOTSUPP && ret != -ENXIO) { if (ret) /* other threshold is set? */ goto out; ret = get_real_thresh(bat, THRESH_START, NULL); if (ret) /* this threshold is set? */ goto out; if (other_thresh < thresh+MIN_THRESH_DELTA) { /* move other thresh to keep it above this one */ ret = set_thresh(bat, THRESH_STOP, thresh+MIN_THRESH_DELTA); if (ret) goto out; } } ret = set_thresh(bat, THRESH_START, thresh); out: up(&smapi_mutex); return count; } /** * store_battery_stop_charge_thresh - store battery_stop_charge_thresh attr * Since this is a kernel<->user interface, we ensure a valid state for * the hardware. We do this by clamping the requested threshold to the * valid range and, if necessary, moving the other threshold so that * it's MIN_THRESH_DELTA away from this one. */ static ssize_t store_battery_stop_charge_thresh(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int thresh, other_thresh, ret; int bat = attr_get_bat(attr); if (sscanf(buf, "%d", &thresh) != 1 || thresh < 1 || thresh > 100) return -EINVAL; if (thresh < MIN_THRESH_STOP) /* clamp up to MIN_THRESH_STOP */ thresh = MIN_THRESH_STOP; down(&smapi_mutex); ret = get_thresh(bat, THRESH_START, &other_thresh); if (ret != -EOPNOTSUPP && ret != -ENXIO) { /* other threshold exists? */ if (ret) goto out; /* this threshold exists? */ ret = get_real_thresh(bat, THRESH_STOP, NULL); if (ret) goto out; if (other_thresh >= thresh-MIN_THRESH_DELTA) { /* move other thresh to be below this one */ ret = set_thresh(bat, THRESH_START, thresh-MIN_THRESH_DELTA); if (ret) goto out; } } ret = set_thresh(bat, THRESH_STOP, thresh); out: up(&smapi_mutex); return count; } static ssize_t show_battery_inhibit_charge_minutes(struct device *dev, struct device_attribute *attr, char *buf) { int minutes; int bat = attr_get_bat(attr); int ret = get_inhibit_charge_minutes(bat, &minutes); if (ret) return ret; return sprintf(buf, "%d\n", minutes); /* units: minutes */ } static ssize_t store_battery_inhibit_charge_minutes(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; int minutes; int bat = attr_get_bat(attr); if (sscanf(buf, "%d", &minutes) != 1 || minutes < 0) { TPRINTK(KERN_ERR, "inhibit_charge_minutes: " "must be a non-negative integer"); return -EINVAL; } ret = set_inhibit_charge_minutes(bat, minutes); if (ret) return ret; return count; } static ssize_t show_battery_force_discharge(struct device *dev, struct device_attribute *attr, char *buf) { int enabled; int bat = attr_get_bat(attr); int ret = get_force_discharge(bat, &enabled); if (ret) return ret; return sprintf(buf, "%d\n", enabled); /* type: boolean */ } static ssize_t store_battery_force_discharge(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; int enabled; int bat = attr_get_bat(attr); if (sscanf(buf, "%d", &enabled) != 1 || enabled < 0 || enabled > 1) return -EINVAL; ret = set_force_discharge(bat, enabled); if (ret) return ret; return count; } static ssize_t show_battery_installed( struct device *dev, struct device_attribute *attr, char *buf) { int bat = attr_get_bat(attr); int ret = power_device_present(bat); if (ret < 0) return ret; return sprintf(buf, "%d\n", ret); /* type: boolean */ } static ssize_t show_battery_state( struct device *dev, struct device_attribute *attr, char *buf) { u8 row[TP_CONTROLLER_ROW_LEN]; const char *txt; int ret; int bat = attr_get_bat(attr); if (bat_has_status(bat) != 1) return sprintf(buf, "none\n"); ret = read_tp_ec_row(1, bat, 0, row); if (ret) return ret; switch (row[1] & 0xf0) { case 0xc0: txt = "idle"; break; case 0xd0: txt = "discharging"; break; case 0xe0: txt = "charging"; break; default: return sprintf(buf, "unknown (0x%x)\n", row[1]); } return sprintf(buf, "%s\n", txt); /* type: string from fixed set */ } static ssize_t show_battery_manufacturer( struct device *dev, struct device_attribute *attr, char *buf) { /* type: string. SBS spec v1.1 p34: ManufacturerName() */ return show_tp_ec_bat_str(4, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); } static ssize_t show_battery_model( struct device *dev, struct device_attribute *attr, char *buf) { /* type: string. SBS spec v1.1 p34: DeviceName() */ return show_tp_ec_bat_str(5, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); } static ssize_t show_battery_barcoding( struct device *dev, struct device_attribute *attr, char *buf) { /* type: string */ return show_tp_ec_bat_str(7, 2, TP_CONTROLLER_ROW_LEN-2, attr, buf); } static ssize_t show_battery_chemistry( struct device *dev, struct device_attribute *attr, char *buf) { /* type: string. SBS spec v1.1 p34-35: DeviceChemistry() */ return show_tp_ec_bat_str(6, 2, 5, attr, buf); } static ssize_t show_battery_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV. SBS spec v1.1 p24: Voltage() */ return show_tp_ec_bat_u16(1, 6, 1, NULL, attr, buf); } static ssize_t show_battery_design_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV. SBS spec v1.1 p32: DesignVoltage() */ return show_tp_ec_bat_u16(3, 4, 1, NULL, attr, buf); } static ssize_t show_battery_charging_max_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV. SBS spec v1.1 p37,39: ChargingVoltage() */ return show_tp_ec_bat_u16(9, 8, 1, NULL, attr, buf); } static ssize_t show_battery_group0_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV */ return show_tp_ec_bat_u16(0xA, 12, 1, NULL, attr, buf); } static ssize_t show_battery_group1_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV */ return show_tp_ec_bat_u16(0xA, 10, 1, NULL, attr, buf); } static ssize_t show_battery_group2_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV */ return show_tp_ec_bat_u16(0xA, 8, 1, NULL, attr, buf); } static ssize_t show_battery_group3_voltage( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mV */ return show_tp_ec_bat_u16(0xA, 6, 1, NULL, attr, buf); } static ssize_t show_battery_current_now( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mA. SBS spec v1.1 p24: Current() */ return show_tp_ec_bat_s16(1, 8, 1, 0, attr, buf); } static ssize_t show_battery_current_avg( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mA. SBS spec v1.1 p24: AverageCurrent() */ return show_tp_ec_bat_s16(1, 10, 1, 0, attr, buf); } static ssize_t show_battery_charging_max_current( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mA. SBS spec v1.1 p36,38: ChargingCurrent() */ return show_tp_ec_bat_s16(9, 6, 1, 0, attr, buf); } static ssize_t show_battery_power_now( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mW. SBS spec v1.1: Voltage()*Current() */ return show_tp_ec_bat_power(1, 6, 8, attr, buf); } static ssize_t show_battery_power_avg( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mW. SBS spec v1.1: Voltage()*AverageCurrent() */ return show_tp_ec_bat_power(1, 6, 10, attr, buf); } static ssize_t show_battery_remaining_percent( struct device *dev, struct device_attribute *attr, char *buf) { /* units: percent. SBS spec v1.1 p25: RelativeStateOfCharge() */ return show_tp_ec_bat_u16(1, 12, 1, NULL, attr, buf); } static ssize_t show_battery_remaining_percent_error( struct device *dev, struct device_attribute *attr, char *buf) { /* units: percent. SBS spec v1.1 p25: MaxError() */ return show_tp_ec_bat_u16(9, 4, 1, NULL, attr, buf); } static ssize_t show_battery_remaining_charging_time( struct device *dev, struct device_attribute *attr, char *buf) { /* units: minutes. SBS spec v1.1 p27: AverageTimeToFull() */ return show_tp_ec_bat_u16(2, 8, 1, "not_charging", attr, buf); } static ssize_t show_battery_remaining_running_time( struct device *dev, struct device_attribute *attr, char *buf) { /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ return show_tp_ec_bat_u16(2, 6, 1, "not_discharging", attr, buf); } static ssize_t show_battery_remaining_running_time_now( struct device *dev, struct device_attribute *attr, char *buf) { /* units: minutes. SBS spec v1.1 p27: RunTimeToEmpty() */ return show_tp_ec_bat_u16(2, 4, 1, "not_discharging", attr, buf); } static ssize_t show_battery_remaining_capacity( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mWh. SBS spec v1.1 p26. */ return show_tp_ec_bat_u16(1, 14, 10, "", attr, buf); } static ssize_t show_battery_last_full_capacity( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mWh. SBS spec v1.1 p26: FullChargeCapacity() */ return show_tp_ec_bat_u16(2, 2, 10, "", attr, buf); } static ssize_t show_battery_design_capacity( struct device *dev, struct device_attribute *attr, char *buf) { /* units: mWh. SBS spec v1.1 p32: DesignCapacity() */ return show_tp_ec_bat_u16(3, 2, 10, "", attr, buf); } static ssize_t show_battery_cycle_count( struct device *dev, struct device_attribute *attr, char *buf) { /* units: ordinal. SBS spec v1.1 p32: CycleCount() */ return show_tp_ec_bat_u16(2, 12, 1, "", attr, buf); } static ssize_t show_battery_temperature( struct device *dev, struct device_attribute *attr, char *buf) { /* units: millicelsius. SBS spec v1.1: Temperature()*10 */ return show_tp_ec_bat_s16(1, 4, 100, -273100, attr, buf); } static ssize_t show_battery_serial( struct device *dev, struct device_attribute *attr, char *buf) { /* type: int. SBS spec v1.1 p34: SerialNumber() */ return show_tp_ec_bat_u16(3, 10, 1, "", attr, buf); } static ssize_t show_battery_manufacture_date( struct device *dev, struct device_attribute *attr, char *buf) { /* type: YYYY-MM-DD. SBS spec v1.1 p34: ManufactureDate() */ return show_tp_ec_bat_date(3, 8, attr, buf); } static ssize_t show_battery_first_use_date( struct device *dev, struct device_attribute *attr, char *buf) { /* type: YYYY-MM-DD */ return show_tp_ec_bat_date(8, 2, attr, buf); } /** * show_battery_dump - show the battery's dump attribute * The dump attribute gives a hex dump of all EC readouts related to a * battery. Some of the enumerated values don't really exist (i.e., the * EC function just leaves them untouched); we use a kludge to detect and * denote these. */ #define MIN_DUMP_ARG0 0x00 #define MAX_DUMP_ARG0 0x0a /* 0x0b is useful too but hangs old EC firmware */ static ssize_t show_battery_dump( struct device *dev, struct device_attribute *attr, char *buf) { int i; char *p = buf; int bat = attr_get_bat(attr); u8 arg0; /* first argument to EC */ u8 rowa[TP_CONTROLLER_ROW_LEN], rowb[TP_CONTROLLER_ROW_LEN]; const u8 junka = 0xAA, junkb = 0x55; /* junk values for testing changes */ int ret; for (arg0 = MIN_DUMP_ARG0; arg0 <= MAX_DUMP_ARG0; ++arg0) { if ((p-buf) > PAGE_SIZE-TP_CONTROLLER_ROW_LEN*5) return -ENOMEM; /* don't overflow sysfs buf */ /* Read raw twice with different junk values, * to detect unused output bytes which are left unchaged: */ ret = read_tp_ec_row(arg0, bat, junka, rowa); if (ret) return ret; ret = read_tp_ec_row(arg0, bat, junkb, rowb); if (ret) return ret; for (i = 0; i < TP_CONTROLLER_ROW_LEN; i++) { if (rowa[i] == junka && rowb[i] == junkb) p += sprintf(p, "-- "); /* unused by EC */ else p += sprintf(p, "%02x ", rowa[i]); } p += sprintf(p, "\n"); } return p-buf; } /********************************************************************* * sysfs attribute I/O, other than batteries */ static ssize_t show_ac_connected( struct device *dev, struct device_attribute *attr, char *buf) { int ret = power_device_present(0xFF); if (ret < 0) return ret; return sprintf(buf, "%d\n", ret); /* type: boolean */ } /********************************************************************* * The the "smapi_request" sysfs attribute executes a raw SMAPI call. * You write to make a request and read to get the result. The state * is saved globally rather than per fd (sysfs limitation), so * simultaenous requests may get each other's results! So this is for * development and debugging only. */ #define MAX_SMAPI_ATTR_ANSWER_LEN 128 static char smapi_attr_answer[MAX_SMAPI_ATTR_ANSWER_LEN] = ""; static ssize_t show_smapi_request(struct device *dev, struct device_attribute *attr, char *buf) { int ret = snprintf(buf, PAGE_SIZE, "%s", smapi_attr_answer); smapi_attr_answer[0] = '\0'; return ret; } static ssize_t store_smapi_request(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned int inEBX, inECX, inEDI, inESI; u32 outEBX, outECX, outEDX, outEDI, outESI; const char *msg; int ret; if (sscanf(buf, "%x %x %x %x", &inEBX, &inECX, &inEDI, &inESI) != 4) { smapi_attr_answer[0] = '\0'; return -EINVAL; } ret = smapi_request( inEBX, inECX, inEDI, inESI, &outEBX, &outECX, &outEDX, &outEDI, &outESI, &msg); snprintf(smapi_attr_answer, MAX_SMAPI_ATTR_ANSWER_LEN, "%x %x %x %x %x %d '%s'\n", (unsigned int)outEBX, (unsigned int)outECX, (unsigned int)outEDX, (unsigned int)outEDI, (unsigned int)outESI, ret, msg); if (ret) return ret; else return count; } /********************************************************************* * Power management: the embedded controller forgets the battery * thresholds when the system is suspended to disk and unplugged from * AC and battery, so we restore it upon resume. */ static int saved_threshs[4] = {-1, -1, -1, -1}; /* -1 = don't know */ static int tp_suspend(struct platform_device *dev, pm_message_t state) { int restore = (state.event == PM_EVENT_HIBERNATE || state.event == PM_EVENT_FREEZE); if (!restore || get_real_thresh(0, THRESH_STOP , &saved_threshs[0])) saved_threshs[0] = -1; if (!restore || get_real_thresh(0, THRESH_START, &saved_threshs[1])) saved_threshs[1] = -1; if (!restore || get_real_thresh(1, THRESH_STOP , &saved_threshs[2])) saved_threshs[2] = -1; if (!restore || get_real_thresh(1, THRESH_START, &saved_threshs[3])) saved_threshs[3] = -1; DPRINTK("suspend saved: %d %d %d %d", saved_threshs[0], saved_threshs[1], saved_threshs[2], saved_threshs[3]); return 0; } static int tp_resume(struct platform_device *dev) { DPRINTK("resume restoring: %d %d %d %d", saved_threshs[0], saved_threshs[1], saved_threshs[2], saved_threshs[3]); if (saved_threshs[0] >= 0) set_real_thresh(0, THRESH_STOP , saved_threshs[0]); if (saved_threshs[1] >= 0) set_real_thresh(0, THRESH_START, saved_threshs[1]); if (saved_threshs[2] >= 0) set_real_thresh(1, THRESH_STOP , saved_threshs[2]); if (saved_threshs[3] >= 0) set_real_thresh(1, THRESH_START, saved_threshs[3]); return 0; } /********************************************************************* * Driver model */ static struct platform_driver tp_driver = { .suspend = tp_suspend, .resume = tp_resume, .driver = { .name = "smapi", .owner = THIS_MODULE }, }; /********************************************************************* * Sysfs device model */ /* Attributes in /sys/devices/platform/smapi/ */ static DEVICE_ATTR(ac_connected, 0444, show_ac_connected, NULL); static DEVICE_ATTR(smapi_request, 0600, show_smapi_request, store_smapi_request); static struct attribute *tp_root_attributes[] = { &dev_attr_ac_connected.attr, &dev_attr_smapi_request.attr, NULL }; static struct attribute_group tp_root_attribute_group = { .attrs = tp_root_attributes }; /* Attributes under /sys/devices/platform/smapi/BAT{0,1}/ : * Every attribute needs to be defined (i.e., statically allocated) for * each battery, and then referenced in the attribute list of each battery. * We use preprocessor voodoo to avoid duplicating the list of attributes 4 * times. The preprocessor output is just normal sysfs attributes code. */ /** * FOREACH_BAT_ATTR - invoke the given macros on all our battery attributes * @_BAT: battery number (0 or 1) * @_ATTR_RW: macro to invoke for each read/write attribute * @_ATTR_R: macro to invoke for each read-only attribute */ #define FOREACH_BAT_ATTR(_BAT, _ATTR_RW, _ATTR_R) \ _ATTR_RW(_BAT, start_charge_thresh) \ _ATTR_RW(_BAT, stop_charge_thresh) \ _ATTR_RW(_BAT, inhibit_charge_minutes) \ _ATTR_RW(_BAT, force_discharge) \ _ATTR_R(_BAT, installed) \ _ATTR_R(_BAT, state) \ _ATTR_R(_BAT, manufacturer) \ _ATTR_R(_BAT, model) \ _ATTR_R(_BAT, barcoding) \ _ATTR_R(_BAT, chemistry) \ _ATTR_R(_BAT, voltage) \ _ATTR_R(_BAT, group0_voltage) \ _ATTR_R(_BAT, group1_voltage) \ _ATTR_R(_BAT, group2_voltage) \ _ATTR_R(_BAT, group3_voltage) \ _ATTR_R(_BAT, current_now) \ _ATTR_R(_BAT, current_avg) \ _ATTR_R(_BAT, charging_max_current) \ _ATTR_R(_BAT, power_now) \ _ATTR_R(_BAT, power_avg) \ _ATTR_R(_BAT, remaining_percent) \ _ATTR_R(_BAT, remaining_percent_error) \ _ATTR_R(_BAT, remaining_charging_time) \ _ATTR_R(_BAT, remaining_running_time) \ _ATTR_R(_BAT, remaining_running_time_now) \ _ATTR_R(_BAT, remaining_capacity) \ _ATTR_R(_BAT, last_full_capacity) \ _ATTR_R(_BAT, design_voltage) \ _ATTR_R(_BAT, charging_max_voltage) \ _ATTR_R(_BAT, design_capacity) \ _ATTR_R(_BAT, cycle_count) \ _ATTR_R(_BAT, temperature) \ _ATTR_R(_BAT, serial) \ _ATTR_R(_BAT, manufacture_date) \ _ATTR_R(_BAT, first_use_date) \ _ATTR_R(_BAT, dump) /* Define several macros we will feed into FOREACH_BAT_ATTR: */ #define DEFINE_BAT_ATTR_RW(_BAT,_NAME) \ static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, \ store_battery_##_NAME), \ .bat = _BAT \ }; #define DEFINE_BAT_ATTR_R(_BAT,_NAME) \ static struct bat_device_attribute dev_attr_##_NAME##_##_BAT = { \ .dev_attr = __ATTR(_NAME, 0644, show_battery_##_NAME, 0), \ .bat = _BAT \ }; #define REF_BAT_ATTR(_BAT,_NAME) \ &dev_attr_##_NAME##_##_BAT.dev_attr.attr, /* This provide all attributes for one battery: */ #define PROVIDE_BAT_ATTRS(_BAT) \ FOREACH_BAT_ATTR(_BAT, DEFINE_BAT_ATTR_RW, DEFINE_BAT_ATTR_R) \ static struct attribute *tp_bat##_BAT##_attributes[] = { \ FOREACH_BAT_ATTR(_BAT, REF_BAT_ATTR, REF_BAT_ATTR) \ NULL \ }; \ static struct attribute_group tp_bat##_BAT##_attribute_group = { \ .name = "BAT" #_BAT, \ .attrs = tp_bat##_BAT##_attributes \ }; /* Finally genereate the attributes: */ PROVIDE_BAT_ATTRS(0) PROVIDE_BAT_ATTRS(1) /* List of attribute groups */ static struct attribute_group *attr_groups[] = { &tp_root_attribute_group, &tp_bat0_attribute_group, &tp_bat1_attribute_group, NULL }; /********************************************************************* * Init and cleanup */ static struct attribute_group **next_attr_group; /* next to register */ static int __init tp_init(void) { int ret; printk(KERN_INFO "tp_smapi " TP_VERSION " loading...\n"); ret = find_smapi_port(); if (ret < 0) goto err; else smapi_port = ret; if (!request_region(smapi_port, 1, "smapi")) { printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", smapi_port); ret = -ENXIO; goto err; } if (!request_region(SMAPI_PORT2, 1, "smapi")) { printk(KERN_ERR "tp_smapi cannot claim port 0x%x\n", SMAPI_PORT2); ret = -ENXIO; goto err_port1; } ret = platform_driver_register(&tp_driver); if (ret) goto err_port2; pdev = platform_device_alloc("smapi", -1); if (!pdev) { ret = -ENOMEM; goto err_driver; } ret = platform_device_add(pdev); if (ret) goto err_device_free; for (next_attr_group = attr_groups; *next_attr_group; ++next_attr_group) { ret = sysfs_create_group(&pdev->dev.kobj, *next_attr_group); if (ret) goto err_attr; } printk(KERN_INFO "tp_smapi successfully loaded (smapi_port=0x%x).\n", smapi_port); return 0; err_attr: while (--next_attr_group >= attr_groups) sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); platform_device_unregister(pdev); err_device_free: platform_device_put(pdev); err_driver: platform_driver_unregister(&tp_driver); err_port2: release_region(SMAPI_PORT2, 1); err_port1: release_region(smapi_port, 1); err: printk(KERN_ERR "tp_smapi init failed (ret=%d)!\n", ret); return ret; } static void __exit tp_exit(void) { while (next_attr_group && --next_attr_group >= attr_groups) sysfs_remove_group(&pdev->dev.kobj, *next_attr_group); platform_device_unregister(pdev); platform_driver_unregister(&tp_driver); release_region(SMAPI_PORT2, 1); if (smapi_port) release_region(smapi_port, 1); printk(KERN_INFO "tp_smapi unloaded.\n"); } module_init(tp_init); module_exit(tp_exit); 0707010000000C000081A40000000200000002000000015A918BF700000475000000000000000000000000000000000000001C00000000tp_smapi-0.43/tp_smapi.spec%define module tp_smapi %define version 0.43 Name: %{module} Version: %{version} Release: 1%{?dist} Summary: IBM ThinkPad hardware functions driver - DKMS version Group: Kernel/Drivers License: GPLv2 Source0: %{module}-%{version}.tgz Requires: dkms >= 1.00 Requires: kernel-headers Requires: kernel-devel BuildArch: noarch %description The package contains kernel driver for ThinkPad SMAPI (System Management Application Program Interface). The driver is built using DKMS. %prep %setup -q %install if [ "$RPM_BUILD_ROOT" != "/" ]; then rm -rf $RPM_BUILD_ROOT fi mkdir -p $RPM_BUILD_ROOT/usr/src/%{module}-%{version}/ cp -rf * $RPM_BUILD_ROOT/usr/src/%{module}-%{version} %clean if [ "$RPM_BUILD_ROOT" != "/" ]; then rm -rf $RPM_BUILD_ROOT fi %files %defattr(-,root,root) %doc README TODO %{_usrsrc}/%{module}-%{version}/ %doc %post dkms add -m %{module} -v %{version} --rpm_safe_upgrade dkms build -m %{module} -v %{version} dkms install -m %{module} -v %{version} %preun dkms remove -m %{module} -v %{version} --all --rpm_safe_upgrade %changelog 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!281 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor