mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-19 18:24:14 +08:00
Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (26 commits) hwmon: (w83627ehf) Better fix for negative temperature values hwmon: (w83627ehf) Uninline is_word_sized hwmon: (lm75) Document why clones are not detected hwmon: (w83627ehf) Move fan pins check to a separate function hwmon: (w83627ehf) Skip reading unused voltage registers hwmon: (lm75) Add support for Analog Devices ADT75 hwmon: (pmbus_core) Simplify sign extensions hwmon: (pmbus) Add support for Lineage Power DC-DC converters hwmon: (pmbus/ltc2978) Add support for LTC3880 to LTC2978 driver hwmon: (pmbus/ltc2978) Explicit driver for LTC2978 hwmon: (pmbus) Add support for TEMP2 peak attributes hwmon: AD7314 driver (ported from IIO) hwmon: (pmbus) Add support for Intersil power management chips hwmon: (pmbus) Always call _pmbus_read_byte in core driver hwmon: (pmbus) Replace EINVAL return codes with more appropriate errors hwmon: (pmbus) Provide more documentation hwmon/f71882fg: Make the decision wether to register fan attr. per fan hwmon/f71882fg: Add a f71882fg_create_fan_sysfs_files helper function hwmon/f71882fg: Make all fan/pwm attr tables 2 dimensional hwmon: (exynos4_tmu) Remove IRQF_DISABLED ...
This commit is contained in:
commit
3cb603284b
25
Documentation/hwmon/ad7314
Normal file
25
Documentation/hwmon/ad7314
Normal file
@ -0,0 +1,25 @@
|
||||
Kernel driver ad7314
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
* Analog Devices AD7314
|
||||
Prefix: 'ad7314'
|
||||
Datasheet: Publicly available at Analog Devices website.
|
||||
* Analog Devices ADT7301
|
||||
Prefix: 'adt7301'
|
||||
Datasheet: Publicly available at Analog Devices website.
|
||||
* Analog Devices ADT7302
|
||||
Prefix: 'adt7302'
|
||||
Datasheet: Publicly available at Analog Devices website.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Driver supports the above parts. The ad7314 has a 10 bit
|
||||
sensor with 1lsb = 0.25 degrees centigrade. The adt7301 and
|
||||
adt7302 have 14 bit sensors with 1lsb = 0.03125 degrees centigrade.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
Currently power down mode is not supported.
|
@ -6,6 +6,10 @@ Supported chips:
|
||||
Prefix: 'adm1275'
|
||||
Addresses scanned: -
|
||||
Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1275.pdf
|
||||
* Analog Devices ADM1276
|
||||
Prefix: 'adm1276'
|
||||
Addresses scanned: -
|
||||
Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1276.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
@ -13,13 +17,13 @@ Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware montoring for Analog Devices ADM1275 Hot-Swap
|
||||
Controller and Digital Power Monitor.
|
||||
This driver supports hardware montoring for Analog Devices ADM1275 and ADM1276
|
||||
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.
|
||||
ADM1275 and ADM1276 are hot-swap controllers that allow a circuit board to be
|
||||
removed from or inserted into a live backplane. They also feature 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.
|
||||
@ -48,17 +52,25 @@ attributes are write-only, 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.
|
||||
in1_input Measured voltage.
|
||||
in1_min Minumum Voltage.
|
||||
in1_max Maximum voltage.
|
||||
in1_min_alarm Voltage low alarm.
|
||||
in1_max_alarm Voltage high alarm.
|
||||
in1_highest Historical maximum voltage.
|
||||
in1_reset_history Write any value to reset history.
|
||||
|
||||
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.
|
||||
curr1_input Measured current.
|
||||
curr1_max Maximum current.
|
||||
curr1_max_alarm Current high alarm.
|
||||
curr1_lcrit Critical minimum current. Depending on the chip
|
||||
configuration, either curr1_lcrit or curr1_crit is
|
||||
supported, but not both.
|
||||
curr1_lcrit_alarm Critical current low alarm.
|
||||
curr1_crit Critical maximum current. Depending on the chip
|
||||
configuration, either curr1_lcrit or curr1_crit is
|
||||
supported, but not both.
|
||||
curr1_crit_alarm Critical current high alarm.
|
||||
curr1_highest Historical maximum current.
|
||||
curr1_reset_history Write any value to reset history.
|
||||
|
81
Documentation/hwmon/exynos4_tmu
Normal file
81
Documentation/hwmon/exynos4_tmu
Normal file
@ -0,0 +1,81 @@
|
||||
Kernel driver exynos4_tmu
|
||||
=================
|
||||
|
||||
Supported chips:
|
||||
* ARM SAMSUNG EXYNOS4 series of SoC
|
||||
Prefix: 'exynos4-tmu'
|
||||
Datasheet: Not publicly available
|
||||
|
||||
Authors: Donggeun Kim <dg77.kim@samsung.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver allows to read temperature inside SAMSUNG EXYNOS4 series of SoC.
|
||||
|
||||
The chip only exposes the measured 8-bit temperature code value
|
||||
through a register.
|
||||
Temperature can be taken from the temperature code.
|
||||
There are three equations converting from temperature to temperature code.
|
||||
|
||||
The three equations are:
|
||||
1. Two point trimming
|
||||
Tc = (T - 25) * (TI2 - TI1) / (85 - 25) + TI1
|
||||
|
||||
2. One point trimming
|
||||
Tc = T + TI1 - 25
|
||||
|
||||
3. No trimming
|
||||
Tc = T + 50
|
||||
|
||||
Tc: Temperature code, T: Temperature,
|
||||
TI1: Trimming info for 25 degree Celsius (stored at TRIMINFO register)
|
||||
Temperature code measured at 25 degree Celsius which is unchanged
|
||||
TI2: Trimming info for 85 degree Celsius (stored at TRIMINFO register)
|
||||
Temperature code measured at 85 degree Celsius which is unchanged
|
||||
|
||||
TMU(Thermal Management Unit) in EXYNOS4 generates interrupt
|
||||
when temperature exceeds pre-defined levels.
|
||||
The maximum number of configurable threshold is four.
|
||||
The threshold levels are defined as follows:
|
||||
Level_0: current temperature > trigger_level_0 + threshold
|
||||
Level_1: current temperature > trigger_level_1 + threshold
|
||||
Level_2: current temperature > trigger_level_2 + threshold
|
||||
Level_3: current temperature > trigger_level_3 + threshold
|
||||
|
||||
The threshold and each trigger_level are set
|
||||
through the corresponding registers.
|
||||
|
||||
When an interrupt occurs, this driver notify user space of
|
||||
one of four threshold levels for the interrupt
|
||||
through kobject_uevent_env and sysfs_notify functions.
|
||||
Although an interrupt condition for level_0 can be set,
|
||||
it is not notified to user space through sysfs_notify function.
|
||||
|
||||
Sysfs Interface
|
||||
---------------
|
||||
name name of the temperature sensor
|
||||
RO
|
||||
|
||||
temp1_input temperature
|
||||
RO
|
||||
|
||||
temp1_max temperature for level_1 interrupt
|
||||
RO
|
||||
|
||||
temp1_crit temperature for level_2 interrupt
|
||||
RO
|
||||
|
||||
temp1_emergency temperature for level_3 interrupt
|
||||
RO
|
||||
|
||||
temp1_max_alarm alarm for level_1 interrupt
|
||||
RO
|
||||
|
||||
temp1_crit_alarm
|
||||
alarm for level_2 interrupt
|
||||
RO
|
||||
|
||||
temp1_emergency_alarm
|
||||
alarm for level_3 interrupt
|
||||
RO
|
@ -12,26 +12,46 @@ Supported chips:
|
||||
Addresses scanned: I2C 0x48 - 0x4f
|
||||
Datasheet: Publicly available at the National Semiconductor website
|
||||
http://www.national.com/
|
||||
* Dallas Semiconductor DS75
|
||||
Prefix: 'lm75'
|
||||
Addresses scanned: I2C 0x48 - 0x4f
|
||||
Datasheet: Publicly available at the Dallas Semiconductor website
|
||||
http://www.maxim-ic.com/
|
||||
* Dallas Semiconductor DS1775
|
||||
Prefix: 'lm75'
|
||||
Addresses scanned: I2C 0x48 - 0x4f
|
||||
* Dallas Semiconductor DS75, DS1775
|
||||
Prefixes: 'ds75', 'ds1775'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Dallas Semiconductor website
|
||||
http://www.maxim-ic.com/
|
||||
* Maxim MAX6625, MAX6626
|
||||
Prefix: 'lm75'
|
||||
Addresses scanned: I2C 0x48 - 0x4b
|
||||
Prefixes: 'max6625', 'max6626'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Maxim website
|
||||
http://www.maxim-ic.com/
|
||||
* Microchip (TelCom) TCN75
|
||||
Prefix: 'lm75'
|
||||
Addresses scanned: I2C 0x48 - 0x4f
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Microchip website
|
||||
http://www.microchip.com/
|
||||
* Microchip MCP9800, MCP9801, MCP9802, MCP9803
|
||||
Prefix: 'mcp980x'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Microchip website
|
||||
http://www.microchip.com/
|
||||
* Analog Devices ADT75
|
||||
Prefix: 'adt75'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Analog Devices website
|
||||
http://www.analog.com/adt75
|
||||
* ST Microelectronics STDS75
|
||||
Prefix: 'stds75'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the ST website
|
||||
http://www.st.com/internet/analog/product/121769.jsp
|
||||
* Texas Instruments TMP100, TMP101, TMP105, TMP75, TMP175, TMP275
|
||||
Prefixes: 'tmp100', 'tmp101', 'tmp105', 'tmp175', 'tmp75', 'tmp275'
|
||||
Addresses scanned: none
|
||||
Datasheet: Publicly available at the Texas Instruments website
|
||||
http://www.ti.com/product/tmp100
|
||||
http://www.ti.com/product/tmp101
|
||||
http://www.ti.com/product/tmp105
|
||||
http://www.ti.com/product/tmp75
|
||||
http://www.ti.com/product/tmp175
|
||||
http://www.ti.com/product/tmp275
|
||||
|
||||
Author: Frodo Looijaard <frodol@dds.nl>
|
||||
|
||||
@ -50,21 +70,16 @@ range of -55 to +125 degrees.
|
||||
The LM75 only updates its values each 1.5 seconds; reading it more often
|
||||
will do no harm, but will return 'old' values.
|
||||
|
||||
The LM75 is usually used in combination with LM78-like chips, to measure
|
||||
the temperature of the processor(s).
|
||||
|
||||
The DS75, DS1775, MAX6625, and MAX6626 are supported as well.
|
||||
They are not distinguished from an LM75. While most of these chips
|
||||
have three additional bits of accuracy (12 vs. 9 for the LM75),
|
||||
the additional bits are not supported. Not only that, but these chips will
|
||||
not be detected if not in 9-bit precision mode (use the force parameter if
|
||||
needed).
|
||||
|
||||
The TCN75 is supported as well, and is not distinguished from an LM75.
|
||||
The original LM75 was typically used in combination with LM78-like chips
|
||||
on PC motherboards, to measure the temperature of the processor(s). Clones
|
||||
are now used in various embedded designs.
|
||||
|
||||
The LM75 is essentially an industry standard; there may be other
|
||||
LM75 clones not listed here, with or without various enhancements,
|
||||
that are supported.
|
||||
that are supported. The clones are not detected by the driver, unless
|
||||
they reproduce the exact register tricks of the original LM75, and must
|
||||
therefore be instantiated explicitly. The specific enhancements (such as
|
||||
higher resolution) are not currently supported by the driver.
|
||||
|
||||
The LM77 is not supported, contrary to what we pretended for a long time.
|
||||
Both chips are simply not compatible, value encoding differs.
|
||||
|
103
Documentation/hwmon/ltc2978
Normal file
103
Documentation/hwmon/ltc2978
Normal file
@ -0,0 +1,103 @@
|
||||
Kernel driver ltc2978
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
* Linear Technology LTC2978
|
||||
Prefix: 'ltc2978'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://cds.linear.com/docs/Datasheet/2978fa.pdf
|
||||
* Linear Technology LTC3880
|
||||
Prefix: 'ltc3880'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://cds.linear.com/docs/Datasheet/3880f.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The LTC2978 is an octal power supply monitor, supervisor, sequencer and
|
||||
margin controller. The LTC3880 is a dual, PolyPhase DC/DC synchronous
|
||||
step-down switching regulator controller.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not probe for PMBus devices. You will have to instantiate
|
||||
devices explicitly.
|
||||
|
||||
Example: the following commands will load the driver for an LTC2978 at address
|
||||
0x60 on I2C bus #1:
|
||||
|
||||
# modprobe ltc2978
|
||||
# echo ltc2978 0x60 > /sys/bus/i2c/devices/i2c-1/new_device
|
||||
|
||||
|
||||
Sysfs attributes
|
||||
----------------
|
||||
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage.
|
||||
in1_min Minimum input voltage.
|
||||
in1_max Maximum input voltage.
|
||||
in1_lcrit Critical minimum input voltage.
|
||||
in1_crit Critical maximum input voltage.
|
||||
in1_min_alarm Input voltage low alarm.
|
||||
in1_max_alarm Input voltage high alarm.
|
||||
in1_lcrit_alarm Input voltage critical low alarm.
|
||||
in1_crit_alarm Input voltage critical high alarm.
|
||||
in1_lowest Lowest input voltage. LTC2978 only.
|
||||
in1_highest Highest input voltage.
|
||||
in1_reset_history Reset history. Writing into this attribute will reset
|
||||
history for all attributes.
|
||||
|
||||
in[2-9]_label "vout[1-8]". Channels 3 to 9 on LTC2978 only.
|
||||
in[2-9]_input Measured output voltage.
|
||||
in[2-9]_min Minimum output voltage.
|
||||
in[2-9]_max Maximum output voltage.
|
||||
in[2-9]_lcrit Critical minimum output voltage.
|
||||
in[2-9]_crit Critical maximum output voltage.
|
||||
in[2-9]_min_alarm Output voltage low alarm.
|
||||
in[2-9]_max_alarm Output voltage high alarm.
|
||||
in[2-9]_lcrit_alarm Output voltage critical low alarm.
|
||||
in[2-9]_crit_alarm Output voltage critical high alarm.
|
||||
in[2-9]_lowest Lowest output voltage. LTC2978 only.
|
||||
in[2-9]_highest Lowest output voltage.
|
||||
in[2-9]_reset_history Reset history. Writing into this attribute will reset
|
||||
history for all attributes.
|
||||
|
||||
temp[1-3]_input Measured temperature.
|
||||
On LTC2978, only one temperature measurement is
|
||||
supported and reflects the internal temperature.
|
||||
On LTC3880, temp1 and temp2 report external
|
||||
temperatures, and temp3 reports the internal
|
||||
temperature.
|
||||
temp[1-3]_min Mimimum temperature.
|
||||
temp[1-3]_max Maximum temperature.
|
||||
temp[1-3]_lcrit Critical low temperature.
|
||||
temp[1-3]_crit Critical high temperature.
|
||||
temp[1-3]_min_alarm Chip temperature low alarm.
|
||||
temp[1-3]_max_alarm Chip temperature high alarm.
|
||||
temp[1-3]_lcrit_alarm Chip temperature critical low alarm.
|
||||
temp[1-3]_crit_alarm Chip temperature critical high alarm.
|
||||
temp[1-3]_lowest Lowest measured temperature. LTC2978 only.
|
||||
temp[1-3]_highest Highest measured temperature.
|
||||
temp[1-3]_reset_history Reset history. Writing into this attribute will reset
|
||||
history for all attributes.
|
||||
|
||||
power[1-2]_label "pout[1-2]". LTC3880 only.
|
||||
power[1-2]_input Measured power.
|
||||
|
||||
curr1_label "iin". LTC3880 only.
|
||||
curr1_input Measured input current.
|
||||
curr1_max Maximum input current.
|
||||
curr1_max_alarm Input current high alarm.
|
||||
|
||||
curr[2-3]_label "iout[1-2]". LTC3880 only.
|
||||
curr[2-3]_input Measured input current.
|
||||
curr[2-3]_max Maximum input current.
|
||||
curr[2-3]_crit Critical input current.
|
||||
curr[2-3]_max_alarm Input current high alarm.
|
||||
curr[2-3]_crit_alarm Input current critical high alarm.
|
@ -8,11 +8,6 @@ Supported chips:
|
||||
Addresses scanned: -
|
||||
Datasheet:
|
||||
http://archive.ericsson.net/service/internet/picov/get?DocNo=28701-EN/LZT146395
|
||||
* Linear Technology LTC2978
|
||||
Octal PMBus Power Supply Monitor and Controller
|
||||
Prefix: 'ltc2978'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://cds.linear.com/docs/Datasheet/2978fa.pdf
|
||||
* ON Semiconductor ADP4000, NCP4200, NCP4208
|
||||
Prefixes: 'adp4000', 'ncp4200', 'ncp4208'
|
||||
Addresses scanned: -
|
||||
@ -20,6 +15,14 @@ Supported chips:
|
||||
http://www.onsemi.com/pub_link/Collateral/ADP4000-D.PDF
|
||||
http://www.onsemi.com/pub_link/Collateral/NCP4200-D.PDF
|
||||
http://www.onsemi.com/pub_link/Collateral/JUNE%202009-%20REV.%200.PDF
|
||||
* Lineage Power
|
||||
Prefixes: 'pdt003', 'pdt006', 'pdt012', 'udt020'
|
||||
Addresses scanned: -
|
||||
Datasheets:
|
||||
http://www.lineagepower.com/oem/pdf/PDT003A0X.pdf
|
||||
http://www.lineagepower.com/oem/pdf/PDT006A0X.pdf
|
||||
http://www.lineagepower.com/oem/pdf/PDT012A0X.pdf
|
||||
http://www.lineagepower.com/oem/pdf/UDT020A0X.pdf
|
||||
* Generic PMBus devices
|
||||
Prefix: 'pmbus'
|
||||
Addresses scanned: -
|
||||
|
283
Documentation/hwmon/pmbus-core
Normal file
283
Documentation/hwmon/pmbus-core
Normal file
@ -0,0 +1,283 @@
|
||||
PMBus core driver and internal API
|
||||
==================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
[from pmbus.org] The Power Management Bus (PMBus) is an open standard
|
||||
power-management protocol with a fully defined command language that facilitates
|
||||
communication with power converters and other devices in a power system. The
|
||||
protocol is implemented over the industry-standard SMBus serial interface and
|
||||
enables programming, control, and real-time monitoring of compliant power
|
||||
conversion products. This flexible and highly versatile standard allows for
|
||||
communication between devices based on both analog and digital technologies, and
|
||||
provides true interoperability which will reduce design complexity and shorten
|
||||
time to market for power system designers. Pioneered by leading power supply and
|
||||
semiconductor companies, this open power system standard is maintained and
|
||||
promoted by the PMBus Implementers Forum (PMBus-IF), comprising 30+ adopters
|
||||
with the objective to provide support to, and facilitate adoption among, users.
|
||||
|
||||
Unfortunately, while PMBus commands are standardized, there are no mandatory
|
||||
commands, and manufacturers can add as many non-standard commands as they like.
|
||||
Also, different PMBUs devices act differently if non-supported commands are
|
||||
executed. Some devices return an error, some devices return 0xff or 0xffff and
|
||||
set a status error flag, and some devices may simply hang up.
|
||||
|
||||
Despite all those difficulties, a generic PMBus device driver is still useful
|
||||
and supported since kernel version 2.6.39. However, it was necessary to support
|
||||
device specific extensions in addition to the core PMBus driver, since it is
|
||||
simply unknown what new device specific functionality PMBus device developers
|
||||
come up with next.
|
||||
|
||||
To make device specific extensions as scalable as possible, and to avoid having
|
||||
to modify the core PMBus driver repeatedly for new devices, the PMBus driver was
|
||||
split into core, generic, and device specific code. The core code (in
|
||||
pmbus_core.c) provides generic functionality. The generic code (in pmbus.c)
|
||||
provides support for generic PMBus devices. Device specific code is responsible
|
||||
for device specific initialization and, if needed, maps device specific
|
||||
functionality into generic functionality. This is to some degree comparable
|
||||
to PCI code, where generic code is augmented as needed with quirks for all kinds
|
||||
of devices.
|
||||
|
||||
PMBus device capabilities auto-detection
|
||||
========================================
|
||||
|
||||
For generic PMBus devices, code in pmbus.c attempts to auto-detect all supported
|
||||
PMBus commands. Auto-detection is somewhat limited, since there are simply too
|
||||
many variables to consider. For example, it is almost impossible to autodetect
|
||||
which PMBus commands are paged and which commands are replicated across all
|
||||
pages (see the PMBus specification for details on multi-page PMBus devices).
|
||||
|
||||
For this reason, it often makes sense to provide a device specific driver if not
|
||||
all commands can be auto-detected. The data structures in this driver can be
|
||||
used to inform the core driver about functionality supported by individual
|
||||
chips.
|
||||
|
||||
Some commands are always auto-detected. This applies to all limit commands
|
||||
(lcrit, min, max, and crit attributes) as well as associated alarm attributes.
|
||||
Limits and alarm attributes are auto-detected because there are simply too many
|
||||
possible combinations to provide a manual configuration interface.
|
||||
|
||||
PMBus internal API
|
||||
==================
|
||||
|
||||
The API between core and device specific PMBus code is defined in
|
||||
drivers/hwmon/pmbus/pmbus.h. In addition to the internal API, pmbus.h defines
|
||||
standard PMBus commands and virtual PMBus commands.
|
||||
|
||||
Standard PMBus commands
|
||||
-----------------------
|
||||
|
||||
Standard PMBus commands (commands values 0x00 to 0xff) are defined in the PMBUs
|
||||
specification.
|
||||
|
||||
Virtual PMBus commands
|
||||
----------------------
|
||||
|
||||
Virtual PMBus commands are provided to enable support for non-standard
|
||||
functionality which has been implemented by several chip vendors and is thus
|
||||
desirable to support.
|
||||
|
||||
Virtual PMBus commands start with command value 0x100 and can thus easily be
|
||||
distinguished from standard PMBus commands (which can not have values larger
|
||||
than 0xff). Support for virtual PMBus commands is device specific and thus has
|
||||
to be implemented in device specific code.
|
||||
|
||||
Virtual commands are named PMBUS_VIRT_xxx and start with PMBUS_VIRT_BASE. All
|
||||
virtual commands are word sized.
|
||||
|
||||
There are currently two types of virtual commands.
|
||||
|
||||
- READ commands are read-only; writes are either ignored or return an error.
|
||||
- RESET commands are read/write. Reading reset registers returns zero
|
||||
(used for detection), writing any value causes the associated history to be
|
||||
reset.
|
||||
|
||||
Virtual commands have to be handled in device specific driver code. Chip driver
|
||||
code returns non-negative values if a virtual command is supported, or a
|
||||
negative error code if not. The chip driver may return -ENODATA or any other
|
||||
Linux error code in this case, though an error code other than -ENODATA is
|
||||
handled more efficiently and thus preferred. Either case, the calling PMBus
|
||||
core code will abort if the chip driver returns an error code when reading
|
||||
or writing virtual registers (in other words, the PMBus core code will never
|
||||
send a virtual command to a chip).
|
||||
|
||||
PMBus driver information
|
||||
------------------------
|
||||
|
||||
PMBus driver information, defined in struct pmbus_driver_info, is the main means
|
||||
for device specific drivers to pass information to the core PMBus driver.
|
||||
Specifically, it provides the following information.
|
||||
|
||||
- For devices supporting its data in Direct Data Format, it provides coefficients
|
||||
for converting register values into normalized data. This data is usually
|
||||
provided by chip manufacturers in device datasheets.
|
||||
- Supported chip functionality can be provided to the core driver. This may be
|
||||
necessary for chips which react badly if non-supported commands are executed,
|
||||
and/or to speed up device detection and initialization.
|
||||
- Several function entry points are provided to support overriding and/or
|
||||
augmenting generic command execution. This functionality can be used to map
|
||||
non-standard PMBus commands to standard commands, or to augment standard
|
||||
command return values with device specific information.
|
||||
|
||||
API functions
|
||||
-------------
|
||||
|
||||
Functions provided by chip driver
|
||||
---------------------------------
|
||||
|
||||
All functions return the command return value (read) or zero (write) if
|
||||
successful. A return value of -ENODATA indicates that there is no manufacturer
|
||||
specific command, but that a standard PMBus command may exist. Any other
|
||||
negative return value indicates that the commands does not exist for this
|
||||
chip, and that no attempt should be made to read or write the standard
|
||||
command.
|
||||
|
||||
As mentioned above, an exception to this rule applies to virtual commands,
|
||||
which _must_ be handled in driver specific code. See "Virtual PMBus Commands"
|
||||
above for more details.
|
||||
|
||||
Command execution in the core PMBus driver code is as follows.
|
||||
|
||||
if (chip_access_function) {
|
||||
status = chip_access_function();
|
||||
if (status != -ENODATA)
|
||||
return status;
|
||||
}
|
||||
if (command >= PMBUS_VIRT_BASE) /* For word commands/registers only */
|
||||
return -EINVAL;
|
||||
return generic_access();
|
||||
|
||||
Chip drivers may provide pointers to the following functions in struct
|
||||
pmbus_driver_info. All functions are optional.
|
||||
|
||||
int (*read_byte_data)(struct i2c_client *client, int page, int reg);
|
||||
|
||||
Read byte from page <page>, register <reg>.
|
||||
<page> may be -1, which means "current page".
|
||||
|
||||
int (*read_word_data)(struct i2c_client *client, int page, int reg);
|
||||
|
||||
Read word from page <page>, register <reg>.
|
||||
|
||||
int (*write_word_data)(struct i2c_client *client, int page, int reg,
|
||||
u16 word);
|
||||
|
||||
Write word to page <page>, register <reg>.
|
||||
|
||||
int (*write_byte)(struct i2c_client *client, int page, u8 value);
|
||||
|
||||
Write byte to page <page>, register <reg>.
|
||||
<page> may be -1, which means "current page".
|
||||
|
||||
int (*identify)(struct i2c_client *client, struct pmbus_driver_info *info);
|
||||
|
||||
Determine supported PMBus functionality. This function is only necessary
|
||||
if a chip driver supports multiple chips, and the chip functionality is not
|
||||
pre-determined. It is currently only used by the generic pmbus driver
|
||||
(pmbus.c).
|
||||
|
||||
Functions exported by core driver
|
||||
---------------------------------
|
||||
|
||||
Chip drivers are expected to use the following functions to read or write
|
||||
PMBus registers. Chip drivers may also use direct I2C commands. If direct I2C
|
||||
commands are used, the chip driver code must not directly modify the current
|
||||
page, since the selected page is cached in the core driver and the core driver
|
||||
will assume that it is selected. Using pmbus_set_page() to select a new page
|
||||
is mandatory.
|
||||
|
||||
int pmbus_set_page(struct i2c_client *client, u8 page);
|
||||
|
||||
Set PMBus page register to <page> for subsequent commands.
|
||||
|
||||
int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg);
|
||||
|
||||
Read word data from <page>, <reg>. Similar to i2c_smbus_read_word_data(), but
|
||||
selects page first.
|
||||
|
||||
int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg,
|
||||
u16 word);
|
||||
|
||||
Write word data to <page>, <reg>. Similar to i2c_smbus_write_word_data(), but
|
||||
selects page first.
|
||||
|
||||
int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg);
|
||||
|
||||
Read byte data from <page>, <reg>. Similar to i2c_smbus_read_byte_data(), but
|
||||
selects page first. <page> may be -1, which means "current page".
|
||||
|
||||
int pmbus_write_byte(struct i2c_client *client, int page, u8 value);
|
||||
|
||||
Write byte data to <page>, <reg>. Similar to i2c_smbus_write_byte(), but
|
||||
selects page first. <page> may be -1, which means "current page".
|
||||
|
||||
void pmbus_clear_faults(struct i2c_client *client);
|
||||
|
||||
Execute PMBus "Clear Fault" command on all chip pages.
|
||||
This function calls the device specific write_byte function if defined.
|
||||
Therefore, it must _not_ be called from that function.
|
||||
|
||||
bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
|
||||
|
||||
Check if byte register exists. Return true if the register exists, false
|
||||
otherwise.
|
||||
This function calls the device specific write_byte function if defined to
|
||||
obtain the chip status. Therefore, it must _not_ be called from that function.
|
||||
|
||||
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
|
||||
|
||||
Check if word register exists. Return true if the register exists, false
|
||||
otherwise.
|
||||
This function calls the device specific write_byte function if defined to
|
||||
obtain the chip status. Therefore, it must _not_ be called from that function.
|
||||
|
||||
int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
||||
struct pmbus_driver_info *info);
|
||||
|
||||
Execute probe function. Similar to standard probe function for other drivers,
|
||||
with the pointer to struct pmbus_driver_info as additional argument. Calls
|
||||
identify function if supported. Must only be called from device probe
|
||||
function.
|
||||
|
||||
void pmbus_do_remove(struct i2c_client *client);
|
||||
|
||||
Execute driver remove function. Similar to standard driver remove function.
|
||||
|
||||
const struct pmbus_driver_info
|
||||
*pmbus_get_driver_info(struct i2c_client *client);
|
||||
|
||||
Return pointer to struct pmbus_driver_info as passed to pmbus_do_probe().
|
||||
|
||||
|
||||
PMBus driver platform data
|
||||
==========================
|
||||
|
||||
PMBus platform data is defined in include/linux/i2c/pmbus.h. Platform data
|
||||
currently only provides a flag field with a single bit used.
|
||||
|
||||
#define PMBUS_SKIP_STATUS_CHECK (1 << 0)
|
||||
|
||||
struct pmbus_platform_data {
|
||||
u32 flags; /* Device specific flags */
|
||||
};
|
||||
|
||||
|
||||
Flags
|
||||
-----
|
||||
|
||||
PMBUS_SKIP_STATUS_CHECK
|
||||
|
||||
During register detection, skip checking the status register for
|
||||
communication or command errors.
|
||||
|
||||
Some PMBus chips respond with valid data when trying to read an unsupported
|
||||
register. For such chips, checking the status register is mandatory when
|
||||
trying to determine if a chip register exists or not.
|
||||
Other PMBus chips don't support the STATUS_CML register, or report
|
||||
communication errors for no explicable reason. For such chips, checking the
|
||||
status register must be disabled.
|
||||
|
||||
Some i2c controllers do not support single-byte commands (write commands with
|
||||
no data, i2c_smbus_write_byte()). With such controllers, clearing the status
|
||||
register is impossible, and the PMBUS_SKIP_STATUS_CHECK flag must be set.
|
125
Documentation/hwmon/zl6100
Normal file
125
Documentation/hwmon/zl6100
Normal file
@ -0,0 +1,125 @@
|
||||
Kernel driver zl6100
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
* Intersil / Zilker Labs ZL2004
|
||||
Prefix: 'zl2004'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6847.pdf
|
||||
* Intersil / Zilker Labs ZL2006
|
||||
Prefix: 'zl2006'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6850.pdf
|
||||
* Intersil / Zilker Labs ZL2008
|
||||
Prefix: 'zl2008'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6859.pdf
|
||||
* Intersil / Zilker Labs ZL2105
|
||||
Prefix: 'zl2105'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6851.pdf
|
||||
* Intersil / Zilker Labs ZL2106
|
||||
Prefix: 'zl2106'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6852.pdf
|
||||
* Intersil / Zilker Labs ZL6100
|
||||
Prefix: 'zl6100'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6876.pdf
|
||||
* Intersil / Zilker Labs ZL6105
|
||||
Prefix: 'zl6105'
|
||||
Addresses scanned: -
|
||||
Datasheet: http://www.intersil.com/data/fn/fn6906.pdf
|
||||
|
||||
Author: Guenter Roeck <guenter.roeck@ericsson.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware montoring for Intersil / Zilker Labs ZL6100 and
|
||||
compatible digital DC-DC controllers.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus and Documentation.hwmon/pmbus-core 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.
|
||||
|
||||
WARNING: Do not access chip registers using the i2cdump command, and do not use
|
||||
any of the i2ctools commands on a command register used to save and restore
|
||||
configuration data (0x11, 0x12, 0x15, 0x16, and 0xf4). The chips supported by
|
||||
this driver interpret any access to those command registers (including read
|
||||
commands) as request to execute the command in question. Unless write accesses
|
||||
to those registers are protected, this may result in power loss, board resets,
|
||||
and/or Flash corruption. Worst case, your board may turn into a brick.
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data.
|
||||
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
delay
|
||||
-----
|
||||
|
||||
Some Intersil/Zilker Labs DC-DC controllers require a minimum interval between
|
||||
I2C bus accesses. According to Intersil, the minimum interval is 2 ms, though
|
||||
1 ms appears to be sufficient and has not caused any problems in testing.
|
||||
The problem is known to affect ZL6100, ZL2105, and ZL2008. It is known not to
|
||||
affect ZL2004 and ZL6105. The driver automatically sets the interval to 1 ms
|
||||
except for ZL2004 and ZL6105. To enable manual override, the driver provides a
|
||||
writeable module parameter, 'delay', which can be used to set the interval to
|
||||
a value between 0 and 65,535 microseconds.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
The following attributes are supported. Limits are read-write; all other
|
||||
attributes are read-only.
|
||||
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage.
|
||||
in1_min Minimum input voltage.
|
||||
in1_max Maximum input voltage.
|
||||
in1_lcrit Critical minumum input voltage.
|
||||
in1_crit Critical maximum input voltage.
|
||||
in1_min_alarm Input voltage low alarm.
|
||||
in1_max_alarm Input voltage high alarm.
|
||||
in1_lcrit_alarm Input voltage critical low alarm.
|
||||
in1_crit_alarm Input voltage critical high alarm.
|
||||
|
||||
in2_label "vout1"
|
||||
in2_input Measured output voltage.
|
||||
in2_lcrit Critical minumum output Voltage.
|
||||
in2_crit Critical maximum output voltage.
|
||||
in2_lcrit_alarm Critical output voltage critical low alarm.
|
||||
in2_crit_alarm Critical output voltage critical high alarm.
|
||||
|
||||
curr1_label "iout1"
|
||||
curr1_input Measured output current.
|
||||
curr1_lcrit Critical minimum output current.
|
||||
curr1_crit Critical maximum output current.
|
||||
curr1_lcrit_alarm Output current critical low alarm.
|
||||
curr1_crit_alarm Output current critical high alarm.
|
||||
|
||||
temp[12]_input Measured temperature.
|
||||
temp[12]_min Minimum temperature.
|
||||
temp[12]_max Maximum temperature.
|
||||
temp[12]_lcrit Critical low temperature.
|
||||
temp[12]_crit Critical high temperature.
|
||||
temp[12]_min_alarm Chip temperature low alarm.
|
||||
temp[12]_max_alarm Chip temperature high alarm.
|
||||
temp[12]_lcrit_alarm Chip temperature critical low alarm.
|
||||
temp[12]_crit_alarm Chip temperature critical high alarm.
|
@ -68,6 +68,16 @@ config SENSORS_ABITUGURU3
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called abituguru3.
|
||||
|
||||
config SENSORS_AD7314
|
||||
tristate "Analog Devices AD7314 and compatibles"
|
||||
depends on SPI && EXPERIMENTAL
|
||||
help
|
||||
If you say yes here you get support for the Analog Devices
|
||||
AD7314, ADT7301 and ADT7302 temperature sensors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ad7314.
|
||||
|
||||
config SENSORS_AD7414
|
||||
tristate "Analog Devices AD7414"
|
||||
depends on I2C && EXPERIMENTAL
|
||||
@ -303,6 +313,16 @@ config SENSORS_DS1621
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called ds1621.
|
||||
|
||||
config SENSORS_EXYNOS4_TMU
|
||||
tristate "Temperature sensor on Samsung EXYNOS4"
|
||||
depends on EXYNOS4_DEV_TMU
|
||||
help
|
||||
If you say yes here you get support for TMU (Thermal Managment
|
||||
Unit) on SAMSUNG EXYNOS4 series of SoC.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called exynos4-tmu.
|
||||
|
||||
config SENSORS_I5K_AMB
|
||||
tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets"
|
||||
depends on PCI && EXPERIMENTAL
|
||||
@ -531,6 +551,7 @@ config SENSORS_LM75
|
||||
If you say yes here you get support for one common type of
|
||||
temperature sensor chip, with models including:
|
||||
|
||||
- Analog Devices ADT75
|
||||
- Dallas Semiconductor DS75 and DS1775
|
||||
- Maxim MAX6625 and MAX6626
|
||||
- Microchip MCP980x
|
||||
|
@ -21,6 +21,7 @@ obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||
|
||||
obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o
|
||||
obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
|
||||
obj-$(CONFIG_SENSORS_AD7314) += ad7314.o
|
||||
obj-$(CONFIG_SENSORS_AD7414) += ad7414.o
|
||||
obj-$(CONFIG_SENSORS_AD7418) += ad7418.o
|
||||
obj-$(CONFIG_SENSORS_ADCXX) += adcxx.o
|
||||
@ -47,6 +48,7 @@ obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o
|
||||
obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o
|
||||
obj-$(CONFIG_SENSORS_EMC6W201) += emc6w201.o
|
||||
obj-$(CONFIG_SENSORS_EXYNOS4_TMU) += exynos4_tmu.o
|
||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||
obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
|
||||
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
|
||||
|
186
drivers/hwmon/ad7314.c
Normal file
186
drivers/hwmon/ad7314.c
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* AD7314 digital temperature sensor driver for AD7314, ADT7301 and ADT7302
|
||||
*
|
||||
* Copyright 2010 Analog Devices Inc.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*
|
||||
* Conversion to hwmon from IIO done by Jonathan Cameron <jic23@cam.ac.uk>
|
||||
*/
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
|
||||
/*
|
||||
* AD7314 power mode
|
||||
*/
|
||||
#define AD7314_PD 0x2000
|
||||
|
||||
/*
|
||||
* AD7314 temperature masks
|
||||
*/
|
||||
#define AD7314_TEMP_SIGN 0x200
|
||||
#define AD7314_TEMP_MASK 0x7FE0
|
||||
#define AD7314_TEMP_OFFSET 5
|
||||
|
||||
/*
|
||||
* ADT7301 and ADT7302 temperature masks
|
||||
*/
|
||||
#define ADT7301_TEMP_SIGN 0x2000
|
||||
#define ADT7301_TEMP_MASK 0x3FFF
|
||||
|
||||
enum ad7314_variant {
|
||||
adt7301,
|
||||
adt7302,
|
||||
ad7314,
|
||||
};
|
||||
|
||||
struct ad7314_data {
|
||||
struct spi_device *spi_dev;
|
||||
struct device *hwmon_dev;
|
||||
u16 rx ____cacheline_aligned;
|
||||
};
|
||||
|
||||
static int ad7314_spi_read(struct ad7314_data *chip, s16 *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_read(chip->spi_dev, (u8 *)&chip->rx, sizeof(chip->rx));
|
||||
if (ret < 0) {
|
||||
dev_err(&chip->spi_dev->dev, "SPI read error\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
*data = be16_to_cpu(chip->rx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t ad7314_show_temperature(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct ad7314_data *chip = dev_get_drvdata(dev);
|
||||
s16 data;
|
||||
int ret;
|
||||
|
||||
ret = ad7314_spi_read(chip, &data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
switch (spi_get_device_id(chip->spi_dev)->driver_data) {
|
||||
case ad7314:
|
||||
data = (data & AD7314_TEMP_MASK) >> AD7314_TEMP_OFFSET;
|
||||
data = (data << 6) >> 6;
|
||||
|
||||
return sprintf(buf, "%d\n", 250 * data);
|
||||
case adt7301:
|
||||
case adt7302:
|
||||
/*
|
||||
* Documented as a 13 bit twos complement register
|
||||
* with a sign bit - which is a 14 bit 2's complement
|
||||
* register. 1lsb - 31.25 milli degrees centigrade
|
||||
*/
|
||||
data &= ADT7301_TEMP_MASK;
|
||||
data = (data << 2) >> 2;
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
DIV_ROUND_CLOSEST(data * 3125, 100));
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
|
||||
ad7314_show_temperature, NULL, 0);
|
||||
|
||||
static struct attribute *ad7314_attributes[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7314_group = {
|
||||
.attrs = ad7314_attributes,
|
||||
};
|
||||
|
||||
static int __devinit ad7314_probe(struct spi_device *spi_dev)
|
||||
{
|
||||
int ret;
|
||||
struct ad7314_data *chip;
|
||||
|
||||
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
||||
if (chip == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error_ret;
|
||||
}
|
||||
dev_set_drvdata(&spi_dev->dev, chip);
|
||||
|
||||
ret = sysfs_create_group(&spi_dev->dev.kobj, &ad7314_group);
|
||||
if (ret < 0)
|
||||
goto error_free_chip;
|
||||
chip->hwmon_dev = hwmon_device_register(&spi_dev->dev);
|
||||
if (IS_ERR(chip->hwmon_dev)) {
|
||||
ret = PTR_ERR(chip->hwmon_dev);
|
||||
goto error_remove_group;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error_remove_group:
|
||||
sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
|
||||
error_free_chip:
|
||||
kfree(chip);
|
||||
error_ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit ad7314_remove(struct spi_device *spi_dev)
|
||||
{
|
||||
struct ad7314_data *chip = dev_get_drvdata(&spi_dev->dev);
|
||||
|
||||
hwmon_device_unregister(chip->hwmon_dev);
|
||||
sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
|
||||
kfree(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad7314_id[] = {
|
||||
{ "adt7301", adt7301 },
|
||||
{ "adt7302", adt7302 },
|
||||
{ "ad7314", ad7314 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ad7314_id);
|
||||
|
||||
static struct spi_driver ad7314_driver = {
|
||||
.driver = {
|
||||
.name = "ad7314",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ad7314_probe,
|
||||
.remove = __devexit_p(ad7314_remove),
|
||||
.id_table = ad7314_id,
|
||||
};
|
||||
|
||||
static __init int ad7314_init(void)
|
||||
{
|
||||
return spi_register_driver(&ad7314_driver);
|
||||
}
|
||||
module_init(ad7314_init);
|
||||
|
||||
static __exit void ad7314_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&ad7314_driver);
|
||||
}
|
||||
module_exit(ad7314_exit);
|
||||
|
||||
MODULE_AUTHOR("Sonic Zhang <sonic.zhang@analog.com>");
|
||||
MODULE_DESCRIPTION("Analog Devices AD7314, ADT7301 and ADT7302 digital"
|
||||
" temperature sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
524
drivers/hwmon/exynos4_tmu.c
Normal file
524
drivers/hwmon/exynos4_tmu.c
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
* exynos4_tmu.c - Samsung EXYNOS4 TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
|
||||
#include <linux/platform_data/exynos4_tmu.h>
|
||||
|
||||
#define EXYNOS4_TMU_REG_TRIMINFO 0x0
|
||||
#define EXYNOS4_TMU_REG_CONTROL 0x20
|
||||
#define EXYNOS4_TMU_REG_STATUS 0x28
|
||||
#define EXYNOS4_TMU_REG_CURRENT_TEMP 0x40
|
||||
#define EXYNOS4_TMU_REG_THRESHOLD_TEMP 0x44
|
||||
#define EXYNOS4_TMU_REG_TRIG_LEVEL0 0x50
|
||||
#define EXYNOS4_TMU_REG_TRIG_LEVEL1 0x54
|
||||
#define EXYNOS4_TMU_REG_TRIG_LEVEL2 0x58
|
||||
#define EXYNOS4_TMU_REG_TRIG_LEVEL3 0x5C
|
||||
#define EXYNOS4_TMU_REG_PAST_TEMP0 0x60
|
||||
#define EXYNOS4_TMU_REG_PAST_TEMP1 0x64
|
||||
#define EXYNOS4_TMU_REG_PAST_TEMP2 0x68
|
||||
#define EXYNOS4_TMU_REG_PAST_TEMP3 0x6C
|
||||
#define EXYNOS4_TMU_REG_INTEN 0x70
|
||||
#define EXYNOS4_TMU_REG_INTSTAT 0x74
|
||||
#define EXYNOS4_TMU_REG_INTCLEAR 0x78
|
||||
|
||||
#define EXYNOS4_TMU_GAIN_SHIFT 8
|
||||
#define EXYNOS4_TMU_REF_VOLTAGE_SHIFT 24
|
||||
|
||||
#define EXYNOS4_TMU_TRIM_TEMP_MASK 0xff
|
||||
#define EXYNOS4_TMU_CORE_ON 3
|
||||
#define EXYNOS4_TMU_CORE_OFF 2
|
||||
#define EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET 50
|
||||
#define EXYNOS4_TMU_TRIG_LEVEL0_MASK 0x1
|
||||
#define EXYNOS4_TMU_TRIG_LEVEL1_MASK 0x10
|
||||
#define EXYNOS4_TMU_TRIG_LEVEL2_MASK 0x100
|
||||
#define EXYNOS4_TMU_TRIG_LEVEL3_MASK 0x1000
|
||||
#define EXYNOS4_TMU_INTCLEAR_VAL 0x1111
|
||||
|
||||
struct exynos4_tmu_data {
|
||||
struct exynos4_tmu_platform_data *pdata;
|
||||
struct device *hwmon_dev;
|
||||
struct resource *mem;
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
struct work_struct irq_work;
|
||||
struct mutex lock;
|
||||
struct clk *clk;
|
||||
u8 temp_error1, temp_error2;
|
||||
};
|
||||
|
||||
/*
|
||||
* TMU treats temperature as a mapped temperature code.
|
||||
* The temperature is converted differently depending on the calibration type.
|
||||
*/
|
||||
static int temp_to_code(struct exynos4_tmu_data *data, u8 temp)
|
||||
{
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
int temp_code;
|
||||
|
||||
/* temp should range between 25 and 125 */
|
||||
if (temp < 25 || temp > 125) {
|
||||
temp_code = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (pdata->cal_type) {
|
||||
case TYPE_TWO_POINT_TRIMMING:
|
||||
temp_code = (temp - 25) *
|
||||
(data->temp_error2 - data->temp_error1) /
|
||||
(85 - 25) + data->temp_error1;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING:
|
||||
temp_code = temp + data->temp_error1 - 25;
|
||||
break;
|
||||
default:
|
||||
temp_code = temp + EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return temp_code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate a temperature value from a temperature code.
|
||||
* The unit of the temperature is degree Celsius.
|
||||
*/
|
||||
static int code_to_temp(struct exynos4_tmu_data *data, u8 temp_code)
|
||||
{
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
int temp;
|
||||
|
||||
/* temp_code should range between 75 and 175 */
|
||||
if (temp_code < 75 || temp_code > 175) {
|
||||
temp = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (pdata->cal_type) {
|
||||
case TYPE_TWO_POINT_TRIMMING:
|
||||
temp = (temp_code - data->temp_error1) * (85 - 25) /
|
||||
(data->temp_error2 - data->temp_error1) + 25;
|
||||
break;
|
||||
case TYPE_ONE_POINT_TRIMMING:
|
||||
temp = temp_code - data->temp_error1 + 25;
|
||||
break;
|
||||
default:
|
||||
temp = temp_code - EXYNOS4_TMU_DEF_CODE_TO_TEMP_OFFSET;
|
||||
break;
|
||||
}
|
||||
out:
|
||||
return temp;
|
||||
}
|
||||
|
||||
static int exynos4_tmu_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
unsigned int status, trim_info;
|
||||
int ret = 0, threshold_code;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
status = readb(data->base + EXYNOS4_TMU_REG_STATUS);
|
||||
if (!status) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Save trimming info in order to perform calibration */
|
||||
trim_info = readl(data->base + EXYNOS4_TMU_REG_TRIMINFO);
|
||||
data->temp_error1 = trim_info & EXYNOS4_TMU_TRIM_TEMP_MASK;
|
||||
data->temp_error2 = ((trim_info >> 8) & EXYNOS4_TMU_TRIM_TEMP_MASK);
|
||||
|
||||
/* Write temperature code for threshold */
|
||||
threshold_code = temp_to_code(data, pdata->threshold);
|
||||
if (threshold_code < 0) {
|
||||
ret = threshold_code;
|
||||
goto out;
|
||||
}
|
||||
writeb(threshold_code,
|
||||
data->base + EXYNOS4_TMU_REG_THRESHOLD_TEMP);
|
||||
|
||||
writeb(pdata->trigger_levels[0],
|
||||
data->base + EXYNOS4_TMU_REG_TRIG_LEVEL0);
|
||||
writeb(pdata->trigger_levels[1],
|
||||
data->base + EXYNOS4_TMU_REG_TRIG_LEVEL1);
|
||||
writeb(pdata->trigger_levels[2],
|
||||
data->base + EXYNOS4_TMU_REG_TRIG_LEVEL2);
|
||||
writeb(pdata->trigger_levels[3],
|
||||
data->base + EXYNOS4_TMU_REG_TRIG_LEVEL3);
|
||||
|
||||
writel(EXYNOS4_TMU_INTCLEAR_VAL,
|
||||
data->base + EXYNOS4_TMU_REG_INTCLEAR);
|
||||
out:
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void exynos4_tmu_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
unsigned int con, interrupt_en;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
con = pdata->reference_voltage << EXYNOS4_TMU_REF_VOLTAGE_SHIFT |
|
||||
pdata->gain << EXYNOS4_TMU_GAIN_SHIFT;
|
||||
if (on) {
|
||||
con |= EXYNOS4_TMU_CORE_ON;
|
||||
interrupt_en = pdata->trigger_level3_en << 12 |
|
||||
pdata->trigger_level2_en << 8 |
|
||||
pdata->trigger_level1_en << 4 |
|
||||
pdata->trigger_level0_en;
|
||||
} else {
|
||||
con |= EXYNOS4_TMU_CORE_OFF;
|
||||
interrupt_en = 0; /* Disable all interrupts */
|
||||
}
|
||||
writel(interrupt_en, data->base + EXYNOS4_TMU_REG_INTEN);
|
||||
writel(con, data->base + EXYNOS4_TMU_REG_CONTROL);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
static int exynos4_tmu_read(struct exynos4_tmu_data *data)
|
||||
{
|
||||
u8 temp_code;
|
||||
int temp;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
temp_code = readb(data->base + EXYNOS4_TMU_REG_CURRENT_TEMP);
|
||||
temp = code_to_temp(data, temp_code);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
static void exynos4_tmu_work(struct work_struct *work)
|
||||
{
|
||||
struct exynos4_tmu_data *data = container_of(work,
|
||||
struct exynos4_tmu_data, irq_work);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
clk_enable(data->clk);
|
||||
|
||||
writel(EXYNOS4_TMU_INTCLEAR_VAL, data->base + EXYNOS4_TMU_REG_INTCLEAR);
|
||||
|
||||
kobject_uevent(&data->hwmon_dev->kobj, KOBJ_CHANGE);
|
||||
|
||||
enable_irq(data->irq);
|
||||
|
||||
clk_disable(data->clk);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
static irqreturn_t exynos4_tmu_irq(int irq, void *id)
|
||||
{
|
||||
struct exynos4_tmu_data *data = id;
|
||||
|
||||
disable_irq_nosync(irq);
|
||||
schedule_work(&data->irq_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static ssize_t exynos4_tmu_show_name(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "exynos4-tmu\n");
|
||||
}
|
||||
|
||||
static ssize_t exynos4_tmu_show_temp(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct exynos4_tmu_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = exynos4_tmu_read(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* convert from degree Celsius to millidegree Celsius */
|
||||
return sprintf(buf, "%d\n", ret * 1000);
|
||||
}
|
||||
|
||||
static ssize_t exynos4_tmu_show_alarm(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct exynos4_tmu_data *data = dev_get_drvdata(dev);
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
int temp;
|
||||
unsigned int trigger_level;
|
||||
|
||||
temp = exynos4_tmu_read(data);
|
||||
if (temp < 0)
|
||||
return temp;
|
||||
|
||||
trigger_level = pdata->threshold + pdata->trigger_levels[attr->index];
|
||||
|
||||
return sprintf(buf, "%d\n", !!(temp > trigger_level));
|
||||
}
|
||||
|
||||
static ssize_t exynos4_tmu_show_level(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
struct exynos4_tmu_data *data = dev_get_drvdata(dev);
|
||||
struct exynos4_tmu_platform_data *pdata = data->pdata;
|
||||
unsigned int temp = pdata->threshold +
|
||||
pdata->trigger_levels[attr->index];
|
||||
|
||||
return sprintf(buf, "%u\n", temp * 1000);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(name, S_IRUGO, exynos4_tmu_show_name, NULL);
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, exynos4_tmu_show_temp, NULL, 0);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO,
|
||||
exynos4_tmu_show_alarm, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO,
|
||||
exynos4_tmu_show_alarm, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO,
|
||||
exynos4_tmu_show_alarm, NULL, 3);
|
||||
|
||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, exynos4_tmu_show_level, NULL, 1);
|
||||
static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, exynos4_tmu_show_level, NULL, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp1_emergency, S_IRUGO,
|
||||
exynos4_tmu_show_level, NULL, 3);
|
||||
|
||||
static struct attribute *exynos4_tmu_attributes[] = {
|
||||
&dev_attr_name.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_crit.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_emergency.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group exynos4_tmu_attr_group = {
|
||||
.attrs = exynos4_tmu_attributes,
|
||||
};
|
||||
|
||||
static int __devinit exynos4_tmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos4_tmu_data *data;
|
||||
struct exynos4_tmu_platform_data *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "No platform init data supplied.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(struct exynos4_tmu_data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
dev_err(&pdev->dev, "Failed to allocate driver structure\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
data->irq = platform_get_irq(pdev, 0);
|
||||
if (data->irq < 0) {
|
||||
ret = data->irq;
|
||||
dev_err(&pdev->dev, "Failed to get platform irq\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
INIT_WORK(&data->irq_work, exynos4_tmu_work);
|
||||
|
||||
data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!data->mem) {
|
||||
ret = -ENOENT;
|
||||
dev_err(&pdev->dev, "Failed to get platform resource\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
data->mem = request_mem_region(data->mem->start,
|
||||
resource_size(data->mem), pdev->name);
|
||||
if (!data->mem) {
|
||||
ret = -ENODEV;
|
||||
dev_err(&pdev->dev, "Failed to request memory region\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
data->base = ioremap(data->mem->start, resource_size(data->mem));
|
||||
if (!data->base) {
|
||||
ret = -ENODEV;
|
||||
dev_err(&pdev->dev, "Failed to ioremap memory\n");
|
||||
goto err_mem_region;
|
||||
}
|
||||
|
||||
ret = request_irq(data->irq, exynos4_tmu_irq,
|
||||
IRQF_TRIGGER_RISING,
|
||||
"exynos4-tmu", data);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
|
||||
goto err_io_remap;
|
||||
}
|
||||
|
||||
data->clk = clk_get(NULL, "tmu_apbif");
|
||||
if (IS_ERR(data->clk)) {
|
||||
ret = PTR_ERR(data->clk);
|
||||
dev_err(&pdev->dev, "Failed to get clock\n");
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
data->pdata = pdata;
|
||||
platform_set_drvdata(pdev, data);
|
||||
mutex_init(&data->lock);
|
||||
|
||||
ret = exynos4_tmu_initialize(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to initialize TMU\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to create sysfs group\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
ret = PTR_ERR(data->hwmon_dev);
|
||||
dev_err(&pdev->dev, "Failed to register hwmon device\n");
|
||||
goto err_create_group;
|
||||
}
|
||||
|
||||
exynos4_tmu_control(pdev, true);
|
||||
|
||||
return 0;
|
||||
|
||||
err_create_group:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
|
||||
err_clk:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
clk_put(data->clk);
|
||||
err_irq:
|
||||
free_irq(data->irq, data);
|
||||
err_io_remap:
|
||||
iounmap(data->base);
|
||||
err_mem_region:
|
||||
release_mem_region(data->mem->start, resource_size(data->mem));
|
||||
err_free:
|
||||
kfree(data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit exynos4_tmu_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos4_tmu_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
exynos4_tmu_control(pdev, false);
|
||||
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &exynos4_tmu_attr_group);
|
||||
|
||||
clk_put(data->clk);
|
||||
|
||||
free_irq(data->irq, data);
|
||||
|
||||
iounmap(data->base);
|
||||
release_mem_region(data->mem->start, resource_size(data->mem));
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int exynos4_tmu_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
exynos4_tmu_control(pdev, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int exynos4_tmu_resume(struct platform_device *pdev)
|
||||
{
|
||||
exynos4_tmu_initialize(pdev);
|
||||
exynos4_tmu_control(pdev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define exynos4_tmu_suspend NULL
|
||||
#define exynos4_tmu_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver exynos4_tmu_driver = {
|
||||
.driver = {
|
||||
.name = "exynos4-tmu",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = exynos4_tmu_probe,
|
||||
.remove = __devexit_p(exynos4_tmu_remove),
|
||||
.suspend = exynos4_tmu_suspend,
|
||||
.resume = exynos4_tmu_resume,
|
||||
};
|
||||
|
||||
static int __init exynos4_tmu_driver_init(void)
|
||||
{
|
||||
return platform_driver_register(&exynos4_tmu_driver);
|
||||
}
|
||||
module_init(exynos4_tmu_driver_init);
|
||||
|
||||
static void __exit exynos4_tmu_driver_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&exynos4_tmu_driver);
|
||||
}
|
||||
module_exit(exynos4_tmu_driver_exit);
|
||||
|
||||
MODULE_DESCRIPTION("EXYNOS4 TMU Driver");
|
||||
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:exynos4-tmu");
|
@ -605,7 +605,7 @@ static struct sensor_device_attribute_2 fxxxx_fan_beep_attr[] = {
|
||||
|
||||
/* PWM attr for the f71862fg, fewer pwms and fewer zones per pwm than the
|
||||
standard models */
|
||||
static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
|
||||
static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[3][7] = { {
|
||||
SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 0),
|
||||
@ -627,7 +627,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
|
||||
0, 0),
|
||||
SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 1),
|
||||
@ -649,7 +649,7 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
|
||||
0, 1),
|
||||
SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 2),
|
||||
@ -671,12 +671,12 @@ static struct sensor_device_attribute_2 f71862fg_auto_pwm_attr[] = {
|
||||
0, 2),
|
||||
SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
|
||||
};
|
||||
} };
|
||||
|
||||
/* PWM attr for the f71808e/f71869, almost identical to the f71862fg, but the
|
||||
pwm setting when the temperature is above the pwmX_auto_point1_temp can be
|
||||
programmed instead of being hardcoded to 0xff */
|
||||
static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
|
||||
static struct sensor_device_attribute_2 f71869_auto_pwm_attr[3][8] = { {
|
||||
SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 0),
|
||||
@ -701,7 +701,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
|
||||
0, 0),
|
||||
SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 1),
|
||||
@ -726,7 +726,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
|
||||
0, 1),
|
||||
SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 2),
|
||||
@ -751,7 +751,7 @@ static struct sensor_device_attribute_2 f71869_auto_pwm_attr[] = {
|
||||
0, 2),
|
||||
SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
|
||||
};
|
||||
} };
|
||||
|
||||
/* PWM attr for the standard models */
|
||||
static struct sensor_device_attribute_2 fxxxx_auto_pwm_attr[4][14] = { {
|
||||
@ -928,7 +928,7 @@ static struct sensor_device_attribute_2 f8000_fan_attr[] = {
|
||||
/* PWM attr for the f8000, zones mapped to temp instead of to pwm!
|
||||
Also the register block at offset A0 maps to TEMP1 (so our temp2, as the
|
||||
F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 */
|
||||
static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
|
||||
static struct sensor_device_attribute_2 f8000_auto_pwm_attr[3][14] = { {
|
||||
SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 0),
|
||||
@ -969,7 +969,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
|
||||
show_pwm_auto_point_temp_hyst, NULL, 2, 2),
|
||||
SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 1),
|
||||
@ -1010,7 +1010,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
|
||||
show_pwm_auto_point_temp_hyst, NULL, 2, 0),
|
||||
SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
|
||||
|
||||
}, {
|
||||
SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
|
||||
show_pwm_auto_point_channel,
|
||||
store_pwm_auto_point_channel, 0, 2),
|
||||
@ -1051,7 +1051,7 @@ static struct sensor_device_attribute_2 f8000_auto_pwm_attr[] = {
|
||||
show_pwm_auto_point_temp_hyst, NULL, 2, 1),
|
||||
SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO,
|
||||
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
|
||||
};
|
||||
} };
|
||||
|
||||
/* Super I/O functions */
|
||||
static inline int superio_inb(int base, int reg)
|
||||
@ -2154,6 +2154,104 @@ static void f71882fg_remove_sysfs_files(struct platform_device *pdev,
|
||||
device_remove_file(&pdev->dev, &attr[i].dev_attr);
|
||||
}
|
||||
|
||||
static int __devinit f71882fg_create_fan_sysfs_files(
|
||||
struct platform_device *pdev, int idx)
|
||||
{
|
||||
struct f71882fg_data *data = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
/* Sanity check the pwm setting */
|
||||
err = 0;
|
||||
switch (data->type) {
|
||||
case f71858fg:
|
||||
if (((data->pwm_enable >> (idx * 2)) & 3) == 3)
|
||||
err = 1;
|
||||
break;
|
||||
case f71862fg:
|
||||
if (((data->pwm_enable >> (idx * 2)) & 1) != 1)
|
||||
err = 1;
|
||||
break;
|
||||
case f8000:
|
||||
if (idx == 2)
|
||||
err = data->pwm_enable & 0x20;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Invalid (reserved) pwm settings: 0x%02x, "
|
||||
"skipping fan %d\n",
|
||||
(data->pwm_enable >> (idx * 2)) & 3, idx + 1);
|
||||
return 0; /* This is a non fatal condition */
|
||||
}
|
||||
|
||||
err = f71882fg_create_sysfs_files(pdev, &fxxxx_fan_attr[idx][0],
|
||||
ARRAY_SIZE(fxxxx_fan_attr[0]));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (f71882fg_fan_has_beep[data->type]) {
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&fxxxx_fan_beep_attr[idx],
|
||||
1);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "Fan: %d is in %s mode\n", idx + 1,
|
||||
(data->pwm_enable & (1 << (2 * idx))) ? "duty-cycle" : "RPM");
|
||||
|
||||
/* Check for unsupported auto pwm settings */
|
||||
switch (data->type) {
|
||||
case f71808e:
|
||||
case f71808a:
|
||||
case f71869:
|
||||
case f71869a:
|
||||
case f71889fg:
|
||||
case f71889ed:
|
||||
case f71889a:
|
||||
data->pwm_auto_point_mapping[idx] =
|
||||
f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(idx));
|
||||
if ((data->pwm_auto_point_mapping[idx] & 0x80) ||
|
||||
(data->pwm_auto_point_mapping[idx] & 3) == 0) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Auto pwm controlled by raw digital "
|
||||
"data, disabling pwm auto_point "
|
||||
"sysfs attributes for fan %d\n", idx + 1);
|
||||
return 0; /* This is a non fatal condition */
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data->type) {
|
||||
case f71862fg:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&f71862fg_auto_pwm_attr[idx][0],
|
||||
ARRAY_SIZE(f71862fg_auto_pwm_attr[0]));
|
||||
break;
|
||||
case f71808e:
|
||||
case f71869:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&f71869_auto_pwm_attr[idx][0],
|
||||
ARRAY_SIZE(f71869_auto_pwm_attr[0]));
|
||||
break;
|
||||
case f8000:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&f8000_auto_pwm_attr[idx][0],
|
||||
ARRAY_SIZE(f8000_auto_pwm_attr[0]));
|
||||
break;
|
||||
default:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&fxxxx_auto_pwm_attr[idx][0],
|
||||
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]));
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devinit f71882fg_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct f71882fg_data *data;
|
||||
@ -2272,117 +2370,29 @@ static int __devinit f71882fg_probe(struct platform_device *pdev)
|
||||
data->pwm_enable =
|
||||
f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
|
||||
|
||||
/* Sanity check the pwm settings */
|
||||
switch (data->type) {
|
||||
case f71858fg:
|
||||
err = 0;
|
||||
for (i = 0; i < nr_fans; i++)
|
||||
if (((data->pwm_enable >> (i * 2)) & 3) == 3)
|
||||
err = 1;
|
||||
break;
|
||||
case f71862fg:
|
||||
err = (data->pwm_enable & 0x15) != 0x15;
|
||||
break;
|
||||
case f8000:
|
||||
err = data->pwm_enable & 0x20;
|
||||
break;
|
||||
default:
|
||||
err = 0;
|
||||
break;
|
||||
}
|
||||
if (err) {
|
||||
dev_err(&pdev->dev,
|
||||
"Invalid (reserved) pwm settings: 0x%02x\n",
|
||||
(unsigned int)data->pwm_enable);
|
||||
err = -ENODEV;
|
||||
goto exit_unregister_sysfs;
|
||||
}
|
||||
|
||||
err = f71882fg_create_sysfs_files(pdev, &fxxxx_fan_attr[0][0],
|
||||
ARRAY_SIZE(fxxxx_fan_attr[0]) * nr_fans);
|
||||
if (err)
|
||||
goto exit_unregister_sysfs;
|
||||
|
||||
if (f71882fg_fan_has_beep[data->type]) {
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
fxxxx_fan_beep_attr, nr_fans);
|
||||
for (i = 0; i < nr_fans; i++) {
|
||||
err = f71882fg_create_fan_sysfs_files(pdev, i);
|
||||
if (err)
|
||||
goto exit_unregister_sysfs;
|
||||
}
|
||||
|
||||
switch (data->type) {
|
||||
case f71808e:
|
||||
case f71808a:
|
||||
case f71869:
|
||||
case f71869a:
|
||||
case f71889fg:
|
||||
case f71889ed:
|
||||
case f71889a:
|
||||
for (i = 0; i < nr_fans; i++) {
|
||||
data->pwm_auto_point_mapping[i] =
|
||||
f71882fg_read8(data,
|
||||
F71882FG_REG_POINT_MAPPING(i));
|
||||
if ((data->pwm_auto_point_mapping[i] & 0x80) ||
|
||||
(data->pwm_auto_point_mapping[i] & 3) == 0)
|
||||
break;
|
||||
}
|
||||
if (i != nr_fans) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Auto pwm controlled by raw digital "
|
||||
"data, disabling pwm auto_point "
|
||||
"sysfs attributes\n");
|
||||
goto no_pwm_auto_point;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Some types have 1 extra fan with limited functionality */
|
||||
switch (data->type) {
|
||||
case f71808a:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&fxxxx_auto_pwm_attr[0][0],
|
||||
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
|
||||
if (err)
|
||||
goto exit_unregister_sysfs;
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
f71808a_fan3_attr,
|
||||
ARRAY_SIZE(f71808a_fan3_attr));
|
||||
break;
|
||||
case f71862fg:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
f71862fg_auto_pwm_attr,
|
||||
ARRAY_SIZE(f71862fg_auto_pwm_attr));
|
||||
break;
|
||||
case f71808e:
|
||||
case f71869:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
f71869_auto_pwm_attr,
|
||||
ARRAY_SIZE(f71869_auto_pwm_attr));
|
||||
break;
|
||||
case f8000:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
f8000_fan_attr,
|
||||
ARRAY_SIZE(f8000_fan_attr));
|
||||
if (err)
|
||||
goto exit_unregister_sysfs;
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
f8000_auto_pwm_attr,
|
||||
ARRAY_SIZE(f8000_auto_pwm_attr));
|
||||
break;
|
||||
default:
|
||||
err = f71882fg_create_sysfs_files(pdev,
|
||||
&fxxxx_auto_pwm_attr[0][0],
|
||||
ARRAY_SIZE(fxxxx_auto_pwm_attr[0]) * nr_fans);
|
||||
break;
|
||||
}
|
||||
if (err)
|
||||
goto exit_unregister_sysfs;
|
||||
|
||||
no_pwm_auto_point:
|
||||
for (i = 0; i < nr_fans; i++)
|
||||
dev_info(&pdev->dev, "Fan: %d is in %s mode\n", i + 1,
|
||||
(data->pwm_enable & (1 << 2 * i)) ?
|
||||
"duty-cycle" : "RPM");
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
@ -2476,22 +2486,23 @@ static int f71882fg_remove(struct platform_device *pdev)
|
||||
break;
|
||||
case f71862fg:
|
||||
f71882fg_remove_sysfs_files(pdev,
|
||||
f71862fg_auto_pwm_attr,
|
||||
ARRAY_SIZE(f71862fg_auto_pwm_attr));
|
||||
&f71862fg_auto_pwm_attr[0][0],
|
||||
ARRAY_SIZE(f71862fg_auto_pwm_attr[0]) *
|
||||
nr_fans);
|
||||
break;
|
||||
case f71808e:
|
||||
case f71869:
|
||||
f71882fg_remove_sysfs_files(pdev,
|
||||
f71869_auto_pwm_attr,
|
||||
ARRAY_SIZE(f71869_auto_pwm_attr));
|
||||
&f71869_auto_pwm_attr[0][0],
|
||||
ARRAY_SIZE(f71869_auto_pwm_attr[0]) * nr_fans);
|
||||
break;
|
||||
case f8000:
|
||||
f71882fg_remove_sysfs_files(pdev,
|
||||
f8000_fan_attr,
|
||||
ARRAY_SIZE(f8000_fan_attr));
|
||||
f71882fg_remove_sysfs_files(pdev,
|
||||
f8000_auto_pwm_attr,
|
||||
ARRAY_SIZE(f8000_auto_pwm_attr));
|
||||
&f8000_auto_pwm_attr[0][0],
|
||||
ARRAY_SIZE(f8000_auto_pwm_attr[0]) * nr_fans);
|
||||
break;
|
||||
default:
|
||||
f71882fg_remove_sysfs_files(pdev,
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
enum lm75_type { /* keep sorted in alphabetical order */
|
||||
adt75,
|
||||
ds1775,
|
||||
ds75,
|
||||
lm75,
|
||||
@ -213,6 +214,7 @@ static int lm75_remove(struct i2c_client *client)
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lm75_ids[] = {
|
||||
{ "adt75", adt75, },
|
||||
{ "ds1775", ds1775, },
|
||||
{ "ds75", ds75, },
|
||||
{ "lm75", lm75, },
|
||||
@ -247,19 +249,30 @@ static int lm75_detect(struct i2c_client *new_client,
|
||||
I2C_FUNC_SMBUS_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
/* Now, we do the remaining detection. There is no identification-
|
||||
dedicated register so we have to rely on several tricks:
|
||||
unused bits, registers cycling over 8-address boundaries,
|
||||
addresses 0x04-0x07 returning the last read value.
|
||||
The cycling+unused addresses combination is not tested,
|
||||
since it would significantly slow the detection down and would
|
||||
hardly add any value.
|
||||
|
||||
The National Semiconductor LM75A is different than earlier
|
||||
LM75s. It has an ID byte of 0xaX (where X is the chip
|
||||
revision, with 1 being the only revision in existence) in
|
||||
register 7, and unused registers return 0xff rather than the
|
||||
last read value. */
|
||||
/*
|
||||
* Now, we do the remaining detection. There is no identification-
|
||||
* dedicated register so we have to rely on several tricks:
|
||||
* unused bits, registers cycling over 8-address boundaries,
|
||||
* addresses 0x04-0x07 returning the last read value.
|
||||
* The cycling+unused addresses combination is not tested,
|
||||
* since it would significantly slow the detection down and would
|
||||
* hardly add any value.
|
||||
*
|
||||
* The National Semiconductor LM75A is different than earlier
|
||||
* LM75s. It has an ID byte of 0xaX (where X is the chip
|
||||
* revision, with 1 being the only revision in existence) in
|
||||
* register 7, and unused registers return 0xff rather than the
|
||||
* last read value.
|
||||
*
|
||||
* Note that this function only detects the original National
|
||||
* Semiconductor LM75 and the LM75A. Clones from other vendors
|
||||
* aren't detected, on purpose, because they are typically never
|
||||
* found on PC hardware. They are found on embedded designs where
|
||||
* they can be instantiated explicitly so detection is not needed.
|
||||
* The absence of identification registers on all these clones
|
||||
* would make their exhaustive detection very difficult and weak,
|
||||
* and odds are that the driver would bind to unsupported devices.
|
||||
*/
|
||||
|
||||
/* Unused bits */
|
||||
conf = i2c_smbus_read_byte_data(new_client, 1);
|
||||
|
@ -20,17 +20,18 @@ config SENSORS_PMBUS
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for generic
|
||||
PMBus devices, including but not limited to ADP4000, BMR450, BMR451,
|
||||
BMR453, BMR454, LTC2978, NCP4200, and NCP4208.
|
||||
BMR453, BMR454, NCP4200, and NCP4208.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called pmbus.
|
||||
|
||||
config SENSORS_ADM1275
|
||||
tristate "Analog Devices ADM1275"
|
||||
tristate "Analog Devices ADM1275 and compatibles"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Analog
|
||||
Devices ADM1275 Hot-Swap Controller and Digital Power Monitor.
|
||||
Devices ADM1275 and ADM1276 Hot-Swap Controller and Digital Power
|
||||
Monitor.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called adm1275.
|
||||
@ -45,6 +46,16 @@ config SENSORS_LM25066
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called lm25066.
|
||||
|
||||
config SENSORS_LTC2978
|
||||
tristate "Linear Technologies LTC2978 and LTC3880"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Linear
|
||||
Technology LTC2978 and LTC3880.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2978.
|
||||
|
||||
config SENSORS_MAX16064
|
||||
tristate "Maxim MAX16064"
|
||||
default n
|
||||
@ -97,4 +108,15 @@ config SENSORS_UCD9200
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ucd9200.
|
||||
|
||||
config SENSORS_ZL6100
|
||||
tristate "Intersil ZL6100 and compatibles"
|
||||
default n
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Intersil
|
||||
ZL2004, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, and ZL6105 Digital
|
||||
DC/DC Controllers.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called zl6100.
|
||||
|
||||
endif # PMBUS
|
||||
|
@ -6,8 +6,10 @@ obj-$(CONFIG_PMBUS) += pmbus_core.o
|
||||
obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
|
||||
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
|
||||
obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
||||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.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
|
||||
obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <linux/i2c.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { adm1275, adm1276 };
|
||||
|
||||
#define ADM1275_PEAK_IOUT 0xd0
|
||||
#define ADM1275_PEAK_VIN 0xd1
|
||||
#define ADM1275_PEAK_VOUT 0xd2
|
||||
@ -31,14 +33,47 @@
|
||||
#define ADM1275_VIN_VOUT_SELECT (1 << 6)
|
||||
#define ADM1275_VRANGE (1 << 5)
|
||||
|
||||
#define ADM1275_IOUT_WARN2_LIMIT 0xd7
|
||||
#define ADM1275_DEVICE_CONFIG 0xd8
|
||||
|
||||
#define ADM1275_IOUT_WARN2_SELECT (1 << 4)
|
||||
|
||||
#define ADM1276_PEAK_PIN 0xda
|
||||
|
||||
#define ADM1275_MFR_STATUS_IOUT_WARN2 (1 << 0)
|
||||
|
||||
struct adm1275_data {
|
||||
int id;
|
||||
bool have_oc_fault;
|
||||
struct pmbus_driver_info info;
|
||||
};
|
||||
|
||||
#define to_adm1275_data(x) container_of(x, struct adm1275_data, info)
|
||||
|
||||
static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct adm1275_data *data = to_adm1275_data(info);
|
||||
int ret = 0;
|
||||
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_UC_FAULT_LIMIT:
|
||||
if (data->have_oc_fault) {
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
}
|
||||
ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT);
|
||||
break;
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
if (!data->have_oc_fault) {
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
}
|
||||
ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT);
|
||||
break;
|
||||
@ -48,10 +83,20 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
case PMBUS_VIRT_READ_VIN_MAX:
|
||||
ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN);
|
||||
break;
|
||||
case PMBUS_VIRT_READ_PIN_MAX:
|
||||
if (data->id != adm1276) {
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
}
|
||||
ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
ret = 0;
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
if (data->id != adm1276)
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
@ -66,9 +111,14 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
int ret;
|
||||
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IOUT_UC_FAULT_LIMIT:
|
||||
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||
ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT,
|
||||
word);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0);
|
||||
break;
|
||||
@ -78,6 +128,41 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct adm1275_data *data = to_adm1275_data(info);
|
||||
int mfr_status, ret;
|
||||
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_IOUT:
|
||||
ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT);
|
||||
if (ret < 0)
|
||||
break;
|
||||
mfr_status = pmbus_read_byte_data(client, page,
|
||||
PMBUS_STATUS_MFR_SPECIFIC);
|
||||
if (mfr_status < 0) {
|
||||
ret = mfr_status;
|
||||
break;
|
||||
}
|
||||
if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) {
|
||||
ret |= data->have_oc_fault ?
|
||||
PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
@ -88,16 +173,17 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
static int adm1275_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int config;
|
||||
int config, device_config;
|
||||
int ret;
|
||||
struct pmbus_driver_info *info;
|
||||
struct adm1275_data *data;
|
||||
|
||||
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)
|
||||
data = kzalloc(sizeof(struct adm1275_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG);
|
||||
@ -106,6 +192,15 @@ static int adm1275_probe(struct i2c_client *client,
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG);
|
||||
if (device_config < 0) {
|
||||
ret = device_config;
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
data->id = id->driver_data;
|
||||
info = &data->info;
|
||||
|
||||
info->pages = 1;
|
||||
info->format[PSC_VOLTAGE_IN] = direct;
|
||||
info->format[PSC_VOLTAGE_OUT] = direct;
|
||||
@ -116,6 +211,7 @@ static int adm1275_probe(struct i2c_client *client,
|
||||
info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
|
||||
|
||||
info->read_word_data = adm1275_read_word_data;
|
||||
info->read_byte_data = adm1275_read_byte_data;
|
||||
info->write_word_data = adm1275_write_word_data;
|
||||
|
||||
if (config & ADM1275_VRANGE) {
|
||||
@ -134,10 +230,36 @@ static int adm1275_probe(struct i2c_client *client,
|
||||
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;
|
||||
if (device_config & ADM1275_IOUT_WARN2_SELECT)
|
||||
data->have_oc_fault = true;
|
||||
|
||||
switch (id->driver_data) {
|
||||
case adm1275:
|
||||
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;
|
||||
break;
|
||||
case adm1276:
|
||||
info->format[PSC_POWER] = direct;
|
||||
info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN
|
||||
| PMBUS_HAVE_STATUS_INPUT;
|
||||
if (config & ADM1275_VIN_VOUT_SELECT)
|
||||
info->func[0] |=
|
||||
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
|
||||
if (config & ADM1275_VRANGE) {
|
||||
info->m[PSC_POWER] = 6043;
|
||||
info->b[PSC_POWER] = 0;
|
||||
info->R[PSC_POWER] = -2;
|
||||
} else {
|
||||
info->m[PSC_POWER] = 2115;
|
||||
info->b[PSC_POWER] = 0;
|
||||
info->R[PSC_POWER] = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ret = pmbus_do_probe(client, id, info);
|
||||
if (ret)
|
||||
@ -145,22 +267,23 @@ static int adm1275_probe(struct i2c_client *client,
|
||||
return 0;
|
||||
|
||||
err_mem:
|
||||
kfree(info);
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adm1275_remove(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
int ret;
|
||||
const struct adm1275_data *data = to_adm1275_data(info);
|
||||
|
||||
ret = pmbus_do_remove(client);
|
||||
kfree(info);
|
||||
return ret;
|
||||
pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id adm1275_id[] = {
|
||||
{"adm1275", 0},
|
||||
{ "adm1275", adm1275 },
|
||||
{ "adm1276", adm1276 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adm1275_id);
|
||||
@ -185,7 +308,7 @@ static void __exit adm1275_exit(void)
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275");
|
||||
MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(adm1275_init);
|
||||
module_exit(adm1275_exit);
|
||||
|
@ -57,7 +57,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
int ret;
|
||||
|
||||
if (page > 1)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
/* Map READ_VAUX into READ_VOUT register on page 1 */
|
||||
if (page == 1) {
|
||||
@ -85,7 +85,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
break;
|
||||
default:
|
||||
/* No other valid registers on page 1 */
|
||||
ret = -EINVAL;
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
}
|
||||
goto done;
|
||||
@ -138,7 +138,7 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
int ret;
|
||||
|
||||
if (page > 1)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
@ -164,10 +164,10 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
static int lm25066_write_byte(struct i2c_client *client, int page, u8 value)
|
||||
{
|
||||
if (page > 1)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
if (page == 0)
|
||||
return pmbus_write_byte(client, 0, value);
|
||||
if (page <= 0)
|
||||
return pmbus_write_byte(client, page, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -309,11 +309,10 @@ static int lm25066_remove(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct lm25066_data *data = to_lm25066_data(info);
|
||||
int ret;
|
||||
|
||||
ret = pmbus_do_remove(client);
|
||||
pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lm25066_id[] = {
|
||||
|
408
drivers/hwmon/pmbus/ltc2978.c
Normal file
408
drivers/hwmon/pmbus/ltc2978.c
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* Hardware monitoring driver for LTC2978 and LTC3880
|
||||
*
|
||||
* 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 "pmbus.h"
|
||||
|
||||
enum chips { ltc2978, ltc3880 };
|
||||
|
||||
/* LTC2978 and LTC3880 */
|
||||
#define LTC2978_MFR_VOUT_PEAK 0xdd
|
||||
#define LTC2978_MFR_VIN_PEAK 0xde
|
||||
#define LTC2978_MFR_TEMPERATURE_PEAK 0xdf
|
||||
#define LTC2978_MFR_SPECIAL_ID 0xe7
|
||||
|
||||
/* LTC2978 only */
|
||||
#define LTC2978_MFR_VOUT_MIN 0xfb
|
||||
#define LTC2978_MFR_VIN_MIN 0xfc
|
||||
#define LTC2978_MFR_TEMPERATURE_MIN 0xfd
|
||||
|
||||
/* LTC3880 only */
|
||||
#define LTC3880_MFR_IOUT_PEAK 0xd7
|
||||
#define LTC3880_MFR_CLEAR_PEAKS 0xe3
|
||||
#define LTC3880_MFR_TEMPERATURE2_PEAK 0xf4
|
||||
|
||||
#define LTC2978_ID_REV1 0x0121
|
||||
#define LTC2978_ID_REV2 0x0122
|
||||
#define LTC3880_ID 0x4000
|
||||
#define LTC3880_ID_MASK 0xff00
|
||||
|
||||
/*
|
||||
* LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which
|
||||
* happens pretty much each time chip data is updated. Raw peak data therefore
|
||||
* does not provide much value. To be able to provide useful peak data, keep an
|
||||
* internal cache of measured peak data, which is only cleared if an explicit
|
||||
* "clear peak" command is executed for the sensor in question.
|
||||
*/
|
||||
struct ltc2978_data {
|
||||
enum chips id;
|
||||
int vin_min, vin_max;
|
||||
int temp_min, temp_max;
|
||||
int vout_min[8], vout_max[8];
|
||||
int iout_max[2];
|
||||
int temp2_max[2];
|
||||
struct pmbus_driver_info info;
|
||||
};
|
||||
|
||||
#define to_ltc2978_data(x) container_of(x, struct ltc2978_data, info)
|
||||
|
||||
static inline int lin11_to_val(int data)
|
||||
{
|
||||
s16 e = ((s16)data) >> 11;
|
||||
s32 m = (((s16)(data << 5)) >> 5);
|
||||
|
||||
/*
|
||||
* mantissa is 10 bit + sign, exponent adds up to 15 bit.
|
||||
* Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31).
|
||||
*/
|
||||
e += 6;
|
||||
return (e < 0 ? m >> -e : m << e);
|
||||
}
|
||||
|
||||
static int ltc2978_read_word_data_common(struct i2c_client *client, int page,
|
||||
int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct ltc2978_data *data = to_ltc2978_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VIN_MAX:
|
||||
ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret) > lin11_to_val(data->vin_max))
|
||||
data->vin_max = ret;
|
||||
ret = data->vin_max;
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK);
|
||||
if (ret >= 0) {
|
||||
/*
|
||||
* VOUT is 16 bit unsigned with fixed exponent,
|
||||
* so we can compare it directly
|
||||
*/
|
||||
if (ret > data->vout_max[page])
|
||||
data->vout_max[page] = ret;
|
||||
ret = data->vout_max[page];
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_TEMP_MAX:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
LTC2978_MFR_TEMPERATURE_PEAK);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret) > lin11_to_val(data->temp_max))
|
||||
data->temp_max = ret;
|
||||
ret = data->temp_max;
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct ltc2978_data *data = to_ltc2978_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VIN_MIN:
|
||||
ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret) < lin11_to_val(data->vin_min))
|
||||
data->vin_min = ret;
|
||||
ret = data->vin_min;
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN);
|
||||
if (ret >= 0) {
|
||||
/*
|
||||
* VOUT_MIN is known to not be supported on some lots
|
||||
* of LTC2978 revision 1, and will return the maximum
|
||||
* possible voltage if read. If VOUT_MAX is valid and
|
||||
* lower than the reading of VOUT_MIN, use it instead.
|
||||
*/
|
||||
if (data->vout_max[page] && ret > data->vout_max[page])
|
||||
ret = data->vout_max[page];
|
||||
if (ret < data->vout_min[page])
|
||||
data->vout_min[page] = ret;
|
||||
ret = data->vout_min[page];
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_TEMP_MIN:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
LTC2978_MFR_TEMPERATURE_MIN);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret)
|
||||
< lin11_to_val(data->temp_min))
|
||||
data->temp_min = ret;
|
||||
ret = data->temp_min;
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_READ_TEMP2_MAX:
|
||||
case PMBUS_VIRT_RESET_TEMP2_HISTORY:
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
default:
|
||||
ret = ltc2978_read_word_data_common(client, page, reg);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct ltc2978_data *data = to_ltc2978_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||
ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret)
|
||||
> lin11_to_val(data->iout_max[page]))
|
||||
data->iout_max[page] = ret;
|
||||
ret = data->iout_max[page];
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_TEMP2_MAX:
|
||||
ret = pmbus_read_word_data(client, page,
|
||||
LTC3880_MFR_TEMPERATURE2_PEAK);
|
||||
if (ret >= 0) {
|
||||
if (lin11_to_val(ret)
|
||||
> lin11_to_val(data->temp2_max[page]))
|
||||
data->temp2_max[page] = ret;
|
||||
ret = data->temp2_max[page];
|
||||
}
|
||||
break;
|
||||
case PMBUS_VIRT_READ_VIN_MIN:
|
||||
case PMBUS_VIRT_READ_VOUT_MIN:
|
||||
case PMBUS_VIRT_READ_TEMP_MIN:
|
||||
ret = -ENXIO;
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
case PMBUS_VIRT_RESET_TEMP2_HISTORY:
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = ltc2978_read_word_data_common(client, page, reg);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2978_clear_peaks(struct i2c_client *client, int page,
|
||||
enum chips id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (id == ltc2978)
|
||||
ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
|
||||
else
|
||||
ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2978_write_word_data(struct i2c_client *client, int page,
|
||||
int reg, u16 word)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct ltc2978_data *data = to_ltc2978_data(info);
|
||||
int ret;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||
data->iout_max[page] = 0x7fff;
|
||||
ret = ltc2978_clear_peaks(client, page, data->id);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_TEMP2_HISTORY:
|
||||
data->temp2_max[page] = 0x7fff;
|
||||
ret = ltc2978_clear_peaks(client, page, data->id);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_VOUT_HISTORY:
|
||||
data->vout_min[page] = 0xffff;
|
||||
data->vout_max[page] = 0;
|
||||
ret = ltc2978_clear_peaks(client, page, data->id);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_VIN_HISTORY:
|
||||
data->vin_min = 0x7bff;
|
||||
data->vin_max = 0;
|
||||
ret = ltc2978_clear_peaks(client, page, data->id);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_TEMP_HISTORY:
|
||||
data->temp_min = 0x7bff;
|
||||
data->temp_max = 0x7fff;
|
||||
ret = ltc2978_clear_peaks(client, page, data->id);
|
||||
break;
|
||||
default:
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ltc2978_id[] = {
|
||||
{"ltc2978", ltc2978},
|
||||
{"ltc3880", ltc3880},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ltc2978_id);
|
||||
|
||||
static int ltc2978_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int chip_id, ret, i;
|
||||
struct ltc2978_data *data;
|
||||
struct pmbus_driver_info *info;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_WORD_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
data = kzalloc(sizeof(struct ltc2978_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID);
|
||||
if (chip_id < 0) {
|
||||
ret = chip_id;
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2) {
|
||||
data->id = ltc2978;
|
||||
} else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) {
|
||||
data->id = ltc3880;
|
||||
} else {
|
||||
dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id);
|
||||
ret = -ENODEV;
|
||||
goto err_mem;
|
||||
}
|
||||
if (data->id != id->driver_data)
|
||||
dev_warn(&client->dev,
|
||||
"Device mismatch: Configured %s, detected %s\n",
|
||||
id->name,
|
||||
ltc2978_id[data->id].name);
|
||||
|
||||
info = &data->info;
|
||||
info->write_word_data = ltc2978_write_word_data;
|
||||
|
||||
data->vout_min[0] = 0xffff;
|
||||
data->vin_min = 0x7bff;
|
||||
data->temp_min = 0x7bff;
|
||||
data->temp_max = 0x7fff;
|
||||
|
||||
switch (id->driver_data) {
|
||||
case ltc2978:
|
||||
info->read_word_data = ltc2978_read_word_data;
|
||||
info->pages = 8;
|
||||
info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
|
||||
| PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
|
||||
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
for (i = 1; i < 8; i++) {
|
||||
info->func[i] = PMBUS_HAVE_VOUT
|
||||
| PMBUS_HAVE_STATUS_VOUT;
|
||||
data->vout_min[i] = 0xffff;
|
||||
}
|
||||
break;
|
||||
case ltc3880:
|
||||
info->read_word_data = ltc3880_read_word_data;
|
||||
info->pages = 2;
|
||||
info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN
|
||||
| PMBUS_HAVE_STATUS_INPUT
|
||||
| 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;
|
||||
info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
|
||||
| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
|
||||
| PMBUS_HAVE_POUT
|
||||
| PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
|
||||
data->vout_min[1] = 0xffff;
|
||||
break;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
goto err_mem;
|
||||
}
|
||||
|
||||
ret = pmbus_do_probe(client, id, info);
|
||||
if (ret)
|
||||
goto err_mem;
|
||||
return 0;
|
||||
|
||||
err_mem:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ltc2978_remove(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct ltc2978_data *data = to_ltc2978_data(info);
|
||||
|
||||
pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This is the driver that will be inserted */
|
||||
static struct i2c_driver ltc2978_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2978",
|
||||
},
|
||||
.probe = ltc2978_probe,
|
||||
.remove = ltc2978_remove,
|
||||
.id_table = ltc2978_id,
|
||||
};
|
||||
|
||||
static int __init ltc2978_init(void)
|
||||
{
|
||||
return i2c_add_driver(<c2978_driver);
|
||||
}
|
||||
|
||||
static void __exit ltc2978_exit(void)
|
||||
{
|
||||
i2c_del_driver(<c2978_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for LTC2978 and LTC3880");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(ltc2978_init);
|
||||
module_exit(ltc2978_exit);
|
@ -105,7 +105,8 @@ static int max16064_probe(struct i2c_client *client,
|
||||
|
||||
static int max16064_remove(struct i2c_client *client)
|
||||
{
|
||||
return pmbus_do_remove(client);
|
||||
pmbus_do_remove(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max16064_id[] = {
|
||||
|
@ -93,12 +93,14 @@ static int max34440_write_word_data(struct i2c_client *client, int page,
|
||||
|
||||
static int max34440_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
int mfg_status;
|
||||
|
||||
ret = pmbus_set_page(client, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (page >= 0) {
|
||||
ret = pmbus_set_page(client, page);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_IOUT:
|
||||
@ -224,7 +226,8 @@ static int max34440_probe(struct i2c_client *client,
|
||||
|
||||
static int max34440_remove(struct i2c_client *client)
|
||||
{
|
||||
return pmbus_do_remove(client);
|
||||
pmbus_do_remove(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max34440_id[] = {
|
||||
|
@ -45,7 +45,7 @@ static int max8688_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
int ret;
|
||||
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_VIRT_READ_VOUT_MAX:
|
||||
@ -101,8 +101,8 @@ static int max8688_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
int ret = 0;
|
||||
int mfg_status;
|
||||
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_STATUS_VOUT:
|
||||
@ -182,7 +182,8 @@ static int max8688_probe(struct i2c_client *client,
|
||||
|
||||
static int max8688_remove(struct i2c_client *client)
|
||||
{
|
||||
return pmbus_do_remove(client);
|
||||
pmbus_do_remove(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max8688_id[] = {
|
||||
|
@ -187,13 +187,12 @@ out:
|
||||
|
||||
static int pmbus_remove(struct i2c_client *client)
|
||||
{
|
||||
int ret;
|
||||
const struct pmbus_driver_info *info;
|
||||
|
||||
info = pmbus_get_driver_info(client);
|
||||
ret = pmbus_do_remove(client);
|
||||
pmbus_do_remove(client);
|
||||
kfree(info);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -205,10 +204,13 @@ static const struct i2c_device_id pmbus_id[] = {
|
||||
{"bmr451", 1},
|
||||
{"bmr453", 1},
|
||||
{"bmr454", 1},
|
||||
{"ltc2978", 8},
|
||||
{"ncp4200", 1},
|
||||
{"ncp4208", 1},
|
||||
{"pdt003", 1},
|
||||
{"pdt006", 1},
|
||||
{"pdt012", 1},
|
||||
{"pmbus", 0},
|
||||
{"udt020", 1},
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -134,8 +134,16 @@
|
||||
* Semantics:
|
||||
* Virtual registers are all word size.
|
||||
* READ registers are read-only; writes are either ignored or return an error.
|
||||
* RESET registers are read/write. Reading returns zero (used for detection),
|
||||
* writing any value causes the associated history to be reset.
|
||||
* RESET registers are read/write. Reading reset registers returns zero
|
||||
* (used for detection), writing any value causes the associated history to be
|
||||
* reset.
|
||||
* Virtual registers have to be handled in device specific driver code. Chip
|
||||
* driver code returns non-negative register values if a virtual register is
|
||||
* supported, or a negative error code if not. The chip driver may return
|
||||
* -ENODATA or any other error code in this case, though an error code other
|
||||
* than -ENODATA is handled more efficiently and thus preferred. Either case,
|
||||
* the calling PMBus core code will abort if the chip driver returns an error
|
||||
* code when reading or writing virtual registers.
|
||||
*/
|
||||
#define PMBUS_VIRT_BASE 0x100
|
||||
#define PMBUS_VIRT_READ_TEMP_MIN (PMBUS_VIRT_BASE + 0)
|
||||
@ -160,6 +168,9 @@
|
||||
#define PMBUS_VIRT_READ_IOUT_MIN (PMBUS_VIRT_BASE + 19)
|
||||
#define PMBUS_VIRT_READ_IOUT_MAX (PMBUS_VIRT_BASE + 20)
|
||||
#define PMBUS_VIRT_RESET_IOUT_HISTORY (PMBUS_VIRT_BASE + 21)
|
||||
#define PMBUS_VIRT_READ_TEMP2_MIN (PMBUS_VIRT_BASE + 22)
|
||||
#define PMBUS_VIRT_READ_TEMP2_MAX (PMBUS_VIRT_BASE + 23)
|
||||
#define PMBUS_VIRT_RESET_TEMP2_HISTORY (PMBUS_VIRT_BASE + 24)
|
||||
|
||||
/*
|
||||
* CAPABILITY
|
||||
@ -320,6 +331,12 @@ struct pmbus_driver_info {
|
||||
* The following functions map manufacturing specific register values
|
||||
* to PMBus standard register values. Specify only if mapping is
|
||||
* necessary.
|
||||
* Functions return the register value (read) or zero (write) if
|
||||
* successful. A return value of -ENODATA indicates that there is no
|
||||
* manufacturer specific register, but that a standard PMBus register
|
||||
* may exist. Any other negative return value indicates that the
|
||||
* register does not exist, and that no attempt should be made to read
|
||||
* the standard register.
|
||||
*/
|
||||
int (*read_byte_data)(struct i2c_client *client, int page, int reg);
|
||||
int (*read_word_data)(struct i2c_client *client, int page, int reg);
|
||||
@ -347,7 +364,7 @@ bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
|
||||
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
|
||||
int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
||||
struct pmbus_driver_info *info);
|
||||
int pmbus_do_remove(struct i2c_client *client);
|
||||
void pmbus_do_remove(struct i2c_client *client);
|
||||
const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client
|
||||
*client);
|
||||
|
||||
|
@ -160,7 +160,7 @@ int pmbus_set_page(struct i2c_client *client, u8 page)
|
||||
rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page);
|
||||
newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
|
||||
if (newpage != page)
|
||||
rv = -EINVAL;
|
||||
rv = -EIO;
|
||||
else
|
||||
data->currpage = page;
|
||||
}
|
||||
@ -229,7 +229,7 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
return status;
|
||||
}
|
||||
if (reg >= PMBUS_VIRT_BASE)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
return pmbus_write_word_data(client, page, reg, word);
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
return status;
|
||||
}
|
||||
if (reg >= PMBUS_VIRT_BASE)
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
return pmbus_read_word_data(client, page, reg);
|
||||
}
|
||||
|
||||
@ -316,11 +316,11 @@ static int pmbus_check_status_cml(struct i2c_client *client)
|
||||
{
|
||||
int status, status2;
|
||||
|
||||
status = pmbus_read_byte_data(client, -1, PMBUS_STATUS_BYTE);
|
||||
status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_BYTE);
|
||||
if (status < 0 || (status & PB_STATUS_CML)) {
|
||||
status2 = pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
|
||||
status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML);
|
||||
if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND))
|
||||
return -EINVAL;
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -371,8 +371,8 @@ static struct pmbus_data *pmbus_update_device(struct device *dev)
|
||||
|
||||
for (i = 0; i < info->pages; i++)
|
||||
data->status[PB_STATUS_BASE + i]
|
||||
= pmbus_read_byte_data(client, i,
|
||||
PMBUS_STATUS_BYTE);
|
||||
= _pmbus_read_byte_data(client, i,
|
||||
PMBUS_STATUS_BYTE);
|
||||
for (i = 0; i < info->pages; i++) {
|
||||
if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT))
|
||||
continue;
|
||||
@ -445,13 +445,8 @@ static long pmbus_reg2data_linear(struct pmbus_data *data,
|
||||
exponent = data->exponent;
|
||||
mantissa = (u16) sensor->data;
|
||||
} else { /* LINEAR11 */
|
||||
exponent = (sensor->data >> 11) & 0x001f;
|
||||
mantissa = sensor->data & 0x07ff;
|
||||
|
||||
if (exponent > 0x0f)
|
||||
exponent |= 0xffe0; /* sign extend exponent */
|
||||
if (mantissa > 0x03ff)
|
||||
mantissa |= 0xfffff800; /* sign extend mantissa */
|
||||
exponent = ((s16)sensor->data) >> 11;
|
||||
mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5;
|
||||
}
|
||||
|
||||
val = mantissa;
|
||||
@ -1401,7 +1396,42 @@ static const struct pmbus_limit_attr temp_limit_attrs[] = {
|
||||
}
|
||||
};
|
||||
|
||||
static const struct pmbus_limit_attr temp_limit_attrs23[] = {
|
||||
static const struct pmbus_limit_attr temp_limit_attrs2[] = {
|
||||
{
|
||||
.reg = PMBUS_UT_WARN_LIMIT,
|
||||
.low = true,
|
||||
.attr = "min",
|
||||
.alarm = "min_alarm",
|
||||
.sbit = PB_TEMP_UT_WARNING,
|
||||
}, {
|
||||
.reg = PMBUS_UT_FAULT_LIMIT,
|
||||
.low = true,
|
||||
.attr = "lcrit",
|
||||
.alarm = "lcrit_alarm",
|
||||
.sbit = PB_TEMP_UT_FAULT,
|
||||
}, {
|
||||
.reg = PMBUS_OT_WARN_LIMIT,
|
||||
.attr = "max",
|
||||
.alarm = "max_alarm",
|
||||
.sbit = PB_TEMP_OT_WARNING,
|
||||
}, {
|
||||
.reg = PMBUS_OT_FAULT_LIMIT,
|
||||
.attr = "crit",
|
||||
.alarm = "crit_alarm",
|
||||
.sbit = PB_TEMP_OT_FAULT,
|
||||
}, {
|
||||
.reg = PMBUS_VIRT_READ_TEMP2_MIN,
|
||||
.attr = "lowest",
|
||||
}, {
|
||||
.reg = PMBUS_VIRT_READ_TEMP2_MAX,
|
||||
.attr = "highest",
|
||||
}, {
|
||||
.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY,
|
||||
.attr = "reset_history",
|
||||
}
|
||||
};
|
||||
|
||||
static const struct pmbus_limit_attr temp_limit_attrs3[] = {
|
||||
{
|
||||
.reg = PMBUS_UT_WARN_LIMIT,
|
||||
.low = true,
|
||||
@ -1450,8 +1480,8 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
|
||||
.sfunc = PMBUS_HAVE_STATUS_TEMP,
|
||||
.sbase = PB_STATUS_TEMP_BASE,
|
||||
.gbit = PB_STATUS_TEMPERATURE,
|
||||
.limit = temp_limit_attrs23,
|
||||
.nlimit = ARRAY_SIZE(temp_limit_attrs23),
|
||||
.limit = temp_limit_attrs2,
|
||||
.nlimit = ARRAY_SIZE(temp_limit_attrs2),
|
||||
}, {
|
||||
.reg = PMBUS_READ_TEMPERATURE_3,
|
||||
.class = PSC_TEMPERATURE,
|
||||
@ -1462,8 +1492,8 @@ static const struct pmbus_sensor_attr temp_attributes[] = {
|
||||
.sfunc = PMBUS_HAVE_STATUS_TEMP,
|
||||
.sbase = PB_STATUS_TEMP_BASE,
|
||||
.gbit = PB_STATUS_TEMPERATURE,
|
||||
.limit = temp_limit_attrs23,
|
||||
.nlimit = ARRAY_SIZE(temp_limit_attrs23),
|
||||
.limit = temp_limit_attrs3,
|
||||
.nlimit = ARRAY_SIZE(temp_limit_attrs3),
|
||||
}
|
||||
};
|
||||
|
||||
@ -1593,10 +1623,10 @@ static void pmbus_find_attributes(struct i2c_client *client,
|
||||
static int pmbus_identify_common(struct i2c_client *client,
|
||||
struct pmbus_data *data)
|
||||
{
|
||||
int vout_mode = -1, exponent;
|
||||
int vout_mode = -1;
|
||||
|
||||
if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE))
|
||||
vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
vout_mode = _pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
|
||||
if (vout_mode >= 0 && vout_mode != 0xff) {
|
||||
/*
|
||||
* Not all chips support the VOUT_MODE command,
|
||||
@ -1607,11 +1637,7 @@ static int pmbus_identify_common(struct i2c_client *client,
|
||||
if (data->info->format[PSC_VOLTAGE_OUT] != linear)
|
||||
return -ENODEV;
|
||||
|
||||
exponent = vout_mode & 0x1f;
|
||||
/* and sign-extend it */
|
||||
if (exponent & 0x10)
|
||||
exponent |= ~0x1f;
|
||||
data->exponent = exponent;
|
||||
data->exponent = ((s8)(vout_mode << 3)) >> 3;
|
||||
break;
|
||||
case 1: /* VID mode */
|
||||
if (data->info->format[PSC_VOLTAGE_OUT] != vid)
|
||||
@ -1682,7 +1708,7 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
||||
if (info->pages <= 0 || info->pages > PMBUS_PAGES) {
|
||||
dev_err(&client->dev, "Bad number of PMBus pages: %d\n",
|
||||
info->pages);
|
||||
ret = -EINVAL;
|
||||
ret = -ENODEV;
|
||||
goto out_data;
|
||||
}
|
||||
|
||||
@ -1764,7 +1790,7 @@ out_data:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_do_probe);
|
||||
|
||||
int pmbus_do_remove(struct i2c_client *client)
|
||||
void pmbus_do_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pmbus_data *data = i2c_get_clientdata(client);
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
@ -1774,7 +1800,6 @@ int pmbus_do_remove(struct i2c_client *client)
|
||||
kfree(data->booleans);
|
||||
kfree(data->sensors);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pmbus_do_remove);
|
||||
|
||||
|
@ -74,8 +74,8 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
|
||||
switch (reg) {
|
||||
case PMBUS_FAN_CONFIG_12:
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = ucd9000_get_fan_config(client, 0);
|
||||
if (ret < 0)
|
||||
@ -88,8 +88,8 @@ static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
ret = fan_config;
|
||||
break;
|
||||
case PMBUS_FAN_CONFIG_34:
|
||||
if (page)
|
||||
return -EINVAL;
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
ret = ucd9000_get_fan_config(client, 2);
|
||||
if (ret < 0)
|
||||
@ -239,13 +239,12 @@ out:
|
||||
|
||||
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);
|
||||
pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -171,13 +171,12 @@ out:
|
||||
|
||||
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);
|
||||
pmbus_do_remove(client);
|
||||
kfree(info);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
256
drivers/hwmon/pmbus/zl6100.c
Normal file
256
drivers/hwmon/pmbus/zl6100.c
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Hardware monitoring driver for ZL6100 and compatibles
|
||||
*
|
||||
* 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/ktime.h>
|
||||
#include <linux/delay.h>
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { zl2004, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105 };
|
||||
|
||||
struct zl6100_data {
|
||||
int id;
|
||||
ktime_t access; /* chip access time */
|
||||
struct pmbus_driver_info info;
|
||||
};
|
||||
|
||||
#define to_zl6100_data(x) container_of(x, struct zl6100_data, info)
|
||||
|
||||
#define ZL6100_DEVICE_ID 0xe4
|
||||
|
||||
#define ZL6100_WAIT_TIME 1000 /* uS */
|
||||
|
||||
static ushort delay = ZL6100_WAIT_TIME;
|
||||
module_param(delay, ushort, 0644);
|
||||
MODULE_PARM_DESC(delay, "Delay between chip accesses in uS");
|
||||
|
||||
/* Some chips need a delay between accesses */
|
||||
static inline void zl6100_wait(const struct zl6100_data *data)
|
||||
{
|
||||
if (delay) {
|
||||
s64 delta = ktime_us_delta(ktime_get(), data->access);
|
||||
if (delta < delay)
|
||||
udelay(delay - delta);
|
||||
}
|
||||
}
|
||||
|
||||
static int zl6100_read_word_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct zl6100_data *data = to_zl6100_data(info);
|
||||
int ret;
|
||||
|
||||
if (page || reg >= PMBUS_VIRT_BASE)
|
||||
return -ENXIO;
|
||||
|
||||
zl6100_wait(data);
|
||||
ret = pmbus_read_word_data(client, page, reg);
|
||||
data->access = ktime_get();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct zl6100_data *data = to_zl6100_data(info);
|
||||
int ret;
|
||||
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
zl6100_wait(data);
|
||||
ret = pmbus_read_byte_data(client, page, reg);
|
||||
data->access = ktime_get();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int zl6100_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
u16 word)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct zl6100_data *data = to_zl6100_data(info);
|
||||
int ret;
|
||||
|
||||
if (page || reg >= PMBUS_VIRT_BASE)
|
||||
return -ENXIO;
|
||||
|
||||
zl6100_wait(data);
|
||||
ret = pmbus_write_word_data(client, page, reg, word);
|
||||
data->access = ktime_get();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int zl6100_write_byte(struct i2c_client *client, int page, u8 value)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
struct zl6100_data *data = to_zl6100_data(info);
|
||||
int ret;
|
||||
|
||||
if (page > 0)
|
||||
return -ENXIO;
|
||||
|
||||
zl6100_wait(data);
|
||||
ret = pmbus_write_byte(client, page, value);
|
||||
data->access = ktime_get();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id zl6100_id[] = {
|
||||
{"zl2004", zl2004},
|
||||
{"zl2006", zl2006},
|
||||
{"zl2008", zl2008},
|
||||
{"zl2105", zl2105},
|
||||
{"zl2106", zl2106},
|
||||
{"zl6100", zl6100},
|
||||
{"zl6105", zl6105},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, zl6100_id);
|
||||
|
||||
static int zl6100_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct zl6100_data *data;
|
||||
struct pmbus_driver_info *info;
|
||||
u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
const struct i2c_device_id *mid;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_READ_BLOCK_DATA))
|
||||
return -ENODEV;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID,
|
||||
device_id);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "Failed to read device ID\n");
|
||||
return ret;
|
||||
}
|
||||
device_id[ret] = '\0';
|
||||
dev_info(&client->dev, "Device ID %s\n", device_id);
|
||||
|
||||
mid = NULL;
|
||||
for (mid = zl6100_id; mid->name[0]; mid++) {
|
||||
if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
|
||||
break;
|
||||
}
|
||||
if (!mid->name[0]) {
|
||||
dev_err(&client->dev, "Unsupported device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (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 zl6100_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->id = mid->driver_data;
|
||||
|
||||
/*
|
||||
* ZL2008, ZL2105, and ZL6100 are known to require a wait time
|
||||
* between I2C accesses. ZL2004 and ZL6105 are known to be safe.
|
||||
*
|
||||
* Only clear the wait time for chips known to be safe. The wait time
|
||||
* can be cleared later for additional chips if tests show that it
|
||||
* is not needed (in other words, better be safe than sorry).
|
||||
*/
|
||||
if (data->id == zl2004 || data->id == zl6105)
|
||||
delay = 0;
|
||||
|
||||
/*
|
||||
* Since there was a direct I2C device access above, wait before
|
||||
* accessing the chip again.
|
||||
* Set the timestamp, wait, then set it again. This should provide
|
||||
* enough buffer time to be safe.
|
||||
*/
|
||||
data->access = ktime_get();
|
||||
zl6100_wait(data);
|
||||
data->access = ktime_get();
|
||||
|
||||
info = &data->info;
|
||||
|
||||
info->pages = 1;
|
||||
info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT
|
||||
| PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT
|
||||
| PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT
|
||||
| PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP;
|
||||
|
||||
info->read_word_data = zl6100_read_word_data;
|
||||
info->read_byte_data = zl6100_read_byte_data;
|
||||
info->write_word_data = zl6100_write_word_data;
|
||||
info->write_byte = zl6100_write_byte;
|
||||
|
||||
ret = pmbus_do_probe(client, mid, info);
|
||||
if (ret)
|
||||
goto err_mem;
|
||||
return 0;
|
||||
|
||||
err_mem:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int zl6100_remove(struct i2c_client *client)
|
||||
{
|
||||
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||
const struct zl6100_data *data = to_zl6100_data(info);
|
||||
|
||||
pmbus_do_remove(client);
|
||||
kfree(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver zl6100_driver = {
|
||||
.driver = {
|
||||
.name = "zl6100",
|
||||
},
|
||||
.probe = zl6100_probe,
|
||||
.remove = zl6100_remove,
|
||||
.id_table = zl6100_id,
|
||||
};
|
||||
|
||||
static int __init zl6100_init(void)
|
||||
{
|
||||
return i2c_add_driver(&zl6100_driver);
|
||||
}
|
||||
|
||||
static void __exit zl6100_exit(void)
|
||||
{
|
||||
i2c_del_driver(&zl6100_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Guenter Roeck");
|
||||
MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles");
|
||||
MODULE_LICENSE("GPL");
|
||||
module_init(zl6100_init);
|
||||
module_exit(zl6100_exit);
|
@ -197,6 +197,9 @@ static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0, 0x152, 0x252, 0 };
|
||||
#define W83627EHF_REG_ALARM2 0x45A
|
||||
#define W83627EHF_REG_ALARM3 0x45B
|
||||
|
||||
#define W83627EHF_REG_CASEOPEN_DET 0x42 /* SMI STATUS #2 */
|
||||
#define W83627EHF_REG_CASEOPEN_CLR 0x46 /* SMI MASK #3 */
|
||||
|
||||
/* SmartFan registers */
|
||||
#define W83627EHF_REG_FAN_STEPUP_TIME 0x0f
|
||||
#define W83627EHF_REG_FAN_STEPDOWN_TIME 0x0e
|
||||
@ -316,7 +319,7 @@ static const char *const nct6776_temp_label[] = {
|
||||
|
||||
#define NUM_REG_TEMP ARRAY_SIZE(NCT6775_REG_TEMP)
|
||||
|
||||
static inline int is_word_sized(u16 reg)
|
||||
static int is_word_sized(u16 reg)
|
||||
{
|
||||
return ((((reg & 0xff00) == 0x100
|
||||
|| (reg & 0xff00) == 0x200)
|
||||
@ -385,23 +388,6 @@ div_from_reg(u8 reg)
|
||||
return 1 << reg;
|
||||
}
|
||||
|
||||
static inline int
|
||||
temp_from_reg(u16 reg, s16 regval)
|
||||
{
|
||||
if (is_word_sized(reg))
|
||||
return LM75_TEMP_FROM_REG(regval);
|
||||
return ((s8)regval) * 1000;
|
||||
}
|
||||
|
||||
static inline u16
|
||||
temp_to_reg(u16 reg, long temp)
|
||||
{
|
||||
if (is_word_sized(reg))
|
||||
return LM75_TEMP_TO_REG(temp);
|
||||
return (s8)DIV_ROUND_CLOSEST(SENSORS_LIMIT(temp, -127000, 128000),
|
||||
1000);
|
||||
}
|
||||
|
||||
/* Some of analog inputs have internal scaling (2x), 8mV is ADC LSB */
|
||||
|
||||
static u8 scale_in[10] = { 8, 8, 16, 16, 8, 8, 8, 16, 16, 8 };
|
||||
@ -469,6 +455,7 @@ struct w83627ehf_data {
|
||||
s16 temp_max[9];
|
||||
s16 temp_max_hyst[9];
|
||||
u32 alarms;
|
||||
u8 caseopen;
|
||||
|
||||
u8 pwm_mode[4]; /* 0->DC variable voltage, 1->PWM variable duty cycle */
|
||||
u8 pwm_enable[4]; /* 1->manual
|
||||
@ -557,6 +544,26 @@ static int w83627ehf_write_value(struct w83627ehf_data *data, u16 reg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We left-align 8-bit temperature values to make the code simpler */
|
||||
static u16 w83627ehf_read_temp(struct w83627ehf_data *data, u16 reg)
|
||||
{
|
||||
u16 res;
|
||||
|
||||
res = w83627ehf_read_value(data, reg);
|
||||
if (!is_word_sized(reg))
|
||||
res <<= 8;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int w83627ehf_write_temp(struct w83627ehf_data *data, u16 reg,
|
||||
u16 value)
|
||||
{
|
||||
if (!is_word_sized(reg))
|
||||
value >>= 8;
|
||||
return w83627ehf_write_value(data, reg, value);
|
||||
}
|
||||
|
||||
/* This function assumes that the caller holds data->update_lock */
|
||||
static void nct6775_write_fan_div(struct w83627ehf_data *data, int nr)
|
||||
{
|
||||
@ -771,6 +778,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
|
||||
|
||||
/* Measured voltages and limits */
|
||||
for (i = 0; i < data->in_num; i++) {
|
||||
if ((i == 6) && data->in6_skip)
|
||||
continue;
|
||||
|
||||
data->in[i] = w83627ehf_read_value(data,
|
||||
W83627EHF_REG_IN(i));
|
||||
data->in_min[i] = w83627ehf_read_value(data,
|
||||
@ -855,15 +865,15 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
|
||||
for (i = 0; i < NUM_REG_TEMP; i++) {
|
||||
if (!(data->have_temp & (1 << i)))
|
||||
continue;
|
||||
data->temp[i] = w83627ehf_read_value(data,
|
||||
data->temp[i] = w83627ehf_read_temp(data,
|
||||
data->reg_temp[i]);
|
||||
if (data->reg_temp_over[i])
|
||||
data->temp_max[i]
|
||||
= w83627ehf_read_value(data,
|
||||
= w83627ehf_read_temp(data,
|
||||
data->reg_temp_over[i]);
|
||||
if (data->reg_temp_hyst[i])
|
||||
data->temp_max_hyst[i]
|
||||
= w83627ehf_read_value(data,
|
||||
= w83627ehf_read_temp(data,
|
||||
data->reg_temp_hyst[i]);
|
||||
}
|
||||
|
||||
@ -874,6 +884,9 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
|
||||
(w83627ehf_read_value(data,
|
||||
W83627EHF_REG_ALARM3) << 16);
|
||||
|
||||
data->caseopen = w83627ehf_read_value(data,
|
||||
W83627EHF_REG_CASEOPEN_DET);
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
}
|
||||
@ -1156,8 +1169,7 @@ show_##reg(struct device *dev, struct device_attribute *attr, \
|
||||
struct sensor_device_attribute *sensor_attr = \
|
||||
to_sensor_dev_attr(attr); \
|
||||
int nr = sensor_attr->index; \
|
||||
return sprintf(buf, "%d\n", \
|
||||
temp_from_reg(data->addr[nr], data->reg[nr])); \
|
||||
return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->reg[nr])); \
|
||||
}
|
||||
show_temp_reg(reg_temp, temp);
|
||||
show_temp_reg(reg_temp_over, temp_max);
|
||||
@ -1178,9 +1190,8 @@ store_##reg(struct device *dev, struct device_attribute *attr, \
|
||||
if (err < 0) \
|
||||
return err; \
|
||||
mutex_lock(&data->update_lock); \
|
||||
data->reg[nr] = temp_to_reg(data->addr[nr], val); \
|
||||
w83627ehf_write_value(data, data->addr[nr], \
|
||||
data->reg[nr]); \
|
||||
data->reg[nr] = LM75_TEMP_TO_REG(val); \
|
||||
w83627ehf_write_temp(data, data->addr[nr], data->reg[nr]); \
|
||||
mutex_unlock(&data->update_lock); \
|
||||
return count; \
|
||||
}
|
||||
@ -1655,6 +1666,48 @@ show_vid(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
}
|
||||
static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL);
|
||||
|
||||
|
||||
/* Case open detection */
|
||||
|
||||
static ssize_t
|
||||
show_caseopen(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct w83627ehf_data *data = w83627ehf_update_device(dev);
|
||||
|
||||
return sprintf(buf, "%d\n",
|
||||
!!(data->caseopen & to_sensor_dev_attr_2(attr)->index));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
clear_caseopen(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct w83627ehf_data *data = dev_get_drvdata(dev);
|
||||
unsigned long val;
|
||||
u16 reg, mask;
|
||||
|
||||
if (strict_strtoul(buf, 10, &val) || val != 0)
|
||||
return -EINVAL;
|
||||
|
||||
mask = to_sensor_dev_attr_2(attr)->nr;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
reg = w83627ehf_read_value(data, W83627EHF_REG_CASEOPEN_CLR);
|
||||
w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg | mask);
|
||||
w83627ehf_write_value(data, W83627EHF_REG_CASEOPEN_CLR, reg & ~mask);
|
||||
data->valid = 0; /* Force cache refresh */
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct sensor_device_attribute_2 sda_caseopen[] = {
|
||||
SENSOR_ATTR_2(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen,
|
||||
clear_caseopen, 0x80, 0x10),
|
||||
SENSOR_ATTR_2(intrusion1_alarm, S_IWUSR | S_IRUGO, show_caseopen,
|
||||
clear_caseopen, 0x40, 0x40),
|
||||
};
|
||||
|
||||
/*
|
||||
* Driver and device management
|
||||
*/
|
||||
@ -1711,6 +1764,9 @@ static void w83627ehf_device_remove_files(struct device *dev)
|
||||
device_remove_file(dev, &sda_temp_type[i].dev_attr);
|
||||
}
|
||||
|
||||
device_remove_file(dev, &sda_caseopen[0].dev_attr);
|
||||
device_remove_file(dev, &sda_caseopen[1].dev_attr);
|
||||
|
||||
device_remove_file(dev, &dev_attr_name);
|
||||
device_remove_file(dev, &dev_attr_cpu0_vid);
|
||||
}
|
||||
@ -1789,13 +1845,78 @@ static void w82627ehf_swap_tempreg(struct w83627ehf_data *data,
|
||||
data->reg_temp_config[r2] = tmp;
|
||||
}
|
||||
|
||||
static void __devinit
|
||||
w83627ehf_check_fan_inputs(const struct w83627ehf_sio_data *sio_data,
|
||||
struct w83627ehf_data *data)
|
||||
{
|
||||
int fan3pin, fan4pin, fan4min, fan5pin, regval;
|
||||
|
||||
superio_enter(sio_data->sioreg);
|
||||
|
||||
/* fan4 and fan5 share some pins with the GPIO and serial flash */
|
||||
if (sio_data->kind == nct6775) {
|
||||
/* On NCT6775, fan4 shares pins with the fdc interface */
|
||||
fan3pin = 1;
|
||||
fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
|
||||
fan4min = 0;
|
||||
fan5pin = 0;
|
||||
} else if (sio_data->kind == nct6776) {
|
||||
fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
|
||||
fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01);
|
||||
fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02);
|
||||
fan4min = fan4pin;
|
||||
} else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
|
||||
fan3pin = 1;
|
||||
fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40;
|
||||
fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20;
|
||||
fan4min = fan4pin;
|
||||
} else {
|
||||
fan3pin = 1;
|
||||
fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06);
|
||||
fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02);
|
||||
fan4min = fan4pin;
|
||||
}
|
||||
|
||||
superio_exit(sio_data->sioreg);
|
||||
|
||||
data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
|
||||
data->has_fan |= (fan3pin << 2);
|
||||
data->has_fan_min |= (fan3pin << 2);
|
||||
|
||||
if (sio_data->kind == nct6775 || sio_data->kind == nct6776) {
|
||||
/*
|
||||
* NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1
|
||||
* register
|
||||
*/
|
||||
data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
|
||||
data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
|
||||
} else {
|
||||
/*
|
||||
* It looks like fan4 and fan5 pins can be alternatively used
|
||||
* as fan on/off switches, but fan5 control is write only :/
|
||||
* We assume that if the serial interface is disabled, designers
|
||||
* connected fan5 as input unless they are emitting log 1, which
|
||||
* is not the default.
|
||||
*/
|
||||
regval = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1);
|
||||
if ((regval & (1 << 2)) && fan4pin) {
|
||||
data->has_fan |= (1 << 3);
|
||||
data->has_fan_min |= (1 << 3);
|
||||
}
|
||||
if (!(regval & (1 << 1)) && fan5pin) {
|
||||
data->has_fan |= (1 << 4);
|
||||
data->has_fan_min |= (1 << 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __devinit w83627ehf_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct w83627ehf_sio_data *sio_data = dev->platform_data;
|
||||
struct w83627ehf_data *data;
|
||||
struct resource *res;
|
||||
u8 fan3pin, fan4pin, fan4min, fan5pin, en_vrm10;
|
||||
u8 en_vrm10;
|
||||
int i, err = 0;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
@ -2080,30 +2201,6 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
/* fan4 and fan5 share some pins with the GPIO and serial flash */
|
||||
if (sio_data->kind == nct6775) {
|
||||
/* On NCT6775, fan4 shares pins with the fdc interface */
|
||||
fan3pin = 1;
|
||||
fan4pin = !(superio_inb(sio_data->sioreg, 0x2A) & 0x80);
|
||||
fan4min = 0;
|
||||
fan5pin = 0;
|
||||
} else if (sio_data->kind == nct6776) {
|
||||
fan3pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x40);
|
||||
fan4pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x01);
|
||||
fan5pin = !!(superio_inb(sio_data->sioreg, 0x1C) & 0x02);
|
||||
fan4min = fan4pin;
|
||||
} else if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) {
|
||||
fan3pin = 1;
|
||||
fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40;
|
||||
fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20;
|
||||
fan4min = fan4pin;
|
||||
} else {
|
||||
fan3pin = 1;
|
||||
fan4pin = !(superio_inb(sio_data->sioreg, 0x29) & 0x06);
|
||||
fan5pin = !(superio_inb(sio_data->sioreg, 0x24) & 0x02);
|
||||
fan4min = fan4pin;
|
||||
}
|
||||
|
||||
if (fan_debounce &&
|
||||
(sio_data->kind == nct6775 || sio_data->kind == nct6776)) {
|
||||
u8 tmp;
|
||||
@ -2121,34 +2218,7 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
|
||||
|
||||
superio_exit(sio_data->sioreg);
|
||||
|
||||
/* It looks like fan4 and fan5 pins can be alternatively used
|
||||
as fan on/off switches, but fan5 control is write only :/
|
||||
We assume that if the serial interface is disabled, designers
|
||||
connected fan5 as input unless they are emitting log 1, which
|
||||
is not the default. */
|
||||
|
||||
data->has_fan = data->has_fan_min = 0x03; /* fan1 and fan2 */
|
||||
|
||||
data->has_fan |= (fan3pin << 2);
|
||||
data->has_fan_min |= (fan3pin << 2);
|
||||
|
||||
/*
|
||||
* NCT6775F and NCT6776F don't have the W83627EHF_REG_FANDIV1 register
|
||||
*/
|
||||
if (sio_data->kind == nct6775 || sio_data->kind == nct6776) {
|
||||
data->has_fan |= (fan4pin << 3) | (fan5pin << 4);
|
||||
data->has_fan_min |= (fan4min << 3) | (fan5pin << 4);
|
||||
} else {
|
||||
i = w83627ehf_read_value(data, W83627EHF_REG_FANDIV1);
|
||||
if ((i & (1 << 2)) && fan4pin) {
|
||||
data->has_fan |= (1 << 3);
|
||||
data->has_fan_min |= (1 << 3);
|
||||
}
|
||||
if (!(i & (1 << 1)) && fan5pin) {
|
||||
data->has_fan |= (1 << 4);
|
||||
data->has_fan_min |= (1 << 4);
|
||||
}
|
||||
}
|
||||
w83627ehf_check_fan_inputs(sio_data, data);
|
||||
|
||||
/* Read fan clock dividers immediately */
|
||||
w83627ehf_update_fan_div_common(dev, data);
|
||||
@ -2269,6 +2339,16 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev)
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
err = device_create_file(dev, &sda_caseopen[0].dev_attr);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
|
||||
if (sio_data->kind == nct6776) {
|
||||
err = device_create_file(dev, &sda_caseopen[1].dev_attr);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
}
|
||||
|
||||
err = device_create_file(dev, &dev_attr_name);
|
||||
if (err)
|
||||
goto exit_remove;
|
||||
|
83
include/linux/platform_data/exynos4_tmu.h
Normal file
83
include/linux/platform_data/exynos4_tmu.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* exynos4_tmu.h - Samsung EXYNOS4 TMU (Thermal Management Unit)
|
||||
*
|
||||
* Copyright (C) 2011 Samsung Electronics
|
||||
* Donggeun Kim <dg77.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_EXYNOS4_TMU_H
|
||||
#define _LINUX_EXYNOS4_TMU_H
|
||||
|
||||
enum calibration_type {
|
||||
TYPE_ONE_POINT_TRIMMING,
|
||||
TYPE_TWO_POINT_TRIMMING,
|
||||
TYPE_NONE,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct exynos4_tmu_platform_data
|
||||
* @threshold: basic temperature for generating interrupt
|
||||
* 25 <= threshold <= 125 [unit: degree Celsius]
|
||||
* @trigger_levels: array for each interrupt levels
|
||||
* [unit: degree Celsius]
|
||||
* 0: temperature for trigger_level0 interrupt
|
||||
* condition for trigger_level0 interrupt:
|
||||
* current temperature > threshold + trigger_levels[0]
|
||||
* 1: temperature for trigger_level1 interrupt
|
||||
* condition for trigger_level1 interrupt:
|
||||
* current temperature > threshold + trigger_levels[1]
|
||||
* 2: temperature for trigger_level2 interrupt
|
||||
* condition for trigger_level2 interrupt:
|
||||
* current temperature > threshold + trigger_levels[2]
|
||||
* 3: temperature for trigger_level3 interrupt
|
||||
* condition for trigger_level3 interrupt:
|
||||
* current temperature > threshold + trigger_levels[3]
|
||||
* @trigger_level0_en:
|
||||
* 1 = enable trigger_level0 interrupt,
|
||||
* 0 = disable trigger_level0 interrupt
|
||||
* @trigger_level1_en:
|
||||
* 1 = enable trigger_level1 interrupt,
|
||||
* 0 = disable trigger_level1 interrupt
|
||||
* @trigger_level2_en:
|
||||
* 1 = enable trigger_level2 interrupt,
|
||||
* 0 = disable trigger_level2 interrupt
|
||||
* @trigger_level3_en:
|
||||
* 1 = enable trigger_level3 interrupt,
|
||||
* 0 = disable trigger_level3 interrupt
|
||||
* @gain: gain of amplifier in the positive-TC generator block
|
||||
* 0 <= gain <= 15
|
||||
* @reference_voltage: reference voltage of amplifier
|
||||
* in the positive-TC generator block
|
||||
* 0 <= reference_voltage <= 31
|
||||
* @cal_type: calibration type for temperature
|
||||
*
|
||||
* This structure is required for configuration of exynos4_tmu driver.
|
||||
*/
|
||||
struct exynos4_tmu_platform_data {
|
||||
u8 threshold;
|
||||
u8 trigger_levels[4];
|
||||
bool trigger_level0_en;
|
||||
bool trigger_level1_en;
|
||||
bool trigger_level2_en;
|
||||
bool trigger_level3_en;
|
||||
|
||||
u8 gain;
|
||||
u8 reference_voltage;
|
||||
|
||||
enum calibration_type cal_type;
|
||||
};
|
||||
#endif /* _LINUX_EXYNOS4_TMU_H */
|
Loading…
Reference in New Issue
Block a user