Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/staging: hwmon: (coretemp) Fix checkpatch errors hwmon: Remove pkgtemp driver hwmon: (coretemp) Merge pkgtemp with coretemp hwmon: (pmbus) Add support for Analog Devices ADM1275 hwmon: (pmbus) Support for TI UCD90xxx series Sequencer and System Health Controllers hwmon: (pmbus) Add support for TI UCD9200 series of PWM System Controllers hwmon: (pmbus) Use device specific function to read fan configuration hwmon: (pmbus) Expand scope of device specific get_status function hwmon: (pmbus) Introduce infrastructure to detect sensors and limit registers hwmon: Driver for MAX16065 System Manager and compatibles hwmon: (sht15) add support for CRC validation hwmon: (sht15) add support for the status register hwmon: (sht15) clean-up the probe function hwmon: (sht15) general code clean-up hwmon: Add support for MAX6642
This commit is contained in:
Коммит
6fad2b5b64
|
@ -0,0 +1,60 @@
|
|||
Kernel driver adm1275
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* Analog Devices ADM1275
|
||||
Prefix: 'adm1275'
|
||||
Addresses scanned: -
|
||||
Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1275.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware montoring for Analog Devices ADM1275 Hot-Swap
|
||||
Controller and Digital Power Monitor.
|
||||
|
||||
The ADM1275 is a hot-swap controller that allows a circuit board to be removed
|
||||
from or inserted into a live backplane. It also features current and voltage
|
||||
readback via an integrated 12-bit analog-to-digital converter (ADC), accessed
|
||||
using a PMBus. interface.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus for details on PMBus client drivers.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices for
|
||||
details.
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data. Please see
|
||||
Documentation/hwmon/pmbus for details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Limits are read-write; all other
|
||||
attributes are read-only.
|
||||
|
||||
in1_label "vin1" or "vout1" depending on chip variant and
|
||||
configuration.
|
||||
in1_input Measured voltage. From READ_VOUT register.
|
||||
in1_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register.
|
||||
in1_max Maximum voltage. From VOUT_OV_WARN_LIMIT register.
|
||||
in1_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status.
|
||||
in1_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status.
|
||||
|
||||
curr1_label "iout1"
|
||||
curr1_input Measured current. From READ_IOUT register.
|
||||
curr1_max Maximum current. From IOUT_OC_WARN_LIMIT register.
|
||||
curr1_max_alarm Current high alarm. From IOUT_OC_WARN_LIMIT register.
|
|
@ -15,8 +15,13 @@ Author: Rudolf Marek
|
|||
|
||||
Description
|
||||
-----------
|
||||
This driver permits reading the DTS (Digital Temperature Sensor) embedded
|
||||
inside Intel CPUs. This driver can read both the per-core and per-package
|
||||
temperature using the appropriate sensors. The per-package sensor is new;
|
||||
as of now, it is present only in the SandyBridge platform. The driver will
|
||||
show the temperature of all cores inside a package under a single device
|
||||
directory inside hwmon.
|
||||
|
||||
This driver permits reading temperature sensor embedded inside Intel Core CPU.
|
||||
Temperature is measured in degrees Celsius and measurement resolution is
|
||||
1 degree C. Valid temperatures are from 0 to TjMax degrees C, because
|
||||
the actual value of temperature register is in fact a delta from TjMax.
|
||||
|
@ -27,13 +32,15 @@ mechanism will perform actions to forcibly cool down the processor. Alarm
|
|||
may be raised, if the temperature grows enough (more than TjMax) to trigger
|
||||
the Out-Of-Spec bit. Following table summarizes the exported sysfs files:
|
||||
|
||||
temp1_input - Core temperature (in millidegrees Celsius).
|
||||
temp1_max - All cooling devices should be turned on (on Core2).
|
||||
temp1_crit - Maximum junction temperature (in millidegrees Celsius).
|
||||
temp1_crit_alarm - Set when Out-of-spec bit is set, never clears.
|
||||
All Sysfs entries are named with their core_id (represented here by 'X').
|
||||
tempX_input - Core temperature (in millidegrees Celsius).
|
||||
tempX_max - All cooling devices should be turned on (on Core2).
|
||||
tempX_crit - Maximum junction temperature (in millidegrees Celsius).
|
||||
tempX_crit_alarm - Set when Out-of-spec bit is set, never clears.
|
||||
Correct CPU operation is no longer guaranteed.
|
||||
temp1_label - Contains string "Core X", where X is processor
|
||||
number.
|
||||
tempX_label - Contains string "Core X", where X is processor
|
||||
number. For Package temp, this will be "Physical id Y",
|
||||
where Y is the package number.
|
||||
|
||||
The TjMax temperature is set to 85 degrees C if undocumented model specific
|
||||
register (UMSR) 0xee has bit 30 set. If not the TjMax is 100 degrees C as
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
Kernel driver max16065
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
* Maxim MAX16065, MAX16066
|
||||
Prefixes: 'max16065', 'max16066'
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX16065-MAX16066.pdf
|
||||
* Maxim MAX16067
|
||||
Prefix: 'max16067'
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX16067.pdf
|
||||
* Maxim MAX16068
|
||||
Prefix: 'max16068'
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX16068.pdf
|
||||
* Maxim MAX16070/MAX16071
|
||||
Prefixes: 'max16070', 'max16071'
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX16070-MAX16071.pdf
|
||||
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
[From datasheets] The MAX16065/MAX16066 flash-configurable system managers
|
||||
monitor and sequence multiple system voltages. The MAX16065/MAX16066 can also
|
||||
accurately monitor (+/-2.5%) one current channel using a dedicated high-side
|
||||
current-sense amplifier. The MAX16065 manages up to twelve system voltages
|
||||
simultaneously, and the MAX16066 manages up to eight supply voltages.
|
||||
|
||||
The MAX16067 flash-configurable system manager monitors and sequences multiple
|
||||
system voltages. The MAX16067 manages up to six system voltages simultaneously.
|
||||
|
||||
The MAX16068 flash-configurable system manager monitors and manages up to six
|
||||
system voltages simultaneously.
|
||||
|
||||
The MAX16070/MAX16071 flash-configurable system monitors supervise multiple
|
||||
system voltages. The MAX16070/MAX16071 can also accurately monitor (+/-2.5%)
|
||||
one current channel using a dedicated high-side current-sense amplifier. The
|
||||
MAX16070 monitors up to twelve system voltages simultaneously, and the MAX16071
|
||||
monitors up to eight supply voltages.
|
||||
|
||||
Each monitored channel has its own low and high critical limits. MAX16065,
|
||||
MAX16066, MAX16070, and MAX16071 support an additional limit which is
|
||||
configurable as either low or high secondary limit. MAX16065, MAX16066,
|
||||
MAX16070, and MAX16071 also support supply current monitoring.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not probe for devices, since there is no register which
|
||||
can be safely used to identify the chip. You will have to instantiate
|
||||
the devices explicitly. Please see Documentation/i2c/instantiating-devices for
|
||||
details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
in[0-11]_input Input voltage measurements.
|
||||
|
||||
in12_input Voltage on CSP (Current Sense Positive) pin.
|
||||
Only if the chip supports current sensing and if
|
||||
current sensing is enabled.
|
||||
|
||||
in[0-11]_min Low warning limit.
|
||||
Supported on MAX16065, MAX16066, MAX16070, and MAX16071
|
||||
only.
|
||||
|
||||
in[0-11]_max High warning limit.
|
||||
Supported on MAX16065, MAX16066, MAX16070, and MAX16071
|
||||
only.
|
||||
|
||||
Either low or high warning limits are supported
|
||||
(depending on chip configuration), but not both.
|
||||
|
||||
in[0-11]_lcrit Low critical limit.
|
||||
|
||||
in[0-11]_crit High critical limit.
|
||||
|
||||
in[0-11]_alarm Input voltage alarm.
|
||||
|
||||
curr1_input Current sense input; only if the chip supports current
|
||||
sensing and if current sensing is enabled.
|
||||
Displayed current assumes 0.001 Ohm current sense
|
||||
resistor.
|
||||
|
||||
curr1_alarm Overcurrent alarm; only if the chip supports current
|
||||
sensing and if current sensing is enabled.
|
|
@ -0,0 +1,21 @@
|
|||
Kernel driver max6642
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* Maxim MAX6642
|
||||
Prefix: 'max6642'
|
||||
Addresses scanned: I2C 0x48-0x4f
|
||||
Datasheet: Publicly available at the Maxim website
|
||||
http://datasheets.maxim-ic.com/en/ds/MAX6642.pdf
|
||||
|
||||
Authors:
|
||||
Per Dalen <per.dalen@appeartv.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The MAX6642 is a digital temperature sensor. It senses its own temperature as
|
||||
well as the temperature on one external diode.
|
||||
|
||||
All temperature values are given in degrees Celsius. Resolution
|
||||
is 0.25 degree for the local temperature and for the remote temperature.
|
|
@ -1,36 +0,0 @@
|
|||
Kernel driver pkgtemp
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
* Intel family
|
||||
Prefix: 'pkgtemp'
|
||||
CPUID:
|
||||
Datasheet: Intel 64 and IA-32 Architectures Software Developer's Manual
|
||||
Volume 3A: System Programming Guide
|
||||
|
||||
Author: Fenghua Yu
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver permits reading package level temperature sensor embedded inside
|
||||
Intel CPU package. The sensors can be in core, uncore, memory controller, or
|
||||
other components in a package. The feature is first implemented in Intel Sandy
|
||||
Bridge platform.
|
||||
|
||||
Temperature is measured in degrees Celsius and measurement resolution is
|
||||
1 degree C. Valid temperatures are from 0 to TjMax degrees C, because the actual
|
||||
value of temperature register is in fact a delta from TjMax.
|
||||
|
||||
Temperature known as TjMax is the maximum junction temperature of package.
|
||||
We get this from MSR_IA32_TEMPERATURE_TARGET. If the MSR is not accessible,
|
||||
we define TjMax as 100 degrees Celsius. At this temperature, protection
|
||||
mechanism will perform actions to forcibly cool down the package. Alarm
|
||||
may be raised, if the temperature grows enough (more than TjMax) to trigger
|
||||
the Out-Of-Spec bit. Following table summarizes the exported sysfs files:
|
||||
|
||||
temp1_input - Package temperature (in millidegrees Celsius).
|
||||
temp1_max - All cooling devices should be turned on.
|
||||
temp1_crit - Maximum junction temperature (in millidegrees Celsius).
|
||||
temp1_crit_alarm - Set when Out-of-spec bit is set, never clears.
|
||||
Correct CPU operation is no longer guaranteed.
|
|
@ -0,0 +1,74 @@
|
|||
Kernel driver sht15
|
||||
===================
|
||||
|
||||
Authors:
|
||||
* Wouter Horre
|
||||
* Jonathan Cameron
|
||||
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
* Jerome Oufella <jerome.oufella@savoirfairelinux.com>
|
||||
|
||||
Supported chips:
|
||||
* Sensirion SHT10
|
||||
Prefix: 'sht10'
|
||||
|
||||
* Sensirion SHT11
|
||||
Prefix: 'sht11'
|
||||
|
||||
* Sensirion SHT15
|
||||
Prefix: 'sht15'
|
||||
|
||||
* Sensirion SHT71
|
||||
Prefix: 'sht71'
|
||||
|
||||
* Sensirion SHT75
|
||||
Prefix: 'sht75'
|
||||
|
||||
Datasheet: Publicly available at the Sensirion website
|
||||
http://www.sensirion.ch/en/pdf/product_information/Datasheet-humidity-sensor-SHT1x.pdf
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The SHT10, SHT11, SHT15, SHT71, and SHT75 are humidity and temperature
|
||||
sensors.
|
||||
|
||||
The devices communicate using two GPIO lines.
|
||||
|
||||
Supported resolutions for the measurements are 14 bits for temperature and 12
|
||||
bits for humidity, or 12 bits for temperature and 8 bits for humidity.
|
||||
|
||||
The humidity calibration coefficients are programmed into an OTP memory on the
|
||||
chip. These coefficients are used to internally calibrate the signals from the
|
||||
sensors. Disabling the reload of those coefficients allows saving 10ms for each
|
||||
measurement and decrease power consumption, while loosing on precision.
|
||||
|
||||
Some options may be set directly in the sht15_platform_data structure
|
||||
or via sysfs attributes.
|
||||
|
||||
Notes:
|
||||
* The regulator supply name is set to "vcc".
|
||||
* If a CRC validation fails, a soft reset command is sent, which resets
|
||||
status register to its hardware default value, but the driver will try to
|
||||
restore the previous device configuration.
|
||||
|
||||
Platform data
|
||||
-------------
|
||||
|
||||
* checksum:
|
||||
set it to true to enable CRC validation of the readings (default to false).
|
||||
* no_otp_reload:
|
||||
flag to indicate not to reload from OTP (default to false).
|
||||
* low_resolution:
|
||||
flag to indicate the temp/humidity resolution to use (default to false).
|
||||
|
||||
Sysfs interface
|
||||
---------------
|
||||
|
||||
* temp1_input: temperature input
|
||||
* humidity1_input: humidity input
|
||||
* heater_enable: write 1 in this attribute to enable the on-chip heater,
|
||||
0 to disable it. Be careful not to enable the heater
|
||||
for too long.
|
||||
* temp1_fault: if 1, this means that the voltage is low (below 2.47V) and
|
||||
measurement may be invalid.
|
||||
* humidity1_fault: same as temp1_fault.
|
|
@ -0,0 +1,110 @@
|
|||
Kernel driver ucd9000
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* TI UCD90120, UCD90124, UCD9090, and UCD90910
|
||||
Prefixes: 'ucd90120', 'ucd90124', 'ucd9090', 'ucd90910'
|
||||
Addresses scanned: -
|
||||
Datasheets:
|
||||
http://focus.ti.com/lit/ds/symlink/ucd90120.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd90124.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9090.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd90910.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
From datasheets:
|
||||
|
||||
The UCD90120 Power Supply Sequencer and System Health Monitor monitors and
|
||||
sequences up to 12 independent voltage rails. The device integrates a 12-bit
|
||||
ADC with a 2.5V internal reference for monitoring up to 13 power supply voltage,
|
||||
current, or temperature inputs.
|
||||
|
||||
The UCD90124 is a 12-rail PMBus/I2C addressable power-supply sequencer and
|
||||
system-health monitor. The device integrates a 12-bit ADC for monitoring up to
|
||||
13 power-supply voltage, current, or temperature inputs. Twenty-six GPIO pins
|
||||
can be used for power supply enables, power-on reset signals, external
|
||||
interrupts, cascading, or other system functions. Twelve of these pins offer PWM
|
||||
functionality. Using these pins, the UCD90124 offers support for fan control,
|
||||
margining, and general-purpose PWM functions.
|
||||
|
||||
The UCD9090 is a 10-rail PMBus/I2C addressable power-supply sequencer and
|
||||
monitor. The device integrates a 12-bit ADC for monitoring up to 10 power-supply
|
||||
voltage inputs. Twenty-three GPIO pins can be used for power supply enables,
|
||||
power-on reset signals, external interrupts, cascading, or other system
|
||||
functions. Ten of these pins offer PWM functionality. Using these pins, the
|
||||
UCD9090 offers support for margining, and general-purpose PWM functions.
|
||||
|
||||
The UCD90910 is a ten-rail I2C / PMBus addressable power-supply sequencer and
|
||||
system-health monitor. The device integrates a 12-bit ADC for monitoring up to
|
||||
13 power-supply voltage, current, or temperature inputs.
|
||||
|
||||
This driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus for details on PMBus client drivers.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices for
|
||||
details.
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data. Please see
|
||||
Documentation/hwmon/pmbus for details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Limits are read-write; all other
|
||||
attributes are read-only.
|
||||
|
||||
in[1-12]_label "vout[1-12]".
|
||||
in[1-12]_input Measured voltage. From READ_VOUT register.
|
||||
in[1-12]_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register.
|
||||
in[1-12]_max Maximum voltage. From VOUT_OV_WARN_LIMIT register.
|
||||
in[1-12]_lcrit Critical minumum Voltage. VOUT_UV_FAULT_LIMIT register.
|
||||
in[1-12]_crit Critical maximum voltage. From VOUT_OV_FAULT_LIMIT register.
|
||||
in[1-12]_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status.
|
||||
in[1-12]_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status.
|
||||
in[1-12]_lcrit_alarm Voltage critical low alarm. From VOLTAGE_UV_FAULT status.
|
||||
in[1-12]_crit_alarm Voltage critical high alarm. From VOLTAGE_OV_FAULT status.
|
||||
|
||||
curr[1-12]_label "iout[1-12]".
|
||||
curr[1-12]_input Measured current. From READ_IOUT register.
|
||||
curr[1-12]_max Maximum current. From IOUT_OC_WARN_LIMIT register.
|
||||
curr[1-12]_lcrit Critical minumum output current. From IOUT_UC_FAULT_LIMIT
|
||||
register.
|
||||
curr[1-12]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register.
|
||||
curr[1-12]_max_alarm Current high alarm. From IOUT_OC_WARNING status.
|
||||
curr[1-12]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status.
|
||||
|
||||
For each attribute index, either voltage or current is
|
||||
reported, but not both. If voltage or current is
|
||||
reported depends on the chip configuration.
|
||||
|
||||
temp[1-2]_input Measured temperatures. From READ_TEMPERATURE_1 and
|
||||
READ_TEMPERATURE_2 registers.
|
||||
temp[1-2]_max Maximum temperature. From OT_WARN_LIMIT register.
|
||||
temp[1-2]_crit Critical high temperature. From OT_FAULT_LIMIT register.
|
||||
temp[1-2]_max_alarm Temperature high alarm.
|
||||
temp[1-2]_crit_alarm Temperature critical high alarm.
|
||||
|
||||
fan[1-4]_input Fan RPM.
|
||||
fan[1-4]_alarm Fan alarm.
|
||||
fan[1-4]_fault Fan fault.
|
||||
|
||||
Fan attributes are only available on chips supporting
|
||||
fan control (UCD90124, UCD90910). Attribute files are
|
||||
created only for enabled fans.
|
||||
Note that even though UCD90910 supports up to 10 fans,
|
||||
only up to four fans are currently supported.
|
|
@ -0,0 +1,112 @@
|
|||
Kernel driver ucd9200
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248
|
||||
Prefixes: 'ucd9220', 'ucd9222', 'ucd9224', 'ucd9240', 'ucd9244', 'ucd9246',
|
||||
'ucd9248'
|
||||
Addresses scanned: -
|
||||
Datasheets:
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9220.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9222.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9224.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9240.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9244.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9246.pdf
|
||||
http://focus.ti.com/lit/ds/symlink/ucd9248.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
[From datasheets] UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and
|
||||
UCD9248 are multi-rail, multi-phase synchronous buck digital PWM controllers
|
||||
designed for non-isolated DC/DC power applications. The devices integrate
|
||||
dedicated circuitry for DC/DC loop management with flash memory and a serial
|
||||
interface to support configuration, monitoring and management.
|
||||
|
||||
This driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus for details on PMBus client drivers.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices for
|
||||
details.
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data. Please see
|
||||
Documentation/hwmon/pmbus for details.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Limits are read-write; all other
|
||||
attributes are read-only.
|
||||
|
||||
in1_label "vin".
|
||||
in1_input Measured voltage. From READ_VIN register.
|
||||
in1_min Minumum Voltage. From VIN_UV_WARN_LIMIT register.
|
||||
in1_max Maximum voltage. From VIN_OV_WARN_LIMIT register.
|
||||
in1_lcrit Critical minumum Voltage. VIN_UV_FAULT_LIMIT register.
|
||||
in1_crit Critical maximum voltage. From VIN_OV_FAULT_LIMIT register.
|
||||
in1_min_alarm Voltage low alarm. From VIN_UV_WARNING status.
|
||||
in1_max_alarm Voltage high alarm. From VIN_OV_WARNING status.
|
||||
in1_lcrit_alarm Voltage critical low alarm. From VIN_UV_FAULT status.
|
||||
in1_crit_alarm Voltage critical high alarm. From VIN_OV_FAULT status.
|
||||
|
||||
in[2-5]_label "vout[1-4]".
|
||||
in[2-5]_input Measured voltage. From READ_VOUT register.
|
||||
in[2-5]_min Minumum Voltage. From VOUT_UV_WARN_LIMIT register.
|
||||
in[2-5]_max Maximum voltage. From VOUT_OV_WARN_LIMIT register.
|
||||
in[2-5]_lcrit Critical minumum Voltage. VOUT_UV_FAULT_LIMIT register.
|
||||
in[2-5]_crit Critical maximum voltage. From VOUT_OV_FAULT_LIMIT register.
|
||||
in[2-5]_min_alarm Voltage low alarm. From VOLTAGE_UV_WARNING status.
|
||||
in[2-5]_max_alarm Voltage high alarm. From VOLTAGE_OV_WARNING status.
|
||||
in[2-5]_lcrit_alarm Voltage critical low alarm. From VOLTAGE_UV_FAULT status.
|
||||
in[2-5]_crit_alarm Voltage critical high alarm. From VOLTAGE_OV_FAULT status.
|
||||
|
||||
curr1_label "iin".
|
||||
curr1_input Measured current. From READ_IIN register.
|
||||
|
||||
curr[2-5]_label "iout[1-4]".
|
||||
curr[2-5]_input Measured current. From READ_IOUT register.
|
||||
curr[2-5]_max Maximum current. From IOUT_OC_WARN_LIMIT register.
|
||||
curr[2-5]_lcrit Critical minumum output current. From IOUT_UC_FAULT_LIMIT
|
||||
register.
|
||||
curr[2-5]_crit Critical maximum current. From IOUT_OC_FAULT_LIMIT register.
|
||||
curr[2-5]_max_alarm Current high alarm. From IOUT_OC_WARNING status.
|
||||
curr[2-5]_crit_alarm Current critical high alarm. From IOUT_OC_FAULT status.
|
||||
|
||||
power1_input Measured input power. From READ_PIN register.
|
||||
power1_label "pin"
|
||||
|
||||
power[2-5]_input Measured output power. From READ_POUT register.
|
||||
power[2-5]_label "pout[1-4]"
|
||||
|
||||
The number of output voltage, current, and power
|
||||
attribute sets is determined by the number of enabled
|
||||
rails. See chip datasheets for details.
|
||||
|
||||
temp[1-5]_input Measured temperatures. From READ_TEMPERATURE_1 and
|
||||
READ_TEMPERATURE_2 registers.
|
||||
temp1 is the chip internal temperature. temp[2-5] are
|
||||
rail temperatures. temp[2-5] attributes are only
|
||||
created for enabled rails. See chip datasheets for
|
||||
details.
|
||||
temp[1-5]_max Maximum temperature. From OT_WARN_LIMIT register.
|
||||
temp[1-5]_crit Critical high temperature. From OT_FAULT_LIMIT register.
|
||||
temp[1-5]_max_alarm Temperature high alarm.
|
||||
temp[1-5]_crit_alarm Temperature critical high alarm.
|
||||
|
||||
fan1_input Fan RPM. ucd9240 only.
|
||||
fan1_alarm Fan alarm. ucd9240 only.
|
||||
fan1_fault Fan fault. ucd9240 only.
|
|
@ -408,13 +408,6 @@ config SENSORS_CORETEMP
|
|||
sensor inside your CPU. Most of the family 6 CPUs
|
||||
are supported. Check Documentation/hwmon/coretemp for details.
|
||||
|
||||
config SENSORS_PKGTEMP
|
||||
tristate "Intel processor package temperature sensor"
|
||||
depends on X86 && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the package level temperature
|
||||
sensor inside your CPU. Check documentation/driver for details.
|
||||
|
||||
config SENSORS_IBMAEM
|
||||
tristate "IBM Active Energy Manager temperature/power sensors and control"
|
||||
select IPMI_SI
|
||||
|
@ -708,6 +701,22 @@ config SENSORS_MAX1111
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called max1111.
|
||||
|
||||
config SENSORS_MAX16065
|
||||
tristate "Maxim MAX16065 System Manager and compatibles"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for hardware monitoring
|
||||
capabilities of the following Maxim System Manager chips.
|
||||
MAX16065
|
||||
MAX16066
|
||||
MAX16067
|
||||
MAX16068
|
||||
MAX16070
|
||||
MAX16071
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max16065.
|
||||
|
||||
config SENSORS_MAX1619
|
||||
tristate "Maxim MAX1619 sensor chip"
|
||||
depends on I2C
|
||||
|
@ -727,6 +736,17 @@ config SENSORS_MAX6639
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called max6639.
|
||||
|
||||
config SENSORS_MAX6642
|
||||
tristate "Maxim MAX6642 sensor chip"
|
||||
depends on I2C && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for MAX6642 sensor chip.
|
||||
MAX6642 is a SMBus-Compatible Remote/Local Temperature Sensor
|
||||
with Overtemperature Alarm from Maxim.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max6642.
|
||||
|
||||
config SENSORS_MAX6650
|
||||
tristate "Maxim MAX6650 sensor chip"
|
||||
depends on I2C && EXPERIMENTAL
|
||||
|
@ -800,6 +820,16 @@ config SENSORS_PMBUS
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called pmbus.
|
||||
|
||||
config SENSORS_ADM1275
|
||||
tristate "Analog Devices ADM1275"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices ADM1275 Hot-Swap Controller and Digital Power Monitor.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called adm1275.
|
||||
|
||||
config SENSORS_MAX16064
|
||||
tristate "Maxim MAX16064"
|
||||
default n
|
||||
|
@ -830,6 +860,28 @@ config SENSORS_MAX8688
|
|||
This driver can also be built as a module. If so, the module will
|
||||
be called max8688.
|
||||
|
||||
config SENSORS_UCD9000
|
||||
tristate "TI UCD90120, UCD90124, UCD9090, UCD90910"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for TI
|
||||
UCD90120, UCD90124, UCD9090, UCD90910 Sequencer and System Health
|
||||
Controllers.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9000.
|
||||
|
||||
config SENSORS_UCD9200
|
||||
tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for TI
|
||||
UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248
|
||||
Digital PWM System Controllers.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9200.
|
||||
|
||||
endif # PMBUS
|
||||
|
||||
config SENSORS_SHT15
|
||||
|
|
|
@ -40,7 +40,6 @@ obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
|||
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
|
||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
||||
obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o
|
||||
obj-$(CONFIG_SENSORS_DME1737) += dme1737.o
|
||||
obj-$(CONFIG_SENSORS_DS620) += ds620.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
|
@ -83,8 +82,10 @@ obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
|
|||
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
|
||||
obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o
|
||||
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
|
||||
obj-$(CONFIG_SENSORS_MAX16065) += max16065.o
|
||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||
obj-$(CONFIG_SENSORS_MAX6639) += max6639.o
|
||||
obj-$(CONFIG_SENSORS_MAX6642) += max6642.o
|
||||
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
|
||||
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
|
@ -118,9 +119,12 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
|
|||
# PMBus drivers
|
||||
obj-$(CONFIG_PMBUS) += pmbus_core.o
|
||||
obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
|
||||
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
|
||||
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
|
||||
obj-$(CONFIG_SENSORS_MAX34440) += max34440.o
|
||||
obj-$(CONFIG_SENSORS_MAX8688) += max8688.o
|
||||
obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
|
||||
obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o
|
||||
|
||||
ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
|
||||
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller
|
||||
* and Digital Power Monitor
|
||||
*
|
||||
* Copyright (c) 2011 Ericsson AB.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define ADM1275_PMON_CONFIG 0xd4
|
||||
|
||||
#define ADM1275_VIN_VOUT_SELECT (1 << 6)
|
||||
#define ADM1275_VRANGE (1 << 5)
|
||||
|
||||
static int adm1275_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int config;
|
||||
struct pmbus_driver_info *info;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
|
||||
if (config < 0)
|
||||
return config;
|
||||
|
||||
info->pages = 1;
|
||||
info->direct[PSC_VOLTAGE_IN] = true;
|
||||
info->direct[PSC_VOLTAGE_OUT] = true;
|
||||
info->direct[PSC_CURRENT_OUT] = true;
|
||||
info->m[PSC_CURRENT_OUT] = 800;
|
||||
info->b[PSC_CURRENT_OUT] = 20475;
|
||||
info->R[PSC_CURRENT_OUT] = -1;
|
||||
info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
|
||||
|
||||
if (config & ADM1275_VRANGE) {
|
||||
info->m[PSC_VOLTAGE_IN] = 19045;
|
||||
info->b[PSC_VOLTAGE_IN] = 0;
|
||||
info->R[PSC_VOLTAGE_IN] = -2;
|
||||
info->m[PSC_VOLTAGE_OUT] = 19045;
|
||||
info->b[PSC_VOLTAGE_OUT] = 0;
|
||||
info->R[PSC_VOLTAGE_OUT] = -2;
|
||||
} else {
|
||||
info->m[PSC_VOLTAGE_IN] = 6666;
|
||||
info->b[PSC_VOLTAGE_IN] = 0;
|
||||
info->R[PSC_VOLTAGE_IN] = -1;
|
||||
info->m[PSC_VOLTAGE_OUT] = 6666;
|
||||
info->b[PSC_VOLTAGE_OUT] = 0;
|
||||
info->R[PSC_VOLTAGE_OUT] = -1;
|
||||
}
|
||||
|
||||
if (config & ADM1275_VIN_VOUT_SELECT)
|
||||
info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
|
||||
else
|
||||
info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT;
|
||||
|
||||
return pmbus_do_probe(client, id, info);
|
||||
}
|
||||
|
||||
static int adm1275_remove(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
int ret;
|
||||
|
||||
ret = pmbus_do_remove(client);
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adm1275_id[] = {
|
||||
{"adm1275", 0},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adm1275_id);
|
||||
|
||||
static struct i2c_driver adm1275_driver = {
|
||||
.driver = {
|
||||
.name = "adm1275",
|
||||
},
|
||||
.probe = adm1275_probe,
|
||||
.remove = adm1275_remove,
|
||||
.id_table = adm1275_id,
|
||||
};
|
||||
|
||||
static int __init adm1275_init(void)
|
||||
{
|
||||
return i2c_add_driver(&adm1275_driver);
|
||||
}
|
||||
|
||||
static void __exit adm1275_exit(void)
|
||||
{
|
||||
i2c_del_driver(&adm1275_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(adm1275_init);
|
||||
module_exit(adm1275_exit);
|
|
@ -35,128 +35,152 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/smp.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/smp.h>
|
||||
|
||||
#define DRVNAME "coretemp"
|
||||
|
||||
typedef enum { SHOW_TEMP, SHOW_TJMAX, SHOW_TTARGET, SHOW_LABEL,
|
||||
SHOW_NAME } SHOW;
|
||||
#define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */
|
||||
#define NUM_REAL_CORES 16 /* Number of Real cores per cpu */
|
||||
#define CORETEMP_NAME_LENGTH 17 /* String Length of attrs */
|
||||
#define MAX_ATTRS 5 /* Maximum no of per-core attrs */
|
||||
#define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO)
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
#define TO_PHYS_ID(cpu) cpu_data(cpu).phys_proc_id
|
||||
#define TO_CORE_ID(cpu) cpu_data(cpu).cpu_core_id
|
||||
#define TO_ATTR_NO(cpu) (TO_CORE_ID(cpu) + BASE_SYSFS_ATTR_NO)
|
||||
#else
|
||||
#define TO_PHYS_ID(cpu) (cpu)
|
||||
#define TO_CORE_ID(cpu) (cpu)
|
||||
#define TO_ATTR_NO(cpu) (cpu)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
* Per-Core Temperature Data
|
||||
* @last_updated: The time when the current temperature value was updated
|
||||
* earlier (in jiffies).
|
||||
* @cpu_core_id: The CPU Core from which temperature values should be read
|
||||
* This value is passed as "id" field to rdmsr/wrmsr functions.
|
||||
* @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS,
|
||||
* from where the temperature values should be read.
|
||||
* @is_pkg_data: If this is 1, the temp_data holds pkgtemp data.
|
||||
* Otherwise, temp_data holds coretemp data.
|
||||
* @valid: If this is 1, the current temperature is valid.
|
||||
*/
|
||||
|
||||
static struct coretemp_data *coretemp_update_device(struct device *dev);
|
||||
|
||||
struct coretemp_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
const char *name;
|
||||
u32 id;
|
||||
u16 core_id;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
struct temp_data {
|
||||
int temp;
|
||||
int tjmax;
|
||||
int ttarget;
|
||||
u8 alarm;
|
||||
int tjmax;
|
||||
unsigned long last_updated;
|
||||
unsigned int cpu;
|
||||
u32 cpu_core_id;
|
||||
u32 status_reg;
|
||||
bool is_pkg_data;
|
||||
bool valid;
|
||||
struct sensor_device_attribute sd_attrs[MAX_ATTRS];
|
||||
char attr_name[MAX_ATTRS][CORETEMP_NAME_LENGTH];
|
||||
struct mutex update_lock;
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
/* Platform Data per Physical CPU */
|
||||
struct platform_data {
|
||||
struct device *hwmon_dev;
|
||||
u16 phys_proc_id;
|
||||
struct temp_data *core_data[MAX_CORE_DATA];
|
||||
struct device_attribute name_attr;
|
||||
};
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
struct pdev_entry {
|
||||
struct list_head list;
|
||||
struct platform_device *pdev;
|
||||
unsigned int cpu;
|
||||
u16 phys_proc_id;
|
||||
u16 cpu_core_id;
|
||||
};
|
||||
|
||||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
|
||||
static ssize_t show_name(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct coretemp_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (attr->index == SHOW_NAME)
|
||||
ret = sprintf(buf, "%s\n", data->name);
|
||||
else /* show label */
|
||||
ret = sprintf(buf, "Core %d\n", data->core_id);
|
||||
return ret;
|
||||
return sprintf(buf, "%s\n", DRVNAME);
|
||||
}
|
||||
|
||||
static ssize_t show_alarm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
static ssize_t show_label(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct coretemp_data *data = coretemp_update_device(dev);
|
||||
/* read the Out-of-spec log, never clear */
|
||||
return sprintf(buf, "%d\n", data->alarm);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
struct temp_data *tdata = pdata->core_data[attr->index];
|
||||
|
||||
if (tdata->is_pkg_data)
|
||||
return sprintf(buf, "Physical id %u\n", pdata->phys_proc_id);
|
||||
|
||||
return sprintf(buf, "Core %u\n", tdata->cpu_core_id);
|
||||
}
|
||||
|
||||
static ssize_t show_crit_alarm(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
u32 eax, edx;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
struct temp_data *tdata = pdata->core_data[attr->index];
|
||||
|
||||
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
|
||||
|
||||
return sprintf(buf, "%d\n", (eax >> 5) & 1);
|
||||
}
|
||||
|
||||
static ssize_t show_tjmax(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", pdata->core_data[attr->index]->tjmax);
|
||||
}
|
||||
|
||||
static ssize_t show_ttarget(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", pdata->core_data[attr->index]->ttarget);
|
||||
}
|
||||
|
||||
static ssize_t show_temp(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
u32 eax, edx;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct coretemp_data *data = coretemp_update_device(dev);
|
||||
int err;
|
||||
struct platform_data *pdata = dev_get_drvdata(dev);
|
||||
struct temp_data *tdata = pdata->core_data[attr->index];
|
||||
|
||||
if (attr->index == SHOW_TEMP)
|
||||
err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN;
|
||||
else if (attr->index == SHOW_TJMAX)
|
||||
err = sprintf(buf, "%d\n", data->tjmax);
|
||||
else
|
||||
err = sprintf(buf, "%d\n", data->ttarget);
|
||||
return err;
|
||||
}
|
||||
mutex_lock(&tdata->update_lock);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TEMP);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TJMAX);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL,
|
||||
SHOW_TTARGET);
|
||||
static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL);
|
||||
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
|
||||
static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME);
|
||||
|
||||
static struct attribute *coretemp_attributes[] = {
|
||||
&sensor_dev_attr_name.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
&dev_attr_temp1_crit_alarm.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group coretemp_group = {
|
||||
.attrs = coretemp_attributes,
|
||||
};
|
||||
|
||||
static struct coretemp_data *coretemp_update_device(struct device *dev)
|
||||
{
|
||||
struct coretemp_data *data = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (!data->valid || time_after(jiffies, data->last_updated + HZ)) {
|
||||
u32 eax, edx;
|
||||
|
||||
data->valid = 0;
|
||||
rdmsr_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx);
|
||||
data->alarm = (eax >> 5) & 1;
|
||||
/* update only if data has been valid */
|
||||
/* Check whether the time interval has elapsed */
|
||||
if (!tdata->valid || time_after(jiffies, tdata->last_updated + HZ)) {
|
||||
rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx);
|
||||
tdata->valid = 0;
|
||||
/* Check whether the data is valid */
|
||||
if (eax & 0x80000000) {
|
||||
data->temp = data->tjmax - (((eax >> 16)
|
||||
& 0x7f) * 1000);
|
||||
data->valid = 1;
|
||||
} else {
|
||||
dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax);
|
||||
tdata->temp = tdata->tjmax -
|
||||
((eax >> 16) & 0x7f) * 1000;
|
||||
tdata->valid = 1;
|
||||
}
|
||||
data->last_updated = jiffies;
|
||||
tdata->last_updated = jiffies;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return data;
|
||||
mutex_unlock(&tdata->update_lock);
|
||||
return tdata->valid ? sprintf(buf, "%d\n", tdata->temp) : -EAGAIN;
|
||||
}
|
||||
|
||||
static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
||||
static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
||||
{
|
||||
/* The 100C is default for both mobile and non mobile CPUs */
|
||||
|
||||
|
@ -169,9 +193,8 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *
|
|||
|
||||
/* Early chips have no MSR for TjMax */
|
||||
|
||||
if ((c->x86_model == 0xf) && (c->x86_mask < 4)) {
|
||||
if (c->x86_model == 0xf && c->x86_mask < 4)
|
||||
usemsr_ee = 0;
|
||||
}
|
||||
|
||||
/* Atom CPUs */
|
||||
|
||||
|
@ -190,14 +213,14 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *
|
|||
pci_dev_put(host_bridge);
|
||||
}
|
||||
|
||||
if ((c->x86_model > 0xe) && (usemsr_ee)) {
|
||||
if (c->x86_model > 0xe && usemsr_ee) {
|
||||
u8 platform_id;
|
||||
|
||||
/* Now we can detect the mobile CPU using Intel provided table
|
||||
http://softwarecommunity.intel.com/Wiki/Mobility/720.htm
|
||||
For Core2 cores, check MSR 0x17, bit 28 1 = Mobile CPU
|
||||
*/
|
||||
|
||||
/*
|
||||
* Now we can detect the mobile CPU using Intel provided table
|
||||
* http://softwarecommunity.intel.com/Wiki/Mobility/720.htm
|
||||
* For Core2 cores, check MSR 0x17, bit 28 1 = Mobile CPU
|
||||
*/
|
||||
err = rdmsr_safe_on_cpu(id, 0x17, &eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(dev,
|
||||
|
@ -205,20 +228,26 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *
|
|||
" CPU\n");
|
||||
usemsr_ee = 0;
|
||||
} else if (c->x86_model < 0x17 && !(eax & 0x10000000)) {
|
||||
/* Trust bit 28 up to Penryn, I could not find any
|
||||
documentation on that; if you happen to know
|
||||
someone at Intel please ask */
|
||||
/*
|
||||
* Trust bit 28 up to Penryn, I could not find any
|
||||
* documentation on that; if you happen to know
|
||||
* someone at Intel please ask
|
||||
*/
|
||||
usemsr_ee = 0;
|
||||
} else {
|
||||
/* Platform ID bits 52:50 (EDX starts at bit 32) */
|
||||
platform_id = (edx >> 18) & 0x7;
|
||||
|
||||
/* Mobile Penryn CPU seems to be platform ID 7 or 5
|
||||
(guesswork) */
|
||||
if ((c->x86_model == 0x17) &&
|
||||
((platform_id == 5) || (platform_id == 7))) {
|
||||
/* If MSR EE bit is set, set it to 90 degrees C,
|
||||
otherwise 105 degrees C */
|
||||
/*
|
||||
* Mobile Penryn CPU seems to be platform ID 7 or 5
|
||||
* (guesswork)
|
||||
*/
|
||||
if (c->x86_model == 0x17 &&
|
||||
(platform_id == 5 || platform_id == 7)) {
|
||||
/*
|
||||
* If MSR EE bit is set, set it to 90 degrees C,
|
||||
* otherwise 105 degrees C
|
||||
*/
|
||||
tjmax_ee = 90000;
|
||||
tjmax = 105000;
|
||||
}
|
||||
|
@ -226,7 +255,6 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *
|
|||
}
|
||||
|
||||
if (usemsr_ee) {
|
||||
|
||||
err = rdmsr_safe_on_cpu(id, 0xee, &eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(dev,
|
||||
|
@ -235,25 +263,28 @@ static int __devinit adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *
|
|||
} else if (eax & 0x40000000) {
|
||||
tjmax = tjmax_ee;
|
||||
}
|
||||
/* if we dont use msr EE it means we are desktop CPU (with exeception
|
||||
of Atom) */
|
||||
} else if (tjmax == 100000) {
|
||||
/*
|
||||
* If we don't use msr EE it means we are desktop CPU
|
||||
* (with exeception of Atom)
|
||||
*/
|
||||
dev_warn(dev, "Using relative temperature scale!\n");
|
||||
}
|
||||
|
||||
return tjmax;
|
||||
}
|
||||
|
||||
static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id,
|
||||
struct device *dev)
|
||||
static int get_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev)
|
||||
{
|
||||
/* The 100C is default for both mobile and non mobile CPUs */
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
u32 val;
|
||||
|
||||
/* A new feature of current Intel(R) processors, the
|
||||
IA32_TEMPERATURE_TARGET contains the TjMax value */
|
||||
/*
|
||||
* A new feature of current Intel(R) processors, the
|
||||
* IA32_TEMPERATURE_TARGET contains the TjMax value
|
||||
*/
|
||||
err = rdmsr_safe_on_cpu(id, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(dev, "Unable to read TjMax from CPU.\n");
|
||||
|
@ -263,7 +294,7 @@ static int __devinit get_tjmax(struct cpuinfo_x86 *c, u32 id,
|
|||
* If the TjMax is not plausible, an assumption
|
||||
* will be used
|
||||
*/
|
||||
if ((val > 80) && (val < 120)) {
|
||||
if (val > 80 && val < 120) {
|
||||
dev_info(dev, "TjMax is %d C.\n", val);
|
||||
return val * 1000;
|
||||
}
|
||||
|
@ -300,115 +331,293 @@ static void __devinit get_ucode_rev_on_cpu(void *edx)
|
|||
rdmsr(MSR_IA32_UCODE_REV, eax, *(u32 *)edx);
|
||||
}
|
||||
|
||||
static int __devinit coretemp_probe(struct platform_device *pdev)
|
||||
static int get_pkg_tjmax(unsigned int cpu, struct device *dev)
|
||||
{
|
||||
int err;
|
||||
u32 eax, edx, val;
|
||||
|
||||
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (!err) {
|
||||
val = (eax >> 16) & 0xff;
|
||||
if (val > 80 && val < 120)
|
||||
return val * 1000;
|
||||
}
|
||||
dev_warn(dev, "Unable to read Pkg-TjMax from CPU:%u\n", cpu);
|
||||
return 100000; /* Default TjMax: 100 degree celsius */
|
||||
}
|
||||
|
||||
static int create_name_attr(struct platform_data *pdata, struct device *dev)
|
||||
{
|
||||
pdata->name_attr.attr.name = "name";
|
||||
pdata->name_attr.attr.mode = S_IRUGO;
|
||||
pdata->name_attr.show = show_name;
|
||||
return device_create_file(dev, &pdata->name_attr);
|
||||
}
|
||||
|
||||
static int create_core_attrs(struct temp_data *tdata, struct device *dev,
|
||||
int attr_no)
|
||||
{
|
||||
int err, i;
|
||||
static ssize_t (*rd_ptr[MAX_ATTRS]) (struct device *dev,
|
||||
struct device_attribute *devattr, char *buf) = {
|
||||
show_label, show_crit_alarm, show_ttarget,
|
||||
show_temp, show_tjmax };
|
||||
static const char *names[MAX_ATTRS] = {
|
||||
"temp%d_label", "temp%d_crit_alarm",
|
||||
"temp%d_max", "temp%d_input",
|
||||
"temp%d_crit" };
|
||||
|
||||
for (i = 0; i < MAX_ATTRS; i++) {
|
||||
snprintf(tdata->attr_name[i], CORETEMP_NAME_LENGTH, names[i],
|
||||
attr_no);
|
||||
tdata->sd_attrs[i].dev_attr.attr.name = tdata->attr_name[i];
|
||||
tdata->sd_attrs[i].dev_attr.attr.mode = S_IRUGO;
|
||||
tdata->sd_attrs[i].dev_attr.show = rd_ptr[i];
|
||||
tdata->sd_attrs[i].dev_attr.store = NULL;
|
||||
tdata->sd_attrs[i].index = attr_no;
|
||||
err = device_create_file(dev, &tdata->sd_attrs[i].dev_attr);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit_free:
|
||||
while (--i >= 0)
|
||||
device_remove_file(dev, &tdata->sd_attrs[i].dev_attr);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void update_ttarget(__u8 cpu_model, struct temp_data *tdata,
|
||||
struct device *dev)
|
||||
{
|
||||
struct coretemp_data *data;
|
||||
struct cpuinfo_x86 *c = &cpu_data(pdev->id);
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct coretemp_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
dev_err(&pdev->dev, "Out of memory\n");
|
||||
goto exit;
|
||||
/*
|
||||
* Initialize ttarget value. Eventually this will be
|
||||
* initialized with the value from MSR_IA32_THERM_INTERRUPT
|
||||
* register. If IA32_TEMPERATURE_TARGET is supported, this
|
||||
* value will be over written below.
|
||||
* To Do: Patch to initialize ttarget from MSR_IA32_THERM_INTERRUPT
|
||||
*/
|
||||
tdata->ttarget = tdata->tjmax - 20000;
|
||||
|
||||
/*
|
||||
* Read the still undocumented IA32_TEMPERATURE_TARGET. It exists
|
||||
* on older CPUs but not in this register,
|
||||
* Atoms don't have it either.
|
||||
*/
|
||||
if (cpu_model > 0xe && cpu_model != 0x1c) {
|
||||
err = rdmsr_safe_on_cpu(tdata->cpu,
|
||||
MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(dev,
|
||||
"Unable to read IA32_TEMPERATURE_TARGET MSR\n");
|
||||
} else {
|
||||
tdata->ttarget = tdata->tjmax -
|
||||
((eax >> 8) & 0xff) * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->id = pdev->id;
|
||||
#ifdef CONFIG_SMP
|
||||
data->core_id = c->cpu_core_id;
|
||||
#endif
|
||||
data->name = "coretemp";
|
||||
mutex_init(&data->update_lock);
|
||||
static int chk_ucode_version(struct platform_device *pdev)
|
||||
{
|
||||
struct cpuinfo_x86 *c = &cpu_data(pdev->id);
|
||||
int err;
|
||||
u32 edx;
|
||||
|
||||
/* test if we can access the THERM_STATUS MSR */
|
||||
err = rdmsr_safe_on_cpu(data->id, MSR_IA32_THERM_STATUS, &eax, &edx);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to access THERM_STATUS MSR, giving up\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* Check if we have problem with errata AE18 of Core processors:
|
||||
Readings might stop update when processor visited too deep sleep,
|
||||
fixed for stepping D0 (6EC).
|
||||
*/
|
||||
|
||||
if ((c->x86_model == 0xe) && (c->x86_mask < 0xc)) {
|
||||
/*
|
||||
* Check if we have problem with errata AE18 of Core processors:
|
||||
* Readings might stop update when processor visited too deep sleep,
|
||||
* fixed for stepping D0 (6EC).
|
||||
*/
|
||||
if (c->x86_model == 0xe && c->x86_mask < 0xc) {
|
||||
/* check for microcode update */
|
||||
err = smp_call_function_single(data->id, get_ucode_rev_on_cpu,
|
||||
err = smp_call_function_single(pdev->id, get_ucode_rev_on_cpu,
|
||||
&edx, 1);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Cannot determine microcode revision of "
|
||||
"CPU#%u (%d)!\n", data->id, err);
|
||||
err = -ENODEV;
|
||||
goto exit_free;
|
||||
"CPU#%u (%d)!\n", pdev->id, err);
|
||||
return -ENODEV;
|
||||
} else if (edx < 0x39) {
|
||||
err = -ENODEV;
|
||||
dev_err(&pdev->dev,
|
||||
"Errata AE18 not fixed, update BIOS or "
|
||||
"microcode of the CPU!\n");
|
||||
goto exit_free;
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
data->tjmax = get_tjmax(c, data->id, &pdev->dev);
|
||||
platform_set_drvdata(pdev, data);
|
||||
static struct platform_device *coretemp_get_pdev(unsigned int cpu)
|
||||
{
|
||||
u16 phys_proc_id = TO_PHYS_ID(cpu);
|
||||
struct pdev_entry *p;
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
|
||||
list_for_each_entry(p, &pdev_list, list)
|
||||
if (p->phys_proc_id == phys_proc_id) {
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return p->pdev;
|
||||
}
|
||||
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct temp_data *init_temp_data(unsigned int cpu, int pkg_flag)
|
||||
{
|
||||
struct temp_data *tdata;
|
||||
|
||||
tdata = kzalloc(sizeof(struct temp_data), GFP_KERNEL);
|
||||
if (!tdata)
|
||||
return NULL;
|
||||
|
||||
tdata->status_reg = pkg_flag ? MSR_IA32_PACKAGE_THERM_STATUS :
|
||||
MSR_IA32_THERM_STATUS;
|
||||
tdata->is_pkg_data = pkg_flag;
|
||||
tdata->cpu = cpu;
|
||||
tdata->cpu_core_id = TO_CORE_ID(cpu);
|
||||
mutex_init(&tdata->update_lock);
|
||||
return tdata;
|
||||
}
|
||||
|
||||
static int create_core_data(struct platform_data *pdata,
|
||||
struct platform_device *pdev,
|
||||
unsigned int cpu, int pkg_flag)
|
||||
{
|
||||
struct temp_data *tdata;
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
u32 eax, edx;
|
||||
int err, attr_no;
|
||||
|
||||
/*
|
||||
* read the still undocumented IA32_TEMPERATURE_TARGET. It exists
|
||||
* on older CPUs but not in this register,
|
||||
* Atoms don't have it either.
|
||||
* Find attr number for sysfs:
|
||||
* We map the attr number to core id of the CPU
|
||||
* The attr number is always core id + 2
|
||||
* The Pkgtemp will always show up as temp1_*, if available
|
||||
*/
|
||||
attr_no = pkg_flag ? 1 : TO_ATTR_NO(cpu);
|
||||
|
||||
if ((c->x86_model > 0xe) && (c->x86_model != 0x1c)) {
|
||||
err = rdmsr_safe_on_cpu(data->id, MSR_IA32_TEMPERATURE_TARGET,
|
||||
&eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(&pdev->dev, "Unable to read"
|
||||
" IA32_TEMPERATURE_TARGET MSR\n");
|
||||
} else {
|
||||
data->ttarget = data->tjmax -
|
||||
(((eax >> 8) & 0xff) * 1000);
|
||||
err = device_create_file(&pdev->dev,
|
||||
&sensor_dev_attr_temp1_max.dev_attr);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
}
|
||||
}
|
||||
if (attr_no > MAX_CORE_DATA - 1)
|
||||
return -ERANGE;
|
||||
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &coretemp_group)))
|
||||
goto exit_dev;
|
||||
/* Skip if it is a HT core, Not an error */
|
||||
if (pdata->core_data[attr_no] != NULL)
|
||||
return 0;
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
dev_err(&pdev->dev, "Class registration failed (%d)\n",
|
||||
err);
|
||||
goto exit_class;
|
||||
}
|
||||
tdata = init_temp_data(cpu, pkg_flag);
|
||||
if (!tdata)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Test if we can access the status register */
|
||||
err = rdmsr_safe_on_cpu(cpu, tdata->status_reg, &eax, &edx);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
|
||||
/* We can access status register. Get Critical Temperature */
|
||||
if (pkg_flag)
|
||||
tdata->tjmax = get_pkg_tjmax(pdev->id, &pdev->dev);
|
||||
else
|
||||
tdata->tjmax = get_tjmax(c, cpu, &pdev->dev);
|
||||
|
||||
update_ttarget(c->x86_model, tdata, &pdev->dev);
|
||||
pdata->core_data[attr_no] = tdata;
|
||||
|
||||
/* Create sysfs interfaces */
|
||||
err = create_core_attrs(tdata, &pdev->dev, attr_no);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_class:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &coretemp_group);
|
||||
exit_dev:
|
||||
device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
kfree(tdata);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void coretemp_add_core(unsigned int cpu, int pkg_flag)
|
||||
{
|
||||
struct platform_data *pdata;
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
int err;
|
||||
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
pdata = platform_get_drvdata(pdev);
|
||||
|
||||
err = create_core_data(pdata, pdev, cpu, pkg_flag);
|
||||
if (err)
|
||||
dev_err(&pdev->dev, "Adding Core %u failed\n", cpu);
|
||||
}
|
||||
|
||||
static void coretemp_remove_core(struct platform_data *pdata,
|
||||
struct device *dev, int indx)
|
||||
{
|
||||
int i;
|
||||
struct temp_data *tdata = pdata->core_data[indx];
|
||||
|
||||
/* Remove the sysfs attributes */
|
||||
for (i = 0; i < MAX_ATTRS; i++)
|
||||
device_remove_file(dev, &tdata->sd_attrs[i].dev_attr);
|
||||
|
||||
kfree(pdata->core_data[indx]);
|
||||
pdata->core_data[indx] = NULL;
|
||||
}
|
||||
|
||||
static int __devinit coretemp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct platform_data *pdata;
|
||||
int err;
|
||||
|
||||
/* Check the microcode version of the CPU */
|
||||
err = chk_ucode_version(pdev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Initialize the per-package data structures */
|
||||
pdata = kzalloc(sizeof(struct platform_data), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
err = create_name_attr(pdata, &pdev->dev);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
|
||||
pdata->phys_proc_id = TO_PHYS_ID(pdev->id);
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
|
||||
pdata->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(pdata->hwmon_dev)) {
|
||||
err = PTR_ERR(pdata->hwmon_dev);
|
||||
dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
|
||||
goto exit_name;
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit_name:
|
||||
device_remove_file(&pdev->dev, &pdata->name_attr);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
exit_free:
|
||||
kfree(pdata);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit coretemp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct coretemp_data *data = platform_get_drvdata(pdev);
|
||||
struct platform_data *pdata = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &coretemp_group);
|
||||
device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr);
|
||||
for (i = MAX_CORE_DATA - 1; i >= 0; --i)
|
||||
if (pdata->core_data[i])
|
||||
coretemp_remove_core(pdata, &pdev->dev, i);
|
||||
|
||||
device_remove_file(&pdev->dev, &pdata->name_attr);
|
||||
hwmon_device_unregister(pdata->hwmon_dev);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
kfree(pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -421,50 +630,14 @@ static struct platform_driver coretemp_driver = {
|
|||
.remove = __devexit_p(coretemp_remove),
|
||||
};
|
||||
|
||||
struct pdev_entry {
|
||||
struct list_head list;
|
||||
struct platform_device *pdev;
|
||||
unsigned int cpu;
|
||||
#ifdef CONFIG_SMP
|
||||
u16 phys_proc_id;
|
||||
u16 cpu_core_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
|
||||
static int __cpuinit coretemp_device_add(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
struct platform_device *pdev;
|
||||
struct pdev_entry *pdev_entry;
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
|
||||
/*
|
||||
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
|
||||
* sensors. We check this bit only, all the early CPUs
|
||||
* without thermal sensors will be filtered out.
|
||||
*/
|
||||
if (!cpu_has(c, X86_FEATURE_DTS)) {
|
||||
pr_info("CPU (model=0x%x) has no thermal sensor\n",
|
||||
c->x86_model);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/* Skip second HT entry of each core */
|
||||
list_for_each_entry(pdev_entry, &pdev_list, list) {
|
||||
if (c->phys_proc_id == pdev_entry->phys_proc_id &&
|
||||
c->cpu_core_id == pdev_entry->cpu_core_id) {
|
||||
err = 0; /* Not an error */
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
pdev = platform_device_alloc(DRVNAME, cpu);
|
||||
if (!pdev) {
|
||||
err = -ENOMEM;
|
||||
|
@ -486,10 +659,9 @@ static int __cpuinit coretemp_device_add(unsigned int cpu)
|
|||
|
||||
pdev_entry->pdev = pdev;
|
||||
pdev_entry->cpu = cpu;
|
||||
#ifdef CONFIG_SMP
|
||||
pdev_entry->phys_proc_id = c->phys_proc_id;
|
||||
pdev_entry->cpu_core_id = c->cpu_core_id;
|
||||
#endif
|
||||
pdev_entry->phys_proc_id = TO_PHYS_ID(cpu);
|
||||
pdev_entry->cpu_core_id = TO_CORE_ID(cpu);
|
||||
|
||||
list_add_tail(&pdev_entry->list, &pdev_list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
|
||||
|
@ -504,28 +676,108 @@ exit:
|
|||
return err;
|
||||
}
|
||||
|
||||
static void __cpuinit coretemp_device_remove(unsigned int cpu)
|
||||
static void coretemp_device_remove(unsigned int cpu)
|
||||
{
|
||||
struct pdev_entry *p;
|
||||
unsigned int i;
|
||||
struct pdev_entry *p, *n;
|
||||
u16 phys_proc_id = TO_PHYS_ID(cpu);
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry(p, &pdev_list, list) {
|
||||
if (p->cpu != cpu)
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
if (p->phys_proc_id != phys_proc_id)
|
||||
continue;
|
||||
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
kfree(p);
|
||||
for_each_cpu(i, cpu_sibling_mask(cpu))
|
||||
if (i != cpu && !coretemp_device_add(i))
|
||||
break;
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
}
|
||||
|
||||
static bool is_any_core_online(struct platform_data *pdata)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Find online cores, except pkgtemp data */
|
||||
for (i = MAX_CORE_DATA - 1; i >= 0; --i) {
|
||||
if (pdata->core_data[i] &&
|
||||
!pdata->core_data[i]->is_pkg_data) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __cpuinit get_core_online(unsigned int cpu)
|
||||
{
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
int err;
|
||||
|
||||
/*
|
||||
* CPUID.06H.EAX[0] indicates whether the CPU has thermal
|
||||
* sensors. We check this bit only, all the early CPUs
|
||||
* without thermal sensors will be filtered out.
|
||||
*/
|
||||
if (!cpu_has(c, X86_FEATURE_DTS))
|
||||
return;
|
||||
|
||||
if (!pdev) {
|
||||
/*
|
||||
* Alright, we have DTS support.
|
||||
* We are bringing the _first_ core in this pkg
|
||||
* online. So, initialize per-pkg data structures and
|
||||
* then bring this core online.
|
||||
*/
|
||||
err = coretemp_device_add(cpu);
|
||||
if (err)
|
||||
return;
|
||||
/*
|
||||
* Check whether pkgtemp support is available.
|
||||
* If so, add interfaces for pkgtemp.
|
||||
*/
|
||||
if (cpu_has(c, X86_FEATURE_PTS))
|
||||
coretemp_add_core(cpu, 1);
|
||||
}
|
||||
/*
|
||||
* Physical CPU device already exists.
|
||||
* So, just add interfaces for this core.
|
||||
*/
|
||||
coretemp_add_core(cpu, 0);
|
||||
}
|
||||
|
||||
static void __cpuinit put_core_offline(unsigned int cpu)
|
||||
{
|
||||
int i, indx;
|
||||
struct platform_data *pdata;
|
||||
struct platform_device *pdev = coretemp_get_pdev(cpu);
|
||||
|
||||
/* If the physical CPU device does not exist, just return */
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
pdata = platform_get_drvdata(pdev);
|
||||
|
||||
indx = TO_ATTR_NO(cpu);
|
||||
|
||||
if (pdata->core_data[indx] && pdata->core_data[indx]->cpu == cpu)
|
||||
coretemp_remove_core(pdata, &pdev->dev, indx);
|
||||
|
||||
/* Online the HT version of this core, if any */
|
||||
for_each_cpu(i, cpu_sibling_mask(cpu)) {
|
||||
if (i != cpu) {
|
||||
get_core_online(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If all cores in this pkg are offline, remove the device.
|
||||
* coretemp_device_remove calls unregister_platform_device,
|
||||
* which in turn calls coretemp_remove. This removes the
|
||||
* pkgtemp entry and does other clean ups.
|
||||
*/
|
||||
if (!is_any_core_online(pdata))
|
||||
coretemp_device_remove(cpu);
|
||||
}
|
||||
|
||||
static int __cpuinit coretemp_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
|
@ -534,10 +786,10 @@ static int __cpuinit coretemp_cpu_callback(struct notifier_block *nfb,
|
|||
switch (action) {
|
||||
case CPU_ONLINE:
|
||||
case CPU_DOWN_FAILED:
|
||||
coretemp_device_add(cpu);
|
||||
get_core_online(cpu);
|
||||
break;
|
||||
case CPU_DOWN_PREPARE:
|
||||
coretemp_device_remove(cpu);
|
||||
put_core_offline(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
|
@ -560,7 +812,7 @@ static int __init coretemp_init(void)
|
|||
goto exit;
|
||||
|
||||
for_each_online_cpu(i)
|
||||
coretemp_device_add(i);
|
||||
get_core_online(i);
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
if (list_empty(&pdev_list)) {
|
||||
|
|
|
@ -0,0 +1,717 @@
|
|||
/*
|
||||
* Driver for
|
||||
* Maxim MAX16065/MAX16066 12-Channel/8-Channel, Flash-Configurable
|
||||
* System Managers with Nonvolatile Fault Registers
|
||||
* Maxim MAX16067/MAX16068 6-Channel, Flash-Configurable System Managers
|
||||
* with Nonvolatile Fault Registers
|
||||
* Maxim MAX16070/MAX16071 12-Channel/8-Channel, Flash-Configurable System
|
||||
* Monitors with Nonvolatile Fault Registers
|
||||
*
|
||||
* Copyright (C) 2011 Ericsson AB.
|
||||
*
|
||||
* 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; version 2 of the License.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
enum chips { max16065, max16066, max16067, max16068, max16070, max16071 };
|
||||
|
||||
/*
|
||||
* Registers
|
||||
*/
|
||||
#define MAX16065_ADC(x) ((x) * 2)
|
||||
|
||||
#define MAX16065_CURR_SENSE 0x18
|
||||
#define MAX16065_CSP_ADC 0x19
|
||||
#define MAX16065_FAULT(x) (0x1b + (x))
|
||||
#define MAX16065_SCALE(x) (0x43 + (x))
|
||||
#define MAX16065_CURR_CONTROL 0x47
|
||||
#define MAX16065_LIMIT(l, x) (0x48 + (l) + (x) * 3) /*
|
||||
* l: limit
|
||||
* 0: min/max
|
||||
* 1: crit
|
||||
* 2: lcrit
|
||||
* x: ADC index
|
||||
*/
|
||||
|
||||
#define MAX16065_SW_ENABLE 0x73
|
||||
|
||||
#define MAX16065_WARNING_OV (1 << 3) /* Set if secondary threshold is OV
|
||||
warning */
|
||||
|
||||
#define MAX16065_CURR_ENABLE (1 << 0)
|
||||
|
||||
#define MAX16065_NUM_LIMIT 3
|
||||
#define MAX16065_NUM_ADC 12 /* maximum number of ADC channels */
|
||||
|
||||
static const int max16065_num_adc[] = {
|
||||
[max16065] = 12,
|
||||
[max16066] = 8,
|
||||
[max16067] = 6,
|
||||
[max16068] = 6,
|
||||
[max16070] = 12,
|
||||
[max16071] = 8,
|
||||
};
|
||||
|
||||
static const bool max16065_have_secondary[] = {
|
||||
[max16065] = true,
|
||||
[max16066] = true,
|
||||
[max16067] = false,
|
||||
[max16068] = false,
|
||||
[max16070] = true,
|
||||
[max16071] = true,
|
||||
};
|
||||
|
||||
static const bool max16065_have_current[] = {
|
||||
[max16065] = true,
|
||||
[max16066] = true,
|
||||
[max16067] = false,
|
||||
[max16068] = false,
|
||||
[max16070] = true,
|
||||
[max16071] = true,
|
||||
};
|
||||
|
||||
struct max16065_data {
|
||||
enum chips type;
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
bool valid;
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
int num_adc;
|
||||
bool have_current;
|
||||
int curr_gain;
|
||||
/* limits are in mV */
|
||||
int limit[MAX16065_NUM_LIMIT][MAX16065_NUM_ADC];
|
||||
int range[MAX16065_NUM_ADC + 1];/* voltage range */
|
||||
int adc[MAX16065_NUM_ADC + 1]; /* adc values (raw) including csp_adc */
|
||||
int curr_sense;
|
||||
int fault[2];
|
||||
};
|
||||
|
||||
static const int max16065_adc_range[] = { 5560, 2780, 1390, 0 };
|
||||
static const int max16065_csp_adc_range[] = { 7000, 14000 };
|
||||
|
||||
/* ADC registers have 10 bit resolution. */
|
||||
static inline int ADC_TO_MV(int adc, int range)
|
||||
{
|
||||
return (adc * range) / 1024;
|
||||
}
|
||||
|
||||
/*
|
||||
* Limit registers have 8 bit resolution and match upper 8 bits of ADC
|
||||
* registers.
|
||||
*/
|
||||
static inline int LIMIT_TO_MV(int limit, int range)
|
||||
{
|
||||
return limit * range / 256;
|
||||
}
|
||||
|
||||
static inline int MV_TO_LIMIT(int mv, int range)
|
||||
{
|
||||
return SENSORS_LIMIT(DIV_ROUND_CLOSEST(mv * 256, range), 0, 255);
|
||||
}
|
||||
|
||||
static inline int ADC_TO_CURR(int adc, int gain)
|
||||
{
|
||||
return adc * 1400000 / gain * 255;
|
||||
}
|
||||
|
||||
/*
|
||||
* max16065_read_adc()
|
||||
*
|
||||
* Read 16 bit value from <reg>, <reg+1>.
|
||||
* Upper 8 bits are in <reg>, lower 2 bits are in bits 7:6 of <reg+1>.
|
||||
*/
|
||||
static int max16065_read_adc(struct i2c_client *client, int reg)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = i2c_smbus_read_word_data(client, reg);
|
||||
if (unlikely(rv < 0))
|
||||
return rv;
|
||||
return ((rv & 0xff) << 2) | ((rv >> 14) & 0x03);
|
||||
}
|
||||
|
||||
static struct max16065_data *max16065_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max16065_data *data = i2c_get_clientdata(client);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < data->num_adc; i++)
|
||||
data->adc[i]
|
||||
= max16065_read_adc(client, MAX16065_ADC(i));
|
||||
|
||||
if (data->have_current) {
|
||||
data->adc[MAX16065_NUM_ADC]
|
||||
= max16065_read_adc(client, MAX16065_CSP_ADC);
|
||||
data->curr_sense
|
||||
= i2c_smbus_read_byte_data(client,
|
||||
MAX16065_CURR_SENSE);
|
||||
}
|
||||
|
||||
for (i = 0; i < DIV_ROUND_UP(data->num_adc, 8); i++)
|
||||
data->fault[i]
|
||||
= i2c_smbus_read_byte_data(client, MAX16065_FAULT(i));
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
mutex_unlock(&data->update_lock);
|
||||
return data;
|
||||
}
|
||||
|
||||
static ssize_t max16065_show_alarm(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da);
|
||||
struct max16065_data *data = max16065_update_device(dev);
|
||||
int val = data->fault[attr2->nr];
|
||||
|
||||
if (val < 0)
|
||||
return val;
|
||||
|
||||
val &= (1 << attr2->index);
|
||||
if (val)
|
||||
i2c_smbus_write_byte_data(to_i2c_client(dev),
|
||||
MAX16065_FAULT(attr2->nr), val);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", !!val);
|
||||
}
|
||||
|
||||
static ssize_t max16065_show_input(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||
struct max16065_data *data = max16065_update_device(dev);
|
||||
int adc = data->adc[attr->index];
|
||||
|
||||
if (unlikely(adc < 0))
|
||||
return adc;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n",
|
||||
ADC_TO_MV(adc, data->range[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t max16065_show_current(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct max16065_data *data = max16065_update_device(dev);
|
||||
|
||||
if (unlikely(data->curr_sense < 0))
|
||||
return data->curr_sense;
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n",
|
||||
ADC_TO_CURR(data->curr_sense, data->curr_gain));
|
||||
}
|
||||
|
||||
static ssize_t max16065_set_limit(struct device *dev,
|
||||
struct device_attribute *da,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max16065_data *data = i2c_get_clientdata(client);
|
||||
unsigned long val;
|
||||
int err;
|
||||
int limit;
|
||||
|
||||
err = strict_strtoul(buf, 10, &val);
|
||||
if (unlikely(err < 0))
|
||||
return err;
|
||||
|
||||
limit = MV_TO_LIMIT(val, data->range[attr2->index]);
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->limit[attr2->nr][attr2->index]
|
||||
= LIMIT_TO_MV(limit, data->range[attr2->index]);
|
||||
i2c_smbus_write_byte_data(client,
|
||||
MAX16065_LIMIT(attr2->nr, attr2->index),
|
||||
limit);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t max16065_show_limit(struct device *dev,
|
||||
struct device_attribute *da, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(da);
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max16065_data *data = i2c_get_clientdata(client);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n",
|
||||
data->limit[attr2->nr][attr2->index]);
|
||||
}
|
||||
|
||||
/* Construct a sensor_device_attribute structure for each register */
|
||||
|
||||
/* Input voltages */
|
||||
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, max16065_show_input, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, max16065_show_input, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, max16065_show_input, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, max16065_show_input, NULL, 3);
|
||||
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, max16065_show_input, NULL, 4);
|
||||
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, max16065_show_input, NULL, 5);
|
||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, max16065_show_input, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, max16065_show_input, NULL, 7);
|
||||
static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, max16065_show_input, NULL, 8);
|
||||
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, max16065_show_input, NULL, 9);
|
||||
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, max16065_show_input, NULL, 10);
|
||||
static SENSOR_DEVICE_ATTR(in11_input, S_IRUGO, max16065_show_input, NULL, 11);
|
||||
static SENSOR_DEVICE_ATTR(in12_input, S_IRUGO, max16065_show_input, NULL, 12);
|
||||
|
||||
/* Input voltages lcrit */
|
||||
static SENSOR_DEVICE_ATTR_2(in0_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in1_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in2_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in3_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 3);
|
||||
static SENSOR_DEVICE_ATTR_2(in4_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 4);
|
||||
static SENSOR_DEVICE_ATTR_2(in5_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 5);
|
||||
static SENSOR_DEVICE_ATTR_2(in6_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 6);
|
||||
static SENSOR_DEVICE_ATTR_2(in7_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 7);
|
||||
static SENSOR_DEVICE_ATTR_2(in8_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 8);
|
||||
static SENSOR_DEVICE_ATTR_2(in9_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 9);
|
||||
static SENSOR_DEVICE_ATTR_2(in10_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 10);
|
||||
static SENSOR_DEVICE_ATTR_2(in11_lcrit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 2, 11);
|
||||
|
||||
/* Input voltages crit */
|
||||
static SENSOR_DEVICE_ATTR_2(in0_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in1_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in2_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in3_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 3);
|
||||
static SENSOR_DEVICE_ATTR_2(in4_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 4);
|
||||
static SENSOR_DEVICE_ATTR_2(in5_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 5);
|
||||
static SENSOR_DEVICE_ATTR_2(in6_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 6);
|
||||
static SENSOR_DEVICE_ATTR_2(in7_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 7);
|
||||
static SENSOR_DEVICE_ATTR_2(in8_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 8);
|
||||
static SENSOR_DEVICE_ATTR_2(in9_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 9);
|
||||
static SENSOR_DEVICE_ATTR_2(in10_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 10);
|
||||
static SENSOR_DEVICE_ATTR_2(in11_crit, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 1, 11);
|
||||
|
||||
/* Input voltages min */
|
||||
static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 3);
|
||||
static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 4);
|
||||
static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 5);
|
||||
static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 6);
|
||||
static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 7);
|
||||
static SENSOR_DEVICE_ATTR_2(in8_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 8);
|
||||
static SENSOR_DEVICE_ATTR_2(in9_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 9);
|
||||
static SENSOR_DEVICE_ATTR_2(in10_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 10);
|
||||
static SENSOR_DEVICE_ATTR_2(in11_min, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 11);
|
||||
|
||||
/* Input voltages max */
|
||||
static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 3);
|
||||
static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 4);
|
||||
static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 5);
|
||||
static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 6);
|
||||
static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 7);
|
||||
static SENSOR_DEVICE_ATTR_2(in8_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 8);
|
||||
static SENSOR_DEVICE_ATTR_2(in9_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 9);
|
||||
static SENSOR_DEVICE_ATTR_2(in10_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 10);
|
||||
static SENSOR_DEVICE_ATTR_2(in11_max, S_IWUSR | S_IRUGO, max16065_show_limit,
|
||||
max16065_set_limit, 0, 11);
|
||||
|
||||
/* alarms */
|
||||
static SENSOR_DEVICE_ATTR_2(in0_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in1_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in3_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 3);
|
||||
static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 4);
|
||||
static SENSOR_DEVICE_ATTR_2(in5_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 5);
|
||||
static SENSOR_DEVICE_ATTR_2(in6_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 6);
|
||||
static SENSOR_DEVICE_ATTR_2(in7_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
0, 7);
|
||||
static SENSOR_DEVICE_ATTR_2(in8_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
1, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(in9_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
1, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(in10_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
1, 2);
|
||||
static SENSOR_DEVICE_ATTR_2(in11_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
1, 3);
|
||||
|
||||
/* Current and alarm */
|
||||
static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, max16065_show_current, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, max16065_show_alarm, NULL,
|
||||
1, 4);
|
||||
|
||||
/*
|
||||
* Finally, construct an array of pointers to members of the above objects,
|
||||
* as required for sysfs_create_group()
|
||||
*/
|
||||
static struct attribute *max16065_basic_attributes[] = {
|
||||
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in8_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in9_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in10_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_in11_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_lcrit.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_alarm.dev_attr.attr,
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *max16065_current_attributes[] = {
|
||||
&sensor_dev_attr_in12_input.dev_attr.attr,
|
||||
&sensor_dev_attr_curr1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_curr1_alarm.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *max16065_min_attributes[] = {
|
||||
&sensor_dev_attr_in0_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_min.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute *max16065_max_attributes[] = {
|
||||
&sensor_dev_attr_in0_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in4_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in6_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in8_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in11_max.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group max16065_basic_group = {
|
||||
.attrs = max16065_basic_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group max16065_current_group = {
|
||||
.attrs = max16065_current_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group max16065_min_group = {
|
||||
.attrs = max16065_min_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group max16065_max_group = {
|
||||
.attrs = max16065_max_attributes,
|
||||
};
|
||||
|
||||
static void max16065_cleanup(struct i2c_client *client)
|
||||
{
|
||||
sysfs_remove_group(&client->dev.kobj, &max16065_max_group);
|
||||
sysfs_remove_group(&client->dev.kobj, &max16065_min_group);
|
||||
sysfs_remove_group(&client->dev.kobj, &max16065_current_group);
|
||||
sysfs_remove_group(&client->dev.kobj, &max16065_basic_group);
|
||||
}
|
||||
|
||||
static int max16065_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct max16065_data *data;
|
||||
int i, j, val, ret;
|
||||
bool have_secondary; /* true if chip has secondary limits */
|
||||
bool secondary_is_max = false; /* secondary limits reflect max */
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_READ_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (unlikely(!data))
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
data->num_adc = max16065_num_adc[id->driver_data];
|
||||
data->have_current = max16065_have_current[id->driver_data];
|
||||
have_secondary = max16065_have_secondary[id->driver_data];
|
||||
|
||||
if (have_secondary) {
|
||||
val = i2c_smbus_read_byte_data(client, MAX16065_SW_ENABLE);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = val;
|
||||
goto out_free;
|
||||
}
|
||||
secondary_is_max = val & MAX16065_WARNING_OV;
|
||||
}
|
||||
|
||||
/* Read scale registers, convert to range */
|
||||
for (i = 0; i < DIV_ROUND_UP(data->num_adc, 4); i++) {
|
||||
val = i2c_smbus_read_byte_data(client, MAX16065_SCALE(i));
|
||||
if (unlikely(val < 0)) {
|
||||
ret = val;
|
||||
goto out_free;
|
||||
}
|
||||
for (j = 0; j < 4 && i * 4 + j < data->num_adc; j++) {
|
||||
data->range[i * 4 + j] =
|
||||
max16065_adc_range[(val >> (j * 2)) & 0x3];
|
||||
}
|
||||
}
|
||||
|
||||
/* Read limits */
|
||||
for (i = 0; i < MAX16065_NUM_LIMIT; i++) {
|
||||
if (i == 0 && !have_secondary)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < data->num_adc; j++) {
|
||||
val = i2c_smbus_read_byte_data(client,
|
||||
MAX16065_LIMIT(i, j));
|
||||
if (unlikely(val < 0)) {
|
||||
ret = val;
|
||||
goto out_free;
|
||||
}
|
||||
data->limit[i][j] = LIMIT_TO_MV(val, data->range[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Register sysfs hooks */
|
||||
for (i = 0; i < data->num_adc * 4; i++) {
|
||||
/* Do not create sysfs entry if channel is disabled */
|
||||
if (!data->range[i / 4])
|
||||
continue;
|
||||
|
||||
ret = sysfs_create_file(&client->dev.kobj,
|
||||
max16065_basic_attributes[i]);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (have_secondary) {
|
||||
struct attribute **attr = secondary_is_max ?
|
||||
max16065_max_attributes : max16065_min_attributes;
|
||||
|
||||
for (i = 0; i < data->num_adc; i++) {
|
||||
if (!data->range[i])
|
||||
continue;
|
||||
|
||||
ret = sysfs_create_file(&client->dev.kobj, attr[i]);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (data->have_current) {
|
||||
val = i2c_smbus_read_byte_data(client, MAX16065_CURR_CONTROL);
|
||||
if (unlikely(val < 0)) {
|
||||
ret = val;
|
||||
goto out;
|
||||
}
|
||||
if (val & MAX16065_CURR_ENABLE) {
|
||||
/*
|
||||
* Current gain is 6, 12, 24, 48 based on values in
|
||||
* bit 2,3.
|
||||
*/
|
||||
data->curr_gain = 6 << ((val >> 2) & 0x03);
|
||||
data->range[MAX16065_NUM_ADC]
|
||||
= max16065_csp_adc_range[(val >> 1) & 0x01];
|
||||
ret = sysfs_create_group(&client->dev.kobj,
|
||||
&max16065_current_group);
|
||||
if (unlikely(ret))
|
||||
goto out;
|
||||
} else {
|
||||
data->have_current = false;
|
||||
}
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&client->dev);
|
||||
if (unlikely(IS_ERR(data->hwmon_dev))) {
|
||||
ret = PTR_ERR(data->hwmon_dev);
|
||||
goto out;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out:
|
||||
max16065_cleanup(client);
|
||||
out_free:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max16065_remove(struct i2c_client *client)
|
||||
{
|
||||
struct max16065_data *data = i2c_get_clientdata(client);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
max16065_cleanup(client);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max16065_id[] = {
|
||||
{ "max16065", max16065 },
|
||||
{ "max16066", max16066 },
|
||||
{ "max16067", max16067 },
|
||||
{ "max16068", max16068 },
|
||||
{ "max16070", max16070 },
|
||||
{ "max16071", max16071 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, max16065_id);
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver max16065_driver = {
|
||||
.driver = {
|
||||
.name = "max16065",
|
||||
},
|
||||
.probe = max16065_probe,
|
||||
.remove = max16065_remove,
|
||||
.id_table = max16065_id,
|
||||
};
|
||||
|
||||
static int __init max16065_init(void)
|
||||
{
|
||||
return i2c_add_driver(&max16065_driver);
|
||||
}
|
||||
|
||||
static void __exit max16065_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max16065_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>");
|
||||
MODULE_DESCRIPTION("MAX16065 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(max16065_init);
|
||||
module_exit(max16065_exit);
|
|
@ -32,7 +32,7 @@ enum chips { max34440, max34441 };
|
|||
#define MAX34440_STATUS_OT_FAULT (1 << 5)
|
||||
#define MAX34440_STATUS_OT_WARN (1 << 6)
|
||||
|
||||
static int max34440_get_status(struct i2c_client *client, int page, int reg)
|
||||
static int max34440_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
int mfg_status;
|
||||
|
@ -108,7 +108,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
|||
.func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.get_status = max34440_get_status,
|
||||
.read_byte_data = max34440_read_byte_data,
|
||||
},
|
||||
[max34441] = {
|
||||
.pages = 12,
|
||||
|
@ -149,7 +149,7 @@ static struct pmbus_driver_info max34440_info[] = {
|
|||
.func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
|
||||
.get_status = max34440_get_status,
|
||||
.read_byte_data = max34440_read_byte_data,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
/*
|
||||
* Driver for +/-1 degree C, SMBus-Compatible Remote/Local Temperature Sensor
|
||||
* with Overtemperature Alarm
|
||||
*
|
||||
* Copyright (C) 2011 AppearTV AS
|
||||
*
|
||||
* Derived from:
|
||||
*
|
||||
* Based on the max1619 driver.
|
||||
* Copyright (C) 2003-2004 Alexey Fisher <fishor@mail.ru>
|
||||
* Jean Delvare <khali@linux-fr.org>
|
||||
*
|
||||
* The MAX6642 is a sensor chip made by Maxim.
|
||||
* It reports up to two temperatures (its own plus up to
|
||||
* one external one). Complete datasheet can be
|
||||
* obtained from Maxim's website at:
|
||||
* http://datasheets.maxim-ic.com/en/ds/MAX6642.pdf
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
static const unsigned short normal_i2c[] = {
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||
|
||||
/*
|
||||
* The MAX6642 registers
|
||||
*/
|
||||
|
||||
#define MAX6642_REG_R_MAN_ID 0xFE
|
||||
#define MAX6642_REG_R_CONFIG 0x03
|
||||
#define MAX6642_REG_W_CONFIG 0x09
|
||||
#define MAX6642_REG_R_STATUS 0x02
|
||||
#define MAX6642_REG_R_LOCAL_TEMP 0x00
|
||||
#define MAX6642_REG_R_LOCAL_TEMPL 0x11
|
||||
#define MAX6642_REG_R_LOCAL_HIGH 0x05
|
||||
#define MAX6642_REG_W_LOCAL_HIGH 0x0B
|
||||
#define MAX6642_REG_R_REMOTE_TEMP 0x01
|
||||
#define MAX6642_REG_R_REMOTE_TEMPL 0x10
|
||||
#define MAX6642_REG_R_REMOTE_HIGH 0x07
|
||||
#define MAX6642_REG_W_REMOTE_HIGH 0x0D
|
||||
|
||||
/*
|
||||
* Conversions
|
||||
*/
|
||||
|
||||
static int temp_from_reg10(int val)
|
||||
{
|
||||
return val * 250;
|
||||
}
|
||||
|
||||
static int temp_from_reg(int val)
|
||||
{
|
||||
return val * 1000;
|
||||
}
|
||||
|
||||
static int temp_to_reg(int val)
|
||||
{
|
||||
return val / 1000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Client data (each client gets its own)
|
||||
*/
|
||||
|
||||
struct max6642_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
bool valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
|
||||
/* registers values */
|
||||
u16 temp_input[2]; /* local/remote */
|
||||
u16 temp_high[2]; /* local/remote */
|
||||
u8 alarms;
|
||||
};
|
||||
|
||||
/*
|
||||
* Real code
|
||||
*/
|
||||
|
||||
static void max6642_init_client(struct i2c_client *client)
|
||||
{
|
||||
u8 config;
|
||||
struct max6642_data *data = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
* Start the conversions.
|
||||
*/
|
||||
config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG);
|
||||
if (config & 0x40)
|
||||
i2c_smbus_write_byte_data(client, MAX6642_REG_W_CONFIG,
|
||||
config & 0xBF); /* run */
|
||||
|
||||
data->temp_high[0] = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_LOCAL_HIGH);
|
||||
data->temp_high[1] = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_REMOTE_HIGH);
|
||||
}
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int max6642_detect(struct i2c_client *client,
|
||||
struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
u8 reg_config, reg_status, man_id;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
/* identification */
|
||||
man_id = i2c_smbus_read_byte_data(client, MAX6642_REG_R_MAN_ID);
|
||||
if (man_id != 0x4D)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* We read the config and status register, the 4 lower bits in the
|
||||
* config register should be zero and bit 5, 3, 1 and 0 should be
|
||||
* zero in the status register.
|
||||
*/
|
||||
reg_config = i2c_smbus_read_byte_data(client, MAX6642_REG_R_CONFIG);
|
||||
reg_status = i2c_smbus_read_byte_data(client, MAX6642_REG_R_STATUS);
|
||||
if (((reg_config & 0x0f) != 0x00) ||
|
||||
((reg_status & 0x2b) != 0x00))
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "max6642", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct max6642_data *max6642_update_device(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6642_data *data = i2c_get_clientdata(client);
|
||||
u16 val, tmp;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
|
||||
dev_dbg(&client->dev, "Updating max6642 data.\n");
|
||||
val = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_LOCAL_TEMPL);
|
||||
tmp = (val >> 6) & 3;
|
||||
val = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_LOCAL_TEMP);
|
||||
val = (val << 2) | tmp;
|
||||
data->temp_input[0] = val;
|
||||
val = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_REMOTE_TEMPL);
|
||||
tmp = (val >> 6) & 3;
|
||||
val = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_REMOTE_TEMP);
|
||||
val = (val << 2) | tmp;
|
||||
data->temp_input[1] = val;
|
||||
data->alarms = i2c_smbus_read_byte_data(client,
|
||||
MAX6642_REG_R_STATUS);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
static ssize_t show_temp_max10(struct device *dev,
|
||||
struct device_attribute *dev_attr, char *buf)
|
||||
{
|
||||
struct max6642_data *data = max6642_update_device(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr);
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
temp_from_reg10(data->temp_input[attr->index]));
|
||||
}
|
||||
|
||||
static ssize_t show_temp_max(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct max6642_data *data = max6642_update_device(dev);
|
||||
struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
|
||||
|
||||
return sprintf(buf, "%d\n", temp_from_reg(data->temp_high[attr2->nr]));
|
||||
}
|
||||
|
||||
static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long val;
|
||||
int err;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct max6642_data *data = i2c_get_clientdata(client);
|
||||
struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr);
|
||||
|
||||
err = strict_strtoul(buf, 10, &val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
data->temp_high[attr2->nr] = SENSORS_LIMIT(temp_to_reg(val), 0, 255);
|
||||
i2c_smbus_write_byte_data(client, attr2->index,
|
||||
data->temp_high[attr2->nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t show_alarm(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int bitnr = to_sensor_dev_attr(attr)->index;
|
||||
struct max6642_data *data = max6642_update_device(dev);
|
||||
return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_max10, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_max10, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR_2(temp1_max, S_IWUSR | S_IRUGO, show_temp_max,
|
||||
set_temp_max, 0, MAX6642_REG_W_LOCAL_HIGH);
|
||||
static SENSOR_DEVICE_ATTR_2(temp2_max, S_IWUSR | S_IRUGO, show_temp_max,
|
||||
set_temp_max, 1, MAX6642_REG_W_REMOTE_HIGH);
|
||||
static SENSOR_DEVICE_ATTR(temp_fault, S_IRUGO, show_alarm, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 6);
|
||||
static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 4);
|
||||
|
||||
static struct attribute *max6642_attributes[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_max.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_temp_fault.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group max6642_group = {
|
||||
.attrs = max6642_attributes,
|
||||
};
|
||||
|
||||
static int max6642_probe(struct i2c_client *new_client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct max6642_data *data;
|
||||
int err;
|
||||
|
||||
data = kzalloc(sizeof(struct max6642_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(new_client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
/* Initialize the MAX6642 chip */
|
||||
max6642_init_client(new_client);
|
||||
|
||||
/* Register sysfs hooks */
|
||||
err = sysfs_create_group(&new_client->dev.kobj, &max6642_group);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&new_client->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove_files;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_files:
|
||||
sysfs_remove_group(&new_client->dev.kobj, &max6642_group);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int max6642_remove(struct i2c_client *client)
|
||||
{
|
||||
struct max6642_data *data = i2c_get_clientdata(client);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&client->dev.kobj, &max6642_group);
|
||||
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Driver data (common to all clients)
|
||||
*/
|
||||
|
||||
static const struct i2c_device_id max6642_id[] = {
|
||||
{ "max6642", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max6642_id);
|
||||
|
||||
static struct i2c_driver max6642_driver = {
|
||||
.class = I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "max6642",
|
||||
},
|
||||
.probe = max6642_probe,
|
||||
.remove = max6642_remove,
|
||||
.id_table = max6642_id,
|
||||
.detect = max6642_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static int __init max6642_init(void)
|
||||
{
|
||||
return i2c_add_driver(&max6642_driver);
|
||||
}
|
||||
|
||||
static void __exit max6642_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max6642_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Per Dalen <per.dalen@appeartv.com>");
|
||||
MODULE_DESCRIPTION("MAX6642 sensor driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(max6642_init);
|
||||
module_exit(max6642_exit);
|
|
@ -37,7 +37,7 @@
|
|||
#define MAX8688_STATUS_OT_FAULT (1 << 13)
|
||||
#define MAX8688_STATUS_OT_WARNING (1 << 14)
|
||||
|
||||
static int max8688_get_status(struct i2c_client *client, int page, int reg)
|
||||
static int max8688_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret = 0;
|
||||
int mfg_status;
|
||||
|
@ -110,7 +110,7 @@ static struct pmbus_driver_info max8688_info = {
|
|||
.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP
|
||||
| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT
|
||||
| PMBUS_HAVE_STATUS_TEMP,
|
||||
.get_status = max8688_get_status,
|
||||
.read_byte_data = max8688_read_byte_data,
|
||||
};
|
||||
|
||||
static int max8688_probe(struct i2c_client *client,
|
||||
|
|
|
@ -1,444 +0,0 @@
|
|||
/*
|
||||
* pkgtemp.c - Linux kernel module for processor package hardware monitoring
|
||||
*
|
||||
* Copyright (C) 2010 Fenghua Yu <fenghua.yu@intel.com>
|
||||
*
|
||||
* Inspired from many hwmon drivers especially coretemp.
|
||||
*
|
||||
* 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; version 2 of the License.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/smp.h>
|
||||
|
||||
#define DRVNAME "pkgtemp"
|
||||
|
||||
enum { SHOW_TEMP, SHOW_TJMAX, SHOW_TTARGET, SHOW_LABEL, SHOW_NAME };
|
||||
|
||||
/*
|
||||
* Functions declaration
|
||||
*/
|
||||
|
||||
static struct pkgtemp_data *pkgtemp_update_device(struct device *dev);
|
||||
|
||||
struct pkgtemp_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
const char *name;
|
||||
u32 id;
|
||||
u16 phys_proc_id;
|
||||
char valid; /* zero until following fields are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
int temp;
|
||||
int tjmax;
|
||||
int ttarget;
|
||||
u8 alarm;
|
||||
};
|
||||
|
||||
/*
|
||||
* Sysfs stuff
|
||||
*/
|
||||
|
||||
static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
int ret;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct pkgtemp_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (attr->index == SHOW_NAME)
|
||||
ret = sprintf(buf, "%s\n", data->name);
|
||||
else /* show label */
|
||||
ret = sprintf(buf, "physical id %d\n",
|
||||
data->phys_proc_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t show_alarm(struct device *dev, struct device_attribute
|
||||
*devattr, char *buf)
|
||||
{
|
||||
struct pkgtemp_data *data = pkgtemp_update_device(dev);
|
||||
/* read the Out-of-spec log, never clear */
|
||||
return sprintf(buf, "%d\n", data->alarm);
|
||||
}
|
||||
|
||||
static ssize_t show_temp(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct pkgtemp_data *data = pkgtemp_update_device(dev);
|
||||
int err = 0;
|
||||
|
||||
if (attr->index == SHOW_TEMP)
|
||||
err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN;
|
||||
else if (attr->index == SHOW_TJMAX)
|
||||
err = sprintf(buf, "%d\n", data->tjmax);
|
||||
else
|
||||
err = sprintf(buf, "%d\n", data->ttarget);
|
||||
return err;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, SHOW_TEMP);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, SHOW_TJMAX);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, SHOW_TTARGET);
|
||||
static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL);
|
||||
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL);
|
||||
static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME);
|
||||
|
||||
static struct attribute *pkgtemp_attributes[] = {
|
||||
&sensor_dev_attr_name.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
&dev_attr_temp1_crit_alarm.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group pkgtemp_group = {
|
||||
.attrs = pkgtemp_attributes,
|
||||
};
|
||||
|
||||
static struct pkgtemp_data *pkgtemp_update_device(struct device *dev)
|
||||
{
|
||||
struct pkgtemp_data *data = dev_get_drvdata(dev);
|
||||
unsigned int cpu;
|
||||
int err;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (!data->valid || time_after(jiffies, data->last_updated + HZ)) {
|
||||
u32 eax, edx;
|
||||
|
||||
data->valid = 0;
|
||||
cpu = data->id;
|
||||
err = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_STATUS,
|
||||
&eax, &edx);
|
||||
if (!err) {
|
||||
data->alarm = (eax >> 5) & 1;
|
||||
data->temp = data->tjmax - (((eax >> 16)
|
||||
& 0x7f) * 1000);
|
||||
data->valid = 1;
|
||||
} else
|
||||
dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->update_lock);
|
||||
return data;
|
||||
}
|
||||
|
||||
static int get_tjmax(int cpu, struct device *dev)
|
||||
{
|
||||
int default_tjmax = 100000;
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
u32 val;
|
||||
|
||||
/* IA32_TEMPERATURE_TARGET contains the TjMax value */
|
||||
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
|
||||
if (!err) {
|
||||
val = (eax >> 16) & 0xff;
|
||||
if ((val > 80) && (val < 120)) {
|
||||
dev_info(dev, "TjMax is %d C.\n", val);
|
||||
return val * 1000;
|
||||
}
|
||||
}
|
||||
dev_warn(dev, "Unable to read TjMax from CPU.\n");
|
||||
return default_tjmax;
|
||||
}
|
||||
|
||||
static int __devinit pkgtemp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct pkgtemp_data *data;
|
||||
int err;
|
||||
u32 eax, edx;
|
||||
#ifdef CONFIG_SMP
|
||||
struct cpuinfo_x86 *c = &cpu_data(pdev->id);
|
||||
#endif
|
||||
|
||||
data = kzalloc(sizeof(struct pkgtemp_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
err = -ENOMEM;
|
||||
dev_err(&pdev->dev, "Out of memory\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
data->id = pdev->id;
|
||||
#ifdef CONFIG_SMP
|
||||
data->phys_proc_id = c->phys_proc_id;
|
||||
#endif
|
||||
data->name = "pkgtemp";
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
/* test if we can access the THERM_STATUS MSR */
|
||||
err = rdmsr_safe_on_cpu(data->id, MSR_IA32_PACKAGE_THERM_STATUS,
|
||||
&eax, &edx);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Unable to access THERM_STATUS MSR, giving up\n");
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
data->tjmax = get_tjmax(data->id, &pdev->dev);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
err = rdmsr_safe_on_cpu(data->id, MSR_IA32_TEMPERATURE_TARGET,
|
||||
&eax, &edx);
|
||||
if (err) {
|
||||
dev_warn(&pdev->dev, "Unable to read"
|
||||
" IA32_TEMPERATURE_TARGET MSR\n");
|
||||
} else {
|
||||
data->ttarget = data->tjmax - (((eax >> 8) & 0xff) * 1000);
|
||||
err = device_create_file(&pdev->dev,
|
||||
&sensor_dev_attr_temp1_max.dev_attr);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
err = sysfs_create_group(&pdev->dev.kobj, &pkgtemp_group);
|
||||
if (err)
|
||||
goto exit_dev;
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
dev_err(&pdev->dev, "Class registration failed (%d)\n",
|
||||
err);
|
||||
goto exit_class;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_class:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group);
|
||||
exit_dev:
|
||||
device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr);
|
||||
exit_free:
|
||||
kfree(data);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit pkgtemp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pkgtemp_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group);
|
||||
device_remove_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pkgtemp_driver = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = DRVNAME,
|
||||
},
|
||||
.probe = pkgtemp_probe,
|
||||
.remove = __devexit_p(pkgtemp_remove),
|
||||
};
|
||||
|
||||
struct pdev_entry {
|
||||
struct list_head list;
|
||||
struct platform_device *pdev;
|
||||
unsigned int cpu;
|
||||
#ifdef CONFIG_SMP
|
||||
u16 phys_proc_id;
|
||||
#endif
|
||||
};
|
||||
|
||||
static LIST_HEAD(pdev_list);
|
||||
static DEFINE_MUTEX(pdev_list_mutex);
|
||||
|
||||
static int __cpuinit pkgtemp_device_add(unsigned int cpu)
|
||||
{
|
||||
int err;
|
||||
struct platform_device *pdev;
|
||||
struct pdev_entry *pdev_entry;
|
||||
struct cpuinfo_x86 *c = &cpu_data(cpu);
|
||||
|
||||
if (!cpu_has(c, X86_FEATURE_PTS))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
/* Only keep the first entry in each package */
|
||||
list_for_each_entry(pdev_entry, &pdev_list, list) {
|
||||
if (c->phys_proc_id == pdev_entry->phys_proc_id) {
|
||||
err = 0; /* Not an error */
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
pdev = platform_device_alloc(DRVNAME, cpu);
|
||||
if (!pdev) {
|
||||
err = -ENOMEM;
|
||||
pr_err("Device allocation failed\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL);
|
||||
if (!pdev_entry) {
|
||||
err = -ENOMEM;
|
||||
goto exit_device_put;
|
||||
}
|
||||
|
||||
err = platform_device_add(pdev);
|
||||
if (err) {
|
||||
pr_err("Device addition failed (%d)\n", err);
|
||||
goto exit_device_free;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
pdev_entry->phys_proc_id = c->phys_proc_id;
|
||||
#endif
|
||||
pdev_entry->pdev = pdev;
|
||||
pdev_entry->cpu = cpu;
|
||||
list_add_tail(&pdev_entry->list, &pdev_list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_free:
|
||||
kfree(pdev_entry);
|
||||
exit_device_put:
|
||||
platform_device_put(pdev);
|
||||
exit:
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __cpuinit pkgtemp_device_remove(unsigned int cpu)
|
||||
{
|
||||
struct pdev_entry *p;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry(p, &pdev_list, list) {
|
||||
if (p->cpu != cpu)
|
||||
continue;
|
||||
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
kfree(p);
|
||||
for_each_cpu(i, cpu_core_mask(cpu)) {
|
||||
if (i != cpu) {
|
||||
err = pkgtemp_device_add(i);
|
||||
if (!err)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
}
|
||||
|
||||
static int __cpuinit pkgtemp_cpu_callback(struct notifier_block *nfb,
|
||||
unsigned long action, void *hcpu)
|
||||
{
|
||||
unsigned int cpu = (unsigned long) hcpu;
|
||||
|
||||
switch (action) {
|
||||
case CPU_ONLINE:
|
||||
case CPU_DOWN_FAILED:
|
||||
pkgtemp_device_add(cpu);
|
||||
break;
|
||||
case CPU_DOWN_PREPARE:
|
||||
pkgtemp_device_remove(cpu);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static struct notifier_block pkgtemp_cpu_notifier __refdata = {
|
||||
.notifier_call = pkgtemp_cpu_callback,
|
||||
};
|
||||
|
||||
static int __init pkgtemp_init(void)
|
||||
{
|
||||
int i, err = -ENODEV;
|
||||
|
||||
/* quick check if we run Intel */
|
||||
if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL)
|
||||
goto exit;
|
||||
|
||||
err = platform_driver_register(&pkgtemp_driver);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
for_each_online_cpu(i)
|
||||
pkgtemp_device_add(i);
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
if (list_empty(&pdev_list)) {
|
||||
err = -ENODEV;
|
||||
goto exit_driver_unreg;
|
||||
}
|
||||
#endif
|
||||
|
||||
register_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
||||
return 0;
|
||||
|
||||
#ifndef CONFIG_HOTPLUG_CPU
|
||||
exit_driver_unreg:
|
||||
platform_driver_unregister(&pkgtemp_driver);
|
||||
#endif
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit pkgtemp_exit(void)
|
||||
{
|
||||
struct pdev_entry *p, *n;
|
||||
|
||||
unregister_hotcpu_notifier(&pkgtemp_cpu_notifier);
|
||||
mutex_lock(&pdev_list_mutex);
|
||||
list_for_each_entry_safe(p, n, &pdev_list, list) {
|
||||
platform_device_unregister(p->pdev);
|
||||
list_del(&p->list);
|
||||
kfree(p);
|
||||
}
|
||||
mutex_unlock(&pdev_list_mutex);
|
||||
platform_driver_unregister(&pkgtemp_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Fenghua Yu <fenghua.yu@intel.com>");
|
||||
MODULE_DESCRIPTION("Intel processor package temperature monitor");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(pkgtemp_init)
|
||||
module_exit(pkgtemp_exit)
|
|
@ -281,13 +281,11 @@ struct pmbus_driver_info {
|
|||
|
||||
u32 func[PMBUS_PAGES]; /* Functionality, per page */
|
||||
/*
|
||||
* The get_status function maps manufacturing specific status values
|
||||
* into PMBus standard status values.
|
||||
* This function is optional and only necessary if chip specific status
|
||||
* register values have to be mapped into standard PMBus status register
|
||||
* values.
|
||||
* The following functions map manufacturing specific register values
|
||||
* to PMBus standard register values. Specify only if mapping is
|
||||
* necessary.
|
||||
*/
|
||||
int (*get_status)(struct i2c_client *client, int page, int reg);
|
||||
int (*read_byte_data)(struct i2c_client *client, int page, int reg);
|
||||
/*
|
||||
* The identify function determines supported PMBus functionality.
|
||||
* This function is only necessary if a chip driver supports multiple
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* Hardware monitoring driver for UCD90xxx Sequencer and System Health
|
||||
* Controller series
|
||||
*
|
||||
* Copyright (C) 2011 Ericsson AB.
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c/pmbus.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { ucd9000, ucd90120, ucd90124, ucd9090, ucd90910 };
|
||||
|
||||
#define UCD9000_MONITOR_CONFIG 0xd5
|
||||
#define UCD9000_NUM_PAGES 0xd6
|
||||
#define UCD9000_FAN_CONFIG_INDEX 0xe7
|
||||
#define UCD9000_FAN_CONFIG 0xe8
|
||||
#define UCD9000_DEVICE_ID 0xfd
|
||||
|
||||
#define UCD9000_MON_TYPE(x) (((x) >> 5) & 0x07)
|
||||
#define UCD9000_MON_PAGE(x) ((x) & 0x0f)
|
||||
|
||||
#define UCD9000_MON_VOLTAGE 1
|
||||
#define UCD9000_MON_TEMPERATURE 2
|
||||
#define UCD9000_MON_CURRENT 3
|
||||
#define UCD9000_MON_VOLTAGE_HW 4
|
||||
|
||||
#define UCD9000_NUM_FAN 4
|
||||
|
||||
struct ucd9000_data {
|
||||
u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX];
|
||||
struct pmbus_driver_info info;
|
||||
};
|
||||
#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info)
|
||||
|
||||
static int ucd9000_get_fan_config(struct i2c_client *client, int fan)
|
||||
{
|
||||
int fan_config = 0;
|
||||
struct ucd9000_data *data
|
||||
= to_ucd9000_data(pmbus_get_driver_info(client));
|
||||
|
||||
if (data->fan_data[fan][3] & 1)
|
||||
fan_config |= PB_FAN_2_INSTALLED; /* Use lower bit position */
|
||||
|
||||
/* Pulses/revolution */
|
||||
fan_config |= (data->fan_data[fan][3] & 0x06) >> 1;
|
||||
|
||||
return fan_config;
|
||||
}
|
||||
|
||||
static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret = 0;
|
||||
int fan_config;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_FAN_CONFIG_12:
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
|
||||
ret = ucd9000_get_fan_config(client, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
fan_config = ret << 4;
|
||||
ret = ucd9000_get_fan_config(client, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
fan_config |= ret;
|
||||
ret = fan_config;
|
||||
break;
|
||||
case PMBUS_FAN_CONFIG_34:
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
|
||||
ret = ucd9000_get_fan_config(client, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
fan_config = ret << 4;
|
||||
ret = ucd9000_get_fan_config(client, 3);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
fan_config |= ret;
|
||||
ret = fan_config;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ucd9000_id[] = {
|
||||
{"ucd9000", ucd9000},
|
||||
{"ucd90120", ucd90120},
|
||||
{"ucd90124", ucd90124},
|
||||
{"ucd9090", ucd9090},
|
||||
{"ucd90910", ucd90910},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ucd9000_id);
|
||||
|
||||
static int ucd9000_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
struct ucd9000_data *data;
|
||||
struct pmbus_driver_info *info;
|
||||
const struct i2c_device_id *mid;
|
||||
int i, ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID,
|
||||
block_buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read device ID\n");
|
||||
return ret;
|
||||
}
|
||||
block_buffer[ret] = '\0';
|
||||
dev_info(&client->dev, "Device ID %s\n", block_buffer);
|
||||
|
||||
mid = NULL;
|
||||
for (i = 0; i < ARRAY_SIZE(ucd9000_id); i++) {
|
||||
mid = &ucd9000_id[i];
|
||||
if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
|
||||
break;
|
||||
}
|
||||
if (!mid || !strlen(mid->name)) {
|
||||
dev_err(&client->dev, "Unsupported device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (id->driver_data != ucd9000 && id->driver_data != mid->driver_data)
|
||||
dev_notice(&client->dev,
|
||||
"Device mismatch: Configured %s, detected %s\n",
|
||||
id->name, mid->name);
|
||||
|
||||
data = kzalloc(sizeof(struct ucd9000_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
info = &data->info;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"Failed to read number of active pages\n");
|
||||
goto out;
|
||||
}
|
||||
info->pages = ret;
|
||||
if (!info->pages) {
|
||||
dev_err(&client->dev, "No pages configured\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* The internal temperature sensor is always active */
|
||||
info->func[0] = PMBUS_HAVE_TEMP;
|
||||
|
||||
/* Everything else is configurable */
|
||||
ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG,
|
||||
block_buffer);
|
||||
if (ret <= 0) {
|
||||
dev_err(&client->dev, "Failed to read configuration data\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < ret; i++) {
|
||||
int page = UCD9000_MON_PAGE(block_buffer[i]);
|
||||
|
||||
if (page >= info->pages)
|
||||
continue;
|
||||
|
||||
switch (UCD9000_MON_TYPE(block_buffer[i])) {
|
||||
case UCD9000_MON_VOLTAGE:
|
||||
case UCD9000_MON_VOLTAGE_HW:
|
||||
info->func[page] |= PMBUS_HAVE_VOUT
|
||||
| PMBUS_HAVE_STATUS_VOUT;
|
||||
break;
|
||||
case UCD9000_MON_TEMPERATURE:
|
||||
info->func[page] |= PMBUS_HAVE_TEMP2
|
||||
| PMBUS_HAVE_STATUS_TEMP;
|
||||
break;
|
||||
case UCD9000_MON_CURRENT:
|
||||
info->func[page] |= PMBUS_HAVE_IOUT
|
||||
| PMBUS_HAVE_STATUS_IOUT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fan configuration */
|
||||
if (mid->driver_data == ucd90124) {
|
||||
for (i = 0; i < UCD9000_NUM_FAN; i++) {
|
||||
i2c_smbus_write_byte_data(client,
|
||||
UCD9000_FAN_CONFIG_INDEX, i);
|
||||
ret = i2c_smbus_read_block_data(client,
|
||||
UCD9000_FAN_CONFIG,
|
||||
data->fan_data[i]);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0);
|
||||
|
||||
info->read_byte_data = ucd9000_read_byte_data;
|
||||
info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12
|
||||
| PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34;
|
||||
}
|
||||
|
||||
ret = pmbus_do_probe(client, mid, info);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return 0;
|
||||
|
||||
out:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ucd9000_remove(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
struct ucd9000_data *data;
|
||||
|
||||
data = to_ucd9000_data(pmbus_get_driver_info(client));
|
||||
ret = pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver ucd9000_driver = {
|
||||
.driver = {
|
||||
.name = "ucd9000",
|
||||
},
|
||||
.probe = ucd9000_probe,
|
||||
.remove = ucd9000_remove,
|
||||
.id_table = ucd9000_id,
|
||||
};
|
||||
|
||||
static int __init ucd9000_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ucd9000_driver);
|
||||
}
|
||||
|
||||
static void __exit ucd9000_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ucd9000_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(ucd9000_init);
|
||||
module_exit(ucd9000_exit);
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Hardware monitoring driver for ucd9200 series Digital PWM System Controllers
|
||||
*
|
||||
* Copyright (C) 2011 Ericsson AB.
|
||||
*
|
||||
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/i2c/pmbus.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
#define UCD9200_PHASE_INFO 0xd2
|
||||
#define UCD9200_DEVICE_ID 0xfd
|
||||
|
||||
enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246,
|
||||
ucd9248 };
|
||||
|
||||
static const struct i2c_device_id ucd9200_id[] = {
|
||||
{"ucd9200", ucd9200},
|
||||
{"ucd9220", ucd9220},
|
||||
{"ucd9222", ucd9222},
|
||||
{"ucd9224", ucd9224},
|
||||
{"ucd9240", ucd9240},
|
||||
{"ucd9244", ucd9244},
|
||||
{"ucd9246", ucd9246},
|
||||
{"ucd9248", ucd9248},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ucd9200_id);
|
||||
|
||||
static int ucd9200_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
struct pmbus_driver_info *info;
|
||||
const struct i2c_device_id *mid;
|
||||
int i, j, ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
I2C_FUNC_SMBUS_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID,
|
||||
block_buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read device ID\n");
|
||||
return ret;
|
||||
}
|
||||
block_buffer[ret] = '\0';
|
||||
dev_info(&client->dev, "Device ID %s\n", block_buffer);
|
||||
|
||||
mid = NULL;
|
||||
for (i = 0; i < ARRAY_SIZE(ucd9200_id); i++) {
|
||||
mid = &ucd9200_id[i];
|
||||
if (!strncasecmp(mid->name, block_buffer, strlen(mid->name)))
|
||||
break;
|
||||
}
|
||||
if (!mid || !strlen(mid->name)) {
|
||||
dev_err(&client->dev, "Unsupported device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (id->driver_data != ucd9200 && id->driver_data != mid->driver_data)
|
||||
dev_notice(&client->dev,
|
||||
"Device mismatch: Configured %s, detected %s\n",
|
||||
id->name, mid->name);
|
||||
|
||||
info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL);
|
||||
if (!info)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO,
|
||||
block_buffer);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read phase information\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate number of configured pages (rails) from PHASE_INFO
|
||||
* register.
|
||||
* Rails have to be sequential, so we can abort after finding
|
||||
* the first unconfigured rail.
|
||||
*/
|
||||
info->pages = 0;
|
||||
for (i = 0; i < ret; i++) {
|
||||
if (!block_buffer[i])
|
||||
break;
|
||||
info->pages++;
|
||||
}
|
||||
if (!info->pages) {
|
||||
dev_err(&client->dev, "No rails configured\n");
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
dev_info(&client->dev, "%d rails configured\n", info->pages);
|
||||
|
||||
/*
|
||||
* Set PHASE registers on all pages to 0xff to ensure that phase
|
||||
* specific commands will apply to all phases of a given page (rail).
|
||||
* This only affects the READ_IOUT and READ_TEMPERATURE2 registers.
|
||||
* READ_IOUT will return the sum of currents of all phases of a rail,
|
||||
* and READ_TEMPERATURE2 will return the maximum temperature detected
|
||||
* for the the phases of the rail.
|
||||
*/
|
||||
for (i = 0; i < info->pages; i++) {
|
||||
/*
|
||||
* Setting PAGE & PHASE fails once in a while for no obvious
|
||||
* reason, so we need to retry a couple of times.
|
||||
*/
|
||||
for (j = 0; j < 3; j++) {
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE,
|
||||
0xff);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev,
|
||||
"Failed to initialize PHASE registers\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (info->pages > 1)
|
||||
i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
|
||||
info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP |
|
||||
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
for (i = 1; i < info->pages; i++)
|
||||
info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_POUT |
|
||||
PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
/* ucd9240 supports a single fan */
|
||||
if (mid->driver_data == ucd9240)
|
||||
info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12;
|
||||
|
||||
ret = pmbus_do_probe(client, mid, info);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
return 0;
|
||||
out:
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ucd9200_remove(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
const struct pmbus_driver_info *info;
|
||||
|
||||
info = pmbus_get_driver_info(client);
|
||||
ret = pmbus_do_remove(client);
|
||||
kfree(info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver ucd9200_driver = {
|
||||
.driver = {
|
||||
.name = "ucd9200",
|
||||
},
|
||||
.probe = ucd9200_probe,
|
||||
.remove = ucd9200_remove,
|
||||
.id_table = ucd9200_id,
|
||||
};
|
||||
|
||||
static int __init ucd9200_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ucd9200_driver);
|
||||
}
|
||||
|
||||
static void __exit ucd9200_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ucd9200_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(ucd9200_init);
|
||||
module_exit(ucd9200_exit);
|
|
@ -8,17 +8,27 @@
|
|||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* For further information, see the Documentation/hwmon/sht15 file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct sht15_platform_data - sht15 connectivity info
|
||||
* @gpio_data: no. of gpio to which bidirectional data line is connected
|
||||
* @gpio_sck: no. of gpio to which the data clock is connected.
|
||||
* @supply_mv: supply voltage in mv. Overridden by regulator if available.
|
||||
**/
|
||||
* @gpio_data: no. of gpio to which bidirectional data line is
|
||||
* connected.
|
||||
* @gpio_sck: no. of gpio to which the data clock is connected.
|
||||
* @supply_mv: supply voltage in mv. Overridden by regulator if
|
||||
* available.
|
||||
* @checksum: flag to indicate the checksum should be validated.
|
||||
* @no_otp_reload: flag to indicate no reload from OTP.
|
||||
* @low_resolution: flag to indicate the temp/humidity resolution to use.
|
||||
*/
|
||||
struct sht15_platform_data {
|
||||
int gpio_data;
|
||||
int gpio_sck;
|
||||
int supply_mv;
|
||||
bool checksum;
|
||||
bool no_otp_reload;
|
||||
bool low_resolution;
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче