mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge branch 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6
* 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6: hwmon: Add MAINTAINERS entry for new ams driver hwmon: New AMS hardware monitoring driver hwmon/w83793: Add documentation and maintainer hwmon: New Winbond W83793 hardware monitoring driver hwmon: Update Rudolf Marek's e-mail address hwmon/f71805f: Fix the device address decoding hwmon/f71805f: Always create all fan inputs hwmon/f71805f: Add support for the Fintek F71872F/FG chip hwmon: New PC87427 hardware monitoring driver hwmon/it87: Remove the SMBus interface support hwmon/hdaps: Update the list of supported devices hwmon/hdaps: Move the DMI detection data to .data hwmon/pc87360: Autodetect the VRM version hwmon/f71805f: Document the fan control features hwmon/f71805f: Add support for "speed mode" fan speed control hwmon/f71805f: Support DC fan speed control mode hwmon/f71805f: Let the user adjust the PWM base frequency hwmon/f71805f: Add manual fan speed control hwmon/f71805f: Store the fan control registers
This commit is contained in:
commit
bbc7610c06
30 files changed
+4115
-300
No files matched your search
@@ -151,15 +151,6 @@ Who: Thomas Gleixner <tglx@linutronix.de>
|
||||
|
||||
---------------------------
|
||||
|
||||
What: I2C interface of the it87 driver
|
||||
When: January 2007
|
||||
Why: The ISA interface is faster and should be always available. The I2C
|
||||
probing is also known to cause trouble in at least one case (see
|
||||
bug #5889.)
|
||||
Who: Jean Delvare <khali@linux-fr.org>
|
||||
|
||||
---------------------------
|
||||
|
||||
What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports
|
||||
(temporary transition config option provided until then)
|
||||
The transition config option will also be removed at the same time.
|
||||
|
||||
@@ -6,6 +6,10 @@ Supported chips:
|
||||
Prefix: 'f71805f'
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
Datasheet: Provided by Fintek on request
|
||||
* Fintek F71872F/FG
|
||||
Prefix: 'f71872f'
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
Datasheet: Provided by Fintek on request
|
||||
|
||||
Author: Jean Delvare <khali@linux-fr.org>
|
||||
|
||||
@@ -13,8 +17,8 @@ Thanks to Denis Kieft from Barracuda Networks for the donation of a
|
||||
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
|
||||
for providing initial documentation.
|
||||
|
||||
Thanks to Kris Chen from Fintek for answering technical questions and
|
||||
providing additional documentation.
|
||||
Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
|
||||
questions and providing additional documentation.
|
||||
|
||||
Thanks to Chris Lin from Jetway for providing wiring schematics and
|
||||
answering technical questions.
|
||||
@@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power
|
||||
source), 3 fans and 3 temperature sensors.
|
||||
|
||||
This chip also has fan controlling features, using either DC or PWM, in
|
||||
three different modes (one manual, two automatic). The driver doesn't
|
||||
support these features yet.
|
||||
three different modes (one manual, two automatic).
|
||||
|
||||
The Fintek F71872F/FG Super I/O chip is almost the same, with two
|
||||
additional internal voltages monitored (VSB and battery). It also features
|
||||
6 VID inputs. The VID inputs are not yet supported by this driver.
|
||||
|
||||
The driver assumes that no more than one chip is present, which seems
|
||||
reasonable.
|
||||
@@ -42,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
|
||||
range is thus from 0 to 2.040 V. Voltage values outside of this range
|
||||
need external resistors. An exception is in0, which is used to monitor
|
||||
the chip's own power source (+3.3V), and is divided internally by a
|
||||
factor 2.
|
||||
factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
|
||||
divided internally by a factor 2.
|
||||
|
||||
The two LSB of the voltage limit registers are not used (always 0), so
|
||||
you can only set the limits in steps of 32 mV (before scaling).
|
||||
@@ -61,9 +69,12 @@ in5 VIN5 +12V 200K 20K 11.00 1.05 V
|
||||
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
|
||||
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
|
||||
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
|
||||
in10 VSB VSB3.3V int. int. 2.00 1.65 V (3)
|
||||
in9 VBAT VBATTERY int. int. 2.00 1.50 V (3)
|
||||
|
||||
(1) Depends on your hardware setup.
|
||||
(2) Obviously not correct, swapping R1 and R2 would make more sense.
|
||||
(3) F71872F/FG only.
|
||||
|
||||
These values can be used as hints at best, as motherboard manufacturers
|
||||
are free to use a completely different setup. As a matter of fact, the
|
||||
@@ -103,3 +114,38 @@ sensor. Each channel can be used for connecting either a thermal diode
|
||||
or a thermistor. The driver reports the currently selected mode, but
|
||||
doesn't allow changing it. In theory, the BIOS should have configured
|
||||
everything properly.
|
||||
|
||||
|
||||
Fan Control
|
||||
-----------
|
||||
|
||||
Both PWM (pulse-width modulation) and DC fan speed control methods are
|
||||
supported. The right one to use depends on external circuitry on the
|
||||
motherboard, so the driver assumes that the BIOS set the method
|
||||
properly. The driver will report the method, but won't let you change
|
||||
it.
|
||||
|
||||
When the PWM method is used, you can select the operating frequency,
|
||||
from 187.5 kHz (default) to 31 Hz. The best frequency depends on the
|
||||
fan model. As a rule of thumb, lower frequencies seem to give better
|
||||
control, but may generate annoying high-pitch noise. Fintek recommends
|
||||
not going below 1 kHz, as the fan tachometers get confused by lower
|
||||
frequencies as well.
|
||||
|
||||
When the DC method is used, Fintek recommends not going below 5 V, which
|
||||
corresponds to a pwm value of 106 for the driver. The driver doesn't
|
||||
enforce this limit though.
|
||||
|
||||
Three different fan control modes are supported:
|
||||
|
||||
* Manual mode
|
||||
You ask for a specific PWM duty cycle or DC voltage.
|
||||
|
||||
* Fan speed mode
|
||||
You ask for a specific fan speed. This mode assumes that pwm1
|
||||
corresponds to fan1, pwm2 to fan2 and pwm3 to fan3.
|
||||
|
||||
* Temperature mode
|
||||
You define 3 temperature/fan speed trip points, and the fan speed is
|
||||
adjusted depending on the measured temperature, using interpolation.
|
||||
This mode is not yet supported by the driver.
|
||||
@@ -9,8 +9,7 @@ Supported chips:
|
||||
http://www.ite.com.tw/
|
||||
* IT8712F
|
||||
Prefix: 'it8712'
|
||||
Addresses scanned: I2C 0x2d
|
||||
from Super I/O config space (8 I/O ports)
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Publicly available at the ITE website
|
||||
http://www.ite.com.tw/
|
||||
* IT8716F
|
||||
@@ -53,6 +52,18 @@ Module Parameters
|
||||
misconfigured by BIOS - PWM values would be inverted. This option tries
|
||||
to fix this. Please contact your BIOS manufacturer and ask him for fix.
|
||||
|
||||
|
||||
Hardware Interfaces
|
||||
-------------------
|
||||
|
||||
All the chips suported by this driver are LPC Super-I/O chips, accessed
|
||||
through the LPC bus (ISA-like I/O ports). The IT8712F additionally has an
|
||||
SMBus interface to the hardware monitoring functions. This driver no
|
||||
longer supports this interface though, as it is slower and less reliable
|
||||
than the ISA access, and was only available on a small number of
|
||||
motherboard models.
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Supported chips:
|
||||
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
|
||||
|
||||
Author: Rudolf Marek
|
||||
Contact: Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Contact: Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
Kernel driver pc87427
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* National Semiconductor PC87427
|
||||
Prefix: 'pc87427'
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
Datasheet: http://www.winbond.com.tw/E-WINBONDHTM/partner/apc_007.html
|
||||
|
||||
Author: Jean Delvare <khali@linux-fr.org>
|
||||
|
||||
Thanks to Amir Habibi at Candelis for setting up a test system, and to
|
||||
Michael Kress for testing several iterations of this driver.
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The National Semiconductor Super I/O chip includes complete hardware
|
||||
monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
|
||||
6 temperature sensors. Only the fans are supported at the moment.
|
||||
|
||||
This chip also has fan controlling features, which are not yet supported
|
||||
by this driver either.
|
||||
|
||||
The driver assumes that no more than one chip is present, which seems
|
||||
reasonable.
|
||||
|
||||
|
||||
Fan Monitoring
|
||||
--------------
|
||||
|
||||
Fan rotation speeds are reported as 14-bit values from a gated clock
|
||||
signal. Speeds down to 83 RPM can be measured.
|
||||
|
||||
An alarm is triggered if the rotation speed drops below a programmable
|
||||
limit. Another alarm is triggered if the speed is too low to to be measured
|
||||
(including stalled or missing fan).
|
||||
@@ -208,12 +208,14 @@ temp[1-*]_auto_point[1-*]_temp_hyst
|
||||
****************
|
||||
|
||||
temp[1-*]_type Sensor type selection.
|
||||
Integers 1 to 4 or thermistor Beta value (typically 3435)
|
||||
Integers 1 to 6 or thermistor Beta value (typically 3435)
|
||||
RW
|
||||
1: PII/Celeron Diode
|
||||
2: 3904 transistor
|
||||
3: thermal diode
|
||||
4: thermistor (default/unknown Beta)
|
||||
5: AMD AMDSI
|
||||
6: Intel PECI
|
||||
Not all types are supported by all chips
|
||||
|
||||
temp[1-*]_max Temperature max value.
|
||||
|
||||
@@ -10,7 +10,7 @@ Supported chips:
|
||||
Authors:
|
||||
Jean Delvare <khali@linux-fr.org>
|
||||
Yuan Mu (Winbond)
|
||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
@@ -18,7 +18,7 @@ Credits:
|
||||
and Mark Studebaker <mdsxyz123@yahoo.com>
|
||||
w83792d.c:
|
||||
Chunhao Huang <DZShen@Winbond.com.tw>,
|
||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Additional contributors:
|
||||
Sven Anders <anders@anduras.de>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
Kernel driver w83793
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
* Winbond W83793G/W83793R
|
||||
Prefix: 'w83793'
|
||||
Addresses scanned: I2C 0x2c - 0x2f
|
||||
Datasheet: Still not published
|
||||
|
||||
Authors:
|
||||
Yuan Mu (Winbond Electronics)
|
||||
Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
* reset int
|
||||
(default 0)
|
||||
This parameter is not recommended, it will lose motherboard specific
|
||||
settings. Use 'reset=1' to reset the chip when loading this module.
|
||||
|
||||
* force_subclients=bus,caddr,saddr1,saddr2
|
||||
This is used to force the i2c addresses for subclients of
|
||||
a certain chip. Typical usage is `force_subclients=0,0x2f,0x4a,0x4b'
|
||||
to force the subclients of chip 0x2f on bus 0 to i2c addresses
|
||||
0x4a and 0x4b.
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Winbond W83793G/W83793R chips.
|
||||
|
||||
* Exported features
|
||||
This driver exports 10 voltage sensors, up to 12 fan tachometer inputs,
|
||||
6 remote temperatures, up to 8 sets of PWM fan controls, SmartFan
|
||||
(automatic fan speed control) on all temperature/PWM combinations, 2
|
||||
sets of 6-pin CPU VID input.
|
||||
|
||||
* Sensor resolutions
|
||||
If your motherboard maker used the reference design, the resolution of
|
||||
voltage0-2 is 2mV, resolution of voltage3/4/5 is 16mV, 8mV for voltage6,
|
||||
24mV for voltage7/8. Temp1-4 have a 0.25 degree Celsius resolution,
|
||||
temp5-6 have a 1 degree Celsiis resolution.
|
||||
|
||||
* Temperature sensor types
|
||||
Temp1-4 have 3 possible types. It can be read from (and written to)
|
||||
temp[1-4]_type.
|
||||
- If the value of 0, the related temperature channel stops
|
||||
monitoring.
|
||||
- If the value is 3, it starts monitoring using a remote termal diode
|
||||
(default).
|
||||
- If the value is 5, it starts monitoring using the temperature sensor
|
||||
in AMD CPU and get result by AMDSI.
|
||||
- If the value is 6, it starts monitoring using the temperature sensor
|
||||
in Intel CPU and get result by PECI.
|
||||
Temp5-6 can be connected to external thermistors (value of
|
||||
temp[5-6]_type is 4). They can also be disabled (value is 0).
|
||||
|
||||
* Alarm mechanism
|
||||
For voltage sensors, an alarm triggers if the measured value is below
|
||||
the low voltage limit or over the high voltage limit.
|
||||
For temperature sensors, an alarm triggers if the measured value goes
|
||||
above the high temperature limit, and wears off only after the measured
|
||||
value drops below the hysteresis value.
|
||||
For fan sensors, an alarm triggers if the measured value is below the
|
||||
low speed limit.
|
||||
|
||||
* SmartFan/PWM control
|
||||
If you want to set a pwm fan to manual mode, you just need to make sure it
|
||||
is not controlled by any temp channel, for example, you want to set fan1
|
||||
to manual mode, you need to check the value of temp[1-6]_fan_map, make
|
||||
sure bit 0 is cleared in the 6 values. And then set the pwm1 value to
|
||||
control the fan.
|
||||
|
||||
Each temperature channel can control all the 8 PWM outputs (by setting the
|
||||
corresponding bit in tempX_fan_map), you can set the temperature channel
|
||||
mode using temp[1-6]_pwm_enable, 2 is Thermal Cruise mode and 3
|
||||
is the SmartFanII mode. Temperature channels will try to speed up or
|
||||
slow down all controlled fans, this means one fan can receive different
|
||||
PWM value requests from different temperature channels, but the chip
|
||||
will always pick the safest (max) PWM value for each fan.
|
||||
|
||||
In Thermal Cruise mode, the chip attempts to keep the temperature at a
|
||||
predefined value, within a tolerance margin. So if tempX_input >
|
||||
thermal_cruiseX + toleranceX, the chip will increase the PWM value,
|
||||
if tempX_input < thermal_cruiseX - toleranceX, the chip will decrease
|
||||
the PWM value. If the temperature is within the tolerance range, the PWM
|
||||
value is left unchanged.
|
||||
|
||||
SmartFanII works differently, you have to define up to 7 PWM, temperature
|
||||
trip points, defining a PWM/temperature curve which the chip will follow.
|
||||
While not fundamentally different from the Thermal Cruise mode, the
|
||||
implementation is quite different, giving you a finer-grained control.
|
||||
|
||||
* Chassis
|
||||
If the case open alarm triggers, it will stay in this state unless cleared
|
||||
by any write to the sysfs file "chassis".
|
||||
|
||||
* VID and VRM
|
||||
The VRM version is detected automatically, don't modify the it unless you
|
||||
*do* know the cpu VRM version and it's not properly detected.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
Only Fan1-5 and PWM1-3 are guaranteed to always exist, other fan inputs and
|
||||
PWM outputs may or may not exist depending on the chip pin configuration.
|
||||
+14
-1
@@ -277,7 +277,7 @@ S: Maintained
|
||||
|
||||
ALI1563 I2C DRIVER
|
||||
P: Rudolf Marek
|
||||
M: r.marek@sh.cvut.cz
|
||||
M: r.marek@assembler.cz
|
||||
L: i2c@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
@@ -296,6 +296,13 @@ L: info-linux@geode.amd.com
|
||||
W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html
|
||||
S: Supported
|
||||
|
||||
AMS (Apple Motion Sensor) DRIVER
|
||||
P: Stelian Pop
|
||||
M: stelian@popies.net
|
||||
P: Michael Hanselmann
|
||||
M: linux-kernel@hansmi.ch
|
||||
S: Supported
|
||||
|
||||
AMSO1100 RNIC DRIVER
|
||||
P: Tom Tucker
|
||||
M: tom@opengridcomputing.com
|
||||
@@ -3436,6 +3443,12 @@ M: bezaur@gmail.com
|
||||
L: lm-sensors@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
W83793 HARDWARE MONITORING DRIVER
|
||||
P: Rudolf Marek
|
||||
M: r.marek@assembler.cz
|
||||
L: lm-sensors@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
W83L51xD SD/MMC CARD INTERFACE DRIVER
|
||||
P: Pierre Ossman
|
||||
M: drzeus-wbsd@drzeus.cx
|
||||
|
||||
+54
-2
@@ -106,6 +106,31 @@ config SENSORS_K8TEMP
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called k8temp.
|
||||
|
||||
config SENSORS_AMS
|
||||
tristate "Apple Motion Sensor driver"
|
||||
depends on HWMON && PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL
|
||||
help
|
||||
Support for the motion sensor included in PowerBooks. Includes
|
||||
implementations for PMU and I2C.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ams.
|
||||
|
||||
config SENSORS_AMS_PMU
|
||||
bool "PMU variant"
|
||||
depends on SENSORS_AMS && ADB_PMU
|
||||
default y
|
||||
help
|
||||
PMU variant of motion sensor, found in late 2005 PowerBooks.
|
||||
|
||||
config SENSORS_AMS_I2C
|
||||
bool "I2C variant"
|
||||
depends on SENSORS_AMS && I2C
|
||||
default y
|
||||
help
|
||||
I2C variant of motion sensor, found in early 2005 PowerBooks and
|
||||
iBooks.
|
||||
|
||||
config SENSORS_ASB100
|
||||
tristate "Asus ASB100 Bach"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
@@ -142,11 +167,12 @@ config SENSORS_DS1621
|
||||
will be called ds1621.
|
||||
|
||||
config SENSORS_F71805F
|
||||
tristate "Fintek F71805F/FG"
|
||||
tristate "Fintek F71805F/FG and F71872F/FG"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for hardware monitoring
|
||||
features of the Fintek F71805F/FG chips.
|
||||
features of the Fintek F71805F/FG and F71872F/FG Super-I/O
|
||||
chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called f71805f.
|
||||
@@ -353,6 +379,19 @@ config SENSORS_PC87360
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called pc87360.
|
||||
|
||||
config SENSORS_PC87427
|
||||
tristate "National Semiconductor PC87427"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get access to the hardware monitoring
|
||||
functions of the National Semiconductor PC87427 Super-I/O chip.
|
||||
The chip has two distinct logical devices, one for fan speed
|
||||
monitoring and control, and one for voltage and temperature
|
||||
monitoring. Only fan speed monitoring is supported right now.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called pc87427.
|
||||
|
||||
config SENSORS_SIS5595
|
||||
tristate "Silicon Integrated Systems Corp. SiS5595"
|
||||
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
||||
@@ -474,6 +513,16 @@ config SENSORS_W83792D
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called w83792d.
|
||||
|
||||
config SENSORS_W83793
|
||||
tristate "Winbond W83793"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Winbond W83793
|
||||
hardware monitoring chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called w83793.
|
||||
|
||||
config SENSORS_W83L785TS
|
||||
tristate "Winbond W83L785TS-S"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
@@ -527,6 +576,9 @@ config SENSORS_HDAPS
|
||||
This driver also provides an absolute input class device, allowing
|
||||
the laptop to act as a pinball machine-esque joystick.
|
||||
|
||||
If your ThinkPad is not recognized by the driver, please update to latest
|
||||
BIOS. This is especially the case for some R52 ThinkPads.
|
||||
|
||||
Say Y here if you have an applicable laptop and want to experience
|
||||
the awesome power of hdaps.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
|
||||
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
|
||||
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
|
||||
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
|
||||
obj-$(CONFIG_SENSORS_W83793) += w83793.o
|
||||
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
||||
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||
|
||||
@@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
|
||||
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
||||
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
|
||||
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
||||
obj-$(CONFIG_SENSORS_AMS) += ams/
|
||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||
@@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
|
||||
obj-$(CONFIG_SENSORS_LM92) += lm92.o
|
||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
||||
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Makefile for Apple Motion Sensor driver
|
||||
#
|
||||
|
||||
ams-y := ams-core.o ams-input.o
|
||||
ams-$(CONFIG_SENSORS_AMS_PMU) += ams-pmu.o
|
||||
ams-$(CONFIG_SENSORS_AMS_I2C) += ams-i2c.o
|
||||
obj-$(CONFIG_SENSORS_AMS) += ams.o
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Apple Motion Sensor driver
|
||||
*
|
||||
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
|
||||
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
|
||||
*
|
||||
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/pmac_pfunc.h>
|
||||
#include <asm/of_platform.h>
|
||||
|
||||
#include "ams.h"
|
||||
|
||||
/* There is only one motion sensor per machine */
|
||||
struct ams ams_info;
|
||||
|
||||
static unsigned int verbose;
|
||||
module_param(verbose, bool, 0644);
|
||||
MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
void ams_sensors(s8 *x, s8 *y, s8 *z)
|
||||
{
|
||||
u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2;
|
||||
|
||||
if (orient & 0x80)
|
||||
/* X and Y swapped */
|
||||
ams_info.get_xyz(y, x, z);
|
||||
else
|
||||
ams_info.get_xyz(x, y, z);
|
||||
|
||||
if (orient & 0x04)
|
||||
*z = ~(*z);
|
||||
if (orient & 0x02)
|
||||
*y = ~(*y);
|
||||
if (orient & 0x01)
|
||||
*x = ~(*x);
|
||||
}
|
||||
|
||||
static ssize_t ams_show_current(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
s8 x, y, z;
|
||||
|
||||
mutex_lock(&ams_info.lock);
|
||||
ams_sensors(&x, &y, &z);
|
||||
mutex_unlock(&ams_info.lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
|
||||
|
||||
static void ams_handle_irq(void *data)
|
||||
{
|
||||
enum ams_irq irq = *((enum ams_irq *)data);
|
||||
|
||||
spin_lock(&ams_info.irq_lock);
|
||||
|
||||
ams_info.worker_irqs |= irq;
|
||||
schedule_work(&ams_info.worker);
|
||||
|
||||
spin_unlock(&ams_info.irq_lock);
|
||||
}
|
||||
|
||||
static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
|
||||
static struct pmf_irq_client ams_freefall_client = {
|
||||
.owner = THIS_MODULE,
|
||||
.handler = ams_handle_irq,
|
||||
.data = &ams_freefall_irq_data,
|
||||
};
|
||||
|
||||
static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
|
||||
static struct pmf_irq_client ams_shock_client = {
|
||||
.owner = THIS_MODULE,
|
||||
.handler = ams_handle_irq,
|
||||
.data = &ams_shock_irq_data,
|
||||
};
|
||||
|
||||
/* Once hard disk parking is implemented in the kernel, this function can
|
||||
* trigger it.
|
||||
*/
|
||||
static void ams_worker(struct work_struct *work)
|
||||
{
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
if (ams_info.has_device) {
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&ams_info.irq_lock, flags);
|
||||
|
||||
if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
|
||||
if (verbose)
|
||||
printk(KERN_INFO "ams: freefall detected!\n");
|
||||
|
||||
ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
|
||||
|
||||
/* we must call this with interrupts enabled */
|
||||
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
|
||||
ams_info.clear_irq(AMS_IRQ_FREEFALL);
|
||||
spin_lock_irqsave(&ams_info.irq_lock, flags);
|
||||
}
|
||||
|
||||
if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
|
||||
if (verbose)
|
||||
printk(KERN_INFO "ams: shock detected!\n");
|
||||
|
||||
ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
|
||||
|
||||
/* we must call this with interrupts enabled */
|
||||
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
|
||||
ams_info.clear_irq(AMS_IRQ_SHOCK);
|
||||
spin_lock_irqsave(&ams_info.irq_lock, flags);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
|
||||
}
|
||||
|
||||
mutex_unlock(&ams_info.lock);
|
||||
}
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
int ams_sensor_attach(void)
|
||||
{
|
||||
int result;
|
||||
u32 *prop;
|
||||
|
||||
/* Get orientation */
|
||||
prop = (u32*)get_property(ams_info.of_node, "orientation", NULL);
|
||||
if (!prop)
|
||||
return -ENODEV;
|
||||
ams_info.orient1 = *prop;
|
||||
ams_info.orient2 = *(prop + 1);
|
||||
|
||||
/* Register freefall interrupt handler */
|
||||
result = pmf_register_irq_client(ams_info.of_node,
|
||||
"accel-int-1",
|
||||
&ams_freefall_client);
|
||||
if (result < 0)
|
||||
return -ENODEV;
|
||||
|
||||
/* Reset saved irqs */
|
||||
ams_info.worker_irqs = 0;
|
||||
|
||||
/* Register shock interrupt handler */
|
||||
result = pmf_register_irq_client(ams_info.of_node,
|
||||
"accel-int-2",
|
||||
&ams_shock_client);
|
||||
if (result < 0)
|
||||
goto release_freefall;
|
||||
|
||||
/* Create device */
|
||||
ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL);
|
||||
if (!ams_info.of_dev) {
|
||||
result = -ENODEV;
|
||||
goto release_shock;
|
||||
}
|
||||
|
||||
/* Create attributes */
|
||||
result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current);
|
||||
if (result)
|
||||
goto release_of;
|
||||
|
||||
ams_info.vflag = !!(ams_info.get_vendor() & 0x10);
|
||||
|
||||
/* Init input device */
|
||||
result = ams_input_init();
|
||||
if (result)
|
||||
goto release_device_file;
|
||||
|
||||
return result;
|
||||
release_device_file:
|
||||
device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
|
||||
release_of:
|
||||
of_device_unregister(ams_info.of_dev);
|
||||
release_shock:
|
||||
pmf_unregister_irq_client(&ams_shock_client);
|
||||
release_freefall:
|
||||
pmf_unregister_irq_client(&ams_freefall_client);
|
||||
return result;
|
||||
}
|
||||
|
||||
int __init ams_init(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
spin_lock_init(&ams_info.irq_lock);
|
||||
mutex_init(&ams_info.lock);
|
||||
INIT_WORK(&ams_info.worker, ams_worker);
|
||||
|
||||
#ifdef CONFIG_SENSORS_AMS_I2C
|
||||
np = of_find_node_by_name(NULL, "accelerometer");
|
||||
if (np && device_is_compatible(np, "AAPL,accelerometer_1"))
|
||||
/* Found I2C motion sensor */
|
||||
return ams_i2c_init(np);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SENSORS_AMS_PMU
|
||||
np = of_find_node_by_name(NULL, "sms");
|
||||
if (np && device_is_compatible(np, "sms"))
|
||||
/* Found PMU motion sensor */
|
||||
return ams_pmu_init(np);
|
||||
#endif
|
||||
|
||||
printk(KERN_ERR "ams: No motion sensor found.\n");
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
void ams_exit(void)
|
||||
{
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
if (ams_info.has_device) {
|
||||
/* Remove input device */
|
||||
ams_input_exit();
|
||||
|
||||
/* Shut down implementation */
|
||||
ams_info.exit();
|
||||
|
||||
/* Flush interrupt worker
|
||||
*
|
||||
* We do this after ams_info.exit(), because an interrupt might
|
||||
* have arrived before disabling them.
|
||||
*/
|
||||
flush_scheduled_work();
|
||||
|
||||
/* Remove attributes */
|
||||
device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
|
||||
|
||||
/* Remove device */
|
||||
of_device_unregister(ams_info.of_dev);
|
||||
|
||||
/* Remove handler */
|
||||
pmf_unregister_irq_client(&ams_shock_client);
|
||||
pmf_unregister_irq_client(&ams_freefall_client);
|
||||
}
|
||||
|
||||
mutex_unlock(&ams_info.lock);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
|
||||
MODULE_DESCRIPTION("Apple Motion Sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(ams_init);
|
||||
module_exit(ams_exit);
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Apple Motion Sensor driver (I2C variant)
|
||||
*
|
||||
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
|
||||
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
|
||||
*
|
||||
* Clean room implementation based on the reverse engineered Mac OS X driver by
|
||||
* Johannes Berg <johannes@sipsolutions.net>, documentation available at
|
||||
* http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "ams.h"
|
||||
|
||||
/* AMS registers */
|
||||
#define AMS_COMMAND 0x00 /* command register */
|
||||
#define AMS_STATUS 0x01 /* status register */
|
||||
#define AMS_CTRL1 0x02 /* read control 1 (number of values) */
|
||||
#define AMS_CTRL2 0x03 /* read control 2 (offset?) */
|
||||
#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */
|
||||
#define AMS_DATA1 0x05 /* read data 1 */
|
||||
#define AMS_DATA2 0x06 /* read data 2 */
|
||||
#define AMS_DATA3 0x07 /* read data 3 */
|
||||
#define AMS_DATA4 0x08 /* read data 4 */
|
||||
#define AMS_DATAX 0x20 /* data X */
|
||||
#define AMS_DATAY 0x21 /* data Y */
|
||||
#define AMS_DATAZ 0x22 /* data Z */
|
||||
#define AMS_FREEFALL 0x24 /* freefall int control */
|
||||
#define AMS_SHOCK 0x25 /* shock int control */
|
||||
#define AMS_SENSLOW 0x26 /* sensitivity low limit */
|
||||
#define AMS_SENSHIGH 0x27 /* sensitivity high limit */
|
||||
#define AMS_CTRLX 0x28 /* control X */
|
||||
#define AMS_CTRLY 0x29 /* control Y */
|
||||
#define AMS_CTRLZ 0x2A /* control Z */
|
||||
#define AMS_UNKNOWN1 0x2B /* unknown 1 */
|
||||
#define AMS_UNKNOWN2 0x2C /* unknown 2 */
|
||||
#define AMS_UNKNOWN3 0x2D /* unknown 3 */
|
||||
#define AMS_VENDOR 0x2E /* vendor */
|
||||
|
||||
/* AMS commands - use with the AMS_COMMAND register */
|
||||
enum ams_i2c_cmd {
|
||||
AMS_CMD_NOOP = 0,
|
||||
AMS_CMD_VERSION,
|
||||
AMS_CMD_READMEM,
|
||||
AMS_CMD_WRITEMEM,
|
||||
AMS_CMD_ERASEMEM,
|
||||
AMS_CMD_READEE,
|
||||
AMS_CMD_WRITEEE,
|
||||
AMS_CMD_RESET,
|
||||
AMS_CMD_START,
|
||||
};
|
||||
|
||||
static int ams_i2c_attach(struct i2c_adapter *adapter);
|
||||
static int ams_i2c_detach(struct i2c_adapter *adapter);
|
||||
|
||||
static struct i2c_driver ams_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "ams",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.attach_adapter = ams_i2c_attach,
|
||||
.detach_adapter = ams_i2c_detach,
|
||||
};
|
||||
|
||||
static s32 ams_i2c_read(u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(&ams_info.i2c_client, reg);
|
||||
}
|
||||
|
||||
static int ams_i2c_write(u8 reg, u8 value)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(&ams_info.i2c_client, reg, value);
|
||||
}
|
||||
|
||||
static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
|
||||
{
|
||||
s32 result;
|
||||
int remaining = HZ / 20;
|
||||
|
||||
ams_i2c_write(AMS_COMMAND, cmd);
|
||||
mdelay(5);
|
||||
|
||||
while (remaining) {
|
||||
result = ams_i2c_read(AMS_COMMAND);
|
||||
if (result == 0 || result & 0x80)
|
||||
return 0;
|
||||
|
||||
remaining = schedule_timeout(remaining);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ams_i2c_set_irq(enum ams_irq reg, char enable)
|
||||
{
|
||||
if (reg & AMS_IRQ_FREEFALL) {
|
||||
u8 val = ams_i2c_read(AMS_CTRLX);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_i2c_write(AMS_CTRLX, val);
|
||||
}
|
||||
|
||||
if (reg & AMS_IRQ_SHOCK) {
|
||||
u8 val = ams_i2c_read(AMS_CTRLY);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_i2c_write(AMS_CTRLY, val);
|
||||
}
|
||||
|
||||
if (reg & AMS_IRQ_GLOBAL) {
|
||||
u8 val = ams_i2c_read(AMS_CTRLZ);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_i2c_write(AMS_CTRLZ, val);
|
||||
}
|
||||
}
|
||||
|
||||
static void ams_i2c_clear_irq(enum ams_irq reg)
|
||||
{
|
||||
if (reg & AMS_IRQ_FREEFALL)
|
||||
ams_i2c_write(AMS_FREEFALL, 0);
|
||||
|
||||
if (reg & AMS_IRQ_SHOCK)
|
||||
ams_i2c_write(AMS_SHOCK, 0);
|
||||
}
|
||||
|
||||
static u8 ams_i2c_get_vendor(void)
|
||||
{
|
||||
return ams_i2c_read(AMS_VENDOR);
|
||||
}
|
||||
|
||||
static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
|
||||
{
|
||||
*x = ams_i2c_read(AMS_DATAX);
|
||||
*y = ams_i2c_read(AMS_DATAY);
|
||||
*z = ams_i2c_read(AMS_DATAZ);
|
||||
}
|
||||
|
||||
static int ams_i2c_attach(struct i2c_adapter *adapter)
|
||||
{
|
||||
unsigned long bus;
|
||||
int vmaj, vmin;
|
||||
int result;
|
||||
|
||||
/* There can be only one */
|
||||
if (unlikely(ams_info.has_device))
|
||||
return -ENODEV;
|
||||
|
||||
if (strncmp(adapter->name, "uni-n", 5))
|
||||
return -ENODEV;
|
||||
|
||||
bus = simple_strtoul(adapter->name + 6, NULL, 10);
|
||||
if (bus != ams_info.i2c_bus)
|
||||
return -ENODEV;
|
||||
|
||||
ams_info.i2c_client.addr = ams_info.i2c_address;
|
||||
ams_info.i2c_client.adapter = adapter;
|
||||
ams_info.i2c_client.driver = &ams_i2c_driver;
|
||||
strcpy(ams_info.i2c_client.name, "Apple Motion Sensor");
|
||||
|
||||
if (ams_i2c_cmd(AMS_CMD_RESET)) {
|
||||
printk(KERN_INFO "ams: Failed to reset the device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (ams_i2c_cmd(AMS_CMD_START)) {
|
||||
printk(KERN_INFO "ams: Failed to start the device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* get version/vendor information */
|
||||
ams_i2c_write(AMS_CTRL1, 0x02);
|
||||
ams_i2c_write(AMS_CTRL2, 0x85);
|
||||
ams_i2c_write(AMS_CTRL3, 0x01);
|
||||
|
||||
ams_i2c_cmd(AMS_CMD_READMEM);
|
||||
|
||||
vmaj = ams_i2c_read(AMS_DATA1);
|
||||
vmin = ams_i2c_read(AMS_DATA2);
|
||||
if (vmaj != 1 || vmin != 52) {
|
||||
printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
|
||||
vmaj, vmin);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ams_i2c_cmd(AMS_CMD_VERSION);
|
||||
|
||||
vmaj = ams_i2c_read(AMS_DATA1);
|
||||
vmin = ams_i2c_read(AMS_DATA2);
|
||||
if (vmaj != 0 || vmin != 1) {
|
||||
printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
|
||||
vmaj, vmin);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Disable interrupts */
|
||||
ams_i2c_set_irq(AMS_IRQ_ALL, 0);
|
||||
|
||||
result = ams_sensor_attach();
|
||||
if (result < 0)
|
||||
return result;
|
||||
|
||||
/* Set default values */
|
||||
ams_i2c_write(AMS_SENSLOW, 0x15);
|
||||
ams_i2c_write(AMS_SENSHIGH, 0x60);
|
||||
ams_i2c_write(AMS_CTRLX, 0x08);
|
||||
ams_i2c_write(AMS_CTRLY, 0x0F);
|
||||
ams_i2c_write(AMS_CTRLZ, 0x4F);
|
||||
ams_i2c_write(AMS_UNKNOWN1, 0x14);
|
||||
|
||||
/* Clear interrupts */
|
||||
ams_i2c_clear_irq(AMS_IRQ_ALL);
|
||||
|
||||
ams_info.has_device = 1;
|
||||
|
||||
/* Enable interrupts */
|
||||
ams_i2c_set_irq(AMS_IRQ_ALL, 1);
|
||||
|
||||
printk(KERN_INFO "ams: Found I2C based motion sensor\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ams_i2c_detach(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (ams_info.has_device) {
|
||||
/* Disable interrupts */
|
||||
ams_i2c_set_irq(AMS_IRQ_ALL, 0);
|
||||
|
||||
/* Clear interrupts */
|
||||
ams_i2c_clear_irq(AMS_IRQ_ALL);
|
||||
|
||||
printk(KERN_INFO "ams: Unloading\n");
|
||||
|
||||
ams_info.has_device = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ams_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ams_i2c_driver);
|
||||
}
|
||||
|
||||
int __init ams_i2c_init(struct device_node *np)
|
||||
{
|
||||
char *tmp_bus;
|
||||
int result;
|
||||
u32 *prop;
|
||||
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
/* Set implementation stuff */
|
||||
ams_info.of_node = np;
|
||||
ams_info.exit = ams_i2c_exit;
|
||||
ams_info.get_vendor = ams_i2c_get_vendor;
|
||||
ams_info.get_xyz = ams_i2c_get_xyz;
|
||||
ams_info.clear_irq = ams_i2c_clear_irq;
|
||||
ams_info.bustype = BUS_I2C;
|
||||
|
||||
/* look for bus either using "reg" or by path */
|
||||
prop = (u32*)get_property(ams_info.of_node, "reg", NULL);
|
||||
if (!prop) {
|
||||
result = -ENODEV;
|
||||
|
||||
goto exit;
|
||||
}
|
||||
|
||||
tmp_bus = strstr(ams_info.of_node->full_name, "/i2c-bus@");
|
||||
if (tmp_bus)
|
||||
ams_info.i2c_bus = *(tmp_bus + 9) - '0';
|
||||
else
|
||||
ams_info.i2c_bus = ((*prop) >> 8) & 0x0f;
|
||||
ams_info.i2c_address = ((*prop) & 0xff) >> 1;
|
||||
|
||||
result = i2c_add_driver(&ams_i2c_driver);
|
||||
|
||||
exit:
|
||||
mutex_unlock(&ams_info.lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Apple Motion Sensor driver (joystick emulation)
|
||||
*
|
||||
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
|
||||
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include "ams.h"
|
||||
|
||||
static unsigned int joystick;
|
||||
module_param(joystick, bool, 0644);
|
||||
MODULE_PARM_DESC(joystick, "Enable the input class device on module load");
|
||||
|
||||
static unsigned int invert;
|
||||
module_param(invert, bool, 0644);
|
||||
MODULE_PARM_DESC(invert, "Invert input data on X and Y axis");
|
||||
|
||||
static int ams_input_kthread(void *data)
|
||||
{
|
||||
s8 x, y, z;
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
ams_sensors(&x, &y, &z);
|
||||
|
||||
x -= ams_info.xcalib;
|
||||
y -= ams_info.ycalib;
|
||||
z -= ams_info.zcalib;
|
||||
|
||||
input_report_abs(ams_info.idev, ABS_X, invert ? -x : x);
|
||||
input_report_abs(ams_info.idev, ABS_Y, invert ? -y : y);
|
||||
input_report_abs(ams_info.idev, ABS_Z, z);
|
||||
|
||||
input_sync(ams_info.idev);
|
||||
|
||||
mutex_unlock(&ams_info.lock);
|
||||
|
||||
msleep(25);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ams_input_open(struct input_dev *dev)
|
||||
{
|
||||
ams_info.kthread = kthread_run(ams_input_kthread, NULL, "kams");
|
||||
return IS_ERR(ams_info.kthread) ? PTR_ERR(ams_info.kthread) : 0;
|
||||
}
|
||||
|
||||
static void ams_input_close(struct input_dev *dev)
|
||||
{
|
||||
kthread_stop(ams_info.kthread);
|
||||
}
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
static void ams_input_enable(void)
|
||||
{
|
||||
s8 x, y, z;
|
||||
|
||||
if (ams_info.idev)
|
||||
return;
|
||||
|
||||
ams_sensors(&x, &y, &z);
|
||||
ams_info.xcalib = x;
|
||||
ams_info.ycalib = y;
|
||||
ams_info.zcalib = z;
|
||||
|
||||
ams_info.idev = input_allocate_device();
|
||||
if (!ams_info.idev)
|
||||
return;
|
||||
|
||||
ams_info.idev->name = "Apple Motion Sensor";
|
||||
ams_info.idev->id.bustype = ams_info.bustype;
|
||||
ams_info.idev->id.vendor = 0;
|
||||
ams_info.idev->open = ams_input_open;
|
||||
ams_info.idev->close = ams_input_close;
|
||||
ams_info.idev->cdev.dev = &ams_info.of_dev->dev;
|
||||
|
||||
input_set_abs_params(ams_info.idev, ABS_X, -50, 50, 3, 0);
|
||||
input_set_abs_params(ams_info.idev, ABS_Y, -50, 50, 3, 0);
|
||||
input_set_abs_params(ams_info.idev, ABS_Z, -50, 50, 3, 0);
|
||||
|
||||
set_bit(EV_ABS, ams_info.idev->evbit);
|
||||
set_bit(EV_KEY, ams_info.idev->evbit);
|
||||
set_bit(BTN_TOUCH, ams_info.idev->keybit);
|
||||
|
||||
if (input_register_device(ams_info.idev)) {
|
||||
input_free_device(ams_info.idev);
|
||||
ams_info.idev = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
static void ams_input_disable(void)
|
||||
{
|
||||
if (ams_info.idev) {
|
||||
input_unregister_device(ams_info.idev);
|
||||
ams_info.idev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t ams_input_show_joystick(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", joystick);
|
||||
}
|
||||
|
||||
static ssize_t ams_input_store_joystick(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
if (sscanf(buf, "%d\n", &joystick) != 1)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
if (joystick)
|
||||
ams_input_enable();
|
||||
else
|
||||
ams_input_disable();
|
||||
|
||||
mutex_unlock(&ams_info.lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR,
|
||||
ams_input_show_joystick, ams_input_store_joystick);
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
int ams_input_init(void)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick);
|
||||
|
||||
if (!result && joystick)
|
||||
ams_input_enable();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Call with ams_info.lock held! */
|
||||
void ams_input_exit()
|
||||
{
|
||||
ams_input_disable();
|
||||
device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Apple Motion Sensor driver (PMU variant)
|
||||
*
|
||||
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/adb.h>
|
||||
#include <linux/pmu.h>
|
||||
|
||||
#include "ams.h"
|
||||
|
||||
/* Attitude */
|
||||
#define AMS_X 0x00
|
||||
#define AMS_Y 0x01
|
||||
#define AMS_Z 0x02
|
||||
|
||||
/* Not exactly known, maybe chip vendor */
|
||||
#define AMS_VENDOR 0x03
|
||||
|
||||
/* Freefall registers */
|
||||
#define AMS_FF_CLEAR 0x04
|
||||
#define AMS_FF_ENABLE 0x05
|
||||
#define AMS_FF_LOW_LIMIT 0x06
|
||||
#define AMS_FF_DEBOUNCE 0x07
|
||||
|
||||
/* Shock registers */
|
||||
#define AMS_SHOCK_CLEAR 0x08
|
||||
#define AMS_SHOCK_ENABLE 0x09
|
||||
#define AMS_SHOCK_HIGH_LIMIT 0x0a
|
||||
#define AMS_SHOCK_DEBOUNCE 0x0b
|
||||
|
||||
/* Global interrupt and power control register */
|
||||
#define AMS_CONTROL 0x0c
|
||||
|
||||
static u8 ams_pmu_cmd;
|
||||
|
||||
static void ams_pmu_req_complete(struct adb_request *req)
|
||||
{
|
||||
complete((struct completion *)req->arg);
|
||||
}
|
||||
|
||||
/* Only call this function from task context */
|
||||
static void ams_pmu_set_register(u8 reg, u8 value)
|
||||
{
|
||||
static struct adb_request req;
|
||||
DECLARE_COMPLETION(req_complete);
|
||||
|
||||
req.arg = &req_complete;
|
||||
if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value))
|
||||
return;
|
||||
|
||||
wait_for_completion(&req_complete);
|
||||
}
|
||||
|
||||
/* Only call this function from task context */
|
||||
static u8 ams_pmu_get_register(u8 reg)
|
||||
{
|
||||
static struct adb_request req;
|
||||
DECLARE_COMPLETION(req_complete);
|
||||
|
||||
req.arg = &req_complete;
|
||||
if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg))
|
||||
return 0;
|
||||
|
||||
wait_for_completion(&req_complete);
|
||||
|
||||
if (req.reply_len > 0)
|
||||
return req.reply[0];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enables or disables the specified interrupts */
|
||||
static void ams_pmu_set_irq(enum ams_irq reg, char enable)
|
||||
{
|
||||
if (reg & AMS_IRQ_FREEFALL) {
|
||||
u8 val = ams_pmu_get_register(AMS_FF_ENABLE);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_pmu_set_register(AMS_FF_ENABLE, val);
|
||||
}
|
||||
|
||||
if (reg & AMS_IRQ_SHOCK) {
|
||||
u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_pmu_set_register(AMS_SHOCK_ENABLE, val);
|
||||
}
|
||||
|
||||
if (reg & AMS_IRQ_GLOBAL) {
|
||||
u8 val = ams_pmu_get_register(AMS_CONTROL);
|
||||
if (enable)
|
||||
val |= 0x80;
|
||||
else
|
||||
val &= ~0x80;
|
||||
ams_pmu_set_register(AMS_CONTROL, val);
|
||||
}
|
||||
}
|
||||
|
||||
static void ams_pmu_clear_irq(enum ams_irq reg)
|
||||
{
|
||||
if (reg & AMS_IRQ_FREEFALL)
|
||||
ams_pmu_set_register(AMS_FF_CLEAR, 0x00);
|
||||
|
||||
if (reg & AMS_IRQ_SHOCK)
|
||||
ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00);
|
||||
}
|
||||
|
||||
static u8 ams_pmu_get_vendor(void)
|
||||
{
|
||||
return ams_pmu_get_register(AMS_VENDOR);
|
||||
}
|
||||
|
||||
static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z)
|
||||
{
|
||||
*x = ams_pmu_get_register(AMS_X);
|
||||
*y = ams_pmu_get_register(AMS_Y);
|
||||
*z = ams_pmu_get_register(AMS_Z);
|
||||
}
|
||||
|
||||
static void ams_pmu_exit(void)
|
||||
{
|
||||
/* Disable interrupts */
|
||||
ams_pmu_set_irq(AMS_IRQ_ALL, 0);
|
||||
|
||||
/* Clear interrupts */
|
||||
ams_pmu_clear_irq(AMS_IRQ_ALL);
|
||||
|
||||
ams_info.has_device = 0;
|
||||
|
||||
printk(KERN_INFO "ams: Unloading\n");
|
||||
}
|
||||
|
||||
int __init ams_pmu_init(struct device_node *np)
|
||||
{
|
||||
u32 *prop;
|
||||
int result;
|
||||
|
||||
mutex_lock(&ams_info.lock);
|
||||
|
||||
/* Set implementation stuff */
|
||||
ams_info.of_node = np;
|
||||
ams_info.exit = ams_pmu_exit;
|
||||
ams_info.get_vendor = ams_pmu_get_vendor;
|
||||
ams_info.get_xyz = ams_pmu_get_xyz;
|
||||
ams_info.clear_irq = ams_pmu_clear_irq;
|
||||
ams_info.bustype = BUS_HOST;
|
||||
|
||||
/* Get PMU command, should be 0x4e, but we can never know */
|
||||
prop = (u32*)get_property(ams_info.of_node, "reg", NULL);
|
||||
if (!prop) {
|
||||
result = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
ams_pmu_cmd = ((*prop) >> 8) & 0xff;
|
||||
|
||||
/* Disable interrupts */
|
||||
ams_pmu_set_irq(AMS_IRQ_ALL, 0);
|
||||
|
||||
/* Clear interrupts */
|
||||
ams_pmu_clear_irq(AMS_IRQ_ALL);
|
||||
|
||||
result = ams_sensor_attach();
|
||||
if (result < 0)
|
||||
goto exit;
|
||||
|
||||
/* Set default values */
|
||||
ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15);
|
||||
ams_pmu_set_register(AMS_FF_ENABLE, 0x08);
|
||||
ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14);
|
||||
|
||||
ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60);
|
||||
ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f);
|
||||
ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14);
|
||||
|
||||
ams_pmu_set_register(AMS_CONTROL, 0x4f);
|
||||
|
||||
/* Clear interrupts */
|
||||
ams_pmu_clear_irq(AMS_IRQ_ALL);
|
||||
|
||||
ams_info.has_device = 1;
|
||||
|
||||
/* Enable interrupts */
|
||||
ams_pmu_set_irq(AMS_IRQ_ALL, 1);
|
||||
|
||||
printk(KERN_INFO "ams: Found PMU based motion sensor\n");
|
||||
|
||||
result = 0;
|
||||
|
||||
exit:
|
||||
mutex_unlock(&ams_info.lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
#include <asm/of_device.h>
|
||||
|
||||
enum ams_irq {
|
||||
AMS_IRQ_FREEFALL = 0x01,
|
||||
AMS_IRQ_SHOCK = 0x02,
|
||||
AMS_IRQ_GLOBAL = 0x04,
|
||||
AMS_IRQ_ALL =
|
||||
AMS_IRQ_FREEFALL |
|
||||
AMS_IRQ_SHOCK |
|
||||
AMS_IRQ_GLOBAL,
|
||||
};
|
||||
|
||||
struct ams {
|
||||
/* Locks */
|
||||
spinlock_t irq_lock;
|
||||
struct mutex lock;
|
||||
|
||||
/* General properties */
|
||||
struct device_node *of_node;
|
||||
struct of_device *of_dev;
|
||||
char has_device;
|
||||
char vflag;
|
||||
u32 orient1;
|
||||
u32 orient2;
|
||||
|
||||
/* Interrupt worker */
|
||||
struct work_struct worker;
|
||||
u8 worker_irqs;
|
||||
|
||||
/* Implementation
|
||||
*
|
||||
* Only call these functions with the main lock held.
|
||||
*/
|
||||
void (*exit)(void);
|
||||
|
||||
void (*get_xyz)(s8 *x, s8 *y, s8 *z);
|
||||
u8 (*get_vendor)(void);
|
||||
|
||||
void (*clear_irq)(enum ams_irq reg);
|
||||
|
||||
#ifdef CONFIG_SENSORS_AMS_I2C
|
||||
/* I2C properties */
|
||||
int i2c_bus;
|
||||
int i2c_address;
|
||||
struct i2c_client i2c_client;
|
||||
#endif
|
||||
|
||||
/* Joystick emulation */
|
||||
struct task_struct *kthread;
|
||||
struct input_dev *idev;
|
||||
__u16 bustype;
|
||||
|
||||
/* calibrated null values */
|
||||
int xcalib, ycalib, zcalib;
|
||||
};
|
||||
|
||||
extern struct ams ams_info;
|
||||
|
||||
extern void ams_sensors(s8 *x, s8 *y, s8 *z);
|
||||
extern int ams_sensor_attach(void);
|
||||
|
||||
extern int ams_pmu_init(struct device_node *np);
|
||||
extern int ams_i2c_init(struct device_node *np);
|
||||
|
||||
extern int ams_input_init(void);
|
||||
extern void ams_input_exit(void);
|
||||
+491
-78
File diff suppressed because it is too large.
Load diff
+29
-39
@@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = {
|
||||
/* Module stuff */
|
||||
|
||||
/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
|
||||
static int hdaps_dmi_match(struct dmi_system_id *id)
|
||||
static int __init hdaps_dmi_match(struct dmi_system_id *id)
|
||||
{
|
||||
printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* hdaps_dmi_match_invert - found an inverted match. */
|
||||
static int hdaps_dmi_match_invert(struct dmi_system_id *id)
|
||||
static int __init hdaps_dmi_match_invert(struct dmi_system_id *id)
|
||||
{
|
||||
hdaps_invert = 1;
|
||||
printk(KERN_INFO "hdaps: inverting axis readings.\n");
|
||||
return hdaps_dmi_match(id);
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_NORMAL(model) { \
|
||||
.ident = "IBM " model, \
|
||||
#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
|
||||
.ident = vendor " " model, \
|
||||
.callback = hdaps_dmi_match, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_INVERT(model) { \
|
||||
.ident = "IBM " model, \
|
||||
#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
|
||||
.ident = vendor " " model, \
|
||||
.callback = hdaps_dmi_match_invert, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_LENOVO(model) { \
|
||||
.ident = "Lenovo " model, \
|
||||
.callback = hdaps_dmi_match_invert, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
||||
"ThinkPad T42p", so the order of the entries matters.
|
||||
If your ThinkPad is not recognized, please update to latest
|
||||
BIOS. This is especially the case for some R52 ThinkPads. */
|
||||
static struct dmi_system_id __initdata hdaps_whitelist[] = {
|
||||
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
|
||||
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
|
||||
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
|
||||
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
|
||||
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
|
||||
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
|
||||
{ .ident = NULL }
|
||||
};
|
||||
|
||||
static int __init hdaps_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
||||
"ThinkPad T42p", so the order of the entries matters */
|
||||
struct dmi_system_id hdaps_whitelist[] = {
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),
|
||||
HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */
|
||||
HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
|
||||
HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
|
||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"),
|
||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"),
|
||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"),
|
||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"),
|
||||
{ .ident = NULL }
|
||||
};
|
||||
|
||||
if (!dmi_check_system(hdaps_whitelist)) {
|
||||
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
|
||||
ret = -ENODEV;
|
||||
|
||||
Loaded 20 of 30 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user